| var Reporter = require('../base').Reporter; |
| var EncoderBuffer = require('../base').EncoderBuffer; |
| var DecoderBuffer = require('../base').DecoderBuffer; |
| var assert = require('minimalistic-assert'); |
| |
| // Supported tags |
| var tags = [ |
| 'seq', 'seqof', 'set', 'setof', 'objid', 'bool', |
| 'gentime', 'utctime', 'null_', 'enum', 'int', 'objDesc', |
| 'bitstr', 'bmpstr', 'charstr', 'genstr', 'graphstr', 'ia5str', 'iso646str', |
| 'numstr', 'octstr', 'printstr', 't61str', 'unistr', 'utf8str', 'videostr' |
| ]; |
| |
| // Public methods list |
| var methods = [ |
| 'key', 'obj', 'use', 'optional', 'explicit', 'implicit', 'def', 'choice', |
| 'any', 'contains' |
| ].concat(tags); |
| |
| // Overrided methods list |
| var overrided = [ |
| '_peekTag', '_decodeTag', '_use', |
| '_decodeStr', '_decodeObjid', '_decodeTime', |
| '_decodeNull', '_decodeInt', '_decodeBool', '_decodeList', |
| |
| '_encodeComposite', '_encodeStr', '_encodeObjid', '_encodeTime', |
| '_encodeNull', '_encodeInt', '_encodeBool' |
| ]; |
| |
| function Node(enc, parent) { |
| var state = {}; |
| this._baseState = state; |
| |
| state.enc = enc; |
| |
| state.parent = parent || null; |
| state.children = null; |
| |
| // State |
| state.tag = null; |
| state.args = null; |
| state.reverseArgs = null; |
| state.choice = null; |
| state.optional = false; |
| state.any = false; |
| state.obj = false; |
| state.use = null; |
| state.useDecoder = null; |
| state.key = null; |
| state['default'] = null; |
| state.explicit = null; |
| state.implicit = null; |
| state.contains = null; |
| |
| // Should create new instance on each method |
| if (!state.parent) { |
| state.children = []; |
| this._wrap(); |
| } |
| } |
| module.exports = Node; |
| |
| var stateProps = [ |
| 'enc', 'parent', 'children', 'tag', 'args', 'reverseArgs', 'choice', |
| 'optional', 'any', 'obj', 'use', 'alteredUse', 'key', 'default', 'explicit', |
| 'implicit', 'contains' |
| ]; |
| |
| Node.prototype.clone = function clone() { |
| var state = this._baseState; |
| var cstate = {}; |
| stateProps.forEach(function(prop) { |
| cstate[prop] = state[prop]; |
| }); |
| var res = new this.constructor(cstate.parent); |
| res._baseState = cstate; |
| return res; |
| }; |
| |
| Node.prototype._wrap = function wrap() { |
| var state = this._baseState; |
| methods.forEach(function(method) { |
| this[method] = function _wrappedMethod() { |
| var clone = new this.constructor(this); |
| state.children.push(clone); |
| return clone[method].apply(clone, arguments); |
| }; |
| }, this); |
| }; |
| |
| Node.prototype._init = function init(body) { |
| var state = this._baseState; |
| |
| assert(state.parent === null); |
| body.call(this); |
| |
| // Filter children |
| state.children = state.children.filter(function(child) { |
| return child._baseState.parent === this; |
| }, this); |
| assert.equal(state.children.length, 1, 'Root node can have only one child'); |
| }; |
| |
| Node.prototype._useArgs = function useArgs(args) { |
| var state = this._baseState; |
| |
| // Filter children and args |
| var children = args.filter(function(arg) { |
| return arg instanceof this.constructor; |
| }, this); |
| args = args.filter(function(arg) { |
| return !(arg instanceof this.constructor); |
| }, this); |
| |
| if (children.length !== 0) { |
| assert(state.children === null); |
| state.children = children; |
| |
| // Replace parent to maintain backward link |
| children.forEach(function(child) { |
| child._baseState.parent = this; |
| }, this); |
| } |
| if (args.length !== 0) { |
| assert(state.args === null); |
| state.args = args; |
| state.reverseArgs = args.map(function(arg) { |
| if (typeof arg !== 'object' || arg.constructor !== Object) |
| return arg; |
| |
| var res = {}; |
| Object.keys(arg).forEach(function(key) { |
| if (key == (key | 0)) |
| key |= 0; |
| var value = arg[key]; |
| res[value] = key; |
| }); |
| return res; |
| }); |
| } |
| }; |
| |
| // |
| // Overrided methods |
| // |
| |
| overrided.forEach(function(method) { |
| Node.prototype[method] = function _overrided() { |
| var state = this._baseState; |
| throw new Error(method + ' not implemented for encoding: ' + state.enc); |
| }; |
| }); |
| |
| // |
| // Public methods |
| // |
| |
| tags.forEach(function(tag) { |
| Node.prototype[tag] = function _tagMethod() { |
| var state = this._baseState; |
| var args = Array.prototype.slice.call(arguments); |
| |
| assert(state.tag === null); |
| state.tag = tag; |
| |
| this._useArgs(args); |
| |
| return this; |
| }; |
| }); |
| |
| Node.prototype.use = function use(item) { |
| assert(item); |
| var state = this._baseState; |
| |
| assert(state.use === null); |
| state.use = item; |
| |
| return this; |
| }; |
| |
| Node.prototype.optional = function optional() { |
| var state = this._baseState; |
| |
| state.optional = true; |
| |
| return this; |
| }; |
| |
| Node.prototype.def = function def(val) { |
| var state = this._baseState; |
| |
| assert(state['default'] === null); |
| state['default'] = val; |
| state.optional = true; |
| |
| return this; |
| }; |
| |
| Node.prototype.explicit = function explicit(num) { |
| var state = this._baseState; |
| |
| assert(state.explicit === null && state.implicit === null); |
| state.explicit = num; |
| |
| return this; |
| }; |
| |
| Node.prototype.implicit = function implicit(num) { |
| var state = this._baseState; |
| |
| assert(state.explicit === null && state.implicit === null); |
| state.implicit = num; |
| |
| return this; |
| }; |
| |
| Node.prototype.obj = function obj() { |
| var state = this._baseState; |
| var args = Array.prototype.slice.call(arguments); |
| |
| state.obj = true; |
| |
| if (args.length !== 0) |
| this._useArgs(args); |
| |
| return this; |
| }; |
| |
| Node.prototype.key = function key(newKey) { |
| var state = this._baseState; |
| |
| assert(state.key === null); |
| state.key = newKey; |
| |
| return this; |
| }; |
| |
| Node.prototype.any = function any() { |
| var state = this._baseState; |
| |
| state.any = true; |
| |
| return this; |
| }; |
| |
| Node.prototype.choice = function choice(obj) { |
| var state = this._baseState; |
| |
| assert(state.choice === null); |
| state.choice = obj; |
| this._useArgs(Object.keys(obj).map(function(key) { |
| return obj[key]; |
| })); |
| |
| return this; |
| }; |
| |
| Node.prototype.contains = function contains(item) { |
| var state = this._baseState; |
| |
| assert(state.use === null); |
| state.contains = item; |
| |
| return this; |
| }; |
| |
| // |
| // Decoding |
| // |
| |
| Node.prototype._decode = function decode(input, options) { |
| var state = this._baseState; |
| |
| // Decode root node |
| if (state.parent === null) |
| return input.wrapResult(state.children[0]._decode(input, options)); |
| |
| var result = state['default']; |
| var present = true; |
| |
| var prevKey = null; |
| if (state.key !== null) |
| prevKey = input.enterKey(state.key); |
| |
| // Check if tag is there |
| if (state.optional) { |
| var tag = null; |
| if (state.explicit !== null) |
| tag = state.explicit; |
| else if (state.implicit !== null) |
| tag = state.implicit; |
| else if (state.tag !== null) |
| tag = state.tag; |
| |
| if (tag === null && !state.any) { |
| // Trial and Error |
| var save = input.save(); |
| try { |
| if (state.choice === null) |
| this._decodeGeneric(state.tag, input, options); |
| else |
| this._decodeChoice(input, options); |
| present = true; |
| } catch (e) { |
| present = false; |
| } |
| input.restore(save); |
| } else { |
| present = this._peekTag(input, tag, state.any); |
| |
| if (input.isError(present)) |
| return present; |
| } |
| } |
| |
| // Push object on stack |
| var prevObj; |
| if (state.obj && present) |
| prevObj = input.enterObject(); |
| |
| if (present) { |
| // Unwrap explicit values |
| if (state.explicit !== null) { |
| var explicit = this._decodeTag(input, state.explicit); |
| if (input.isError(explicit)) |
| return explicit; |
| input = explicit; |
| } |
| |
| var start = input.offset; |
| |
| // Unwrap implicit and normal values |
| if (state.use === null && state.choice === null) { |
| if (state.any) |
| var save = input.save(); |
| var body = this._decodeTag( |
| input, |
| state.implicit !== null ? state.implicit : state.tag, |
| state.any |
| ); |
| if (input.isError(body)) |
| return body; |
| |
| if (state.any) |
| result = input.raw(save); |
| else |
| input = body; |
| } |
| |
| if (options && options.track && state.tag !== null) |
| options.track(input.path(), start, input.length, 'tagged'); |
| |
| if (options && options.track && state.tag !== null) |
| options.track(input.path(), input.offset, input.length, 'content'); |
| |
| // Select proper method for tag |
| if (state.any) |
| result = result; |
| else if (state.choice === null) |
| result = this._decodeGeneric(state.tag, input, options); |
| else |
| result = this._decodeChoice(input, options); |
| |
| if (input.isError(result)) |
| return result; |
| |
| // Decode children |
| if (!state.any && state.choice === null && state.children !== null) { |
| state.children.forEach(function decodeChildren(child) { |
| // NOTE: We are ignoring errors here, to let parser continue with other |
| // parts of encoded data |
| child._decode(input, options); |
| }); |
| } |
| |
| // Decode contained/encoded by schema, only in bit or octet strings |
| if (state.contains && (state.tag === 'octstr' || state.tag === 'bitstr')) { |
| var data = new DecoderBuffer(result); |
| result = this._getUse(state.contains, input._reporterState.obj) |
| ._decode(data, options); |
| } |
| } |
| |
| // Pop object |
| if (state.obj && present) |
| result = input.leaveObject(prevObj); |
| |
| // Set key |
| if (state.key !== null && (result !== null || present === true)) |
| input.leaveKey(prevKey, state.key, result); |
| else if (prevKey !== null) |
| input.exitKey(prevKey); |
| |
| return result; |
| }; |
| |
| Node.prototype._decodeGeneric = function decodeGeneric(tag, input, options) { |
| var state = this._baseState; |
| |
| if (tag === 'seq' || tag === 'set') |
| return null; |
| if (tag === 'seqof' || tag === 'setof') |
| return this._decodeList(input, tag, state.args[0], options); |
| else if (/str$/.test(tag)) |
| return this._decodeStr(input, tag, options); |
| else if (tag === 'objid' && state.args) |
| return this._decodeObjid(input, state.args[0], state.args[1], options); |
| else if (tag === 'objid') |
| return this._decodeObjid(input, null, null, options); |
| else if (tag === 'gentime' || tag === 'utctime') |
| return this._decodeTime(input, tag, options); |
| else if (tag === 'null_') |
| return this._decodeNull(input, options); |
| else if (tag === 'bool') |
| return this._decodeBool(input, options); |
| else if (tag === 'objDesc') |
| return this._decodeStr(input, tag, options); |
| else if (tag === 'int' || tag === 'enum') |
| return this._decodeInt(input, state.args && state.args[0], options); |
| |
| if (state.use !== null) { |
| return this._getUse(state.use, input._reporterState.obj) |
| ._decode(input, options); |
| } else { |
| return input.error('unknown tag: ' + tag); |
| } |
| }; |
| |
| Node.prototype._getUse = function _getUse(entity, obj) { |
| |
| var state = this._baseState; |
| // Create altered use decoder if implicit is set |
| state.useDecoder = this._use(entity, obj); |
| assert(state.useDecoder._baseState.parent === null); |
| state.useDecoder = state.useDecoder._baseState.children[0]; |
| if (state.implicit !== state.useDecoder._baseState.implicit) { |
| state.useDecoder = state.useDecoder.clone(); |
| state.useDecoder._baseState.implicit = state.implicit; |
| } |
| return state.useDecoder; |
| }; |
| |
| Node.prototype._decodeChoice = function decodeChoice(input, options) { |
| var state = this._baseState; |
| var result = null; |
| var match = false; |
| |
| Object.keys(state.choice).some(function(key) { |
| var save = input.save(); |
| var node = state.choice[key]; |
| try { |
| var value = node._decode(input, options); |
| if (input.isError(value)) |
| return false; |
| |
| result = { type: key, value: value }; |
| match = true; |
| } catch (e) { |
| input.restore(save); |
| return false; |
| } |
| return true; |
| }, this); |
| |
| if (!match) |
| return input.error('Choice not matched'); |
| |
| return result; |
| }; |
| |
| // |
| // Encoding |
| // |
| |
| Node.prototype._createEncoderBuffer = function createEncoderBuffer(data) { |
| return new EncoderBuffer(data, this.reporter); |
| }; |
| |
| Node.prototype._encode = function encode(data, reporter, parent) { |
| var state = this._baseState; |
| if (state['default'] !== null && state['default'] === data) |
| return; |
| |
| var result = this._encodeValue(data, reporter, parent); |
| if (result === undefined) |
| return; |
| |
| if (this._skipDefault(result, reporter, parent)) |
| return; |
| |
| return result; |
| }; |
| |
| Node.prototype._encodeValue = function encode(data, reporter, parent) { |
| var state = this._baseState; |
| |
| // Decode root node |
| if (state.parent === null) |
| return state.children[0]._encode(data, reporter || new Reporter()); |
| |
| var result = null; |
| |
| // Set reporter to share it with a child class |
| this.reporter = reporter; |
| |
| // Check if data is there |
| if (state.optional && data === undefined) { |
| if (state['default'] !== null) |
| data = state['default'] |
| else |
| return; |
| } |
| |
| // Encode children first |
| var content = null; |
| var primitive = false; |
| if (state.any) { |
| // Anything that was given is translated to buffer |
| result = this._createEncoderBuffer(data); |
| } else if (state.choice) { |
| result = this._encodeChoice(data, reporter); |
| } else if (state.contains) { |
| content = this._getUse(state.contains, parent)._encode(data, reporter); |
| primitive = true; |
| } else if (state.children) { |
| content = state.children.map(function(child) { |
| if (child._baseState.tag === 'null_') |
| return child._encode(null, reporter, data); |
| |
| if (child._baseState.key === null) |
| return reporter.error('Child should have a key'); |
| var prevKey = reporter.enterKey(child._baseState.key); |
| |
| if (typeof data !== 'object') |
| return reporter.error('Child expected, but input is not object'); |
| |
| var res = child._encode(data[child._baseState.key], reporter, data); |
| reporter.leaveKey(prevKey); |
| |
| return res; |
| }, this).filter(function(child) { |
| return child; |
| }); |
| content = this._createEncoderBuffer(content); |
| } else { |
| if (state.tag === 'seqof' || state.tag === 'setof') { |
| // TODO(indutny): this should be thrown on DSL level |
| if (!(state.args && state.args.length === 1)) |
| return reporter.error('Too many args for : ' + state.tag); |
| |
| if (!Array.isArray(data)) |
| return reporter.error('seqof/setof, but data is not Array'); |
| |
| var child = this.clone(); |
| child._baseState.implicit = null; |
| content = this._createEncoderBuffer(data.map(function(item) { |
| var state = this._baseState; |
| |
| return this._getUse(state.args[0], data)._encode(item, reporter); |
| }, child)); |
| } else if (state.use !== null) { |
| result = this._getUse(state.use, parent)._encode(data, reporter); |
| } else { |
| content = this._encodePrimitive(state.tag, data); |
| primitive = true; |
| } |
| } |
| |
| // Encode data itself |
| var result; |
| if (!state.any && state.choice === null) { |
| var tag = state.implicit !== null ? state.implicit : state.tag; |
| var cls = state.implicit === null ? 'universal' : 'context'; |
| |
| if (tag === null) { |
| if (state.use === null) |
| reporter.error('Tag could be omitted only for .use()'); |
| } else { |
| if (state.use === null) |
| result = this._encodeComposite(tag, primitive, cls, content); |
| } |
| } |
| |
| // Wrap in explicit |
| if (state.explicit !== null) |
| result = this._encodeComposite(state.explicit, false, 'context', result); |
| |
| return result; |
| }; |
| |
| Node.prototype._encodeChoice = function encodeChoice(data, reporter) { |
| var state = this._baseState; |
| |
| var node = state.choice[data.type]; |
| if (!node) { |
| assert( |
| false, |
| data.type + ' not found in ' + |
| JSON.stringify(Object.keys(state.choice))); |
| } |
| return node._encode(data.value, reporter); |
| }; |
| |
| Node.prototype._encodePrimitive = function encodePrimitive(tag, data) { |
| var state = this._baseState; |
| |
| if (/str$/.test(tag)) |
| return this._encodeStr(data, tag); |
| else if (tag === 'objid' && state.args) |
| return this._encodeObjid(data, state.reverseArgs[0], state.args[1]); |
| else if (tag === 'objid') |
| return this._encodeObjid(data, null, null); |
| else if (tag === 'gentime' || tag === 'utctime') |
| return this._encodeTime(data, tag); |
| else if (tag === 'null_') |
| return this._encodeNull(); |
| else if (tag === 'int' || tag === 'enum') |
| return this._encodeInt(data, state.args && state.reverseArgs[0]); |
| else if (tag === 'bool') |
| return this._encodeBool(data); |
| else if (tag === 'objDesc') |
| return this._encodeStr(data, tag); |
| else |
| throw new Error('Unsupported tag: ' + tag); |
| }; |
| |
| Node.prototype._isNumstr = function isNumstr(str) { |
| return /^[0-9 ]*$/.test(str); |
| }; |
| |
| Node.prototype._isPrintstr = function isPrintstr(str) { |
| return /^[A-Za-z0-9 '\(\)\+,\-\.\/:=\?]*$/.test(str); |
| }; |