UNPKG

@aokiapp/tlv

Version:

Tag-Length-Value (TLV) parser and builder library with schema support. Provides both parsing and building APIs as submodules.

159 lines (158 loc) 4.9 kB
/** * Common encode/decode utilities for TLV ASN.1 DER operations */ export function identity(ab) { return ab; } export function bufferToArrayBuffer(buf) { const out = new ArrayBuffer(buf.byteLength); new Uint8Array(out).set(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)); return out; } export function toHex(input) { const bytes = input instanceof Uint8Array ? input : new Uint8Array(input); return Array.from(bytes) .map((b) => b.toString(16).padStart(2, "0")) .join(""); } export function toArrayBuffer(u8) { const buf = new ArrayBuffer(u8.byteLength); new Uint8Array(buf).set(u8); return buf; } export function encodeUtf8(str) { return new TextEncoder().encode(str).buffer; } export function decodeUtf8(buffer) { return new TextDecoder("utf-8").decode(buffer); } export function decodeShiftJis(buffer) { return new TextDecoder("shift_jis").decode(buffer); } export function decodeAscii(buffer) { return new TextDecoder("ascii").decode(buffer); } export function encodeInteger(n) { if (!Number.isFinite(n) || n < 0) { throw new Error(`INTEGER must be non-negative finite number; got ${n}`); } if (n === 0) return new Uint8Array([0x00]).buffer; const out = []; let temp = n; while (temp > 0) { out.unshift(temp & 0xff); temp >>>= 8; } if (out[0] & 0x80) out.unshift(0x00); return new Uint8Array(out).buffer; } export function decodeInteger(buffer) { const bytes = new Uint8Array(buffer); if (bytes.length === 0) return 0; let i = 0; if (bytes[0] === 0x00 && bytes.length > 1) i = 1; let n = 0; for (; i < bytes.length; i++) n = (n << 8) | bytes[i]; return n; } function encodeBase128(n) { if (n === 0) return [0x00]; const stack = []; while (n > 0) { stack.push(n & 0x7f); n = Math.floor(n / 128); } const out = stack.reverse(); for (let i = 0; i < out.length - 1; i++) out[i] |= 0x80; return out; } export function encodeOID(oid) { const arcs = oid.split(".").map((s) => { const n = Number(s); if (!Number.isFinite(n) || n < 0) throw new Error(`Invalid OID arc: ${s}`); return Math.floor(n); }); if (arcs.length < 2) throw new Error(`OID must have at least two arcs; got '${oid}'`); const first = arcs[0]; const second = arcs[1]; let firstByte = 0; if (first < 2) { firstByte = first * 40 + second; } else { firstByte = 80 + second; } const out = [firstByte]; for (let i = 2; i < arcs.length; i++) { out.push(...encodeBase128(arcs[i])); } return new Uint8Array(out).buffer; } export function decodeOID(buffer) { const bytes = new Uint8Array(buffer); if (bytes.length === 0) throw new Error("Empty OID encoding (0 bytes)"); const firstByte = bytes[0]; let first = Math.floor(firstByte / 40); let second = firstByte % 40; if (firstByte >= 80) { first = 2; second = firstByte - 80; } const arcs = [first, second]; let i = 1; while (i < bytes.length) { let val = 0; let b; do { if (i >= bytes.length) throw new Error(`Truncated OID at byte index ${i}`); b = bytes[i++]; val = (val << 7) | (b & 0x7f); } while (b & 0x80); arcs.push(val); } return arcs.join("."); } export function decodeBitStringHex(buffer) { const bytes = new Uint8Array(buffer); if (bytes.length === 0) { return { unusedBits: 0, hex: "" }; } const unusedBits = bytes[0]; // DER: unusedBits must be in 0..7 (and fit in a single octet). if (!Number.isInteger(unusedBits) || unusedBits < 0 || unusedBits > 7) { throw new Error(`BIT STRING unusedBits must be in range 0..7; got ${unusedBits}`); } const content = bytes.slice(1); // A BIT STRING with non-zero unusedBits must have at least one content byte. if (content.length === 0 && unusedBits !== 0) { throw new Error(`BIT STRING with unusedBits=${unusedBits} must have at least one content byte`); } if (content.length > 0 && unusedBits !== 0) { const last = content[content.length - 1]; const mask = (1 << unusedBits) - 1; if ((last & mask) !== 0) { throw new Error(`BIT STRING unused bits in last byte must be zero; got 0x${last .toString(16) .padStart(2, "0")} with unusedBits=${unusedBits}`); } } return { unusedBits, hex: toHex(content) }; } export function encodeBitString(bits) { const buffer = new ArrayBuffer(bits.data.length + 1); const view = new Uint8Array(buffer); view[0] = bits.unusedBits; view.set(bits.data, 1); return buffer; }