From f4bb1e542f62dbee1277e96128542fc79389e087 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 22 Sep 2026 21:39:58 +0200 Subject: [PATCH] Leave alert_notification and outbind unanswered --- CHANGELOG.md | 2 ++ src/incoming-requests.ts | 4 ++++ test/session.test.ts | 19 +++++++++++++++++++ todo.md | 6 ------ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e94672e..39a21aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ a caller that reads only `smsIds` meets a failure it has not met before. A whole number in an address or an id still spells its digits, so `message_id: 123` is unchanged. The integer fields name a refused `NaN` too, where the refusal used to read `null`. +- An inbound `alert_notification` or `outbind` is left unanswered, as SMPP 3.4 defines no response + to either. Each used to raise `sessionError`. ## 0.5.0 diff --git a/src/incoming-requests.ts b/src/incoming-requests.ts index 420dbf7..c02365a 100644 --- a/src/incoming-requests.ts +++ b/src/incoming-requests.ts @@ -115,6 +115,10 @@ export class IncomingRequests { private async route(pduObj: PduObject): Promise { switch (pduObj.cmdName) { + case 'alert_notification': + case 'outbind': + this.log.info('session - command with no response, left unanswered', { cmdName: pduObj.cmdName }); + break; case 'data_sm': case 'deliver_sm': // A data_sm at the SMSC end is a submission, and a submission is never a report. diff --git a/test/session.test.ts b/test/session.test.ts index fa7d0a1..6ff29f0 100644 --- a/test/session.test.ts +++ b/test/session.test.ts @@ -1792,6 +1792,25 @@ describe('a PDU the codec cannot read', () => { assert.ok((await raceWithin(2000, failed)) instanceof Error, 'one sessionError per refused PDU'); }); + test('leaves alert_notification and outbind unanswered, as SMPP 3.4 defines no response to either', async t => { + const { peer, session } = await bound(t); + const failed = once(resolve => { session.on('sessionError', resolve); }); + + peer.writeRaw(pduBytes({ + cmdName: 'alert_notification', + params: { esme_addr: '46701234567', source_addr: '46709876543' }, + seqNr: 21, + })); + peer.writeRaw(pduBytes({ cmdName: 'outbind', params: { password: 'secret', system_id: 'smsc' }, seqNr: 22 })); + peer.writeRaw(pduBytes({ cmdName: 'enquire_link', seqNr: 23 })); + + const answered = await answerTo(peer); + + assert.equal(answered.cmdName, 'enquire_link_resp'); + assert.equal(answered.seqNr, 23); + assert.equal(await raceWithin(200, failed), false, 'neither is a session error'); + }); + test('answers a deliver_sm with a truncated TLV stream with ESME_RINVTLVSTREAM', async t => { const { peer, session } = await bound(t); let reports = 0; diff --git a/todo.md b/todo.md index fe521dd..0ecb709 100644 --- a/todo.md +++ b/todo.md @@ -200,12 +200,6 @@ and is also what the panel ranked hardest — two methods, one answer. caller never wrote." A goal is the maintainer's, so nothing edits README until that is answered. From the prose pass of #18. -- [ ] **Answer `alert_notification` and `outbind` by not answering them.** Both are response-less in - SMPP 3.4, both fall through `route()`'s default into `unhandled()`, which calls - `sendReturn(pduObj, 'ESME_RINVCMDID')`; `pduReturn()` then finds no response command, and the - failure reaches the application as `sessionError` on every occurrence. `alert_notification` - appears nowhere in `src/` but `defs/commands.ts`. One case arm each: log and return. - - [ ] **Range-check `maxOctets` with its five siblings.** `limitsOf()` in `session-options.ts` covers `idleTimeout`, `maxOutstanding`, `maxReassembly`, `reassemblyTimeout`, `responseTimeout` and `shutdownTimeout`; `maxOctets` is documented, consumed by `Reassembler`, and absent from