1
|
var Reporter = require('../base').Reporter;
|
2
|
var EncoderBuffer = require('../base').EncoderBuffer;
|
3
|
var DecoderBuffer = require('../base').DecoderBuffer;
|
4
|
var assert = require('minimalistic-assert');
|
5
|
|
6
|
// Supported tags
|
7
|
var tags = [
|
8
|
'seq', 'seqof', 'set', 'setof', 'objid', 'bool',
|
9
|
'gentime', 'utctime', 'null_', 'enum', 'int', 'objDesc',
|
10
|
'bitstr', 'bmpstr', 'charstr', 'genstr', 'graphstr', 'ia5str', 'iso646str',
|
11
|
'numstr', 'octstr', 'printstr', 't61str', 'unistr', 'utf8str', 'videostr'
|
12
|
];
|
13
|
|
14
|
// Public methods list
|
15
|
var methods = [
|
16
|
'key', 'obj', 'use', 'optional', 'explicit', 'implicit', 'def', 'choice',
|
17
|
'any', 'contains'
|
18
|
].concat(tags);
|
19
|
|
20
|
// Overrided methods list
|
21
|
var overrided = [
|
22
|
'_peekTag', '_decodeTag', '_use',
|
23
|
'_decodeStr', '_decodeObjid', '_decodeTime',
|
24
|
'_decodeNull', '_decodeInt', '_decodeBool', '_decodeList',
|
25
|
|
26
|
'_encodeComposite', '_encodeStr', '_encodeObjid', '_encodeTime',
|
27
|
'_encodeNull', '_encodeInt', '_encodeBool'
|
28
|
];
|
29
|
|
30
|
function Node(enc, parent) {
|
31
|
var state = {};
|
32
|
this._baseState = state;
|
33
|
|
34
|
state.enc = enc;
|
35
|
|
36
|
state.parent = parent || null;
|
37
|
state.children = null;
|
38
|
|
39
|
// State
|
40
|
state.tag = null;
|
41
|
state.args = null;
|
42
|
state.reverseArgs = null;
|
43
|
state.choice = null;
|
44
|
state.optional = false;
|
45
|
state.any = false;
|
46
|
state.obj = false;
|
47
|
state.use = null;
|
48
|
state.useDecoder = null;
|
49
|
state.key = null;
|
50
|
state['default'] = null;
|
51
|
state.explicit = null;
|
52
|
state.implicit = null;
|
53
|
state.contains = null;
|
54
|
|
55
|
// Should create new instance on each method
|
56
|
if (!state.parent) {
|
57
|
state.children = [];
|
58
|
this._wrap();
|
59
|
}
|
60
|
}
|
61
|
module.exports = Node;
|
62
|
|
63
|
var stateProps = [
|
64
|
'enc', 'parent', 'children', 'tag', 'args', 'reverseArgs', 'choice',
|
65
|
'optional', 'any', 'obj', 'use', 'alteredUse', 'key', 'default', 'explicit',
|
66
|
'implicit', 'contains'
|
67
|
];
|
68
|
|
69
|
Node.prototype.clone = function clone() {
|
70
|
var state = this._baseState;
|
71
|
var cstate = {};
|
72
|
stateProps.forEach(function(prop) {
|
73
|
cstate[prop] = state[prop];
|
74
|
});
|
75
|
var res = new this.constructor(cstate.parent);
|
76
|
res._baseState = cstate;
|
77
|
return res;
|
78
|
};
|
79
|
|
80
|
Node.prototype._wrap = function wrap() {
|
81
|
var state = this._baseState;
|
82
|
methods.forEach(function(method) {
|
83
|
this[method] = function _wrappedMethod() {
|
84
|
var clone = new this.constructor(this);
|
85
|
state.children.push(clone);
|
86
|
return clone[method].apply(clone, arguments);
|
87
|
};
|
88
|
}, this);
|
89
|
};
|
90
|
|
91
|
Node.prototype._init = function init(body) {
|
92
|
var state = this._baseState;
|
93
|
|
94
|
assert(state.parent === null);
|
95
|
body.call(this);
|
96
|
|
97
|
// Filter children
|
98
|
state.children = state.children.filter(function(child) {
|
99
|
return child._baseState.parent === this;
|
100
|
}, this);
|
101
|
assert.equal(state.children.length, 1, 'Root node can have only one child');
|
102
|
};
|
103
|
|
104
|
Node.prototype._useArgs = function useArgs(args) {
|
105
|
var state = this._baseState;
|
106
|
|
107
|
// Filter children and args
|
108
|
var children = args.filter(function(arg) {
|
109
|
return arg instanceof this.constructor;
|
110
|
}, this);
|
111
|
args = args.filter(function(arg) {
|
112
|
return !(arg instanceof this.constructor);
|
113
|
}, this);
|
114
|
|
115
|
if (children.length !== 0) {
|
116
|
assert(state.children === null);
|
117
|
state.children = children;
|
118
|
|
119
|
// Replace parent to maintain backward link
|
120
|
children.forEach(function(child) {
|
121
|
child._baseState.parent = this;
|
122
|
}, this);
|
123
|
}
|
124
|
if (args.length !== 0) {
|
125
|
assert(state.args === null);
|
126
|
state.args = args;
|
127
|
state.reverseArgs = args.map(function(arg) {
|
128
|
if (typeof arg !== 'object' || arg.constructor !== Object)
|
129
|
return arg;
|
130
|
|
131
|
var res = {};
|
132
|
Object.keys(arg).forEach(function(key) {
|
133
|
if (key == (key | 0))
|
134
|
key |= 0;
|
135
|
var value = arg[key];
|
136
|
res[value] = key;
|
137
|
});
|
138
|
return res;
|
139
|
});
|
140
|
}
|
141
|
};
|
142
|
|
143
|
//
|
144
|
// Overrided methods
|
145
|
//
|
146
|
|
147
|
overrided.forEach(function(method) {
|
148
|
Node.prototype[method] = function _overrided() {
|
149
|
var state = this._baseState;
|
150
|
throw new Error(method + ' not implemented for encoding: ' + state.enc);
|
151
|
};
|
152
|
});
|
153
|
|
154
|
//
|
155
|
// Public methods
|
156
|
//
|
157
|
|
158
|
tags.forEach(function(tag) {
|
159
|
Node.prototype[tag] = function _tagMethod() {
|
160
|
var state = this._baseState;
|
161
|
var args = Array.prototype.slice.call(arguments);
|
162
|
|
163
|
assert(state.tag === null);
|
164
|
state.tag = tag;
|
165
|
|
166
|
this._useArgs(args);
|
167
|
|
168
|
return this;
|
169
|
};
|
170
|
});
|
171
|
|
172
|
Node.prototype.use = function use(item) {
|
173
|
assert(item);
|
174
|
var state = this._baseState;
|
175
|
|
176
|
assert(state.use === null);
|
177
|
state.use = item;
|
178
|
|
179
|
return this;
|
180
|
};
|
181
|
|
182
|
Node.prototype.optional = function optional() {
|
183
|
var state = this._baseState;
|
184
|
|
185
|
state.optional = true;
|
186
|
|
187
|
return this;
|
188
|
};
|
189
|
|
190
|
Node.prototype.def = function def(val) {
|
191
|
var state = this._baseState;
|
192
|
|
193
|
assert(state['default'] === null);
|
194
|
state['default'] = val;
|
195
|
state.optional = true;
|
196
|
|
197
|
return this;
|
198
|
};
|
199
|
|
200
|
Node.prototype.explicit = function explicit(num) {
|
201
|
var state = this._baseState;
|
202
|
|
203
|
assert(state.explicit === null && state.implicit === null);
|
204
|
state.explicit = num;
|
205
|
|
206
|
return this;
|
207
|
};
|
208
|
|
209
|
Node.prototype.implicit = function implicit(num) {
|
210
|
var state = this._baseState;
|
211
|
|
212
|
assert(state.explicit === null && state.implicit === null);
|
213
|
state.implicit = num;
|
214
|
|
215
|
return this;
|
216
|
};
|
217
|
|
218
|
Node.prototype.obj = function obj() {
|
219
|
var state = this._baseState;
|
220
|
var args = Array.prototype.slice.call(arguments);
|
221
|
|
222
|
state.obj = true;
|
223
|
|
224
|
if (args.length !== 0)
|
225
|
this._useArgs(args);
|
226
|
|
227
|
return this;
|
228
|
};
|
229
|
|
230
|
Node.prototype.key = function key(newKey) {
|
231
|
var state = this._baseState;
|
232
|
|
233
|
assert(state.key === null);
|
234
|
state.key = newKey;
|
235
|
|
236
|
return this;
|
237
|
};
|
238
|
|
239
|
Node.prototype.any = function any() {
|
240
|
var state = this._baseState;
|
241
|
|
242
|
state.any = true;
|
243
|
|
244
|
return this;
|
245
|
};
|
246
|
|
247
|
Node.prototype.choice = function choice(obj) {
|
248
|
var state = this._baseState;
|
249
|
|
250
|
assert(state.choice === null);
|
251
|
state.choice = obj;
|
252
|
this._useArgs(Object.keys(obj).map(function(key) {
|
253
|
return obj[key];
|
254
|
}));
|
255
|
|
256
|
return this;
|
257
|
};
|
258
|
|
259
|
Node.prototype.contains = function contains(item) {
|
260
|
var state = this._baseState;
|
261
|
|
262
|
assert(state.use === null);
|
263
|
state.contains = item;
|
264
|
|
265
|
return this;
|
266
|
};
|
267
|
|
268
|
//
|
269
|
// Decoding
|
270
|
//
|
271
|
|
272
|
Node.prototype._decode = function decode(input, options) {
|
273
|
var state = this._baseState;
|
274
|
|
275
|
// Decode root node
|
276
|
if (state.parent === null)
|
277
|
return input.wrapResult(state.children[0]._decode(input, options));
|
278
|
|
279
|
var result = state['default'];
|
280
|
var present = true;
|
281
|
|
282
|
var prevKey = null;
|
283
|
if (state.key !== null)
|
284
|
prevKey = input.enterKey(state.key);
|
285
|
|
286
|
// Check if tag is there
|
287
|
if (state.optional) {
|
288
|
var tag = null;
|
289
|
if (state.explicit !== null)
|
290
|
tag = state.explicit;
|
291
|
else if (state.implicit !== null)
|
292
|
tag = state.implicit;
|
293
|
else if (state.tag !== null)
|
294
|
tag = state.tag;
|
295
|
|
296
|
if (tag === null && !state.any) {
|
297
|
// Trial and Error
|
298
|
var save = input.save();
|
299
|
try {
|
300
|
if (state.choice === null)
|
301
|
this._decodeGeneric(state.tag, input, options);
|
302
|
else
|
303
|
this._decodeChoice(input, options);
|
304
|
present = true;
|
305
|
} catch (e) {
|
306
|
present = false;
|
307
|
}
|
308
|
input.restore(save);
|
309
|
} else {
|
310
|
present = this._peekTag(input, tag, state.any);
|
311
|
|
312
|
if (input.isError(present))
|
313
|
return present;
|
314
|
}
|
315
|
}
|
316
|
|
317
|
// Push object on stack
|
318
|
var prevObj;
|
319
|
if (state.obj && present)
|
320
|
prevObj = input.enterObject();
|
321
|
|
322
|
if (present) {
|
323
|
// Unwrap explicit values
|
324
|
if (state.explicit !== null) {
|
325
|
var explicit = this._decodeTag(input, state.explicit);
|
326
|
if (input.isError(explicit))
|
327
|
return explicit;
|
328
|
input = explicit;
|
329
|
}
|
330
|
|
331
|
var start = input.offset;
|
332
|
|
333
|
// Unwrap implicit and normal values
|
334
|
if (state.use === null && state.choice === null) {
|
335
|
if (state.any)
|
336
|
var save = input.save();
|
337
|
var body = this._decodeTag(
|
338
|
input,
|
339
|
state.implicit !== null ? state.implicit : state.tag,
|
340
|
state.any
|
341
|
);
|
342
|
if (input.isError(body))
|
343
|
return body;
|
344
|
|
345
|
if (state.any)
|
346
|
result = input.raw(save);
|
347
|
else
|
348
|
input = body;
|
349
|
}
|
350
|
|
351
|
if (options && options.track && state.tag !== null)
|
352
|
options.track(input.path(), start, input.length, 'tagged');
|
353
|
|
354
|
if (options && options.track && state.tag !== null)
|
355
|
options.track(input.path(), input.offset, input.length, 'content');
|
356
|
|
357
|
// Select proper method for tag
|
358
|
if (state.any)
|
359
|
result = result;
|
360
|
else if (state.choice === null)
|
361
|
result = this._decodeGeneric(state.tag, input, options);
|
362
|
else
|
363
|
result = this._decodeChoice(input, options);
|
364
|
|
365
|
if (input.isError(result))
|
366
|
return result;
|
367
|
|
368
|
// Decode children
|
369
|
if (!state.any && state.choice === null && state.children !== null) {
|
370
|
state.children.forEach(function decodeChildren(child) {
|
371
|
// NOTE: We are ignoring errors here, to let parser continue with other
|
372
|
// parts of encoded data
|
373
|
child._decode(input, options);
|
374
|
});
|
375
|
}
|
376
|
|
377
|
// Decode contained/encoded by schema, only in bit or octet strings
|
378
|
if (state.contains && (state.tag === 'octstr' || state.tag === 'bitstr')) {
|
379
|
var data = new DecoderBuffer(result);
|
380
|
result = this._getUse(state.contains, input._reporterState.obj)
|
381
|
._decode(data, options);
|
382
|
}
|
383
|
}
|
384
|
|
385
|
// Pop object
|
386
|
if (state.obj && present)
|
387
|
result = input.leaveObject(prevObj);
|
388
|
|
389
|
// Set key
|
390
|
if (state.key !== null && (result !== null || present === true))
|
391
|
input.leaveKey(prevKey, state.key, result);
|
392
|
else if (prevKey !== null)
|
393
|
input.exitKey(prevKey);
|
394
|
|
395
|
return result;
|
396
|
};
|
397
|
|
398
|
Node.prototype._decodeGeneric = function decodeGeneric(tag, input, options) {
|
399
|
var state = this._baseState;
|
400
|
|
401
|
if (tag === 'seq' || tag === 'set')
|
402
|
return null;
|
403
|
if (tag === 'seqof' || tag === 'setof')
|
404
|
return this._decodeList(input, tag, state.args[0], options);
|
405
|
else if (/str$/.test(tag))
|
406
|
return this._decodeStr(input, tag, options);
|
407
|
else if (tag === 'objid' && state.args)
|
408
|
return this._decodeObjid(input, state.args[0], state.args[1], options);
|
409
|
else if (tag === 'objid')
|
410
|
return this._decodeObjid(input, null, null, options);
|
411
|
else if (tag === 'gentime' || tag === 'utctime')
|
412
|
return this._decodeTime(input, tag, options);
|
413
|
else if (tag === 'null_')
|
414
|
return this._decodeNull(input, options);
|
415
|
else if (tag === 'bool')
|
416
|
return this._decodeBool(input, options);
|
417
|
else if (tag === 'objDesc')
|
418
|
return this._decodeStr(input, tag, options);
|
419
|
else if (tag === 'int' || tag === 'enum')
|
420
|
return this._decodeInt(input, state.args && state.args[0], options);
|
421
|
|
422
|
if (state.use !== null) {
|
423
|
return this._getUse(state.use, input._reporterState.obj)
|
424
|
._decode(input, options);
|
425
|
} else {
|
426
|
return input.error('unknown tag: ' + tag);
|
427
|
}
|
428
|
};
|
429
|
|
430
|
Node.prototype._getUse = function _getUse(entity, obj) {
|
431
|
|
432
|
var state = this._baseState;
|
433
|
// Create altered use decoder if implicit is set
|
434
|
state.useDecoder = this._use(entity, obj);
|
435
|
assert(state.useDecoder._baseState.parent === null);
|
436
|
state.useDecoder = state.useDecoder._baseState.children[0];
|
437
|
if (state.implicit !== state.useDecoder._baseState.implicit) {
|
438
|
state.useDecoder = state.useDecoder.clone();
|
439
|
state.useDecoder._baseState.implicit = state.implicit;
|
440
|
}
|
441
|
return state.useDecoder;
|
442
|
};
|
443
|
|
444
|
Node.prototype._decodeChoice = function decodeChoice(input, options) {
|
445
|
var state = this._baseState;
|
446
|
var result = null;
|
447
|
var match = false;
|
448
|
|
449
|
Object.keys(state.choice).some(function(key) {
|
450
|
var save = input.save();
|
451
|
var node = state.choice[key];
|
452
|
try {
|
453
|
var value = node._decode(input, options);
|
454
|
if (input.isError(value))
|
455
|
return false;
|
456
|
|
457
|
result = { type: key, value: value };
|
458
|
match = true;
|
459
|
} catch (e) {
|
460
|
input.restore(save);
|
461
|
return false;
|
462
|
}
|
463
|
return true;
|
464
|
}, this);
|
465
|
|
466
|
if (!match)
|
467
|
return input.error('Choice not matched');
|
468
|
|
469
|
return result;
|
470
|
};
|
471
|
|
472
|
//
|
473
|
// Encoding
|
474
|
//
|
475
|
|
476
|
Node.prototype._createEncoderBuffer = function createEncoderBuffer(data) {
|
477
|
return new EncoderBuffer(data, this.reporter);
|
478
|
};
|
479
|
|
480
|
Node.prototype._encode = function encode(data, reporter, parent) {
|
481
|
var state = this._baseState;
|
482
|
if (state['default'] !== null && state['default'] === data)
|
483
|
return;
|
484
|
|
485
|
var result = this._encodeValue(data, reporter, parent);
|
486
|
if (result === undefined)
|
487
|
return;
|
488
|
|
489
|
if (this._skipDefault(result, reporter, parent))
|
490
|
return;
|
491
|
|
492
|
return result;
|
493
|
};
|
494
|
|
495
|
Node.prototype._encodeValue = function encode(data, reporter, parent) {
|
496
|
var state = this._baseState;
|
497
|
|
498
|
// Decode root node
|
499
|
if (state.parent === null)
|
500
|
return state.children[0]._encode(data, reporter || new Reporter());
|
501
|
|
502
|
var result = null;
|
503
|
|
504
|
// Set reporter to share it with a child class
|
505
|
this.reporter = reporter;
|
506
|
|
507
|
// Check if data is there
|
508
|
if (state.optional && data === undefined) {
|
509
|
if (state['default'] !== null)
|
510
|
data = state['default']
|
511
|
else
|
512
|
return;
|
513
|
}
|
514
|
|
515
|
// Encode children first
|
516
|
var content = null;
|
517
|
var primitive = false;
|
518
|
if (state.any) {
|
519
|
// Anything that was given is translated to buffer
|
520
|
result = this._createEncoderBuffer(data);
|
521
|
} else if (state.choice) {
|
522
|
result = this._encodeChoice(data, reporter);
|
523
|
} else if (state.contains) {
|
524
|
content = this._getUse(state.contains, parent)._encode(data, reporter);
|
525
|
primitive = true;
|
526
|
} else if (state.children) {
|
527
|
content = state.children.map(function(child) {
|
528
|
if (child._baseState.tag === 'null_')
|
529
|
return child._encode(null, reporter, data);
|
530
|
|
531
|
if (child._baseState.key === null)
|
532
|
return reporter.error('Child should have a key');
|
533
|
var prevKey = reporter.enterKey(child._baseState.key);
|
534
|
|
535
|
if (typeof data !== 'object')
|
536
|
return reporter.error('Child expected, but input is not object');
|
537
|
|
538
|
var res = child._encode(data[child._baseState.key], reporter, data);
|
539
|
reporter.leaveKey(prevKey);
|
540
|
|
541
|
return res;
|
542
|
}, this).filter(function(child) {
|
543
|
return child;
|
544
|
});
|
545
|
content = this._createEncoderBuffer(content);
|
546
|
} else {
|
547
|
if (state.tag === 'seqof' || state.tag === 'setof') {
|
548
|
// TODO(indutny): this should be thrown on DSL level
|
549
|
if (!(state.args && state.args.length === 1))
|
550
|
return reporter.error('Too many args for : ' + state.tag);
|
551
|
|
552
|
if (!Array.isArray(data))
|
553
|
return reporter.error('seqof/setof, but data is not Array');
|
554
|
|
555
|
var child = this.clone();
|
556
|
child._baseState.implicit = null;
|
557
|
content = this._createEncoderBuffer(data.map(function(item) {
|
558
|
var state = this._baseState;
|
559
|
|
560
|
return this._getUse(state.args[0], data)._encode(item, reporter);
|
561
|
}, child));
|
562
|
} else if (state.use !== null) {
|
563
|
result = this._getUse(state.use, parent)._encode(data, reporter);
|
564
|
} else {
|
565
|
content = this._encodePrimitive(state.tag, data);
|
566
|
primitive = true;
|
567
|
}
|
568
|
}
|
569
|
|
570
|
// Encode data itself
|
571
|
var result;
|
572
|
if (!state.any && state.choice === null) {
|
573
|
var tag = state.implicit !== null ? state.implicit : state.tag;
|
574
|
var cls = state.implicit === null ? 'universal' : 'context';
|
575
|
|
576
|
if (tag === null) {
|
577
|
if (state.use === null)
|
578
|
reporter.error('Tag could be omitted only for .use()');
|
579
|
} else {
|
580
|
if (state.use === null)
|
581
|
result = this._encodeComposite(tag, primitive, cls, content);
|
582
|
}
|
583
|
}
|
584
|
|
585
|
// Wrap in explicit
|
586
|
if (state.explicit !== null)
|
587
|
result = this._encodeComposite(state.explicit, false, 'context', result);
|
588
|
|
589
|
return result;
|
590
|
};
|
591
|
|
592
|
Node.prototype._encodeChoice = function encodeChoice(data, reporter) {
|
593
|
var state = this._baseState;
|
594
|
|
595
|
var node = state.choice[data.type];
|
596
|
if (!node) {
|
597
|
assert(
|
598
|
false,
|
599
|
data.type + ' not found in ' +
|
600
|
JSON.stringify(Object.keys(state.choice)));
|
601
|
}
|
602
|
return node._encode(data.value, reporter);
|
603
|
};
|
604
|
|
605
|
Node.prototype._encodePrimitive = function encodePrimitive(tag, data) {
|
606
|
var state = this._baseState;
|
607
|
|
608
|
if (/str$/.test(tag))
|
609
|
return this._encodeStr(data, tag);
|
610
|
else if (tag === 'objid' && state.args)
|
611
|
return this._encodeObjid(data, state.reverseArgs[0], state.args[1]);
|
612
|
else if (tag === 'objid')
|
613
|
return this._encodeObjid(data, null, null);
|
614
|
else if (tag === 'gentime' || tag === 'utctime')
|
615
|
return this._encodeTime(data, tag);
|
616
|
else if (tag === 'null_')
|
617
|
return this._encodeNull();
|
618
|
else if (tag === 'int' || tag === 'enum')
|
619
|
return this._encodeInt(data, state.args && state.reverseArgs[0]);
|
620
|
else if (tag === 'bool')
|
621
|
return this._encodeBool(data);
|
622
|
else if (tag === 'objDesc')
|
623
|
return this._encodeStr(data, tag);
|
624
|
else
|
625
|
throw new Error('Unsupported tag: ' + tag);
|
626
|
};
|
627
|
|
628
|
Node.prototype._isNumstr = function isNumstr(str) {
|
629
|
return /^[0-9 ]*$/.test(str);
|
630
|
};
|
631
|
|
632
|
Node.prototype._isPrintstr = function isPrintstr(str) {
|
633
|
return /^[A-Za-z0-9 '\(\)\+,\-\.\/:=\?]*$/.test(str);
|
634
|
};
|