UNPKG

asn1tools-js

Version:

ASN.1 encoding and decoding library for TypeScript/JavaScript, compatible with Python asn1tools

244 lines 7.45 kB
"use strict"; /** * BER (Basic Encoding Rules) encoding utilities */ Object.defineProperty(exports, "__esModule", { value: true }); exports.encodeTag = encodeTag; exports.encodeLength = encodeLength; exports.decodeTag = decodeTag; exports.decodeLength = decodeLength; exports.encodeSignedInteger = encodeSignedInteger; exports.decodeSignedInteger = decodeSignedInteger; exports.toSafeNumber = toSafeNumber; const types_1 = require("../types"); /** * Encode a BER tag */ function encodeTag(tagNumber, flags = 0) { if (tagNumber < 31) { return new Uint8Array([tagNumber | flags]); } // High tag number form const result = [0x1f | flags]; const bytes = []; let temp = tagNumber; while (temp > 0) { bytes.unshift(temp & 0x7f); temp >>= 7; } // Set continuation bit for all but last byte for (let i = 0; i < bytes.length - 1; i++) { const byte = bytes[i]; if (byte !== undefined) { bytes[i] = byte | 0x80; } } return new Uint8Array([...result, ...bytes]); } /** * Encode BER length (definite form) */ function encodeLength(length) { if (length < 0) { throw new types_1.EncodeError('Length cannot be negative'); } if (length <= 127) { // Short form return new Uint8Array([length]); } // Long form const bytes = []; let temp = length; while (temp > 0) { bytes.unshift(temp & 0xff); temp >>= 8; } if (bytes.length > 127) { throw new types_1.EncodeError('Length too large for BER encoding'); } return new Uint8Array([0x80 | bytes.length, ...bytes]); } /** * Decode BER tag */ function decodeTag(data, offset = 0) { if (offset >= data.length) { throw new types_1.DecodeError('Unexpected end of data while reading tag', offset); } const firstByte = data[offset]; if (firstByte === undefined) { throw new types_1.DecodeError('Unexpected end of data while reading tag byte', offset); } const constructed = (firstByte & 0x20) !== 0; let tagNumber; let length; if ((firstByte & 0x1f) !== 0x1f) { // Low tag number form tagNumber = firstByte & 0x1f; length = 1; } else { // High tag number form tagNumber = 0; length = 1; while (length + offset < data.length) { const byte = data[offset + length]; if (byte === undefined) { throw new types_1.DecodeError('Unexpected end of data while reading tag', offset + length); } length++; tagNumber = (tagNumber << 7) | (byte & 0x7f); if ((byte & 0x80) === 0) { break; } if (length > 6) { // Prevent infinite loops throw new types_1.DecodeError('Tag number too large', offset); } } if (offset + length > data.length) { throw new types_1.DecodeError('Unexpected end of data while reading high tag number', offset); } } return { tag: tagNumber, length, constructed }; } /** * Decode BER length */ function decodeLength(data, offset) { if (offset >= data.length) { throw new types_1.DecodeError('Unexpected end of data while reading length', offset); } const firstByte = data[offset]; if (firstByte === undefined) { throw new types_1.DecodeError('No data available for length', offset); } if ((firstByte & 0x80) === 0) { // Short form return { length: firstByte, octets: 1 }; } // Long form const lengthOctets = firstByte & 0x7f; if (lengthOctets === 0) { throw new types_1.DecodeError('Indefinite length not supported in this context', offset); } if (lengthOctets > 4) { throw new types_1.DecodeError('Length too large', offset); } if (offset + lengthOctets >= data.length) { throw new types_1.DecodeError('Unexpected end of data while reading length', offset); } let length = 0; for (let i = 1; i <= lengthOctets; i++) { const byte = data[offset + i]; if (byte === undefined) { throw new types_1.DecodeError(`Missing length byte at position ${offset + i}`, offset + i); } length = (length << 8) | byte; } return { length, octets: lengthOctets + 1 }; } /** * Encode a signed integer in two's complement form */ function encodeSignedInteger(value) { if (typeof value === 'number') { if (!Number.isInteger(value)) { throw new types_1.EncodeError('Value must be an integer'); } value = BigInt(value); } if (value === 0n) { return new Uint8Array([0]); } const negative = value < 0n; let temp = negative ? -value - 1n : value; const bytes = []; // Calculate minimum number of bytes needed while (temp > 0n) { bytes.unshift(Number(temp & 0xffn)); temp >>= 8n; } if (bytes.length === 0) { bytes.push(0); } // Adjust for sign bit if (negative) { // Two's complement let carry = 1; for (let i = bytes.length - 1; i >= 0; i--) { const byte = bytes[i]; if (byte !== undefined) { bytes[i] = (~byte + carry) & 0xff; carry = bytes[i] > 0xff ? 1 : 0; } } // Ensure sign bit is set const firstByte = bytes[0]; if (firstByte !== undefined && (firstByte & 0x80) === 0) { bytes.unshift(0xff); } } else { // Ensure sign bit is not set for positive numbers const firstByte = bytes[0]; if (firstByte !== undefined && (firstByte & 0x80) !== 0) { bytes.unshift(0x00); } } return new Uint8Array(bytes); } /** * Decode a signed integer from two's complement form */ function decodeSignedInteger(data) { if (data.length === 0) { throw new types_1.DecodeError('Empty integer data'); } const firstByte = data[0]; if (firstByte === undefined) { throw new types_1.DecodeError('No data available for integer'); } const negative = (firstByte & 0x80) !== 0; let result = 0n; if (negative) { // Two's complement decoding let borrow = 1; const bytes = Array.from(data); // Subtract 1 and invert for (let i = bytes.length - 1; i >= 0; i--) { const byte = bytes[i]; if (byte !== undefined) { bytes[i] = byte - borrow; if (bytes[i] < 0) { bytes[i] = bytes[i] + 256; borrow = 1; } else { borrow = 0; } bytes[i] = (~bytes[i]) & 0xff; } } for (const byte of bytes) { result = (result << 8n) | BigInt(byte); } return -result - 1n; } else { // Positive number for (const byte of data) { result = (result << 8n) | BigInt(byte); } return result; } } /** * Convert number to safe JavaScript number if possible, otherwise keep as bigint */ function toSafeNumber(value) { if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) { return Number(value); } return value; } //# sourceMappingURL=encoding.js.map