Refuse a bind bound() cannot record, and document it for a 0.4.0 consumer
Mirror / push (push) Successful in 6s
Test / lint (pull_request) Successful in 23s
Test / test (18) (pull_request) Successful in 31s
Test / test (20) (pull_request) Successful in 45s
Test / test (22) (pull_request) Successful in 32s
Test / test (24) (pull_request) Successful in 31s
Test / test (26) (pull_request) Successful in 32s
Mirror / push (push) Successful in 6s
Test / lint (pull_request) Successful in 23s
Test / test (18) (pull_request) Successful in 31s
Test / test (20) (pull_request) Successful in 45s
Test / test (22) (pull_request) Successful in 32s
Test / test (24) (pull_request) Successful in 31s
Test / test (26) (pull_request) Successful in 32s
This commit is contained in:
+2
-2
@@ -76,8 +76,8 @@
|
|||||||
- `cmds.broadcast_sm_resp.tlvMap` is removed; nothing read it.
|
- `cmds.broadcast_sm_resp.tlvMap` is removed; nothing read it.
|
||||||
- `session.boundAs` and `session.peerInterfaceVersion` are read-only, and `session.loggedIn` is
|
- `session.boundAs` and `session.peerInterfaceVersion` are read-only, and `session.loggedIn` is
|
||||||
removed: read `session.boundAs !== undefined`. A session you wire yourself records the bind it
|
removed: read `session.boundAs !== undefined`. A session you wire yourself records the bind it
|
||||||
accepted or had accepted with `session.bound(bindType, declaredVersion)`; an assignment to either
|
accepted or had accepted with `session.bound(bindType, declaredVersion)`, which returns `err` for a
|
||||||
field now throws a `TypeError`.
|
bind type or version it cannot record; an assignment to either field now throws a `TypeError`.
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ shape is the same, connect, send, listen for delivery reports, with callbacks re
|
|||||||
Write `alert_on_message_delivery` and `broadcast_area_identifier`, the names they read back
|
Write `alert_on_message_delivery` and `broadcast_area_identifier`, the names they read back
|
||||||
under, for `alert_on_msg_delivery` and `failed_broadcast_area_identifier`, which are gone from
|
under, for `alert_on_msg_delivery` and `failed_broadcast_area_identifier`, which are gone from
|
||||||
`tlvs` too.
|
`tlvs` too.
|
||||||
|
- **`session.loggedIn` is gone**: read `session.boundAs !== undefined`. `boundAs` and
|
||||||
|
`peerInterfaceVersion` are read-only; a session you construct yourself records a bind with
|
||||||
|
`session.bound()`.
|
||||||
- **The `error` event is `sessionError`**, and `serverError` on the server handle.
|
- **The `error` event is `sessionError`**, and `serverError` on the server handle.
|
||||||
- **`log`** takes any object with `debug`, `error`, `info`, `verbose` and `warn` methods instead of
|
- **`log`** takes any object with `debug`, `error`, `info`, `verbose` and `warn` methods instead of
|
||||||
a `larvitutils` one, and is silent by default: [README](README.md#logging).
|
a `larvitutils` one, and is silent by default: [README](README.md#logging).
|
||||||
|
|||||||
@@ -423,10 +423,11 @@ const { err, pduObj } = await session.send({
|
|||||||
a `send()` you build is passed through as written, so check it yourself.
|
a `send()` you build is passed through as written, so check it yourself.
|
||||||
- `peerInterfaceVersion`: the version the peer declared, `0x00` if none, `undefined` before any bind.
|
- `peerInterfaceVersion`: the version the peer declared, `0x00` if none, `undefined` before any bind.
|
||||||
- `bindAllows(cmdName)` and `boundAs`: what the bind direction carries: [Bind direction](#bind-direction).
|
- `bindAllows(cmdName)` and `boundAs`: what the bind direction carries: [Bind direction](#bind-direction).
|
||||||
- Both hold through a reconnect's gap: the loop binds again as before.
|
- `boundAs` and `peerInterfaceVersion` are read-only, and hold through a reconnect's gap until the
|
||||||
|
next `bound()`.
|
||||||
- `bound(bindType, declaredVersion)`: how a session you construct yourself records a bind, whichever
|
- `bound(bindType, declaredVersion)`: how a session you construct yourself records a bind, whichever
|
||||||
end accepted it. A `declaredVersion` that is not a number is none declared. Both fields are
|
end accepted it, on every link it binds. `declaredVersion` is 0-255, or `undefined` where the peer
|
||||||
read-only.
|
declared none; anything else, or an unknown `bindType`, returns `err` and records nothing.
|
||||||
|
|
||||||
## Receiving in depth
|
## Receiving in depth
|
||||||
|
|
||||||
@@ -544,7 +545,8 @@ if (err) throw err;
|
|||||||
the link. Guard on the command name, as above, and a failing hook costs only its own request.
|
the link. Guard on the command name, as above, and a failing hook costs only its own request.
|
||||||
- A `Session` you construct yourself takes the same hook as a session option, and that is where a
|
- A `Session` you construct yourself takes the same hook as a session option, and that is where a
|
||||||
peer's bind gets accepted, since a hand-wired session has no bind handling of its own: call
|
peer's bind gets accepted, since a hand-wired session has no bind handling of its own: call
|
||||||
`session.bound(bindType, pduObj.params.interface_version)` before answering it.
|
`session.bound(bindType, pduObj.params.interface_version)` before answering it, and refuse the bind
|
||||||
|
with `ESME_RBINDFAIL` where that returns `err`.
|
||||||
|
|
||||||
**`sendDlr()`** takes `SCHEDULED`, `ENROUTE`, `DELIVERED`, `EXPIRED`, `DELETED`, `UNDELIVERABLE`,
|
**`sendDlr()`** takes `SCHEDULED`, `ENROUTE`, `DELIVERED`, `EXPIRED`, `DELETED`, `UNDELIVERABLE`,
|
||||||
`ACCEPTED`, `UNKNOWN`, `REJECTED` or `SKIPPED`. The first two go out as intermediate delivery
|
`ACCEPTED`, `UNKNOWN`, `REJECTED` or `SKIPPED`. The first two go out as intermediate delivery
|
||||||
|
|||||||
+4
-1
@@ -174,7 +174,10 @@ async function bind(session: Session, options: ClientOptions): Promise<VoidResul
|
|||||||
return { err: new Error(`Remote host refused login: ${sent.pduObj.cmdStatus ?? 'unknown'}`) };
|
return { err: new Error(`Remote host refused login: ${sent.pduObj.cmdStatus ?? 'unknown'}`) };
|
||||||
}
|
}
|
||||||
|
|
||||||
session.bound(bindType, sent.pduObj.tlvs.sc_interface_version?.tagValue);
|
const recorded = session.bound(bindType, sent.pduObj.tlvs.sc_interface_version?.tagValue);
|
||||||
|
|
||||||
|
if (recorded.err) return recorded;
|
||||||
|
|
||||||
session.log.info('client - bound', { bindType, systemId });
|
session.log.info('client - bound', { bindType, systemId });
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
+2
-2
@@ -177,9 +177,9 @@ async function acceptBind(
|
|||||||
options: ServerOptions,
|
options: ServerOptions,
|
||||||
identity: Record<string, string>,
|
identity: Record<string, string>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
session.bound(bindType, pduObj.params.interface_version);
|
const recorded = session.bound(bindType, pduObj.params.interface_version);
|
||||||
|
|
||||||
await session.sendReturn(pduObj, 'ESME_ROK', identity, bindRespTlvs(session, options));
|
await session.sendReturn(pduObj, recorded.err ? 'ESME_RBINDFAIL' : 'ESME_ROK', identity, bindRespTlvs(session, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onBind(
|
async function onBind(
|
||||||
|
|||||||
@@ -121,6 +121,29 @@ export const defaultSystemId = '';
|
|||||||
/** SMPP 3.4: a peer that declares no version at all is one from before optional parameters. */
|
/** SMPP 3.4: a peer that declares no version at all is one from before optional parameters. */
|
||||||
export const undeclaredInterfaceVersion = 0x00;
|
export const undeclaredInterfaceVersion = 0x00;
|
||||||
|
|
||||||
|
export type SessionBind = { as: BindType; peerVersion: number };
|
||||||
|
|
||||||
|
const bindTypes: readonly string[] = ['receiver', 'transceiver', 'transmitter'];
|
||||||
|
|
||||||
|
function isBindType(value: unknown): value is BindType {
|
||||||
|
return typeof value === 'string' && bindTypes.includes(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 ${bindTypes.join(', ')}, got ${namedValue(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 0-255, or undefined where the peer declared none, got ${namedValue(declaredVersion)}`) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { bind: { as: bindType, peerVersion: declaredVersion } };
|
||||||
|
}
|
||||||
|
|
||||||
export const defaults = {
|
export const defaults = {
|
||||||
/** 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,
|
||||||
|
|||||||
+10
-9
@@ -4,7 +4,7 @@ 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, TlvInputs } from './pdu.ts';
|
import type { PduObject, PduObjectInput, TlvInputs } from './pdu.ts';
|
||||||
import type { PduRefusedError } from './pdu-refusal.ts';
|
import type { PduRefusedError } from './pdu-refusal.ts';
|
||||||
import type { BindType, CloseOptions, LinkEnd, OnRequest, ReconnectOptions, SendOptions, SessionEvents, SessionOptions } from './session-options.ts';
|
import type { BindType, CloseOptions, LinkEnd, OnRequest, ReconnectOptions, SendOptions, SessionBind, 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 { SmppLog } from './log.ts';
|
||||||
@@ -19,7 +19,7 @@ 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 './error-from.ts';
|
||||||
import { optionalParamsMinVersion } from './defs/constants.ts';
|
import { optionalParamsMinVersion } from './defs/constants.ts';
|
||||||
import { bindCarries, bindCommands, defaultSystemId, defaults, undeclaredInterfaceVersion } from './session-options.ts';
|
import { bindCarries, bindCommands, checkedBind, defaultSystemId, defaults } from './session-options.ts';
|
||||||
import { isResp, objToPdu, pduReturn } from './pdu.ts';
|
import { isResp, objToPdu, pduReturn } from './pdu.ts';
|
||||||
import { refusalAnswer } from './pdu-refusal.ts';
|
import { refusalAnswer } from './pdu-refusal.ts';
|
||||||
import { guardedLog } from './log.ts';
|
import { guardedLog } from './log.ts';
|
||||||
@@ -58,7 +58,7 @@ export class Session extends EventEmitter<SessionEvents> {
|
|||||||
linkEnd: LinkEnd = 'esme';
|
linkEnd: LinkEnd = 'esme';
|
||||||
userData: unknown = undefined;
|
userData: unknown = undefined;
|
||||||
|
|
||||||
private bind: { as: BindType; peerVersion: number } | undefined = undefined;
|
private bind: SessionBind | undefined = undefined;
|
||||||
|
|
||||||
private readonly concatReference = new ConcatReference();
|
private readonly concatReference = new ConcatReference();
|
||||||
private readonly dlrMerger: DlrMerger;
|
private readonly dlrMerger: DlrMerger;
|
||||||
@@ -161,11 +161,13 @@ export class Session extends EventEmitter<SessionEvents> {
|
|||||||
return this.bind?.peerVersion;
|
return this.bind?.peerVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Records a bind this link accepted or had accepted, until the next one. A version that is not a number is none. */
|
/** Records a bind this link accepted or had accepted, until the next one. */
|
||||||
bound(bindType: BindType, declaredVersion: unknown): void {
|
bound(bindType: BindType, declaredVersion: unknown): VoidResult {
|
||||||
const peerVersion = typeof declaredVersion === 'number' ? declaredVersion : undeclaredInterfaceVersion;
|
const checked = checkedBind(bindType, declaredVersion);
|
||||||
|
|
||||||
this.bind = { as: bindType, peerVersion };
|
if (!checked.err) this.bind = checked.bind;
|
||||||
|
|
||||||
|
return checked.err ? { err: checked.err } : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this session's bind direction carries a command. Consulted by the library's senders. */
|
/** Whether this session's bind direction carries a command. Consulted by the library's senders. */
|
||||||
@@ -175,8 +177,7 @@ export class Session extends EventEmitter<SessionEvents> {
|
|||||||
|
|
||||||
/** SMPP 3.4 forbids sending optional parameters to a peer that declared an older version. */
|
/** SMPP 3.4 forbids sending optional parameters to a peer that declared an older version. */
|
||||||
acceptsOptionalParams(): boolean {
|
acceptsOptionalParams(): boolean {
|
||||||
return this.peerInterfaceVersion === undefined
|
return this.peerInterfaceVersion === undefined || this.peerInterfaceVersion >= optionalParamsMinVersion;
|
||||||
|| this.peerInterfaceVersion >= optionalParamsMinVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sends a request and resolves with the peer's response. */
|
/** Sends a request and resolves with the peer's response. */
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ describe('bind', () => {
|
|||||||
|
|
||||||
// The mistakes an untyped caller makes: the TLV object for its value, a version past int8, a typo.
|
// The mistakes an untyped caller makes: the TLV object for its value, a version past int8, a typo.
|
||||||
for (const [bindType, declared] of [['receiver', { tagValue: 0x34 }], ['receiver', 0x100], ['receiver', 3.4], ['tx', 0x34]]) {
|
for (const [bindType, declared] of [['receiver', { tagValue: 0x34 }], ['receiver', 0x100], ['receiver', 3.4], ['tx', 0x34]]) {
|
||||||
const refused: unknown = Reflect.apply(session.bound, session, [bindType, declared]);
|
const refused: unknown = Reflect.apply(Reflect.get(session, 'bound'), session, [bindType, declared]);
|
||||||
|
|
||||||
assert.ok(refused && typeof refused === 'object' && 'err' in refused && refused.err instanceof Error);
|
assert.ok(refused && typeof refused === 'object' && 'err' in refused && refused.err instanceof Error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user