UNPKG

asn1tools-js

Version:

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

257 lines 11 kB
"use strict"; /** * Complex ASN.1 type implementations */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ChoiceType = exports.SequenceOfType = exports.SequenceType = void 0; const types_1 = require("../types"); const encoding_1 = require("./encoding"); const types_2 = require("./types"); /** * ASN.1 SEQUENCE type */ class SequenceType extends types_2.BaseType { constructor(name, members) { super(name, types_1.BER.TAG.SEQUENCE); this.constructed = true; this.members = members; } encode(value) { if (!value || typeof value !== 'object') { throw new types_1.EncodeError(`SEQUENCE ${this.name}: expected object, got ${typeof value}`); } const encodedMembers = []; for (const member of this.members) { const memberValue = value[member.name]; if (memberValue === undefined || memberValue === null) { if (member.optional) { continue; // Skip optional members } if (member.defaultValue !== undefined) { // Use default value const encoded = member.type.encode(member.defaultValue); encodedMembers.push(encoded); } else { throw new types_1.EncodeError(`SEQUENCE ${this.name}: missing required member '${member.name}'`); } } else { const encoded = member.type.encode(memberValue); encodedMembers.push(encoded); } } // Concatenate all encoded members const totalLength = encodedMembers.reduce((sum, arr) => sum + arr.length, 0); const content = new Uint8Array(totalLength); let offset = 0; for (const encoded of encodedMembers) { content.set(encoded, offset); offset += encoded.length; } return this.encodeWithTag(content); } decode(data, offset = 0) { const { content, totalLength } = this.decodeWithTag(data, offset); const result = {}; let contentOffset = 0; let memberIndex = 0; while (contentOffset < content.length && memberIndex < this.members.length) { const member = this.members[memberIndex]; if (!member) { throw new types_1.DecodeError(`SEQUENCE ${this.name}: internal error - member at index ${memberIndex} not found`, offset); } try { const decoded = member.type.decode(content, contentOffset); result[member.name] = decoded.value; contentOffset += decoded.length; memberIndex++; } catch (error) { if (member.optional) { // Skip optional member that's not present memberIndex++; continue; } if (member.defaultValue !== undefined) { // Use default value result[member.name] = member.defaultValue; memberIndex++; continue; } throw new types_1.DecodeError(`SEQUENCE ${this.name}: failed to decode member '${member.name}': ${error instanceof Error ? error.message : String(error)}`, offset + contentOffset); } } // Check for missing required members while (memberIndex < this.members.length) { const member = this.members[memberIndex]; if (!member) { throw new types_1.DecodeError(`SEQUENCE ${this.name}: internal error - member at index ${memberIndex} not found`, offset); } if (!member.optional && member.defaultValue === undefined) { throw new types_1.DecodeError(`SEQUENCE ${this.name}: missing required member '${member.name}'`, offset); } if (member.defaultValue !== undefined) { result[member.name] = member.defaultValue; } memberIndex++; } return { value: result, length: totalLength }; } } exports.SequenceType = SequenceType; /** * ASN.1 SEQUENCE OF type */ class SequenceOfType extends types_2.BaseType { constructor(name, elementType) { super(name, types_1.BER.TAG.SEQUENCE); this.constructed = true; this.elementType = elementType; } encode(value) { if (!Array.isArray(value)) { throw new types_1.EncodeError(`SEQUENCE OF ${this.name}: expected array, got ${typeof value}`); } const encodedElements = []; for (let i = 0; i < value.length; i++) { try { const encoded = this.elementType.encode(value[i]); encodedElements.push(encoded); } catch (error) { throw new types_1.EncodeError(`SEQUENCE OF ${this.name}: failed to encode element ${i}: ${error instanceof Error ? error.message : String(error)}`); } } // Concatenate all encoded elements const totalLength = encodedElements.reduce((sum, arr) => sum + arr.length, 0); const content = new Uint8Array(totalLength); let offset = 0; for (const encoded of encodedElements) { content.set(encoded, offset); offset += encoded.length; } return this.encodeWithTag(content); } decode(data, offset = 0) { const { content, totalLength } = this.decodeWithTag(data, offset); const result = []; let contentOffset = 0; while (contentOffset < content.length) { try { const decoded = this.elementType.decode(content, contentOffset); result.push(decoded.value); contentOffset += decoded.length; } catch (error) { throw new types_1.DecodeError(`SEQUENCE OF ${this.name}: failed to decode element ${result.length}: ${error instanceof Error ? error.message : String(error)}`, offset + contentOffset); } } return { value: result, length: totalLength }; } } exports.SequenceOfType = SequenceOfType; /** * ASN.1 CHOICE type */ class ChoiceType extends types_2.BaseType { constructor(name, choices) { super(name, types_1.BER.TAG.CHOICE); // Special marker tag this.choices = new Map(); this.tagToChoice = new Map(); for (const choice of choices) { const choiceObj = { type: choice.type }; if (choice.tag !== undefined) { choiceObj.tag = choice.tag; } this.choices.set(choice.name, choiceObj); // Map tag to choice name for decoding const choiceTag = choice.tag ?? choice.type.tag; this.tagToChoice.set(choiceTag, choice.name); } } encode(value) { if (!value || typeof value !== 'object') { throw new types_1.EncodeError(`CHOICE ${this.name}: expected object with single property, got ${typeof value}`); } const keys = Object.keys(value); if (keys.length !== 1) { throw new types_1.EncodeError(`CHOICE ${this.name}: expected object with exactly one property, got ${keys.length}`); } const choiceName = keys[0]; if (!choiceName) { throw new types_1.EncodeError(`CHOICE ${this.name}: invalid choice name`); } const choiceValue = value[choiceName]; const choice = this.choices.get(choiceName); if (!choice) { throw new types_1.EncodeError(`CHOICE ${this.name}: unknown choice '${choiceName}'`); } // For CHOICE, we encode the selected alternative directly // If there's a context-specific tag, apply it if (choice.tag !== undefined) { // Apply context-specific tag const innerEncoded = choice.type.encode(choiceValue); const tagBytes = (0, encoding_1.encodeTag)(choice.tag, types_1.BER.CLASS.CONTEXT_SPECIFIC | types_1.BER.ENCODING.CONSTRUCTED); const lengthBytes = (0, encoding_1.encodeLength)(innerEncoded.length); const result = new Uint8Array(tagBytes.length + lengthBytes.length + innerEncoded.length); let offset = 0; result.set(tagBytes, offset); offset += tagBytes.length; result.set(lengthBytes, offset); offset += lengthBytes.length; result.set(innerEncoded, offset); return result; } else { return choice.type.encode(choiceValue); } } decode(data, offset = 0) { if (offset >= data.length) { throw new types_1.DecodeError(`CHOICE ${this.name}: unexpected end of data`, offset); } // Peek at the tag to determine which choice to decode const tagInfo = (0, encoding_1.decodeTag)(data, offset); const choiceName = this.tagToChoice.get(tagInfo.tag); if (!choiceName) { throw new types_1.DecodeError(`CHOICE ${this.name}: no choice found for tag ${tagInfo.tag}`, offset); } const choice = this.choices.get(choiceName); if (!choice) { throw new types_1.DecodeError(`CHOICE ${this.name}: internal error - choice '${choiceName}' not found`, offset); } // Decode using the appropriate choice type if (choice.tag !== undefined) { // Handle context-specific tagged choice const lengthInfo = (0, encoding_1.decodeLength)(data, offset + tagInfo.length); const contentStart = offset + tagInfo.length + lengthInfo.octets; const contentEnd = contentStart + lengthInfo.length; if (contentEnd > data.length) { throw new types_1.DecodeError(`CHOICE ${this.name}: not enough data for choice content`, offset); } const contentData = data.slice(contentStart, contentEnd); const decoded = choice.type.decode(contentData, 0); return { value: { [choiceName]: decoded.value }, length: tagInfo.length + lengthInfo.octets + lengthInfo.length }; } else { const decoded = choice.type.decode(data, offset); return { value: { [choiceName]: decoded.value }, length: decoded.length }; } } // Override the base class method since CHOICE doesn't have a fixed tag decodeWithTag(_data, _offset = 0) { throw new Error('CHOICE type should not use decodeWithTag - use decode directly'); } encodeWithTag(_content) { throw new Error('CHOICE type should not use encodeWithTag - use encode directly'); } } exports.ChoiceType = ChoiceType; //# sourceMappingURL=complex-types.js.map