Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bbfd1083b8 | |||
| faa5482c7a | |||
| 93d794e61b |
+4
-4
@@ -25,10 +25,10 @@
|
||||
will not match the sender it came from. Ids most SMSCs issue are digits or hex and are unaffected.
|
||||
- A `U+0000` inside a C-Octet String — `source_addr`, `message_id`, `system_id` and the rest — is
|
||||
refused. An Octet String carries a NULL as before.
|
||||
- A number with no decimal spelling — `NaN`, `Infinity`, `-Infinity` — is refused where a text field
|
||||
takes one. `sendSms({ from: NaN })` put the literal sender `NaN` on the wire and reported the send
|
||||
as successful; `message_id`, `source_addr` and every other text field took such a number the same
|
||||
way. A finite number still writes its decimals, so `message_id: 123` is unchanged.
|
||||
- A non-finite number — `NaN`, `Infinity`, `-Infinity` — is refused where a text field takes one.
|
||||
`sendSms({ from: NaN })` put the literal sender `NaN` on the wire and reported the send as
|
||||
successful; `message_id`, `source_addr` and every other text field took such a number the same way.
|
||||
Any other number goes out as `String()` spells it, so `message_id: 123` is unchanged.
|
||||
|
||||
## 0.5.0
|
||||
|
||||
|
||||
+6
-1
@@ -38,6 +38,11 @@ export function paramNumber(value: ParamValue | undefined, fallback: number): nu
|
||||
return typeof value === 'number' ? value : fallback;
|
||||
}
|
||||
|
||||
/** Spells a refused value for the caller who wrote it; JSON spells NaN and the infinities `null`. */
|
||||
export function valueText(value: unknown): string {
|
||||
return typeof value === 'number' ? String(value) : JSON.stringify(value);
|
||||
}
|
||||
|
||||
function outOfRange(buffer: Buffer, offset: number, needed: number): Error | undefined {
|
||||
if (offset < 0 || needed < 0 || offset + needed > buffer.length) {
|
||||
return new Error(
|
||||
@@ -50,7 +55,7 @@ function outOfRange(buffer: Buffer, offset: number, needed: number): Error | und
|
||||
|
||||
function wantInt(value: ParamValue, max: number): Result<{ int: number }> {
|
||||
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
||||
return { err: new Error(`Expected an integer, got ${JSON.stringify(value)}`) };
|
||||
return { err: new Error(`Expected an integer, got ${valueText(value)}`) };
|
||||
}
|
||||
|
||||
if (value < 0 || value > max) {
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ import { cmds, commandNameById, respNameFor } from './defs/commands.ts';
|
||||
import { hasUdh } from './defs/constants.ts';
|
||||
import { decodeMessage, encodeBody } from './message.ts';
|
||||
import { errorNameById, errors, isErrorName } from './defs/errors.ts';
|
||||
import { paramNumber } from './defs/types.ts';
|
||||
import { paramNumber, valueText } from './defs/types.ts';
|
||||
import { tagIdOf, tlvDefault, tlvs, tlvsById, writeTlvs } from './defs/tlvs.ts';
|
||||
|
||||
/** The highest sequence number this library hands out; SMPP 3.4 4.7.1 reserves 0x7fffffff. */
|
||||
@@ -234,7 +234,7 @@ function buildPdu(
|
||||
}
|
||||
|
||||
if (!Number.isInteger(seqNr) || seqNr < 0 || seqNr > maxWireSeqNr) {
|
||||
return { err: new Error(`Invalid seqNr: ${JSON.stringify(seqNr)}`) };
|
||||
return { err: new Error(`Invalid seqNr: ${valueText(seqNr)}`) };
|
||||
}
|
||||
|
||||
const built = buildBody(definition, cmdName, cmdStatus, params, tlvs);
|
||||
|
||||
+6
-2
@@ -51,6 +51,11 @@ describe('header', () => {
|
||||
assert.equal(decode(encode({ cmdName: 'enquire_link', seqNr: 0x80000001 })).seqNr, 0x80000001);
|
||||
assert.equal(decode(encode({ cmdName: 'enquire_link', seqNr: 0xFFFFFFFF })).seqNr, 0xFFFFFFFF);
|
||||
assert.ok(objToPdu({ cmdName: 'submit_sm', seqNr: 0x100000000 }).err instanceof Error);
|
||||
|
||||
const notANumber = objToPdu({ cmdName: 'submit_sm', seqNr: NaN });
|
||||
|
||||
assert.ok(notANumber.err instanceof Error);
|
||||
assert.match(notANumber.err.message, /NaN/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -144,7 +149,7 @@ describe('parsing real PDUs', () => {
|
||||
assert.equal(decode(pdu).params.source_addr, 'Kaffeé');
|
||||
});
|
||||
|
||||
test('refuses an address the field cannot carry rather than truncating it', () => {
|
||||
test('refuses an address the field cannot carry rather than truncating or coercing it', () => {
|
||||
const smuggled = objToPdu({
|
||||
cmdName: 'submit_sm',
|
||||
params: { destination_addr: '46709771337', source_addr: '46701113311\u0000EVIL' },
|
||||
@@ -154,7 +159,6 @@ describe('parsing real PDUs', () => {
|
||||
assert.equal(smuggled.buffer, undefined);
|
||||
assert.ok(objToPdu({ cmdName: 'deliver_sm', params: { source_addr: '一' } }).err instanceof Error);
|
||||
|
||||
// Coercing these spells the senders "NaN" and "Infinity", which a peer reads as those letters.
|
||||
assert.ok(objToPdu({ cmdName: 'deliver_sm', params: { source_addr: NaN } }).err instanceof Error);
|
||||
assert.ok(objToPdu({ cmdName: 'deliver_sm', params: { source_addr: Infinity } }).err instanceof Error);
|
||||
});
|
||||
|
||||
+9
-1
@@ -39,6 +39,12 @@ describe('integers', () => {
|
||||
assert.ok(types.int8.write(-1, Buffer.alloc(1), 0).err instanceof Error);
|
||||
assert.ok(types.int16.write(1.5, Buffer.alloc(2), 0).err instanceof Error);
|
||||
assert.ok(types.int8.write('nope', Buffer.alloc(1), 0).err instanceof Error);
|
||||
|
||||
const notANumber = types.int8.write(NaN, Buffer.alloc(1), 0);
|
||||
|
||||
// JSON spells NaN and the infinities `null`, which is a value the caller never wrote.
|
||||
assert.ok(notANumber.err instanceof Error);
|
||||
assert.match(notANumber.err.message, /NaN/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,7 +118,7 @@ describe('cstring (C-Octet String)', () => {
|
||||
assert.deepEqual(target, encoded);
|
||||
});
|
||||
|
||||
test('coerces a numeric value to its decimal string, and refuses one with no decimals', () => {
|
||||
test('coerces a numeric value to its decimal string, and refuses a non-finite one', () => {
|
||||
const target = Buffer.alloc(4);
|
||||
|
||||
types.cstring.write(123, target, 0);
|
||||
@@ -124,7 +130,9 @@ describe('cstring (C-Octet String)', () => {
|
||||
assert.ok(types.cstring.size(value).err instanceof Error, String(value));
|
||||
assert.ok(types.cstring.write(value, Buffer.alloc(9), 0).err instanceof Error, String(value));
|
||||
assert.ok(types.string.write(value, Buffer.alloc(9), 0).err instanceof Error, String(value));
|
||||
assert.ok(types.buffer.write(value, Buffer.alloc(9), 0).err instanceof Error, String(value));
|
||||
assert.ok(types.tlv.string.write(value, Buffer.alloc(9), 0).err instanceof Error, String(value));
|
||||
assert.ok(types.tlv.cstring.write(value, Buffer.alloc(9), 0).err instanceof Error, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -216,6 +216,28 @@ and is also what the panel ranked hardest — two methods, one answer.
|
||||
send — and not this one. If goal 2 forbids reporting on a message we cannot fully account for,
|
||||
that is the answer; it is stated in no file today either way.
|
||||
|
||||
- [ ] **Return an `err` where `message` is not a string, rather than throwing.**
|
||||
`sendSms({ message: undefined })` — a forgotten property — reaches `value.replace()` in
|
||||
`defs/encodings.ts` through the alphabet detection `checkOptions()` runs, and the `TypeError`
|
||||
escapes `submitSms()` into the caller's process; `NaN` and `12345` do the same. README promises
|
||||
"Never throws. Every fallible call resolves to `{ err?, … }`" and AGENTS.md hard rule 1 says it
|
||||
again, so the docs are false for the likeliest caller mistake there is. From the stability
|
||||
review of #18.
|
||||
|
||||
- [ ] **Derive `sm_length` for a numeric body, or refuse one.** `resolveBody()` in `pdu.ts` reads the
|
||||
length only where the body is a Buffer or a string, so
|
||||
`objToPdu({ cmdName: 'submit_sm', params: { short_message: 12345 } })` writes `sm_length: 0`,
|
||||
then five octets after it, and reports success — and this library's own parser refuses what it
|
||||
built, as "TLV 12594 runs past the end of the PDU". Goals 1 and 2. From the stability review
|
||||
of #18.
|
||||
|
||||
- [ ] **Settle which numbers may spell a text field, and refuse the rest.** `wantText()` takes every
|
||||
finite number through `String()`, so `message_id: 1e21` writes `1e+21` and `from: 0.1 + 0.2`
|
||||
writes `0.30000000000000004` — neither is the id or the address the caller meant, and both are
|
||||
reported as sent. The numeric branch exists for a digit sequence (`message_id: 123`): either
|
||||
narrow it to one, or record why exponential notation may go on the wire. Goal 3, and the open
|
||||
half of the non-finite guard #18 shipped. From the stability review of #18.
|
||||
|
||||
### Throughput — goal 6, and the default window is where we are slowest
|
||||
|
||||
- [ ] **Close the gap to jsmpp at `maxOutstanding: 10`.** Measured 2026-09-20 against the same sink,
|
||||
|
||||
Reference in New Issue
Block a user