Type each known TLV's value by its tag, on read and on write #30
@@ -58,6 +58,11 @@
|
|||||||
`Buffer.isBuffer()` or `typeof` check written for 0.5.0 now reads it as absent. Read `tagValue[0]`
|
`Buffer.isBuffer()` or `typeof` check written for 0.5.0 now reads it as absent. Read `tagValue[0]`
|
||||||
for the first occurrence. `objToPdu()`, `session.send()` and `session.sendReturn()` take
|
for the first occurrence. `objToPdu()`, `session.send()` and `session.sendReturn()` take
|
||||||
`{ tagValue: [value] }` for them and refuse a lone value before anything goes out.
|
`{ tagValue: [value] }` for them and refuse a lone value before anything goes out.
|
||||||
|
- `pduObj.tlvs` and every `tlvs` input are typed per tag, as `Tlvs` and `TlvInputs`:
|
||||||
|
`receipted_message_id` reads as a `string`, `callback_num` as a `Buffer[]`, and a value of the wrong
|
||||||
|
type for its tag fails to compile. An unknown tag reads as a `Buffer` under its decimal id. Code
|
||||||
|
that narrowed `tagValue` with `typeof` still compiles; a `Record<string, Tlv>` annotation does not,
|
||||||
|
so use `Tlvs`.
|
||||||
- `cmds.broadcast_sm_resp.tlvMap` is removed; nothing read it.
|
- `cmds.broadcast_sm_resp.tlvMap` is removed; nothing read it.
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|||||||
@@ -613,6 +613,9 @@ if (isCommand(pduObj, 'submit_sm')) {
|
|||||||
and hands back the UDH where the PDU carries one.
|
and hands back the UDH where the PDU carries one.
|
||||||
- `concatOf(pduObj)`: the `part`, `total` and `reference` a PDU declares and the `spelling` that
|
- `concatOf(pduObj)`: the `part`, `total` and `reference` a PDU declares and the `spelling` that
|
||||||
carried them, `'udh'` or `'sar'`, or `undefined` for a whole message.
|
carried them, `'udh'` or `'sar'`, or `undefined` for a whole message.
|
||||||
|
- `pduObj.tlvs` is typed per tag, as `Tlvs`: `receipted_message_id` a string, `message_state` a
|
||||||
|
number, `message_payload` a `Buffer`. A tag the table does not define is a `Buffer` keyed by its
|
||||||
|
decimal id, `tlvs['5142']`.
|
||||||
- `callback_num`, `callback_num_atag`, `callback_num_pres_ind`, `broadcast_area_identifier` and
|
- `callback_num`, `callback_num_atag`, `callback_num_pres_ind`, `broadcast_area_identifier` and
|
||||||
`broadcast_error_status` may repeat in one PDU, so each reads as an array of every occurrence in wire
|
`broadcast_error_status` may repeat in one PDU, so each reads as an array of every occurrence in wire
|
||||||
order: `number[]` for `callback_num_pres_ind` and `broadcast_error_status`, `Buffer[]` for the rest.
|
order: `number[]` for `callback_num_pres_ind` and `broadcast_error_status`, `Buffer[]` for the rest.
|
||||||
@@ -627,6 +630,8 @@ if (isCommand(pduObj, 'submit_sm')) {
|
|||||||
naming the character, its code point and where it is.
|
naming the character, its code point and where it is.
|
||||||
- Every text field is latin1: addresses, `system_id`, `message_id`, `service_type` and the C-Octet
|
- Every text field is latin1: addresses, `system_id`, `message_id`, `service_type` and the C-Octet
|
||||||
String TLVs. A character past `U+00FF` is refused, as is a `U+0000` in a C-Octet String.
|
String TLVs. A character past `U+00FF` is refused, as is a `U+0000` in a C-Octet String.
|
||||||
|
- `tlvs` is typed per tag, as `TlvInputs`, so `{ message_state: { tagValue: 'ENROUTE' } }` fails to
|
||||||
|
compile. A tag the table does not define takes a `tagId`.
|
||||||
- The five repeatable TLVs take an array, written as one TLV per element; a lone value or an empty
|
- The five repeatable TLVs take an array, written as one TLV per element; a lone value or an empty
|
||||||
array is refused.
|
array is refused.
|
||||||
- A `Buffer` goes out exactly as given under any `data_coding`: binary payloads, hand-built user
|
- A `Buffer` goes out exactly as given under any `data_coding`: binary payloads, hand-built user
|
||||||
|
|||||||
+73
-21
@@ -79,15 +79,18 @@ const specs = tlvSpecs({
|
|||||||
its_session_info: { id: 0x1383, tag: 'its_session_info', type: tlv.buffer },
|
its_session_info: { id: 0x1383, tag: 'its_session_info', type: tlv.buffer },
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TlvName = keyof typeof specs;
|
type Specs = typeof specs;
|
||||||
|
|
||||||
export const tlvs: Record<TlvName, TlvDefinition> & Record<string, TlvDefinition> = {
|
export type TlvName = keyof Specs;
|
||||||
...specs,
|
|
||||||
// Alternate spellings; the definition behind each keeps its canonical name.
|
// Alternate spellings; the definition behind each keeps its canonical name.
|
||||||
|
const alternates = {
|
||||||
alert_on_msg_delivery: specs.alert_on_message_delivery,
|
alert_on_msg_delivery: specs.alert_on_message_delivery,
|
||||||
failed_broadcast_area_identifier: specs.broadcast_area_identifier,
|
failed_broadcast_area_identifier: specs.broadcast_area_identifier,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const tlvs: Record<TlvName, TlvDefinition> & Record<string, TlvDefinition> = { ...specs, ...alternates };
|
||||||
|
|
||||||
export const tlvsById: Record<number, TlvDefinition> = {};
|
export const tlvsById: Record<number, TlvDefinition> = {};
|
||||||
|
|
||||||
for (const definition of Object.values<TlvDefinition>(specs)) {
|
for (const definition of Object.values<TlvDefinition>(specs)) {
|
||||||
@@ -97,11 +100,28 @@ for (const definition of Object.values<TlvDefinition>(specs)) {
|
|||||||
/** Fallback for tags this table does not know: keep the raw octets. */
|
/** Fallback for tags this table does not know: keep the raw octets. */
|
||||||
export const tlvDefault: WireType<Buffer> = tlv.buffer;
|
export const tlvDefault: WireType<Buffer> = tlv.buffer;
|
||||||
|
|
||||||
export type Tlv = {
|
type Repeated<K extends TlvName, V> = Specs[K] extends { multiple: true } ? V[] : V;
|
||||||
tagId: number;
|
|
||||||
tagName: string | undefined;
|
type WireValue<K extends TlvName> = Specs[K]['type']['default'];
|
||||||
tagValue: TlvValue;
|
|
||||||
};
|
export type TlvReadValue<K extends TlvName> = Repeated<K, WireValue<K>>;
|
||||||
|
|
||||||
|
/** A lone text field also takes a number, and a lone octet field text, which goes out as latin1. */
|
||||||
|
export type TlvWriteValue<K extends TlvName> = Specs[K] extends { multiple: true } ? TlvReadValue<K>
|
||||||
|
: WireValue<K> extends number ? number
|
||||||
|
: WireValue<K> extends string ? number | string
|
||||||
|
: Buffer | number | string;
|
||||||
|
|
||||||
|
type OneTlv<K extends TlvName> = { tagId: number; tagName: K; tagValue: TlvReadValue<K> };
|
||||||
|
|
||||||
|
export type Tlv<K extends TlvName = TlvName> = { [N in K]: OneTlv<N> }[K];
|
||||||
|
|
||||||
|
/** A tag the TLV table does not define, keyed by its decimal id. */
|
||||||
|
export type UnknownTlv = { tagId: number; tagName: undefined; tagValue: Buffer };
|
||||||
|
|
||||||
|
type KnownTlvs = { [K in TlvName]?: OneTlv<K> };
|
||||||
|
|
||||||
|
export type Tlvs = KnownTlvs & Record<`${number}`, UnknownTlv>;
|
||||||
|
|
||||||
export type TlvInput = {
|
export type TlvInput = {
|
||||||
/** Resolved from the record key; pass it for a tag the TLV table does not define. */
|
/** Resolved from the record key; pass it for a tag the TLV table does not define. */
|
||||||
@@ -109,6 +129,14 @@ export type TlvInput = {
|
|||||||
tagValue: TlvValue;
|
tagValue: TlvValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Alternate = keyof typeof alternates;
|
||||||
|
|
||||||
|
type Canonical<K extends Alternate | TlvName> = K extends Alternate ? (typeof alternates)[K]['tag'] : K;
|
||||||
|
|
||||||
|
export type TlvInputs = {
|
||||||
|
[K in Alternate | TlvName]?: { tagId?: number | undefined; tagValue: TlvWriteValue<Canonical<K>> };
|
||||||
|
} & Record<string, TlvInput>;
|
||||||
|
|
||||||
export function tagIdOf(name: string, input: TlvInput): Result<{ tagId: number }> {
|
export function tagIdOf(name: string, input: TlvInput): Result<{ tagId: number }> {
|
||||||
const tagId = input.tagId ?? tlvs[name]?.id;
|
const tagId = input.tagId ?? tlvs[name]?.id;
|
||||||
|
|
||||||
@@ -198,10 +226,38 @@ function readTlv(pdu: Buffer, offset: number): Result<{ octets: number; occurren
|
|||||||
return { occurrence: { definition, tagId, value: read.value }, octets: 4 + tagLength };
|
return { occurrence: { definition, tagId, value: read.value }, octets: 4 + tagLength };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTlvName(name: string): name is TlvName {
|
||||||
|
return Object.hasOwn(specs, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readsAs(definition: TlvDefinition, value: unknown): boolean {
|
||||||
|
const kind = definition.type.default;
|
||||||
|
const fits = (one: unknown): boolean => typeof one === typeof kind && Buffer.isBuffer(one) === Buffer.isBuffer(kind);
|
||||||
|
|
||||||
|
return definition.multiple === true ? Array.isArray(value) && value.every(fits) : fits(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTlvShape(tlv: unknown): tlv is { tagId: number; tagName: unknown; tagValue: unknown } {
|
||||||
|
return typeof tlv === 'object' && tlv !== null && 'tagId' in tlv && typeof tlv.tagId === 'number'
|
||||||
|
&& 'tagName' in tlv && 'tagValue' in tlv;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTlv(key: string, tlv: unknown): boolean {
|
||||||
|
if (!isTlvShape(tlv)) return false;
|
||||||
|
if (tlv.tagName === undefined) return /^\d+$/.test(key) && Buffer.isBuffer(tlv.tagValue);
|
||||||
|
|
||||||
|
return tlv.tagName === key && isTlvName(key) && readsAs(specs[key], tlv.tagValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Every entry keyed by its tag name, or an unknown tag by its decimal id, holding what its table type reads. */
|
||||||
|
export function isTlvs(record: Record<string, unknown>): record is Tlvs {
|
||||||
|
return Object.entries(record).every(([key, tlv]) => isTlv(key, tlv));
|
||||||
|
}
|
||||||
|
|
||||||
/** Keyed by tag name, a repeatable tag listing every occurrence in wire order and any other keeping its last. */
|
/** Keyed by tag name, a repeatable tag listing every occurrence in wire order and any other keeping its last. */
|
||||||
function keyedTlvs(occurrences: Occurrence[]): Record<string, Tlv> {
|
function keyedTlvs(occurrences: Occurrence[]): Result<{ tlvs: Tlvs }> {
|
||||||
const repeated = new Map<string, { tagId: number; values: Occurrence['value'][] }>();
|
const repeated = new Map<string, { tagId: number; values: Occurrence['value'][] }>();
|
||||||
const tlvs: Record<string, Tlv> = {};
|
const tlvs: Record<string, unknown> = {};
|
||||||
|
|
||||||
for (const { definition, tagId, value } of occurrences) {
|
for (const { definition, tagId, value } of occurrences) {
|
||||||
const key = definition?.tag ?? tagId.toString();
|
const key = definition?.tag ?? tagId.toString();
|
||||||
@@ -217,19 +273,13 @@ function keyedTlvs(occurrences: Occurrence[]): Record<string, Tlv> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, { tagId, values }] of repeated) {
|
for (const [key, { tagId, values }] of repeated) {
|
||||||
const buffers = values.filter(value => Buffer.isBuffer(value));
|
tlvs[key] = { tagId, tagName: key, tagValue: values };
|
||||||
|
|
||||||
tlvs[key] = {
|
|
||||||
tagId,
|
|
||||||
tagName: key,
|
|
||||||
tagValue: buffers.length === values.length ? buffers : values.filter(value => typeof value === 'number'),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tlvs;
|
return isTlvs(tlvs) ? { tlvs } : { err: new Error('A TLV did not read as its table type') };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseTlvs(pdu: Buffer, start: number): Result<{ offset: number; tlvs: Record<string, Tlv> }> {
|
export function parseTlvs(pdu: Buffer, start: number): Result<{ offset: number; tlvs: Tlvs }> {
|
||||||
const occurrences: Occurrence[] = [];
|
const occurrences: Occurrence[] = [];
|
||||||
let offset = start;
|
let offset = start;
|
||||||
|
|
||||||
@@ -242,5 +292,7 @@ export function parseTlvs(pdu: Buffer, start: number): Result<{ offset: number;
|
|||||||
offset += read.octets;
|
offset += read.octets;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { offset, tlvs: keyedTlvs(occurrences) };
|
const keyed = keyedTlvs(occurrences);
|
||||||
|
|
||||||
|
return keyed.err ? { err: keyed.err } : { offset, tlvs: keyed.tlvs };
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -64,10 +64,10 @@ export type { CommandName, PduParams, PduParamsInput } from './defs/commands.ts'
|
|||||||
export type { ConstGroup, MessageState, SubmitMessagingMode } from './defs/constants.ts';
|
export type { ConstGroup, MessageState, SubmitMessagingMode } from './defs/constants.ts';
|
||||||
export type { Encoding, EncodingName, Unencodable } from './defs/encodings.ts';
|
export type { Encoding, EncodingName, Unencodable } from './defs/encodings.ts';
|
||||||
export type { ErrorName } from './defs/errors.ts';
|
export type { ErrorName } from './defs/errors.ts';
|
||||||
export type { PduObject, PduObjectInput, TlvInput } from './pdu.ts';
|
export type { PduObject, PduObjectInput, TlvInput, TlvInputs } from './pdu.ts';
|
||||||
export type { PduHeader } from './pdu-refusal.ts';
|
export type { PduHeader } from './pdu-refusal.ts';
|
||||||
export type { SplitOptions } from './message.ts';
|
export type { SplitOptions } from './message.ts';
|
||||||
export type { Tlv, TlvDefinition, TlvName } from './defs/tlvs.ts';
|
export type { Tlv, TlvDefinition, TlvName, TlvReadValue, TlvWriteValue, Tlvs, UnknownTlv } from './defs/tlvs.ts';
|
||||||
export type { DestAddress, ParamValue, TlvValue, UnsuccessSme, WireType } from './defs/types.ts';
|
export type { DestAddress, ParamValue, TlvValue, UnsuccessSme, WireType } from './defs/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. */
|
||||||
|
|||||||
+6
-6
@@ -3,7 +3,7 @@ import type { ErrorName } from './defs/errors.ts';
|
|||||||
import type { ParamValue } from './defs/types.ts';
|
import type { ParamValue } from './defs/types.ts';
|
||||||
import type { PduHeader } from './pdu-refusal.ts';
|
import type { PduHeader } from './pdu-refusal.ts';
|
||||||
import type { Result, VoidResult } from './result.ts';
|
import type { Result, VoidResult } from './result.ts';
|
||||||
import type { Tlv, TlvInput } from './defs/tlvs.ts';
|
import type { TlvInput, TlvInputs, Tlvs } from './defs/tlvs.ts';
|
||||||
import { PduRefusedError, framingRefusal } from './pdu-refusal.ts';
|
import { PduRefusedError, framingRefusal } from './pdu-refusal.ts';
|
||||||
import { cmds, commandNameById, respNameFor } from './defs/commands.ts';
|
import { cmds, commandNameById, respNameFor } from './defs/commands.ts';
|
||||||
import { hasUdh } from './defs/constants.ts';
|
import { hasUdh } from './defs/constants.ts';
|
||||||
@@ -23,7 +23,7 @@ export type PduObjectInput<C extends CommandName = CommandName> = {
|
|||||||
cmdStatus?: ErrorName;
|
cmdStatus?: ErrorName;
|
||||||
params?: PduParamsInput<C>;
|
params?: PduParamsInput<C>;
|
||||||
seqNr?: number;
|
seqNr?: number;
|
||||||
tlvs?: Record<string, TlvInput> | undefined;
|
tlvs?: TlvInputs | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,10 +43,10 @@ export type PduObject = {
|
|||||||
* the peer put in `message_payload` is not here; `messageOctets()` is what reads either.
|
* the peer put in `message_payload` is not here; `messageOctets()` is what reads either.
|
||||||
*/
|
*/
|
||||||
shortMessageOctets: Buffer | undefined;
|
shortMessageOctets: Buffer | undefined;
|
||||||
tlvs: Record<string, Tlv>;
|
tlvs: Tlvs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type { TlvInput };
|
export type { TlvInput, TlvInputs };
|
||||||
|
|
||||||
const respBit = 0x80000000;
|
const respBit = 0x80000000;
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ function readOptionalParams(
|
|||||||
pdu: Buffer,
|
pdu: Buffer,
|
||||||
start: number,
|
start: number,
|
||||||
afterShortMessage: boolean,
|
afterShortMessage: boolean,
|
||||||
): Result<{ tlvs: Record<string, Tlv> }> {
|
): Result<{ tlvs: Tlvs }> {
|
||||||
const plain = parseTlvs(pdu, start);
|
const plain = parseTlvs(pdu, start);
|
||||||
|
|
||||||
if (!plain.err && plain.offset === pdu.length) return { tlvs: plain.tlvs };
|
if (!plain.err && plain.offset === pdu.length) return { tlvs: plain.tlvs };
|
||||||
@@ -418,7 +418,7 @@ export function pduReturn(
|
|||||||
pdu: Buffer | PduObject,
|
pdu: Buffer | PduObject,
|
||||||
status: ErrorName = 'ESME_ROK',
|
status: ErrorName = 'ESME_ROK',
|
||||||
params: Record<string, ParamValue> = {},
|
params: Record<string, ParamValue> = {},
|
||||||
tlvs?: Record<string, TlvInput>,
|
tlvs?: TlvInputs,
|
||||||
): Result<{ buffer: Buffer }> {
|
): Result<{ buffer: Buffer }> {
|
||||||
if (Buffer.isBuffer(pdu)) {
|
if (Buffer.isBuffer(pdu)) {
|
||||||
const parsed = pduToObj(pdu);
|
const parsed = pduToObj(pdu);
|
||||||
|
|||||||
+3
-3
@@ -1,12 +1,12 @@
|
|||||||
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 { Tlv } from './defs/tlvs.ts';
|
|
||||||
import { detachedTlv, tlvOctets } from './defs/types.ts';
|
import { detachedTlv, tlvOctets } from './defs/types.ts';
|
||||||
|
import { isTlvs } from './defs/tlvs.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 {
|
||||||
const params: Record<string, ParamValue> = {};
|
const params: Record<string, ParamValue> = {};
|
||||||
const tlvs: Record<string, Tlv> = {};
|
const tlvs: Record<string, unknown> = {};
|
||||||
|
|
||||||
for (const [name, value] of Object.entries(pduObj.params)) {
|
for (const [name, value] of Object.entries(pduObj.params)) {
|
||||||
params[name] = Buffer.isBuffer(value) ? Buffer.from(value) : value;
|
params[name] = Buffer.isBuffer(value) ? Buffer.from(value) : value;
|
||||||
@@ -21,7 +21,7 @@ export function detach(pduObj: PduObject): PduObject {
|
|||||||
? params.short_message
|
? params.short_message
|
||||||
: pduObj.shortMessageOctets && Buffer.from(pduObj.shortMessageOctets);
|
: pduObj.shortMessageOctets && Buffer.from(pduObj.shortMessageOctets);
|
||||||
|
|
||||||
return { ...pduObj, params, shortMessageOctets: octets, tlvs };
|
return { ...pduObj, params, shortMessageOctets: octets, tlvs: isTlvs(tlvs) ? tlvs : pduObj.tlvs };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measured heap beyond the octets, so a PDU of empty fields or empty TLVs is not free.
|
// Measured heap beyond the octets, so a PDU of empty fields or empty TLVs is not free.
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
import type { CloseOptions, OnRequest } from './session-options.ts';
|
import type { CloseOptions, OnRequest } from './session-options.ts';
|
||||||
import type { PduObject, TlvInput } from './pdu.ts';
|
import type { PduObject, TlvInputs } 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';
|
||||||
@@ -162,7 +162,7 @@ async function authenticate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** An ESME reads a missing sc_interface_version as this SMSC having none. */
|
/** An ESME reads a missing sc_interface_version as this SMSC having none. */
|
||||||
function bindRespTlvs(session: Session, options: ServerOptions): Record<string, TlvInput> | undefined {
|
function bindRespTlvs(session: Session, options: ServerOptions): TlvInputs | undefined {
|
||||||
if (!session.acceptsOptionalParams()) return undefined;
|
if (!session.acceptsOptionalParams()) return undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import type { ErrorName } from './defs/errors.ts';
|
import type { ErrorName } from './defs/errors.ts';
|
||||||
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, TlvInputs } from './pdu.ts';
|
||||||
import type { PduRefusedError } from './pdu-refusal.ts';
|
import type { PduRefusedError } from './pdu-refusal.ts';
|
||||||
import type { BindType, CloseOptions, LinkEnd, ReconnectOptions, SendOptions, SessionEvents, SessionOptions } from './session-options.ts';
|
import type { BindType, CloseOptions, LinkEnd, ReconnectOptions, SendOptions, SessionEvents, SessionOptions } from './session-options.ts';
|
||||||
import type { Result, VoidResult } from './result.ts';
|
import type { Result, VoidResult } from './result.ts';
|
||||||
@@ -175,7 +175,7 @@ export class Session extends EventEmitter<SessionEvents> {
|
|||||||
pdu: PduObject,
|
pdu: PduObject,
|
||||||
status: ErrorName = 'ESME_ROK',
|
status: ErrorName = 'ESME_ROK',
|
||||||
params: Record<string, ParamValue> = {},
|
params: Record<string, ParamValue> = {},
|
||||||
tlvs?: Record<string, TlvInput>,
|
tlvs?: TlvInputs,
|
||||||
): Promise<VoidResult> {
|
): Promise<VoidResult> {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
this.answer(pduReturn(pdu, status, params, tlvs), pdu.cmdName, pdu.seqNr),
|
this.answer(pduReturn(pdu, status, params, tlvs), pdu.cmdName, pdu.seqNr),
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import type { ErrorName } from './defs/errors.ts';
|
import type { ErrorName } from './defs/errors.ts';
|
||||||
import type { MessageState } from './defs/constants.ts';
|
import type { MessageState } from './defs/constants.ts';
|
||||||
import type { PduObject, PduObjectInput, TlvInput } from './pdu.ts';
|
import type { PduObject, PduObjectInput, TlvInputs } 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 { UnansweredError } from './unanswered-error.ts';
|
import { UnansweredError } from './unanswered-error.ts';
|
||||||
@@ -179,7 +179,7 @@ function receiptText(sms: Sms, smsId: string, status: MessageState): string {
|
|||||||
].join(' ');
|
].join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function receiptTlvs(smsId: string, status: MessageState): Record<string, TlvInput> {
|
function receiptTlvs(smsId: string, status: MessageState): TlvInputs {
|
||||||
return {
|
return {
|
||||||
message_state: { tagValue: consts.MESSAGE_STATE[status] },
|
message_state: { tagValue: consts.MESSAGE_STATE[status] },
|
||||||
receipted_message_id: { tagValue: smsId },
|
receipted_message_id: { tagValue: smsId },
|
||||||
|
|||||||
+30
-6
@@ -475,6 +475,10 @@ describe('TLVs', () => {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const firstRead: Buffer | undefined = pduObj.tlvs.callback_num?.tagValue[0];
|
||||||
|
const presentation: number | undefined = pduObj.tlvs.callback_num_pres_ind?.tagValue[0];
|
||||||
|
|
||||||
|
assert.deepEqual([firstRead, presentation], [first, 1]);
|
||||||
assert.deepEqual(pduObj.tlvs.callback_num?.tagValue, [first, second]);
|
assert.deepEqual(pduObj.tlvs.callback_num?.tagValue, [first, second]);
|
||||||
assert.deepEqual(pduObj.tlvs.callback_num_pres_ind?.tagValue, [1]);
|
assert.deepEqual(pduObj.tlvs.callback_num_pres_ind?.tagValue, [1]);
|
||||||
});
|
});
|
||||||
@@ -493,18 +497,38 @@ describe('TLVs', () => {
|
|||||||
test('refuses a repeatable TLV given one value, and a lone TLV given several', () => {
|
test('refuses a repeatable TLV given one value, and a lone TLV given several', () => {
|
||||||
const params = { destination_addr: '46709771337', short_message: 'hi', source_addr: '46701113311' };
|
const params = { destination_addr: '46709771337', short_message: 'hi', source_addr: '46701113311' };
|
||||||
|
|
||||||
for (const tlvs of [
|
for (const { buffer, err } of [
|
||||||
{ callback_num: { tagValue: Buffer.from('01', 'hex') } },
|
// @ts-expect-error callback_num repeats, so it takes an array
|
||||||
{ callback_num: { tagValue: [] } },
|
objToPdu({ cmdName: 'submit_sm', params, tlvs: { callback_num: { tagValue: Buffer.from('01', 'hex') } } }),
|
||||||
{ source_port: { tagValue: [1234, 1235] } },
|
objToPdu({ cmdName: 'submit_sm', params, tlvs: { callback_num: { tagValue: [] } } }),
|
||||||
|
// @ts-expect-error source_port does not repeat
|
||||||
|
objToPdu({ cmdName: 'submit_sm', params, tlvs: { source_port: { tagValue: [1234, 1235] } } }),
|
||||||
]) {
|
]) {
|
||||||
const { buffer, err } = objToPdu({ cmdName: 'submit_sm', params, tlvs });
|
|
||||||
|
|
||||||
assert.equal(buffer, undefined);
|
assert.equal(buffer, undefined);
|
||||||
assert.ok(err instanceof Error);
|
assert.ok(err instanceof Error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('types each known TLV by its tag, and an unknown one as octets', () => {
|
||||||
|
const pduObj = decode(encode({
|
||||||
|
cmdName: 'deliver_sm',
|
||||||
|
params: { destination_addr: '46709771337', short_message: 'hi', source_addr: '46701113311' },
|
||||||
|
tlvs: {
|
||||||
|
5142: { tagId: 5142, tagValue: Buffer.from('01', 'hex') },
|
||||||
|
message_state: { tagValue: 2 },
|
||||||
|
receipted_message_id: { tagValue: '0199d8a4-5e2c-7b3f-9a61-c4e07f2d8b15' },
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const id: string | undefined = pduObj.tlvs.receipted_message_id?.tagValue;
|
||||||
|
const state: number | undefined = pduObj.tlvs.message_state?.tagValue;
|
||||||
|
const unknown: Buffer | undefined = pduObj.tlvs['5142']?.tagValue;
|
||||||
|
|
||||||
|
assert.deepEqual([id, state, unknown], ['0199d8a4-5e2c-7b3f-9a61-c4e07f2d8b15', 2, Buffer.from('01', 'hex')]);
|
||||||
|
|
||||||
|
// @ts-expect-error message_state is an integer
|
||||||
|
assert.ok(objToPdu({ cmdName: 'deliver_sm', params: {}, tlvs: { message_state: { tagValue: 'ENROUTE' } } }).err);
|
||||||
|
});
|
||||||
|
|
||||||
test('round-trips a receipt with message_state and receipted_message_id', () => {
|
test('round-trips a receipt with message_state and receipted_message_id', () => {
|
||||||
const receipt = 'id:450 sub:001 dlvrd:1 submit date:1504031342 done date:1504031342 stat:DELIVRD err:0 text:xxx';
|
const receipt = 'id:450 sub:001 dlvrd:1 submit date:1504031342 done date:1504031342 stat:DELIVRD err:0 text:xxx';
|
||||||
const pduObj = decode(encode({
|
const pduObj = decode(encode({
|
||||||
|
|||||||
@@ -1808,7 +1808,7 @@ describe('where a segment says it is concatenated', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('reads no concatenation where a sar_* TLV is missing', () => {
|
test('reads no concatenation where a sar_* TLV is missing', () => {
|
||||||
const lone = {
|
const lone: PduObject = {
|
||||||
...sarSegment(5, 1, 2),
|
...sarSegment(5, 1, 2),
|
||||||
tlvs: { sar_msg_ref_num: { tagId: 0x020c, tagName: 'sar_msg_ref_num', tagValue: 5 } },
|
tlvs: { sar_msg_ref_num: { tagId: 0x020c, tagName: 'sar_msg_ref_num', tagValue: 5 } },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -189,14 +189,6 @@ and is also what the panel ranked hardest — two methods, one answer.
|
|||||||
|
|
||||||
### Correctness, ahead of everything below
|
### Correctness, ahead of everything below
|
||||||
|
|
||||||
- [ ] **Type each known TLV's value by its tag, on read and on write, before 0.6.0 is cut.** Every
|
|
||||||
`tagValue` is `TlvValue`, so `{ callback_num: { tagValue: buf } }` compiles and is refused
|
|
||||||
only at runtime, and reading `receipted_message_id` has to narrow out arrays it can never
|
|
||||||
hold. Derive the types from the specs' own wire types and `multiple` flag. It has to land in
|
|
||||||
the same minor as the arrays, or narrowing the types is a second break. A test compiles the
|
|
||||||
CHANGELOG's `tagValue[0]` advice, which the union does not type-check today. From the
|
|
||||||
product-owner review of #25.
|
|
||||||
|
|
||||||
- [ ] **Settle what a repeated tag not marked `multiple` reads as, and pin it in a test.** A vendor
|
- [ ] **Settle what a repeated tag not marked `multiple` reads as, and pin it in a test.** A vendor
|
||||||
tag or a known single-value tag a peer sends twice keeps the last occurrence and drops the
|
tag or a known single-value tag a peer sends twice keeps the last occurrence and drops the
|
||||||
rest silently, which goal 3 argues against; listing it would change every such tag's shape.
|
rest silently, which goal 3 argues against; listing it would change every such tag's shape.
|
||||||
|
|||||||
Reference in New Issue
Block a user