UNPKG

asn1tools-js

Version:

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

214 lines 8.19 kB
"use strict"; /** * ASN.1 type implementations using BER encoding */ Object.defineProperty(exports, "__esModule", { value: true }); exports.EnumeratedType = exports.NullType = exports.OctetStringType = exports.BooleanType = exports.IntegerType = exports.BaseType = void 0; const types_1 = require("../types"); const encoding_1 = require("./encoding"); /** * Base class for all ASN.1 types */ class BaseType { constructor(name, tag) { this.constructed = false; this.name = name; this.tag = tag; } encodeWithTag(content) { const tagBytes = (0, encoding_1.encodeTag)(this.tag, this.constructed ? types_1.BER.ENCODING.CONSTRUCTED : types_1.BER.ENCODING.PRIMITIVE); const lengthBytes = (0, encoding_1.encodeLength)(content.length); const result = new Uint8Array(tagBytes.length + lengthBytes.length + content.length); let offset = 0; result.set(tagBytes, offset); offset += tagBytes.length; result.set(lengthBytes, offset); offset += lengthBytes.length; result.set(content, offset); return result; } decodeWithTag(data, offset = 0) { if (offset >= data.length) { throw new types_1.DecodeError(`Unexpected end of data while decoding ${this.name}`, offset); } const tagInfo = (0, encoding_1.decodeTag)(data, offset); let currentOffset = offset + tagInfo.length; // Verify tag matches expected if (tagInfo.tag !== this.tag) { throw new types_1.DecodeError(`Expected tag ${this.tag} for ${this.name}, got ${tagInfo.tag}`, offset); } const lengthInfo = (0, encoding_1.decodeLength)(data, currentOffset); currentOffset += lengthInfo.octets; if (currentOffset + lengthInfo.length > data.length) { throw new types_1.DecodeError(`Not enough data for ${this.name}: expected ${lengthInfo.length} bytes`, currentOffset); } const content = data.slice(currentOffset, currentOffset + lengthInfo.length); return { content, totalLength: tagInfo.length + lengthInfo.octets + lengthInfo.length }; } } exports.BaseType = BaseType; /** * ASN.1 INTEGER type */ class IntegerType extends BaseType { constructor(name) { super(name, types_1.BER.TAG.INTEGER); } encode(value) { if (typeof value !== 'number' && typeof value !== 'bigint') { throw new types_1.EncodeError(`INTEGER ${this.name}: expected number or bigint, got ${typeof value}`); } const content = (0, encoding_1.encodeSignedInteger)(value); return this.encodeWithTag(content); } decode(data, offset = 0) { const { content, totalLength } = this.decodeWithTag(data, offset); if (content.length === 0) { throw new types_1.DecodeError(`INTEGER ${this.name}: empty content`, offset); } const bigintValue = (0, encoding_1.decodeSignedInteger)(content); const value = (0, encoding_1.toSafeNumber)(bigintValue); return { value, length: totalLength }; } } exports.IntegerType = IntegerType; /** * ASN.1 BOOLEAN type */ class BooleanType extends BaseType { constructor(name) { super(name, types_1.BER.TAG.BOOLEAN); } encode(value) { if (typeof value !== 'boolean') { throw new types_1.EncodeError(`BOOLEAN ${this.name}: expected boolean, got ${typeof value}`); } const content = new Uint8Array([value ? 0xff : 0x00]); return this.encodeWithTag(content); } decode(data, offset = 0) { const { content, totalLength } = this.decodeWithTag(data, offset); if (content.length !== 1) { throw new types_1.DecodeError(`BOOLEAN ${this.name}: expected 1 byte content, got ${content.length}`, offset); } const value = content[0] !== 0; return { value, length: totalLength }; } } exports.BooleanType = BooleanType; /** * ASN.1 OCTET STRING type */ class OctetStringType extends BaseType { constructor(name) { super(name, types_1.BER.TAG.OCTET_STRING); } encode(value) { let content; if (value instanceof Uint8Array) { content = value; } else if (value instanceof ArrayBuffer) { content = new Uint8Array(value); } else if (Array.isArray(value)) { content = new Uint8Array(value); } else if (typeof value === 'string') { // Assume hex string const hex = value.replace(/[^0-9a-fA-F]/g, ''); if (hex.length % 2 !== 0) { throw new types_1.EncodeError(`OCTET STRING ${this.name}: hex string must have even length`); } content = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { content[i / 2] = parseInt(hex.substr(i, 2), 16); } } else { throw new types_1.EncodeError(`OCTET STRING ${this.name}: expected Uint8Array, ArrayBuffer, number[], or hex string, got ${typeof value}`); } return this.encodeWithTag(content); } decode(data, offset = 0) { const { content, totalLength } = this.decodeWithTag(data, offset); return { value: content, length: totalLength }; } } exports.OctetStringType = OctetStringType; /** * ASN.1 NULL type */ class NullType extends BaseType { constructor(name) { super(name, types_1.BER.TAG.NULL); } encode(value) { if (value !== null && value !== undefined) { throw new types_1.EncodeError(`NULL ${this.name}: expected null or undefined, got ${typeof value}`); } const content = new Uint8Array(0); return this.encodeWithTag(content); } decode(data, offset = 0) { const { content, totalLength } = this.decodeWithTag(data, offset); if (content.length !== 0) { throw new types_1.DecodeError(`NULL ${this.name}: expected empty content, got ${content.length} bytes`, offset); } return { value: null, length: totalLength }; } } exports.NullType = NullType; /** * ASN.1 ENUMERATED type */ class EnumeratedType extends BaseType { constructor(name, values) { super(name, types_1.BER.TAG.ENUMERATED); this.valueMap = new Map(); this.nameMap = new Map(); for (const [valueName, valueNumber] of values) { this.valueMap.set(valueName, valueNumber); this.nameMap.set(valueNumber, valueName); } } encode(value) { let enumValue; if (typeof value === 'string') { const mappedValue = this.valueMap.get(value); if (mappedValue === undefined) { throw new types_1.EncodeError(`ENUMERATED ${this.name}: unknown value '${value}'`); } enumValue = mappedValue; } else if (typeof value === 'number') { if (!this.nameMap.has(value)) { throw new types_1.EncodeError(`ENUMERATED ${this.name}: unknown numeric value ${value}`); } enumValue = value; } else { throw new types_1.EncodeError(`ENUMERATED ${this.name}: expected string or number, got ${typeof value}`); } const content = (0, encoding_1.encodeSignedInteger)(enumValue); return this.encodeWithTag(content); } decode(data, offset = 0) { const { content, totalLength } = this.decodeWithTag(data, offset); if (content.length === 0) { throw new types_1.DecodeError(`ENUMERATED ${this.name}: empty content`, offset); } const bigintValue = (0, encoding_1.decodeSignedInteger)(content); const numericValue = Number(bigintValue); const name = this.nameMap.get(numericValue); if (name === undefined) { throw new types_1.DecodeError(`ENUMERATED ${this.name}: unknown value ${numericValue}`, offset); } return { value: name, length: totalLength }; } } exports.EnumeratedType = EnumeratedType; //# sourceMappingURL=types.js.map