Declare the logger contract in the library instead of depending on @larvit/log

This commit is contained in:
2026-08-27 13:42:58 +02:00
parent ff3667e120
commit 8d245c82c4
18 changed files with 110 additions and 50 deletions
+8 -2
View File
@@ -20,7 +20,7 @@ These are not preferences. Breaking one is a defect.
`err`. No `throw`, no rejected promises, no exceptions as control flow. Node APIs that throw are `err`. No `throw`, no rejected promises, no exceptions as control flow. Node APIs that throw are
wrapped at the boundary and converted into a result. Programmer errors (bad arguments) are wrapped at the boundary and converted into a result. Programmer errors (bad arguments) are
results too. results too.
2. **Log messages are static strings.** Every dynamic value goes into `@larvit/log` metadata. Never 2. **Log messages are static strings.** Every dynamic value goes into the log metadata. Never
interpolate, never concatenate. interpolate, never concatenate.
- GOOD: `log.debug('sendSms() - splitting message', { parts: msgs.length, to });` - GOOD: `log.debug('sendSms() - splitting message', { parts: msgs.length, to });`
- BANNED: `log.debug('sendSms() - splitting into ' + msgs.length + ' parts');` - BANNED: `log.debug('sendSms() - splitting into ' + msgs.length + ' parts');`
@@ -45,7 +45,7 @@ src/
expiring-groups.ts ExpiringGroups: the capped, expiring store both of those share expiring-groups.ts ExpiringGroups: the capped, expiring store both of those share
incoming-requests.ts Every request the peer sends: messages, receipts, links, unknown commands incoming-requests.ts Every request the peer sends: messages, receipts, links, unknown commands
link-timers.ts LinkTimers: the enquire_link heartbeat and the idle timeout link-timers.ts LinkTimers: the enquire_link heartbeat and the idle timeout
log.ts silentLog — the default when the application passes none log.ts SmppLog, the logger contract, and silentLog — the default
message.ts Encoding detection, splitting, bit counting, SMPP date formatting message.ts Encoding detection, splitting, bit counting, SMPP date formatting
pdu.ts pduToObj / objToPdu / pduReturn — synchronous, result-returning pdu.ts pduToObj / objToPdu / pduReturn — synchronous, result-returning
pdu-framer.ts PduFramer: a byte stream cut into complete PDUs pdu-framer.ts PduFramer: a byte stream cut into complete PDUs
@@ -185,6 +185,12 @@ exactly 140.
`server()`, so an implementation that needs 5.0 throughout can have it. The threshold at or above `server()`, so an implementation that needs 5.0 throughout can have it. The threshold at or above
which a peer may be sent optional parameters is fixed at 0x34 by the spec and is not the same which a peer may be sent optional parameters is fixed at 0x34 by the spec and is not the same
constant as the declared version. constant as the declared version.
- **The logger is a five-method contract this library declares, not a dependency.** `SmppLog` in
`log.ts` is what the code actually calls (`debug`, `error`, `info`, `verbose`, `warn`), so an
application can satisfy it with an object literal and `@larvit/smpp` ships with no runtime
dependencies. `@larvit/log` implements it structurally and stays a devDependency, where
`test/tls.test.ts` passing a real `Log` as the server's logger keeps that compatibility compiled.
- **The TLS tests build their own self-signed certificate in DER** (`test/tls.test.ts`) instead of - **The TLS tests build their own self-signed certificate in DER** (`test/tls.test.ts`) instead of
adding a devDependency or shelling out to openssl. Maintainer's call, 2026-08-26: the dev image adding a devDependency or shelling out to openssl. Maintainer's call, 2026-08-26: the dev image
`node:24.18.0-bookworm-slim` ships no openssl binary, so a shelled-out fixture would pass in CI `node:24.18.0-bookworm-slim` ships no openssl binary, so a shelled-out fixture would pass in CI
+33 -5
View File
@@ -11,8 +11,7 @@ promises and the rough edges taken off.
## Requirements ## Requirements
Node 18 or later. The only runtime dependency is Node 18 or later. No runtime dependencies.
[`@larvit/log`](https://www.npmjs.com/package/@larvit/log).
## Install ## Install
@@ -85,7 +84,7 @@ Every one is optional.
| `responseTimeout` | `30000` | How long to wait for a response before giving up on it; `0` waits forever. | | `responseTimeout` | `30000` | How long to wait for a response before giving up on it; `0` waits forever. |
| `maxOutstanding` | `10` | Requests allowed on the wire at once; further sends queue. | | `maxOutstanding` | `10` | Requests allowed on the wire at once; further sends queue. |
| `reconnect` | off | `{ minDelay, maxDelay }` to re-bind automatically after a drop or an idle timeout, with exponential backoff. | | `reconnect` | off | `{ minDelay, maxDelay }` to re-bind automatically after a drop or an idle timeout, with exponential backoff. |
| `log` | silent | A `@larvit/log` instance. | | `log` | silent | Any object with `debug`, `error`, `info`, `verbose` and `warn` methods — see [Logging](#logging). |
| `signal` | — | An `AbortSignal` that cancels connecting and tears the session down. | | `signal` | — | An `AbortSignal` that cancels connecting and tears the session down. |
### Sending ### Sending
@@ -217,6 +216,35 @@ Runtime failures on a live connection arrive as `sessionError` and `serverError`
deliberately not called `error`: Node turns an unhandled `error` event into a thrown exception, which deliberately not called `error`: Node turns an unhandled `error` event into a thrown exception, which
is exactly what this library promises not to do. is exactly what this library promises not to do.
## Logging
`log` takes any object with `debug`, `error`, `info`, `verbose` and `warn` methods, each
`(msg: string, metadata?: Record<string, boolean | number | string>) => void`. Message strings are
static and every dynamic value goes in the metadata, so entries group by message.
[`@larvit/log`](https://www.npmjs.com/package/@larvit/log) implements it as it stands:
```javascript
import { Log } from '@larvit/log';
import { client } from '@larvit/smpp';
const { err, session } = await client({ log: new Log('debug') });
```
So does an object of your own, forwarding wherever you want it:
```javascript
const log = {
debug: () => undefined,
error: (msg, metadata) => { console.error(msg, metadata); },
info: (msg, metadata) => { console.info(msg, metadata); },
verbose: () => undefined,
warn: (msg, metadata) => { console.warn(msg, metadata); },
};
```
TypeScript users can import `SmppLog` to have the compiler check one.
## Sessions ## Sessions
### Events ### Events
@@ -289,8 +317,8 @@ The spec tables are exported both individually (`cmds`, `consts`, `encodings`, `
- **`defs.filters` is gone.** It was declared on every command and TLV but never invoked, so it did - **`defs.filters` is gone.** It was declared on every command and TLV but never invoked, so it did
nothing. SMPP time formatting, the one part worth keeping, is exported as `smppTime`. nothing. SMPP time formatting, the one part worth keeping, is exported as `smppTime`.
- **The `error` event is `sessionError`** (and `serverError` on the server handle). - **The `error` event is `sessionError`** (and `serverError` on the server handle).
- **`log`** takes a [`@larvit/log`](https://www.npmjs.com/package/@larvit/log) instance instead of a - **`log`** takes any object with `debug`, `error`, `info`, `verbose` and `warn` methods instead of a
`larvitutils` one, and is silent by default. `larvitutils` one, and is silent by default. See [Logging](#logging).
### Behaviour that changed on the wire ### Behaviour that changed on the wire
+2 -3
View File
@@ -8,11 +8,9 @@
"name": "@larvit/smpp", "name": "@larvit/smpp",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": {
"@larvit/log": "2.3.0"
},
"devDependencies": { "devDependencies": {
"@eslint/js": "10.0.1", "@eslint/js": "10.0.1",
"@larvit/log": "2.3.0",
"@types/node": "22.20.1", "@types/node": "22.20.1",
"eslint": "10.9.1", "eslint": "10.9.1",
"smpp": "0.6.0-rc.4", "smpp": "0.6.0-rc.4",
@@ -221,6 +219,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@larvit/log/-/log-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@larvit/log/-/log-2.3.0.tgz",
"integrity": "sha512-gLRMDWrFnoMGdAr9NXMXRKOUeceU6Qd/CW0c6qDO+DLFT1xWrqO2ubsHyPqPzlZHVUO3w3Bk7kOWo6C2Ykn4ZQ==", "integrity": "sha512-gLRMDWrFnoMGdAr9NXMXRKOUeceU6Qd/CW0c6qDO+DLFT1xWrqO2ubsHyPqPzlZHVUO3w3Bk7kOWo6C2Ykn4ZQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
+1 -3
View File
@@ -49,13 +49,11 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "10.0.1", "@eslint/js": "10.0.1",
"@larvit/log": "2.3.0",
"@types/node": "22.20.1", "@types/node": "22.20.1",
"eslint": "10.9.1", "eslint": "10.9.1",
"smpp": "0.6.0-rc.4", "smpp": "0.6.0-rc.4",
"typescript": "6.0.3", "typescript": "6.0.3",
"typescript-eslint": "8.68.0" "typescript-eslint": "8.68.0"
},
"dependencies": {
"@larvit/log": "2.3.0"
} }
} }
+4 -4
View File
@@ -1,6 +1,6 @@
import type { ConnectionOptions } from 'node:tls'; import type { ConnectionOptions } from 'node:tls';
import type { LogInt } from '@larvit/log';
import type { Result, VoidResult } from './result.ts'; import type { Result, VoidResult } from './result.ts';
import type { SmppLog } from './log.ts';
import type { Socket } from 'node:net'; import type { Socket } from 'node:net';
import { Session } from './session.ts'; import { Session } from './session.ts';
import { checkSessionOptions, undeclaredInterfaceVersion } from './session-options.ts'; import { checkSessionOptions, undeclaredInterfaceVersion } from './session-options.ts';
@@ -20,7 +20,7 @@ export type ClientOptions = {
host?: string; host?: string;
idleTimeout?: number; idleTimeout?: number;
interfaceVersion?: number; interfaceVersion?: number;
log?: LogInt; log?: SmppLog;
maxOutstanding?: number; maxOutstanding?: number;
password?: string; password?: string;
port?: number; port?: number;
@@ -134,7 +134,7 @@ async function bind(session: Session, options: ClientOptions): Promise<VoidResul
return {}; return {};
} }
function createSession(options: ClientOptions, log: LogInt, sock: Socket): Session { function createSession(options: ClientOptions, log: SmppLog, sock: Socket): Session {
const enquireLinkInterval = options.enquireLinkInterval ?? defaults.enquireLinkInterval; const enquireLinkInterval = options.enquireLinkInterval ?? defaults.enquireLinkInterval;
return new Session({ return new Session({
@@ -157,7 +157,7 @@ function createSession(options: ClientOptions, log: LogInt, sock: Socket): Sessi
}); });
} }
async function connect(options: ClientOptions, log: LogInt): Promise<Result<{ sock: Socket }>> { async function connect(options: ClientOptions, log: SmppLog): Promise<Result<{ sock: Socket }>> {
const checked = checkSessionOptions(options); const checked = checkSessionOptions(options);
if (checked.err) { if (checked.err) {
+3 -3
View File
@@ -1,12 +1,12 @@
import type { Dlr } from './dlr.ts'; import type { Dlr } from './dlr.ts';
import type { MessageState } from './defs/constants.ts'; import type { MessageState } from './defs/constants.ts';
import type { LogInt } from '@larvit/log'; import type { SmppLog } from './log.ts';
import { ExpiringGroups } from './expiring-groups.ts'; import { ExpiringGroups } from './expiring-groups.ts';
export type MessageDlr = Dlr & { segments: Dlr[] }; export type MessageDlr = Dlr & { segments: Dlr[] };
export type DlrMergerOptions = { export type DlrMergerOptions = {
log: LogInt; log: SmppLog;
max: number; max: number;
/** Injected so expiry can be exercised without a wall clock. */ /** Injected so expiry can be exercised without a wall clock. */
now?: (() => number) | undefined; now?: (() => number) | undefined;
@@ -50,7 +50,7 @@ function severityOf(dlr: Dlr): number {
export class DlrMerger { export class DlrMerger {
private readonly groups: ExpiringGroups<Group>; private readonly groups: ExpiringGroups<Group>;
private readonly log: LogInt; private readonly log: SmppLog;
private readonly max: number; private readonly max: number;
constructor(options: DlrMergerOptions) { constructor(options: DlrMergerOptions) {
+3 -3
View File
@@ -1,8 +1,8 @@
import type { DlrMerger } from './dlr-merger.ts'; import type { DlrMerger } from './dlr-merger.ts';
import type { LogInt } from '@larvit/log';
import type { OnRequest } from './session-options.ts'; import type { OnRequest } from './session-options.ts';
import type { PduObject } from './pdu.ts'; import type { PduObject } from './pdu.ts';
import type { Session } from './session.ts'; import type { Session } from './session.ts';
import type { SmppLog } from './log.ts';
import { Reassembler, decodeSegments } from './reassembly.ts'; import { Reassembler, decodeSegments } from './reassembly.ts';
import { bindCommands, defaults } from './session-options.ts'; import { bindCommands, defaults } from './session-options.ts';
import { concatInfo } from './udh.ts'; import { concatInfo } from './udh.ts';
@@ -13,7 +13,7 @@ import { paramText } from './defs/types.ts';
export type IncomingRequestsOptions = { export type IncomingRequestsOptions = {
dlrMerger: DlrMerger; dlrMerger: DlrMerger;
log: LogInt; log: SmppLog;
maxOctets?: number | undefined; maxOctets?: number | undefined;
maxReassembly?: number | undefined; maxReassembly?: number | undefined;
onRequest?: OnRequest | undefined; onRequest?: OnRequest | undefined;
@@ -25,7 +25,7 @@ export type IncomingRequestsOptions = {
/** Everything the peer asks of a session: messages, receipts, links and the answers to them. */ /** Everything the peer asks of a session: messages, receipts, links and the answers to them. */
export class IncomingRequests { export class IncomingRequests {
private readonly dlrMerger: DlrMerger; private readonly dlrMerger: DlrMerger;
private readonly log: LogInt; private readonly log: SmppLog;
private readonly onRequest: OnRequest | undefined; private readonly onRequest: OnRequest | undefined;
private readonly reassembler: Reassembler; private readonly reassembler: Reassembler;
private readonly session: Session; private readonly session: Session;
+1
View File
@@ -38,6 +38,7 @@ export type { Dlr, Receipt } from './dlr.ts';
export type { SendRespOptions, Sms, SmsInput } from './sms.ts'; export type { SendRespOptions, Sms, SmsInput } from './sms.ts';
export type { ConcatInfo } from './udh.ts'; export type { ConcatInfo } from './udh.ts';
export type { Result, VoidResult } from './result.ts'; export type { Result, VoidResult } from './result.ts';
export type { SmppLog } from './log.ts';
export type { export type {
AuthenticateInput, AuthenticateInput,
AuthenticateResult, AuthenticateResult,
+2 -2
View File
@@ -1,11 +1,11 @@
import type { LogInt } from '@larvit/log'; 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. */
enquireLinkInterval?: number | undefined; enquireLinkInterval?: number | undefined;
/** How long a silent peer is kept. Undefined or 0 keeps it forever. */ /** How long a silent peer is kept. Undefined or 0 keeps it forever. */
idleTimeout?: number | undefined; idleTimeout?: number | undefined;
log: LogInt; log: SmppLog;
onEnquireLink: () => void; onEnquireLink: () => void;
onIdle: () => void; onIdle: () => void;
}; };
+20 -3
View File
@@ -1,5 +1,22 @@
import type { LogInt } from '@larvit/log'; export type LogMetadata = Record<string, boolean | number | string>;
import { Log } from '@larvit/log'; export type LogMethod = (msg: string, metadata?: LogMetadata) => void;
/** What the library needs from a logger. A `@larvit/log` instance satisfies it as it stands. */
export type SmppLog = {
debug: LogMethod;
error: LogMethod;
info: LogMethod;
verbose: LogMethod;
warn: LogMethod;
};
const noop: LogMethod = () => undefined;
/** The default: a library that says nothing unless the application asks it to. */ /** The default: a library that says nothing unless the application asks it to. */
export const silentLog: LogInt = new Log({ logLevel: 'none' }); export const silentLog: SmppLog = {
debug: noop,
error: noop,
info: noop,
verbose: noop,
warn: noop,
};
+3 -3
View File
@@ -1,6 +1,6 @@
import type { LogInt } from '@larvit/log';
import type { PduObject } from './pdu.ts'; import type { PduObject } from './pdu.ts';
import type { Result } from './result.ts'; import type { Result } from './result.ts';
import type { SmppLog } from './log.ts';
import { maxSeqNr } from './pdu.ts'; import { maxSeqNr } from './pdu.ts';
export type WaitOptions = { export type WaitOptions = {
@@ -14,11 +14,11 @@ type Pending = {
/** Hands out sequence numbers and matches responses to the requests waiting for them. */ /** Hands out sequence numbers and matches responses to the requests waiting for them. */
export class PendingRequests { export class PendingRequests {
private readonly log: LogInt; private readonly log: SmppLog;
private readonly pending = new Map<number, Pending>(); private readonly pending = new Map<number, Pending>();
private ourSeqNr = 1; private ourSeqNr = 1;
constructor(log: LogInt) { constructor(log: SmppLog) {
this.log = log; this.log = log;
} }
+3 -3
View File
@@ -1,14 +1,14 @@
import type { ConcatInfo } from './udh.ts'; import type { ConcatInfo } from './udh.ts';
import type { LogInt } from '@larvit/log';
import type { ParamValue } from './defs/types.ts'; import type { ParamValue } from './defs/types.ts';
import type { PduObject } from './pdu.ts'; import type { PduObject } from './pdu.ts';
import type { SmppLog } from './log.ts';
import type { Tlv } from './defs/tlvs.ts'; import type { Tlv } from './defs/tlvs.ts';
import { ExpiringGroups } from './expiring-groups.ts'; import { ExpiringGroups } from './expiring-groups.ts';
import { decodeMessage } from './message.ts'; import { decodeMessage } from './message.ts';
import { paramText } from './defs/types.ts'; import { paramText } from './defs/types.ts';
export type ReassemblerOptions = { export type ReassemblerOptions = {
log: LogInt; log: SmppLog;
max: number; max: number;
maxOctets?: number | undefined; maxOctets?: number | undefined;
/** Injected so expiry can be exercised without a wall clock. */ /** Injected so expiry can be exercised without a wall clock. */
@@ -97,7 +97,7 @@ export function decodeSegments(pduObjs: PduObject[]): string {
/** Holds the segments of incomplete multipart messages until they are whole, capped and expiring. */ /** Holds the segments of incomplete multipart messages until they are whole, capped and expiring. */
export class Reassembler { export class Reassembler {
private readonly groups: ExpiringGroups<Group>; private readonly groups: ExpiringGroups<Group>;
private readonly log: LogInt; private readonly log: SmppLog;
private readonly max: number; private readonly max: number;
private readonly maxOctets: number; private readonly maxOctets: number;
private octets = 0; private octets = 0;
+2 -2
View File
@@ -1,10 +1,10 @@
import type { LogInt } from '@larvit/log';
import type { Result, VoidResult } from './result.ts'; import type { Result, VoidResult } from './result.ts';
import type { SmppLog } from './log.ts';
import type { Socket } from 'node:net'; import type { Socket } from 'node:net';
export type ReconnectLoopOptions = { export type ReconnectLoopOptions = {
connect: () => Promise<Result<{ sock: Socket }>>; connect: () => Promise<Result<{ sock: Socket }>>;
log: LogInt; log: SmppLog;
maxDelay: number; maxDelay: number;
minDelay: number; minDelay: number;
/** Brings the owner back up on a freshly opened socket. An err means try again. */ /** Brings the owner back up on a freshly opened socket. An err means try again. */
+2 -2
View File
@@ -1,8 +1,8 @@
import type { EncodingName } from './defs/encodings.ts'; import type { EncodingName } from './defs/encodings.ts';
import type { LogInt } from '@larvit/log';
import type { ParamValue } from './defs/types.ts'; import type { ParamValue } from './defs/types.ts';
import type { PduObject, PduObjectInput } from './pdu.ts'; import type { PduObject, PduObjectInput } from './pdu.ts';
import type { Result } from './result.ts'; import type { Result } from './result.ts';
import type { SmppLog } from './log.ts';
import { consts } from './defs/constants.ts'; import { consts } from './defs/constants.ts';
import { detect } from './defs/encodings.ts'; import { detect } from './defs/encodings.ts';
import { paramText } from './defs/types.ts'; import { paramText } from './defs/types.ts';
@@ -28,7 +28,7 @@ export type SendSmsResult = { err?: Error; pduObjs: PduObject[]; smsIds: string[
/** What sending needs from the session: a concat reference and a way onto the wire. */ /** What sending needs from the session: a concat reference and a way onto the wire. */
export type SendSmsDeps = { export type SendSmsDeps = {
log: LogInt; log: SmppLog;
reference: number; reference: number;
send: (input: PduObjectInput) => Promise<Result<{ pduObj: PduObject }>>; send: (input: PduObjectInput) => Promise<Result<{ pduObj: PduObject }>>;
}; };
+7 -7
View File
@@ -1,8 +1,8 @@
import type { LogInt } from '@larvit/log';
import type { PduObject, TlvInput } from './pdu.ts'; import type { PduObject, TlvInput } from './pdu.ts';
import type { Result, VoidResult } from './result.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 { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import { Session, bindCommands, defaultSystemId } from './session.ts'; import { Session, bindCommands, defaultSystemId } from './session.ts';
import { checkSessionOptions, undeclaredInterfaceVersion } from './session-options.ts'; import { checkSessionOptions, undeclaredInterfaceVersion } from './session-options.ts';
@@ -26,7 +26,7 @@ export type ServerOptions = {
host?: string; host?: string;
idleTimeout?: number; idleTimeout?: number;
interfaceVersion?: number; interfaceVersion?: number;
log?: LogInt; log?: SmppLog;
maxOutstanding?: number; maxOutstanding?: number;
maxOctets?: number; maxOctets?: number;
maxReassembly?: number; maxReassembly?: number;
@@ -54,10 +54,10 @@ const defaults = {
export class SmppServer extends EventEmitter<ServerEvents> { export class SmppServer extends EventEmitter<ServerEvents> {
readonly sessions = new Set<Session>(); readonly sessions = new Set<Session>();
private readonly log: LogInt; private readonly log: SmppLog;
private readonly server: NetServer; private readonly server: NetServer;
constructor(server: NetServer, log: LogInt) { constructor(server: NetServer, log: SmppLog) {
super(); super();
this.log = log; this.log = log;
this.server = server; this.server = server;
@@ -223,7 +223,7 @@ function onConnection(sock: Socket, options: ServerOptions, server: SmppServer):
} }
/** Node reports a rejected handshake as `tlsClientError`, which is never an `error` event. */ /** Node reports a rejected handshake as `tlsClientError`, which is never an `error` event. */
function createSecureListener(tlsOptions: TlsOptions, log: LogInt): TlsServer { function createSecureListener(tlsOptions: TlsOptions, log: SmppLog): TlsServer {
const listener = createTlsServer(tlsOptions); const listener = createTlsServer(tlsOptions);
listener.on('tlsClientError', err => { listener.on('tlsClientError', err => {
@@ -233,7 +233,7 @@ function createSecureListener(tlsOptions: TlsOptions, log: LogInt): TlsServer {
return listener; return listener;
} }
function checkOptions(options: ServerOptions, log: LogInt, port: number): VoidResult { function checkOptions(options: ServerOptions, log: SmppLog, port: number): VoidResult {
const checked = checkSessionOptions(options); const checked = checkSessionOptions(options);
if (checked.err) return { err: checked.err }; if (checked.err) return { err: checked.err };
@@ -258,7 +258,7 @@ function checkOptions(options: ServerOptions, log: LogInt, port: number): VoidRe
function createListener( function createListener(
options: ServerOptions, options: ServerOptions,
log: LogInt, log: SmppLog,
port: number, port: number,
): Result<{ listener: NetServer | TlsServer; useTls: boolean }> { ): Result<{ listener: NetServer | TlsServer; useTls: boolean }> {
const useTls = options.tls !== undefined && options.tls !== false; const useTls = options.tls !== undefined && options.tls !== false;
+2 -2
View File
@@ -1,9 +1,9 @@
import type { Dlr } from './dlr.ts'; import type { Dlr } from './dlr.ts';
import type { LogInt } from '@larvit/log';
import type { MessageDlr } from './dlr-merger.ts'; import type { MessageDlr } from './dlr-merger.ts';
import type { PduObject } from './pdu.ts'; import type { PduObject } from './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 type { SmppLog } from './log.ts';
import type { Sms } from './sms.ts'; import type { Sms } from './sms.ts';
import type { Socket } from 'node:net'; import type { Socket } from 'node:net';
@@ -48,7 +48,7 @@ export type ReconnectOptions = {
export type SessionOptions = { export type SessionOptions = {
enquireLinkInterval?: number | undefined; enquireLinkInterval?: number | undefined;
idleTimeout?: number | undefined; idleTimeout?: number | undefined;
log?: LogInt | undefined; log?: SmppLog | undefined;
maxOctets?: number | undefined; maxOctets?: number | undefined;
maxOutstanding?: number | undefined; maxOutstanding?: number | undefined;
maxReassembly?: number | undefined; maxReassembly?: number | undefined;
+2 -2
View File
@@ -1,11 +1,11 @@
import type { ErrorName } from './defs/errors.ts'; import type { ErrorName } from './defs/errors.ts';
import type { LogInt } from '@larvit/log';
import type { MessageDlr } from './dlr-merger.ts'; import type { MessageDlr } from './dlr-merger.ts';
import type { ParamValue } from './defs/types.ts'; import type { ParamValue } from './defs/types.ts';
import type { PduObject, PduObjectInput, TlvInput } from './pdu.ts'; import type { PduObject, PduObjectInput, TlvInput } from './pdu.ts';
import type { ReconnectOptions, SendOptions, SessionEvents, SessionOptions } from './session-options.ts'; import type { ReconnectOptions, SendOptions, SessionEvents, SessionOptions } from './session-options.ts';
import type { Result, VoidResult } from './result.ts'; import type { Result, VoidResult } from './result.ts';
import type { SendSmsOptions, SendSmsResult } from './send-sms.ts'; import type { SendSmsOptions, SendSmsResult } from './send-sms.ts';
import type { SmppLog } from './log.ts';
import type { Socket } from 'node:net'; import type { Socket } from 'node:net';
import { DlrMerger } from './dlr-merger.ts'; import { DlrMerger } from './dlr-merger.ts';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
@@ -35,7 +35,7 @@ export { bindCommands, defaultSystemId };
export class Session extends EventEmitter<SessionEvents> { export class Session extends EventEmitter<SessionEvents> {
/** Replaced on reconnect, so hold the session rather than this. */ /** Replaced on reconnect, so hold the session rather than this. */
sock: Socket; sock: Socket;
readonly log: LogInt; readonly log: SmppLog;
loggedIn = false; loggedIn = false;
/** What the peer declared when binding: 0x00 if it declared none, undefined before any bind. */ /** What the peer declared when binding: 0x00 if it declared none, undefined before any bind. */
+12 -1
View File
@@ -8,6 +8,7 @@ import type { MessageDlr } from '../src/session.ts';
import type { PduObject, PduObjectInput } from '../src/pdu.ts'; import type { PduObject, PduObjectInput } from '../src/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/send-sms.ts';
import type { SmppLog } from '../src/log.ts';
import type { Sms } from '../src/sms.ts'; import type { Sms } from '../src/sms.ts';
import type { SmppServer } from '../src/server.ts'; import type { SmppServer } from '../src/server.ts';
import { Reassembler, decodeSegments } from '../src/reassembly.ts'; import { Reassembler, decodeSegments } from '../src/reassembly.ts';
@@ -357,12 +358,22 @@ describe('reassembly bounds', () => {
// The UDH is peer-controlled, and the default authenticate() accepts every peer. // The UDH is peer-controlled, and the default authenticate() accepts every peer.
test('refuses a segment whose concatenation metadata cannot be honoured', () => { test('refuses a segment whose concatenation metadata cannot be honoured', () => {
const reassembler = new Reassembler({ log: silentLog, max: 10, now: () => 0, timeout: 60_000 }); const warnings: string[] = [];
const noop = (): void => undefined;
const log: SmppLog = {
debug: noop,
error: noop,
info: noop,
verbose: noop,
warn: msg => { warnings.push(msg); },
};
const reassembler = new Reassembler({ log, max: 10, now: () => 0, timeout: 60_000 });
assert.equal(collect(reassembler, 1, 1, 0), undefined); assert.equal(collect(reassembler, 1, 1, 0), undefined);
assert.equal(collect(reassembler, 2, 0, 3), undefined); assert.equal(collect(reassembler, 2, 0, 3), undefined);
assert.equal(collect(reassembler, 3, 4, 3), undefined); assert.equal(collect(reassembler, 3, 4, 3), undefined);
assert.equal(reassembler.size, 0); assert.equal(reassembler.size, 0);
assert.deepEqual(warnings, Array(3).fill('reassembler - dropping a segment the UDH numbers impossibly'));
}); });
// Parts 1/2 then 2/3 would otherwise complete the stored two-part group, truncating the message. // Parts 1/2 then 2/3 would otherwise complete the stored two-part group, truncating the message.