Move src into codec, protocol, messages and session layers #53
@@ -37,55 +37,59 @@ These are not preferences. Breaking one is a defect.
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
index.ts Public surface. Named exports only, no default export.
|
index.ts Public surface. Named exports only, no default export; assembles `defs`.
|
||||||
client.ts client() -> { err, session }
|
log.ts SmppLog, silentLog — the default, and guardedLog(): a logger that cannot throw
|
||||||
server.ts server() -> { err, server }, server owns the listener + close()
|
message.ts Message bodies: encodeBody/decodeMessage under a data_coding, splitting, bit counting, smppDate/smppTime
|
||||||
session.ts Session: the socket's life, dispatch, events, and the collaborators below
|
options.ts SessionOptions, ReconnectOptions, their checks, and `defaults`: every default and internal cap
|
||||||
sms.ts The live handle emitted as the 'sms' event (sendResp/sendDlr)
|
result.ts Result<T>, and an untyped value as error material: errorFrom(), namedValue(), quoted()
|
||||||
concat.ts How a PDU says it is a segment: its UDH, or the sar_* TLVs
|
unanswered-error.ts
|
||||||
dlr.ts Delivery receipts: text and TLV parsing, receipt status codes
|
codec/ Bytes <-> PduObject
|
||||||
dlr-merger.ts DlrMerger: per-segment receipts counted into one MessageDlr
|
commands.ts The 33 commands, their ids and ordered parameter lists
|
||||||
error-from.ts An untyped value as error material: errorFrom() an Error, namedValue() a name
|
constants.ts consts + constsById, optionalParamsMinVersion, and esm_class's readers: hasUdh(), messageTypeOf()
|
||||||
expiring-groups.ts ExpiringGroups: the capped, weighed, expiring store DlrMerger, HeldMessages and Reassembler share
|
encodings.ts GSM 03.38, LATIN1, UCS2, detection, data_coding resolution, message class
|
||||||
held-messages.ts HeldMessages: a message from its `sms` event to its answer, capped and expiring, one MessageHold each
|
errors.ts errors + errorsById (ESME_*)
|
||||||
idle-waiters.ts IdleWaiters: waiting for a count to fall to zero, and what is left of a budget
|
pdu-framer.ts PduFramer
|
||||||
incoming-requests.ts Every request the peer sends: messages, receipts, links, unknown commands
|
pdu.ts pduToObj / objToPdu / pduReturn — synchronous, result-returning
|
||||||
link-life.ts LinkLife: whether the link lives, and where a request waits for the next one
|
refusal.ts A PDU the codec would not read, and the answer SMPP names for it
|
||||||
link-timers.ts LinkTimers: the enquire_link heartbeat and the idle timeout
|
retained-pdu.ts A PDU copied off the wire so holding it pins nothing else, and what holding it costs
|
||||||
log.ts SmppLog, the logger contract, and silentLog — the default
|
tlvs.ts TLV definitions, tlvsById, the typed read and input shapes, and reading and writing a TLV stream
|
||||||
message.ts Encoding detection, splitting, bit counting, SMPP date formatting
|
types.ts Wire types: int8/int16/int32/string/cstring/buffer/arrays
|
||||||
message-body.ts Where an inbound body is: short_message, or the message_payload TLV
|
protocol/ What the fields mean
|
||||||
outgoing-requests.ts OutgoingRequests: the window, the pending map and the retry
|
bind.ts Bind directions: which commands bind, what a direction carries, data_sm's stand-in, checkedBind()
|
||||||
pdu.ts pduToObj / objToPdu / pduReturn — synchronous, result-returning
|
concat.ts How a PDU says it is a segment: its UDH, or the sar_* TLVs
|
||||||
pdu-framer.ts PduFramer: a byte stream cut into complete PDUs
|
dlr.ts Delivery receipts: text and TLV parsing, receipt status codes
|
||||||
pdu-refusal.ts A PDU the codec would not read, and the answer SMPP names for it
|
message-body.ts Where an inbound body is: short_message, or the message_payload TLV
|
||||||
pdu-transport.ts PduTransport: the socket a session reads complete PDUs off
|
message-ids.ts Message ids: the peer's notation, the <base>-<n> a segment gets, which response carries one
|
||||||
pending-requests.ts PendingRequests: sequence numbers, correlation, timeout, abort
|
udh.ts User data header: its length, the concatenation fields of a long SMS and their reference
|
||||||
reassembly.ts Reassembler: capped, expiring multipart groups
|
uuid.ts uuidv7() — the ids the library generates for messages
|
||||||
reconnect-loop.ts ReconnectLoop: backoff, retry timer, stopped-ness
|
messages/ Whole messages across segments and time
|
||||||
result.ts Result<T> — the shape every fallible call returns
|
dlr-merger.ts DlrMerger: per-segment receipts counted into one MessageDlr
|
||||||
retained-pdu.ts A PDU copied off the wire so holding it pins nothing else, and what holding it costs
|
expiring-groups.ts ExpiringGroups: the capped, weighed, expiring store DlrMerger, HeldMessages and Reassembler share
|
||||||
send-sms.ts submitSms composition and the submitSmParams builder
|
reassembly.ts Reassembler: capped, expiring multipart groups
|
||||||
send-window.ts SendWindow: the maxOutstanding semaphore
|
submit.ts submitSms composition and the submitSmParams builder
|
||||||
session-options.ts SessionOptions, ReconnectOptions, bind direction and the session defaults
|
session/ One socket's life, and reconnecting it
|
||||||
sms-id.ts Message ids: the peer's notation, the <base>-<n> a segment gets, which response carries one
|
held-messages.ts HeldMessages: a message from its `sms` event to its answer, capped and expiring, one MessageHold each
|
||||||
udh.ts User data header: its length, the concatenation fields of a long SMS and their reference
|
idle-waiters.ts IdleWaiters: waiting for a count to fall to zero, and what is left of a budget
|
||||||
unanswered-error.ts UnansweredError: it went out and no answer came back
|
incoming-requests.ts IncomingRequests: every request the peer sends — messages, receipts, links, unknown commands
|
||||||
uuid.ts uuidv7() — the ids the library generates for messages
|
link-life.ts LinkLife: whether the link lives, and where a request waits for the next one
|
||||||
defs/
|
link-timers.ts LinkTimers: the enquire_link heartbeat and the idle timeout
|
||||||
commands.ts The 33 commands, their ids and ordered parameter lists
|
outgoing-requests.ts OutgoingRequests: the window, the pending map and the retry
|
||||||
constants.ts consts + constsById, and the SMPP version constants
|
pdu-transport.ts PduTransport: the socket a session reads complete PDUs off
|
||||||
encodings.ts GSM 03.38, LATIN1, UCS2, detection, data_coding resolution
|
pending-requests.ts PendingRequests: sequence numbers, correlation, timeout, abort
|
||||||
errors.ts errors + errorsById (ESME_*)
|
reconnect-loop.ts ReconnectLoop: backoff, retry timer, stopped-ness
|
||||||
index.ts defs: every table as one group
|
send-window.ts SendWindow: the maxOutstanding semaphore
|
||||||
tlvs.ts TLV definitions, tlvsById, the typed read and input shapes, and reading and writing a TLV stream
|
session.ts Session: the socket's life, dispatch and events, composing the rest of session/
|
||||||
types.ts Wire types: int8/int16/int32/string/cstring/buffer/arrays
|
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: `defs` knows nothing above it but `result.ts`, `pdu` uses `defs`, `session`
|
Imports point one way: `codec` ← `protocol` ← `messages` ← `session` ← `client`/`server`. At the
|
||||||
uses `pdu`, and `client`/`server` use `session`. The ways back up are the `Session` handed to
|
root, `result.ts`, `log.ts` and `unanswered-error.ts` sit below `codec`, and `message.ts` and
|
||||||
`createSms()`, `HeldMessages` and `IncomingRequests`, which call back into it, and to `OnRequest`
|
`options.ts` with `protocol`. One edge runs up: `codec/pdu.ts` imports `encodeBody()` and
|
||||||
and `onConnected` in `session-options.ts`, all imported as a type only.
|
`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
|
**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
|
written to and read from the buffer. Never sort those alphabetically — the alphabetical-ordering
|
||||||
@@ -314,8 +318,8 @@ this is not a changelog.
|
|||||||
`SendWindow` rather than extracted.
|
`SendWindow` rather than extracted.
|
||||||
- `SmppLog` is a five-method contract this library declares, not a dependency.
|
- `SmppLog` is a five-method contract this library declares, not a dependency.
|
||||||
- The TLS tests build their own self-signed certificate in DER
|
- The TLS tests build their own self-signed certificate in DER
|
||||||
- `src/` stays flat until a module has to move for another reason.
|
- `src/` is grouped by layer, and imports point down the layers.
|
||||||
- `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.
|
module it covers.
|
||||||
- CI tests on Linux only; `src/` keeps off what is known to break on macOS or Windows.
|
- 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
|
- GitHub mirrors Gitea without pruning, and a ref deleted on Gitea is deleted on GitHub by a run of
|
||||||
|
|||||||
@@ -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
|
* Answers every submit_sm ESME_ROK and does nothing else, so a measurement against it reads this
|
||||||
|
|||||||
@@ -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
|
* Pushes `count` single-segment messages and reports what the wire carried per second. Keeps
|
||||||
|
|||||||
@@ -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,
|
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.
|
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.
|
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
|
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:
|
lands in docs/decisions.md with the chunk that builds it:
|
||||||
|
|||||||
+8
-8
@@ -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
|
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
|
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
|
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/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
|
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
|
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
|
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
|
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
|
([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
|
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 `<base>-<n>`, the notation `sms-id.ts` owns
|
generated when it opens and each segment is answered `<base>-<n>`, 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`
|
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
|
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
|
no-op. `answeredOnArrival` is on `Sms` because nothing the application can compute says it, and the
|
||||||
@@ -795,13 +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
|
fail on every developer machine, and a committed key leaks in a public repository. Valid while the
|
||||||
dev image has no openssl.
|
dev image has no openssl.
|
||||||
|
|
||||||
- **`src/` stays flat until a module has to move for another reason.** Architecture review,
|
- **`src/` is grouped by layer, and imports point down the layers.** Maintainer's call, 2026-09-30,
|
||||||
2026-09-06: the grouping the [file map](../AGENTS.md#architecture) already implies — `wire/` for `pdu*` and `defs`,
|
with the Locality rewrite; the [map](../AGENTS.md#architecture) names the order and places each
|
||||||
`link/` for `link-*`, `reconnect-*`, `pdu-transport` and `send-window`, `messages/` for `sms*`,
|
root file in a layer. Serves goal 8: internals are reshapeable only once a reader can find them,
|
||||||
`dlr*`, `message*`, `reassembly` and `udh` — rewrites every import for no change to
|
and every comprehension panel navigated by the map. Rejected: `src/` flat until a module has to
|
||||||
`dist/index.js`, the one published entry. Valid while that map is what a reader navigates by.
|
move for another reason. Valid while the map is what readers navigate 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
|
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
|
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
|
it came from, cutting across four modules, where filing it by module would enter each new operator
|
||||||
|
|||||||
+3
-3
@@ -35,17 +35,17 @@ export default tseslint.config(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// The spec tables are data: their length tracks the specification, not any complexity.
|
// The spec tables are data: their length tracks the specification, not any complexity.
|
||||||
files: ['src/defs/*.ts'],
|
files: ['src/codec/{commands,constants,encodings,errors,tlvs,types}.ts'],
|
||||||
rules: { 'max-lines': 'off' },
|
rules: { 'max-lines': 'off' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// ESLint counts every ?. and ?? in dlrFromPdu as a branch; the 19 is 26 lines of flat field resolution.
|
// 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/dlr.ts'],
|
||||||
rules: { complexity: ['error', 19] },
|
rules: { complexity: ['error', 19] },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// ESC (0x1B) is the GSM 03.38 escape character, so it belongs in these patterns.
|
// 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' },
|
rules: { 'no-control-regex': 'off' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import test, { after, describe } from 'node:test';
|
import test, { after, describe } from 'node:test';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import type { SmppServer } from '../src/server.ts';
|
import type { SmppServer } from '../src/server/server.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
|
|
||||||
const CLOUDHOPPER_HOST = process.env.CLOUDHOPPER_HOST ?? 'cloudhopper:8080';
|
const CLOUDHOPPER_HOST = process.env.CLOUDHOPPER_HOST ?? 'cloudhopper:8080';
|
||||||
const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775');
|
const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775');
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ services:
|
|||||||
dumbclient-netns:
|
dumbclient-netns:
|
||||||
condition: service_started
|
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.
|
# should ever be throttled - see findings/07-load.md.
|
||||||
dumbclient-w500:
|
dumbclient-w500:
|
||||||
build: ./interop-tests/peers/dumbclient
|
build: ./interop-tests/peers/dumbclient
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { after, describe } from 'node:test';
|
import test, { after, describe } from 'node:test';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import type { LogMethod, SmppLog } from '../src/log.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');
|
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
|
/** 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
|
// 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
|
// 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
|
// 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.
|
// 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';
|
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');
|
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
|
// 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/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
|
// 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
|
// 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.
|
// even started is still seen; budget is slack for a session that is still open, not a clock.
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import test, { after, describe } from 'node:test';
|
import test, { after, describe } from 'node:test';
|
||||||
import type { Dlr } from '../src/dlr.ts';
|
import type { Dlr } from '../src/protocol/dlr.ts';
|
||||||
import type { EncodingName } from '../src/defs/encodings.ts';
|
import type { EncodingName } from '../src/codec/encodings.ts';
|
||||||
import type { PduObject } from '../src/pdu.ts';
|
import type { PduObject } from '../src/codec/pdu.ts';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import { ConcatReference } from '../src/udh.ts';
|
import { ConcatReference } from '../src/protocol/udh.ts';
|
||||||
import { client } from '../src/client.ts';
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter } from '../test/teardown.ts';
|
import { closeAfter } from '../test/teardown.ts';
|
||||||
import { paramText } from '../src/defs/types.ts';
|
import { paramText } from '../src/codec/types.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
import { encodeMessage, splitMessage } from '../src/message.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_HOST = process.env.PEER_HOST ?? 'jasmin';
|
||||||
const PEER_PORT = Number(process.env.PEER_PORT ?? '2775');
|
const PEER_PORT = Number(process.env.PEER_PORT ?? '2775');
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import test, { after, describe } from 'node:test';
|
import test, { after, describe } from 'node:test';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import { PduRefusedError } from '../src/index.ts';
|
import { PduRefusedError } from '../src/index.ts';
|
||||||
import { bareTlvHeader, pduBytes } from '../test/raw-pdus.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 JSMPP_HOST = process.env.JSMPP_HOST ?? 'jsmpp:8080';
|
||||||
const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775');
|
const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775');
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import test, { after, describe } from 'node:test';
|
import test, { after, describe } from 'node:test';
|
||||||
import type { MessageState } from '../src/defs/constants.ts';
|
import type { MessageState } from '../src/codec/constants.ts';
|
||||||
import type { Dlr } from '../src/dlr.ts';
|
import type { Dlr } from '../src/protocol/dlr.ts';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import { ConcatReference } from '../src/udh.ts';
|
import { ConcatReference } from '../src/protocol/udh.ts';
|
||||||
import { consts } from '../src/defs/constants.ts';
|
import { consts } from '../src/codec/constants.ts';
|
||||||
import { detect, encodings } from '../src/defs/encodings.ts';
|
import { detect, encodings } from '../src/codec/encodings.ts';
|
||||||
import { paramText } from '../src/defs/types.ts';
|
import { paramText } from '../src/codec/types.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
import { splitMessage } from '../src/message.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.
|
// 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';
|
const MAIN_SMSBOX = process.env.MAIN_SMSBOX ?? 'kannel-smsbox:13013';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { after, describe } from 'node:test';
|
import test, { after, describe } from 'node:test';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import { paramText } from '../src/defs/types.ts';
|
import { paramText } from '../src/codec/types.ts';
|
||||||
import { isCommand, server } from '../src/index.ts';
|
import { isCommand, server } from '../src/index.ts';
|
||||||
|
|
||||||
const DRIVER = process.env.PHP_DRIVER ?? 'php:8080';
|
const DRIVER = process.env.PHP_DRIVER ?? 'php:8080';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { after, describe } from 'node:test';
|
import test, { after, describe } from 'node:test';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import { encodings } from '../src/defs/encodings.ts';
|
import { encodings } from '../src/codec/encodings.ts';
|
||||||
import { paramText } from '../src/defs/types.ts';
|
import { paramText } from '../src/codec/types.ts';
|
||||||
import { isCommand, server } from '../src/index.ts';
|
import { isCommand, server } from '../src/index.ts';
|
||||||
|
|
||||||
const DRIVER = process.env.PYTHON_DRIVER ?? 'python:8080';
|
const DRIVER = process.env.PYTHON_DRIVER ?? 'python:8080';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { after, describe } from 'node:test';
|
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.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
|
|
||||||
const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775');
|
const SMPP_PORT = Number(process.env.SMPP_PORT ?? '2775');
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ describe('smppload (blocked)', () => {
|
|||||||
const refusal = await waitFor(() => sessionErr, 10_000);
|
const refusal = await waitFor(() => sessionErr, 10_000);
|
||||||
|
|
||||||
assert.ok(refusal);
|
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
|
// 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...".
|
// "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+/);
|
assert.match(refusal.message, /Refusing a cmd_length of \d+/);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { Dlr } from '../src/dlr.ts';
|
import type { Dlr } from '../src/protocol/dlr.ts';
|
||||||
import type { EncodingName } from '../src/defs/encodings.ts';
|
import type { EncodingName } from '../src/codec/encodings.ts';
|
||||||
import type { MessageDlr } from '../src/dlr-merger.ts';
|
import type { MessageDlr } from '../src/messages/dlr-merger.ts';
|
||||||
import type { PduObject } from '../src/pdu.ts';
|
import type { PduObject } from '../src/codec/pdu.ts';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import { client } from '../src/client.ts';
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter } from '../test/teardown.ts';
|
import { closeAfter } from '../test/teardown.ts';
|
||||||
import { consts } from '../src/defs/constants.ts';
|
import { consts } from '../src/codec/constants.ts';
|
||||||
import { paramText } from '../src/defs/types.ts';
|
import { paramText } from '../src/codec/types.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
|
|
||||||
const PEER_HOST = process.env.PEER_HOST ?? 'smppsim';
|
const PEER_HOST = process.env.PEER_HOST ?? 'smppsim';
|
||||||
const PEER_PORT = Number(process.env.PEER_PORT ?? '2775');
|
const PEER_PORT = Number(process.env.PEER_PORT ?? '2775');
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { Dlr } from '../src/dlr.ts';
|
import type { Dlr } from '../src/protocol/dlr.ts';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import { client } from '../src/client.ts';
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter } from '../test/teardown.ts';
|
import { closeAfter } from '../test/teardown.ts';
|
||||||
|
|
||||||
const PEER_HOST = process.env.PEER_HOST ?? 'smscsim';
|
const PEER_HOST = process.env.PEER_HOST ?? 'smscsim';
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import type { ConnectionOptions } from 'node:tls';
|
import type { ConnectionOptions } from 'node:tls';
|
||||||
import type { Result, VoidResult } from './result.ts';
|
import type { Result, VoidResult } from '../result.ts';
|
||||||
import type { BindType, ReconnectOptions } from './session-options.ts';
|
import type { BindType } from '../protocol/bind.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { ReconnectOptions } from '../options.ts';
|
||||||
import type { SmsIdFormat } from './sms-id.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
|
import type { SmsIdFormat } from '../protocol/message-ids.ts';
|
||||||
import type { Socket } from 'node:net';
|
import type { Socket } from 'node:net';
|
||||||
export type { BindType };
|
export type { BindType };
|
||||||
|
|
||||||
import { ReconnectLoop } from './reconnect-loop.ts';
|
import { ReconnectLoop } from '../session/reconnect-loop.ts';
|
||||||
import { Session } from './session.ts';
|
import { Session } from '../session/session.ts';
|
||||||
import { checkSessionOptions } from './session-options.ts';
|
import { checkSessionOptions, defaults } from '../options.ts';
|
||||||
import { connect as netConnect } from 'node:net';
|
import { connect as netConnect } from 'node:net';
|
||||||
import { connect as tlsConnect } from 'node:tls';
|
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. */
|
/** `fromStart` puts the very first connect and bind through the same backoff loop as a drop. */
|
||||||
type ReconnectTuning = { fromStart?: boolean; maxDelay?: number; minDelay?: number };
|
type ReconnectTuning = { fromStart?: boolean; maxDelay?: number; minDelay?: number };
|
||||||
@@ -41,19 +41,6 @@ export type ClientOptions = {
|
|||||||
username?: string;
|
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(
|
function armConnectTimeout(
|
||||||
sock: Socket,
|
sock: Socket,
|
||||||
connectTimeout: number | false,
|
connectTimeout: number | false,
|
||||||
@@ -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. */
|
/** Spec rule, not a preference: a peer declaring less than 3.4 is sent no optional parameters. */
|
||||||
export const optionalParamsMinVersion = 0x34;
|
export const optionalParamsMinVersion = 0x34;
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Result } from './result.ts';
|
import type { Result } from '../result.ts';
|
||||||
import { framingRefusal } from './pdu-refusal.ts';
|
import { framingRefusal } from './refusal.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cuts a byte stream into whole PDUs.
|
* Cuts a byte stream into whole PDUs.
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import type { CommandDefinition, CommandName, PduParams, PduParamsInput } from './defs/commands.ts';
|
import type { CommandDefinition, CommandName, PduParams, PduParamsInput } from './commands.ts';
|
||||||
import type { ErrorName } from './defs/errors.ts';
|
import type { ErrorName } from './errors.ts';
|
||||||
import type { ParamValue } from './defs/types.ts';
|
import type { ParamValue } from './types.ts';
|
||||||
import type { PduHeader } from './pdu-refusal.ts';
|
import type { PduHeader } from './refusal.ts';
|
||||||
import type { Result, VoidResult } from './result.ts';
|
import type { Result, VoidResult } from '../result.ts';
|
||||||
import type { TlvInputs, Tlvs } from './defs/tlvs.ts';
|
import type { TlvInputs, Tlvs } from './tlvs.ts';
|
||||||
import { PduRefusedError, framingRefusal } from './pdu-refusal.ts';
|
import { PduRefusedError, framingRefusal } from './refusal.ts';
|
||||||
import { cmds, commandNameById, respNameFor } from './defs/commands.ts';
|
import { cmds, commandNameById, respNameFor } from './commands.ts';
|
||||||
import { hasUdh } from './defs/constants.ts';
|
import { hasUdh } from './constants.ts';
|
||||||
import { decodeMessage, encodeBody } from './message.ts';
|
import { decodeMessage, encodeBody } from '../message.ts';
|
||||||
import { errorNameById, errors, isErrorName } from './defs/errors.ts';
|
import { errorNameById, errors, isErrorName } from './errors.ts';
|
||||||
import { paramNumber, valueText } from './defs/types.ts';
|
import { paramNumber, valueText } from './types.ts';
|
||||||
import { parseTlvs, writeTlvs } from './defs/tlvs.ts';
|
import { parseTlvs, writeTlvs } from './tlvs.ts';
|
||||||
|
|
||||||
/** The highest sequence number this library hands out; SMPP 3.4 4.7.1 reserves 0x7fffffff. */
|
/** The highest sequence number this library hands out; SMPP 3.4 4.7.1 reserves 0x7fffffff. */
|
||||||
export const maxSeqNr = 2147483646;
|
export const maxSeqNr = 2147483646;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { CommandName } from './defs/commands.ts';
|
import type { CommandName } from './commands.ts';
|
||||||
import type { ErrorName } from './defs/errors.ts';
|
import type { ErrorName } from './errors.ts';
|
||||||
import { respNameFor } from './defs/commands.ts';
|
import { respNameFor } from './commands.ts';
|
||||||
|
|
||||||
/** A hostile peer must not be able to make us allocate arbitrarily. */
|
/** A hostile peer must not be able to make us allocate arbitrarily. */
|
||||||
export const maxPduLength = 1024 * 1024;
|
export const maxPduLength = 1024 * 1024;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ParamValue } from './defs/types.ts';
|
import type { ParamValue } from './types.ts';
|
||||||
import type { PduObject } from './pdu.ts';
|
import type { PduObject } from './pdu.ts';
|
||||||
import { tlvOctets } from './defs/types.ts';
|
import { tlvOctets } from './types.ts';
|
||||||
|
|
||||||
/** Wire reads hand back views, so retaining one PDU would pin the whole chunk it arrived in. */
|
/** Wire reads hand back views, so retaining one PDU would pin the whole chunk it arrived in. */
|
||||||
export function detach(pduObj: PduObject): PduObject {
|
export function detach(pduObj: PduObject): PduObject {
|
||||||
@@ -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,
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
+52
-34
@@ -1,13 +1,20 @@
|
|||||||
export { client } from './client.ts';
|
import { cmds, cmdsById } from './codec/commands.ts';
|
||||||
export { server, SmppServer } from './server.ts';
|
import { consts, constsById } from './codec/constants.ts';
|
||||||
export { Session } from './session.ts';
|
import { encodings } from './codec/encodings.ts';
|
||||||
|
import { errors, errorsById } from './codec/errors.ts';
|
||||||
|
import { tlvs, tlvsById } from './codec/tlvs.ts';
|
||||||
|
import { types } from './codec/types.ts';
|
||||||
|
|
||||||
export { cmds, cmdsById, commandNameById, isCommandName } from './defs/commands.ts';
|
export { client } from './client/client.ts';
|
||||||
export { consts, constsById } from './defs/constants.ts';
|
export { server, SmppServer } from './server/server.ts';
|
||||||
export { dataCodingByEncoding, detect, encodingByDataCoding, encodings, isEncodingName, messageClassOf, unencodable } from './defs/encodings.ts';
|
export { Session } from './session/session.ts';
|
||||||
export { errorNameById, errors, errorsById, isErrorName } from './defs/errors.ts';
|
|
||||||
export { isTlvName, tlvs, tlvsById } from './defs/tlvs.ts';
|
export { cmds, cmdsById, commandNameById, isCommandName } from './codec/commands.ts';
|
||||||
export { types } from './defs/types.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/errors.ts';
|
||||||
|
export { isTlvName, tlvs, tlvsById } from './codec/tlvs.ts';
|
||||||
|
export { types } from './codec/types.ts';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isCommand,
|
isCommand,
|
||||||
@@ -16,9 +23,9 @@ export {
|
|||||||
objToPdu,
|
objToPdu,
|
||||||
pduReturn,
|
pduReturn,
|
||||||
pduToObj,
|
pduToObj,
|
||||||
} from './pdu.ts';
|
} from './codec/pdu.ts';
|
||||||
|
|
||||||
export { maxPduLength, PduRefusedError } from './pdu-refusal.ts';
|
export { maxPduLength, PduRefusedError } from './codec/refusal.ts';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
bitCount,
|
bitCount,
|
||||||
@@ -29,27 +36,27 @@ export {
|
|||||||
splitMessage,
|
splitMessage,
|
||||||
} from './message.ts';
|
} from './message.ts';
|
||||||
|
|
||||||
export { dlrFromPdu, parseReceipt, receiptCodes } from './dlr.ts';
|
export { dlrFromPdu, parseReceipt, receiptCodes } from './protocol/dlr.ts';
|
||||||
export { messageOctets } from './message-body.ts';
|
export { messageOctets } from './protocol/message-body.ts';
|
||||||
export { concatOf } from './concat.ts';
|
export { concatOf } from './protocol/concat.ts';
|
||||||
export { concatInfo } from './udh.ts';
|
export { concatInfo } from './protocol/udh.ts';
|
||||||
export { PduFramer } from './pdu-framer.ts';
|
export { PduFramer } from './codec/pdu-framer.ts';
|
||||||
export { uuidv7 } from './uuid.ts';
|
export { uuidv7 } from './protocol/uuid.ts';
|
||||||
|
|
||||||
export type { BindType, ClientOptions } from './client.ts';
|
export type { BindType, ClientOptions } from './client/client.ts';
|
||||||
export type { Dlr, Receipt } from './dlr.ts';
|
export type { Dlr, Receipt } from './protocol/dlr.ts';
|
||||||
export type { SendDlrResult, SendRespOptions, Sms } from './sms.ts';
|
export type { SendDlrResult, SendRespOptions, Sms } from './session/sms.ts';
|
||||||
export type { Concat } from './concat.ts';
|
export type { Concat } from './protocol/concat.ts';
|
||||||
export type { ConcatInfo } from './udh.ts';
|
export type { ConcatInfo } from './protocol/udh.ts';
|
||||||
export type { Result, VoidResult } from './result.ts';
|
export type { Result, VoidResult } from './result.ts';
|
||||||
export type { SmppLog } from './log.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 {
|
export type {
|
||||||
AuthenticateInput,
|
AuthenticateInput,
|
||||||
AuthenticateResult,
|
AuthenticateResult,
|
||||||
ServerEvents,
|
ServerEvents,
|
||||||
ServerOptions,
|
ServerOptions,
|
||||||
} from './server.ts';
|
} from './server/server.ts';
|
||||||
export type {
|
export type {
|
||||||
CloseOptions,
|
CloseOptions,
|
||||||
MessageDlr,
|
MessageDlr,
|
||||||
@@ -59,16 +66,27 @@ export type {
|
|||||||
SendSmsResult,
|
SendSmsResult,
|
||||||
SessionEvents,
|
SessionEvents,
|
||||||
SessionOptions,
|
SessionOptions,
|
||||||
} from './session.ts';
|
} from './session/session.ts';
|
||||||
export type { CommandName, PduParams, PduParamsInput } from './defs/commands.ts';
|
export type { CommandName, PduParams, PduParamsInput } from './codec/commands.ts';
|
||||||
export type { ConstGroup, MessageState, SubmitMessagingMode } from './defs/constants.ts';
|
export type { ConstGroup, MessageState, SubmitMessagingMode } from './codec/constants.ts';
|
||||||
export type { Encoding, EncodingName, Unencodable } from './defs/encodings.ts';
|
export type { Encoding, EncodingName, Unencodable } from './codec/encodings.ts';
|
||||||
export type { ErrorName } from './defs/errors.ts';
|
export type { ErrorName } from './codec/errors.ts';
|
||||||
export type { PduObject, PduObjectInput, TlvInputs } from './pdu.ts';
|
export type { PduObject, PduObjectInput, TlvInputs } from './codec/pdu.ts';
|
||||||
export type { PduHeader } from './pdu-refusal.ts';
|
export type { PduHeader } from './codec/refusal.ts';
|
||||||
export type { SplitOptions } from './message.ts';
|
export type { SplitOptions } from './message.ts';
|
||||||
export type { Tlv, TlvDefinition, TlvName, Tlvs } from './defs/tlvs.ts';
|
export type { Tlv, TlvDefinition, TlvName, Tlvs } from './codec/tlvs.ts';
|
||||||
export type { DestAddress, ParamValue, TlvValue, UnsuccessSme, WireType } from './defs/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. */
|
/** 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,
|
||||||
|
};
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
import type { Result } from './result.ts';
|
import type { Result } from './result.ts';
|
||||||
import type { EncodingName } from './defs/encodings.ts';
|
import type { EncodingName } from './codec/encodings.ts';
|
||||||
import { dataCodingByEncoding, detect, encodingByDataCoding, encodings, unencodable, unencodableText } from './defs/encodings.ts';
|
import { dataCodingByEncoding, detect, encodingByDataCoding, encodings, unencodable, unencodableText } from './codec/encodings.ts';
|
||||||
import { hasUdh } from './defs/constants.ts';
|
import { hasUdh } from './codec/constants.ts';
|
||||||
import { udhLength } from './udh.ts';
|
import { udhLength } from './protocol/udh.ts';
|
||||||
|
|
||||||
/** A single SMS carries 1120 bits, whatever the alphabet. */
|
/** A single SMS carries 1120 bits, whatever the alphabet. */
|
||||||
const singleMessageBits = 1120;
|
const singleMessageBits = 1120;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Dlr } from './dlr.ts';
|
import type { Dlr } from '../protocol/dlr.ts';
|
||||||
import type { MessageState } from './defs/constants.ts';
|
import type { MessageState } from '../codec/constants.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import { ExpiringGroups } from './expiring-groups.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 };
|
export type MessageDlr = Dlr & { segments: Dlr[]; smsId: string };
|
||||||
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import type { Concat } from './concat.ts';
|
import type { Concat } from '../protocol/concat.ts';
|
||||||
import type { PduObject } from './pdu.ts';
|
import type { PduObject } from '../codec/pdu.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import { ExpiringGroups } from './expiring-groups.ts';
|
import { ExpiringGroups } from './expiring-groups.ts';
|
||||||
import { decodeMessage } from './message.ts';
|
import { decodeMessage } from '../message.ts';
|
||||||
import { detach, retainedOctets } from './retained-pdu.ts';
|
import { defaults } from '../options.ts';
|
||||||
import { messageOctets } from './message-body.ts';
|
import { detach, retainedOctets } from '../codec/retained-pdu.ts';
|
||||||
import { paramNumber, paramText } from './defs/types.ts';
|
import { messageOctets } from '../protocol/message-body.ts';
|
||||||
import { uuidv7 } from './uuid.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. */
|
/** A concatenated message given up on, whose segments the peer has already been answered for. */
|
||||||
export type LostGroup = {
|
export type LostGroup = {
|
||||||
@@ -42,8 +43,6 @@ export type Collected =
|
|||||||
whole?: PduObject[] | undefined;
|
whole?: PduObject[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultMaxOctets = 64 * 1024 * 1024;
|
|
||||||
|
|
||||||
type Group = {
|
type Group = {
|
||||||
parts: Map<number, PduObject>;
|
parts: Map<number, PduObject>;
|
||||||
smsId: string;
|
smsId: string;
|
||||||
@@ -89,7 +88,7 @@ export class Reassembler {
|
|||||||
private readonly onLost: (lost: LostGroup) => void;
|
private readonly onLost: (lost: LostGroup) => void;
|
||||||
|
|
||||||
constructor(options: ReassemblerOptions) {
|
constructor(options: ReassemblerOptions) {
|
||||||
this.maxOctets = options.maxOctets ?? defaultMaxOctets;
|
this.maxOctets = options.maxOctets ?? defaults.maxOctets;
|
||||||
this.groups = new ExpiringGroups<Group>({
|
this.groups = new ExpiringGroups<Group>({
|
||||||
max: options.max,
|
max: options.max,
|
||||||
maxWeight: this.maxOctets,
|
maxWeight: this.maxOctets,
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import type { EncodingName, Unencodable } from './defs/encodings.ts';
|
import type { EncodingName, Unencodable } from '../codec/encodings.ts';
|
||||||
import type { ParamValue } from './defs/types.ts';
|
import type { ParamValue } from '../codec/types.ts';
|
||||||
import type { SubmitMessagingMode } from './defs/constants.ts';
|
import type { SubmitMessagingMode } from '../codec/constants.ts';
|
||||||
import type { PduObject, PduObjectInput } from './pdu.ts';
|
import type { PduObject, PduObjectInput } from '../codec/pdu.ts';
|
||||||
import type { Result } from './result.ts';
|
import type { Result } from '../result.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import type { SmsIdNotation } from './sms-id.ts';
|
import type { SmsIdNotation } from '../protocol/message-ids.ts';
|
||||||
import { UnansweredError } from './unanswered-error.ts';
|
import { UnansweredError } from '../unanswered-error.ts';
|
||||||
import { consts, defaultMessagingMode, isMessagingMode, isSubmitMessagingMode, submitMessagingModes } from './defs/constants.ts';
|
import { consts, defaultMessagingMode, isMessagingMode, isSubmitMessagingMode, submitMessagingModes } from '../codec/constants.ts';
|
||||||
import { cstring, paramText } from './defs/types.ts';
|
import { cstring, paramText } from '../codec/types.ts';
|
||||||
import { dataCodingByEncoding, detect, encodingNames, isEncodingName, unencodable, unencodableText } from './defs/encodings.ts';
|
import { dataCodingByEncoding, detect, encodingNames, isEncodingName, unencodable, unencodableText } from '../codec/encodings.ts';
|
||||||
import { namedValue } from './error-from.ts';
|
import { namedValue } from '../result.ts';
|
||||||
import { normaliseSmsId } from './sms-id.ts';
|
import { normaliseSmsId } from '../protocol/message-ids.ts';
|
||||||
import { maxSegments, smppTime, splitMessage } from './message.ts';
|
import { maxSegments, smppTime, splitMessage } from '../message.ts';
|
||||||
|
|
||||||
export type SendSmsOptions = {
|
export type SendSmsOptions = {
|
||||||
dlr?: boolean;
|
dlr?: boolean;
|
||||||
@@ -1,77 +1,11 @@
|
|||||||
import type { Dlr } from './dlr.ts';
|
import type { PduObject } from './codec/pdu.ts';
|
||||||
import type { MessageDlr } from './dlr-merger.ts';
|
|
||||||
import type { PduObject } from './pdu.ts';
|
|
||||||
import type { PduRefusedError } from './pdu-refusal.ts';
|
|
||||||
import type { Result, VoidResult } from './result.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 { SmppLog } from './log.ts';
|
||||||
import type { SmsIdFormat } from './sms-id.ts';
|
import type { SmsIdFormat } from './protocol/message-ids.ts';
|
||||||
import type { Sms } from './sms.ts';
|
|
||||||
import type { Socket } from 'node:net';
|
import type { Socket } from 'node:net';
|
||||||
import { backoffDefaults } from './reconnect-loop.ts';
|
import { isSmsIdNotation, smsIdNotations, smsIdPlaces } from './protocol/message-ids.ts';
|
||||||
import { defaultMaxOctets } from './reassembly.ts';
|
import { namedValue, quoted } from './result.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SendOptions = { signal?: AbortSignal | undefined };
|
export type SendOptions = { signal?: AbortSignal | undefined };
|
||||||
|
|
||||||
@@ -116,51 +50,36 @@ export type SessionOptions = {
|
|||||||
systemId?: string | undefined;
|
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 = {
|
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. */
|
/** Receipts of a multipart message can be a working day apart, so the cap does the bounding. */
|
||||||
dlrMergeTimeout: 86_400_000,
|
dlrMergeTimeout: 86_400_000,
|
||||||
|
enquireLinkInterval: 20_000,
|
||||||
/** The peer gave up on an unanswered message long before this; the bound is against growth. */
|
/** The peer gave up on an unanswered message long before this; the bound is against growth. */
|
||||||
heldMessageTimeout: 300_000,
|
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,
|
maxDlrMerges: 1000,
|
||||||
maxHeldMessages: 1000,
|
maxHeldMessages: 1000,
|
||||||
maxHeldOctets: 64 * 1024 * 1024,
|
maxHeldOctets: 64 * 1024 * 1024,
|
||||||
|
maxOctets: 64 * 1024 * 1024,
|
||||||
maxOutstanding: 10,
|
maxOutstanding: 10,
|
||||||
maxReassembly: 1000,
|
maxReassembly: 1000,
|
||||||
|
minDelay: 1000,
|
||||||
|
password: 'pass',
|
||||||
|
port: 2775,
|
||||||
reassemblyTimeout: 300_000,
|
reassemblyTimeout: 300_000,
|
||||||
responseTimeout: 30_000,
|
responseTimeout: 30_000,
|
||||||
|
serverIdleTimeout: 40_000,
|
||||||
shutdownTimeout: 5000,
|
shutdownTimeout: 5000,
|
||||||
systemId: defaultSystemId,
|
systemId: '',
|
||||||
};
|
username: 'user',
|
||||||
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A count below 1 does not fail loudly anywhere downstream: `maxOutstanding: 0` leaves every send
|
* 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][] {
|
function limitsOf(options: CheckableOptions): [string, number, number][] {
|
||||||
return [
|
return [
|
||||||
['idleTimeout', options.idleTimeout ?? 0, 0],
|
['idleTimeout', options.idleTimeout ?? 0, 0],
|
||||||
['maxOctets', options.maxOctets ?? defaultMaxOctets, 1],
|
['maxOctets', options.maxOctets ?? defaults.maxOctets, 1],
|
||||||
['maxOutstanding', options.maxOutstanding ?? defaults.maxOutstanding, 1],
|
['maxOutstanding', options.maxOutstanding ?? defaults.maxOutstanding, 1],
|
||||||
['maxReassembly', options.maxReassembly ?? defaults.maxReassembly, 1],
|
['maxReassembly', options.maxReassembly ?? defaults.maxReassembly, 1],
|
||||||
['reassemblyTimeout', options.reassemblyTimeout ?? defaults.reassemblyTimeout, 0],
|
['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}`) };
|
return { err: new Error(`reconnect.fromStart must be true or false, got ${typeof reconnect.fromStart}`) };
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxDelay = delayOr(reconnect.maxDelay, backoffDefaults.maxDelay);
|
const maxDelay = delayOr(reconnect.maxDelay, defaults.maxDelay);
|
||||||
const minDelay = delayOr(reconnect.minDelay, backoffDefaults.minDelay);
|
const minDelay = delayOr(reconnect.minDelay, defaults.minDelay);
|
||||||
// A delay of 0 never doubles, so the backoff never starts and every retry lands at once.
|
// 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]]);
|
const checked = checkLimits([['maxDelay', maxDelay, 1], ['minDelay', minDelay, 1]]);
|
||||||
|
|
||||||
@@ -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 } };
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { ConcatInfo } from './udh.ts';
|
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 { concatInfo } from './udh.ts';
|
||||||
import { hasUdh } from './defs/constants.ts';
|
import { hasUdh } from '../codec/constants.ts';
|
||||||
import { messageOctets } from './message-body.ts';
|
import { messageOctets } from './message-body.ts';
|
||||||
import { paramNumber } from './defs/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. */
|
/** Where a segment sits in its message, and what ties it to the rest of that message. */
|
||||||
export type Concat = ConcatInfo & {
|
export type Concat = ConcatInfo & {
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { MessageState } from './defs/constants.ts';
|
import type { MessageState } from '../codec/constants.ts';
|
||||||
import type { TlvValue } from './defs/types.ts';
|
import type { TlvValue } from '../codec/types.ts';
|
||||||
import type { PduObject } from './pdu.ts';
|
import type { PduObject } from '../codec/pdu.ts';
|
||||||
import type { SmsIdFormat } from './sms-id.ts';
|
import type { SmsIdFormat } from './message-ids.ts';
|
||||||
import { consts, constsById, hasUdh, messageTypeOf } from './defs/constants.ts';
|
import { consts, constsById, hasUdh, messageTypeOf } from '../codec/constants.ts';
|
||||||
import { encodings } from './defs/encodings.ts';
|
import { encodings } from '../codec/encodings.ts';
|
||||||
import { messageOctets } from './message-body.ts';
|
import { messageOctets } from './message-body.ts';
|
||||||
import { normaliseSmsId } from './sms-id.ts';
|
import { normaliseSmsId } from './message-ids.ts';
|
||||||
import { paramNumber, paramText } from './defs/types.ts';
|
import { paramNumber, paramText } from '../codec/types.ts';
|
||||||
import { udhLength } from './udh.ts';
|
import { udhLength } from './udh.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
|
* The user data, wherever the peer put it. SMPP 3.4 5.3.2.32 carries up to 64 KB in
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { CommandName } from './defs/commands.ts';
|
import type { CommandName } from '../codec/commands.ts';
|
||||||
import type { ParamValue } from './defs/types.ts';
|
import type { ParamValue } from '../codec/types.ts';
|
||||||
|
|
||||||
const notations = {
|
const notations = {
|
||||||
decimal: { digits: /^[0-9]+$/, prefix: '' },
|
decimal: { digits: /^[0-9]+$/, prefix: '' },
|
||||||
@@ -7,3 +7,26 @@ export type Result<T> =
|
|||||||
| ({ err?: undefined } & T);
|
| ({ err?: undefined } & T);
|
||||||
|
|
||||||
export type VoidResult = { err?: Error };
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import type { BindType, CloseOptions, OnRequest } from './session-options.ts';
|
import type { BindType } from '../protocol/bind.ts';
|
||||||
import type { PduObject, TlvInputs } from './pdu.ts';
|
import type { CloseOptions, OnRequest } from '../options.ts';
|
||||||
import type { Result, VoidResult } from './result.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 NetServer, Socket } from 'node:net';
|
||||||
import type { Server as TlsServer, TlsOptions } from 'node:tls';
|
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 { EventEmitter } from 'node:events';
|
||||||
import { Session, defaultSystemId } from './session.ts';
|
import { Session } from '../session/session.ts';
|
||||||
import { bindTypeFromCommand, checkSessionOptions } from './session-options.ts';
|
import { bindTypeFromCommand } from '../protocol/bind.ts';
|
||||||
|
import { checkSessionOptions, defaults } from '../options.ts';
|
||||||
import { createServer as createNetServer } from 'node:net';
|
import { createServer as createNetServer } from 'node:net';
|
||||||
import { createServer as createTlsServer } from 'node:tls';
|
import { createServer as createTlsServer } from 'node:tls';
|
||||||
import { defaultInterfaceVersion } from './defs/constants.ts';
|
import { errorFrom } from '../result.ts';
|
||||||
import { errorFrom } from './error-from.ts';
|
import { paramText } from '../codec/types.ts';
|
||||||
import { paramText } from './defs/types.ts';
|
import { guardedLog } from '../log.ts';
|
||||||
import { guardedLog } from './log.ts';
|
import { respNameFor } from '../codec/commands.ts';
|
||||||
import { respNameFor } from './defs/commands.ts';
|
|
||||||
|
|
||||||
export type AuthenticateResult = { userData?: unknown } | boolean;
|
export type AuthenticateResult = { userData?: unknown } | boolean;
|
||||||
|
|
||||||
@@ -49,13 +50,6 @@ export type ServerEvents = {
|
|||||||
session: [Session];
|
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. */
|
/** A listener may return a promise: an `async` one that rejects is routed like one that throws. */
|
||||||
type ServerListener<K extends keyof ServerEvents> = (...args: ServerEvents[K]) => unknown;
|
type ServerListener<K extends keyof ServerEvents> = (...args: ServerEvents[K]) => unknown;
|
||||||
|
|
||||||
@@ -233,7 +227,7 @@ async function handleRequest(
|
|||||||
function onConnection(sock: Socket, options: ServerOptions, server: SmppServer): void {
|
function onConnection(sock: Socket, options: ServerOptions, server: SmppServer): void {
|
||||||
const log = guardedLog(options.log);
|
const log = guardedLog(options.log);
|
||||||
const session = new Session({
|
const session = new Session({
|
||||||
idleTimeout: options.idleTimeout ?? defaults.idleTimeout,
|
idleTimeout: options.idleTimeout ?? defaults.serverIdleTimeout,
|
||||||
log,
|
log,
|
||||||
maxOutstanding: options.maxOutstanding,
|
maxOutstanding: options.maxOutstanding,
|
||||||
maxOctets: options.maxOctets,
|
maxOctets: options.maxOctets,
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { LinkLife } from './link-life.ts';
|
import type { LinkLife } from './link-life.ts';
|
||||||
import type { PduObject, PduObjectInput } from './pdu.ts';
|
import type { PduObject, PduObjectInput } from '../codec/pdu.ts';
|
||||||
import type { Result } from './result.ts';
|
import type { Result } from '../result.ts';
|
||||||
import type { Session } from './session.ts';
|
import type { Session } from './session.ts';
|
||||||
import type { SmsHandlers } from './sms.ts';
|
import type { SmsHandlers } from './sms.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import { ExpiringGroups } from './expiring-groups.ts';
|
import { ExpiringGroups } from '../messages/expiring-groups.ts';
|
||||||
import { IdleWaiters } from './idle-waiters.ts';
|
import { IdleWaiters } from './idle-waiters.ts';
|
||||||
import { createSms } from './sms.ts';
|
import { createSms } from './sms.ts';
|
||||||
import { retainedOctets } from './retained-pdu.ts';
|
import { retainedOctets } from '../codec/retained-pdu.ts';
|
||||||
|
|
||||||
export type HeldMessagesOptions = {
|
export type HeldMessagesOptions = {
|
||||||
link: LinkLife;
|
link: LinkLife;
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
import type { Concat } from './concat.ts';
|
import type { Concat } from '../protocol/concat.ts';
|
||||||
import type { DlrMerger } from './dlr-merger.ts';
|
import type { DlrMerger } from '../messages/dlr-merger.ts';
|
||||||
import type { ErrorName } from './defs/errors.ts';
|
import type { ErrorName } from '../codec/errors.ts';
|
||||||
import type { HeldMessagesOptions } from './held-messages.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 './reassembly.ts';
|
import type { LostGroup, Refusal } from '../messages/reassembly.ts';
|
||||||
import type { OnRequest } from './session-options.ts';
|
import type { OnRequest } from '../options.ts';
|
||||||
import type { PduObject } from './pdu.ts';
|
import type { PduObject } from '../codec/pdu.ts';
|
||||||
import type { VoidResult } from './result.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 { SmppLog } from '../log.ts';
|
||||||
import type { SmsIdFormat } from './sms-id.ts';
|
import type { SmsIdFormat } from '../protocol/message-ids.ts';
|
||||||
import { HeldMessages } from './held-messages.ts';
|
import { HeldMessages } from './held-messages.ts';
|
||||||
import { Reassembler } from './reassembly.ts';
|
import { Reassembler } from '../messages/reassembly.ts';
|
||||||
import { bindCommands, defaults, standsInFor } from './session-options.ts';
|
import { bindCommands, standsInFor } from '../protocol/bind.ts';
|
||||||
import { concatOf } from './concat.ts';
|
import { defaults } from '../options.ts';
|
||||||
import { detach } from './retained-pdu.ts';
|
import { concatOf } from '../protocol/concat.ts';
|
||||||
import { dlrFromPdu } from './dlr.ts';
|
import { detach } from '../codec/retained-pdu.ts';
|
||||||
import { respIdParams, segmentId } from './sms-id.ts';
|
import { dlrFromPdu } from '../protocol/dlr.ts';
|
||||||
import { respNameFor } from './defs/commands.ts';
|
import { respIdParams, segmentId } from '../protocol/message-ids.ts';
|
||||||
|
import { respNameFor } from '../codec/commands.ts';
|
||||||
|
|
||||||
/** Asks the peer to keep the message and retry. */
|
/** Asks the peer to keep the message and retry. */
|
||||||
function throttledStatus(carriedAs: string): ErrorName {
|
function throttledStatus(carriedAs: string): ErrorName {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import type { VoidResult } from './result.ts';
|
import type { VoidResult } from '../result.ts';
|
||||||
|
|
||||||
export type LinkLifeOptions = {
|
export type LinkLifeOptions = {
|
||||||
log: SmppLog;
|
log: SmppLog;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
|
|
||||||
export type LinkTimersOptions = {
|
export type LinkTimersOptions = {
|
||||||
/** How long between enquire_link probes. Undefined or 0 never probes. */
|
/** How long between enquire_link probes. Undefined or 0 never probes. */
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import type { LinkLife } from './link-life.ts';
|
import type { LinkLife } from './link-life.ts';
|
||||||
import type { PduObject, PduObjectInput } from './pdu.ts';
|
import type { PduObject, PduObjectInput } from '../codec/pdu.ts';
|
||||||
import type { PduTransport } from './pdu-transport.ts';
|
import type { PduTransport } from './pdu-transport.ts';
|
||||||
import type { Result, VoidResult } from './result.ts';
|
import type { Result, VoidResult } from '../result.ts';
|
||||||
import type { SendOptions } from './session-options.ts';
|
import type { SendOptions } from '../options.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import { PendingRequests } from './pending-requests.ts';
|
import { PendingRequests } from './pending-requests.ts';
|
||||||
import { SendWindow } from './send-window.ts';
|
import { SendWindow } from './send-window.ts';
|
||||||
import { UnansweredError } from './unanswered-error.ts';
|
import { UnansweredError } from '../unanswered-error.ts';
|
||||||
import { bindCommands } from './session-options.ts';
|
import { bindCommands } from '../protocol/bind.ts';
|
||||||
import { objToPdu } from './pdu.ts';
|
import { objToPdu } from '../codec/pdu.ts';
|
||||||
|
|
||||||
export type OutgoingRequestsOptions = {
|
export type OutgoingRequestsOptions = {
|
||||||
link: LinkLife;
|
link: LinkLife;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { PduObject } from './pdu.ts';
|
import type { PduObject } from '../codec/pdu.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import type { Socket } from 'node:net';
|
import type { Socket } from 'node:net';
|
||||||
import type { VoidResult } from './result.ts';
|
import type { VoidResult } from '../result.ts';
|
||||||
import { PduFramer } from './pdu-framer.ts';
|
import { PduFramer } from '../codec/pdu-framer.ts';
|
||||||
import { PduRefusedError } from './pdu-refusal.ts';
|
import { PduRefusedError } from '../codec/refusal.ts';
|
||||||
import { pduToObj } from './pdu.ts';
|
import { pduToObj } from '../codec/pdu.ts';
|
||||||
|
|
||||||
export type PduTransportOptions = {
|
export type PduTransportOptions = {
|
||||||
log: SmppLog;
|
log: SmppLog;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { PduObject } from './pdu.ts';
|
import type { PduObject } from '../codec/pdu.ts';
|
||||||
import type { Result } from './result.ts';
|
import type { Result } from '../result.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import { maxSeqNr } from './pdu.ts';
|
import { maxSeqNr } from '../codec/pdu.ts';
|
||||||
|
|
||||||
export type WaitOptions = {
|
export type WaitOptions = {
|
||||||
signal?: AbortSignal | undefined;
|
signal?: AbortSignal | undefined;
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import type { Result, VoidResult } from './result.ts';
|
import type { Result, VoidResult } from '../result.ts';
|
||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import type { Socket } from 'node:net';
|
import type { Socket } from 'node:net';
|
||||||
|
import { defaults } from '../options.ts';
|
||||||
export const backoffDefaults = {
|
|
||||||
maxDelay: 30_000,
|
|
||||||
minDelay: 1000,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ReconnectLoopOptions = {
|
export type ReconnectLoopOptions = {
|
||||||
connect: () => Promise<Result<{ sock: Socket }>>;
|
connect: () => Promise<Result<{ sock: Socket }>>;
|
||||||
@@ -32,8 +28,8 @@ export class ReconnectLoop {
|
|||||||
private upAt: number | undefined;
|
private upAt: number | undefined;
|
||||||
|
|
||||||
constructor(options: ReconnectLoopOptions) {
|
constructor(options: ReconnectLoopOptions) {
|
||||||
this.maxDelay = options.maxDelay ?? backoffDefaults.maxDelay;
|
this.maxDelay = options.maxDelay ?? defaults.maxDelay;
|
||||||
this.minDelay = options.minDelay ?? backoffDefaults.minDelay;
|
this.minDelay = options.minDelay ?? defaults.minDelay;
|
||||||
this.now = options.now ?? Date.now;
|
this.now = options.now ?? Date.now;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.delay = this.minDelay;
|
this.delay = this.minDelay;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { SmppLog } from './log.ts';
|
import type { SmppLog } from '../log.ts';
|
||||||
import type { VoidResult } from './result.ts';
|
import type { VoidResult } from '../result.ts';
|
||||||
import { IdleWaiters } from './idle-waiters.ts';
|
import { IdleWaiters } from './idle-waiters.ts';
|
||||||
|
|
||||||
export type SendWindowOptions = {
|
export type SendWindowOptions = {
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import type { ErrorName } from './defs/errors.ts';
|
import type { Dlr } from '../protocol/dlr.ts';
|
||||||
import type { MessageDlr } from './dlr-merger.ts';
|
import type { ErrorName } from '../codec/errors.ts';
|
||||||
import type { ParamValue } from './defs/types.ts';
|
import type { MessageDlr } from '../messages/dlr-merger.ts';
|
||||||
import type { PduObject, PduObjectInput, TlvInputs } from './pdu.ts';
|
import type { ParamValue } from '../codec/types.ts';
|
||||||
import type { PduRefusedError } from './pdu-refusal.ts';
|
import type { PduObject, PduObjectInput, TlvInputs } from '../codec/pdu.ts';
|
||||||
import type { BindType, CloseOptions, LinkEnd, ReconnectOptions, SendOptions, SessionBind, SessionEvents, SessionOptions } from './session-options.ts';
|
import type { PduRefusedError } from '../codec/refusal.ts';
|
||||||
import type { Result, VoidResult } from './result.ts';
|
import type { BindType, LinkEnd, SessionBind } from '../protocol/bind.ts';
|
||||||
import type { SendSmsOptions, SendSmsResult } from './send-sms.ts';
|
import type { CloseOptions, ReconnectOptions, SendOptions, SessionOptions } from '../options.ts';
|
||||||
import type { SmppLog } from './log.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 type { Socket } from 'node:net';
|
||||||
import { DlrMerger } from './dlr-merger.ts';
|
import { DlrMerger } from '../messages/dlr-merger.ts';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
import { IncomingRequests } from './incoming-requests.ts';
|
import { IncomingRequests } from './incoming-requests.ts';
|
||||||
import { LinkLife } from './link-life.ts';
|
import { LinkLife } from './link-life.ts';
|
||||||
@@ -17,14 +20,15 @@ import { OutgoingRequests } from './outgoing-requests.ts';
|
|||||||
import { PduTransport } from './pdu-transport.ts';
|
import { PduTransport } from './pdu-transport.ts';
|
||||||
import { ReconnectLoop } from './reconnect-loop.ts';
|
import { ReconnectLoop } from './reconnect-loop.ts';
|
||||||
import { leftOf } from './idle-waiters.ts';
|
import { leftOf } from './idle-waiters.ts';
|
||||||
import { errorFrom } from './error-from.ts';
|
import { errorFrom } from '../result.ts';
|
||||||
import { optionalParamsMinVersion } from './defs/constants.ts';
|
import { optionalParamsMinVersion } from '../codec/constants.ts';
|
||||||
import { bindCarries, bindCommands, checkedBind, defaultSystemId, defaults } from './session-options.ts';
|
import { bindCarries, checkedBind } from '../protocol/bind.ts';
|
||||||
import { isResp, objToPdu, pduReturn } from './pdu.ts';
|
import { defaults } from '../options.ts';
|
||||||
import { refusalAnswer } from './pdu-refusal.ts';
|
import { isResp, objToPdu, pduReturn } from '../codec/pdu.ts';
|
||||||
import { guardedLog } from './log.ts';
|
import { refusalAnswer } from '../codec/refusal.ts';
|
||||||
import { submitSms, unsent } from './send-sms.ts';
|
import { guardedLog } from '../log.ts';
|
||||||
import { ConcatReference } from './udh.ts';
|
import { submitSms, unsent } from '../messages/submit.ts';
|
||||||
|
import { ConcatReference } from '../protocol/udh.ts';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
CloseOptions,
|
CloseOptions,
|
||||||
@@ -33,11 +37,22 @@ export type {
|
|||||||
SendOptions,
|
SendOptions,
|
||||||
SendSmsOptions,
|
SendSmsOptions,
|
||||||
SendSmsResult,
|
SendSmsResult,
|
||||||
SessionEvents,
|
|
||||||
SessionOptions,
|
SessionOptions,
|
||||||
};
|
};
|
||||||
export type { BindType };
|
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. */
|
/** A listener may return a promise: an `async` one that rejects is routed like one that throws. */
|
||||||
type SessionListener<K extends keyof SessionEvents> = (...args: SessionEvents[K]) => unknown;
|
type SessionListener<K extends keyof SessionEvents> = (...args: SessionEvents[K]) => unknown;
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import type { ErrorName } from './defs/errors.ts';
|
import type { ErrorName } from '../codec/errors.ts';
|
||||||
import type { MessageState } from './defs/constants.ts';
|
import type { MessageState } from '../codec/constants.ts';
|
||||||
import type { PduObject, PduObjectInput, TlvInputs } from './pdu.ts';
|
import type { PduObject, PduObjectInput, TlvInputs } from '../codec/pdu.ts';
|
||||||
import type { Result, VoidResult } from './result.ts';
|
import type { Result, VoidResult } from '../result.ts';
|
||||||
import type { Session } from './session.ts';
|
import type { Session } from './session.ts';
|
||||||
import { UnansweredError } from './unanswered-error.ts';
|
import { UnansweredError } from '../unanswered-error.ts';
|
||||||
import { consts } from './defs/constants.ts';
|
import { consts } from '../codec/constants.ts';
|
||||||
import { decodeSegments } from './reassembly.ts';
|
import { decodeSegments } from '../messages/reassembly.ts';
|
||||||
import { messageClassOf } from './defs/encodings.ts';
|
import { messageClassOf } from '../codec/encodings.ts';
|
||||||
import { paramText } from './defs/types.ts';
|
import { paramText } from '../codec/types.ts';
|
||||||
import { receiptCodes, transientStates } from './dlr.ts';
|
import { receiptCodes, transientStates } from '../protocol/dlr.ts';
|
||||||
import { smppDate } from './message.ts';
|
import { smppDate } from '../message.ts';
|
||||||
import { respIdParams, segmentId } from './sms-id.ts';
|
import { respIdParams, segmentId } from '../protocol/message-ids.ts';
|
||||||
import { uuidv7 } from './uuid.ts';
|
import { uuidv7 } from '../protocol/uuid.ts';
|
||||||
|
|
||||||
/** `pduObjs` holds what the peer took, so a partial failure names what is already receipted. */
|
/** `pduObjs` holds what the peer took, so a partial failure names what is already receipted. */
|
||||||
export type SendDlrResult = {
|
export type SendDlrResult = {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { PduParams, PduParamsInput } from '../src/defs/commands.ts';
|
import type { PduParams, PduParamsInput } from '../src/codec/commands.ts';
|
||||||
import { cmds, cmdsById, commandNameById, isCommandName } from '../src/defs/commands.ts';
|
import { cmds, cmdsById, commandNameById, isCommandName } from '../src/codec/commands.ts';
|
||||||
|
|
||||||
describe('command table', () => {
|
describe('command table', () => {
|
||||||
test('every command is reachable by name and by id', () => {
|
test('every command is reachable by name and by id', () => {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import { bindToSmsc, dummySmsc } from './dummy-smsc.ts';
|
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 { 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 { decodeMessage } from '../src/message.ts';
|
||||||
import { dlrFromPdu } from '../src/dlr.ts';
|
import { dlrFromPdu } from '../src/protocol/dlr.ts';
|
||||||
import { encodingByDataCoding, encodings } from '../src/defs/encodings.ts';
|
import { encodingByDataCoding, encodings } from '../src/codec/encodings.ts';
|
||||||
import { objToPdu, pduToObj } from '../src/pdu.ts';
|
import { objToPdu, pduToObj } from '../src/codec/pdu.ts';
|
||||||
import { paramNumber } from '../src/defs/types.ts';
|
import { paramNumber } from '../src/codec/types.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
import type { PduObject } from '../src/pdu.ts';
|
import type { PduObject } from '../src/codec/pdu.ts';
|
||||||
|
|
||||||
const from = '46701113311';
|
const from = '46701113311';
|
||||||
const to = '46709771337';
|
const to = '46709771337';
|
||||||
|
|||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import { consts } from '../src/defs/constants.ts';
|
import { consts } from '../src/codec/constants.ts';
|
||||||
import { dlrFromPdu, parseReceipt, receiptCodes } from '../src/dlr.ts';
|
import { dlrFromPdu, parseReceipt, receiptCodes } from '../src/protocol/dlr.ts';
|
||||||
import { encodeMessage } from '../src/message.ts';
|
import { encodeMessage } from '../src/message.ts';
|
||||||
import { objToPdu, pduToObj } from '../src/pdu.ts';
|
import { objToPdu, pduToObj } from '../src/codec/pdu.ts';
|
||||||
import type { PduObject, TlvInputs } from '../src/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';
|
const receiptText = 'id:0195f0c7 sub:001 dlvrd:001 submit date:2508251430 done date:2508251431 stat:DELIVRD err:000 text:hello there';
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -1,13 +1,13 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import net from 'node:net';
|
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 type { TestContext } from 'node:test';
|
||||||
import { PduFramer } from '../src/pdu-framer.ts';
|
import { PduFramer } from '../src/codec/pdu-framer.ts';
|
||||||
import { client } from '../src/client.ts';
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter, closeListenerAfter } from './teardown.ts';
|
import { closeAfter, closeListenerAfter } from './teardown.ts';
|
||||||
import { consts } from '../src/defs/constants.ts';
|
import { consts } from '../src/codec/constants.ts';
|
||||||
import { objToPdu, pduReturn, pduToObj } from '../src/pdu.ts';
|
import { objToPdu, pduReturn, pduToObj } from '../src/codec/pdu.ts';
|
||||||
import { uuidv7 } from '../src/uuid.ts';
|
import { uuidv7 } from '../src/protocol/uuid.ts';
|
||||||
|
|
||||||
export type DummySmsc = {
|
export type DummySmsc = {
|
||||||
/** Writes a delivery receipt to the ESME, its body spelled as the test names it. */
|
/** Writes a delivery receipt to the ESME, its body spelled as the test names it. */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
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)', () => {
|
describe('ASCII (GSM 03.38)', () => {
|
||||||
const samples: [string, number[]][] = [
|
const samples: [string, number[]][] = [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
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', () => {
|
test('carries an Error through and describes anything else, including what String() refuses', () => {
|
||||||
const original = new Error('the original');
|
const original = new Error('the original');
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import assert from 'node:assert/strict';
|
|||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import reference from 'smpp';
|
import reference from 'smpp';
|
||||||
import type { ReferenceSession } 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.ts';
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter } from './teardown.ts';
|
import { closeAfter } from './teardown.ts';
|
||||||
import { concatInfo } from '../src/udh.ts';
|
import { concatInfo } from '../src/protocol/udh.ts';
|
||||||
import { objToPdu, pduToObj } from '../src/pdu.ts';
|
import { objToPdu, pduToObj } from '../src/codec/pdu.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
import { splitMessage } from '../src/message.ts';
|
import { splitMessage } from '../src/message.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+10
-10
@@ -1,19 +1,19 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { PduObjectInput } from '../src/pdu.ts';
|
import type { PduObjectInput } from '../src/codec/pdu.ts';
|
||||||
import type { SendSmsDeps } from '../src/send-sms.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 { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import type { TestContext } from 'node:test';
|
import type { TestContext } from 'node:test';
|
||||||
import { bindToSmsc, dummySmsc } from './dummy-smsc.ts';
|
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 { closeAfter } from './teardown.ts';
|
||||||
import { messageClassOf } from '../src/defs/encodings.ts';
|
import { messageClassOf } from '../src/codec/encodings.ts';
|
||||||
import { paramNumber } from '../src/defs/types.ts';
|
import { paramNumber } from '../src/codec/types.ts';
|
||||||
import { pduToObj } from '../src/pdu.ts';
|
import { pduToObj } from '../src/codec/pdu.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
import { silentLog } from '../src/log.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 from = '46701113311';
|
||||||
const to = '46709771337';
|
const to = '46709771337';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { EncodingName } from '../src/defs/encodings.ts';
|
import type { EncodingName } from '../src/codec/encodings.ts';
|
||||||
import {
|
import {
|
||||||
bitCount,
|
bitCount,
|
||||||
decodeMessage,
|
decodeMessage,
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { PduObjectInput } from '../src/pdu.ts';
|
import type { PduObjectInput } from '../src/codec/pdu.ts';
|
||||||
import type { SendSmsDeps } from '../src/send-sms.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/defs/constants.ts';
|
import type { SubmitMessagingMode } from '../src/codec/constants.ts';
|
||||||
import type { TestContext } from 'node:test';
|
import type { TestContext } from 'node:test';
|
||||||
import { bindToSmsc, dummySmsc } from './dummy-smsc.ts';
|
import { bindToSmsc, dummySmsc } from './dummy-smsc.ts';
|
||||||
import { consts, submitMessagingModes } from '../src/defs/constants.ts';
|
import { consts, submitMessagingModes } from '../src/codec/constants.ts';
|
||||||
import { paramNumber } from '../src/defs/types.ts';
|
import { paramNumber } from '../src/codec/types.ts';
|
||||||
import { pduToObj } from '../src/pdu.ts';
|
import { pduToObj } from '../src/codec/pdu.ts';
|
||||||
import { silentLog } from '../src/log.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 from = '46701113311';
|
||||||
const to = '46709771337';
|
const to = '46709771337';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { Dlr, Receipt } from '../src/dlr.ts';
|
import type { Dlr, Receipt } from '../src/protocol/dlr.ts';
|
||||||
import type { MessageDlr } from '../src/session.ts';
|
import type { MessageDlr } from '../src/session/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 { bindToSmsc, dummySmsc } from './dummy-smsc.ts';
|
||||||
import { consts } from '../src/defs/constants.ts';
|
import { consts } from '../src/codec/constants.ts';
|
||||||
import { dlrFromPdu, parseReceipt, receiptCodes, transientStates } from '../src/dlr.ts';
|
import { dlrFromPdu, parseReceipt, receiptCodes, transientStates } from '../src/protocol/dlr.ts';
|
||||||
import { objToPdu, pduToObj } from '../src/pdu.ts';
|
import { objToPdu, pduToObj } from '../src/codec/pdu.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receipt bodies as commercial operators document them, from `interop-tests/research/operator-quirks.md`
|
* Receipt bodies as commercial operators document them, from `interop-tests/research/operator-quirks.md`
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import { PduFramer } from '../src/pdu-framer.ts';
|
import { PduFramer } from '../src/codec/pdu-framer.ts';
|
||||||
import { objToPdu } from '../src/pdu.ts';
|
import { objToPdu } from '../src/codec/pdu.ts';
|
||||||
|
|
||||||
function pdu(seqNr: number): Buffer {
|
function pdu(seqNr: number): Buffer {
|
||||||
const { buffer } = objToPdu({ cmdName: 'enquire_link', seqNr });
|
const { buffer } = objToPdu({ cmdName: 'enquire_link', seqNr });
|
||||||
|
|||||||
+4
-4
@@ -1,9 +1,9 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import { PduRefusedError, refusalAnswer } from '../src/pdu-refusal.ts';
|
import { PduRefusedError, refusalAnswer } from '../src/codec/refusal.ts';
|
||||||
import { isCommand, isResp, objToPdu, pduReturn, pduToObj } from '../src/pdu.ts';
|
import { isCommand, isResp, objToPdu, pduReturn, pduToObj } from '../src/codec/pdu.ts';
|
||||||
import { paramText } from '../src/defs/types.ts';
|
import { paramText } from '../src/codec/types.ts';
|
||||||
import { isTlvName, tlvsById } from '../src/defs/tlvs.ts';
|
import { isTlvName, tlvsById } from '../src/codec/tlvs.ts';
|
||||||
|
|
||||||
function encode(...args: Parameters<typeof objToPdu>): Buffer {
|
function encode(...args: Parameters<typeof objToPdu>): Buffer {
|
||||||
const { buffer, err } = objToPdu(...args);
|
const { buffer, err } = objToPdu(...args);
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import type { PduObjectInput } from '../src/pdu.ts';
|
import type { PduObjectInput } from '../src/codec/pdu.ts';
|
||||||
import { objToPdu } from '../src/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. */
|
/** The octets a test writes straight to a socket, which objToPdu builds for every valid PDU. */
|
||||||
export function pduBytes(input: PduObjectInput): Buffer {
|
export function pduBytes(input: PduObjectInput): Buffer {
|
||||||
|
|||||||
+8
-8
@@ -1,16 +1,16 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { Dlr } from '../src/dlr.ts';
|
import type { Dlr } from '../src/protocol/dlr.ts';
|
||||||
import type { Session } from '../src/session.ts';
|
import type { Session } from '../src/session/session.ts';
|
||||||
import type { Sms } from '../src/sms.ts';
|
import type { Sms } from '../src/session/sms.ts';
|
||||||
import type { SmppLog } from '../src/log.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 type { TestContext } from 'node:test';
|
||||||
import { PduRefusedError } from '../src/pdu-refusal.ts';
|
import { PduRefusedError } from '../src/codec/refusal.ts';
|
||||||
import { client } from '../src/client.ts';
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter } from './teardown.ts';
|
import { closeAfter } from './teardown.ts';
|
||||||
import { isCommand, objToPdu } from '../src/pdu.ts';
|
import { isCommand, objToPdu } from '../src/codec/pdu.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
|
|
||||||
function once<T>(register: (resolve: (value: T) => void) => void): Promise<T> {
|
function once<T>(register: (resolve: (value: T) => void) => void): Promise<T> {
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
|||||||
+30
-29
@@ -1,42 +1,43 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { Collected, LostGroup } from '../src/reassembly.ts';
|
import type { Collected, LostGroup } from '../src/messages/reassembly.ts';
|
||||||
import type { Dlr } from '../src/dlr.ts';
|
import type { Dlr } from '../src/protocol/dlr.ts';
|
||||||
import type { ErrorName } from '../src/defs/errors.ts';
|
import type { ErrorName } from '../src/codec/errors.ts';
|
||||||
import type { IncomingRequestsOptions } from '../src/incoming-requests.ts';
|
import type { IncomingRequestsOptions } from '../src/session/incoming-requests.ts';
|
||||||
import type { HeldMessagesOptions, MessageHold } from '../src/held-messages.ts';
|
import type { HeldMessagesOptions, MessageHold } from '../src/session/held-messages.ts';
|
||||||
import type { MessageState } from '../src/defs/constants.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/pdu.ts';
|
import type { PduObject, PduObjectInput } from '../src/codec/pdu.ts';
|
||||||
import type { Result } from '../src/result.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 { 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.ts';
|
import type { SmppServer } from '../src/server/server.ts';
|
||||||
import type { TestContext } from 'node:test';
|
import type { TestContext } from 'node:test';
|
||||||
import { HeldMessages } from '../src/held-messages.ts';
|
import { HeldMessages } from '../src/session/held-messages.ts';
|
||||||
import { IncomingRequests, refusedSegmentStatus } from '../src/incoming-requests.ts';
|
import { IncomingRequests, refusedSegmentStatus } from '../src/session/incoming-requests.ts';
|
||||||
import { UnansweredError } from '../src/unanswered-error.ts';
|
import { UnansweredError } from '../src/unanswered-error.ts';
|
||||||
import { createSms } from '../src/sms.ts';
|
import { createSms } from '../src/session/sms.ts';
|
||||||
import { LinkLife } from '../src/link-life.ts';
|
import { LinkLife } from '../src/session/link-life.ts';
|
||||||
import { SendWindow } from '../src/send-window.ts';
|
import { SendWindow } from '../src/session/send-window.ts';
|
||||||
import { Reassembler, decodeSegments } from '../src/reassembly.ts';
|
import { Reassembler, decodeSegments } from '../src/messages/reassembly.ts';
|
||||||
import { Session } from '../src/session.ts';
|
import { Session } from '../src/session/session.ts';
|
||||||
import { DlrMerger } from '../src/dlr-merger.ts';
|
import { DlrMerger } from '../src/messages/dlr-merger.ts';
|
||||||
import { PduRefusedError } from '../src/pdu-refusal.ts';
|
import { PduRefusedError } from '../src/codec/refusal.ts';
|
||||||
import { objToPdu } from '../src/pdu.ts';
|
import { objToPdu } from '../src/codec/pdu.ts';
|
||||||
import { checkSessionOptions, defaults, standsInFor } from '../src/session-options.ts';
|
import { checkSessionOptions, defaults } from '../src/options.ts';
|
||||||
import { client } from '../src/client.ts';
|
import { standsInFor } from '../src/protocol/bind.ts';
|
||||||
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter, closeListenerAfter } from './teardown.ts';
|
import { closeAfter, closeListenerAfter } from './teardown.ts';
|
||||||
import { concatOf } from '../src/concat.ts';
|
import { concatOf } from '../src/protocol/concat.ts';
|
||||||
import { consts } from '../src/defs/constants.ts';
|
import { consts } from '../src/codec/constants.ts';
|
||||||
import { errors } from '../src/defs/errors.ts';
|
import { errors } from '../src/codec/errors.ts';
|
||||||
import { paramNumber, paramText } from '../src/defs/types.ts';
|
import { paramNumber, paramText } from '../src/codec/types.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
import { silentLog } from '../src/log.ts';
|
import { silentLog } from '../src/log.ts';
|
||||||
import { splitMessage } from '../src/message.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(
|
async function startServer(
|
||||||
t: TestContext,
|
t: TestContext,
|
||||||
|
|||||||
+16
-15
@@ -1,25 +1,26 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { Dlr } from '../src/dlr.ts';
|
import type { Dlr } from '../src/protocol/dlr.ts';
|
||||||
import type { PduObject, PduObjectInput } from '../src/pdu.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.ts';
|
import type { ServerOptions, SmppServer } from '../src/server/server.ts';
|
||||||
import type { SmppLog } from '../src/log.ts';
|
import type { SmppLog } from '../src/log.ts';
|
||||||
import type { TestContext } from 'node:test';
|
import type { TestContext } from 'node:test';
|
||||||
import type { VoidResult } from '../src/result.ts';
|
import type { VoidResult } from '../src/result.ts';
|
||||||
import { DlrMerger } from '../src/dlr-merger.ts';
|
import { DlrMerger } from '../src/messages/dlr-merger.ts';
|
||||||
import { PduFramer } from '../src/pdu-framer.ts';
|
import { PduFramer } from '../src/codec/pdu-framer.ts';
|
||||||
import { ReconnectLoop } from '../src/reconnect-loop.ts';
|
import { ReconnectLoop } from '../src/session/reconnect-loop.ts';
|
||||||
import { Session, bindCommands } from '../src/session.ts';
|
import { Session } from '../src/session/session.ts';
|
||||||
import { checkSessionOptions } from '../src/session-options.ts';
|
import { bindCommands } from '../src/protocol/bind.ts';
|
||||||
import { client } from '../src/client.ts';
|
import { checkSessionOptions } from '../src/options.ts';
|
||||||
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter, closeListenerAfter } from './teardown.ts';
|
import { closeAfter, closeListenerAfter } from './teardown.ts';
|
||||||
import { consts } from '../src/defs/constants.ts';
|
import { consts } from '../src/codec/constants.ts';
|
||||||
import { PduRefusedError } from '../src/pdu-refusal.ts';
|
import { PduRefusedError } from '../src/codec/refusal.ts';
|
||||||
import { isCommand, objToPdu, pduReturn, pduToObj } from '../src/pdu.ts';
|
import { isCommand, objToPdu, pduReturn, pduToObj } from '../src/codec/pdu.ts';
|
||||||
import { paramText } from '../src/defs/types.ts';
|
import { paramText } from '../src/codec/types.ts';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
import { bareTlvHeader, pduBytes, shortened, truncatedTlv, withUnknownCmdId } from './raw-pdus.ts';
|
import { bareTlvHeader, pduBytes, shortened, truncatedTlv, withUnknownCmdId } from './raw-pdus.ts';
|
||||||
import { silentLog } from '../src/log.ts';
|
import { silentLog } from '../src/log.ts';
|
||||||
import { splitMessage } from '../src/message.ts';
|
import { splitMessage } from '../src/message.ts';
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
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()', () => {
|
describe('normaliseSmsId()', () => {
|
||||||
test('reads an id the length a message_id may be, and leaves a longer one alone', () => {
|
test('reads an id the length a message_id may be, and leaves a longer one alone', () => {
|
||||||
|
|||||||
+1
-1
@@ -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 { Server, Socket } from 'node:net';
|
||||||
import type { TestContext } from 'node:test';
|
import type { TestContext } from 'node:test';
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -1,15 +1,15 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import test, { describe } from 'node:test';
|
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.ts';
|
import type { SmppServer } from '../src/server/server.ts';
|
||||||
import type { TestContext } from 'node:test';
|
import type { TestContext } from 'node:test';
|
||||||
import { Log } from '@larvit/log';
|
import { Log } from '@larvit/log';
|
||||||
import { TLSSocket } from 'node:tls';
|
import { TLSSocket } from 'node:tls';
|
||||||
import { client } from '../src/client.ts';
|
import { client } from '../src/client/client.ts';
|
||||||
import { closeAfter } from './teardown.ts';
|
import { closeAfter } from './teardown.ts';
|
||||||
import { generateKeyPairSync, randomBytes, sign } from 'node:crypto';
|
import { generateKeyPairSync, randomBytes, sign } from 'node:crypto';
|
||||||
import { server } from '../src/server.ts';
|
import { server } from '../src/server/server.ts';
|
||||||
|
|
||||||
const host = 'localhost';
|
const host = 'localhost';
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { DestAddress, UnsuccessSme } from '../src/defs/types.ts';
|
import type { DestAddress, UnsuccessSme } from '../src/codec/types.ts';
|
||||||
import { paramText, types } from '../src/defs/types.ts';
|
import { paramText, types } from '../src/codec/types.ts';
|
||||||
import { tlvs } from '../src/defs/tlvs.ts';
|
import { tlvs } from '../src/codec/tlvs.ts';
|
||||||
|
|
||||||
describe('integers', () => {
|
describe('integers', () => {
|
||||||
test('int8 reads, sizes and writes one octet', () => {
|
test('int8 reads, sizes and writes one octet', () => {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test, { describe } from 'node:test';
|
import test, { describe } from 'node:test';
|
||||||
import type { PduObjectInput } from '../src/pdu.ts';
|
import type { PduObjectInput } from '../src/codec/pdu.ts';
|
||||||
import type { SendSmsDeps, SendSmsInput } from '../src/send-sms.ts';
|
import type { SendSmsDeps, SendSmsInput } from '../src/messages/submit.ts';
|
||||||
import { bindToSmsc, dummySmsc } from './dummy-smsc.ts';
|
import { bindToSmsc, dummySmsc } from './dummy-smsc.ts';
|
||||||
import { decodeMessage } from '../src/message.ts';
|
import { decodeMessage } from '../src/message.ts';
|
||||||
import { messageOctets } from '../src/message-body.ts';
|
import { messageOctets } from '../src/protocol/message-body.ts';
|
||||||
import { objToPdu, pduToObj } from '../src/pdu.ts';
|
import { objToPdu, pduToObj } from '../src/codec/pdu.ts';
|
||||||
import { paramNumber, paramText } from '../src/defs/types.ts';
|
import { paramNumber, paramText } from '../src/codec/types.ts';
|
||||||
import { silentLog } from '../src/log.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 from = '46701113311';
|
||||||
const to = '46709771337';
|
const to = '46709771337';
|
||||||
|
|||||||
@@ -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,
|
[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 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
|
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:
|
comprehension-panel scoring run over the whole project. Each round:
|
||||||
|
|
||||||
@@ -215,17 +219,13 @@ 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.
|
- 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
|
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.
|
||||||
dropped mid-rebind.
|
|
||||||
|
|
||||||
Every chunk that changes the public API updates README's examples, MIGRATION.md, CHANGELOG.md,
|
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
|
docs/decisions.md and the AGENTS.md map in its own PR, and deletes what it replaces; no old and new
|
||||||
file stand side by side. The architecture review of 2026-09-30 (ALIGN) set this order; its amendments
|
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).
|
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.**
|
- [ ] **Build `protocol/`: every multi-fact octet read once into a named plain type.**
|
||||||
- `vocabulary.ts`: the glossary as types with one-line TSDoc.
|
- `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
|
- `data-coding.ts`: the `data_coding` table as rows, with a test that it equals today's function
|
||||||
@@ -234,6 +234,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.
|
reference counter goes to `SmppClient` in the lifecycle split.
|
||||||
- The `gsm7` rename, A6 included: `'ASCII'` becomes `'GSM7'` in every export.
|
- The `gsm7` rename, A6 included: `'ASCII'` becomes `'GSM7'` in every export.
|
||||||
- A test that fails on a spec citation without its sentence.
|
- 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
|
- [ ] **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
|
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.
|
spent set expires by age. README's bound text is updated.
|
||||||
@@ -254,6 +256,17 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture
|
|||||||
|
|
||||||
### Correctness
|
### 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
|
- [ ] **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
|
`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,
|
calls `link.open()` on a `down` link, `resetTimers()` skips, and `attempt()` reports success,
|
||||||
@@ -277,7 +290,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.**
|
- [ ] **Return an `err` where `message` is not a string, rather than throwing.**
|
||||||
`sendSms({ message: undefined })` — a forgotten property — reaches `value.replace()` in
|
`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
|
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
|
"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
|
again, so the docs are false for the likeliest caller mistake there is. From the stability
|
||||||
@@ -300,7 +313,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
|
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
|
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
|
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
|
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
|
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.
|
README.md or in MIGRATION.md — write it in the same change as the rule, so it is worded once.
|
||||||
@@ -319,7 +332,7 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture
|
|||||||
`responseTimeout`, `idleTimeout`, `shutdownTimeout` and `reassemblyTimeout` from below only,
|
`responseTimeout`, `idleTimeout`, `shutdownTimeout` and `reassemblyTimeout` from below only,
|
||||||
and Node fires a larger delay after 1 ms. From the 2026-09-28 scoring run.
|
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
|
so `"\x1B("` is detected as GSM, goes out as 0x1B 0x28 and arrives as `{`. From the
|
||||||
2026-09-28 scoring run.
|
2026-09-28 scoring run.
|
||||||
|
|
||||||
@@ -339,32 +352,31 @@ to the plan are [plan 3 §8](docs/comprehension-rewrite/plan-3.md#8-architecture
|
|||||||
|
|
||||||
### Shape — 6 today, and the gate is 7
|
### 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
|
- [ ] **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/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
|
`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,
|
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.
|
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
|
- [ ] **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
|
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
|
`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
|
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.
|
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.**
|
- [ ] **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
|
`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
|
application owes an answer; `refusing` decides no refusal — `held.full()` does — and only makes
|
||||||
@@ -377,12 +389,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
|
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.
|
`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,
|
- [ ] **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
|
`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
|
base again — four fields, one type, `string`. The whole multipart receipt mechanism turns on
|
||||||
@@ -464,9 +470,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
|
the fenced blocks at test time and assert each appears verbatim in the executed source, so an
|
||||||
edit to either fails the gate.
|
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
|
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/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
|
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.
|
repo, under a suppression written for something else.
|
||||||
|
|
||||||
@@ -524,7 +530,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
|
`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
|
`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
|
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/types.ts` is the quoted
|
||||||
spelling to take it from. Raised by review, 2026-09-20.
|
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
|
- [ ] **Refuse a send the codec cannot build before it waits for a link and a window slot.** Today
|
||||||
@@ -561,7 +567,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.
|
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
|
- [ ] **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/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
|
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,
|
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
|
but a reader who learns one and applies it to the other is wrong. A budget type both take
|
||||||
@@ -685,7 +691,7 @@ Each lands under goal 7: an option or a hook, with the call that passes none unc
|
|||||||
### Encodings
|
### Encodings
|
||||||
|
|
||||||
- [ ] **Register a custom encoding.** Maintainer's ask, 2026-09-14. `EncodingName` is a closed union
|
- [ ] **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
|
`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
|
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
|
module table as `smpp` does, so two sessions in one process cannot disagree about a name. A taken
|
||||||
|
|||||||
Reference in New Issue
Block a user