diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a9f2d5..1606b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ - An `alert_notification` or an `outbind` from the peer is logged and left unanswered, as SMPP 3.4 gives neither a response. Each one used to emit `sessionError`, `"alert_notification" has no response command`. +- `server()` refuses a `maxOctets` below 1 or not a whole number, `Infinity` included, like its + other limits. `server({ maxOctets: 0 })` used to start and then refuse every multipart message. ## 0.5.0 diff --git a/src/reassembly.ts b/src/reassembly.ts index bacadcb..98a7abc 100644 --- a/src/reassembly.ts +++ b/src/reassembly.ts @@ -43,7 +43,7 @@ export type Collected = whole?: PduObject[] | undefined; }; -const defaultMaxOctets = 64 * 1024 * 1024; +export const defaultMaxOctets = 64 * 1024 * 1024; type Group = { octets: number; diff --git a/src/session-options.ts b/src/session-options.ts index fd53842..ea1dbf0 100644 --- a/src/session-options.ts +++ b/src/session-options.ts @@ -9,6 +9,7 @@ import type { SmsIdFormat } from './sms-id.ts'; import type { Sms } from './sms.ts'; import type { Socket } from 'node:net'; import { backoffDefaults } from './reconnect-loop.ts'; +import { defaultMaxOctets } from './reassembly.ts'; import { isSmsIdNotation, smsIdNotations, smsIdPlaces } from './sms-id.ts'; import { namedValue } from './error-from.ts'; @@ -160,6 +161,7 @@ export function checkSessionOptions(options: CheckableOptions): VoidResult { function limitsOf(options: CheckableOptions): [string, number, number][] { return [ ['idleTimeout', options.idleTimeout ?? 0, 0], + ['maxOctets', options.maxOctets ?? defaultMaxOctets, 1], ['maxOutstanding', options.maxOutstanding ?? defaults.maxOutstanding, 1], ['maxReassembly', options.maxReassembly ?? defaults.maxReassembly, 1], ['reassemblyTimeout', options.reassemblyTimeout ?? defaults.reassemblyTimeout, 0], @@ -268,6 +270,7 @@ export type CheckableOptions = { /** Not an option: the one spelling is inside reconnect, and this is where the other is refused. */ fromStart?: unknown; idleTimeout?: number | undefined; + maxOctets?: number | undefined; maxOutstanding?: number | undefined; maxReassembly?: number | undefined; reassemblyTimeout?: number | undefined; diff --git a/test/session.test.ts b/test/session.test.ts index 3b4de31..5e936ae 100644 --- a/test/session.test.ts +++ b/test/session.test.ts @@ -12,6 +12,7 @@ import { DlrMerger } from '../src/dlr-merger.ts'; import { PduFramer } from '../src/pdu-framer.ts'; import { ReconnectLoop } from '../src/reconnect-loop.ts'; import { Session, bindCommands } from '../src/session.ts'; +import { checkSessionOptions } from '../src/session-options.ts'; import { client } from '../src/client.ts'; import { closeAfter, closeListenerAfter } from './teardown.ts'; import { consts } from '../src/defs/constants.ts'; @@ -2688,6 +2689,15 @@ describe('option validation', () => { assert.match(hookRefused.err?.message ?? '', /onRequest must be a function/); }); + test('refuses a reassembly octet cap below 1 at startup', async () => { + const listening = await server({ maxOctets: 0, port: 0 }); + + if (listening.server) await listening.server.close(); + + assert.match(listening.err?.message ?? '', /maxOctets must be 1 or more, got 0/); + assert.match(checkSessionOptions({ maxOctets: Infinity }).err?.message ?? '', /maxOctets must be 1 or more, got Infinity/); + }); + test('returns an error rather than rejecting on an impossible port', async () => { const listening = await server({ port: 70_000 }); diff --git a/todo.md b/todo.md index 25bc6fd..1276023 100644 --- a/todo.md +++ b/todo.md @@ -189,12 +189,6 @@ and is also what the panel ranked hardest — two methods, one answer. ### Correctness, ahead of everything below -- [ ] **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 - both that list and `CheckableOptions`. `server({ maxOctets: 0 })` starts, then refuses every - multipart message and reports each as lost traffic. - - [ ] **Read `multiple` in `parseTlvs()` and `writeTlvs()`, or delete it and `tlvMap`.** Five TLVs declare `multiple: true` (`callback_num`, `callback_num_atag`, `callback_num_pres_ind`, `broadcast_area_identifier`, `broadcast_error_status`) and nothing reads it; `parseTlvs()` keys