Reject a renumbered multipart total and check abort before the send window

This commit is contained in:
2026-08-27 12:43:17 +02:00
parent 8a182604b7
commit ff3667e120
5 changed files with 63 additions and 2 deletions
+11
View File
@@ -365,6 +365,17 @@ describe('reassembly bounds', () => {
assert.equal(reassembler.size, 0);
});
// Parts 1/2 then 2/3 would otherwise complete the stored two-part group, truncating the message.
test('refuses a segment that renumbers how many parts the message has', () => {
const reassembler = new Reassembler({ log: silentLog, max: 10, now: () => 0, timeout: 60_000 });
assert.equal(collect(reassembler, 9, 1, 2), undefined);
assert.equal(collect(reassembler, 9, 2, 3), undefined);
assert.equal(reassembler.size, 1);
reassembler.clear();
});
// 0.4.0 held incomplete groups without limit and swept them only when other traffic arrived.
test('drops the oldest incomplete message once the cap is reached', () => {
const reassembler = new Reassembler({ log: silentLog, max: 2, now: () => 0, timeout: 60_000 });
+32
View File
@@ -1000,6 +1000,38 @@ describe('robustness', () => {
assert.deepEqual(seen, []);
});
// The guard sits before the send window, or a full window makes the aborted call queue first.
test('does not wait for a send window slot it will never use', async t => {
const smpp = await startServer();
t.after(() => smpp.close());
// The peer answers nothing, so the one slot stays held for the whole test.
smpp.on('session', session => session.on('sms', () => undefined));
const { session } = await connect(smpp, { maxOutstanding: 1, responseTimeout: 5000 });
assert.ok(session);
t.after(() => { session.close(); });
const held = session.sendSms({ from: '46701113311', message: 'holds the slot', to: '46709771337' });
const controller = new AbortController();
controller.abort();
const aborted = await raceWithin(500, session.sendSms({
from: '46701113311',
message: 'must not queue behind the held one',
to: '46709771337',
}, { signal: controller.signal }));
assert.notEqual(aborted, false, 'an aborted send should not wait for the window');
assert.ok(aborted !== false && aborted.err instanceof Error);
session.close();
await held;
});
// A socket the loop opened and never handed over is one leaked per retry, forever.
test('leaves no socket open when coming back up fails', async () => {
const opened: net.Socket[] = [];