From 980e9d45692ac0c57b4aaea590d6638c6c356295 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 30 Sep 2026 20:15:04 +0200 Subject: [PATCH 1/5] Move src into codec, protocol, messages, session, client and server --- AGENTS.md | 82 +++++------ benchmarks/smsc-sink.ts | 2 +- benchmarks/submit-load.ts | 2 +- docs/decisions.md | 12 +- eslint.config.js | 6 +- interop-tests/cloudhopper.test.ts | 4 +- interop-tests/compose.dumbclient.yaml | 2 +- interop-tests/dumbclient.test.ts | 6 +- interop-tests/jasmin.test.ts | 16 +-- interop-tests/jsmpp.test.ts | 2 +- interop-tests/kannel.test.ts | 16 +-- interop-tests/php.test.ts | 2 +- interop-tests/python.test.ts | 4 +- interop-tests/smppload.test.ts | 4 +- interop-tests/smppsim.test.ts | 16 +-- interop-tests/smscsim.test.ts | 4 +- src/{ => client}/client.ts | 30 ++-- src/{defs => codec}/commands.ts | 4 +- src/{defs => codec}/constants.ts | 3 - src/{defs => codec}/encodings.ts | 0 src/{defs/types.ts => codec/field-types.ts} | 0 src/{pdu-framer.ts => codec/framer.ts} | 4 +- src/{ => codec}/pdu.ts | 26 ++-- src/{pdu-refusal.ts => codec/refusal.ts} | 6 +- src/{retained-pdu.ts => codec/retained.ts} | 4 +- src/{defs/errors.ts => codec/statuses.ts} | 0 src/{defs => codec}/tlvs.ts | 4 +- src/defs/index.ts | 19 --- src/error-from.ts | 17 --- src/index.ts | 80 ++++++----- src/message.ts | 8 +- src/{ => messages}/expiring-groups.ts | 0 src/{ => messages}/reassembly.ts | 21 ++- .../receipt-merge.ts} | 8 +- src/{send-sms.ts => messages/submit.ts} | 28 ++-- src/{session-options.ts => options.ts} | 129 ++++-------------- src/protocol/bind.ts | 73 ++++++++++ src/{ => protocol}/concat.ts | 6 +- src/{ => protocol}/message-body.ts | 2 +- src/{sms-id.ts => protocol/message-ids.ts} | 4 +- src/{dlr.ts => protocol/receipt.ts} | 16 +-- src/{ => protocol}/udh.ts | 0 src/{ => protocol}/uuid.ts | 0 src/reconnect-loop.ts | 10 +- src/result.ts | 23 ++++ src/{ => server}/server.ts | 31 ++--- src/session.ts | 59 +++++--- src/{ => session}/held-messages.ts | 20 +-- src/{link-timers.ts => session/keepalive.ts} | 2 +- src/{ => session}/outgoing-requests.ts | 18 +-- src/{ => session}/pending-requests.ts | 8 +- .../requests-in.ts} | 37 ++--- src/{ => session}/send-window.ts | 6 +- .../transport.ts} | 12 +- src/{idle-waiters.ts => session/waiting.ts} | 0 src/sms.ts | 20 +-- test/commands.test.ts | 4 +- test/declared-alphabet.test.ts | 16 +-- test/dlr.test.ts | 8 +- test/dummy-smsc.ts | 10 +- test/encodings.test.ts | 2 +- test/error-from.test.ts | 2 +- test/interop.test.ts | 8 +- test/message-class.test.ts | 16 +-- test/message.test.ts | 2 +- test/messaging-mode.test.ts | 14 +- test/operator-receipts.test.ts | 10 +- test/pdu-framer.test.ts | 4 +- test/pdu.test.ts | 8 +- test/raw-pdus.ts | 4 +- test/readme.test.ts | 12 +- test/session-extras.test.ts | 49 +++---- test/session.test.ts | 27 ++-- test/sms-id.test.ts | 2 +- test/teardown.ts | 2 +- test/tls.test.ts | 6 +- test/types.test.ts | 6 +- test/unsendable.test.ts | 12 +- todo.md | 41 ++---- 79 files changed, 573 insertions(+), 610 deletions(-) rename src/{ => client}/client.ts (92%) rename src/{defs => codec}/commands.ts (98%) rename src/{defs => codec}/constants.ts (96%) rename src/{defs => codec}/encodings.ts (100%) rename src/{defs/types.ts => codec/field-types.ts} (100%) rename src/{pdu-framer.ts => codec/framer.ts} (94%) rename src/{ => codec}/pdu.ts (94%) rename src/{pdu-refusal.ts => codec/refusal.ts} (92%) rename src/{retained-pdu.ts => codec/retained.ts} (94%) rename src/{defs/errors.ts => codec/statuses.ts} (100%) rename src/{defs => codec}/tlvs.ts (99%) delete mode 100644 src/defs/index.ts delete mode 100644 src/error-from.ts rename src/{ => messages}/expiring-groups.ts (100%) rename src/{ => messages}/reassembly.ts (92%) rename src/{dlr-merger.ts => messages/receipt-merge.ts} (95%) rename src/{send-sms.ts => messages/submit.ts} (93%) rename src/{session-options.ts => options.ts} (65%) create mode 100644 src/protocol/bind.ts rename src/{ => protocol}/concat.ts (89%) rename src/{ => protocol}/message-body.ts (92%) rename src/{sms-id.ts => protocol/message-ids.ts} (95%) rename src/{dlr.ts => protocol/receipt.ts} (94%) rename src/{ => protocol}/udh.ts (100%) rename src/{ => protocol}/uuid.ts (100%) rename src/{ => server}/server.ts (93%) rename src/{ => session}/held-messages.ts (92%) rename src/{link-timers.ts => session/keepalive.ts} (97%) rename src/{ => session}/outgoing-requests.ts (92%) rename src/{ => session}/pending-requests.ts (92%) rename src/{incoming-requests.ts => session/requests-in.ts} (89%) rename src/{ => session}/send-window.ts (94%) rename src/{pdu-transport.ts => session/transport.ts} (90%) rename src/{idle-waiters.ts => session/waiting.ts} (100%) diff --git a/AGENTS.md b/AGENTS.md index 882bb24..2773f86 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,55 +37,58 @@ These are not preferences. Breaking one is a defect. ``` src/ - index.ts Public surface. Named exports only, no default export. - client.ts client() -> { err, session } - server.ts server() -> { err, server }, server owns the listener + close() - session.ts Session: the socket's life, dispatch, events, and the collaborators below - sms.ts The live handle emitted as the 'sms' event (sendResp/sendDlr) - concat.ts How a PDU says it is a segment: its UDH, or the sar_* TLVs - dlr.ts Delivery receipts: text and TLV parsing, receipt status codes - dlr-merger.ts DlrMerger: per-segment receipts counted into one MessageDlr - error-from.ts An untyped value as error material: errorFrom() an Error, namedValue() a name - expiring-groups.ts ExpiringGroups: the capped, weighed, expiring store DlrMerger, HeldMessages and Reassembler share - held-messages.ts HeldMessages: a message from its `sms` event to its answer, capped and expiring, one MessageHold each - idle-waiters.ts IdleWaiters: waiting for a count to fall to zero, and what is left of a budget - incoming-requests.ts Every request the peer sends: messages, receipts, links, unknown commands + index.ts Public surface. Named exports only, no default export; assembles `defs`. link-life.ts LinkLife: whether the link lives, and where a request waits for the next one - link-timers.ts LinkTimers: the enquire_link heartbeat and the idle timeout log.ts SmppLog, the logger contract, and silentLog — the default message.ts Encoding detection, splitting, bit counting, SMPP date formatting - message-body.ts Where an inbound body is: short_message, or the message_payload TLV - outgoing-requests.ts OutgoingRequests: the window, the pending map and the retry - pdu.ts pduToObj / objToPdu / pduReturn — synchronous, result-returning - pdu-framer.ts PduFramer: a byte stream cut into complete PDUs - pdu-refusal.ts A PDU the codec would not read, and the answer SMPP names for it - pdu-transport.ts PduTransport: the socket a session reads complete PDUs off - pending-requests.ts PendingRequests: sequence numbers, correlation, timeout, abort - reassembly.ts Reassembler: capped, expiring multipart groups + options.ts SessionOptions, ReconnectOptions, their checks, and `defaults`: every default and internal cap reconnect-loop.ts ReconnectLoop: backoff, retry timer, stopped-ness - result.ts Result — the shape every fallible call returns - retained-pdu.ts A PDU copied off the wire so holding it pins nothing else, and what holding it costs - send-sms.ts submitSms composition and the submitSmParams builder - send-window.ts SendWindow: the maxOutstanding semaphore - session-options.ts SessionOptions, ReconnectOptions, bind direction and the session defaults - sms-id.ts Message ids: the peer's notation, the - a segment gets, which response carries one - udh.ts User data header: its length, the concatenation fields of a long SMS and their reference + result.ts Result, and an untyped value as error material: errorFrom(), namedValue(), quoted() + session.ts Session: the socket's life, dispatch, events, and the collaborators in session/ + sms.ts The live handle emitted as the 'sms' event (sendResp/sendDlr) unanswered-error.ts UnansweredError: it went out and no answer came back - uuid.ts uuidv7() — the ids the library generates for messages - defs/ + codec/ Bytes <-> PduObject commands.ts The 33 commands, their ids and ordered parameter lists constants.ts consts + constsById, and the SMPP version constants encodings.ts GSM 03.38, LATIN1, UCS2, detection, data_coding resolution - errors.ts errors + errorsById (ESME_*) - index.ts defs: every table as one group + field-types.ts Wire types: int8/int16/int32/string/cstring/buffer/arrays + framer.ts PduFramer: a byte stream cut into complete PDUs + pdu.ts pduToObj / objToPdu / pduReturn — synchronous, result-returning + refusal.ts A PDU the codec would not read, and the answer SMPP names for it + retained.ts A PDU copied off the wire so holding it pins nothing else, and what holding it costs + statuses.ts errors + errorsById (ESME_*) tlvs.ts TLV definitions, tlvsById, the typed read and input shapes, and reading and writing a TLV stream - types.ts Wire types: int8/int16/int32/string/cstring/buffer/arrays + protocol/ What the fields mean + bind.ts Bind directions: which commands bind, what a direction carries, data_sm's stand-in, checkedBind() + concat.ts How a PDU says it is a segment: its UDH, or the sar_* TLVs + message-body.ts Where an inbound body is: short_message, or the message_payload TLV + message-ids.ts Message ids: the peer's notation, the - a segment gets, which response carries one + receipt.ts Delivery receipts: text and TLV parsing, receipt status codes + udh.ts User data header: its length, the concatenation fields of a long SMS and their reference + uuid.ts uuidv7() — the ids the library generates for messages + messages/ Whole messages across segments and time + expiring-groups.ts ExpiringGroups: the capped, weighed, expiring store DlrMerger, HeldMessages and Reassembler share + reassembly.ts Reassembler: capped, expiring multipart groups + receipt-merge.ts DlrMerger: per-segment receipts counted into one MessageDlr + submit.ts submitSms composition and the submitSmParams builder + session/ One socket's collaborators + held-messages.ts HeldMessages: a message from its `sms` event to its answer, capped and expiring, one MessageHold each + keepalive.ts LinkTimers: the enquire_link heartbeat and the idle timeout + outgoing-requests.ts OutgoingRequests: the window, the pending map and the retry + pending-requests.ts PendingRequests: sequence numbers, correlation, timeout, abort + requests-in.ts IncomingRequests: every request the peer sends: messages, receipts, links, unknown commands + send-window.ts SendWindow: the maxOutstanding semaphore + transport.ts PduTransport: the socket a session reads complete PDUs off + waiting.ts IdleWaiters: waiting for a count to fall to zero, and what is left of a budget + client/client.ts client() -> { err, session } + server/server.ts server() -> { err, server }, server owns the listener + close() ``` -Imports point one way: `defs` knows nothing above it but `result.ts`, `pdu` uses `defs`, `session` -uses `pdu`, and `client`/`server` use `session`. The ways back up are the `Session` handed to -`createSms()`, `HeldMessages` and `IncomingRequests`, which call back into it, and to `OnRequest` -and `onConnected` in `session-options.ts`, all imported as a type only. +Imports point one way: `codec` ← `protocol` ← `messages` ← `session/` ← `client`/`server`, and +the root files sit beside that order; `codec` reaches outside itself only for `result.ts` and, from +`pdu.ts`, `message.ts`. The ways back up are the `Session` handed to `createSms()`, `HeldMessages` +and `IncomingRequests`, which call back into it, and to `OnRequest` and `onConnected` in +`options.ts`, all imported as a type only. **Parameter order is wire order.** The key order inside `cmds.*.params` is the order the fields are written to and read from the buffer. Never sort those alphabetically — the alphabetical-ordering @@ -314,8 +317,7 @@ this is not a changelog. `SendWindow` rather than extracted. - `SmppLog` is a five-method contract this library declares, not a dependency. - The TLS tests build their own self-signed certificate in DER -- `src/` stays flat until a module has to move for another reason. -- `test/` stays flat too, and a file there is named for the question it answers rather than for the +- `test/` stays flat, and a file there is named for the question it answers rather than for the module it covers. - CI tests on Linux only; `src/` keeps off what is known to break on macOS or Windows. - GitHub mirrors Gitea without pruning, and a ref deleted on Gitea is deleted on GitHub by a run of diff --git a/benchmarks/smsc-sink.ts b/benchmarks/smsc-sink.ts index 324c11e..2a99ea9 100644 --- a/benchmarks/smsc-sink.ts +++ b/benchmarks/smsc-sink.ts @@ -1,4 +1,4 @@ -import { server } from '../src/server.ts'; +import { server } from '../src/server/server.ts'; /** * Answers every submit_sm ESME_ROK and does nothing else, so a measurement against it reads this diff --git a/benchmarks/submit-load.ts b/benchmarks/submit-load.ts index 95de428..1b00262 100644 --- a/benchmarks/submit-load.ts +++ b/benchmarks/submit-load.ts @@ -1,4 +1,4 @@ -import { client } from '../src/client.ts'; +import { client } from '../src/client/client.ts'; /** * Pushes `count` single-segment messages and reports what the wire carried per second. Keeps diff --git a/docs/decisions.md b/docs/decisions.md index c245097..3f16cd1 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -295,7 +295,7 @@ rule and an index of the titles below. a delivery receipt (0x04).** Appendix B makes a receipt's `stat` the message's final status, so 0x04 over `ENROUTE` emits the two disagreeing spellings of finality the reading side above has to reconcile, and goal 3 has our own senders write the marker 3.4 defines. `sendDlr()` takes the list - from `transientStates` in `dlr.ts`, the same one the reader uses, so the two cannot drift. + from `transientStates` in `protocol/receipt.ts`, the same one the reader uses, so the two cannot drift. Rejected: 0x04 for every state, for the sake of a peer that classifies on the marker — the cost accepted here is that such a peer stops recognising a transient report as a report at all and hands its application receipt text as an inbound message, where under 0x04 it would have read the state @@ -590,7 +590,7 @@ rule and an index of the titles below. deadlocked every multi-segment message against a production gateway ([interop-tests/findings/03-jasmin.md](../interop-tests/findings/03-jasmin.md)). Goal 1 has the answer a real SMSC gives — one `message_id` per `submit_sm`, immediately — so the group's id base is - generated when it opens and each segment is answered `-`, the notation `sms-id.ts` owns + generated when it opens and each segment is answered `-`, the notation `protocol/message-ids.ts` owns and `DlrMerger` reads back. The id is therefore fixed by the first segment, which is why an `smsId` or a refusing `status` passed to `sendResp()` on such a message is an error rather than a silent no-op. `answeredOnArrival` is on `Sms` because nothing the application can compute says it, and the @@ -795,13 +795,7 @@ rule and an index of the titles below. fail on every developer machine, and a committed key leaks in a public repository. Valid while the dev image has no openssl. -- **`src/` stays flat until a module has to move for another reason.** Architecture review, - 2026-09-06: the grouping the [file map](../AGENTS.md#architecture) already implies — `wire/` for `pdu*` and `defs`, - `link/` for `link-*`, `reconnect-*`, `pdu-transport` and `send-window`, `messages/` for `sms*`, - `dlr*`, `message*`, `reassembly` and `udh` — rewrites every import for no change to - `dist/index.js`, the one published entry. Valid while that map is what a reader navigates by. - -- **`test/` stays flat too, and a file there is named for the question it answers rather than for the +- **`test/` stays flat, and a file there is named for the question it answers rather than for the module it covers.** Architecture review, 2026-09-08, at 18 test files: what keeps that count honest is the naming rule rather than a tree — `operator-receipts.test.ts` holds a corpus defined by where it came from, cutting across four modules, where filing it by module would enter each new operator diff --git a/eslint.config.js b/eslint.config.js index 755c9c0..0b22b9d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -35,17 +35,17 @@ export default tseslint.config( }, { // The spec tables are data: their length tracks the specification, not any complexity. - files: ['src/defs/*.ts'], + files: ['src/codec/{commands,constants,encodings,field-types,statuses,tlvs}.ts'], rules: { 'max-lines': 'off' }, }, { // ESLint counts every ?. and ?? in dlrFromPdu as a branch; the 19 is 26 lines of flat field resolution. - files: ['src/dlr.ts'], + files: ['src/protocol/receipt.ts'], rules: { complexity: ['error', 19] }, }, { // ESC (0x1B) is the GSM 03.38 escape character, so it belongs in these patterns. - files: ['src/defs/encodings.ts'], + files: ['src/codec/encodings.ts'], rules: { 'no-control-regex': 'off' }, }, { diff --git a/interop-tests/cloudhopper.test.ts b/interop-tests/cloudhopper.test.ts index dca5b5b..37c3ac7 100644 --- a/interop-tests/cloudhopper.test.ts +++ b/interop-tests/cloudhopper.test.ts @@ -3,8 +3,8 @@ import { readFileSync } from 'node:fs'; import test, { after, describe } from 'node:test'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; -import type { SmppServer } from '../src/server.ts'; -import { server } from '../src/server.ts'; +import type { SmppServer } from '../src/server/server.ts'; +import { server } from '../src/server/server.ts'; const CLOUDHOPPER_HOST = process.env.CLOUDHOPPER_HOST ?? 'cloudhopper:8080'; const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775'); diff --git a/interop-tests/compose.dumbclient.yaml b/interop-tests/compose.dumbclient.yaml index e6c4278..8ce768e 100644 --- a/interop-tests/compose.dumbclient.yaml +++ b/interop-tests/compose.dumbclient.yaml @@ -32,7 +32,7 @@ services: dumbclient-netns: condition: service_started - # S9's comparison run: window below maxHeldMessages (1000, session-options.ts), where nothing + # S9's comparison run: window below maxHeldMessages (1000, options.ts), where nothing # should ever be throttled - see findings/07-load.md. dumbclient-w500: build: ./interop-tests/peers/dumbclient diff --git a/interop-tests/dumbclient.test.ts b/interop-tests/dumbclient.test.ts index 81bc9ff..2edcce9 100644 --- a/interop-tests/dumbclient.test.ts +++ b/interop-tests/dumbclient.test.ts @@ -3,7 +3,7 @@ import test, { after, describe } from 'node:test'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; import type { LogMethod, SmppLog } from '../src/log.ts'; -import { server } from '../src/server.ts'; +import { server } from '../src/server/server.ts'; const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775'); /** Slower than every scenario's submission rate (2000/s for the window runs), so a real backlog @@ -206,7 +206,7 @@ after(async () => { // S9 (target 11) and the backpressure-at-server scenario: window 2000 at a high rate against a // handler slowed enough to build a real backlog. window500 is the same shape with a window below -// maxHeldMessages (1000, session-options.ts defaults.maxHeldMessages), the bound past which a +// maxHeldMessages (1000, options.ts defaults.maxHeldMessages), the bound past which a // peer's window is answered ESME_RTHROTTLED. smpp-dumb-client counts a throttled message as sent // and never resends it, so window 2000 accounts for 20,000 as answered plus throttled. const throttleMessage = 'session - unanswered messages at their bound, asking the peer to retry'; @@ -270,7 +270,7 @@ describe('S6 - idle peer, no enquire_link at all', () => { assert.ok(bound, 'dumb-idle never submitted its one message'); // idleTimeout is 40s from the last byte the peer sent (its submit_sm), never from our own - // writes (link-timers.ts resets only on inbound data). This test may start running well + // writes (session/keepalive.ts resets only on inbound data). This test may start running well // past that mark on its own (S9 above can take a minute) - statsFor(...).closed is set from // a 'close' listener attached at session-creation time, so a close from before this test // even started is still seen; budget is slack for a session that is still open, not a clock. diff --git a/interop-tests/jasmin.test.ts b/interop-tests/jasmin.test.ts index c6c9b92..a835572 100644 --- a/interop-tests/jasmin.test.ts +++ b/interop-tests/jasmin.test.ts @@ -1,18 +1,18 @@ import assert from 'node:assert/strict'; import http from 'node:http'; import test, { after, describe } from 'node:test'; -import type { Dlr } from '../src/dlr.ts'; -import type { EncodingName } from '../src/defs/encodings.ts'; -import type { PduObject } from '../src/pdu.ts'; +import type { Dlr } from '../src/protocol/receipt.ts'; +import type { EncodingName } from '../src/codec/encodings.ts'; +import type { PduObject } from '../src/codec/pdu.ts'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; -import { ConcatReference } from '../src/udh.ts'; -import { client } from '../src/client.ts'; +import { ConcatReference } from '../src/protocol/udh.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter } from '../test/teardown.ts'; -import { paramText } from '../src/defs/types.ts'; -import { server } from '../src/server.ts'; +import { paramText } from '../src/codec/field-types.ts'; +import { server } from '../src/server/server.ts'; import { encodeMessage, splitMessage } from '../src/message.ts'; -import { submitSmParams } from '../src/send-sms.ts'; +import { submitSmParams } from '../src/messages/submit.ts'; const PEER_HOST = process.env.PEER_HOST ?? 'jasmin'; const PEER_PORT = Number(process.env.PEER_PORT ?? '2775'); diff --git a/interop-tests/jsmpp.test.ts b/interop-tests/jsmpp.test.ts index db96513..ed2ba0b 100644 --- a/interop-tests/jsmpp.test.ts +++ b/interop-tests/jsmpp.test.ts @@ -5,7 +5,7 @@ import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; import { PduRefusedError } from '../src/index.ts'; import { bareTlvHeader, pduBytes } from '../test/raw-pdus.ts'; -import { server } from '../src/server.ts'; +import { server } from '../src/server/server.ts'; const JSMPP_HOST = process.env.JSMPP_HOST ?? 'jsmpp:8080'; const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775'); diff --git a/interop-tests/kannel.test.ts b/interop-tests/kannel.test.ts index 0d911d0..4ce96be 100644 --- a/interop-tests/kannel.test.ts +++ b/interop-tests/kannel.test.ts @@ -1,17 +1,17 @@ import assert from 'node:assert/strict'; import http from 'node:http'; import test, { after, describe } from 'node:test'; -import type { MessageState } from '../src/defs/constants.ts'; -import type { Dlr } from '../src/dlr.ts'; +import type { MessageState } from '../src/codec/constants.ts'; +import type { Dlr } from '../src/protocol/receipt.ts'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; -import { ConcatReference } from '../src/udh.ts'; -import { consts } from '../src/defs/constants.ts'; -import { detect, encodings } from '../src/defs/encodings.ts'; -import { paramText } from '../src/defs/types.ts'; -import { server } from '../src/server.ts'; +import { ConcatReference } from '../src/protocol/udh.ts'; +import { consts } from '../src/codec/constants.ts'; +import { detect, encodings } from '../src/codec/encodings.ts'; +import { paramText } from '../src/codec/field-types.ts'; +import { server } from '../src/server/server.ts'; import { splitMessage } from '../src/message.ts'; -import { submitSmParams } from '../src/send-sms.ts'; +import { submitSmParams } from '../src/messages/submit.ts'; // smsbox HTTP hosts, one per variant - all point at the same node:2775 SMPP server. const MAIN_SMSBOX = process.env.MAIN_SMSBOX ?? 'kannel-smsbox:13013'; diff --git a/interop-tests/php.test.ts b/interop-tests/php.test.ts index 0415ce9..761f138 100644 --- a/interop-tests/php.test.ts +++ b/interop-tests/php.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import test, { after, describe } from 'node:test'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; -import { paramText } from '../src/defs/types.ts'; +import { paramText } from '../src/codec/field-types.ts'; import { isCommand, server } from '../src/index.ts'; const DRIVER = process.env.PHP_DRIVER ?? 'php:8080'; diff --git a/interop-tests/python.test.ts b/interop-tests/python.test.ts index 84f1c66..138b897 100644 --- a/interop-tests/python.test.ts +++ b/interop-tests/python.test.ts @@ -2,8 +2,8 @@ import assert from 'node:assert/strict'; import test, { after, describe } from 'node:test'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; -import { encodings } from '../src/defs/encodings.ts'; -import { paramText } from '../src/defs/types.ts'; +import { encodings } from '../src/codec/encodings.ts'; +import { paramText } from '../src/codec/field-types.ts'; import { isCommand, server } from '../src/index.ts'; const DRIVER = process.env.PYTHON_DRIVER ?? 'python:8080'; diff --git a/interop-tests/smppload.test.ts b/interop-tests/smppload.test.ts index cd1cd9d..a8ebe1b 100644 --- a/interop-tests/smppload.test.ts +++ b/interop-tests/smppload.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import test, { after, describe } from 'node:test'; import type { Session } from '../src/session.ts'; -import { server } from '../src/server.ts'; +import { server } from '../src/server/server.ts'; const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775'); @@ -54,7 +54,7 @@ describe('smppload (blocked)', () => { const refusal = await waitFor(() => sessionErr, 10_000); assert.ok(refusal); - // maxPduLength (pdu-refusal.ts) is 1MiB; the corrupted command_length (0x2a shifted into the + // maxPduLength (codec/refusal.ts) is 1MiB; the corrupted command_length (0x2a shifted into the // high bytes) reads as roughly 2.75M, so this is the "unreadable stream" teardown, not the // "one bad PDU, link stays up" path - see AGENTS.md, "A stream this library cannot frame...". assert.match(refusal.message, /Refusing a cmd_length of \d+/); diff --git a/interop-tests/smppsim.test.ts b/interop-tests/smppsim.test.ts index 8a08817..141eda3 100644 --- a/interop-tests/smppsim.test.ts +++ b/interop-tests/smppsim.test.ts @@ -1,16 +1,16 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { Dlr } from '../src/dlr.ts'; -import type { EncodingName } from '../src/defs/encodings.ts'; -import type { MessageDlr } from '../src/dlr-merger.ts'; -import type { PduObject } from '../src/pdu.ts'; +import type { Dlr } from '../src/protocol/receipt.ts'; +import type { EncodingName } from '../src/codec/encodings.ts'; +import type { MessageDlr } from '../src/messages/receipt-merge.ts'; +import type { PduObject } from '../src/codec/pdu.ts'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; -import { client } from '../src/client.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter } from '../test/teardown.ts'; -import { consts } from '../src/defs/constants.ts'; -import { paramText } from '../src/defs/types.ts'; -import { server } from '../src/server.ts'; +import { consts } from '../src/codec/constants.ts'; +import { paramText } from '../src/codec/field-types.ts'; +import { server } from '../src/server/server.ts'; const PEER_HOST = process.env.PEER_HOST ?? 'smppsim'; const PEER_PORT = Number(process.env.PEER_PORT ?? '2775'); diff --git a/interop-tests/smscsim.test.ts b/interop-tests/smscsim.test.ts index ba23042..35bbb0f 100644 --- a/interop-tests/smscsim.test.ts +++ b/interop-tests/smscsim.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { Dlr } from '../src/dlr.ts'; +import type { Dlr } from '../src/protocol/receipt.ts'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; -import { client } from '../src/client.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter } from '../test/teardown.ts'; const PEER_HOST = process.env.PEER_HOST ?? 'smscsim'; diff --git a/src/client.ts b/src/client/client.ts similarity index 92% rename from src/client.ts rename to src/client/client.ts index 6b7299c..d2e36eb 100644 --- a/src/client.ts +++ b/src/client/client.ts @@ -1,18 +1,18 @@ import type { ConnectionOptions } from 'node:tls'; -import type { Result, VoidResult } from './result.ts'; -import type { BindType, ReconnectOptions } from './session-options.ts'; -import type { SmppLog } from './log.ts'; -import type { SmsIdFormat } from './sms-id.ts'; +import type { Result, VoidResult } from '../result.ts'; +import type { BindType } from '../protocol/bind.ts'; +import type { ReconnectOptions } from '../options.ts'; +import type { SmppLog } from '../log.ts'; +import type { SmsIdFormat } from '../protocol/message-ids.ts'; import type { Socket } from 'node:net'; export type { BindType }; -import { ReconnectLoop } from './reconnect-loop.ts'; -import { Session } from './session.ts'; -import { checkSessionOptions } from './session-options.ts'; +import { ReconnectLoop } from '../reconnect-loop.ts'; +import { Session } from '../session.ts'; +import { checkSessionOptions, defaults } from '../options.ts'; import { connect as netConnect } from 'node:net'; import { connect as tlsConnect } from 'node:tls'; -import { defaultInterfaceVersion } from './defs/constants.ts'; -import { guardedLog } from './log.ts'; +import { guardedLog } from '../log.ts'; /** `fromStart` puts the very first connect and bind through the same backoff loop as a drop. */ type ReconnectTuning = { fromStart?: boolean; maxDelay?: number; minDelay?: number }; @@ -41,18 +41,6 @@ export type ClientOptions = { username?: string; }; -const defaults = { - bindType: 'transceiver', - connectTimeout: 10_000, - enquireLinkInterval: 20_000, - host: 'localhost', - /** The idle timeout is what notices a dead link, so it has to outlast one silent probe. */ - idleTimeoutFactor: 2, - interfaceVersion: defaultInterfaceVersion, - password: 'pass', - port: 2775, - username: 'user', -} as const; function armConnectTimeout( sock: Socket, diff --git a/src/defs/commands.ts b/src/codec/commands.ts similarity index 98% rename from src/defs/commands.ts rename to src/codec/commands.ts index 13ebf82..1be9a60 100644 --- a/src/defs/commands.ts +++ b/src/codec/commands.ts @@ -1,5 +1,5 @@ -import type { WireType } from './types.ts'; -import { buffer, cstring, dest_address_array, int8, unsuccess_sme_array } from './types.ts'; +import type { WireType } from './field-types.ts'; +import { buffer, cstring, dest_address_array, int8, unsuccess_sme_array } from './field-types.ts'; type CommandSpec = { id: number; diff --git a/src/defs/constants.ts b/src/codec/constants.ts similarity index 96% rename from src/defs/constants.ts rename to src/codec/constants.ts index 0ed155e..d7ea22e 100644 --- a/src/defs/constants.ts +++ b/src/codec/constants.ts @@ -1,6 +1,3 @@ -/** The version declared on the wire. The tables below cover 5.0, which is a superset of it. */ -export const defaultInterfaceVersion = 0x34; - /** Spec rule, not a preference: a peer declaring less than 3.4 is sent no optional parameters. */ export const optionalParamsMinVersion = 0x34; diff --git a/src/defs/encodings.ts b/src/codec/encodings.ts similarity index 100% rename from src/defs/encodings.ts rename to src/codec/encodings.ts diff --git a/src/defs/types.ts b/src/codec/field-types.ts similarity index 100% rename from src/defs/types.ts rename to src/codec/field-types.ts diff --git a/src/pdu-framer.ts b/src/codec/framer.ts similarity index 94% rename from src/pdu-framer.ts rename to src/codec/framer.ts index 1b23dea..4c83f74 100644 --- a/src/pdu-framer.ts +++ b/src/codec/framer.ts @@ -1,5 +1,5 @@ -import type { Result } from './result.ts'; -import { framingRefusal } from './pdu-refusal.ts'; +import type { Result } from '../result.ts'; +import { framingRefusal } from './refusal.ts'; /** * Cuts a byte stream into whole PDUs. diff --git a/src/pdu.ts b/src/codec/pdu.ts similarity index 94% rename from src/pdu.ts rename to src/codec/pdu.ts index 743cbed..b27c7cc 100644 --- a/src/pdu.ts +++ b/src/codec/pdu.ts @@ -1,16 +1,16 @@ -import type { CommandDefinition, CommandName, PduParams, PduParamsInput } from './defs/commands.ts'; -import type { ErrorName } from './defs/errors.ts'; -import type { ParamValue } from './defs/types.ts'; -import type { PduHeader } from './pdu-refusal.ts'; -import type { Result, VoidResult } from './result.ts'; -import type { TlvInputs, Tlvs } from './defs/tlvs.ts'; -import { PduRefusedError, framingRefusal } from './pdu-refusal.ts'; -import { cmds, commandNameById, respNameFor } from './defs/commands.ts'; -import { hasUdh } from './defs/constants.ts'; -import { decodeMessage, encodeBody } from './message.ts'; -import { errorNameById, errors, isErrorName } from './defs/errors.ts'; -import { paramNumber, valueText } from './defs/types.ts'; -import { parseTlvs, writeTlvs } from './defs/tlvs.ts'; +import type { CommandDefinition, CommandName, PduParams, PduParamsInput } from './commands.ts'; +import type { ErrorName } from './statuses.ts'; +import type { ParamValue } from './field-types.ts'; +import type { PduHeader } from './refusal.ts'; +import type { Result, VoidResult } from '../result.ts'; +import type { TlvInputs, Tlvs } from './tlvs.ts'; +import { PduRefusedError, framingRefusal } from './refusal.ts'; +import { cmds, commandNameById, respNameFor } from './commands.ts'; +import { hasUdh } from './constants.ts'; +import { decodeMessage, encodeBody } from '../message.ts'; +import { errorNameById, errors, isErrorName } from './statuses.ts'; +import { paramNumber, valueText } from './field-types.ts'; +import { parseTlvs, writeTlvs } from './tlvs.ts'; /** The highest sequence number this library hands out; SMPP 3.4 4.7.1 reserves 0x7fffffff. */ export const maxSeqNr = 2147483646; diff --git a/src/pdu-refusal.ts b/src/codec/refusal.ts similarity index 92% rename from src/pdu-refusal.ts rename to src/codec/refusal.ts index 45b7a2a..317b69d 100644 --- a/src/pdu-refusal.ts +++ b/src/codec/refusal.ts @@ -1,6 +1,6 @@ -import type { CommandName } from './defs/commands.ts'; -import type { ErrorName } from './defs/errors.ts'; -import { respNameFor } from './defs/commands.ts'; +import type { CommandName } from './commands.ts'; +import type { ErrorName } from './statuses.ts'; +import { respNameFor } from './commands.ts'; /** A hostile peer must not be able to make us allocate arbitrarily. */ export const maxPduLength = 1024 * 1024; diff --git a/src/retained-pdu.ts b/src/codec/retained.ts similarity index 94% rename from src/retained-pdu.ts rename to src/codec/retained.ts index c46e2c9..9fda3cc 100644 --- a/src/retained-pdu.ts +++ b/src/codec/retained.ts @@ -1,6 +1,6 @@ -import type { ParamValue } from './defs/types.ts'; +import type { ParamValue } from './field-types.ts'; import type { PduObject } from './pdu.ts'; -import { tlvOctets } from './defs/types.ts'; +import { tlvOctets } from './field-types.ts'; /** Wire reads hand back views, so retaining one PDU would pin the whole chunk it arrived in. */ export function detach(pduObj: PduObject): PduObject { diff --git a/src/defs/errors.ts b/src/codec/statuses.ts similarity index 100% rename from src/defs/errors.ts rename to src/codec/statuses.ts diff --git a/src/defs/tlvs.ts b/src/codec/tlvs.ts similarity index 99% rename from src/defs/tlvs.ts rename to src/codec/tlvs.ts index fc4d6b0..d54d57e 100644 --- a/src/defs/tlvs.ts +++ b/src/codec/tlvs.ts @@ -1,6 +1,6 @@ -import type { ParamValue, TlvValue, WireType } from './types.ts'; +import type { ParamValue, TlvValue, WireType } from './field-types.ts'; import type { Result } from '../result.ts'; -import { tlv } from './types.ts'; +import { tlv } from './field-types.ts'; /** Only a tag read as octets or as a number may repeat, since its occurrences are listed as one of those. */ type Definition = { id: number; multiple?: false; tag: Tag; type: WireType } diff --git a/src/defs/index.ts b/src/defs/index.ts deleted file mode 100644 index 311a5c6..0000000 --- a/src/defs/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { cmds, cmdsById } from './commands.ts'; -import { consts, constsById } from './constants.ts'; -import { encodings } from './encodings.ts'; -import { errors, errorsById } from './errors.ts'; -import { tlvs, tlvsById } from './tlvs.ts'; -import { types } from './types.ts'; - -export const defs = { - cmds, - cmdsById, - consts, - constsById, - encodings, - errors, - errorsById, - tlvs, - tlvsById, - types, -}; diff --git a/src/error-from.ts b/src/error-from.ts deleted file mode 100644 index 5d77236..0000000 --- a/src/error-from.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** Whatever was thrown or rejected, as an Error. `String()` throws on some values; this cannot. */ -export function errorFrom(reason: unknown): Error { - if (reason instanceof Error) return reason; - - try { - return new Error(String(reason)); - } catch { - return new Error('A thrown value that cannot be converted to a string'); - } -} - -const printable: readonly string[] = ['boolean', 'number', 'string']; - -/** String() throws on a null-prototype object, so anything but these is named by its type. */ -export function namedValue(value: unknown): string { - return printable.includes(typeof value) ? String(value) : typeof value; -} diff --git a/src/index.ts b/src/index.ts index 71fa973..80ca7d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,20 @@ -export { client } from './client.ts'; -export { server, SmppServer } from './server.ts'; +import { cmds, cmdsById } from './codec/commands.ts'; +import { consts, constsById } from './codec/constants.ts'; +import { encodings } from './codec/encodings.ts'; +import { errors, errorsById } from './codec/statuses.ts'; +import { tlvs, tlvsById } from './codec/tlvs.ts'; +import { types } from './codec/field-types.ts'; + +export { client } from './client/client.ts'; +export { server, SmppServer } from './server/server.ts'; export { Session } from './session.ts'; -export { cmds, cmdsById, commandNameById, isCommandName } from './defs/commands.ts'; -export { consts, constsById } from './defs/constants.ts'; -export { dataCodingByEncoding, detect, encodingByDataCoding, encodings, isEncodingName, messageClassOf, unencodable } from './defs/encodings.ts'; -export { errorNameById, errors, errorsById, isErrorName } from './defs/errors.ts'; -export { isTlvName, tlvs, tlvsById } from './defs/tlvs.ts'; -export { types } from './defs/types.ts'; +export { cmds, cmdsById, commandNameById, isCommandName } from './codec/commands.ts'; +export { consts, constsById } from './codec/constants.ts'; +export { dataCodingByEncoding, detect, encodingByDataCoding, encodings, isEncodingName, messageClassOf, unencodable } from './codec/encodings.ts'; +export { errorNameById, errors, errorsById, isErrorName } from './codec/statuses.ts'; +export { isTlvName, tlvs, tlvsById } from './codec/tlvs.ts'; +export { types } from './codec/field-types.ts'; export { isCommand, @@ -16,9 +23,9 @@ export { objToPdu, pduReturn, pduToObj, -} from './pdu.ts'; +} from './codec/pdu.ts'; -export { maxPduLength, PduRefusedError } from './pdu-refusal.ts'; +export { maxPduLength, PduRefusedError } from './codec/refusal.ts'; export { bitCount, @@ -29,27 +36,27 @@ export { splitMessage, } from './message.ts'; -export { dlrFromPdu, parseReceipt, receiptCodes } from './dlr.ts'; -export { messageOctets } from './message-body.ts'; -export { concatOf } from './concat.ts'; -export { concatInfo } from './udh.ts'; -export { PduFramer } from './pdu-framer.ts'; -export { uuidv7 } from './uuid.ts'; +export { dlrFromPdu, parseReceipt, receiptCodes } from './protocol/receipt.ts'; +export { messageOctets } from './protocol/message-body.ts'; +export { concatOf } from './protocol/concat.ts'; +export { concatInfo } from './protocol/udh.ts'; +export { PduFramer } from './codec/framer.ts'; +export { uuidv7 } from './protocol/uuid.ts'; -export type { BindType, ClientOptions } from './client.ts'; -export type { Dlr, Receipt } from './dlr.ts'; +export type { BindType, ClientOptions } from './client/client.ts'; +export type { Dlr, Receipt } from './protocol/receipt.ts'; export type { SendDlrResult, SendRespOptions, Sms } from './sms.ts'; -export type { Concat } from './concat.ts'; -export type { ConcatInfo } from './udh.ts'; +export type { Concat } from './protocol/concat.ts'; +export type { ConcatInfo } from './protocol/udh.ts'; export type { Result, VoidResult } from './result.ts'; export type { SmppLog } from './log.ts'; -export type { SmsIdFormat, SmsIdNotation } from './sms-id.ts'; +export type { SmsIdFormat, SmsIdNotation } from './protocol/message-ids.ts'; export type { AuthenticateInput, AuthenticateResult, ServerEvents, ServerOptions, -} from './server.ts'; +} from './server/server.ts'; export type { CloseOptions, MessageDlr, @@ -60,15 +67,26 @@ export type { SessionEvents, SessionOptions, } from './session.ts'; -export type { CommandName, PduParams, PduParamsInput } from './defs/commands.ts'; -export type { ConstGroup, MessageState, SubmitMessagingMode } from './defs/constants.ts'; -export type { Encoding, EncodingName, Unencodable } from './defs/encodings.ts'; -export type { ErrorName } from './defs/errors.ts'; -export type { PduObject, PduObjectInput, TlvInputs } from './pdu.ts'; -export type { PduHeader } from './pdu-refusal.ts'; +export type { CommandName, PduParams, PduParamsInput } from './codec/commands.ts'; +export type { ConstGroup, MessageState, SubmitMessagingMode } from './codec/constants.ts'; +export type { Encoding, EncodingName, Unencodable } from './codec/encodings.ts'; +export type { ErrorName } from './codec/statuses.ts'; +export type { PduObject, PduObjectInput, TlvInputs } from './codec/pdu.ts'; +export type { PduHeader } from './codec/refusal.ts'; export type { SplitOptions } from './message.ts'; -export type { Tlv, TlvDefinition, TlvName, Tlvs } from './defs/tlvs.ts'; -export type { DestAddress, ParamValue, TlvValue, UnsuccessSme, WireType } from './defs/types.ts'; +export type { Tlv, TlvDefinition, TlvName, Tlvs } from './codec/tlvs.ts'; +export type { DestAddress, ParamValue, TlvValue, UnsuccessSme, WireType } from './codec/field-types.ts'; /** The spec tables, grouped the way `larvitsmpp.defs` was in 0.4.0. */ -export { defs } from './defs/index.ts'; +export const defs = { + cmds, + cmdsById, + consts, + constsById, + encodings, + errors, + errorsById, + tlvs, + tlvsById, + types, +}; diff --git a/src/message.ts b/src/message.ts index 0665019..54e8f46 100644 --- a/src/message.ts +++ b/src/message.ts @@ -1,8 +1,8 @@ import type { Result } from './result.ts'; -import type { EncodingName } from './defs/encodings.ts'; -import { dataCodingByEncoding, detect, encodingByDataCoding, encodings, unencodable, unencodableText } from './defs/encodings.ts'; -import { hasUdh } from './defs/constants.ts'; -import { udhLength } from './udh.ts'; +import type { EncodingName } from './codec/encodings.ts'; +import { dataCodingByEncoding, detect, encodingByDataCoding, encodings, unencodable, unencodableText } from './codec/encodings.ts'; +import { hasUdh } from './codec/constants.ts'; +import { udhLength } from './protocol/udh.ts'; /** A single SMS carries 1120 bits, whatever the alphabet. */ const singleMessageBits = 1120; diff --git a/src/expiring-groups.ts b/src/messages/expiring-groups.ts similarity index 100% rename from src/expiring-groups.ts rename to src/messages/expiring-groups.ts diff --git a/src/reassembly.ts b/src/messages/reassembly.ts similarity index 92% rename from src/reassembly.ts rename to src/messages/reassembly.ts index 4f3d2c5..97c73ff 100644 --- a/src/reassembly.ts +++ b/src/messages/reassembly.ts @@ -1,12 +1,13 @@ -import type { Concat } from './concat.ts'; -import type { PduObject } from './pdu.ts'; -import type { SmppLog } from './log.ts'; +import type { Concat } from '../protocol/concat.ts'; +import type { PduObject } from '../codec/pdu.ts'; +import type { SmppLog } from '../log.ts'; import { ExpiringGroups } from './expiring-groups.ts'; -import { decodeMessage } from './message.ts'; -import { detach, retainedOctets } from './retained-pdu.ts'; -import { messageOctets } from './message-body.ts'; -import { paramNumber, paramText } from './defs/types.ts'; -import { uuidv7 } from './uuid.ts'; +import { decodeMessage } from '../message.ts'; +import { defaults } from '../options.ts'; +import { detach, retainedOctets } from '../codec/retained.ts'; +import { messageOctets } from '../protocol/message-body.ts'; +import { paramNumber, paramText } from '../codec/field-types.ts'; +import { uuidv7 } from '../protocol/uuid.ts'; /** A concatenated message given up on, whose segments the peer has already been answered for. */ export type LostGroup = { @@ -42,8 +43,6 @@ export type Collected = whole?: PduObject[] | undefined; }; -export const defaultMaxOctets = 64 * 1024 * 1024; - type Group = { parts: Map; smsId: string; @@ -89,7 +88,7 @@ export class Reassembler { private readonly onLost: (lost: LostGroup) => void; constructor(options: ReassemblerOptions) { - this.maxOctets = options.maxOctets ?? defaultMaxOctets; + this.maxOctets = options.maxOctets ?? defaults.maxOctets; this.groups = new ExpiringGroups({ max: options.max, maxWeight: this.maxOctets, diff --git a/src/dlr-merger.ts b/src/messages/receipt-merge.ts similarity index 95% rename from src/dlr-merger.ts rename to src/messages/receipt-merge.ts index 0c20cae..541ecd7 100644 --- a/src/dlr-merger.ts +++ b/src/messages/receipt-merge.ts @@ -1,8 +1,8 @@ -import type { Dlr } from './dlr.ts'; -import type { MessageState } from './defs/constants.ts'; -import type { SmppLog } from './log.ts'; +import type { Dlr } from '../protocol/receipt.ts'; +import type { MessageState } from '../codec/constants.ts'; +import type { SmppLog } from '../log.ts'; import { ExpiringGroups } from './expiring-groups.ts'; -import { parseSegmentId } from './sms-id.ts'; +import { parseSegmentId } from '../protocol/message-ids.ts'; export type MessageDlr = Dlr & { segments: Dlr[]; smsId: string }; diff --git a/src/send-sms.ts b/src/messages/submit.ts similarity index 93% rename from src/send-sms.ts rename to src/messages/submit.ts index 2e70932..4a65858 100644 --- a/src/send-sms.ts +++ b/src/messages/submit.ts @@ -1,17 +1,17 @@ -import type { EncodingName, Unencodable } from './defs/encodings.ts'; -import type { ParamValue } from './defs/types.ts'; -import type { SubmitMessagingMode } from './defs/constants.ts'; -import type { PduObject, PduObjectInput } from './pdu.ts'; -import type { Result } from './result.ts'; -import type { SmppLog } from './log.ts'; -import type { SmsIdNotation } from './sms-id.ts'; -import { UnansweredError } from './unanswered-error.ts'; -import { consts, defaultMessagingMode, isMessagingMode, isSubmitMessagingMode, submitMessagingModes } from './defs/constants.ts'; -import { cstring, paramText } from './defs/types.ts'; -import { dataCodingByEncoding, detect, encodingNames, isEncodingName, unencodable, unencodableText } from './defs/encodings.ts'; -import { namedValue } from './error-from.ts'; -import { normaliseSmsId } from './sms-id.ts'; -import { maxSegments, smppTime, splitMessage } from './message.ts'; +import type { EncodingName, Unencodable } from '../codec/encodings.ts'; +import type { ParamValue } from '../codec/field-types.ts'; +import type { SubmitMessagingMode } from '../codec/constants.ts'; +import type { PduObject, PduObjectInput } from '../codec/pdu.ts'; +import type { Result } from '../result.ts'; +import type { SmppLog } from '../log.ts'; +import type { SmsIdNotation } from '../protocol/message-ids.ts'; +import { UnansweredError } from '../unanswered-error.ts'; +import { consts, defaultMessagingMode, isMessagingMode, isSubmitMessagingMode, submitMessagingModes } from '../codec/constants.ts'; +import { cstring, paramText } from '../codec/field-types.ts'; +import { dataCodingByEncoding, detect, encodingNames, isEncodingName, unencodable, unencodableText } from '../codec/encodings.ts'; +import { namedValue } from '../result.ts'; +import { normaliseSmsId } from '../protocol/message-ids.ts'; +import { maxSegments, smppTime, splitMessage } from '../message.ts'; export type SendSmsOptions = { dlr?: boolean; diff --git a/src/session-options.ts b/src/options.ts similarity index 65% rename from src/session-options.ts rename to src/options.ts index 0b768c9..79e28a3 100644 --- a/src/session-options.ts +++ b/src/options.ts @@ -1,77 +1,11 @@ -import type { Dlr } from './dlr.ts'; -import type { MessageDlr } from './dlr-merger.ts'; -import type { PduObject } from './pdu.ts'; -import type { PduRefusedError } from './pdu-refusal.ts'; +import type { PduObject } from './codec/pdu.ts'; import type { Result, VoidResult } from './result.ts'; import type { Session } from './session.ts'; import type { SmppLog } from './log.ts'; -import type { SmsIdFormat } from './sms-id.ts'; -import type { Sms } from './sms.ts'; +import type { SmsIdFormat } from './protocol/message-ids.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'; - -export type SessionEvents = { - close: []; - data: [Buffer]; - disconnected: []; - dlr: [Dlr, PduObject]; - incomingPdu: [Buffer]; - incomingPduObj: [PduObject]; - messageDlr: [MessageDlr]; - reconnected: []; - sessionError: [Error | PduRefusedError]; - sms: [Sms]; -}; - -export const bindCommands: readonly string[] = [ - 'bind_receiver', - 'bind_transceiver', - 'bind_transmitter', -]; - -export type BindType = 'receiver' | 'transceiver' | 'transmitter'; - -/** Which end of the link a session is. Only `server()` is the SMSC; everything else is the ESME. */ -export type LinkEnd = 'esme' | 'smsc'; - -export function bindTypeFromCommand(cmdName: string): BindType | undefined { - if (cmdName === 'bind_receiver') return 'receiver'; - if (cmdName === 'bind_transceiver') return 'transceiver'; - if (cmdName === 'bind_transmitter') return 'transmitter'; - - return undefined; -} - -/** - * Which message-carrying command an inbound one stands in for. Every command but `data_sm` names - * its own direction; that one travels either way, so the end it arrived at is what says. - */ -export function standsInFor(cmdName: string, linkEnd: LinkEnd): string { - if (cmdName !== 'data_sm') return cmdName; - - return linkEnd === 'smsc' ? 'submit_sm' : 'deliver_sm'; -} - -/** - * Whether a bind direction carries a command at all. A receiver-bound ESME submits nothing and a - * transmitter-bound one is delivered nothing, whichever end of the link is looking. A session that - * has not bound carries everything, since nothing has declared a direction yet. - */ -export function bindCarries( - bindType: BindType | undefined, - cmdName: string, - linkEnd: LinkEnd, -): boolean { - const carried = standsInFor(cmdName, linkEnd); - - if (bindType === 'receiver') return carried !== 'submit_sm'; - if (bindType === 'transmitter') return carried !== 'deliver_sm'; - - return true; -} +import { isSmsIdNotation, smsIdNotations, smsIdPlaces } from './protocol/message-ids.ts'; +import { namedValue, quoted } from './result.ts'; export type SendOptions = { signal?: AbortSignal | undefined }; @@ -116,51 +50,36 @@ export type SessionOptions = { systemId?: string | undefined; }; -export const defaultSystemId = ''; - -/** SMPP 3.4: a peer that declares no version at all is one from before optional parameters. */ -export const undeclaredInterfaceVersion = 0x00; - -export type SessionBind = { as: BindType; peerVersion: number }; - -function quoted(value: unknown): string { - return typeof value === 'string' ? JSON.stringify(value) : namedValue(value); -} - -function isBindType(value: unknown): value is BindType { - return typeof value === 'string' && bindTypeFromCommand(`bind_${value}`) !== undefined; -} - -/** A bind as `Session.bound()` records it: undefined declares no version, which is pre-3.4. */ -export function checkedBind(bindType: unknown, declaredVersion: unknown): Result<{ bind: SessionBind }> { - if (!isBindType(bindType)) { - return { err: new Error(`bindType must be receiver, transceiver or transmitter, the bind command's name without "bind_", got ${quoted(bindType)}`) }; - } - - if (declaredVersion === undefined) return { bind: { as: bindType, peerVersion: undeclaredInterfaceVersion } }; - - if (typeof declaredVersion !== 'number' || !Number.isInteger(declaredVersion) || declaredVersion < 0 || declaredVersion > 0xFF) { - return { err: new Error(`declaredVersion must be an integer 0-255, the interface_version param or the sc_interface_version TLV's tagValue, or undefined where the peer declared none, got ${quoted(declaredVersion)}`) }; - } - - return { bind: { as: bindType, peerVersion: declaredVersion } }; -} - export const defaults = { + bindType: 'transceiver', + connectTimeout: 10_000, /** Receipts of a multipart message can be a working day apart, so the cap does the bounding. */ dlrMergeTimeout: 86_400_000, + enquireLinkInterval: 20_000, /** The peer gave up on an unanswered message long before this; the bound is against growth. */ heldMessageTimeout: 300_000, + host: 'localhost', + /** The idle timeout is what notices a dead link, so it has to outlast one silent probe. */ + idleTimeoutFactor: 2, + /** The version declared on the wire. */ + interfaceVersion: 0x34, + maxDelay: 30_000, maxDlrMerges: 1000, maxHeldMessages: 1000, maxHeldOctets: 64 * 1024 * 1024, + maxOctets: 64 * 1024 * 1024, maxOutstanding: 10, maxReassembly: 1000, + minDelay: 1000, + password: 'pass', + port: 2775, reassemblyTimeout: 300_000, responseTimeout: 30_000, + serverIdleTimeout: 40_000, shutdownTimeout: 5000, - systemId: defaultSystemId, -}; + systemId: '', + username: 'user', +} as const; /** * A count below 1 does not fail loudly anywhere downstream: `maxOutstanding: 0` leaves every send @@ -187,7 +106,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], + ['maxOctets', options.maxOctets ?? defaults.maxOctets, 1], ['maxOutstanding', options.maxOutstanding ?? defaults.maxOutstanding, 1], ['maxReassembly', options.maxReassembly ?? defaults.maxReassembly, 1], ['reassemblyTimeout', options.reassemblyTimeout ?? defaults.reassemblyTimeout, 0], @@ -243,8 +162,8 @@ function checkReconnect(reconnect: unknown): VoidResult { return { err: new Error(`reconnect.fromStart must be true or false, got ${typeof reconnect.fromStart}`) }; } - const maxDelay = delayOr(reconnect.maxDelay, backoffDefaults.maxDelay); - const minDelay = delayOr(reconnect.minDelay, backoffDefaults.minDelay); + const maxDelay = delayOr(reconnect.maxDelay, defaults.maxDelay); + const minDelay = delayOr(reconnect.minDelay, defaults.minDelay); // A delay of 0 never doubles, so the backoff never starts and every retry lands at once. const checked = checkLimits([['maxDelay', maxDelay, 1], ['minDelay', minDelay, 1]]); diff --git a/src/protocol/bind.ts b/src/protocol/bind.ts new file mode 100644 index 0000000..29f1d1d --- /dev/null +++ b/src/protocol/bind.ts @@ -0,0 +1,73 @@ +import type { Result } from '../result.ts'; +import { quoted } from '../result.ts'; + +export const bindCommands: readonly string[] = [ + 'bind_receiver', + 'bind_transceiver', + 'bind_transmitter', +]; + +export type BindType = 'receiver' | 'transceiver' | 'transmitter'; + +/** Which end of the link a session is. Only `server()` is the SMSC; everything else is the ESME. */ +export type LinkEnd = 'esme' | 'smsc'; + +export function bindTypeFromCommand(cmdName: string): BindType | undefined { + if (cmdName === 'bind_receiver') return 'receiver'; + if (cmdName === 'bind_transceiver') return 'transceiver'; + if (cmdName === 'bind_transmitter') return 'transmitter'; + + return undefined; +} + +/** + * Which message-carrying command an inbound one stands in for. Every command but `data_sm` names + * its own direction; that one travels either way, so the end it arrived at is what says. + */ +export function standsInFor(cmdName: string, linkEnd: LinkEnd): string { + if (cmdName !== 'data_sm') return cmdName; + + return linkEnd === 'smsc' ? 'submit_sm' : 'deliver_sm'; +} + +/** + * Whether a bind direction carries a command at all. A receiver-bound ESME submits nothing and a + * transmitter-bound one is delivered nothing, whichever end of the link is looking. A session that + * has not bound carries everything, since nothing has declared a direction yet. + */ +export function bindCarries( + bindType: BindType | undefined, + cmdName: string, + linkEnd: LinkEnd, +): boolean { + const carried = standsInFor(cmdName, linkEnd); + + if (bindType === 'receiver') return carried !== 'submit_sm'; + if (bindType === 'transmitter') return carried !== 'deliver_sm'; + + return true; +} + +/** SMPP 3.4: a peer that declares no version at all is one from before optional parameters. */ +export const undeclaredInterfaceVersion = 0x00; + +export type SessionBind = { as: BindType; peerVersion: number }; + +function isBindType(value: unknown): value is BindType { + return typeof value === 'string' && bindTypeFromCommand(`bind_${value}`) !== undefined; +} + +/** A bind as `Session.bound()` records it: undefined declares no version, which is pre-3.4. */ +export function checkedBind(bindType: unknown, declaredVersion: unknown): Result<{ bind: SessionBind }> { + if (!isBindType(bindType)) { + return { err: new Error(`bindType must be receiver, transceiver or transmitter, the bind command's name without "bind_", got ${quoted(bindType)}`) }; + } + + if (declaredVersion === undefined) return { bind: { as: bindType, peerVersion: undeclaredInterfaceVersion } }; + + if (typeof declaredVersion !== 'number' || !Number.isInteger(declaredVersion) || declaredVersion < 0 || declaredVersion > 0xFF) { + return { err: new Error(`declaredVersion must be an integer 0-255, the interface_version param or the sc_interface_version TLV's tagValue, or undefined where the peer declared none, got ${quoted(declaredVersion)}`) }; + } + + return { bind: { as: bindType, peerVersion: declaredVersion } }; +} diff --git a/src/concat.ts b/src/protocol/concat.ts similarity index 89% rename from src/concat.ts rename to src/protocol/concat.ts index b56f421..3aa021d 100644 --- a/src/concat.ts +++ b/src/protocol/concat.ts @@ -1,9 +1,9 @@ import type { ConcatInfo } from './udh.ts'; -import type { PduObject } from './pdu.ts'; +import type { PduObject } from '../codec/pdu.ts'; import { concatInfo } from './udh.ts'; -import { hasUdh } from './defs/constants.ts'; +import { hasUdh } from '../codec/constants.ts'; import { messageOctets } from './message-body.ts'; -import { paramNumber } from './defs/types.ts'; +import { paramNumber } from '../codec/field-types.ts'; /** Where a segment sits in its message, and what ties it to the rest of that message. */ export type Concat = ConcatInfo & { diff --git a/src/message-body.ts b/src/protocol/message-body.ts similarity index 92% rename from src/message-body.ts rename to src/protocol/message-body.ts index a244b8f..f47805a 100644 --- a/src/message-body.ts +++ b/src/protocol/message-body.ts @@ -1,4 +1,4 @@ -import type { PduObject } from './pdu.ts'; +import type { PduObject } from '../codec/pdu.ts'; /** * The user data, wherever the peer put it. SMPP 3.4 5.3.2.32 carries up to 64 KB in diff --git a/src/sms-id.ts b/src/protocol/message-ids.ts similarity index 95% rename from src/sms-id.ts rename to src/protocol/message-ids.ts index 8a558b6..047dea1 100644 --- a/src/sms-id.ts +++ b/src/protocol/message-ids.ts @@ -1,5 +1,5 @@ -import type { CommandName } from './defs/commands.ts'; -import type { ParamValue } from './defs/types.ts'; +import type { CommandName } from '../codec/commands.ts'; +import type { ParamValue } from '../codec/field-types.ts'; const notations = { decimal: { digits: /^[0-9]+$/, prefix: '' }, diff --git a/src/dlr.ts b/src/protocol/receipt.ts similarity index 94% rename from src/dlr.ts rename to src/protocol/receipt.ts index 27ab4dd..7b85584 100644 --- a/src/dlr.ts +++ b/src/protocol/receipt.ts @@ -1,12 +1,12 @@ -import type { MessageState } from './defs/constants.ts'; -import type { TlvValue } from './defs/types.ts'; -import type { PduObject } from './pdu.ts'; -import type { SmsIdFormat } from './sms-id.ts'; -import { consts, constsById, hasUdh, messageTypeOf } from './defs/constants.ts'; -import { encodings } from './defs/encodings.ts'; +import type { MessageState } from '../codec/constants.ts'; +import type { TlvValue } from '../codec/field-types.ts'; +import type { PduObject } from '../codec/pdu.ts'; +import type { SmsIdFormat } from './message-ids.ts'; +import { consts, constsById, hasUdh, messageTypeOf } from '../codec/constants.ts'; +import { encodings } from '../codec/encodings.ts'; import { messageOctets } from './message-body.ts'; -import { normaliseSmsId } from './sms-id.ts'; -import { paramNumber, paramText } from './defs/types.ts'; +import { normaliseSmsId } from './message-ids.ts'; +import { paramNumber, paramText } from '../codec/field-types.ts'; import { udhLength } from './udh.ts'; /** diff --git a/src/udh.ts b/src/protocol/udh.ts similarity index 100% rename from src/udh.ts rename to src/protocol/udh.ts diff --git a/src/uuid.ts b/src/protocol/uuid.ts similarity index 100% rename from src/uuid.ts rename to src/protocol/uuid.ts diff --git a/src/reconnect-loop.ts b/src/reconnect-loop.ts index af8044d..52a3c92 100644 --- a/src/reconnect-loop.ts +++ b/src/reconnect-loop.ts @@ -1,11 +1,7 @@ import type { Result, VoidResult } from './result.ts'; import type { SmppLog } from './log.ts'; import type { Socket } from 'node:net'; - -export const backoffDefaults = { - maxDelay: 30_000, - minDelay: 1000, -}; +import { defaults } from './options.ts'; export type ReconnectLoopOptions = { connect: () => Promise>; @@ -32,8 +28,8 @@ export class ReconnectLoop { private upAt: number | undefined; constructor(options: ReconnectLoopOptions) { - this.maxDelay = options.maxDelay ?? backoffDefaults.maxDelay; - this.minDelay = options.minDelay ?? backoffDefaults.minDelay; + this.maxDelay = options.maxDelay ?? defaults.maxDelay; + this.minDelay = options.minDelay ?? defaults.minDelay; this.now = options.now ?? Date.now; this.options = options; this.delay = this.minDelay; diff --git a/src/result.ts b/src/result.ts index 9570760..f42c0b1 100644 --- a/src/result.ts +++ b/src/result.ts @@ -7,3 +7,26 @@ export type Result = | ({ err?: undefined } & T); export type VoidResult = { err?: Error }; + +/** Whatever was thrown or rejected, as an Error. `String()` throws on some values; this cannot. */ +export function errorFrom(reason: unknown): Error { + if (reason instanceof Error) return reason; + + try { + return new Error(String(reason)); + } catch { + return new Error('A thrown value that cannot be converted to a string'); + } +} + +const printable: readonly string[] = ['boolean', 'number', 'string']; + +/** String() throws on a null-prototype object, so anything but these is named by its type. */ +export function namedValue(value: unknown): string { + return printable.includes(typeof value) ? String(value) : typeof value; +} + +/** A value named in an error: a string quoted, anything else as `namedValue()` names it. */ +export function quoted(value: unknown): string { + return typeof value === 'string' ? JSON.stringify(value) : namedValue(value); +} diff --git a/src/server.ts b/src/server/server.ts similarity index 93% rename from src/server.ts rename to src/server/server.ts index 2b9aefe..392a4c4 100644 --- a/src/server.ts +++ b/src/server/server.ts @@ -1,19 +1,20 @@ -import type { BindType, CloseOptions, OnRequest } from './session-options.ts'; -import type { PduObject, TlvInputs } from './pdu.ts'; -import type { Result, VoidResult } from './result.ts'; +import type { BindType } from '../protocol/bind.ts'; +import type { CloseOptions, OnRequest } from '../options.ts'; +import type { PduObject, TlvInputs } from '../codec/pdu.ts'; +import type { Result, VoidResult } from '../result.ts'; import type { Server as NetServer, Socket } from 'node:net'; import type { Server as TlsServer, TlsOptions } from 'node:tls'; -import type { SmppLog } from './log.ts'; +import type { SmppLog } from '../log.ts'; import { EventEmitter } from 'node:events'; -import { Session, defaultSystemId } from './session.ts'; -import { bindTypeFromCommand, checkSessionOptions } from './session-options.ts'; +import { Session } from '../session.ts'; +import { bindTypeFromCommand } from '../protocol/bind.ts'; +import { checkSessionOptions, defaults } from '../options.ts'; import { createServer as createNetServer } from 'node:net'; import { createServer as createTlsServer } from 'node:tls'; -import { defaultInterfaceVersion } from './defs/constants.ts'; -import { errorFrom } from './error-from.ts'; -import { paramText } from './defs/types.ts'; -import { guardedLog } from './log.ts'; -import { respNameFor } from './defs/commands.ts'; +import { errorFrom } from '../result.ts'; +import { paramText } from '../codec/field-types.ts'; +import { guardedLog } from '../log.ts'; +import { respNameFor } from '../codec/commands.ts'; export type AuthenticateResult = { userData?: unknown } | boolean; @@ -49,12 +50,6 @@ export type ServerEvents = { session: [Session]; }; -const defaults = { - idleTimeout: 40_000, - interfaceVersion: defaultInterfaceVersion, - port: 2775, - systemId: defaultSystemId, -}; /** A listener may return a promise: an `async` one that rejects is routed like one that throws. */ type ServerListener = (...args: ServerEvents[K]) => unknown; @@ -233,7 +228,7 @@ async function handleRequest( function onConnection(sock: Socket, options: ServerOptions, server: SmppServer): void { const log = guardedLog(options.log); const session = new Session({ - idleTimeout: options.idleTimeout ?? defaults.idleTimeout, + idleTimeout: options.idleTimeout ?? defaults.serverIdleTimeout, log, maxOutstanding: options.maxOutstanding, maxOctets: options.maxOctets, diff --git a/src/session.ts b/src/session.ts index 1fd9b46..c74e902 100644 --- a/src/session.ts +++ b/src/session.ts @@ -1,30 +1,34 @@ -import type { ErrorName } from './defs/errors.ts'; -import type { MessageDlr } from './dlr-merger.ts'; -import type { ParamValue } from './defs/types.ts'; -import type { PduObject, PduObjectInput, TlvInputs } from './pdu.ts'; -import type { PduRefusedError } from './pdu-refusal.ts'; -import type { BindType, CloseOptions, LinkEnd, ReconnectOptions, SendOptions, SessionBind, SessionEvents, SessionOptions } from './session-options.ts'; +import type { Dlr } from './protocol/receipt.ts'; +import type { ErrorName } from './codec/statuses.ts'; +import type { MessageDlr } from './messages/receipt-merge.ts'; +import type { ParamValue } from './codec/field-types.ts'; +import type { PduObject, PduObjectInput, TlvInputs } from './codec/pdu.ts'; +import type { PduRefusedError } from './codec/refusal.ts'; +import type { BindType, LinkEnd, SessionBind } from './protocol/bind.ts'; +import type { CloseOptions, ReconnectOptions, SendOptions, SessionOptions } from './options.ts'; import type { Result, VoidResult } from './result.ts'; -import type { SendSmsOptions, SendSmsResult } from './send-sms.ts'; +import type { SendSmsOptions, SendSmsResult } from './messages/submit.ts'; import type { SmppLog } from './log.ts'; +import type { Sms } from './sms.ts'; import type { Socket } from 'node:net'; -import { DlrMerger } from './dlr-merger.ts'; +import { DlrMerger } from './messages/receipt-merge.ts'; import { EventEmitter } from 'node:events'; -import { IncomingRequests } from './incoming-requests.ts'; +import { IncomingRequests } from './session/requests-in.ts'; import { LinkLife } from './link-life.ts'; -import { LinkTimers } from './link-timers.ts'; -import { OutgoingRequests } from './outgoing-requests.ts'; -import { PduTransport } from './pdu-transport.ts'; +import { LinkTimers } from './session/keepalive.ts'; +import { OutgoingRequests } from './session/outgoing-requests.ts'; +import { PduTransport } from './session/transport.ts'; import { ReconnectLoop } from './reconnect-loop.ts'; -import { leftOf } from './idle-waiters.ts'; -import { errorFrom } from './error-from.ts'; -import { optionalParamsMinVersion } from './defs/constants.ts'; -import { bindCarries, bindCommands, checkedBind, defaultSystemId, defaults } from './session-options.ts'; -import { isResp, objToPdu, pduReturn } from './pdu.ts'; -import { refusalAnswer } from './pdu-refusal.ts'; +import { leftOf } from './session/waiting.ts'; +import { errorFrom } from './result.ts'; +import { optionalParamsMinVersion } from './codec/constants.ts'; +import { bindCarries, checkedBind } from './protocol/bind.ts'; +import { defaults } from './options.ts'; +import { isResp, objToPdu, pduReturn } from './codec/pdu.ts'; +import { refusalAnswer } from './codec/refusal.ts'; import { guardedLog } from './log.ts'; -import { submitSms, unsent } from './send-sms.ts'; -import { ConcatReference } from './udh.ts'; +import { submitSms, unsent } from './messages/submit.ts'; +import { ConcatReference } from './protocol/udh.ts'; export type { CloseOptions, @@ -33,11 +37,22 @@ export type { SendOptions, SendSmsOptions, SendSmsResult, - SessionEvents, SessionOptions, }; export type { BindType }; -export { bindCommands, defaultSystemId }; + +export type SessionEvents = { + close: []; + data: [Buffer]; + disconnected: []; + dlr: [Dlr, PduObject]; + incomingPdu: [Buffer]; + incomingPduObj: [PduObject]; + messageDlr: [MessageDlr]; + reconnected: []; + sessionError: [Error | PduRefusedError]; + sms: [Sms]; +}; /** A listener may return a promise: an `async` one that rejects is routed like one that throws. */ type SessionListener = (...args: SessionEvents[K]) => unknown; diff --git a/src/held-messages.ts b/src/session/held-messages.ts similarity index 92% rename from src/held-messages.ts rename to src/session/held-messages.ts index b9e740e..517472e 100644 --- a/src/held-messages.ts +++ b/src/session/held-messages.ts @@ -1,13 +1,13 @@ -import type { LinkLife } from './link-life.ts'; -import type { PduObject, PduObjectInput } from './pdu.ts'; -import type { Result } from './result.ts'; -import type { Session } from './session.ts'; -import type { SmsHandlers } from './sms.ts'; -import type { SmppLog } from './log.ts'; -import { ExpiringGroups } from './expiring-groups.ts'; -import { IdleWaiters } from './idle-waiters.ts'; -import { createSms } from './sms.ts'; -import { retainedOctets } from './retained-pdu.ts'; +import type { LinkLife } from '../link-life.ts'; +import type { PduObject, PduObjectInput } from '../codec/pdu.ts'; +import type { Result } from '../result.ts'; +import type { Session } from '../session.ts'; +import type { SmsHandlers } from '../sms.ts'; +import type { SmppLog } from '../log.ts'; +import { ExpiringGroups } from '../messages/expiring-groups.ts'; +import { IdleWaiters } from './waiting.ts'; +import { createSms } from '../sms.ts'; +import { retainedOctets } from '../codec/retained.ts'; export type HeldMessagesOptions = { link: LinkLife; diff --git a/src/link-timers.ts b/src/session/keepalive.ts similarity index 97% rename from src/link-timers.ts rename to src/session/keepalive.ts index cd6710a..cbe1d36 100644 --- a/src/link-timers.ts +++ b/src/session/keepalive.ts @@ -1,4 +1,4 @@ -import type { SmppLog } from './log.ts'; +import type { SmppLog } from '../log.ts'; export type LinkTimersOptions = { /** How long between enquire_link probes. Undefined or 0 never probes. */ diff --git a/src/outgoing-requests.ts b/src/session/outgoing-requests.ts similarity index 92% rename from src/outgoing-requests.ts rename to src/session/outgoing-requests.ts index a0adf24..429834e 100644 --- a/src/outgoing-requests.ts +++ b/src/session/outgoing-requests.ts @@ -1,14 +1,14 @@ -import type { LinkLife } from './link-life.ts'; -import type { PduObject, PduObjectInput } from './pdu.ts'; -import type { PduTransport } from './pdu-transport.ts'; -import type { Result, VoidResult } from './result.ts'; -import type { SendOptions } from './session-options.ts'; -import type { SmppLog } from './log.ts'; +import type { LinkLife } from '../link-life.ts'; +import type { PduObject, PduObjectInput } from '../codec/pdu.ts'; +import type { PduTransport } from './transport.ts'; +import type { Result, VoidResult } from '../result.ts'; +import type { SendOptions } from '../options.ts'; +import type { SmppLog } from '../log.ts'; import { PendingRequests } from './pending-requests.ts'; import { SendWindow } from './send-window.ts'; -import { UnansweredError } from './unanswered-error.ts'; -import { bindCommands } from './session-options.ts'; -import { objToPdu } from './pdu.ts'; +import { UnansweredError } from '../unanswered-error.ts'; +import { bindCommands } from '../protocol/bind.ts'; +import { objToPdu } from '../codec/pdu.ts'; export type OutgoingRequestsOptions = { link: LinkLife; diff --git a/src/pending-requests.ts b/src/session/pending-requests.ts similarity index 92% rename from src/pending-requests.ts rename to src/session/pending-requests.ts index 7949106..6c8bb2b 100644 --- a/src/pending-requests.ts +++ b/src/session/pending-requests.ts @@ -1,7 +1,7 @@ -import type { PduObject } from './pdu.ts'; -import type { Result } from './result.ts'; -import type { SmppLog } from './log.ts'; -import { maxSeqNr } from './pdu.ts'; +import type { PduObject } from '../codec/pdu.ts'; +import type { Result } from '../result.ts'; +import type { SmppLog } from '../log.ts'; +import { maxSeqNr } from '../codec/pdu.ts'; export type WaitOptions = { signal?: AbortSignal | undefined; diff --git a/src/incoming-requests.ts b/src/session/requests-in.ts similarity index 89% rename from src/incoming-requests.ts rename to src/session/requests-in.ts index 51aeec7..73e1b22 100644 --- a/src/incoming-requests.ts +++ b/src/session/requests-in.ts @@ -1,23 +1,24 @@ -import type { Concat } from './concat.ts'; -import type { DlrMerger } from './dlr-merger.ts'; -import type { ErrorName } from './defs/errors.ts'; +import type { Concat } from '../protocol/concat.ts'; +import type { DlrMerger } from '../messages/receipt-merge.ts'; +import type { ErrorName } from '../codec/statuses.ts'; import type { HeldMessagesOptions } from './held-messages.ts'; -import type { LinkLife } from './link-life.ts'; -import type { LostGroup, Refusal } from './reassembly.ts'; -import type { OnRequest } from './session-options.ts'; -import type { PduObject } from './pdu.ts'; -import type { VoidResult } from './result.ts'; -import type { Session } from './session.ts'; -import type { SmppLog } from './log.ts'; -import type { SmsIdFormat } from './sms-id.ts'; +import type { LinkLife } from '../link-life.ts'; +import type { LostGroup, Refusal } from '../messages/reassembly.ts'; +import type { OnRequest } from '../options.ts'; +import type { PduObject } from '../codec/pdu.ts'; +import type { VoidResult } from '../result.ts'; +import type { Session } from '../session.ts'; +import type { SmppLog } from '../log.ts'; +import type { SmsIdFormat } from '../protocol/message-ids.ts'; import { HeldMessages } from './held-messages.ts'; -import { Reassembler } from './reassembly.ts'; -import { bindCommands, defaults, standsInFor } from './session-options.ts'; -import { concatOf } from './concat.ts'; -import { detach } from './retained-pdu.ts'; -import { dlrFromPdu } from './dlr.ts'; -import { respIdParams, segmentId } from './sms-id.ts'; -import { respNameFor } from './defs/commands.ts'; +import { Reassembler } from '../messages/reassembly.ts'; +import { bindCommands, standsInFor } from '../protocol/bind.ts'; +import { defaults } from '../options.ts'; +import { concatOf } from '../protocol/concat.ts'; +import { detach } from '../codec/retained.ts'; +import { dlrFromPdu } from '../protocol/receipt.ts'; +import { respIdParams, segmentId } from '../protocol/message-ids.ts'; +import { respNameFor } from '../codec/commands.ts'; /** Asks the peer to keep the message and retry. */ function throttledStatus(carriedAs: string): ErrorName { diff --git a/src/send-window.ts b/src/session/send-window.ts similarity index 94% rename from src/send-window.ts rename to src/session/send-window.ts index e13d67d..3eabe8f 100644 --- a/src/send-window.ts +++ b/src/session/send-window.ts @@ -1,6 +1,6 @@ -import type { SmppLog } from './log.ts'; -import type { VoidResult } from './result.ts'; -import { IdleWaiters } from './idle-waiters.ts'; +import type { SmppLog } from '../log.ts'; +import type { VoidResult } from '../result.ts'; +import { IdleWaiters } from './waiting.ts'; export type SendWindowOptions = { limit: number; diff --git a/src/pdu-transport.ts b/src/session/transport.ts similarity index 90% rename from src/pdu-transport.ts rename to src/session/transport.ts index 966cdee..e453c0c 100644 --- a/src/pdu-transport.ts +++ b/src/session/transport.ts @@ -1,10 +1,10 @@ -import type { PduObject } from './pdu.ts'; -import type { SmppLog } from './log.ts'; +import type { PduObject } from '../codec/pdu.ts'; +import type { SmppLog } from '../log.ts'; import type { Socket } from 'node:net'; -import type { VoidResult } from './result.ts'; -import { PduFramer } from './pdu-framer.ts'; -import { PduRefusedError } from './pdu-refusal.ts'; -import { pduToObj } from './pdu.ts'; +import type { VoidResult } from '../result.ts'; +import { PduFramer } from '../codec/framer.ts'; +import { PduRefusedError } from '../codec/refusal.ts'; +import { pduToObj } from '../codec/pdu.ts'; export type PduTransportOptions = { log: SmppLog; diff --git a/src/idle-waiters.ts b/src/session/waiting.ts similarity index 100% rename from src/idle-waiters.ts rename to src/session/waiting.ts diff --git a/src/sms.ts b/src/sms.ts index 5149ceb..aadcab6 100644 --- a/src/sms.ts +++ b/src/sms.ts @@ -1,17 +1,17 @@ -import type { ErrorName } from './defs/errors.ts'; -import type { MessageState } from './defs/constants.ts'; -import type { PduObject, PduObjectInput, TlvInputs } from './pdu.ts'; +import type { ErrorName } from './codec/statuses.ts'; +import type { MessageState } from './codec/constants.ts'; +import type { PduObject, PduObjectInput, TlvInputs } from './codec/pdu.ts'; import type { Result, VoidResult } from './result.ts'; import type { Session } from './session.ts'; import { UnansweredError } from './unanswered-error.ts'; -import { consts } from './defs/constants.ts'; -import { decodeSegments } from './reassembly.ts'; -import { messageClassOf } from './defs/encodings.ts'; -import { paramText } from './defs/types.ts'; -import { receiptCodes, transientStates } from './dlr.ts'; +import { consts } from './codec/constants.ts'; +import { decodeSegments } from './messages/reassembly.ts'; +import { messageClassOf } from './codec/encodings.ts'; +import { paramText } from './codec/field-types.ts'; +import { receiptCodes, transientStates } from './protocol/receipt.ts'; import { smppDate } from './message.ts'; -import { respIdParams, segmentId } from './sms-id.ts'; -import { uuidv7 } from './uuid.ts'; +import { respIdParams, segmentId } from './protocol/message-ids.ts'; +import { uuidv7 } from './protocol/uuid.ts'; /** `pduObjs` holds what the peer took, so a partial failure names what is already receipted. */ export type SendDlrResult = { diff --git a/test/commands.test.ts b/test/commands.test.ts index 6f9e2a7..bb53a04 100644 --- a/test/commands.test.ts +++ b/test/commands.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { PduParams, PduParamsInput } from '../src/defs/commands.ts'; -import { cmds, cmdsById, commandNameById, isCommandName } from '../src/defs/commands.ts'; +import type { PduParams, PduParamsInput } from '../src/codec/commands.ts'; +import { cmds, cmdsById, commandNameById, isCommandName } from '../src/codec/commands.ts'; describe('command table', () => { test('every command is reachable by name and by id', () => { diff --git a/test/declared-alphabet.test.ts b/test/declared-alphabet.test.ts index 8132883..33bf704 100644 --- a/test/declared-alphabet.test.ts +++ b/test/declared-alphabet.test.ts @@ -1,16 +1,16 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; import { bindToSmsc, dummySmsc } from './dummy-smsc.ts'; -import { client } from '../src/client.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter } from './teardown.ts'; -import { consts } from '../src/defs/constants.ts'; +import { consts } from '../src/codec/constants.ts'; import { decodeMessage } from '../src/message.ts'; -import { dlrFromPdu } from '../src/dlr.ts'; -import { encodingByDataCoding, encodings } from '../src/defs/encodings.ts'; -import { objToPdu, pduToObj } from '../src/pdu.ts'; -import { paramNumber } from '../src/defs/types.ts'; -import { server } from '../src/server.ts'; -import type { PduObject } from '../src/pdu.ts'; +import { dlrFromPdu } from '../src/protocol/receipt.ts'; +import { encodingByDataCoding, encodings } from '../src/codec/encodings.ts'; +import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; +import { paramNumber } from '../src/codec/field-types.ts'; +import { server } from '../src/server/server.ts'; +import type { PduObject } from '../src/codec/pdu.ts'; const from = '46701113311'; const to = '46709771337'; diff --git a/test/dlr.test.ts b/test/dlr.test.ts index fa6ec84..0dfa471 100644 --- a/test/dlr.test.ts +++ b/test/dlr.test.ts @@ -1,10 +1,10 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import { consts } from '../src/defs/constants.ts'; -import { dlrFromPdu, parseReceipt, receiptCodes } from '../src/dlr.ts'; +import { consts } from '../src/codec/constants.ts'; +import { dlrFromPdu, parseReceipt, receiptCodes } from '../src/protocol/receipt.ts'; import { encodeMessage } from '../src/message.ts'; -import { objToPdu, pduToObj } from '../src/pdu.ts'; -import type { PduObject, TlvInputs } from '../src/pdu.ts'; +import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; +import type { PduObject, TlvInputs } from '../src/codec/pdu.ts'; const receiptText = 'id:0195f0c7 sub:001 dlvrd:001 submit date:2508251430 done date:2508251431 stat:DELIVRD err:000 text:hello there'; diff --git a/test/dummy-smsc.ts b/test/dummy-smsc.ts index 7da2609..2a12f82 100644 --- a/test/dummy-smsc.ts +++ b/test/dummy-smsc.ts @@ -2,12 +2,12 @@ import assert from 'node:assert/strict'; import net from 'node:net'; import type { Session } from '../src/session.ts'; import type { TestContext } from 'node:test'; -import { PduFramer } from '../src/pdu-framer.ts'; -import { client } from '../src/client.ts'; +import { PduFramer } from '../src/codec/framer.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter, closeListenerAfter } from './teardown.ts'; -import { consts } from '../src/defs/constants.ts'; -import { objToPdu, pduReturn, pduToObj } from '../src/pdu.ts'; -import { uuidv7 } from '../src/uuid.ts'; +import { consts } from '../src/codec/constants.ts'; +import { objToPdu, pduReturn, pduToObj } from '../src/codec/pdu.ts'; +import { uuidv7 } from '../src/protocol/uuid.ts'; export type DummySmsc = { /** Writes a delivery receipt to the ESME, its body spelled as the test names it. */ diff --git a/test/encodings.test.ts b/test/encodings.test.ts index 67fc68c..c67614d 100644 --- a/test/encodings.test.ts +++ b/test/encodings.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import { dataCodingByEncoding, detect, encodingByDataCoding, encodings, isEncodingName, unencodable } from '../src/defs/encodings.ts'; +import { dataCodingByEncoding, detect, encodingByDataCoding, encodings, isEncodingName, unencodable } from '../src/codec/encodings.ts'; describe('ASCII (GSM 03.38)', () => { const samples: [string, number[]][] = [ diff --git a/test/error-from.test.ts b/test/error-from.test.ts index 6a98889..ebcc8a9 100644 --- a/test/error-from.test.ts +++ b/test/error-from.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { errorFrom } from '../src/error-from.ts'; +import { errorFrom } from '../src/result.ts'; test('carries an Error through and describes anything else, including what String() refuses', () => { const original = new Error('the original'); diff --git a/test/interop.test.ts b/test/interop.test.ts index 2e5d905..d2638f8 100644 --- a/test/interop.test.ts +++ b/test/interop.test.ts @@ -3,11 +3,11 @@ import test, { describe } from 'node:test'; import reference from 'smpp'; import type { ReferenceSession } from 'smpp'; import type { Sms } from '../src/sms.ts'; -import { client } from '../src/client.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter } from './teardown.ts'; -import { concatInfo } from '../src/udh.ts'; -import { objToPdu, pduToObj } from '../src/pdu.ts'; -import { server } from '../src/server.ts'; +import { concatInfo } from '../src/protocol/udh.ts'; +import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; +import { server } from '../src/server/server.ts'; import { splitMessage } from '../src/message.ts'; /** diff --git a/test/message-class.test.ts b/test/message-class.test.ts index e613139..9d0d4a5 100644 --- a/test/message-class.test.ts +++ b/test/message-class.test.ts @@ -1,19 +1,19 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { PduObjectInput } from '../src/pdu.ts'; -import type { SendSmsDeps } from '../src/send-sms.ts'; +import type { PduObjectInput } from '../src/codec/pdu.ts'; +import type { SendSmsDeps } from '../src/messages/submit.ts'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; import type { TestContext } from 'node:test'; import { bindToSmsc, dummySmsc } from './dummy-smsc.ts'; -import { client } from '../src/client.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter } from './teardown.ts'; -import { messageClassOf } from '../src/defs/encodings.ts'; -import { paramNumber } from '../src/defs/types.ts'; -import { pduToObj } from '../src/pdu.ts'; -import { server } from '../src/server.ts'; +import { messageClassOf } from '../src/codec/encodings.ts'; +import { paramNumber } from '../src/codec/field-types.ts'; +import { pduToObj } from '../src/codec/pdu.ts'; +import { server } from '../src/server/server.ts'; import { silentLog } from '../src/log.ts'; -import { submitSms } from '../src/send-sms.ts'; +import { submitSms } from '../src/messages/submit.ts'; const from = '46701113311'; const to = '46709771337'; diff --git a/test/message.test.ts b/test/message.test.ts index 37392a4..1153768 100644 --- a/test/message.test.ts +++ b/test/message.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { EncodingName } from '../src/defs/encodings.ts'; +import type { EncodingName } from '../src/codec/encodings.ts'; import { bitCount, decodeMessage, diff --git a/test/messaging-mode.test.ts b/test/messaging-mode.test.ts index 4ac57ee..4b97e3f 100644 --- a/test/messaging-mode.test.ts +++ b/test/messaging-mode.test.ts @@ -1,16 +1,16 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { PduObjectInput } from '../src/pdu.ts'; -import type { SendSmsDeps } from '../src/send-sms.ts'; +import type { PduObjectInput } from '../src/codec/pdu.ts'; +import type { SendSmsDeps } from '../src/messages/submit.ts'; import type { Session } from '../src/session.ts'; -import type { SubmitMessagingMode } from '../src/defs/constants.ts'; +import type { SubmitMessagingMode } from '../src/codec/constants.ts'; import type { TestContext } from 'node:test'; import { bindToSmsc, dummySmsc } from './dummy-smsc.ts'; -import { consts, submitMessagingModes } from '../src/defs/constants.ts'; -import { paramNumber } from '../src/defs/types.ts'; -import { pduToObj } from '../src/pdu.ts'; +import { consts, submitMessagingModes } from '../src/codec/constants.ts'; +import { paramNumber } from '../src/codec/field-types.ts'; +import { pduToObj } from '../src/codec/pdu.ts'; import { silentLog } from '../src/log.ts'; -import { submitSms } from '../src/send-sms.ts'; +import { submitSms } from '../src/messages/submit.ts'; const from = '46701113311'; const to = '46709771337'; diff --git a/test/operator-receipts.test.ts b/test/operator-receipts.test.ts index 36dd655..03d3bb3 100644 --- a/test/operator-receipts.test.ts +++ b/test/operator-receipts.test.ts @@ -1,12 +1,12 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { Dlr, Receipt } from '../src/dlr.ts'; +import type { Dlr, Receipt } from '../src/protocol/receipt.ts'; import type { MessageDlr } from '../src/session.ts'; -import type { PduObject, TlvInputs } from '../src/pdu.ts'; +import type { PduObject, TlvInputs } from '../src/codec/pdu.ts'; import { bindToSmsc, dummySmsc } from './dummy-smsc.ts'; -import { consts } from '../src/defs/constants.ts'; -import { dlrFromPdu, parseReceipt, receiptCodes, transientStates } from '../src/dlr.ts'; -import { objToPdu, pduToObj } from '../src/pdu.ts'; +import { consts } from '../src/codec/constants.ts'; +import { dlrFromPdu, parseReceipt, receiptCodes, transientStates } from '../src/protocol/receipt.ts'; +import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; /** * Receipt bodies as commercial operators document them, from `interop-tests/research/operator-quirks.md` diff --git a/test/pdu-framer.test.ts b/test/pdu-framer.test.ts index 1461b43..3bafb85 100644 --- a/test/pdu-framer.test.ts +++ b/test/pdu-framer.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import { PduFramer } from '../src/pdu-framer.ts'; -import { objToPdu } from '../src/pdu.ts'; +import { PduFramer } from '../src/codec/framer.ts'; +import { objToPdu } from '../src/codec/pdu.ts'; function pdu(seqNr: number): Buffer { const { buffer } = objToPdu({ cmdName: 'enquire_link', seqNr }); diff --git a/test/pdu.test.ts b/test/pdu.test.ts index c773865..c363efc 100644 --- a/test/pdu.test.ts +++ b/test/pdu.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import { PduRefusedError, refusalAnswer } from '../src/pdu-refusal.ts'; -import { isCommand, isResp, objToPdu, pduReturn, pduToObj } from '../src/pdu.ts'; -import { paramText } from '../src/defs/types.ts'; -import { isTlvName, tlvsById } from '../src/defs/tlvs.ts'; +import { PduRefusedError, refusalAnswer } from '../src/codec/refusal.ts'; +import { isCommand, isResp, objToPdu, pduReturn, pduToObj } from '../src/codec/pdu.ts'; +import { paramText } from '../src/codec/field-types.ts'; +import { isTlvName, tlvsById } from '../src/codec/tlvs.ts'; function encode(...args: Parameters): Buffer { const { buffer, err } = objToPdu(...args); diff --git a/test/raw-pdus.ts b/test/raw-pdus.ts index 46ae6f4..c861707 100644 --- a/test/raw-pdus.ts +++ b/test/raw-pdus.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; -import type { PduObjectInput } from '../src/pdu.ts'; -import { objToPdu } from '../src/pdu.ts'; +import type { PduObjectInput } from '../src/codec/pdu.ts'; +import { objToPdu } from '../src/codec/pdu.ts'; /** The octets a test writes straight to a socket, which objToPdu builds for every valid PDU. */ export function pduBytes(input: PduObjectInput): Buffer { diff --git a/test/readme.test.ts b/test/readme.test.ts index f2198a5..1cb446b 100644 --- a/test/readme.test.ts +++ b/test/readme.test.ts @@ -1,16 +1,16 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { Dlr } from '../src/dlr.ts'; +import type { Dlr } from '../src/protocol/receipt.ts'; import type { Session } from '../src/session.ts'; import type { Sms } from '../src/sms.ts'; import type { SmppLog } from '../src/log.ts'; -import type { SmppServer } from '../src/server.ts'; +import type { SmppServer } from '../src/server/server.ts'; import type { TestContext } from 'node:test'; -import { PduRefusedError } from '../src/pdu-refusal.ts'; -import { client } from '../src/client.ts'; +import { PduRefusedError } from '../src/codec/refusal.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter } from './teardown.ts'; -import { isCommand, objToPdu } from '../src/pdu.ts'; -import { server } from '../src/server.ts'; +import { isCommand, objToPdu } from '../src/codec/pdu.ts'; +import { server } from '../src/server/server.ts'; function once(register: (resolve: (value: T) => void) => void): Promise { return new Promise((resolve, reject) => { diff --git a/test/session-extras.test.ts b/test/session-extras.test.ts index 6b36279..e362ef2 100644 --- a/test/session-extras.test.ts +++ b/test/session-extras.test.ts @@ -1,42 +1,43 @@ import assert from 'node:assert/strict'; import net from 'node:net'; import test, { describe } from 'node:test'; -import type { Collected, LostGroup } from '../src/reassembly.ts'; -import type { Dlr } from '../src/dlr.ts'; -import type { ErrorName } from '../src/defs/errors.ts'; -import type { IncomingRequestsOptions } from '../src/incoming-requests.ts'; -import type { HeldMessagesOptions, MessageHold } from '../src/held-messages.ts'; -import type { MessageState } from '../src/defs/constants.ts'; +import type { Collected, LostGroup } from '../src/messages/reassembly.ts'; +import type { Dlr } from '../src/protocol/receipt.ts'; +import type { ErrorName } from '../src/codec/statuses.ts'; +import type { IncomingRequestsOptions } from '../src/session/requests-in.ts'; +import type { HeldMessagesOptions, MessageHold } from '../src/session/held-messages.ts'; +import type { MessageState } from '../src/codec/constants.ts'; import type { MessageDlr } from '../src/session.ts'; -import type { PduObject, PduObjectInput } from '../src/pdu.ts'; +import type { PduObject, PduObjectInput } from '../src/codec/pdu.ts'; import type { Result } from '../src/result.ts'; -import type { SendSmsResult } from '../src/send-sms.ts'; +import type { SendSmsResult } from '../src/messages/submit.ts'; import type { SmppLog } from '../src/log.ts'; import type { Sms } from '../src/sms.ts'; -import type { SmppServer } from '../src/server.ts'; +import type { SmppServer } from '../src/server/server.ts'; import type { TestContext } from 'node:test'; -import { HeldMessages } from '../src/held-messages.ts'; -import { IncomingRequests, refusedSegmentStatus } from '../src/incoming-requests.ts'; +import { HeldMessages } from '../src/session/held-messages.ts'; +import { IncomingRequests, refusedSegmentStatus } from '../src/session/requests-in.ts'; import { UnansweredError } from '../src/unanswered-error.ts'; import { createSms } from '../src/sms.ts'; import { LinkLife } from '../src/link-life.ts'; -import { SendWindow } from '../src/send-window.ts'; -import { Reassembler, decodeSegments } from '../src/reassembly.ts'; +import { SendWindow } from '../src/session/send-window.ts'; +import { Reassembler, decodeSegments } from '../src/messages/reassembly.ts'; import { Session } from '../src/session.ts'; -import { DlrMerger } from '../src/dlr-merger.ts'; -import { PduRefusedError } from '../src/pdu-refusal.ts'; -import { objToPdu } from '../src/pdu.ts'; -import { checkSessionOptions, defaults, standsInFor } from '../src/session-options.ts'; -import { client } from '../src/client.ts'; +import { DlrMerger } from '../src/messages/receipt-merge.ts'; +import { PduRefusedError } from '../src/codec/refusal.ts'; +import { objToPdu } from '../src/codec/pdu.ts'; +import { checkSessionOptions, defaults } from '../src/options.ts'; +import { standsInFor } from '../src/protocol/bind.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter, closeListenerAfter } from './teardown.ts'; -import { concatOf } from '../src/concat.ts'; -import { consts } from '../src/defs/constants.ts'; -import { errors } from '../src/defs/errors.ts'; -import { paramNumber, paramText } from '../src/defs/types.ts'; -import { server } from '../src/server.ts'; +import { concatOf } from '../src/protocol/concat.ts'; +import { consts } from '../src/codec/constants.ts'; +import { errors } from '../src/codec/statuses.ts'; +import { paramNumber, paramText } from '../src/codec/field-types.ts'; +import { server } from '../src/server/server.ts'; import { silentLog } from '../src/log.ts'; import { splitMessage } from '../src/message.ts'; -import { submitSms, submitSmParams } from '../src/send-sms.ts'; +import { submitSms, submitSmParams } from '../src/messages/submit.ts'; async function startServer( t: TestContext, diff --git a/test/session.test.ts b/test/session.test.ts index 1f31f0b..ee84322 100644 --- a/test/session.test.ts +++ b/test/session.test.ts @@ -1,25 +1,26 @@ import assert from 'node:assert/strict'; import net from 'node:net'; import test, { describe } from 'node:test'; -import type { Dlr } from '../src/dlr.ts'; -import type { PduObject, PduObjectInput } from '../src/pdu.ts'; +import type { Dlr } from '../src/protocol/receipt.ts'; +import type { PduObject, PduObjectInput } from '../src/codec/pdu.ts'; import type { Sms } from '../src/sms.ts'; -import type { ServerOptions, SmppServer } from '../src/server.ts'; +import type { ServerOptions, SmppServer } from '../src/server/server.ts'; import type { SmppLog } from '../src/log.ts'; import type { TestContext } from 'node:test'; import type { VoidResult } from '../src/result.ts'; -import { DlrMerger } from '../src/dlr-merger.ts'; -import { PduFramer } from '../src/pdu-framer.ts'; +import { DlrMerger } from '../src/messages/receipt-merge.ts'; +import { PduFramer } from '../src/codec/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 { Session } from '../src/session.ts'; +import { bindCommands } from '../src/protocol/bind.ts'; +import { checkSessionOptions } from '../src/options.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter, closeListenerAfter } from './teardown.ts'; -import { consts } from '../src/defs/constants.ts'; -import { PduRefusedError } from '../src/pdu-refusal.ts'; -import { isCommand, objToPdu, pduReturn, pduToObj } from '../src/pdu.ts'; -import { paramText } from '../src/defs/types.ts'; -import { server } from '../src/server.ts'; +import { consts } from '../src/codec/constants.ts'; +import { PduRefusedError } from '../src/codec/refusal.ts'; +import { isCommand, objToPdu, pduReturn, pduToObj } from '../src/codec/pdu.ts'; +import { paramText } from '../src/codec/field-types.ts'; +import { server } from '../src/server/server.ts'; import { bareTlvHeader, pduBytes, shortened, truncatedTlv, withUnknownCmdId } from './raw-pdus.ts'; import { silentLog } from '../src/log.ts'; import { splitMessage } from '../src/message.ts'; diff --git a/test/sms-id.test.ts b/test/sms-id.test.ts index 0240cc1..e4da1ba 100644 --- a/test/sms-id.test.ts +++ b/test/sms-id.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import { normaliseSmsId, parseSegmentId, respIdParams, segmentId } from '../src/sms-id.ts'; +import { normaliseSmsId, parseSegmentId, respIdParams, segmentId } from '../src/protocol/message-ids.ts'; describe('normaliseSmsId()', () => { test('reads an id the length a message_id may be, and leaves a longer one alone', () => { diff --git a/test/teardown.ts b/test/teardown.ts index 70632b6..ad95479 100644 --- a/test/teardown.ts +++ b/test/teardown.ts @@ -1,4 +1,4 @@ -import type { CloseOptions } from '../src/session-options.ts'; +import type { CloseOptions } from '../src/options.ts'; import type { Server, Socket } from 'node:net'; import type { TestContext } from 'node:test'; diff --git a/test/tls.test.ts b/test/tls.test.ts index 1e311f4..056f93b 100644 --- a/test/tls.test.ts +++ b/test/tls.test.ts @@ -2,14 +2,14 @@ import assert from 'node:assert/strict'; import net from 'node:net'; import test, { describe } from 'node:test'; import type { Sms } from '../src/sms.ts'; -import type { SmppServer } from '../src/server.ts'; +import type { SmppServer } from '../src/server/server.ts'; import type { TestContext } from 'node:test'; import { Log } from '@larvit/log'; import { TLSSocket } from 'node:tls'; -import { client } from '../src/client.ts'; +import { client } from '../src/client/client.ts'; import { closeAfter } from './teardown.ts'; import { generateKeyPairSync, randomBytes, sign } from 'node:crypto'; -import { server } from '../src/server.ts'; +import { server } from '../src/server/server.ts'; const host = 'localhost'; diff --git a/test/types.test.ts b/test/types.test.ts index 02953e3..aec5399 100644 --- a/test/types.test.ts +++ b/test/types.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { DestAddress, UnsuccessSme } from '../src/defs/types.ts'; -import { paramText, types } from '../src/defs/types.ts'; -import { tlvs } from '../src/defs/tlvs.ts'; +import type { DestAddress, UnsuccessSme } from '../src/codec/field-types.ts'; +import { paramText, types } from '../src/codec/field-types.ts'; +import { tlvs } from '../src/codec/tlvs.ts'; describe('integers', () => { test('int8 reads, sizes and writes one octet', () => { diff --git a/test/unsendable.test.ts b/test/unsendable.test.ts index 3108910..d7cbb67 100644 --- a/test/unsendable.test.ts +++ b/test/unsendable.test.ts @@ -1,14 +1,14 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { PduObjectInput } from '../src/pdu.ts'; -import type { SendSmsDeps, SendSmsInput } from '../src/send-sms.ts'; +import type { PduObjectInput } from '../src/codec/pdu.ts'; +import type { SendSmsDeps, SendSmsInput } from '../src/messages/submit.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 { messageOctets } from '../src/protocol/message-body.ts'; +import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; +import { paramNumber, paramText } from '../src/codec/field-types.ts'; import { silentLog } from '../src/log.ts'; -import { submitSms } from '../src/send-sms.ts'; +import { submitSms } from '../src/messages/submit.ts'; const from = '46701113311'; const to = '46709771337'; diff --git a/todo.md b/todo.md index c37a9e5..d55c79f 100644 --- a/todo.md +++ b/todo.md @@ -215,7 +215,7 @@ comprehension-panel scoring run over the whole project. Each round: - let no dimension drop, and record the four scores and the overall in this paragraph. Each chunk also closes the items further down that it absorbs, and its PR names them: `hold` and -`refusing`, the three `defaults`, `ASCII`, `session-options.ts`, the flat `src/`, and the link that +`refusing`, `ASCII`, and the link that dropped mid-rebind. Every chunk that changes the public API updates README's examples, MIGRATION.md, CHANGELOG.md, @@ -223,9 +223,6 @@ docs/decisions.md and the AGENTS.md map in its own PR, and deletes what it repla file stand side by side. The architecture review of 2026-09-30 (ALIGN) set this order; its amendments to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture-review-2026-09-30). -- [ ] **Move without changing behaviour.** `defs/` becomes `codec/`, `options.ts` becomes one defaults - table, and `result.ts` absorbs `error-from.ts`. Every file with one target moves to its folder - now, and the "`src/` stays flat" decision is retired in the same PR. - [ ] **Build `protocol/`: every multi-fact octet read once into a named plain type.** - `vocabulary.ts`: the glossary as types with one-line TSDoc. - `data-coding.ts`: the `data_coding` table as rows, with a test that it equals today's function @@ -277,7 +274,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture - [ ] **Return an `err` where `message` is not a string, rather than throwing.** `sendSms({ message: undefined })` — a forgotten property — reaches `value.replace()` in - `defs/encodings.ts` through the alphabet detection `checkOptions()` runs, and the `TypeError` + `codec/encodings.ts` through the alphabet detection `checkOptions()` runs, and the `TypeError` escapes `submitSms()` into the caller's process; `NaN` and `12345` do the same. README promises "Never throws. Every fallible call resolves to `{ err?, … }`" and AGENTS.md hard rule 1 says it again, so the docs are false for the likeliest caller mistake there is. From the stability @@ -300,7 +297,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture Goals 2 then 3: `from: 1e21` is reported as sent to an address that reaches nobody, which is the wrong answer about what happened before it is laxness in what we send. That a number is accepted at all reaches a consumer in no sentence either: only the type comment at - `defs/commands.ts:239`, and one CHANGELOG line that stops being visible when + `codec/commands.ts`, and one CHANGELOG line that stops being visible when 0.7.0 is cut, while README's Building bullet reads as the whole rule for a text field. Whether this is a supported spelling or 0.4.0 tolerance decides whether that sentence lands in README.md or in MIGRATION.md — write it in the same change as the rule, so it is worded once. @@ -319,7 +316,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture `responseTimeout`, `idleTimeout`, `shutdownTimeout` and `reassemblyTimeout` from below only, and Node fires a larger delay after 1 ms. From the 2026-09-28 scoring run. -- [ ] **Keep a bare ESC out of GSM detection.** `gsmRegex` in `defs/encodings.ts` admits `\x1B`, +- [ ] **Keep a bare ESC out of GSM detection.** `gsmRegex` in `codec/encodings.ts` admits `\x1B`, so `"\x1B("` is detected as GSM, goes out as 0x1B 0x28 and arrives as `{`. From the 2026-09-28 scoring run. @@ -340,31 +337,17 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture ### Shape — 6 today, and the gate is 7 - [ ] **Answer "is this a bind command" in one place.** `bindCommands` (read by - `incoming-requests.ts`, `outgoing-requests.ts` and `test/session.test.ts`) and + `session/requests-in.ts`, `session/outgoing-requests.ts` and `test/session.test.ts`) and `bindTypeFromCommand()` (read by `server.ts` and `checkedBind()`) each list the three bind commands, so a fourth added to one is missed by the other. Derive the list from the function, or the reverse. From the stability review of #42. -- [ ] **Group `src/` into a second level, and retire whichever record loses.** 34 files on one - plane, where `src/defs/` at 7 proves the shape is known one level down. `docs/decisions.md` - says "`src/` stays flat until a module has to move for another reason. Valid while that map is - what a reader navigates by" — and both architects reported that the map is now AGENTS.md rather - than the tree, which is that premise failing. `todo.md` already carries the opposite - instruction under Worth doing. Two records, opposite answers; one has to go. - - [ ] **Split `test/session-extras.test.ts` by the question each block answers.** 3,010 lines, 19 unrelated `describe` blocks whose names are already the file names they should be. With `session.test.ts` it is 54% of all test code and 84% the size of `src/`. "extras" names neither a question nor a module — it names the rest — and AGENTS.md's own convention forbids exactly that. `max-lines` covers `src/**` only, so nothing has stopped it growing. -- [ ] **Collapse the three objects named `defaults`.** `client.ts`, `server.ts` and - `session-options.ts` each export or hold one; `port: 2775` is written twice and the idle - timeout is derived two ways to the same 40 000, and 64 MiB is both `defaultMaxOctets` and - `defaults.maxHeldOctets`. "What is the default for X" has three answers - depending on the entrypoint, and nothing fails when they drift. Named by both architects as the - most likely first bug a new contributor ships. - - [ ] **Give `hold` one meaning, and rename `IncomingRequests.refusing` for what it does.** `LinkLife.hold()` is a request's budget waiting for a link, `HeldMessages.hold()` a message the application owes an answer; `refusing` decides no refusal — `held.full()` does — and only makes @@ -377,12 +360,6 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture the breaking unit, so this is as cheap as it will ever be, and `todo.md` already requires the `consts.ENCODING` names settled before the custom-encoding registry — this is the other half. -- [ ] **Split `session-options.ts` into the things it is.** Option types and their validator, the - `SessionEvents` map, and the bind-direction rules (`bindCommands`, `bindTypeFromCommand`, - `standsInFor`, `bindCarries`) are three questions in one file, and the `defaults` table mixes - option defaults with four hard bounds that are not options. Both architects named it as where - the codebase rots first: at 34-wide it is where anything session-shaped lands. - - [ ] **Name the base-versus-segment distinction in the message id types.** `Sms.smsId` is a base, `sendSms().smsIds[]` are segment ids, `Dlr.smsId` is a segment id and `MessageDlr.smsId` is a base again — four fields, one type, `string`. The whole multipart receipt mechanism turns on @@ -464,9 +441,9 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture the fenced blocks at test time and assert each appears verbatim in the executed source, so an edit to either fails the gate. -- [ ] **Narrow the `src/defs/*` lint exemption to the four table files.** Its stated reason — "the +- [ ] **Narrow the `src/codec/` table lint exemption to the four table files.** Its stated reason — "the spec tables are data: their length tracks the specification, not any complexity" — is false for - `defs/types.ts`, which is 595 lines of wire codec with 25 functions and is the file that parses + `codec/field-types.ts`, which is 595 lines of wire codec with 25 functions and is the file that parses hostile input from the network. It carries more over-budget methods than any other file in the repo, under a suppression written for something else. @@ -524,7 +501,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture `idleTimeout: '5000'` is refused with `got 5000` — a value the reader reads as correct — where `connectTimeout` quotes it. `namedValue()`'s four sites — `messagingMode`, `encoding`, the time options and `smsIdFormat` — are the same defect once more: there `true` and `'true'` both print - as `true`. One fix closes all three, and `valueText()` in `defs/types.ts` is the quoted + as `true`. One fix closes all three, and `valueText()` in `codec/field-types.ts` is the quoted spelling to take it from. Raised by review, 2026-09-20. - [ ] **Refuse a send the codec cannot build before it waits for a link and a window slot.** Today @@ -685,7 +662,7 @@ Each lands under goal 7: an option or a hook, with the call that passes none unc ### Encodings - [ ] **Register a custom encoding.** Maintainer's ask, 2026-09-14. `EncodingName` is a closed union - of three (`defs/encodings.ts`). An entry needs a name, a `data_coding`, `encode`, `decode`, + of three (`codec/encodings.ts`). An entry needs a name, a `data_coding`, `encode`, `decode`, `match`, whether `detect()` may pick it, and enough for `splitMessage()` to budget a segment without halving a character. Take encodings as a client or server option rather than mutating a module table as `smpp` does, so two sessions in one process cannot disagree about a name. A taken -- 2.52.0 From 92479d697d781b50e774d93595644383c71d9c5c Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 30 Sep 2026 20:21:59 +0200 Subject: [PATCH 2/5] Record the layered src decision and the pdu-to-message edge --- AGENTS.md | 14 ++++++++------ docs/decisions.md | 7 +++++++ src/client/client.ts | 1 - src/server/server.ts | 1 - todo.md | 5 ++--- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2773f86..375ace7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,7 +49,7 @@ src/ unanswered-error.ts UnansweredError: it went out and no answer came back codec/ Bytes <-> PduObject commands.ts The 33 commands, their ids and ordered parameter lists - constants.ts consts + constsById, and the SMPP version constants + constants.ts consts + constsById, and the version that allows optional parameters encodings.ts GSM 03.38, LATIN1, UCS2, detection, data_coding resolution field-types.ts Wire types: int8/int16/int32/string/cstring/buffer/arrays framer.ts PduFramer: a byte stream cut into complete PDUs @@ -84,11 +84,12 @@ src/ server/server.ts server() -> { err, server }, server owns the listener + close() ``` -Imports point one way: `codec` ← `protocol` ← `messages` ← `session/` ← `client`/`server`, and -the root files sit beside that order; `codec` reaches outside itself only for `result.ts` and, from -`pdu.ts`, `message.ts`. The ways back up are the `Session` handed to `createSms()`, `HeldMessages` -and `IncomingRequests`, which call back into it, and to `OnRequest` and `onConnected` in -`options.ts`, all imported as a type only. +Imports point one way: `codec` ← `protocol` ← `messages` ← `session/` ← `client`/`server`, and the +root files sit beside that order. One edge runs against it: `codec/pdu.ts` reads `message.ts`, which +reads `protocol/udh.ts`, so moving `message.ts` into `protocol/` needs `pdu.ts` off it first. The +ways back up are the `Session` handed to `createSms()`, `HeldMessages` and `IncomingRequests`, which +call back into it, and to `OnRequest` and `onConnected` in `options.ts`, all imported as a type +only. **Parameter order is wire order.** The key order inside `cmds.*.params` is the order the fields are written to and read from the buffer. Never sort those alphabetically — the alphabetical-ordering @@ -317,6 +318,7 @@ this is not a changelog. `SendWindow` rather than extracted. - `SmppLog` is a five-method contract this library declares, not a dependency. - The TLS tests build their own self-signed certificate in DER +- `src/` is grouped by layer, and imports point down the layers. - `test/` stays flat, and a file there is named for the question it answers rather than for the module it covers. - CI tests on Linux only; `src/` keeps off what is known to break on macOS or Windows. diff --git a/docs/decisions.md b/docs/decisions.md index 3f16cd1..3828d92 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -795,6 +795,13 @@ rule and an index of the titles below. fail on every developer machine, and a committed key leaks in a public repository. Valid while the dev image has no openssl. +- **`src/` is grouped by layer, and imports point down the layers.** Maintainer's call, 2026-09-30, + building [plan 3](comprehension-rewrite/plan-3.md): `codec/`, `protocol/`, `messages/`, `session/`, + `client/`, `server/`, with the [map](../AGENTS.md#architecture) naming the direction. Serves goal + 8's reshapeable internals, which a reader has to find before reshaping: every comprehension panel + navigated by AGENTS.md's map rather than the flat tree it described. Rejected: `src/` flat until a + module has to move for another reason. + - **`test/` stays flat, and a file there is named for the question it answers rather than for the module it covers.** Architecture review, 2026-09-08, at 18 test files: what keeps that count honest is the naming rule rather than a tree — `operator-receipts.test.ts` holds a corpus defined by where diff --git a/src/client/client.ts b/src/client/client.ts index d2e36eb..4243441 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -41,7 +41,6 @@ export type ClientOptions = { username?: string; }; - function armConnectTimeout( sock: Socket, connectTimeout: number | false, diff --git a/src/server/server.ts b/src/server/server.ts index 392a4c4..2aa4f1f 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -50,7 +50,6 @@ export type ServerEvents = { session: [Session]; }; - /** A listener may return a promise: an `async` one that rejects is routed like one that throws. */ type ServerListener = (...args: ServerEvents[K]) => unknown; diff --git a/todo.md b/todo.md index d55c79f..f73b491 100644 --- a/todo.md +++ b/todo.md @@ -215,8 +215,7 @@ comprehension-panel scoring run over the whole project. Each round: - let no dimension drop, and record the four scores and the overall in this paragraph. Each chunk also closes the items further down that it absorbs, and its PR names them: `hold` and -`refusing`, `ASCII`, and the link that -dropped mid-rebind. +`refusing`, `ASCII`, and the link that dropped mid-rebind. Every chunk that changes the public API updates README's examples, MIGRATION.md, CHANGELOG.md, docs/decisions.md and the AGENTS.md map in its own PR, and deletes what it replaces; no old and new @@ -538,7 +537,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture Maintainer's ask, 2026-09-14; not started until asked. - [ ] **Count what is left of a budget one way in `leftOf()` and `LinkLife`.** Today they are one - concept counted twice. `idle-waiters.ts` reads what is left of a budget as `Math.max(1, + concept counted twice. `session/waiting.ts` reads what is left of a budget as `Math.max(1, deadline - now)`, because 0 means "forever" there; `link-life.ts` runs the same subtraction and calls `<= 0` expired. Neither is reachable from the other, so nothing can disagree today, but a reader who learns one and applies it to the other is wrong. A budget type both take -- 2.52.0 From bf1bddba3ba53ec0850f12f3b40f50f204aa0884 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 30 Sep 2026 20:33:13 +0200 Subject: [PATCH 3/5] Name files for what they export, and move the session files into session/ --- AGENTS.md | 98 +++++++++---------- docs/decisions.md | 11 +-- eslint.config.js | 4 +- interop-tests/cloudhopper.test.ts | 4 +- interop-tests/dumbclient.test.ts | 6 +- interop-tests/jasmin.test.ts | 8 +- interop-tests/jsmpp.test.ts | 4 +- interop-tests/kannel.test.ts | 8 +- interop-tests/php.test.ts | 6 +- interop-tests/python.test.ts | 6 +- interop-tests/smppload.test.ts | 2 +- interop-tests/smppsim.test.ts | 10 +- interop-tests/smscsim.test.ts | 6 +- src/client/client.ts | 4 +- src/codec/commands.ts | 4 +- src/codec/{statuses.ts => errors.ts} | 0 src/codec/{framer.ts => pdu-framer.ts} | 0 src/codec/pdu.ts | 8 +- src/codec/refusal.ts | 2 +- src/codec/{retained.ts => retained-pdu.ts} | 4 +- src/codec/tlvs.ts | 4 +- src/codec/{field-types.ts => types.ts} | 0 src/index.ts | 24 ++--- .../{receipt-merge.ts => dlr-merger.ts} | 2 +- src/messages/reassembly.ts | 4 +- src/messages/submit.ts | 4 +- src/options.ts | 2 +- src/protocol/concat.ts | 2 +- src/protocol/{receipt.ts => dlr.ts} | 4 +- src/protocol/message-ids.ts | 2 +- src/server/server.ts | 4 +- src/session/held-messages.ts | 12 +-- src/session/{waiting.ts => idle-waiters.ts} | 0 .../{requests-in.ts => incoming-requests.ts} | 12 +-- src/{ => session}/link-life.ts | 4 +- src/session/{keepalive.ts => link-timers.ts} | 0 src/session/outgoing-requests.ts | 4 +- .../{transport.ts => pdu-transport.ts} | 2 +- src/{ => session}/reconnect-loop.ts | 6 +- src/session/send-window.ts | 2 +- src/{ => session}/session.ts | 52 +++++----- src/{ => session}/sms.ts | 26 ++--- test/declared-alphabet.test.ts | 4 +- test/dlr.test.ts | 2 +- test/dummy-smsc.ts | 4 +- test/interop.test.ts | 2 +- test/message-class.test.ts | 6 +- test/messaging-mode.test.ts | 4 +- test/operator-receipts.test.ts | 6 +- test/pdu-framer.test.ts | 2 +- test/pdu.test.ts | 2 +- test/readme.test.ts | 6 +- test/session-extras.test.ts | 24 ++--- test/session.test.ts | 14 +-- test/tls.test.ts | 2 +- test/types.test.ts | 4 +- test/unsendable.test.ts | 2 +- todo.md | 21 +++- 58 files changed, 242 insertions(+), 230 deletions(-) rename src/codec/{statuses.ts => errors.ts} (100%) rename src/codec/{framer.ts => pdu-framer.ts} (100%) rename src/codec/{retained.ts => retained-pdu.ts} (94%) rename src/codec/{field-types.ts => types.ts} (100%) rename src/messages/{receipt-merge.ts => dlr-merger.ts} (99%) rename src/protocol/{receipt.ts => dlr.ts} (98%) rename src/session/{waiting.ts => idle-waiters.ts} (100%) rename src/session/{requests-in.ts => incoming-requests.ts} (96%) rename src/{ => session}/link-life.ts (98%) rename src/session/{keepalive.ts => link-timers.ts} (100%) rename src/session/{transport.ts => pdu-transport.ts} (98%) rename src/{ => session}/reconnect-loop.ts (96%) rename src/{ => session}/session.ts (90%) rename src/{ => session}/sms.ts (90%) diff --git a/AGENTS.md b/AGENTS.md index 375ace7..bac78a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,59 +37,59 @@ These are not preferences. Breaking one is a defect. ``` src/ - index.ts Public surface. Named exports only, no default export; assembles `defs`. - link-life.ts LinkLife: whether the link lives, and where a request waits for the next one - log.ts SmppLog, the logger contract, and silentLog — the default - message.ts Encoding detection, splitting, bit counting, SMPP date formatting - options.ts SessionOptions, ReconnectOptions, their checks, and `defaults`: every default and internal cap - reconnect-loop.ts ReconnectLoop: backoff, retry timer, stopped-ness - result.ts Result, and an untyped value as error material: errorFrom(), namedValue(), quoted() - session.ts Session: the socket's life, dispatch, events, and the collaborators in session/ - sms.ts The live handle emitted as the 'sms' event (sendResp/sendDlr) - unanswered-error.ts UnansweredError: it went out and no answer came back - codec/ Bytes <-> PduObject - commands.ts The 33 commands, their ids and ordered parameter lists - constants.ts consts + constsById, and the version that allows optional parameters - encodings.ts GSM 03.38, LATIN1, UCS2, detection, data_coding resolution - field-types.ts Wire types: int8/int16/int32/string/cstring/buffer/arrays - framer.ts PduFramer: a byte stream cut into complete PDUs - pdu.ts pduToObj / objToPdu / pduReturn — synchronous, result-returning - refusal.ts A PDU the codec would not read, and the answer SMPP names for it - retained.ts A PDU copied off the wire so holding it pins nothing else, and what holding it costs - statuses.ts errors + errorsById (ESME_*) - tlvs.ts TLV definitions, tlvsById, the typed read and input shapes, and reading and writing a TLV stream - protocol/ What the fields mean - bind.ts Bind directions: which commands bind, what a direction carries, data_sm's stand-in, checkedBind() - concat.ts How a PDU says it is a segment: its UDH, or the sar_* TLVs - message-body.ts Where an inbound body is: short_message, or the message_payload TLV - message-ids.ts Message ids: the peer's notation, the - a segment gets, which response carries one - receipt.ts Delivery receipts: text and TLV parsing, receipt status codes - udh.ts User data header: its length, the concatenation fields of a long SMS and their reference - uuid.ts uuidv7() — the ids the library generates for messages - messages/ Whole messages across segments and time + index.ts Public surface. Named exports only, no default export; assembles `defs`. + log.ts SmppLog, silentLog — the default, and guardedLog(): a logger that cannot throw + message.ts Message bodies: encodeBody/decodeMessage under a data_coding, splitting, bit counting, smppDate/smppTime + options.ts SessionOptions, ReconnectOptions, their checks, and `defaults`: every default and internal cap + result.ts Result, and an untyped value as error material: errorFrom(), namedValue(), quoted() + unanswered-error.ts + codec/ Bytes <-> PduObject + commands.ts The 33 commands, their ids and ordered parameter lists + constants.ts consts + constsById, optionalParamsMinVersion, and esm_class's readers: hasUdh(), messageTypeOf() + encodings.ts GSM 03.38, LATIN1, UCS2, detection, data_coding resolution, message class + errors.ts errors + errorsById (ESME_*) + pdu-framer.ts PduFramer + pdu.ts pduToObj / objToPdu / pduReturn — synchronous, result-returning + refusal.ts A PDU the codec would not read, and the answer SMPP names for it + retained-pdu.ts A PDU copied off the wire so holding it pins nothing else, and what holding it costs + tlvs.ts TLV definitions, tlvsById, the typed read and input shapes, and reading and writing a TLV stream + types.ts Wire types: int8/int16/int32/string/cstring/buffer/arrays + protocol/ What the fields mean + bind.ts Bind directions: which commands bind, what a direction carries, data_sm's stand-in, checkedBind() + concat.ts How a PDU says it is a segment: its UDH, or the sar_* TLVs + dlr.ts Delivery receipts: text and TLV parsing, receipt status codes + message-body.ts Where an inbound body is: short_message, or the message_payload TLV + message-ids.ts Message ids: the peer's notation, the - a segment gets, which response carries one + udh.ts User data header: its length, the concatenation fields of a long SMS and their reference + uuid.ts uuidv7() — the ids the library generates for messages + messages/ Whole messages across segments and time + dlr-merger.ts DlrMerger: per-segment receipts counted into one MessageDlr expiring-groups.ts ExpiringGroups: the capped, weighed, expiring store DlrMerger, HeldMessages and Reassembler share - reassembly.ts Reassembler: capped, expiring multipart groups - receipt-merge.ts DlrMerger: per-segment receipts counted into one MessageDlr - submit.ts submitSms composition and the submitSmParams builder - session/ One socket's collaborators - held-messages.ts HeldMessages: a message from its `sms` event to its answer, capped and expiring, one MessageHold each - keepalive.ts LinkTimers: the enquire_link heartbeat and the idle timeout + reassembly.ts Reassembler: capped, expiring multipart groups + submit.ts submitSms composition and the submitSmParams builder + session/ One socket's life, and reconnecting it + held-messages.ts HeldMessages: a message from its `sms` event to its answer, capped and expiring, one MessageHold each + idle-waiters.ts IdleWaiters: waiting for a count to fall to zero, and what is left of a budget + incoming-requests.ts IncomingRequests: every request the peer sends — messages, receipts, links, unknown commands + link-life.ts LinkLife: whether the link lives, and where a request waits for the next one + link-timers.ts LinkTimers: the enquire_link heartbeat and the idle timeout outgoing-requests.ts OutgoingRequests: the window, the pending map and the retry - pending-requests.ts PendingRequests: sequence numbers, correlation, timeout, abort - requests-in.ts IncomingRequests: every request the peer sends: messages, receipts, links, unknown commands - send-window.ts SendWindow: the maxOutstanding semaphore - transport.ts PduTransport: the socket a session reads complete PDUs off - waiting.ts IdleWaiters: waiting for a count to fall to zero, and what is left of a budget - client/client.ts client() -> { err, session } - server/server.ts server() -> { err, server }, server owns the listener + close() + pdu-transport.ts PduTransport: the socket a session reads complete PDUs off + pending-requests.ts PendingRequests: sequence numbers, correlation, timeout, abort + reconnect-loop.ts ReconnectLoop: backoff, retry timer, stopped-ness + send-window.ts SendWindow: the maxOutstanding semaphore + session.ts Session: the socket's life, dispatch and events, composing the rest of session/ + sms.ts The live handle emitted as the 'sms' event (sendResp/sendDlr) + client/client.ts client() -> { err, session } + server/server.ts server() -> { err, server }, server owns the listener + close() ``` -Imports point one way: `codec` ← `protocol` ← `messages` ← `session/` ← `client`/`server`, and the -root files sit beside that order. One edge runs against it: `codec/pdu.ts` reads `message.ts`, which -reads `protocol/udh.ts`, so moving `message.ts` into `protocol/` needs `pdu.ts` off it first. The -ways back up are the `Session` handed to `createSms()`, `HeldMessages` and `IncomingRequests`, which -call back into it, and to `OnRequest` and `onConnected` in `options.ts`, all imported as a type -only. +Imports point one way: `codec` ← `protocol` ← `messages` ← `session` ← `client`/`server`. At the +root, `result.ts`, `log.ts` and `unanswered-error.ts` sit below `codec`, and `message.ts` and +`options.ts` with `protocol`. One edge runs up: `codec/pdu.ts` imports `encodeBody()` and +`decodeMessage()` from `message.ts`. The ways back up are the `Session` handed to `createSms()`, +`HeldMessages` and `IncomingRequests`, which call back into it, and to `OnRequest` and `onConnected` +in `options.ts`, all imported as a type only. **Parameter order is wire order.** The key order inside `cmds.*.params` is the order the fields are written to and read from the buffer. Never sort those alphabetically — the alphabetical-ordering diff --git a/docs/decisions.md b/docs/decisions.md index 3828d92..034d884 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -295,7 +295,7 @@ rule and an index of the titles below. a delivery receipt (0x04).** Appendix B makes a receipt's `stat` the message's final status, so 0x04 over `ENROUTE` emits the two disagreeing spellings of finality the reading side above has to reconcile, and goal 3 has our own senders write the marker 3.4 defines. `sendDlr()` takes the list - from `transientStates` in `protocol/receipt.ts`, the same one the reader uses, so the two cannot drift. + from `transientStates` in `protocol/dlr.ts`, the same one the reader uses, so the two cannot drift. Rejected: 0x04 for every state, for the sake of a peer that classifies on the marker — the cost accepted here is that such a peer stops recognising a transient report as a report at all and hands its application receipt text as an inbound message, where under 0x04 it would have read the state @@ -796,11 +796,10 @@ rule and an index of the titles below. dev image has no openssl. - **`src/` is grouped by layer, and imports point down the layers.** Maintainer's call, 2026-09-30, - building [plan 3](comprehension-rewrite/plan-3.md): `codec/`, `protocol/`, `messages/`, `session/`, - `client/`, `server/`, with the [map](../AGENTS.md#architecture) naming the direction. Serves goal - 8's reshapeable internals, which a reader has to find before reshaping: every comprehension panel - navigated by AGENTS.md's map rather than the flat tree it described. Rejected: `src/` flat until a - module has to move for another reason. + with the Locality rewrite; the [map](../AGENTS.md#architecture) names the order and places each + root file in a layer. Serves goal 8: internals are reshapeable only once a reader can find them, + and every comprehension panel navigated by the map. Rejected: `src/` flat until a module has to + move for another reason. Valid while the map is what readers navigate by. - **`test/` stays flat, and a file there is named for the question it answers rather than for the module it covers.** Architecture review, 2026-09-08, at 18 test files: what keeps that count honest diff --git a/eslint.config.js b/eslint.config.js index 0b22b9d..5eb123a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -35,12 +35,12 @@ export default tseslint.config( }, { // The spec tables are data: their length tracks the specification, not any complexity. - files: ['src/codec/{commands,constants,encodings,field-types,statuses,tlvs}.ts'], + files: ['src/codec/{commands,constants,encodings,errors,tlvs,types}.ts'], rules: { 'max-lines': 'off' }, }, { // ESLint counts every ?. and ?? in dlrFromPdu as a branch; the 19 is 26 lines of flat field resolution. - files: ['src/protocol/receipt.ts'], + files: ['src/protocol/dlr.ts'], rules: { complexity: ['error', 19] }, }, { diff --git a/interop-tests/cloudhopper.test.ts b/interop-tests/cloudhopper.test.ts index 37c3ac7..a31e8e7 100644 --- a/interop-tests/cloudhopper.test.ts +++ b/interop-tests/cloudhopper.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import test, { after, describe } from 'node:test'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import type { SmppServer } from '../src/server/server.ts'; import { server } from '../src/server/server.ts'; diff --git a/interop-tests/dumbclient.test.ts b/interop-tests/dumbclient.test.ts index 2edcce9..ba9b82c 100644 --- a/interop-tests/dumbclient.test.ts +++ b/interop-tests/dumbclient.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import test, { after, describe } from 'node:test'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import type { LogMethod, SmppLog } from '../src/log.ts'; import { server } from '../src/server/server.ts'; @@ -270,7 +270,7 @@ describe('S6 - idle peer, no enquire_link at all', () => { assert.ok(bound, 'dumb-idle never submitted its one message'); // idleTimeout is 40s from the last byte the peer sent (its submit_sm), never from our own - // writes (session/keepalive.ts resets only on inbound data). This test may start running well + // writes (session/link-timers.ts resets only on inbound data). This test may start running well // past that mark on its own (S9 above can take a minute) - statsFor(...).closed is set from // a 'close' listener attached at session-creation time, so a close from before this test // even started is still seen; budget is slack for a session that is still open, not a clock. diff --git a/interop-tests/jasmin.test.ts b/interop-tests/jasmin.test.ts index a835572..4ee53a0 100644 --- a/interop-tests/jasmin.test.ts +++ b/interop-tests/jasmin.test.ts @@ -1,15 +1,15 @@ import assert from 'node:assert/strict'; import http from 'node:http'; import test, { after, describe } from 'node:test'; -import type { Dlr } from '../src/protocol/receipt.ts'; +import type { Dlr } from '../src/protocol/dlr.ts'; import type { EncodingName } from '../src/codec/encodings.ts'; import type { PduObject } from '../src/codec/pdu.ts'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import { ConcatReference } from '../src/protocol/udh.ts'; import { client } from '../src/client/client.ts'; import { closeAfter } from '../test/teardown.ts'; -import { paramText } from '../src/codec/field-types.ts'; +import { paramText } from '../src/codec/types.ts'; import { server } from '../src/server/server.ts'; import { encodeMessage, splitMessage } from '../src/message.ts'; import { submitSmParams } from '../src/messages/submit.ts'; diff --git a/interop-tests/jsmpp.test.ts b/interop-tests/jsmpp.test.ts index ed2ba0b..aeefb65 100644 --- a/interop-tests/jsmpp.test.ts +++ b/interop-tests/jsmpp.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import net from 'node:net'; import test, { after, describe } from 'node:test'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import { PduRefusedError } from '../src/index.ts'; import { bareTlvHeader, pduBytes } from '../test/raw-pdus.ts'; import { server } from '../src/server/server.ts'; diff --git a/interop-tests/kannel.test.ts b/interop-tests/kannel.test.ts index 4ce96be..f82e065 100644 --- a/interop-tests/kannel.test.ts +++ b/interop-tests/kannel.test.ts @@ -2,13 +2,13 @@ import assert from 'node:assert/strict'; import http from 'node:http'; import test, { after, describe } from 'node:test'; import type { MessageState } from '../src/codec/constants.ts'; -import type { Dlr } from '../src/protocol/receipt.ts'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Dlr } from '../src/protocol/dlr.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import { ConcatReference } from '../src/protocol/udh.ts'; import { consts } from '../src/codec/constants.ts'; import { detect, encodings } from '../src/codec/encodings.ts'; -import { paramText } from '../src/codec/field-types.ts'; +import { paramText } from '../src/codec/types.ts'; import { server } from '../src/server/server.ts'; import { splitMessage } from '../src/message.ts'; import { submitSmParams } from '../src/messages/submit.ts'; diff --git a/interop-tests/php.test.ts b/interop-tests/php.test.ts index 761f138..0c08623 100644 --- a/interop-tests/php.test.ts +++ b/interop-tests/php.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import test, { after, describe } from 'node:test'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; -import { paramText } from '../src/codec/field-types.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; +import { paramText } from '../src/codec/types.ts'; import { isCommand, server } from '../src/index.ts'; const DRIVER = process.env.PHP_DRIVER ?? 'php:8080'; diff --git a/interop-tests/python.test.ts b/interop-tests/python.test.ts index 138b897..6ed20ee 100644 --- a/interop-tests/python.test.ts +++ b/interop-tests/python.test.ts @@ -1,9 +1,9 @@ import assert from 'node:assert/strict'; import test, { after, describe } from 'node:test'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import { encodings } from '../src/codec/encodings.ts'; -import { paramText } from '../src/codec/field-types.ts'; +import { paramText } from '../src/codec/types.ts'; import { isCommand, server } from '../src/index.ts'; const DRIVER = process.env.PYTHON_DRIVER ?? 'python:8080'; diff --git a/interop-tests/smppload.test.ts b/interop-tests/smppload.test.ts index a8ebe1b..b35add5 100644 --- a/interop-tests/smppload.test.ts +++ b/interop-tests/smppload.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import test, { after, describe } from 'node:test'; -import type { Session } from '../src/session.ts'; +import type { Session } from '../src/session/session.ts'; import { server } from '../src/server/server.ts'; const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775'); diff --git a/interop-tests/smppsim.test.ts b/interop-tests/smppsim.test.ts index 141eda3..169ad22 100644 --- a/interop-tests/smppsim.test.ts +++ b/interop-tests/smppsim.test.ts @@ -1,15 +1,15 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { Dlr } from '../src/protocol/receipt.ts'; +import type { Dlr } from '../src/protocol/dlr.ts'; import type { EncodingName } from '../src/codec/encodings.ts'; -import type { MessageDlr } from '../src/messages/receipt-merge.ts'; +import type { MessageDlr } from '../src/messages/dlr-merger.ts'; import type { PduObject } from '../src/codec/pdu.ts'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import { client } from '../src/client/client.ts'; import { closeAfter } from '../test/teardown.ts'; import { consts } from '../src/codec/constants.ts'; -import { paramText } from '../src/codec/field-types.ts'; +import { paramText } from '../src/codec/types.ts'; import { server } from '../src/server/server.ts'; const PEER_HOST = process.env.PEER_HOST ?? 'smppsim'; diff --git a/interop-tests/smscsim.test.ts b/interop-tests/smscsim.test.ts index 35bbb0f..228bc23 100644 --- a/interop-tests/smscsim.test.ts +++ b/interop-tests/smscsim.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { Dlr } from '../src/protocol/receipt.ts'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Dlr } from '../src/protocol/dlr.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import { client } from '../src/client/client.ts'; import { closeAfter } from '../test/teardown.ts'; diff --git a/src/client/client.ts b/src/client/client.ts index 4243441..046e3f4 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -7,8 +7,8 @@ import type { SmsIdFormat } from '../protocol/message-ids.ts'; import type { Socket } from 'node:net'; export type { BindType }; -import { ReconnectLoop } from '../reconnect-loop.ts'; -import { Session } from '../session.ts'; +import { ReconnectLoop } from '../session/reconnect-loop.ts'; +import { Session } from '../session/session.ts'; import { checkSessionOptions, defaults } from '../options.ts'; import { connect as netConnect } from 'node:net'; import { connect as tlsConnect } from 'node:tls'; diff --git a/src/codec/commands.ts b/src/codec/commands.ts index 1be9a60..13ebf82 100644 --- a/src/codec/commands.ts +++ b/src/codec/commands.ts @@ -1,5 +1,5 @@ -import type { WireType } from './field-types.ts'; -import { buffer, cstring, dest_address_array, int8, unsuccess_sme_array } from './field-types.ts'; +import type { WireType } from './types.ts'; +import { buffer, cstring, dest_address_array, int8, unsuccess_sme_array } from './types.ts'; type CommandSpec = { id: number; diff --git a/src/codec/statuses.ts b/src/codec/errors.ts similarity index 100% rename from src/codec/statuses.ts rename to src/codec/errors.ts diff --git a/src/codec/framer.ts b/src/codec/pdu-framer.ts similarity index 100% rename from src/codec/framer.ts rename to src/codec/pdu-framer.ts diff --git a/src/codec/pdu.ts b/src/codec/pdu.ts index b27c7cc..8d97194 100644 --- a/src/codec/pdu.ts +++ b/src/codec/pdu.ts @@ -1,6 +1,6 @@ import type { CommandDefinition, CommandName, PduParams, PduParamsInput } from './commands.ts'; -import type { ErrorName } from './statuses.ts'; -import type { ParamValue } from './field-types.ts'; +import type { ErrorName } from './errors.ts'; +import type { ParamValue } from './types.ts'; import type { PduHeader } from './refusal.ts'; import type { Result, VoidResult } from '../result.ts'; import type { TlvInputs, Tlvs } from './tlvs.ts'; @@ -8,8 +8,8 @@ import { PduRefusedError, framingRefusal } from './refusal.ts'; import { cmds, commandNameById, respNameFor } from './commands.ts'; import { hasUdh } from './constants.ts'; import { decodeMessage, encodeBody } from '../message.ts'; -import { errorNameById, errors, isErrorName } from './statuses.ts'; -import { paramNumber, valueText } from './field-types.ts'; +import { errorNameById, errors, isErrorName } from './errors.ts'; +import { paramNumber, valueText } from './types.ts'; import { parseTlvs, writeTlvs } from './tlvs.ts'; /** The highest sequence number this library hands out; SMPP 3.4 4.7.1 reserves 0x7fffffff. */ diff --git a/src/codec/refusal.ts b/src/codec/refusal.ts index 317b69d..233503b 100644 --- a/src/codec/refusal.ts +++ b/src/codec/refusal.ts @@ -1,5 +1,5 @@ import type { CommandName } from './commands.ts'; -import type { ErrorName } from './statuses.ts'; +import type { ErrorName } from './errors.ts'; import { respNameFor } from './commands.ts'; /** A hostile peer must not be able to make us allocate arbitrarily. */ diff --git a/src/codec/retained.ts b/src/codec/retained-pdu.ts similarity index 94% rename from src/codec/retained.ts rename to src/codec/retained-pdu.ts index 9fda3cc..36c670d 100644 --- a/src/codec/retained.ts +++ b/src/codec/retained-pdu.ts @@ -1,6 +1,6 @@ -import type { ParamValue } from './field-types.ts'; +import type { ParamValue } from './types.ts'; import type { PduObject } from './pdu.ts'; -import { tlvOctets } from './field-types.ts'; +import { tlvOctets } from './types.ts'; /** Wire reads hand back views, so retaining one PDU would pin the whole chunk it arrived in. */ export function detach(pduObj: PduObject): PduObject { diff --git a/src/codec/tlvs.ts b/src/codec/tlvs.ts index d54d57e..fc4d6b0 100644 --- a/src/codec/tlvs.ts +++ b/src/codec/tlvs.ts @@ -1,6 +1,6 @@ -import type { ParamValue, TlvValue, WireType } from './field-types.ts'; +import type { ParamValue, TlvValue, WireType } from './types.ts'; import type { Result } from '../result.ts'; -import { tlv } from './field-types.ts'; +import { tlv } from './types.ts'; /** Only a tag read as octets or as a number may repeat, since its occurrences are listed as one of those. */ type Definition = { id: number; multiple?: false; tag: Tag; type: WireType } diff --git a/src/codec/field-types.ts b/src/codec/types.ts similarity index 100% rename from src/codec/field-types.ts rename to src/codec/types.ts diff --git a/src/index.ts b/src/index.ts index 80ca7d8..623b60f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,20 @@ import { cmds, cmdsById } from './codec/commands.ts'; import { consts, constsById } from './codec/constants.ts'; import { encodings } from './codec/encodings.ts'; -import { errors, errorsById } from './codec/statuses.ts'; +import { errors, errorsById } from './codec/errors.ts'; import { tlvs, tlvsById } from './codec/tlvs.ts'; -import { types } from './codec/field-types.ts'; +import { types } from './codec/types.ts'; export { client } from './client/client.ts'; export { server, SmppServer } from './server/server.ts'; -export { Session } from './session.ts'; +export { Session } from './session/session.ts'; export { cmds, cmdsById, commandNameById, isCommandName } from './codec/commands.ts'; export { consts, constsById } from './codec/constants.ts'; export { dataCodingByEncoding, detect, encodingByDataCoding, encodings, isEncodingName, messageClassOf, unencodable } from './codec/encodings.ts'; -export { errorNameById, errors, errorsById, isErrorName } from './codec/statuses.ts'; +export { errorNameById, errors, errorsById, isErrorName } from './codec/errors.ts'; export { isTlvName, tlvs, tlvsById } from './codec/tlvs.ts'; -export { types } from './codec/field-types.ts'; +export { types } from './codec/types.ts'; export { isCommand, @@ -36,16 +36,16 @@ export { splitMessage, } from './message.ts'; -export { dlrFromPdu, parseReceipt, receiptCodes } from './protocol/receipt.ts'; +export { dlrFromPdu, parseReceipt, receiptCodes } from './protocol/dlr.ts'; export { messageOctets } from './protocol/message-body.ts'; export { concatOf } from './protocol/concat.ts'; export { concatInfo } from './protocol/udh.ts'; -export { PduFramer } from './codec/framer.ts'; +export { PduFramer } from './codec/pdu-framer.ts'; export { uuidv7 } from './protocol/uuid.ts'; export type { BindType, ClientOptions } from './client/client.ts'; -export type { Dlr, Receipt } from './protocol/receipt.ts'; -export type { SendDlrResult, SendRespOptions, Sms } from './sms.ts'; +export type { Dlr, Receipt } from './protocol/dlr.ts'; +export type { SendDlrResult, SendRespOptions, Sms } from './session/sms.ts'; export type { Concat } from './protocol/concat.ts'; export type { ConcatInfo } from './protocol/udh.ts'; export type { Result, VoidResult } from './result.ts'; @@ -66,16 +66,16 @@ export type { SendSmsResult, SessionEvents, SessionOptions, -} from './session.ts'; +} from './session/session.ts'; export type { CommandName, PduParams, PduParamsInput } from './codec/commands.ts'; export type { ConstGroup, MessageState, SubmitMessagingMode } from './codec/constants.ts'; export type { Encoding, EncodingName, Unencodable } from './codec/encodings.ts'; -export type { ErrorName } from './codec/statuses.ts'; +export type { ErrorName } from './codec/errors.ts'; export type { PduObject, PduObjectInput, TlvInputs } from './codec/pdu.ts'; export type { PduHeader } from './codec/refusal.ts'; export type { SplitOptions } from './message.ts'; export type { Tlv, TlvDefinition, TlvName, Tlvs } from './codec/tlvs.ts'; -export type { DestAddress, ParamValue, TlvValue, UnsuccessSme, WireType } from './codec/field-types.ts'; +export type { DestAddress, ParamValue, TlvValue, UnsuccessSme, WireType } from './codec/types.ts'; /** The spec tables, grouped the way `larvitsmpp.defs` was in 0.4.0. */ export const defs = { diff --git a/src/messages/receipt-merge.ts b/src/messages/dlr-merger.ts similarity index 99% rename from src/messages/receipt-merge.ts rename to src/messages/dlr-merger.ts index 541ecd7..0b85fe7 100644 --- a/src/messages/receipt-merge.ts +++ b/src/messages/dlr-merger.ts @@ -1,4 +1,4 @@ -import type { Dlr } from '../protocol/receipt.ts'; +import type { Dlr } from '../protocol/dlr.ts'; import type { MessageState } from '../codec/constants.ts'; import type { SmppLog } from '../log.ts'; import { ExpiringGroups } from './expiring-groups.ts'; diff --git a/src/messages/reassembly.ts b/src/messages/reassembly.ts index 97c73ff..f9e13f4 100644 --- a/src/messages/reassembly.ts +++ b/src/messages/reassembly.ts @@ -4,9 +4,9 @@ import type { SmppLog } from '../log.ts'; import { ExpiringGroups } from './expiring-groups.ts'; import { decodeMessage } from '../message.ts'; import { defaults } from '../options.ts'; -import { detach, retainedOctets } from '../codec/retained.ts'; +import { detach, retainedOctets } from '../codec/retained-pdu.ts'; import { messageOctets } from '../protocol/message-body.ts'; -import { paramNumber, paramText } from '../codec/field-types.ts'; +import { paramNumber, paramText } from '../codec/types.ts'; import { uuidv7 } from '../protocol/uuid.ts'; /** A concatenated message given up on, whose segments the peer has already been answered for. */ diff --git a/src/messages/submit.ts b/src/messages/submit.ts index 4a65858..4382f13 100644 --- a/src/messages/submit.ts +++ b/src/messages/submit.ts @@ -1,5 +1,5 @@ import type { EncodingName, Unencodable } from '../codec/encodings.ts'; -import type { ParamValue } from '../codec/field-types.ts'; +import type { ParamValue } from '../codec/types.ts'; import type { SubmitMessagingMode } from '../codec/constants.ts'; import type { PduObject, PduObjectInput } from '../codec/pdu.ts'; import type { Result } from '../result.ts'; @@ -7,7 +7,7 @@ import type { SmppLog } from '../log.ts'; import type { SmsIdNotation } from '../protocol/message-ids.ts'; import { UnansweredError } from '../unanswered-error.ts'; import { consts, defaultMessagingMode, isMessagingMode, isSubmitMessagingMode, submitMessagingModes } from '../codec/constants.ts'; -import { cstring, paramText } from '../codec/field-types.ts'; +import { cstring, paramText } from '../codec/types.ts'; import { dataCodingByEncoding, detect, encodingNames, isEncodingName, unencodable, unencodableText } from '../codec/encodings.ts'; import { namedValue } from '../result.ts'; import { normaliseSmsId } from '../protocol/message-ids.ts'; diff --git a/src/options.ts b/src/options.ts index 79e28a3..f856cf0 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,6 +1,6 @@ import type { PduObject } from './codec/pdu.ts'; import type { Result, VoidResult } from './result.ts'; -import type { Session } from './session.ts'; +import type { Session } from './session/session.ts'; import type { SmppLog } from './log.ts'; import type { SmsIdFormat } from './protocol/message-ids.ts'; import type { Socket } from 'node:net'; diff --git a/src/protocol/concat.ts b/src/protocol/concat.ts index 3aa021d..9932323 100644 --- a/src/protocol/concat.ts +++ b/src/protocol/concat.ts @@ -3,7 +3,7 @@ import type { PduObject } from '../codec/pdu.ts'; import { concatInfo } from './udh.ts'; import { hasUdh } from '../codec/constants.ts'; import { messageOctets } from './message-body.ts'; -import { paramNumber } from '../codec/field-types.ts'; +import { paramNumber } from '../codec/types.ts'; /** Where a segment sits in its message, and what ties it to the rest of that message. */ export type Concat = ConcatInfo & { diff --git a/src/protocol/receipt.ts b/src/protocol/dlr.ts similarity index 98% rename from src/protocol/receipt.ts rename to src/protocol/dlr.ts index 7b85584..7782f53 100644 --- a/src/protocol/receipt.ts +++ b/src/protocol/dlr.ts @@ -1,12 +1,12 @@ import type { MessageState } from '../codec/constants.ts'; -import type { TlvValue } from '../codec/field-types.ts'; +import type { TlvValue } from '../codec/types.ts'; import type { PduObject } from '../codec/pdu.ts'; import type { SmsIdFormat } from './message-ids.ts'; import { consts, constsById, hasUdh, messageTypeOf } from '../codec/constants.ts'; import { encodings } from '../codec/encodings.ts'; import { messageOctets } from './message-body.ts'; import { normaliseSmsId } from './message-ids.ts'; -import { paramNumber, paramText } from '../codec/field-types.ts'; +import { paramNumber, paramText } from '../codec/types.ts'; import { udhLength } from './udh.ts'; /** diff --git a/src/protocol/message-ids.ts b/src/protocol/message-ids.ts index 047dea1..b4440d9 100644 --- a/src/protocol/message-ids.ts +++ b/src/protocol/message-ids.ts @@ -1,5 +1,5 @@ import type { CommandName } from '../codec/commands.ts'; -import type { ParamValue } from '../codec/field-types.ts'; +import type { ParamValue } from '../codec/types.ts'; const notations = { decimal: { digits: /^[0-9]+$/, prefix: '' }, diff --git a/src/server/server.ts b/src/server/server.ts index 2aa4f1f..edca63f 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -6,13 +6,13 @@ import type { Server as NetServer, Socket } from 'node:net'; import type { Server as TlsServer, TlsOptions } from 'node:tls'; import type { SmppLog } from '../log.ts'; import { EventEmitter } from 'node:events'; -import { Session } from '../session.ts'; +import { Session } from '../session/session.ts'; import { bindTypeFromCommand } from '../protocol/bind.ts'; import { checkSessionOptions, defaults } from '../options.ts'; import { createServer as createNetServer } from 'node:net'; import { createServer as createTlsServer } from 'node:tls'; import { errorFrom } from '../result.ts'; -import { paramText } from '../codec/field-types.ts'; +import { paramText } from '../codec/types.ts'; import { guardedLog } from '../log.ts'; import { respNameFor } from '../codec/commands.ts'; diff --git a/src/session/held-messages.ts b/src/session/held-messages.ts index 517472e..eb52310 100644 --- a/src/session/held-messages.ts +++ b/src/session/held-messages.ts @@ -1,13 +1,13 @@ -import type { LinkLife } from '../link-life.ts'; +import type { LinkLife } from './link-life.ts'; import type { PduObject, PduObjectInput } from '../codec/pdu.ts'; import type { Result } from '../result.ts'; -import type { Session } from '../session.ts'; -import type { SmsHandlers } from '../sms.ts'; +import type { Session } from './session.ts'; +import type { SmsHandlers } from './sms.ts'; import type { SmppLog } from '../log.ts'; import { ExpiringGroups } from '../messages/expiring-groups.ts'; -import { IdleWaiters } from './waiting.ts'; -import { createSms } from '../sms.ts'; -import { retainedOctets } from '../codec/retained.ts'; +import { IdleWaiters } from './idle-waiters.ts'; +import { createSms } from './sms.ts'; +import { retainedOctets } from '../codec/retained-pdu.ts'; export type HeldMessagesOptions = { link: LinkLife; diff --git a/src/session/waiting.ts b/src/session/idle-waiters.ts similarity index 100% rename from src/session/waiting.ts rename to src/session/idle-waiters.ts diff --git a/src/session/requests-in.ts b/src/session/incoming-requests.ts similarity index 96% rename from src/session/requests-in.ts rename to src/session/incoming-requests.ts index 73e1b22..153dd01 100644 --- a/src/session/requests-in.ts +++ b/src/session/incoming-requests.ts @@ -1,13 +1,13 @@ import type { Concat } from '../protocol/concat.ts'; -import type { DlrMerger } from '../messages/receipt-merge.ts'; -import type { ErrorName } from '../codec/statuses.ts'; +import type { DlrMerger } from '../messages/dlr-merger.ts'; +import type { ErrorName } from '../codec/errors.ts'; import type { HeldMessagesOptions } from './held-messages.ts'; -import type { LinkLife } from '../link-life.ts'; +import type { LinkLife } from './link-life.ts'; import type { LostGroup, Refusal } from '../messages/reassembly.ts'; import type { OnRequest } from '../options.ts'; import type { PduObject } from '../codec/pdu.ts'; import type { VoidResult } from '../result.ts'; -import type { Session } from '../session.ts'; +import type { Session } from './session.ts'; import type { SmppLog } from '../log.ts'; import type { SmsIdFormat } from '../protocol/message-ids.ts'; import { HeldMessages } from './held-messages.ts'; @@ -15,8 +15,8 @@ import { Reassembler } from '../messages/reassembly.ts'; import { bindCommands, standsInFor } from '../protocol/bind.ts'; import { defaults } from '../options.ts'; import { concatOf } from '../protocol/concat.ts'; -import { detach } from '../codec/retained.ts'; -import { dlrFromPdu } from '../protocol/receipt.ts'; +import { detach } from '../codec/retained-pdu.ts'; +import { dlrFromPdu } from '../protocol/dlr.ts'; import { respIdParams, segmentId } from '../protocol/message-ids.ts'; import { respNameFor } from '../codec/commands.ts'; diff --git a/src/link-life.ts b/src/session/link-life.ts similarity index 98% rename from src/link-life.ts rename to src/session/link-life.ts index f44f2ea..6b42559 100644 --- a/src/link-life.ts +++ b/src/session/link-life.ts @@ -1,5 +1,5 @@ -import type { SmppLog } from './log.ts'; -import type { VoidResult } from './result.ts'; +import type { SmppLog } from '../log.ts'; +import type { VoidResult } from '../result.ts'; export type LinkLifeOptions = { log: SmppLog; diff --git a/src/session/keepalive.ts b/src/session/link-timers.ts similarity index 100% rename from src/session/keepalive.ts rename to src/session/link-timers.ts diff --git a/src/session/outgoing-requests.ts b/src/session/outgoing-requests.ts index 429834e..2dede98 100644 --- a/src/session/outgoing-requests.ts +++ b/src/session/outgoing-requests.ts @@ -1,6 +1,6 @@ -import type { LinkLife } from '../link-life.ts'; +import type { LinkLife } from './link-life.ts'; import type { PduObject, PduObjectInput } from '../codec/pdu.ts'; -import type { PduTransport } from './transport.ts'; +import type { PduTransport } from './pdu-transport.ts'; import type { Result, VoidResult } from '../result.ts'; import type { SendOptions } from '../options.ts'; import type { SmppLog } from '../log.ts'; diff --git a/src/session/transport.ts b/src/session/pdu-transport.ts similarity index 98% rename from src/session/transport.ts rename to src/session/pdu-transport.ts index e453c0c..df1b5de 100644 --- a/src/session/transport.ts +++ b/src/session/pdu-transport.ts @@ -2,7 +2,7 @@ import type { PduObject } from '../codec/pdu.ts'; import type { SmppLog } from '../log.ts'; import type { Socket } from 'node:net'; import type { VoidResult } from '../result.ts'; -import { PduFramer } from '../codec/framer.ts'; +import { PduFramer } from '../codec/pdu-framer.ts'; import { PduRefusedError } from '../codec/refusal.ts'; import { pduToObj } from '../codec/pdu.ts'; diff --git a/src/reconnect-loop.ts b/src/session/reconnect-loop.ts similarity index 96% rename from src/reconnect-loop.ts rename to src/session/reconnect-loop.ts index 52a3c92..19dd79a 100644 --- a/src/reconnect-loop.ts +++ b/src/session/reconnect-loop.ts @@ -1,7 +1,7 @@ -import type { Result, VoidResult } from './result.ts'; -import type { SmppLog } from './log.ts'; +import type { Result, VoidResult } from '../result.ts'; +import type { SmppLog } from '../log.ts'; import type { Socket } from 'node:net'; -import { defaults } from './options.ts'; +import { defaults } from '../options.ts'; export type ReconnectLoopOptions = { connect: () => Promise>; diff --git a/src/session/send-window.ts b/src/session/send-window.ts index 3eabe8f..033a746 100644 --- a/src/session/send-window.ts +++ b/src/session/send-window.ts @@ -1,6 +1,6 @@ import type { SmppLog } from '../log.ts'; import type { VoidResult } from '../result.ts'; -import { IdleWaiters } from './waiting.ts'; +import { IdleWaiters } from './idle-waiters.ts'; export type SendWindowOptions = { limit: number; diff --git a/src/session.ts b/src/session/session.ts similarity index 90% rename from src/session.ts rename to src/session/session.ts index c74e902..326d3dc 100644 --- a/src/session.ts +++ b/src/session/session.ts @@ -1,34 +1,34 @@ -import type { Dlr } from './protocol/receipt.ts'; -import type { ErrorName } from './codec/statuses.ts'; -import type { MessageDlr } from './messages/receipt-merge.ts'; -import type { ParamValue } from './codec/field-types.ts'; -import type { PduObject, PduObjectInput, TlvInputs } from './codec/pdu.ts'; -import type { PduRefusedError } from './codec/refusal.ts'; -import type { BindType, LinkEnd, SessionBind } from './protocol/bind.ts'; -import type { CloseOptions, ReconnectOptions, SendOptions, SessionOptions } from './options.ts'; -import type { Result, VoidResult } from './result.ts'; -import type { SendSmsOptions, SendSmsResult } from './messages/submit.ts'; -import type { SmppLog } from './log.ts'; +import type { Dlr } from '../protocol/dlr.ts'; +import type { ErrorName } from '../codec/errors.ts'; +import type { MessageDlr } from '../messages/dlr-merger.ts'; +import type { ParamValue } from '../codec/types.ts'; +import type { PduObject, PduObjectInput, TlvInputs } from '../codec/pdu.ts'; +import type { PduRefusedError } from '../codec/refusal.ts'; +import type { BindType, LinkEnd, SessionBind } from '../protocol/bind.ts'; +import type { CloseOptions, ReconnectOptions, SendOptions, SessionOptions } from '../options.ts'; +import type { Result, VoidResult } from '../result.ts'; +import type { SendSmsOptions, SendSmsResult } from '../messages/submit.ts'; +import type { SmppLog } from '../log.ts'; import type { Sms } from './sms.ts'; import type { Socket } from 'node:net'; -import { DlrMerger } from './messages/receipt-merge.ts'; +import { DlrMerger } from '../messages/dlr-merger.ts'; import { EventEmitter } from 'node:events'; -import { IncomingRequests } from './session/requests-in.ts'; +import { IncomingRequests } from './incoming-requests.ts'; import { LinkLife } from './link-life.ts'; -import { LinkTimers } from './session/keepalive.ts'; -import { OutgoingRequests } from './session/outgoing-requests.ts'; -import { PduTransport } from './session/transport.ts'; +import { LinkTimers } from './link-timers.ts'; +import { OutgoingRequests } from './outgoing-requests.ts'; +import { PduTransport } from './pdu-transport.ts'; import { ReconnectLoop } from './reconnect-loop.ts'; -import { leftOf } from './session/waiting.ts'; -import { errorFrom } from './result.ts'; -import { optionalParamsMinVersion } from './codec/constants.ts'; -import { bindCarries, checkedBind } from './protocol/bind.ts'; -import { defaults } from './options.ts'; -import { isResp, objToPdu, pduReturn } from './codec/pdu.ts'; -import { refusalAnswer } from './codec/refusal.ts'; -import { guardedLog } from './log.ts'; -import { submitSms, unsent } from './messages/submit.ts'; -import { ConcatReference } from './protocol/udh.ts'; +import { leftOf } from './idle-waiters.ts'; +import { errorFrom } from '../result.ts'; +import { optionalParamsMinVersion } from '../codec/constants.ts'; +import { bindCarries, checkedBind } from '../protocol/bind.ts'; +import { defaults } from '../options.ts'; +import { isResp, objToPdu, pduReturn } from '../codec/pdu.ts'; +import { refusalAnswer } from '../codec/refusal.ts'; +import { guardedLog } from '../log.ts'; +import { submitSms, unsent } from '../messages/submit.ts'; +import { ConcatReference } from '../protocol/udh.ts'; export type { CloseOptions, diff --git a/src/sms.ts b/src/session/sms.ts similarity index 90% rename from src/sms.ts rename to src/session/sms.ts index aadcab6..2270f72 100644 --- a/src/sms.ts +++ b/src/session/sms.ts @@ -1,17 +1,17 @@ -import type { ErrorName } from './codec/statuses.ts'; -import type { MessageState } from './codec/constants.ts'; -import type { PduObject, PduObjectInput, TlvInputs } from './codec/pdu.ts'; -import type { Result, VoidResult } from './result.ts'; +import type { ErrorName } from '../codec/errors.ts'; +import type { MessageState } from '../codec/constants.ts'; +import type { PduObject, PduObjectInput, TlvInputs } from '../codec/pdu.ts'; +import type { Result, VoidResult } from '../result.ts'; import type { Session } from './session.ts'; -import { UnansweredError } from './unanswered-error.ts'; -import { consts } from './codec/constants.ts'; -import { decodeSegments } from './messages/reassembly.ts'; -import { messageClassOf } from './codec/encodings.ts'; -import { paramText } from './codec/field-types.ts'; -import { receiptCodes, transientStates } from './protocol/receipt.ts'; -import { smppDate } from './message.ts'; -import { respIdParams, segmentId } from './protocol/message-ids.ts'; -import { uuidv7 } from './protocol/uuid.ts'; +import { UnansweredError } from '../unanswered-error.ts'; +import { consts } from '../codec/constants.ts'; +import { decodeSegments } from '../messages/reassembly.ts'; +import { messageClassOf } from '../codec/encodings.ts'; +import { paramText } from '../codec/types.ts'; +import { receiptCodes, transientStates } from '../protocol/dlr.ts'; +import { smppDate } from '../message.ts'; +import { respIdParams, segmentId } from '../protocol/message-ids.ts'; +import { uuidv7 } from '../protocol/uuid.ts'; /** `pduObjs` holds what the peer took, so a partial failure names what is already receipted. */ export type SendDlrResult = { diff --git a/test/declared-alphabet.test.ts b/test/declared-alphabet.test.ts index 33bf704..66481aa 100644 --- a/test/declared-alphabet.test.ts +++ b/test/declared-alphabet.test.ts @@ -5,10 +5,10 @@ import { client } from '../src/client/client.ts'; import { closeAfter } from './teardown.ts'; import { consts } from '../src/codec/constants.ts'; import { decodeMessage } from '../src/message.ts'; -import { dlrFromPdu } from '../src/protocol/receipt.ts'; +import { dlrFromPdu } from '../src/protocol/dlr.ts'; import { encodingByDataCoding, encodings } from '../src/codec/encodings.ts'; import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; -import { paramNumber } from '../src/codec/field-types.ts'; +import { paramNumber } from '../src/codec/types.ts'; import { server } from '../src/server/server.ts'; import type { PduObject } from '../src/codec/pdu.ts'; diff --git a/test/dlr.test.ts b/test/dlr.test.ts index 0dfa471..b48540a 100644 --- a/test/dlr.test.ts +++ b/test/dlr.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; import { consts } from '../src/codec/constants.ts'; -import { dlrFromPdu, parseReceipt, receiptCodes } from '../src/protocol/receipt.ts'; +import { dlrFromPdu, parseReceipt, receiptCodes } from '../src/protocol/dlr.ts'; import { encodeMessage } from '../src/message.ts'; import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; import type { PduObject, TlvInputs } from '../src/codec/pdu.ts'; diff --git a/test/dummy-smsc.ts b/test/dummy-smsc.ts index 2a12f82..6f6d9af 100644 --- a/test/dummy-smsc.ts +++ b/test/dummy-smsc.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import net from 'node:net'; -import type { Session } from '../src/session.ts'; +import type { Session } from '../src/session/session.ts'; import type { TestContext } from 'node:test'; -import { PduFramer } from '../src/codec/framer.ts'; +import { PduFramer } from '../src/codec/pdu-framer.ts'; import { client } from '../src/client/client.ts'; import { closeAfter, closeListenerAfter } from './teardown.ts'; import { consts } from '../src/codec/constants.ts'; diff --git a/test/interop.test.ts b/test/interop.test.ts index d2638f8..7e658c2 100644 --- a/test/interop.test.ts +++ b/test/interop.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; import reference from 'smpp'; import type { ReferenceSession } from 'smpp'; -import type { Sms } from '../src/sms.ts'; +import type { Sms } from '../src/session/sms.ts'; import { client } from '../src/client/client.ts'; import { closeAfter } from './teardown.ts'; import { concatInfo } from '../src/protocol/udh.ts'; diff --git a/test/message-class.test.ts b/test/message-class.test.ts index 9d0d4a5..6b3ed7c 100644 --- a/test/message-class.test.ts +++ b/test/message-class.test.ts @@ -2,14 +2,14 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; import type { PduObjectInput } from '../src/codec/pdu.ts'; import type { SendSmsDeps } from '../src/messages/submit.ts'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import type { TestContext } from 'node:test'; import { bindToSmsc, dummySmsc } from './dummy-smsc.ts'; import { client } from '../src/client/client.ts'; import { closeAfter } from './teardown.ts'; import { messageClassOf } from '../src/codec/encodings.ts'; -import { paramNumber } from '../src/codec/field-types.ts'; +import { paramNumber } from '../src/codec/types.ts'; import { pduToObj } from '../src/codec/pdu.ts'; import { server } from '../src/server/server.ts'; import { silentLog } from '../src/log.ts'; diff --git a/test/messaging-mode.test.ts b/test/messaging-mode.test.ts index 4b97e3f..fd0b1d7 100644 --- a/test/messaging-mode.test.ts +++ b/test/messaging-mode.test.ts @@ -2,12 +2,12 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; import type { PduObjectInput } from '../src/codec/pdu.ts'; import type { SendSmsDeps } from '../src/messages/submit.ts'; -import type { Session } from '../src/session.ts'; +import type { Session } from '../src/session/session.ts'; import type { SubmitMessagingMode } from '../src/codec/constants.ts'; import type { TestContext } from 'node:test'; import { bindToSmsc, dummySmsc } from './dummy-smsc.ts'; import { consts, submitMessagingModes } from '../src/codec/constants.ts'; -import { paramNumber } from '../src/codec/field-types.ts'; +import { paramNumber } from '../src/codec/types.ts'; import { pduToObj } from '../src/codec/pdu.ts'; import { silentLog } from '../src/log.ts'; import { submitSms } from '../src/messages/submit.ts'; diff --git a/test/operator-receipts.test.ts b/test/operator-receipts.test.ts index 03d3bb3..c02ae29 100644 --- a/test/operator-receipts.test.ts +++ b/test/operator-receipts.test.ts @@ -1,11 +1,11 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { Dlr, Receipt } from '../src/protocol/receipt.ts'; -import type { MessageDlr } from '../src/session.ts'; +import type { Dlr, Receipt } from '../src/protocol/dlr.ts'; +import type { MessageDlr } from '../src/session/session.ts'; import type { PduObject, TlvInputs } from '../src/codec/pdu.ts'; import { bindToSmsc, dummySmsc } from './dummy-smsc.ts'; import { consts } from '../src/codec/constants.ts'; -import { dlrFromPdu, parseReceipt, receiptCodes, transientStates } from '../src/protocol/receipt.ts'; +import { dlrFromPdu, parseReceipt, receiptCodes, transientStates } from '../src/protocol/dlr.ts'; import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; /** diff --git a/test/pdu-framer.test.ts b/test/pdu-framer.test.ts index 3bafb85..fa4d538 100644 --- a/test/pdu-framer.test.ts +++ b/test/pdu-framer.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import { PduFramer } from '../src/codec/framer.ts'; +import { PduFramer } from '../src/codec/pdu-framer.ts'; import { objToPdu } from '../src/codec/pdu.ts'; function pdu(seqNr: number): Buffer { diff --git a/test/pdu.test.ts b/test/pdu.test.ts index c363efc..73179a2 100644 --- a/test/pdu.test.ts +++ b/test/pdu.test.ts @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; import { PduRefusedError, refusalAnswer } from '../src/codec/refusal.ts'; import { isCommand, isResp, objToPdu, pduReturn, pduToObj } from '../src/codec/pdu.ts'; -import { paramText } from '../src/codec/field-types.ts'; +import { paramText } from '../src/codec/types.ts'; import { isTlvName, tlvsById } from '../src/codec/tlvs.ts'; function encode(...args: Parameters): Buffer { diff --git a/test/readme.test.ts b/test/readme.test.ts index 1cb446b..5853bd9 100644 --- a/test/readme.test.ts +++ b/test/readme.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { Dlr } from '../src/protocol/receipt.ts'; -import type { Session } from '../src/session.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Dlr } from '../src/protocol/dlr.ts'; +import type { Session } from '../src/session/session.ts'; +import type { Sms } from '../src/session/sms.ts'; import type { SmppLog } from '../src/log.ts'; import type { SmppServer } from '../src/server/server.ts'; import type { TestContext } from 'node:test'; diff --git a/test/session-extras.test.ts b/test/session-extras.test.ts index e362ef2..530b373 100644 --- a/test/session-extras.test.ts +++ b/test/session-extras.test.ts @@ -2,28 +2,28 @@ import assert from 'node:assert/strict'; import net from 'node:net'; import test, { describe } from 'node:test'; import type { Collected, LostGroup } from '../src/messages/reassembly.ts'; -import type { Dlr } from '../src/protocol/receipt.ts'; -import type { ErrorName } from '../src/codec/statuses.ts'; -import type { IncomingRequestsOptions } from '../src/session/requests-in.ts'; +import type { Dlr } from '../src/protocol/dlr.ts'; +import type { ErrorName } from '../src/codec/errors.ts'; +import type { IncomingRequestsOptions } from '../src/session/incoming-requests.ts'; import type { HeldMessagesOptions, MessageHold } from '../src/session/held-messages.ts'; import type { MessageState } from '../src/codec/constants.ts'; -import type { MessageDlr } from '../src/session.ts'; +import type { MessageDlr } from '../src/session/session.ts'; import type { PduObject, PduObjectInput } from '../src/codec/pdu.ts'; import type { Result } from '../src/result.ts'; import type { SendSmsResult } from '../src/messages/submit.ts'; import type { SmppLog } from '../src/log.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Sms } from '../src/session/sms.ts'; import type { SmppServer } from '../src/server/server.ts'; import type { TestContext } from 'node:test'; import { HeldMessages } from '../src/session/held-messages.ts'; -import { IncomingRequests, refusedSegmentStatus } from '../src/session/requests-in.ts'; +import { IncomingRequests, refusedSegmentStatus } from '../src/session/incoming-requests.ts'; import { UnansweredError } from '../src/unanswered-error.ts'; -import { createSms } from '../src/sms.ts'; -import { LinkLife } from '../src/link-life.ts'; +import { createSms } from '../src/session/sms.ts'; +import { LinkLife } from '../src/session/link-life.ts'; import { SendWindow } from '../src/session/send-window.ts'; import { Reassembler, decodeSegments } from '../src/messages/reassembly.ts'; -import { Session } from '../src/session.ts'; -import { DlrMerger } from '../src/messages/receipt-merge.ts'; +import { Session } from '../src/session/session.ts'; +import { DlrMerger } from '../src/messages/dlr-merger.ts'; import { PduRefusedError } from '../src/codec/refusal.ts'; import { objToPdu } from '../src/codec/pdu.ts'; import { checkSessionOptions, defaults } from '../src/options.ts'; @@ -32,8 +32,8 @@ import { client } from '../src/client/client.ts'; import { closeAfter, closeListenerAfter } from './teardown.ts'; import { concatOf } from '../src/protocol/concat.ts'; import { consts } from '../src/codec/constants.ts'; -import { errors } from '../src/codec/statuses.ts'; -import { paramNumber, paramText } from '../src/codec/field-types.ts'; +import { errors } from '../src/codec/errors.ts'; +import { paramNumber, paramText } from '../src/codec/types.ts'; import { server } from '../src/server/server.ts'; import { silentLog } from '../src/log.ts'; import { splitMessage } from '../src/message.ts'; diff --git a/test/session.test.ts b/test/session.test.ts index ee84322..30dccff 100644 --- a/test/session.test.ts +++ b/test/session.test.ts @@ -1,17 +1,17 @@ import assert from 'node:assert/strict'; import net from 'node:net'; import test, { describe } from 'node:test'; -import type { Dlr } from '../src/protocol/receipt.ts'; +import type { Dlr } from '../src/protocol/dlr.ts'; import type { PduObject, PduObjectInput } from '../src/codec/pdu.ts'; -import type { Sms } from '../src/sms.ts'; +import type { Sms } from '../src/session/sms.ts'; import type { ServerOptions, SmppServer } from '../src/server/server.ts'; import type { SmppLog } from '../src/log.ts'; import type { TestContext } from 'node:test'; import type { VoidResult } from '../src/result.ts'; -import { DlrMerger } from '../src/messages/receipt-merge.ts'; -import { PduFramer } from '../src/codec/framer.ts'; -import { ReconnectLoop } from '../src/reconnect-loop.ts'; -import { Session } from '../src/session.ts'; +import { DlrMerger } from '../src/messages/dlr-merger.ts'; +import { PduFramer } from '../src/codec/pdu-framer.ts'; +import { ReconnectLoop } from '../src/session/reconnect-loop.ts'; +import { Session } from '../src/session/session.ts'; import { bindCommands } from '../src/protocol/bind.ts'; import { checkSessionOptions } from '../src/options.ts'; import { client } from '../src/client/client.ts'; @@ -19,7 +19,7 @@ import { closeAfter, closeListenerAfter } from './teardown.ts'; import { consts } from '../src/codec/constants.ts'; import { PduRefusedError } from '../src/codec/refusal.ts'; import { isCommand, objToPdu, pduReturn, pduToObj } from '../src/codec/pdu.ts'; -import { paramText } from '../src/codec/field-types.ts'; +import { paramText } from '../src/codec/types.ts'; import { server } from '../src/server/server.ts'; import { bareTlvHeader, pduBytes, shortened, truncatedTlv, withUnknownCmdId } from './raw-pdus.ts'; import { silentLog } from '../src/log.ts'; diff --git a/test/tls.test.ts b/test/tls.test.ts index 056f93b..c65ea4b 100644 --- a/test/tls.test.ts +++ b/test/tls.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import net from 'node:net'; import test, { describe } from 'node:test'; -import type { Sms } from '../src/sms.ts'; +import type { Sms } from '../src/session/sms.ts'; import type { SmppServer } from '../src/server/server.ts'; import type { TestContext } from 'node:test'; import { Log } from '@larvit/log'; diff --git a/test/types.test.ts b/test/types.test.ts index aec5399..b8c329a 100644 --- a/test/types.test.ts +++ b/test/types.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import test, { describe } from 'node:test'; -import type { DestAddress, UnsuccessSme } from '../src/codec/field-types.ts'; -import { paramText, types } from '../src/codec/field-types.ts'; +import type { DestAddress, UnsuccessSme } from '../src/codec/types.ts'; +import { paramText, types } from '../src/codec/types.ts'; import { tlvs } from '../src/codec/tlvs.ts'; describe('integers', () => { diff --git a/test/unsendable.test.ts b/test/unsendable.test.ts index d7cbb67..a9bacc2 100644 --- a/test/unsendable.test.ts +++ b/test/unsendable.test.ts @@ -6,7 +6,7 @@ import { bindToSmsc, dummySmsc } from './dummy-smsc.ts'; import { decodeMessage } from '../src/message.ts'; import { messageOctets } from '../src/protocol/message-body.ts'; import { objToPdu, pduToObj } from '../src/codec/pdu.ts'; -import { paramNumber, paramText } from '../src/codec/field-types.ts'; +import { paramNumber, paramText } from '../src/codec/types.ts'; import { silentLog } from '../src/log.ts'; import { submitSms } from '../src/messages/submit.ts'; diff --git a/todo.md b/todo.md index f73b491..58deb96 100644 --- a/todo.md +++ b/todo.md @@ -230,6 +230,8 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture reference counter goes to `SmppClient` in the lifecycle split. - The `gsm7` rename, A6 included: `'ASCII'` becomes `'GSM7'` in every export. - A test that fails on a spec citation without its sentence. + - Settle where `encodeBody()` and `decodeMessage()` land before splitting `message.ts`: `codec/pdu.ts` + reads them, so a home in `protocol/` is an upward edge. - [ ] **Build `messages/` on a `BoundedStore` that enforces its own bounds.** Reassembly refuses at its bound instead of evicting, recorded against goals 2 and 4; the receipt merge follows Q8 and its spent set expires by age. README's bound text is updated. @@ -250,6 +252,17 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture ### Correctness +- [ ] **Bound a rebind's wait when `responseTimeout` is 0.** `comeBackUp()` arms no timers until the + bind is answered, so an SMSC that takes the TCP connection and never answers leaves + `ReconnectLoop` attempting forever: no `close`, no next attempt. `answering()` already falls + back to the default for held messages; the bind has no such floor. From the 2026-09-30 scoring + run; confirm with a test first. + +- [ ] **Type a parsed `short_message` as what the parser puts there.** `parsePdu()` replaces it with + a decoded string wherever no UDH is present, while `PduParams<'submit_sm'>['short_message']` + is `Buffer` and `isCommand()`'s docstring calls the narrowing sound. Public type; from the + 2026-09-30 scoring run. + - [ ] **Refuse to open a link that dropped while its rebind was answered.** A peer sending `bind_resp` and FIN together can tear the link down before `comeBackUp()` resumes; it then calls `link.open()` on a `down` link, `resetTimers()` skips, and `attempt()` reports success, @@ -336,7 +349,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture ### Shape — 6 today, and the gate is 7 - [ ] **Answer "is this a bind command" in one place.** `bindCommands` (read by - `session/requests-in.ts`, `session/outgoing-requests.ts` and `test/session.test.ts`) and + `session/incoming-requests.ts`, `session/outgoing-requests.ts` and `test/session.test.ts`) and `bindTypeFromCommand()` (read by `server.ts` and `checkedBind()`) each list the three bind commands, so a fourth added to one is missed by the other. Derive the list from the function, or the reverse. From the stability review of #42. @@ -442,7 +455,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture - [ ] **Narrow the `src/codec/` table lint exemption to the four table files.** Its stated reason — "the spec tables are data: their length tracks the specification, not any complexity" — is false for - `codec/field-types.ts`, which is 595 lines of wire codec with 25 functions and is the file that parses + `codec/types.ts`, which is 595 lines of wire codec with 25 functions and is the file that parses hostile input from the network. It carries more over-budget methods than any other file in the repo, under a suppression written for something else. @@ -500,7 +513,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture `idleTimeout: '5000'` is refused with `got 5000` — a value the reader reads as correct — where `connectTimeout` quotes it. `namedValue()`'s four sites — `messagingMode`, `encoding`, the time options and `smsIdFormat` — are the same defect once more: there `true` and `'true'` both print - as `true`. One fix closes all three, and `valueText()` in `codec/field-types.ts` is the quoted + as `true`. One fix closes all three, and `valueText()` in `codec/types.ts` is the quoted spelling to take it from. Raised by review, 2026-09-20. - [ ] **Refuse a send the codec cannot build before it waits for a link and a window slot.** Today @@ -537,7 +550,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture Maintainer's ask, 2026-09-14; not started until asked. - [ ] **Count what is left of a budget one way in `leftOf()` and `LinkLife`.** Today they are one - concept counted twice. `session/waiting.ts` reads what is left of a budget as `Math.max(1, + concept counted twice. `session/idle-waiters.ts` reads what is left of a budget as `Math.max(1, deadline - now)`, because 0 means "forever" there; `link-life.ts` runs the same subtraction and calls `<= 0` expired. Neither is reachable from the other, so nothing can disagree today, but a reader who learns one and applies it to the other is wrong. A budget type both take -- 2.52.0 From be0f2c33c9ed1608a771cf25312dc48a6822174c Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 30 Sep 2026 20:35:46 +0200 Subject: [PATCH 4/5] Amend plan 3: a file keeps its export's name until the export is renamed --- docs/comprehension-rewrite/plan-3.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/comprehension-rewrite/plan-3.md b/docs/comprehension-rewrite/plan-3.md index 756f168..7799f35 100644 --- a/docs/comprehension-rewrite/plan-3.md +++ b/docs/comprehension-rewrite/plan-3.md @@ -260,6 +260,9 @@ overturned recorded decisions without naming them. todo.md carries the reordered Reassembly refuses at its bound: an evicted group is answered segments lost, goal 2 outranks goal 4, and the store is per session. The spent set expires by age. 8. **`retained.ts` goes to `codec/`**; `options.ts` joins AGENTS.md's type-only ways back up. +9. **A file keeps its export's name until the chunk that renames the export.** The scoring run of + 2026-09-30 read §2's names on unrenamed classes (`keepalive.ts` holding `LinkTimers`) as lies, so + §2 and §4 name where a file ends, not what it is called before its export changes. Public API questions, answered as the review recommends. Maintainer's call, 2026-09-30; each lands in docs/decisions.md with the chunk that builds it: -- 2.52.0 From e1c2c2fb76c54b69a92915dc7707b4b210ee8a5b Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 30 Sep 2026 20:38:09 +0200 Subject: [PATCH 5/5] File the scoring run's findings and record its scores --- todo.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/todo.md b/todo.md index 58deb96..4e3927e 100644 --- a/todo.md +++ b/todo.md @@ -207,6 +207,10 @@ not SMPP's difficulty. Maintainer's call, 2026-09-30: build plan 3. The backgrou [docs/comprehension-rewrite/](docs/comprehension-rewrite/): the plans, the board, every panel report, the lessons and the six drafts as patches. Delete that directory once this milestone ships. +The move chunk (#53) read 6, 6, 6 and 6, Navigation 7 at every seat, Locality 5, 5, 5 and 6, Shape 6 +at every seat, Self-sufficiency 5, 6, 7 and 8. Every seat's Locality cause is the lifecycle cluster +the split below rewrites. + Every chunk below ships through `/larv-review`, and before it merges it also runs the comprehension-panel scoring run over the whole project. Each round: @@ -348,6 +352,19 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture ### Shape — 6 today, and the gate is 7 +- [ ] **Give each meaning of "refusal" its own name.** `PduRefusedError`/`codec/refusal.ts` (a PDU + the codec would not read), reassembly's `Refusal` (a segment not kept), `LinkLife.refusal()` + (the session is closed), `OutgoingRequests.refuse()` (bad input) and `Session.refuse()` + (answering an unreadable PDU) are five things under one word. From the 2026-09-30 scoring run. + +- [ ] **Collect a batch of sent PDUs in one function.** `collectReceipt()` in `session/sms.ts` and + `collectSent()` in `messages/submit.ts` are the same loop over `Result<{ pduObj }>[]`, counting + `UnansweredError`s and keeping the first failure. From the 2026-09-30 scoring run. + +- [ ] **Say at `readParams()` that only `short_message` reads the length it is handed.** Every field's + `read` gets `sm_length`, and that is sound only because `sm_length` precedes `short_message` in + wire order and `buffer` is the one type that uses it. From the 2026-09-30 scoring run. + - [ ] **Answer "is this a bind command" in one place.** `bindCommands` (read by `session/incoming-requests.ts`, `session/outgoing-requests.ts` and `test/session.test.ts`) and `bindTypeFromCommand()` (read by `server.ts` and `checkedBind()`) each list the three bind -- 2.52.0