Refuse a non-finite number where a text field coerces one #18

Merged
lilleman merged 9 commits from worktree-non-finite-text into main 2026-09-21 10:46:52 +02:00
3 changed files with 19 additions and 7 deletions
Showing only changes of commit 93d794e61b - Show all commits
+4 -4
View File
@@ -25,10 +25,10 @@
will not match the sender it came from. Ids most SMSCs issue are digits or hex and are unaffected. 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 - 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. refused. An Octet String carries a NULL as before.
- A number with no decimal spelling — `NaN`, `Infinity`, `-Infinity` — is refused where a text field - A non-finite number — `NaN`, `Infinity`, `-Infinity` — is refused where a text field takes one.
takes one. `sendSms({ from: NaN })` put the literal sender `NaN` on the wire and reported the send `sendSms({ from: NaN })` put the literal sender `NaN` on the wire and reported the send as
as successful; `message_id`, `source_addr` and every other text field took such a number the same successful; `message_id`, `source_addr` and every other text field took such a number the same way.
way. A finite number still writes its decimals, so `message_id: 123` is unchanged. Any other number goes out as `String()` spells it, so `message_id: 123` is unchanged.
## 0.5.0 ## 0.5.0
+6 -2
View File
@@ -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: 0x80000001 })).seqNr, 0x80000001);
assert.equal(decode(encode({ cmdName: 'enquire_link', seqNr: 0xFFFFFFFF })).seqNr, 0xFFFFFFFF); assert.equal(decode(encode({ cmdName: 'enquire_link', seqNr: 0xFFFFFFFF })).seqNr, 0xFFFFFFFF);
assert.ok(objToPdu({ cmdName: 'submit_sm', seqNr: 0x100000000 }).err instanceof Error); 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é'); 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({ const smuggled = objToPdu({
cmdName: 'submit_sm', cmdName: 'submit_sm',
params: { destination_addr: '46709771337', source_addr: '46701113311\u0000EVIL' }, params: { destination_addr: '46709771337', source_addr: '46701113311\u0000EVIL' },
@@ -154,7 +159,6 @@ describe('parsing real PDUs', () => {
assert.equal(smuggled.buffer, undefined); assert.equal(smuggled.buffer, undefined);
assert.ok(objToPdu({ cmdName: 'deliver_sm', params: { source_addr: '一' } }).err instanceof Error); 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: NaN } }).err instanceof Error);
assert.ok(objToPdu({ cmdName: 'deliver_sm', params: { source_addr: Infinity } }).err instanceof Error); assert.ok(objToPdu({ cmdName: 'deliver_sm', params: { source_addr: Infinity } }).err instanceof Error);
}); });
+9 -1
View File
@@ -39,6 +39,12 @@ describe('integers', () => {
assert.ok(types.int8.write(-1, Buffer.alloc(1), 0).err instanceof Error); 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.int16.write(1.5, Buffer.alloc(2), 0).err instanceof Error);
assert.ok(types.int8.write('nope', Buffer.alloc(1), 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); 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); const target = Buffer.alloc(4);
types.cstring.write(123, target, 0); 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.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.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.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.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));
} }
}); });