Refuse a body the PDU's own data_coding cannot carry (#98)
* Regression tests for a body the PDU's own data_coding cannot carry * Refuse a body the PDU's own data_coding cannot carry, in the codec both send paths build through * Record the codec's body refusal, and drop two todo entries it and the interop run close * Regression tests for the aliased body TLV and the data_coding short_message settles * Find the body TLV by tag id, leave data_coding to the field that will be read, and correct the record * Record the two architecture findings this change does not close * Regression tests for a duplicated body tag and a short_message the wire never carries * Resolve every body TLV against the field that will be read, and move TLV writing to its table * Regression tests for a body only the TLV carries and an empty short_message * Let only octets the command writes settle the alphabet, and stop shadowing the TLV table
This commit is contained in:
+234
-1
@@ -4,8 +4,9 @@ import type { PduObjectInput } from '../src/pdu.ts';
|
||||
import type { SendSmsDeps, SendSmsInput } from '../src/send-sms.ts';
|
||||
import { bindToSmsc, dummySmsc } from './dummy-smsc.ts';
|
||||
import { decodeMessage } from '../src/message.ts';
|
||||
import { messageOctets } from '../src/message-body.ts';
|
||||
import { objToPdu, pduToObj } from '../src/pdu.ts';
|
||||
import { paramNumber, paramText } from '../src/defs/types.ts';
|
||||
import { pduToObj } from '../src/pdu.ts';
|
||||
import { silentLog } from '../src/log.ts';
|
||||
import { submitSms } from '../src/send-sms.ts';
|
||||
|
||||
@@ -112,6 +113,238 @@ describe('an alphabet the caller named that cannot carry the message', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('a body the PDU\'s own data_coding cannot carry', () => {
|
||||
/** The octets a built PDU carries as its body, wherever the caller put them. */
|
||||
function bodyOf(built: ReturnType<typeof objToPdu>): Buffer | undefined {
|
||||
assert.equal(built.err, undefined);
|
||||
assert.ok(built.buffer);
|
||||
|
||||
const { pduObj } = pduToObj(built.buffer);
|
||||
|
||||
assert.ok(pduObj);
|
||||
|
||||
return messageOctets(pduObj);
|
||||
}
|
||||
|
||||
// Latin-1 takes the low octet of every code point, so あ (U+3042) went out as 0x42 — the letter B.
|
||||
test('refuses a string short_message the named alphabet cannot carry, naming the character', () => {
|
||||
const built = objToPdu({
|
||||
cmdName: 'submit_sm',
|
||||
params: { data_coding: 0x03, destination_addr: to, short_message: 'あいう', source_addr: from },
|
||||
});
|
||||
|
||||
assert.ok(built.err instanceof Error);
|
||||
assert.equal(built.buffer, undefined);
|
||||
assert.match(built.err.message, /short_message/);
|
||||
assert.match(built.err.message, /LATIN1/);
|
||||
assert.match(built.err.message, /"あ"/);
|
||||
assert.match(built.err.message, /U\+3042/);
|
||||
assert.match(built.err.message, /index 0/);
|
||||
});
|
||||
|
||||
// Å is in the GSM table at 0x0E; ï is the one the encoder flattened to a space.
|
||||
test('refuses a string short_message GSM 03.38 has no code for, naming that one', () => {
|
||||
for (const dataCoding of [0x00, 0x01]) {
|
||||
const built = objToPdu({
|
||||
cmdName: 'submit_sm',
|
||||
params: { data_coding: dataCoding, destination_addr: to, short_message: 'Åsa naïve', source_addr: from },
|
||||
});
|
||||
|
||||
assert.ok(built.err instanceof Error, String(dataCoding));
|
||||
assert.match(built.err.message, /ASCII/);
|
||||
assert.match(built.err.message, /"ï"/);
|
||||
assert.match(built.err.message, /U\+00EF/);
|
||||
assert.match(built.err.message, /index 6/);
|
||||
}
|
||||
});
|
||||
|
||||
test('refuses a string message_payload on the same terms as short_message', () => {
|
||||
const built = objToPdu({
|
||||
cmdName: 'data_sm',
|
||||
params: { data_coding: 0x03, destination_addr: to, source_addr: from },
|
||||
tlvs: { message_payload: { tagValue: 'あいう' } },
|
||||
});
|
||||
|
||||
assert.ok(built.err instanceof Error);
|
||||
assert.equal(built.buffer, undefined);
|
||||
assert.match(built.err.message, /message_payload/);
|
||||
assert.match(built.err.message, /LATIN1/);
|
||||
assert.match(built.err.message, /"あ"/);
|
||||
assert.match(built.err.message, /U\+3042/);
|
||||
});
|
||||
|
||||
test('refuses the body TLV under whatever name the caller keyed its tagId to', () => {
|
||||
const built = objToPdu({
|
||||
cmdName: 'data_sm',
|
||||
params: { data_coding: 0x03, destination_addr: to, source_addr: from },
|
||||
tlvs: { body: { tagId: 0x0424, tagValue: 'あいう' } },
|
||||
});
|
||||
|
||||
assert.ok(built.err instanceof Error);
|
||||
assert.equal(built.buffer, undefined);
|
||||
assert.match(built.err.message, /"body"/);
|
||||
assert.match(built.err.message, /LATIN1/);
|
||||
assert.match(built.err.message, /U\+3042/);
|
||||
});
|
||||
|
||||
test('leaves data_coding to short_message wherever it carries octets, as messageOctets() reads it', () => {
|
||||
const short = Buffer.from([0xDE, 0xAD]);
|
||||
const built = objToPdu({
|
||||
cmdName: 'deliver_sm',
|
||||
params: { destination_addr: to, short_message: short, source_addr: from },
|
||||
tlvs: { message_payload: { tagValue: 'あいう' } },
|
||||
});
|
||||
|
||||
assert.equal(built.err, undefined);
|
||||
assert.ok(built.buffer);
|
||||
|
||||
const { pduObj } = pduToObj(built.buffer);
|
||||
|
||||
assert.ok(pduObj);
|
||||
assert.equal(pduObj.params.data_coding, 0, 'the TLV must not name an alphabet for octets nothing reads it as');
|
||||
assert.deepEqual(messageOctets(pduObj), short);
|
||||
});
|
||||
|
||||
test('encodes every entry carrying the body tag, so a second one cannot go out truncated', () => {
|
||||
const built = objToPdu({
|
||||
cmdName: 'data_sm',
|
||||
params: { data_coding: 0x08, destination_addr: to, source_addr: from },
|
||||
tlvs: { alias: { tagId: 0x0424, tagValue: 'あいう' }, message_payload: { tagValue: 'あいう' } },
|
||||
});
|
||||
|
||||
assert.equal(built.err, undefined);
|
||||
assert.ok(built.buffer);
|
||||
|
||||
const hex = built.buffer.toString('hex');
|
||||
|
||||
assert.equal(hex.split('304230443046').length - 1, 2, 'both entries carry the UCS2 octets');
|
||||
assert.ok(!hex.includes('424446'), 'no entry goes out as the low octets of its code points');
|
||||
});
|
||||
|
||||
test('leaves the alphabet to the body TLV wherever short_message carries no octets', () => {
|
||||
for (const short of [undefined, '', Buffer.alloc(0)]) {
|
||||
const built = objToPdu({
|
||||
cmdName: 'deliver_sm',
|
||||
params: {
|
||||
destination_addr: to,
|
||||
source_addr: from,
|
||||
...(short === undefined ? {} : { short_message: short }),
|
||||
},
|
||||
tlvs: { message_payload: { tagValue: 'あいう' } },
|
||||
});
|
||||
const name = short === undefined ? 'absent' : JSON.stringify(short);
|
||||
|
||||
assert.equal(built.err, undefined, name);
|
||||
assert.ok(built.buffer);
|
||||
|
||||
const { pduObj } = pduToObj(built.buffer);
|
||||
|
||||
assert.ok(pduObj);
|
||||
assert.equal(pduObj.params.data_coding, 0x08, name);
|
||||
assert.equal(messageOctets(pduObj)?.toString('hex'), '304230443046', name);
|
||||
}
|
||||
});
|
||||
|
||||
test('ignores a short_message on a command that declares none, which the wire never carries', () => {
|
||||
for (const short of ['x', Buffer.from([0xDE, 0xAD])]) {
|
||||
const input: PduObjectInput = {
|
||||
cmdName: 'data_sm',
|
||||
params: { destination_addr: to, short_message: short, source_addr: from },
|
||||
tlvs: { message_payload: { tagValue: 'あいう' } },
|
||||
};
|
||||
const built = objToPdu(input);
|
||||
|
||||
assert.equal(built.err, undefined);
|
||||
assert.ok(built.buffer);
|
||||
|
||||
const { pduObj } = pduToObj(built.buffer);
|
||||
|
||||
assert.ok(pduObj);
|
||||
assert.equal(pduObj.params.data_coding, 0x08, JSON.stringify(short));
|
||||
assert.equal(messageOctets(pduObj)?.toString('hex'), '304230443046');
|
||||
}
|
||||
});
|
||||
|
||||
test('leaves data_coding at its default where an empty short_message is the whole body', () => {
|
||||
for (const short of ['', Buffer.alloc(0)]) {
|
||||
const built = objToPdu({
|
||||
cmdName: 'submit_sm',
|
||||
params: { destination_addr: to, short_message: short, source_addr: from },
|
||||
});
|
||||
|
||||
assert.equal(built.err, undefined);
|
||||
assert.ok(built.buffer);
|
||||
|
||||
const { pduObj } = pduToObj(built.buffer);
|
||||
|
||||
assert.ok(pduObj);
|
||||
assert.equal(pduObj.params.data_coding, 0, JSON.stringify(short));
|
||||
}
|
||||
});
|
||||
|
||||
test('never refuses a Buffer body, whatever the coding, because that is how binary is sent', () => {
|
||||
const payload = Buffer.from([0x30, 0x42, 0xC3, 0x28, 0xFF, 0x00]);
|
||||
|
||||
for (const dataCoding of [0x00, 0x01, 0x03, 0x04, 0x08, 0xF0]) {
|
||||
const params = { data_coding: dataCoding, destination_addr: to, source_addr: from };
|
||||
const short = objToPdu({ cmdName: 'submit_sm', params: { ...params, short_message: payload } });
|
||||
const carried = objToPdu({
|
||||
cmdName: 'data_sm',
|
||||
params,
|
||||
tlvs: { message_payload: { tagValue: payload } },
|
||||
});
|
||||
|
||||
assert.deepEqual(bodyOf(short), payload, `short_message under data_coding ${String(dataCoding)}`);
|
||||
assert.deepEqual(bodyOf(carried), payload, `message_payload under data_coding ${String(dataCoding)}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('never refuses a string the caller named no data_coding for, since detection always fits', () => {
|
||||
for (const message of ['Hello world', 'Åsa naïve', 'あいう', '😀 beyond the basic plane']) {
|
||||
const built = objToPdu({
|
||||
cmdName: 'submit_sm',
|
||||
params: { destination_addr: to, short_message: message, source_addr: from },
|
||||
});
|
||||
|
||||
assert.equal(built.err, undefined, message);
|
||||
}
|
||||
});
|
||||
|
||||
test('writes a body the coding does carry as the octets that alphabet spells it in', () => {
|
||||
const bodies: [number, string, string][] = [
|
||||
[0x00, 'Hello world', '48656c6c6f20776f726c64'],
|
||||
[0x03, 'Räksmörgås', '52e46b736df67267e573'],
|
||||
[0x08, 'あいう', '304230443046'],
|
||||
];
|
||||
|
||||
for (const [dataCoding, message, hex] of bodies) {
|
||||
const params = { data_coding: dataCoding, destination_addr: to, source_addr: from };
|
||||
const short = objToPdu({ cmdName: 'submit_sm', params: { ...params, short_message: message } });
|
||||
const carried = objToPdu({
|
||||
cmdName: 'data_sm',
|
||||
params,
|
||||
tlvs: { message_payload: { tagValue: message } },
|
||||
});
|
||||
|
||||
assert.equal(bodyOf(short)?.toString('hex'), hex, `short_message: ${message}`);
|
||||
assert.equal(bodyOf(carried)?.toString('hex'), hex, `message_payload: ${message}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('refuses the same body through session.send(), with nothing reaching the socket', async t => {
|
||||
const smsc = await dummySmsc(t);
|
||||
const session = await bindToSmsc(t, smsc.port, { reconnect: false });
|
||||
const sent = await session.send({
|
||||
cmdName: 'submit_sm',
|
||||
params: { data_coding: 0x03, destination_addr: to, short_message: 'あいう', source_addr: from },
|
||||
});
|
||||
|
||||
assert.ok(sent.err instanceof Error);
|
||||
assert.match(sent.err.message, /LATIN1/);
|
||||
assert.deepEqual(smsc.octets, [], 'a body the named alphabet cannot carry never reaches the socket');
|
||||
});
|
||||
});
|
||||
|
||||
describe('a time no peer can read', () => {
|
||||
const invalid = new Date('nope');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user