Read every occurrence of a repeatable TLV, and drop the unread tlvMap
Test / lint (pull_request) Successful in 20s
Test / test (18) (pull_request) Successful in 30s
Test / test (20) (pull_request) Successful in 28s
Test / test (22) (pull_request) Successful in 30s
Test / test (24) (pull_request) Successful in 29s
Mirror / push (push) Successful in 4s
Test / test (26) (pull_request) Successful in 30s

This commit is contained in:
2026-09-24 00:13:38 +02:00
parent c5ef4c4703
commit 10877c9f5f
11 changed files with 151 additions and 56 deletions
-2
View File
@@ -4,7 +4,6 @@ import { buffer, cstring, dest_address_array, int8, unsuccess_sme_array } from '
type CommandSpec = {
id: number;
params?: Record<string, WireType>;
tlvMap?: Record<string, string>;
};
const bindParams = {
@@ -59,7 +58,6 @@ const specs = {
broadcast_sm_resp: {
id: 0x80000111,
params: { message_id: cstring },
tlvMap: { broadcast_area_identifier: 'failed_broadcast_area_identifier' },
},
cancel_broadcast_sm: {
id: 0x00000113,
+44 -24
View File
@@ -1,4 +1,4 @@
import type { ParamValue, WireType } from './types.ts';
import type { ParamValue, TlvValue, WireType } from './types.ts';
import type { Result } from '../result.ts';
import { tlv } from './types.ts';
@@ -103,13 +103,13 @@ export const tlvDefault: WireType = tlv.buffer;
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 }> {
@@ -135,30 +135,50 @@ 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 values = occurrences(input.tagValue, definition?.multiple === true);
if (sized.err) {
return { err: new Error(`TLV "${name}": ${sized.err.message}`) };
if (values.err) return { err: new Error(`TLV "${name}": ${values.err.message}`) };
for (const value of values.values) {
const chunk = writeTlv(tag.tagId, definition?.type ?? tlvDefault, value);
if (chunk.err) return { err: new Error(`TLV "${name}": ${chunk.err.message}`) };
chunks.push(chunk.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 };
}
function occurrences(value: TlvValue, multiple: boolean): Result<{ values: ParamValue[] }> {
if (!multiple) {
return Array.isArray(value) ? { err: new Error('takes one value, not an array') } : { values: [value] };
}
if (!Array.isArray(value)) return { err: new Error('is repeatable, give an array of its values') };
if (value.length === 0) return { err: new Error('holds no values, omit it instead') };
return { values: value };
}
function writeTlv(tagId: number, type: WireType, value: ParamValue): Result<{ chunk: Buffer }> {
const sized = type.size(value);
if (sized.err) return { err: sized.err };
if (sized.size > 0xffff) {
return { err: new Error(`${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);
return written.err ? { err: written.err } : { chunk };
}
+4 -1
View File
@@ -14,6 +14,9 @@ export type UnsuccessSme = {
export type ParamValue = Buffer | DestAddress[] | UnsuccessSme[] | number | string;
/** A tag defined `multiple` holds every occurrence, in wire order; any other tag holds one value. */
export type TlvValue = Buffer | Buffer[] | number | number[] | string;
/**
* One field on the wire. `read` reports how many octets it consumed so callers never have to
* re-derive a length that could disagree with what was actually written.
@@ -26,7 +29,7 @@ export type WireType<T extends ParamValue = ParamValue> = {
};
/** Renders a parameter as text without ever falling back to "[object Object]". */
export function paramText(value: ParamValue | undefined): string {
export function paramText(value: ParamValue | TlvValue | undefined): string {
if (typeof value === 'string') return value;
if (typeof value === 'number') return value.toString();
if (Buffer.isBuffer(value)) return value.toString('latin1');