Let the session own its bind state, written by bound() alone #42
+2
-2
@@ -76,8 +76,8 @@
|
||||
- `cmds.broadcast_sm_resp.tlvMap` is removed; nothing read it.
|
||||
- `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
|
||||
accepted or had accepted with `session.bound(bindType, declaredVersion)`; an assignment to either
|
||||
field now throws a `TypeError`.
|
||||
accepted or had accepted with `session.bound(bindType, declaredVersion)`, which returns `err` for a
|
||||
bind type or version it cannot record; an assignment to either field now throws a `TypeError`.
|
||||
|
||||
## 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
|
||||
under, for `alert_on_msg_delivery` and `failed_broadcast_area_identifier`, which are gone from
|
||||
`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.
|
||||
- **`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).
|
||||
|
||||
@@ -423,10 +423,11 @@ const { err, pduObj } = await session.send({
|
||||
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.
|
||||
- `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
|
||||
end accepted it. A `declaredVersion` that is not a number is none declared. Both fields are
|
||||
read-only.
|
||||
end accepted it, on every link it binds. `declaredVersion` is 0-255, or `undefined` where the peer
|
||||
declared none; anything else, or an unknown `bindType`, returns `err` and records nothing.
|
||||
|
||||
## 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.
|
||||
- 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
|
||||
`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`,
|
||||
`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'}`) };
|
||||
}
|
||||
|
||||
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 });
|
||||
|
||||
return {};
|
||||
|
||||
+2
-2
@@ -177,9 +177,9 @@ async function acceptBind(
|
||||
options: ServerOptions,
|
||||
identity: Record<string, string>,
|
||||
): 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(
|
||||
|
||||
@@ -121,6 +121,29 @@ 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 };
|
||||
|
||||
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 = {
|
||||
/** Receipts of a multipart message can be a working day apart, so the cap does the bounding. */
|
||||
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 { PduObject, PduObjectInput, TlvInputs } from './pdu.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 { SendSmsOptions, SendSmsResult } from './send-sms.ts';
|
||||
import type { SmppLog } from './log.ts';
|
||||
@@ -19,7 +19,7 @@ import { ReconnectLoop } from './reconnect-loop.ts';
|
||||
import { leftOf } from './idle-waiters.ts';
|
||||
import { errorFrom } from './error-from.ts';
|
||||
import { optionalParamsMinVersion } from './defs/constants.ts';
|
||||
import { bindCarries, bindCommands, 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 { refusalAnswer } from './pdu-refusal.ts';
|
||||
import { guardedLog } from './log.ts';
|
||||
@@ -58,7 +58,7 @@ export class Session extends EventEmitter<SessionEvents> {
|
||||
linkEnd: LinkEnd = 'esme';
|
||||
userData: unknown = undefined;
|
||||
|
||||
private bind: { as: BindType; peerVersion: number } | undefined = undefined;
|
||||
private bind: SessionBind | undefined = undefined;
|
||||
|
||||
private readonly concatReference = new ConcatReference();
|
||||
private readonly dlrMerger: DlrMerger;
|
||||
@@ -161,11 +161,13 @@ export class Session extends EventEmitter<SessionEvents> {
|
||||
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. */
|
||||
bound(bindType: BindType, declaredVersion: unknown): void {
|
||||
const peerVersion = typeof declaredVersion === 'number' ? declaredVersion : undeclaredInterfaceVersion;
|
||||
/** Records a bind this link accepted or had accepted, until the next one. */
|
||||
bound(bindType: BindType, declaredVersion: unknown): VoidResult {
|
||||
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. */
|
||||
@@ -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. */
|
||||
acceptsOptionalParams(): boolean {
|
||||
return this.peerInterfaceVersion === undefined
|
||||
|| this.peerInterfaceVersion >= optionalParamsMinVersion;
|
||||
return this.peerInterfaceVersion === undefined || this.peerInterfaceVersion >= optionalParamsMinVersion;
|
||||
}
|
||||
|
||||
/** 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.
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user