asn1tools-js
Version:
ASN.1 encoding and decoding library for TypeScript/JavaScript, compatible with Python asn1tools
450 lines • 13.7 kB
JavaScript
"use strict";
/**
* ASN.1 grammar parser
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Asn1Parser = void 0;
const types_1 = require("./types");
class Asn1Parser {
constructor() {
this.input = '';
this.position = 0;
this.line = 1;
this.column = 1;
}
parse(content) {
this.input = content;
this.position = 0;
this.line = 1;
this.column = 1;
const modules = [];
this.skipWhitespaceAndComments();
while (!this.isAtEnd()) {
const module = this.parseModule();
modules.push(module);
this.skipWhitespaceAndComments();
}
return modules;
}
parseModule() {
// Parse module header: ModuleName DEFINITIONS ::= BEGIN
const moduleName = this.parseIdentifier();
this.expectKeyword('DEFINITIONS');
this.expectToken('::=');
this.expectKeyword('BEGIN');
const types = new Map();
this.skipWhitespaceAndComments();
while (!this.checkKeyword('END') && !this.isAtEnd()) {
this.skipWhitespaceAndComments();
const parsedType = this.parseTypeAssignment();
types.set(parsedType.name, parsedType);
}
this.expectKeyword('END');
return {
name: moduleName,
types
};
}
parseTypeAssignment() {
const name = this.parseIdentifier();
this.expectToken('::=');
const type = this.parseType();
return { ...type, name };
}
parseType() {
this.skipWhitespaceAndComments();
let type;
if (this.checkKeyword('INTEGER')) {
type = this.parseIntegerType();
}
else if (this.checkKeyword('BOOLEAN')) {
type = this.parseBooleanType();
}
else if (this.checkKeyword('OCTET')) {
type = this.parseOctetStringType();
}
else if (this.checkKeyword('SEQUENCE')) {
// Peek ahead to decide between "SEQUENCE {...}" and "SEQUENCE OF <Type>"
const savedPos = this.position;
const savedLine = this.line;
const savedCol = this.column;
// Consume the keyword 'SEQUENCE'
this.expectKeyword('SEQUENCE');
this.skipWhitespaceAndComments();
if (this.checkKeyword('OF')) {
// It's a SEQUENCE OF construct
this.expectKeyword('OF');
const elementType = this.parseType();
type = {
name: '',
type: 'SEQUENCE_OF',
elementType
};
}
else {
// Not a SEQUENCE OF – rewind and parse as a normal SEQUENCE
this.position = savedPos;
this.line = savedLine;
this.column = savedCol;
type = this.parseSequenceType();
}
}
else if (this.checkKeyword('CHOICE')) {
type = this.parseChoiceType();
}
else if (this.checkKeyword('ENUMERATED')) {
type = this.parseEnumeratedType();
}
else if (this.checkKeyword('NULL')) {
type = this.parseNullType();
}
else {
// Handle defined types (references to other types)
this.skipWhitespaceAndComments();
if (!this.isAlpha(this.peek())) {
this.error('Expected type reference (identifier)');
}
const typeName = this.parseIdentifier();
type = {
name: '',
type: 'DEFINED',
constraints: { definedType: typeName }
};
}
// After parsing any type, check for constraints
this.skipWhitespaceAndComments();
if (this.check('(')) {
const constraints = this.parseConstraints();
type = { ...type, constraints: { ...(type.constraints || {}), ...constraints } };
}
return type;
}
parseIntegerType() {
this.expectKeyword('INTEGER');
return {
name: '',
type: 'INTEGER'
};
}
parseBooleanType() {
this.expectKeyword('BOOLEAN');
return {
name: '',
type: 'BOOLEAN'
};
}
parseOctetStringType() {
this.expectKeyword('OCTET');
this.skipWhitespaceAndComments();
this.expectKeyword('STRING');
return {
name: '',
type: 'OCTET_STRING'
};
}
parseNullType() {
this.expectKeyword('NULL');
return {
name: '',
type: 'NULL'
};
}
parseSequenceType() {
this.expectKeyword('SEQUENCE');
this.expectToken('{');
const members = [];
while (true) {
this.skipWhitespaceAndComments();
if (this.check('}') || this.isAtEnd()) {
break;
}
const member = this.parseSequenceMember();
members.push(member);
this.skipWhitespaceAndComments();
if (this.check(',')) {
this.expectToken(',');
}
}
this.expectToken('}');
return {
name: '',
type: 'SEQUENCE',
members
};
}
parseSequenceMember() {
const name = this.parseIdentifier();
let tag;
this.skipWhitespaceAndComments();
if (this.check('[')) {
tag = this.parseTag();
this.skipWhitespaceAndComments();
}
let type = this.parseType();
let optional = false;
let defaultValue;
this.skipWhitespaceAndComments();
if (this.checkKeyword('OPTIONAL')) {
this.expectKeyword('OPTIONAL');
optional = true;
}
else if (this.checkKeyword('DEFAULT')) {
this.expectKeyword('DEFAULT');
defaultValue = this.parseValue();
}
return {
...type,
name,
tag,
optional,
default: defaultValue
};
}
parseChoiceType() {
this.expectKeyword('CHOICE');
this.expectToken('{');
const choices = [];
while (true) {
this.skipWhitespaceAndComments();
if (this.check('}') || this.isAtEnd()) {
break;
}
const choice = this.parseChoiceAlternative();
choices.push(choice);
this.skipWhitespaceAndComments();
if (this.check(',')) {
this.expectToken(',');
}
}
this.expectToken('}');
return {
name: '',
type: 'CHOICE',
choices
};
}
parseChoiceAlternative() {
const name = this.parseIdentifier();
let tag;
this.skipWhitespaceAndComments();
if (this.check('[')) {
tag = this.parseTag();
this.skipWhitespaceAndComments();
}
let type = this.parseType();
return {
...type,
name,
tag
};
}
parseEnumeratedType() {
this.expectKeyword('ENUMERATED');
this.expectToken('{');
const values = [];
let autoValue = 0;
this.skipWhitespaceAndComments();
while (!this.check('}') && !this.isAtEnd()) {
const name = this.parseIdentifier();
let value = autoValue;
if (this.check('(')) {
this.expectToken('(');
value = this.parseNumber();
this.expectToken(')');
}
values.push([name, value]);
autoValue = value + 1;
if (this.check(',')) {
this.expectToken(',');
}
}
this.expectToken('}');
return {
name: '',
type: 'ENUMERATED',
constraints: { values }
};
}
parseTag() {
this.expectToken('[');
const tagNumber = this.parseNumber();
this.expectToken(']');
return tagNumber;
}
parseConstraints() {
this.expectToken('(');
this.skipWhitespaceAndComments();
const constraints = {};
if (this.checkKeyword('SIZE')) {
this.expectKeyword('SIZE');
this.expectToken('(');
const size = this.parseNumber();
this.expectToken(')');
constraints.size = size;
}
else {
// Range constraint
const min = this.parseNumber();
if (this.check('.')) {
this.expectToken('.');
this.expectToken('.');
const max = this.parseNumber();
constraints.range = [min, max];
}
else {
constraints.value = min;
}
}
this.expectToken(')');
return constraints;
}
parseValue() {
this.skipWhitespaceAndComments();
if (this.checkNumber()) {
return this.parseNumber();
}
if (this.check('"')) {
return this.parseString();
}
if (this.checkKeyword('TRUE')) {
this.expectKeyword('TRUE');
return true;
}
if (this.checkKeyword('FALSE')) {
this.expectKeyword('FALSE');
return false;
}
if (this.checkKeyword('NULL')) {
this.expectKeyword('NULL');
return null;
}
return this.parseIdentifier();
}
parseIdentifier() {
this.skipWhitespaceAndComments();
if (!this.isAlpha(this.peek())) {
this.error('Expected identifier');
}
const start = this.position;
while (this.isAlphaNumeric(this.peek())) {
this.advance();
}
return this.input.substring(start, this.position);
}
parseNumber() {
this.skipWhitespaceAndComments();
let negative = false;
if (this.check('-')) {
negative = true;
this.advance();
}
if (!this.isDigit(this.peek())) {
this.error('Expected number');
}
const start = this.position;
while (this.isDigit(this.peek())) {
this.advance();
}
const value = parseInt(this.input.substring(start, this.position), 10);
return negative ? -value : value;
}
parseString() {
this.expectToken('"');
const start = this.position;
while (!this.check('"') && !this.isAtEnd()) {
this.advance();
}
const value = this.input.substring(start, this.position);
this.expectToken('"');
return value;
}
skipWhitespaceAndComments() {
while (true) {
const char = this.peek();
if (char === ' ' || char === '\t' || char === '\r') {
this.advance();
}
else if (char === '\n') {
this.line++;
this.column = 1;
this.advance();
}
else if (char === '-' && this.peekNext() === '-') {
// Skip line comment
while (!this.isAtEnd() && this.peek() !== '\n') {
this.advance();
}
}
else {
break;
}
}
}
checkKeyword(keyword) {
if (this.position + keyword.length > this.input.length) {
return false;
}
const slice = this.input.substring(this.position, this.position + keyword.length);
const nextChar = this.position + keyword.length < this.input.length
? this.input[this.position + keyword.length]
: ' ';
return slice === keyword && !this.isAlphaNumeric(nextChar || ' ');
}
expectKeyword(keyword) {
this.skipWhitespaceAndComments();
if (!this.checkKeyword(keyword)) {
this.error(`Expected keyword '${keyword}'`);
}
this.position += keyword.length;
}
expectToken(token) {
this.skipWhitespaceAndComments();
if (!this.check(token)) {
this.error(`Expected '${token}'`);
}
this.position += token.length;
}
check(expected) {
if (this.position + expected.length > this.input.length) {
return false;
}
return this.input.substring(this.position, this.position + expected.length) === expected;
}
checkNumber() {
return this.isDigit(this.peek()) || (this.peek() === '-' && this.isDigit(this.peekNext()));
}
advance() {
if (!this.isAtEnd()) {
this.column++;
return this.input[this.position++] || '\0';
}
return '\0';
}
peek() {
if (this.isAtEnd())
return '\0';
return this.input[this.position] || '\0';
}
peekNext() {
if (this.position + 1 >= this.input.length)
return '\0';
return this.input[this.position + 1] || '\0';
}
isAtEnd() {
return this.position >= this.input.length;
}
isAlpha(char) {
return /[a-zA-Z]/.test(char);
}
isDigit(char) {
return /[0-9]/.test(char);
}
isAlphaNumeric(char) {
return this.isAlpha(char) || this.isDigit(char);
}
error(message) {
throw new types_1.ParseError(`${message} at line ${this.line}, column ${this.column}`);
}
}
exports.Asn1Parser = Asn1Parser;
//# sourceMappingURL=parser.js.map