1#! /usr/bin/env node
2'use strict';
3
4const
5 fs = require('fs'),
6 patches = { // to fix some known RFCs' ASN.1 syntax errors
7 0: [
8 [ /\n\n[A-Z].*\n\f\n[A-Z].*\n\n/g, '' ], // page change
9 ],
10 2459: [ // currently unsupported
11 [ 'videotex (8) } (0..ub-integer-options)', 'videotex (8) }' ],
12 [ /OBJECT IDENTIFIER \( id-qt-cps \| id-qt-unotice \)/g, 'OBJECT IDENTIFIER' ],
13 [ /SIGNED \{ (SEQUENCE \{[^}]+\})\s*\}/g, 'SEQUENCE { toBeSigned $1, algorithm AlgorithmIdentifier, signature BIT STRING }' ],
14 [ /EXTENSION\.&[^,]+/g, 'OBJECT IDENTIFIER'],
15 ],
16 2986: [ // currently unsupported
17 [ /FROM (InformationFramework|AuthenticationFramework) [a-zA-Z]+/g, 'FROM $1 {joint-iso-itu-t(2) ds(5) module(1) usefulDefinitions(0) 3}' ],
18 [ /[(]v1,[^)]+[)]/g, '' ],
19 [ /[{][{][^}]+[}][}]/g, '' ],
20 [ 'SubjectPublicKeyInfo {ALGORITHM: IOSet}', 'SubjectPublicKeyInfo' ],
21 [ /PKInfoAlgorithms ALGORITHM ::=[^}]+[}]/g, '' ],
22 [ /(Attributes?) [{] ATTRIBUTE:IOSet [}]/g, '$1' ],
23 [ /CRIAttributes +ATTRIBUTE +::=[^}]+[}]/g, '' ],
24 [ /[A-Z]+[.]&id[(][{]IOSet[}][)]/g, 'OBJECT IDENTIFIER' ],
25 [ /[A-Z]+[.]&Type[(][{]IOSet[}][{]@[a-z]+[}][)]/g, 'ANY' ],
26 [ /(AlgorithmIdentifier) [{]ALGORITHM:IOSet [}]/g, '$1' ],
27 [ /SignatureAlgorithms ALGORITHM ::=[^}]+[}]/g, '' ],
28 ],
29 3161: [ // actual syntax errors
30 [ /--.*}/g, '}' ],
31 [ /^( +)--.*\n(?:\1 .*\n)+/mg, '' ],
32 [ /addInfoNotAvailable \(17\)/g, '$&,' ],
33 ],
34 5208: [ // currently unsupported
35 [ 'FROM InformationFramework informationFramework', 'FROM InformationFramework {joint-iso-itu-t(2) ds(5) module(1) usefulDefinitions(0) 3}' ],
36 [ ' {{PrivateKeyAlgorithms}}', '' ],
37 [ 'Version ::= INTEGER {v1(0)} (v1,...)', 'Version ::= INTEGER {v1(0)}' ],
38 [ ' {{KeyEncryptionAlgorithms}}', '' ],
39 [ /\.\.\. -- For local profiles/g, '' ],
40 ],
41 5280: [ // currently unsupported
42 [ 'videotex (8) } (0..ub-integer-options)', 'videotex (8) }' ],
43 [ /OBJECT IDENTIFIER \( id-qt-cps \| id-qt-unotice \)/g, 'OBJECT IDENTIFIER' ],
44 ],
45 };
46
47// const reWhitespace = /(?:\s|--(?:[}-]?[^\n}-])*(?:\n|--))*/y;
48const reWhitespace = /(?:\s|--(?:-?[^\n-])*(?:\n|--))*/my;
49const reIdentifier = /[a-zA-Z](?:[-]?[a-zA-Z0-9])*/y;
50const reNumber = /0|[1-9][0-9]*/y;
51const reToken = /[(){},[\];]|::=|OPTIONAL|DEFAULT|NULL|TRUE|FALSE|\.\.|OF|SIZE|MIN|MAX|DEFINED BY|DEFINITIONS|TAGS|BEGIN|EXPORTS|IMPORTS|FROM|END/y;
52const reType = /ANY|BOOLEAN|INTEGER|(?:BIT|OCTET)\s+STRING|OBJECT\s+IDENTIFIER|SEQUENCE|SET|CHOICE|ENUMERATED|(?:Generalized|UTC)Time|(?:BMP|General|Graphic|IA5|ISO64|Numeric|Printable|Teletex|T61|Universal|UTF8|Videotex|Visible)String/y;
53const reTagClass = /UNIVERSAL|APPLICATION|PRIVATE|/y;
54const reTagType = /IMPLICIT|EXPLICIT|/y;
55const reTagDefault = /(AUTOMATIC|IMPLICIT|EXPLICIT) TAGS|/y;
56
57let asn1;
58let currentMod;
59
60function searchImportedValue(id) {
61 for (let imp of Object.values(currentMod.imports))
62 for (let name of imp.types)
63 if (name == id) {
64 if (!(imp.oid in asn1))
65 throw new Error('Cannot find module: ' + imp.oid + ' ' + id);
66 if (id in asn1[imp.oid].values)
67 return asn1[imp.oid].values[id];
68 throw new Error('Cannot find imported value: ' + imp.oid + ' ' + id);
69 }
70 throw new Error('Cannot find imported value in any module: ' + id);
71}
72
73class Parser {
74 constructor(enc, pos) {
75 this.enc = enc;
76 this.pos = pos;
77 this.start = pos;
78 }
79 getChar(pos) {
80 if (pos === undefined)
81 pos = this.pos++;
82 if (pos >= this.enc.length)
83 throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length;
84 return this.enc.charAt(pos);
85 }
86 exception(s, pos) {
87 if (pos == undefined) pos = this.pos;
88 let from = Math.max(pos - 30, this.start);
89 let to = Math.min(pos + 30, this.enc.length);
90 let ctx = '';
91 let arrow = '';
92 let i = from;
93 for (; i < pos; ++i) {
94 ctx += this.getChar(i);
95 arrow += ' ';
96 }
97 ctx += this.getChar(i++);
98 arrow += '^';
99 for (; i < to; ++i)
100 ctx += this.getChar(i);
101 throw new Error('[position ' + pos + '] ' + s + '\n' + ctx.replace(/\s/g, ' ') + '\n' + arrow);
102 }
103 peek() {
104 return (typeof this.enc == 'string') ? this.enc.charCodeAt(this.pos) : this.enc[this.pos];
105 }
106 peekChar() {
107 return (typeof this.enc == 'string') ? this.enc.charAt(this.pos) : String.fromCharCode(this.enc[this.pos]);
108 }
109 isWhitespace() {
110 let c = this.peekChar();
111 return c == ' ' || c == '\n';
112 }
113 isDigit() {
114 let c = this.peekChar();
115 return c >= '0' && c <= '9';
116 }
117 skipWhitespace() {
118 reWhitespace.lastIndex = this.pos;
119 let s = reWhitespace.exec(this.enc);
120 if (s)
121 this.pos = reWhitespace.lastIndex;
122 }
123 // DefStream.prototype.eat = function (str) {
124 // for (let i = 0; i < str.length; ++i) {
125 // let c = this.getChar();
126 // if (c != str.charAt(i))
127 // throw new Error("Found '" + c + "', was expecting '" + str.charAt(i) + "'");
128 // }
129 // };
130 getRegEx(type, re) {
131 this.skipWhitespace();
132 re.lastIndex = this.pos;
133 let s = re.exec(this.enc); //TODO: does not work with typed arrays
134 if (!s)
135 this.exception("Found '" + this.peekChar() + "', was expecting a " + type);
136 s = s[0];
137 // console.log('[debug] getRexEx@' + this.pos + ' = ' + s);
138 this.pos = re.lastIndex;
139 this.skipWhitespace();
140 return s;
141 }
142 parseIdentifier() {
143 let id = this.getRegEx('identifier', reIdentifier);
144 // console.log('[debug] parseIdentifier = ' + id);
145 return id;
146 }
147 parseNumber() {
148 let id = this.getRegEx('number', reNumber);
149 // console.log('[debug] parseNumber = ' + id);
150 return id;
151 }
152 parseToken() {
153 let tok = this.getRegEx('token', reToken);
154 return tok;
155 }
156 tryToken(expect) {
157 let p = this.pos;
158 let t;
159 try { t = this.parseToken(); } catch (e) { /*ignore*/ }
160 // console.log('[debug] tryToken(' + expect + ') = ' + t);
161 if (t == expect)
162 return true;
163 else {
164 this.pos = p;
165 return false;
166 }
167 }
168 expectToken(expect) {
169 let p = this.pos;
170 let t;
171 try { t = this.parseToken(); }
172 catch (e) { console.log('[debug] expectToken', e); }
173 // console.log('[debug] expectToken(' + expect + ') = ' + t);
174 if (t != expect) {
175 this.pos = p;
176 this.exception("Found '" + t + "', was expecting '" + expect + "'");
177 }
178 }
179 parseNumberOrValue() {
180 if (this.isDigit())
181 return +this.parseNumber();
182 return this.parseIdentifier();
183 }
184 parseRange() {
185 let min = this.tryToken('MIN') ? 'MIN' : this.parseNumberOrValue();
186 if (this.tryToken('..')) {
187 let max = this.tryToken('MAX') ? 'MAX' : this.parseNumberOrValue();
188 return [min, max];
189 }
190 return min;
191 }
192 parseBuiltinType() {
193 let x = {
194 name: this.getRegEx('type', reType),
195 type: 'builtin',
196 };
197 // console.log('[debug] parseType = ' + x.name);
198 try {
199 switch (x.name) {
200 case 'ANY':
201 if (this.tryToken('DEFINED BY'))
202 x.definedBy = this.parseIdentifier();
203 break;
204 case 'BOOLEAN':
205 case 'OCTET STRING':
206 case 'OBJECT IDENTIFIER':
207 break;
208 case 'CHOICE':
209 x.content = this.parseElementTypeList();
210 break;
211 case 'SEQUENCE':
212 case 'SET':
213 if (this.peekChar() == '{') {
214 x.content = this.parseElementTypeList();
215 } else {
216 x.typeOf = 1;
217 if (this.tryToken('SIZE')) {
218 this.expectToken('(');
219 x.size = this.parseRange();
220 this.expectToken(')');
221 }
222 this.expectToken('OF');
223 x.content = [this.parseType()];
224 }
225 break;
226 case 'INTEGER':
227 if (this.tryToken('(')) {
228 x.range = this.parseRange();
229 this.expectToken(')');
230 }
231 // falls through
232 case 'ENUMERATED':
233 case 'BIT STRING':
234 if (this.tryToken('{')) {
235 x.content = {};
236 do {
237 let id = this.parseIdentifier();
238 this.expectToken('(');
239 let val = this.parseNumber(); //TODO: signed
240 this.expectToken(')');
241 x.content[id] = +val;
242 } while (this.tryToken(','));
243 this.expectToken('}');
244 }
245 break;
246 case 'BMPString':
247 case 'GeneralString':
248 case 'GraphicString':
249 case 'IA5String':
250 case 'ISO646String':
251 case 'NumericString':
252 case 'PrintableString':
253 case 'TeletexString':
254 case 'T61String':
255 case 'UniversalString':
256 case 'UTF8String':
257 case 'VideotexString':
258 case 'VisibleString':
259 if (this.tryToken('(')) {
260 if (this.tryToken('SIZE')) {
261 this.expectToken('(');
262 x.size = this.parseRange();
263 this.expectToken(')');
264 }
265 this.expectToken(')');
266 }
267 break;
268 default:
269 x.content = 'TODO:unknown';
270 }
271 } catch (e) {
272 console.log('[debug] parseBuiltinType content', e);
273 x.content = 'TODO:exception';
274 }
275 return x;
276 }
277 parseTaggedType() {
278 this.expectToken('[');
279 let tagClass = this.getRegEx('class', reTagClass) || 'CONTEXT'; //TODO: use module defaults
280 let t = this.parseNumber();
281 this.expectToken(']');
282 let plicit = this.getRegEx('explicit/implicit', reTagType);
283 if (plicit == '') plicit = currentMod.tagDefault;
284 let x = this.parseType();
285 return {
286 name: '[' + t + ']',
287 type: 'tag',
288 'class': tagClass,
289 explicit: (plicit == 'EXPLICIT'),
290 content: [{ name: '', type: x }],
291 };
292 }
293 parseType() {
294 if (this.peekChar() == '[')
295 return this.parseTaggedType();
296 let p = this.pos;
297 try {
298 return this.parseBuiltinType();
299 } catch (e) {
300 // console.log('[debug] parseAssignment failed on parseType', e);
301 this.pos = p;
302 let x = {
303 name: this.parseIdentifier(),
304 type: 'defined',
305 };
306 // let from = searchImportedType(x.name);
307 // if (from)
308 // x.module = from;
309 return x;
310 //TODO "restricted string type"
311 }
312 }
313 parseValueBoolean() {
314 let p = this.pos;
315 let t = this.parseToken();
316 if (t == 'TRUE')
317 return true;
318 if (t == 'FALSE')
319 return false;
320 this.pos = p;
321 this.exception("Found '" + t + "', was expecting a boolean");
322 }
323 parseValueOID() {
324 this.expectToken('{');
325 let v = '';
326 while (!this.tryToken('}')) {
327 let p = this.pos;
328 let val;
329 if (this.isDigit())
330 val = this.parseNumber();
331 else {
332 this.pos = p;
333 let id = this.parseIdentifier();
334 if (this.tryToken('(')) {
335 val = this.parseNumber();
336 this.expectToken(')');
337 } else {
338 if (id in currentMod.values) // defined in local module
339 val = currentMod.values[id].value;
340 else
341 val = searchImportedValue(id);
342 }
343 }
344 if (v.length) v += '.';
345 v += val;
346 }
347 return v;
348 }
349 parseValue() {
350 let c = this.peekChar();
351 if (c == '{')
352 return this.parseValueOID();
353 if (c >= '0' && c <= '9')
354 return +this.parseNumber();
355 if (c == '-')
356 return -this.parseNumber();
357 let p = this.pos;
358 try {
359 switch (this.parseToken()) {
360 case 'TRUE':
361 return true;
362 case 'FALSE':
363 return false;
364 case 'NULL':
365 return null;
366 }
367 } catch (e) {
368 this.pos = p;
369 }
370 p = this.pos;
371 try {
372 return this.parseIdentifier();
373 } catch (e) {
374 this.pos = p;
375 }
376 this.exception('Unknown value type.');
377 }
378 /*DefStream.prototype.parseValue = function (type) {
379 console.log('[debug] parseValue type:', type);
380 if (type.type == 'defined') {
381 if (!(type.name in types))
382 this.exception("Missing type: " + type.name);
383 type = types[type.name];
384 }
385 switch (type.name) {
386 case 'BOOLEAN':
387 return this.parseValueBoolean();
388 case 'OBJECT IDENTIFIER':
389 return this.parseValueOID();
390 default:
391 console.log('[debug] parseValue unknown:', type);
392 return 'TODO:value';
393 }
394 }*/
395 parseElementType() {
396 let x = Object.assign({ id: this.parseIdentifier() }, this.parseType());
397 // console.log('[debug] parseElementType 1:', x);
398 if (this.tryToken('OPTIONAL'))
399 x.optional = true;
400 if (this.tryToken('DEFAULT'))
401 x.default = this.parseValue(x.type);
402 // console.log('[debug] parseElementType 2:', x);
403 return x;
404 }
405 parseElementTypeList() {
406 let v = [];
407 this.expectToken('{');
408 do {
409 v.push(this.parseElementType());
410 } while (this.tryToken(','));
411 this.expectToken('}');
412 return v;
413 }
414 parseAssignment() {
415 let name = this.parseIdentifier();
416 if (this.tryToken('::=')) { // type assignment
417 // console.log('type name', name);
418 let type = this.parseType();
419 currentMod.types[name] = { name, type };
420 return currentMod.types[name];
421 } else { // value assignment
422 // console.log('value name', name);
423 let type = this.parseType();
424 // console.log('[debug] parseAssignment type:', type);
425 this.expectToken('::=');
426 let value = this.parseValue(type);
427 currentMod.values[name] = { name, type, value };
428 return currentMod.values[name];
429 }
430 }
431 parseModuleIdentifier() {
432 return {
433 name: this.parseIdentifier(),
434 oid: this.parseValueOID(),
435 };
436 }
437 parseSymbolsImported() {
438 let imports = {};
439 do {
440 let l = [];
441 do {
442 l.push(this.parseIdentifier());
443 } while (this.tryToken(','));
444 this.expectToken('FROM');
445 let mod = this.parseModuleIdentifier();
446 mod.types = l;
447 imports[mod.oid] = mod;
448 } while (this.peekChar() != ';');
449 return imports;
450 }
451 parseModuleDefinition(file) {
452 let mod = this.parseModuleIdentifier();
453 currentMod = mod; // for deeply nested parsers
454 mod.source = file;
455 this.expectToken('DEFINITIONS');
456 mod.tagDefault = this.getRegEx('tag default', reTagDefault).split(' ')[0];
457 this.expectToken('::=');
458 this.expectToken('BEGIN');
459 //TODO this.tryToken('EXPORTS')
460 if (this.tryToken('IMPORTS')) {
461 mod.imports = this.parseSymbolsImported();
462 this.expectToken(';');
463 }
464 mod.values = {};
465 mod.types = {};
466 while (!this.tryToken('END'))
467 this.parseAssignment();
468 return mod;
469 }
470}
471
472let s = fs.readFileSync(process.argv[2], 'utf8');
473let num = /^Request for Comments: ([0-9]+)/m.exec(s)[1];
474console.log('RFC:', num);
475for (let p of patches[0])
476 s = s.replace(p[0], p[1]);
477if (num in patches)
478 for (let p of patches[num])
479 s = s.replace(p[0], p[1]);
480fs.writeFileSync(process.argv[2].replace(/[.]txt$/, '_patched.txt'), s, 'utf8');
481// console.log(s);
482asn1 = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
483const reModuleDefinition = /\s[A-Z](?:[-]?[a-zA-Z0-9])*\s*\{[^}]+\}\s*(^--.*|\n)*DEFINITIONS/gm;
484let m;
485while ((m = reModuleDefinition.exec(s))) {
486 new Parser(s, m.index).parseModuleDefinition(process.argv[2]);
487 console.log('Module:', currentMod.name);
488 // fs.writeFileSync('rfc' + num + '.json', JSON.stringify(currentMod, null, 2) + '\n', 'utf8');
489 asn1[currentMod.oid] = currentMod;
490}
491/*asn1 = Object.keys(asn1).sort().reduce(
492 (obj, key) => {
493 obj[key] = asn1[key];
494 return obj;
495 },
496 {}
497);*/
498fs.writeFileSync(process.argv[3], JSON.stringify(asn1, null, 2) + '\n', 'utf8');
499// console.log('Module:', mod);
500/*while ((idx = s.indexOf('::=', idx + 1)) >= 0) {
501 let line = s.lastIndexOf('\n', idx) + 1;
502 // console.log('[line] ' + s.slice(line, line+30));
503 try {
504 let a = new DefStream(s, line).parseAssignment();
505 // console.log('[assignment]', util.inspect(a, {showHidden: false, depth: null, colors: true}));
506 } catch (e) {
507 console.log('Error:', e);
508 }
509}*/
510console.log('Done.');