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
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:
+61
-27
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user