Read and write every copy of a TLV SMPP lets repeat, and drop the dormant tlvMap
Test / lint (pull_request) Successful in 21s
Test / test (18) (pull_request) Successful in 28s
Test / test (20) (pull_request) Successful in 31s
Test / test (22) (pull_request) Successful in 29s
Test / test (24) (pull_request) Successful in 30s
Test / test (26) (pull_request) Successful in 29s
Mirror / push (push) Successful in 6s

This commit is contained in:
2026-09-24 00:37:10 +02:00
parent c5ef4c4703
commit 224acca7d0
12 changed files with 120 additions and 52 deletions
+61 -27
View File
@@ -1,16 +1,19 @@
import type { ParamValue, WireType } from './types.ts';
import type { WireType } from './types.ts';
import type { Result } from '../result.ts';
import { tlv } from './types.ts';
/** What one TLV on the wire reads as. */
export type TlvScalar = Buffer | number | string;
export type TlvDefinition = {
id: number;
multiple?: boolean;
tag: string;
type: WireType;
type: WireType<TlvScalar>;
};
/** The constraint keys every definition to its own name, so a `tag` that drifts fails to compile. */
const tlvSpecs = <T extends { [K in keyof T]: { id: number; multiple?: boolean; tag: K; type: WireType } }>(
const tlvSpecs = <T extends { [K in keyof T]: { id: number; multiple?: boolean; tag: K; type: WireType<TlvScalar> } }>(
definitions: T,
): T => definitions;
@@ -98,18 +101,21 @@ for (const definition of Object.values<TlvDefinition>(specs)) {
}
/** Fallback for tags this table does not know: keep the raw octets. */
export const tlvDefault: WireType = tlv.buffer;
export const tlvDefault: WireType<TlvScalar> = tlv.buffer;
/** A list for a tag its definition marks `multiple`, one copy per list entry, and a single value otherwise. */
export type TlvValue = TlvScalar | TlvScalar[];
export type Tlv = {
tagId: number;
tagName: string | undefined;
tagValue: ParamValue;
tagValue: TlvValue;
};
export type TlvInput = {
/** Resolved from the record key; pass it for a tag the TLV table does not define. */
tagId?: number | undefined;
tagValue: ParamValue;
tagValue: TlvValue;
};
export function tagIdOf(name: string, input: TlvInput): Result<{ tagId: number }> {
@@ -126,6 +132,45 @@ export function tagIdOf(name: string, input: TlvInput): Result<{ tagId: number }
return { tagId };
}
function copiesOf(name: string, definition: TlvDefinition | undefined, value: TlvValue): Result<{ copies: TlvScalar[] }> {
const multiple = definition?.multiple === true;
if (Array.isArray(value) && multiple) return { copies: value };
if (!Array.isArray(value) && !multiple) return { copies: [value] };
return {
err: new Error(multiple
? `TLV "${name}": the tag may repeat, so give its value as a list`
: `TLV "${name}": the tag may not repeat, so give its value alone rather than as a list`),
};
}
function writeTlv(name: string, tagId: number, type: WireType<TlvScalar>, value: TlvScalar): Result<{ chunk: Buffer }> {
const sized = type.size(value);
if (sized.err) {
return { err: new Error(`TLV "${name}": ${sized.err.message}`) };
}
if (sized.size > 0xffff) {
return { err: new Error(`TLV "${name}": ${String(sized.size)} octets overflow the two octet length`) };
}
const chunk = Buffer.alloc(sized.size + 4);
chunk.writeUInt16BE(tagId, 0);
chunk.writeUInt16BE(sized.size, 2);
const written = type.write(value, chunk, 4);
if (written.err) {
return { err: new Error(`TLV "${name}": ${written.err.message}`) };
}
return { chunk };
}
/** Each TLV as its four octet header and the value the tag's own wire type writes. */
export function writeTlvs(inputs: Record<string, TlvInput> | undefined): Result<{ chunks: Buffer[] }> {
const chunks: Buffer[] = [];
@@ -135,29 +180,18 @@ export function writeTlvs(inputs: Record<string, TlvInput> | undefined): Result<
if (tag.err) return { err: tag.err };
const type = tlvsById[tag.tagId]?.type ?? tlvDefault;
const sized = type.size(input.tagValue);
const definition = tlvsById[tag.tagId];
const listed = copiesOf(name, definition, input.tagValue);
if (sized.err) {
return { err: new Error(`TLV "${name}": ${sized.err.message}`) };
if (listed.err) return { err: listed.err };
for (const value of listed.copies) {
const written = writeTlv(name, tag.tagId, definition?.type ?? tlvDefault, value);
if (written.err) return { err: written.err };
chunks.push(written.chunk);
}
if (sized.size > 0xffff) {
return { err: new Error(`TLV "${name}": ${String(sized.size)} octets overflow the two octet length`) };
}
const chunk = Buffer.alloc(sized.size + 4);
chunk.writeUInt16BE(tag.tagId, 0);
chunk.writeUInt16BE(sized.size, 2);
const written = type.write(input.tagValue, chunk, 4);
if (written.err) {
return { err: new Error(`TLV "${name}": ${written.err.message}`) };
}
chunks.push(chunk);
}
return { chunks };