cap-modules
Version:
Expanded ASN.1 modules for CAMEL Application Part (CAP) (ETSI TS 129 078) in JSON format
506 lines (404 loc) • 11 kB
JavaScript
const qualifierRe = /(?:\.\&\w+)?[\w\s]*\([^()]*(?:\([^)]*\))*\)|DEFAULT[\w\s]+/;
const postQualifierRe = /(?:\.\&\w+)?\([^()]*(?:\([^)]*\))*\)/;
const opCodes = {};
let capArgs = null;
let paramsMap = {};
function mixin(target, source) {
for (var p in source) {
if (source[p] !== undefined) {
target[p] = source[p];
}
}
return target;
}
function getBlockContents(s, startIdx) {
let nesting = 0;
let idx = startIdx;
let ch = null;
while (true) {
ch = s[idx];
switch (ch) {
case '{':
nesting++;
break;
case '}':
if (!nesting) {
throw new Error('Unmatched }');
}
if (nesting === 1) {
return s.substring(startIdx + 1, idx);
}
nesting--;
break;
}
idx++;
if (idx === s.length) {
throw new Error('Block not found');
}
}
}
function parseSequence(s) {
const elements = [];
let idx = 0;
let match = null;
let name = null;
let element = null;
while (true) {
match = /^(\.{3}|[\w-]+\b)\s?/.exec(s.slice(idx));
if (!match) {
throw new Error('Identifier not found');
}
name = match[1];
idx += match[0].length;
if (name !== '...') {
element = parseElement(s.slice(idx));
idx += element.length;
delete element.length;
let qualifiers = undefined;
let optional = undefined;
const qualifierRe = new RegExp(
/^(QUALIFIER)?\s?/
.source
.replace('QUALIFIER', postQualifierRe.source)
);
match = qualifierRe.exec(s.slice(idx));
if (match) {
qualifiers = match[1];
idx += match[0].length;
}
if (/^OPTIONAL/.test(s.slice(idx))) {
optional = true;
idx += 'OPTIONAL'.length;
}
elements.push(mixin(element, {
name,
qualifiers,
optional
}));
}
if (s[idx] !== ',') {
break;
}
idx++;
}
return elements;
}
function parseBitString(s) {
return s
.split(',')
.filter((s) => s !== '...')
.map((s) => {
const match = /^([\w-]+)\((\d+)\)/.exec(s);
if (!match) {
throw new Error('Could not parse bit string value');
}
return {
name: match[1],
value: parseInt(match[2], 10)
};
});
}
function parseElement(s) {
const types = [
'OCTET STRING',
'INTEGER',
'NULL',
'BOOLEAN',
'NumericString',
'SEQUENCE',
'SET',
'CHOICE',
'BIT STRING',
'ENUMERATED',
'MAP-EXTENSION',
'OBJECT IDENTIFIER',
'IA5String',
'EXTENSION'
];
if (/^TRUE\b/.test(s)) {
return {
type: 'BOOLEAN',
value: true,
length: 'TRUE'.length
};
} else if (/^FALSE\b/.test(s)) {
return {
type: 'BOOLEAN',
value: false,
length: 'FALSE'.length
};
}
const typeRe = new RegExp(
/^(?:\[(\d+)\])?(?:(IMPLICIT)\s)?(TYPE)\s?(QUALIFIER)?(?:\b(OF\s))?/
.source
.replace('TYPE', types.join('|'))
.replace('QUALIFIER', qualifierRe.source),
'g'
);
let tag = null;
let implicit = null;
let type = null;
let qualifiers = null;
let ctorOf = null;
let element = null;
let length = 0;
const match = typeRe.exec(s);
if (match) {
tag = match[1] && parseInt(match[1], 10);
implicit = match[2] && true;
type = match[3];
qualifiers = match[4];
ctorOf = !!match[5];
length = match[0].length;
element = {
name: undefined, // to hold first position in output
tag,
implicit,
type,
qualifiers,
length
};
if (ctorOf) {
const el = parseElement(s.slice(typeRe.lastIndex));
element.ofElement = el;
element.length += el.length;
delete el.length;
} else {
let block = null;
switch (type) {
case 'CHOICE':
case 'SEQUENCE':
case 'SET':
block = getBlockContents(s, typeRe.lastIndex);
element.length += block.length + 2;
if (block !== '...') {
element.elements = parseSequence(block);
} else {
element.elements = [];
}
break;
case 'BIT STRING':
case 'ENUMERATED':
if (s[typeRe.lastIndex] === '{') {
block = getBlockContents(s, typeRe.lastIndex);
element.length += block.length + 2;
element.values = parseBitString(block);
}
break;
case 'OCTET STRING':
if (!/^\(?SIZE\b/.test(element.qualifiers)) {
element.qualifiers = null;
element.length = 'OCTET STRING'.length;
}
break;
default:
break;
}
}
element.qualifiers = resolveQualifier(element.qualifiers);
return element;
} else {
return parseSubtype(s);
}
}
function parseSubtype(s) {
const argumentRe = /^(?:\[(\d+)\])?([\w-]+)\s?((?:DEFAULT\s?(?:{?[\w'\s]+}?))|(?:{[^{}]*(?:{[\w]+})*}))?\s?/g;
let matchArg = null;
if (matchArg = argumentRe.exec(s)) {
const arg = findArg(matchArg[2]);
if (arg) {
const elem = parseElement(arg);
elem.tag = matchArg[1] && parseInt(matchArg[1], 10);
if (matchArg[3] && matchArg[3] !== '{bound}') {
elem.qualifiers = resolveQualifier(matchArg[3]);
}
elem.length = argumentRe.lastIndex;
return elem;
}
}
throw new Error('Could not find Subtype');
}
function createMapOfBlock(block) {
let idx = 0;
let match = null;
let elements = {};
while (true) {
match = /(&?[\w-]+)\s?(\d+|INTEGER|(?:&[\w-]+))/.exec(block.slice(idx));
if (!match) {
throw new Error('Block identifier not found');
}
elements[match[1]] = match[2];
idx += match[0].length;
if (!block[idx]) {
break;
}
idx++;
}
return elements;
}
function parseParametersBound(asn) {
const paramsRe = /\b(PARAMETERS-BOUND::=CLASS)/g;
const paramsWithSyntaxRe = /\bWITH SYNTAX/g;
const capSpecBoundSetRe = /\bcAPSpecificBoundSet PARAMETERS-BOUND::=/g;
const paramsBoundMatch = paramsRe.exec(asn);
if (paramsBoundMatch) {
let idx = paramsRe.lastIndex;
const blockParamsBound = getBlockContents(asn, idx);
idx += blockParamsBound.length + 2;
const paramsSyntaxMatch = paramsWithSyntaxRe.exec(asn.slice(idx));
if (paramsSyntaxMatch) {
idx += paramsWithSyntaxRe.lastIndex;
const blockParamsSyntax = getBlockContents(asn, idx);
idx += blockParamsSyntax.length + 2;
const capSpecBoundSetMatch = capSpecBoundSetRe.exec(asn.slice(idx));
if (capSpecBoundSetMatch) {
idx += capSpecBoundSetRe.lastIndex;
const capSpecMap = createMapOfBlock(getBlockContents(asn, idx));
const withSyntaxMap = createMapOfBlock(blockParamsSyntax);
const paramsBoundMap = createMapOfBlock(blockParamsBound);
Object.keys(withSyntaxMap).forEach((key) => {
paramsBoundMap[withSyntaxMap[key]] = capSpecMap[key];
});
return paramsBoundMap;
}
}
} else {
throw new Error('No PAARAMETERS-BOUND found.');
}
}
function resolveQualifier(qualifier) {
let refinedQualifier = qualifier;
if (!refinedQualifier) {
return refinedQualifier;
}
const boundRe = /\b(?:\(|\.\.)(?:bound\.)?([a-zA-Z&]+)/;
let match = null;
while (match = boundRe.exec(refinedQualifier)) {
if (paramsMap[match[1]]) {
refinedQualifier = refinedQualifier.replace(`bound.${match[1]}`, paramsMap[match[1]]);
} else {
const constVarRe = new RegExp(
/\b(?:VAR\sINTEGER)::=([\d]+)/
.source
.replace('VAR', match[1])
);
const varMatch = constVarRe.exec(capArgs);
refinedQualifier = refinedQualifier.replace(match[1], varMatch[1]);
}
}
return refinedQualifier;
}
function findArg(argName) {
const argRe = new RegExp(
/(?:[^-])\b(ARG)(?:{[\w-:]*})?::=/
.source
.replace('ARG', argName),
'g'
);
let match = null;
if (match = argRe.exec(capArgs)) {
return capArgs.slice(argRe.lastIndex);
}
}
function parseOpCode(s) {
const codeRe = /(opcode-[\w]+) Code::=local:(\d+)/g;
let match = null;
while (match = codeRe.exec(s)) {
const opName = match[1];
if (match[2]) {
opCodes[opName] = parseInt(match[2], 10);
}
}
}
function parseOpBody(s) {
const argumentRe = /ARGUMENT\s?/g;
const returnResultRe = /RETURN RESULT\s?/g;
const resultRe = /RESULT\s?/g;
const codeRe = /CODE\s?/g;
let argument = null;
let result = null;
let code = null;
if (argumentRe.exec(s)) {
argument = parseElement(s.slice(argumentRe.lastIndex));
delete argument.length;
}
if (returnResultRe.exec(s)) {
result = parseElement(s.slice(returnResultRe.lastIndex));
delete result.length;
} else if (resultRe.exec(s)) {
result = parseElement(s.slice(resultRe.lastIndex));
delete result.length;
}
if (codeRe.exec(s)) {
const match = /(^opcode-[\w]+)/.exec(s.slice(codeRe.lastIndex));
if (match) {
code = opCodes[match[1]];
} else {
throw new Error('Could not parse code');
}
}
return {
argument,
result,
code
};
}
function parseErrorBody(s) {
const parameterRe = /PARAMETER\s?/g;
const codeRe = /CODE\s?/g;
let parameter = null;
let result = null;
let code = null;
if (parameterRe.exec(s)) {
parameter = parseElement(s.slice(parameterRe.lastIndex));
delete parameter.length;
}
if (codeRe.exec(s)) {
const match = /^local:(\d+)/.exec(s.slice(codeRe.lastIndex));
if (match) {
code = parseInt(match[1], 10);
} else {
throw new Error('Could not parse code');
}
}
return {
parameter,
code
};
}
function parse(s, args) {
s = s
.split('\n') // Split into row
.filter((row) => !/^--/.test(row)) // Filter out comment rows
.join(''); // Join rows
s = s
.replace(/\s+/g, ' ') // Replace sequental whitespace with a single space
.replace(/\B \b|\b \B|\B \B/g, ''); // Replace all space except between words
args = args
.split('\n') // Split into row
.filter((row) => !/--/.test(row)) // Filter out comment rows
.join(' '); // Join rows
capArgs = args
.replace(/\s+/g, ' ') // Replace sequental whitespace with a single space
.replace(/\B \b|\b \B|\B \B/g, ''); // Replace all space except between words
// return capArgs;
parseOpCode(s);
paramsMap = parseParametersBound(capArgs);
const opRe = /\b([\w-]+)\s?(?:{[\w-:]+})\s?(OPERATION)::=/g;
let match = null;
const blocks = {};
while (match = opRe.exec(s)) {
const operationCodeName = match[1];
let block = null;
if (match[2] === 'OPERATION') {
block = parseOpBody(getBlockContents(s, opRe.lastIndex));
} else if (match[2] === 'ERROR') {
block = parseErrorBody(getBlockContents(s, opRe.lastIndex));
}
blocks[operationCodeName] = block;
}
return { blocks };
}
module.exports = Object.freeze({
parse
});