From 4031cfadf145cf59af71bc8f630d864fc48cb898 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 28 Sep 2026 00:52:39 +0200 Subject: [PATCH 1/2] Stub the handlers an Sms answers and receipts through --- test/session-extras.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/session-extras.test.ts b/test/session-extras.test.ts index 25283b1..8e23ed3 100644 --- a/test/session-extras.test.ts +++ b/test/session-extras.test.ts @@ -102,6 +102,7 @@ function abortAfter( function stubPort(session: Session, port: Partial = {}): IncomingDeps { return { + acceptsOptionalParams: () => true, answer: (pduObj, status, params) => session.sendReturn(pduObj, status, params), bindAllows: () => true, boundAs: () => 'transceiver', @@ -1653,11 +1654,9 @@ describe('held message bounds', () => { describe('sendResp()', () => { // A response the wire never carried leaves the peer owed one, so nothing may count it answered. test('does not count a response that never reached the wire as an answer', async t => { - const sock = new net.Socket(); - const session = new Session({ sock }); + const session = new Session({ sock: new net.Socket() }); closeAfter(t, session); - sock.destroy(); let answered = 0; const sms = createSms({ @@ -1667,6 +1666,9 @@ describe('sendResp()', () => { session, to: '46709771337', }, { + acceptsOptionalParams: () => true, + answer: () => Promise.resolve({ err: new Error('Socket is closed') }), + bindAllows: () => true, lostLink: () => false, onAnswered: () => { answered++; }, send: () => Promise.resolve({ err: new Error('never sent') }), @@ -1692,6 +1694,9 @@ describe('sendDlr()', () => { session, to: '46709771337', }, { + acceptsOptionalParams: () => true, + answer: () => Promise.resolve({}), + bindAllows: () => true, lostLink: () => false, onAnswered: () => undefined, send: () => { -- 2.52.0 From 76c6783317f757c2a058e856f366f1b8a49a9ced Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 28 Sep 2026 00:54:01 +0200 Subject: [PATCH 2/2] Route sms.ts through its handlers, all of it --- src/incoming-requests.ts | 4 ++++ src/session.ts | 1 + src/sms.ts | 18 +++++++++++------- todo.md | 5 ----- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/incoming-requests.ts b/src/incoming-requests.ts index 7fa16f7..e018b55 100644 --- a/src/incoming-requests.ts +++ b/src/incoming-requests.ts @@ -48,6 +48,7 @@ type Send = (input: PduObjectInput) => Promise>; /** What the incoming side asks of the session it serves; the session decides how. */ export type IncomingDeps = { + acceptsOptionalParams: () => boolean; answer: (pduObj: PduObject, status?: ErrorName, params?: Record) => Promise; bindAllows: (cmdName: string) => boolean; boundAs: () => BindType | undefined; @@ -318,6 +319,9 @@ export class IncomingRequests { pduObjs, to: paramText(first.params.destination_addr), }, { + acceptsOptionalParams: () => this.deps.acceptsOptionalParams(), + answer: (pduObj, status, params) => this.deps.answer(pduObj, status, params), + bindAllows: cmdName => this.deps.bindAllows(cmdName), lostLink: () => this.linkGeneration !== generation, onAnswered: release, // Past the refusal only while a drain is still waiting for this message; an ordinary send after. diff --git a/src/session.ts b/src/session.ts index 2b878b6..5ae3dcf 100644 --- a/src/session.ts +++ b/src/session.ts @@ -247,6 +247,7 @@ export class Session extends EventEmitter { private incomingDeps(onRequest: OnRequest | undefined): IncomingDeps { return { + acceptsOptionalParams: () => this.acceptsOptionalParams(), answer: (pduObj, status, params) => this.sendReturn(pduObj, status, params), bindAllows: cmdName => this.bindAllows(cmdName), boundAs: () => this.boundAs, diff --git a/src/sms.ts b/src/sms.ts index dc57106..f8526f3 100644 --- a/src/sms.ts +++ b/src/sms.ts @@ -1,5 +1,6 @@ import type { ErrorName } from './defs/errors.ts'; import type { MessageState } from './defs/constants.ts'; +import type { ParamValue } from './defs/types.ts'; import type { PduObject, PduObjectInput, TlvInputs } from './pdu.ts'; import type { Result, VoidResult } from './result.ts'; import type { Session } from './session.ts'; @@ -68,6 +69,9 @@ export type SmsInput = { /** What the session's incoming side gives a message so it can be answered and accounted for. */ export type SmsHandlers = { + acceptsOptionalParams: () => boolean; + answer: (pduObj: PduObject, status: ErrorName, params: Record) => Promise; + bindAllows: (cmdName: string) => boolean; lostLink: () => boolean; onAnswered: () => void; send: (input: PduObjectInput) => Promise>; @@ -89,7 +93,7 @@ export function createSms(input: SmsInput, handlers: SmsHandlers): Sms { from: input.from, message: input.message, pduObjs: input.pduObjs, - sendDlr: status => sendDlr(sms, handlers.send, status), + sendDlr: status => sendDlr(sms, handlers, status), sendResp: options => (input.answeredAs === undefined ? sendResp(sms, answered, options ?? {}, handlers) : answeredOnArrival(options ?? {}, handlers)), @@ -130,7 +134,7 @@ async function sendResp( sms: Sms, answered: { smsId: string }, options: SendRespOptions, - handlers: Pick, + handlers: Pick, ): Promise { const total = sms.pduObjs.length; @@ -149,7 +153,7 @@ async function sendResp( return { err: new Error('The link this message arrived on is gone, so nothing would correlate the response') }; } - const results = await Promise.all(sms.pduObjs.map((pduObj, index) => sms.session.sendReturn( + const results = await Promise.all(sms.pduObjs.map((pduObj, index) => handlers.answer( pduObj, options.status ?? 'ESME_ROK', respIdParams(pduObj.cmdName, segmentId(answered.smsId, index, total)), @@ -210,10 +214,10 @@ function collectReceipt(sent: Result<{ pduObj: PduObject }>[]): SendDlrResult { async function sendDlr( sms: Sms, - send: SmsHandlers['send'], + handlers: Pick, status: MessageState = 'DELIVERED', ): Promise { - if (!sms.session.bindAllows('deliver_sm')) { + if (!handlers.bindAllows('deliver_sm')) { return { err: new Error('A transmitter-bound session does not carry deliver_sm'), pduObjs: [], @@ -226,7 +230,7 @@ async function sendDlr( const sent = await Promise.all(sms.pduObjs.map((_segment, index) => { const smsId = segmentId(sms.smsId, index, total); - return send({ + return handlers.send({ cmdName: 'deliver_sm', params: { destination_addr: sms.from, @@ -236,7 +240,7 @@ async function sendDlr( short_message: receiptText(sms, smsId, status), source_addr: sms.to, }, - ...(sms.session.acceptsOptionalParams() ? { tlvs: receiptTlvs(smsId, status) } : {}), + ...(handlers.acceptsOptionalParams() ? { tlvs: receiptTlvs(smsId, status) } : {}), }); })); return collectReceipt(sent); diff --git a/todo.md b/todo.md index f677cdf..0551ce7 100644 --- a/todo.md +++ b/todo.md @@ -199,11 +199,6 @@ next work ([decision](docs/decisions.md#internals-and-tests)). ### Locality — next, ahead of everything below; 5–6 today, and the gate is 7 -- [ ] **Route `sms.ts` through its handlers, all of it.** `createSms()` already injects - `handlers.send`, and then reaches `sms.session.sendReturn()`, `sms.session.bindAllows()` and - `sms.session.acceptsOptionalParams()` anyway — two channels to one collaborator. `Sms.session` - stays public as data the application reads. - - [ ] **Give the held-message protocol one name and one home.** `emitSms()` is the unit 8 of 9 readers named and 4 would least want to modify, and every one proposed the same fix. It runs five mechanisms in one scope: a hold keyed by array identity, a `working` counter seeded from -- 2.52.0