UNPKG

iris-js-sdk

Version:
1,756 lines (1,415 loc) 3.59 MB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _interop = require("./interop"); Object.keys(_interop).forEach(function (key) { if (key === "default" || key === "__esModule") return; if (key in exports && exports[key] === _interop[key]) return; Object.defineProperty(exports, key, { enumerable: true, get: function get() { return _interop[key]; } }); }); },{"./interop":2}],2:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Interop = void 0; var _lodash = _interopRequireDefault(require("lodash.clonedeep")); var _transform = _interopRequireDefault(require("./transform.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var PLAN_B_MIDS = ['audio', 'video', 'data']; var findSimGroup = function findSimGroup(ssrcGroup) { return ssrcGroup.find(function (grp) { return grp.semantics === 'SIM'; }); }; var findFidGroup = function findFidGroup(ssrcGroup) { return ssrcGroup.find(function (grp) { return grp.semantics === 'FID'; }); }; /** * Add the ssrcs of the SIM group and their corresponding FID group ssrcs * to the m-line. * @param {Object} mLine - The m-line to which ssrcs have to be added. * @param {Object} simGroup - The SIM group whose ssrcs have to be added to * the m-line. * @param {Object} sourceGroups - inverted source-group map. * @param {Array<Object>} sourceList - array containing all the sources. */ function addSimGroupSources(mLine, simGroup, sourceGroups, sourceList) { if (!mLine || !simGroup) { return; } var findSourcebyId = function findSourcebyId(src) { return sourceList.find(function (source) { return source.id.toString() === src; }); }; simGroup.ssrcs.forEach(function (src) { mLine.sources.push(findSourcebyId(src)); // find the related FID group member for this ssrc. var relatedFidGroup = sourceGroups[parseInt(src, 10)].find(function (grp) { return grp.semantics === 'FID'; }); if (relatedFidGroup) { var relatedSsrc = relatedFidGroup.ssrcs.find(function (s) { return s !== src; }); mLine.sources.push(findSourcebyId(relatedSsrc)); mLine.ssrcGroups.push(relatedFidGroup); } }); // Add the SIM group last. mLine.ssrcGroups.push(simGroup); } /** * Add ssrcs and ssrc-groups to the m-line. When a primary ssrc, i.e., the * first ssrc in a SIM group is passed, all the other ssrcs from the SIM * group and the other ssrcs from the related FID groups are added to the same * m-line since they all belong to the same remote source. Since the ssrcs are * not guaranteed to be in the correct order, try to find if a SIM group exists, * if not, just add the FID group. * @param {Object} mLine - The m-line to which ssrcs have to be added. * @param {Object} ssrc - the primary ssrc. * @param {Object} sourceGroups - inverted source-group map. * @param {Array<Object>} sourceList - array containing all the sources. * @returns {void} */ function addSourcesToMline(mLine, ssrc, sourceGroups, sourceList) { if (!mLine || !ssrc) { return; } mLine.sources = []; mLine.ssrcGroups = []; // If there are no associated ssrc-groups, just add the ssrc and msid. if (!sourceGroups[ssrc.id]) { mLine.sources.push(ssrc); mLine.msid = ssrc.msid; return; } var findSourcebyId = function findSourcebyId(src) { return sourceList.find(function (source) { return source.id.toString() === src; }); }; // Find the SIM and FID groups that this ssrc belongs to. var simGroup = findSimGroup(sourceGroups[ssrc.id]); var fidGroup = findFidGroup(sourceGroups[ssrc.id]); // Add the ssrcs for the SIM group and their corresponding FID groups. if (simGroup) { addSimGroupSources(mLine, simGroup, sourceGroups, sourceList); } else if (fidGroup) { // check if the other ssrc from this FID group is part of a SIM group var otherSsrc = fidGroup.ssrcs.find(function (s) { return s !== ssrc; }); var simGroup2 = findSimGroup(sourceGroups[otherSsrc]); if (simGroup2) { addSimGroupSources(mLine, simGroup2, sourceGroups, sourceList); } else { // Add the FID group ssrcs. fidGroup.ssrcs.forEach(function (src) { mLine.sources.push(findSourcebyId(src)); }); mLine.ssrcGroups.push(fidGroup); } } // Set the msid for the media description using the msid attribute of the ssrcs. mLine.msid = mLine.sources[0].msid; } /** * Checks if there is a mline for the given ssrc or its related primary ssrc. * We always implode the SIM group to the first ssrc in the SIM group before sRD, * so we also check if mline for that ssrc exists. * For example: * If the following ssrcs are in a SIM group, * <ssrc-group xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\" semantics=\"SIM\"> * <source ssrc=\"1806330949\"/> * <source ssrc=\"4173145196\"/> * <source ssrc=\"2002632207\"/> * </ssrc-group> * This method returns true for any one of the 3 ssrcs if there is a mline for 1806330949. * @param {Object} ssrc - ssrc to check. * @param {Object} sourceGroups - inverted source-group map. * @param {Array<Object>} mlines - mlines in the description * @returns {Boolean} - Returns true if mline for the given ssrc or the related primary ssrc * exists, returns false otherwise. */ function checkIfMlineForSsrcExists(ssrc, sourceGroups, mlines) { var findMatchingMline = function findMatchingMline(mline) { if (mline.sources) { return mline.sources.some(function (source) { return source.id === ssrc.id; }); } return false; }; if (!mlines.find(findMatchingMline)) { // check if this ssrc is member of a SIM group. If so, check if there // is a matching m-line for the primary ssrc of the SIM group. if (!sourceGroups[ssrc.id]) { return false; } var simGroup = findSimGroup(sourceGroups[ssrc.id]); var fidGroup = findFidGroup(sourceGroups[ssrc.id]); if (simGroup) { return mlines.some(function (mline) { return mline.sources && mline.sources.some(function (src) { return src.id.toString() === simGroup.ssrcs[0]; }); }); } else if (fidGroup && ssrc.id.toString() !== fidGroup.ssrcs[0]) { var otherSsrc = { id: fidGroup.ssrcs[0] }; return checkIfMlineForSsrcExists(otherSsrc, sourceGroups, mlines); } return false; } return true; } /** * Create an inverted sourceGroup map to put all the grouped ssrcs * in the same m-line. * @param {Array<Object>} sourceGroups * @returns {Object} - An inverted sourceGroup map. */ function createSourceGroupMap(sourceGroups) { var ssrc2group = {}; if (!sourceGroups || !Array.isArray(sourceGroups)) { return ssrc2group; } sourceGroups.forEach(function (group) { if (group.ssrcs && Array.isArray(group.ssrcs)) { group.ssrcs.forEach(function (ssrc) { if (typeof ssrc2group[ssrc] === 'undefined') { ssrc2group[ssrc] = []; } ssrc2group[ssrc].push(group); }); } }); return ssrc2group; } /** * Interop provides an API for tranforming a Plan B SDP to a Unified Plan SDP and * vice versa. */ var Interop = /*#__PURE__*/function () { function Interop() { _classCallCheck(this, Interop); } _createClass(Interop, [{ key: "toPlanB", value: /** * This method transforms a Unified Plan SDP to an equivalent Plan B SDP. * @param {RTCSessionDescription} description - The description in Unified plan format. * @returns RTCSessionDescription - The transformed session description. */ function toPlanB(description) { if (!description || typeof description.sdp !== 'string') { console.warn('An empty description was passed as an argument.'); return description; } // Objectify the SDP for easier manipulation. var session = _transform["default"].parse(description.sdp); // If the SDP contains no media, there's nothing to transform. if (!session.media || !session.media.length) { console.warn('The description has no media.'); return description; } // Make sure this is a unified plan sdp if (session.media.every(function (m) { return PLAN_B_MIDS.indexOf(m.mid) !== -1; })) { console.warn('The description does not look like unified plan sdp'); return description; } var media = {}; var sessionMedia = session.media; session.media = []; sessionMedia.forEach(function (mLine) { var type = mLine.type; if (type === 'application') { mLine.mid = 'data'; media[mLine.mid] = mLine; return; } if (typeof media[type] === 'undefined') { var bLine = (0, _lodash["default"])(mLine); // Copy the msid attribute to all the ssrcs if they belong to the same source group if (bLine.sources && Array.isArray(bLine.sources)) { bLine.sources.forEach(function (source) { mLine.msid ? source.msid = mLine.msid : delete source.msid; }); } // Do not signal the FID groups if there is no msid attribute present // on the sources as sesison-accept with this source info will fail strophe // validation and the session will not be established. This behavior is seen // on Firefox (with RTX enabled) when no video source is added at the join time. // FF generates two recvonly ssrcs with no msid and a corresponding FID group in // this case. if (!bLine.ssrcGroups || !mLine.msid) { bLine.ssrcGroups = []; } delete bLine.msid; bLine.mid = type; media[type] = bLine; } else if (mLine.msid) { // Add sources and source-groups to the existing m-line of the same media type. if (mLine.sources && Array.isArray(mLine.sources)) { media[type].sources = media[type].sources.concat(mLine.sources); } if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) { media[type].ssrcGroups = media[type].ssrcGroups.concat(mLine.ssrcGroups); } } }); session.media = Object.values(media); // Bundle the media only if it is active. var bundle = []; Object.values(media).forEach(function (mline) { if (mline.direction !== 'inactive') { bundle.push(mline.mid); } }); // We regenerate the BUNDLE group with the new mids. session.groups.forEach(function (group) { if (group.type === 'BUNDLE') { group.mids = bundle.join(' '); } }); // msid semantic session.msidSemantic = { semantic: 'WMS', token: '*' }; var resStr = _transform["default"].write(session); return new RTCSessionDescription({ type: description.type, sdp: resStr }); } /** * This method transforms a Plan B SDP to an equivalent Unified Plan SDP. * @param {RTCSessionDescription} description - The description in plan-b format. * @param {RTCSessionDescription} current - The current description set on * the peerconnection in Unified-plan format, i.e., the readonly attribute * remoteDescription on the RTCPeerConnection object. * @returns RTCSessionDescription - The transformed session description. */ }, { key: "toUnifiedPlan", value: function toUnifiedPlan(description) { var current = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (!description || typeof description.sdp !== 'string') { console.warn('An empty description was passed as an argument.'); return description; } // Objectify the SDP for easier manipulation. var session = _transform["default"].parse(description.sdp); // If the SDP contains no media, there's nothing to transform. if (!session.media || !session.media.length) { console.warn('The description has no media.'); return description; } // Make sure this is a plan-b sdp. if (session.media.length > 3 || session.media.every(function (m) { return PLAN_B_MIDS.indexOf(m.mid) === -1; })) { console.warn('The description does not look like plan-b'); return description; } var currentDesc = current ? _transform["default"].parse(current.sdp) : null; var media = {}; session.media.forEach(function (mLine) { var type = mLine.type; if (type === 'application') { if (!currentDesc || !currentDesc.media) { var newMline = (0, _lodash["default"])(mLine); newMline.mid = Object.keys(media).length.toString(); media[mLine.mid] = newMline; return; } var mLineForData = currentDesc.media.findIndex(function (m) { return m.type === type; }); if (mLineForData) { currentDesc.media[mLineForData] = mLine; currentDesc.media[mLineForData].mid = mLineForData; } return; } // Create an inverted sourceGroup map here to put all the grouped SSRCs in the same m-line. var ssrc2group = createSourceGroupMap(mLine.ssrcGroups); if (!mLine.sources) { return; } mLine.sources.forEach(function (ssrc, idx) { // Do not add the receive-only ssrcs that Jicofo sends in the source-add. // These ssrcs do not have the "msid" attribute set. if (!ssrc.msid) { return; } // If there is no description set on the peerconnection, create new m-lines. if (!currentDesc || !currentDesc.media) { if (checkIfMlineForSsrcExists(ssrc, ssrc2group, Object.values(media))) { return; } var _newMline = (0, _lodash["default"])(mLine); _newMline.mid = Object.keys(media).length.toString(); _newMline.direction = idx ? 'sendonly' : mLine.direction === 'sendonly' ? 'sendonly' : 'sendrecv'; _newMline.bundleOnly = undefined; addSourcesToMline(_newMline, ssrc, ssrc2group, mLine.sources); media[_newMline.mid] = _newMline; return; } // Create and append the m-lines to the existing description. if (checkIfMlineForSsrcExists(ssrc, ssrc2group, currentDesc.media)) { return; } // check if there is a m-line that is inactive and is of the same media type var inactiveMid = currentDesc.media.findIndex(function (cmLine) { return cmLine.direction && cmLine.direction === 'inactive' && cmLine.type === type; }); if (inactiveMid > -1) { currentDesc.media[inactiveMid].direction = 'sendonly'; addSourcesToMline(currentDesc.media[inactiveMid], ssrc, ssrc2group, mLine.sources); } else { var _newMline2 = (0, _lodash["default"])(mLine); _newMline2.mid = currentDesc.media.length.toString(); _newMline2.direction = 'sendonly'; addSourcesToMline(_newMline2, ssrc, ssrc2group, mLine.sources); currentDesc.media.push(_newMline2); } }); }); session.media = currentDesc ? currentDesc.media : Object.values(media); var mids = []; session.media.forEach(function (mLine) { mids.push(mLine.mid); }); // We regenerate the BUNDLE group (since we regenerated the mids) session.groups.forEach(function (group) { if (group.type === 'BUNDLE') { group.mids = mids.join(' '); } }); // msid semantic session.msidSemantic = { semantic: 'WMS', token: '*' }; // Increment the session version every time. session.origin.sessionVersion++; var resultSdp = _transform["default"].write(session); return new RTCSessionDescription({ type: description.type, sdp: resultSdp }); } }]); return Interop; }(); exports.Interop = Interop; },{"./transform.js":3,"lodash.clonedeep":139}],3:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _sdpTransform = _interopRequireDefault(require("sdp-transform")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } /* Copyright @ 2015 - Present, 8x8 Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Rewrites the source information in the way sdp-transform expects. * Source information is split into multiple ssrc objects each containing * an id, attribute and value. * @param {Object} media - media description to be modified. * @returns {void} */ var write = function write(session, opts) { if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && Array.isArray(session.media)) { session.media.forEach(function (mLine) { if (mLine.sources && mLine.sources.length) { mLine.ssrcs = []; mLine.sources.forEach(function (source) { Object.keys(source).forEach(function (attribute) { if (attribute === 'id') { return; } mLine.ssrcs.push({ id: source.id, attribute: attribute, value: source[attribute] }); }); }); delete mLine.sources; } // join ssrcs in ssrc groups if (mLine.ssrcGroups && mLine.ssrcGroups.length) { mLine.ssrcGroups.forEach(function (ssrcGroup) { if (typeof ssrcGroup.ssrcs !== 'undefined' && Array.isArray(ssrcGroup.ssrcs)) { ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' '); } }); } }); } return _sdpTransform["default"].write(session, opts); }; /** * Rewrites the source information that we get from sdp-transform. * All the ssrc lines with different attributes that belong to the * same ssrc are grouped into a single soure object with multiple key value pairs. * @param {Object} media - media description to be modified. * @returns {void} */ var parse = function parse(sdp) { var session = _sdpTransform["default"].parse(sdp); if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && Array.isArray(session.media)) { session.media.forEach(function (mLine) { // group sources attributes by ssrc if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) { mLine.sources = []; mLine.ssrcs.forEach(function (ssrc) { var found = mLine.sources.findIndex(function (source) { return source.id === ssrc.id; }); if (found > -1) { mLine.sources[found][ssrc.attribute] = ssrc.value; } else { var src = { id: ssrc.id }; src[ssrc.attribute] = ssrc.value; mLine.sources.push(src); } }); delete mLine.ssrcs; } // split ssrcs in ssrc groups if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) { mLine.ssrcGroups.forEach(function (ssrcGroup) { if (typeof ssrcGroup.ssrcs === 'string') { ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' '); } }); } }); } return session; }; var _default = { write: write, parse: parse }; exports["default"] = _default; },{"sdp-transform":213}],4:[function(require,module,exports){ 'use strict'; const asn1 = exports; asn1.bignum = require('bn.js'); asn1.define = require('./asn1/api').define; asn1.base = require('./asn1/base'); asn1.constants = require('./asn1/constants'); asn1.decoders = require('./asn1/decoders'); asn1.encoders = require('./asn1/encoders'); },{"./asn1/api":5,"./asn1/base":7,"./asn1/constants":11,"./asn1/decoders":13,"./asn1/encoders":16,"bn.js":18}],5:[function(require,module,exports){ 'use strict'; const encoders = require('./encoders'); const decoders = require('./decoders'); const inherits = require('inherits'); const api = exports; api.define = function define(name, body) { return new Entity(name, body); }; function Entity(name, body) { this.name = name; this.body = body; this.decoders = {}; this.encoders = {}; } Entity.prototype._createNamed = function createNamed(Base) { const name = this.name; function Generated(entity) { this._initNamed(entity, name); } inherits(Generated, Base); Generated.prototype._initNamed = function _initNamed(entity, name) { Base.call(this, entity, name); }; return new Generated(this); }; Entity.prototype._getDecoder = function _getDecoder(enc) { enc = enc || 'der'; // Lazily create decoder if (!this.decoders.hasOwnProperty(enc)) this.decoders[enc] = this._createNamed(decoders[enc]); return this.decoders[enc]; }; Entity.prototype.decode = function decode(data, enc, options) { return this._getDecoder(enc).decode(data, options); }; Entity.prototype._getEncoder = function _getEncoder(enc) { enc = enc || 'der'; // Lazily create encoder if (!this.encoders.hasOwnProperty(enc)) this.encoders[enc] = this._createNamed(encoders[enc]); return this.encoders[enc]; }; Entity.prototype.encode = function encode(data, enc, /* internal */ reporter) { return this._getEncoder(enc).encode(data, reporter); }; },{"./decoders":13,"./encoders":16,"inherits":120}],6:[function(require,module,exports){ 'use strict'; const inherits = require('inherits'); const Reporter = require('../base/reporter').Reporter; const Buffer = require('safer-buffer').Buffer; function DecoderBuffer(base, options) { Reporter.call(this, options); if (!Buffer.isBuffer(base)) { this.error('Input not Buffer'); return; } this.base = base; this.offset = 0; this.length = base.length; } inherits(DecoderBuffer, Reporter); exports.DecoderBuffer = DecoderBuffer; DecoderBuffer.isDecoderBuffer = function isDecoderBuffer(data) { if (data instanceof DecoderBuffer) { return true; } // Or accept compatible API const isCompatible = typeof data === 'object' && Buffer.isBuffer(data.base) && data.constructor.name === 'DecoderBuffer' && typeof data.offset === 'number' && typeof data.length === 'number' && typeof data.save === 'function' && typeof data.restore === 'function' && typeof data.isEmpty === 'function' && typeof data.readUInt8 === 'function' && typeof data.skip === 'function' && typeof data.raw === 'function'; return isCompatible; }; DecoderBuffer.prototype.save = function save() { return { offset: this.offset, reporter: Reporter.prototype.save.call(this) }; }; DecoderBuffer.prototype.restore = function restore(save) { // Return skipped data const res = new DecoderBuffer(this.base); res.offset = save.offset; res.length = this.offset; this.offset = save.offset; Reporter.prototype.restore.call(this, save.reporter); return res; }; DecoderBuffer.prototype.isEmpty = function isEmpty() { return this.offset === this.length; }; DecoderBuffer.prototype.readUInt8 = function readUInt8(fail) { if (this.offset + 1 <= this.length) return this.base.readUInt8(this.offset++, true); else return this.error(fail || 'DecoderBuffer overrun'); }; DecoderBuffer.prototype.skip = function skip(bytes, fail) { if (!(this.offset + bytes <= this.length)) return this.error(fail || 'DecoderBuffer overrun'); const res = new DecoderBuffer(this.base); // Share reporter state res._reporterState = this._reporterState; res.offset = this.offset; res.length = this.offset + bytes; this.offset += bytes; return res; }; DecoderBuffer.prototype.raw = function raw(save) { return this.base.slice(save ? save.offset : this.offset, this.length); }; function EncoderBuffer(value, reporter) { if (Array.isArray(value)) { this.length = 0; this.value = value.map(function(item) { if (!EncoderBuffer.isEncoderBuffer(item)) item = new EncoderBuffer(item, reporter); this.length += item.length; return item; }, this); } else if (typeof value === 'number') { if (!(0 <= value && value <= 0xff)) return reporter.error('non-byte EncoderBuffer value'); this.value = value; this.length = 1; } else if (typeof value === 'string') { this.value = value; this.length = Buffer.byteLength(value); } else if (Buffer.isBuffer(value)) { this.value = value; this.length = value.length; } else { return reporter.error('Unsupported type: ' + typeof value); } } exports.EncoderBuffer = EncoderBuffer; EncoderBuffer.isEncoderBuffer = function isEncoderBuffer(data) { if (data instanceof EncoderBuffer) { return true; } // Or accept compatible API const isCompatible = typeof data === 'object' && data.constructor.name === 'EncoderBuffer' && typeof data.length === 'number' && typeof data.join === 'function'; return isCompatible; }; EncoderBuffer.prototype.join = function join(out, offset) { if (!out) out = Buffer.alloc(this.length); if (!offset) offset = 0; if (this.length === 0) return out; if (Array.isArray(this.value)) { this.value.forEach(function(item) { item.join(out, offset); offset += item.length; }); } else { if (typeof this.value === 'number') out[offset] = this.value; else if (typeof this.value === 'string') out.write(this.value, offset); else if (Buffer.isBuffer(this.value)) this.value.copy(out, offset); offset += this.length; } return out; }; },{"../base/reporter":9,"inherits":120,"safer-buffer":211}],7:[function(require,module,exports){ 'use strict'; const base = exports; base.Reporter = require('./reporter').Reporter; base.DecoderBuffer = require('./buffer').DecoderBuffer; base.EncoderBuffer = require('./buffer').EncoderBuffer; base.Node = require('./node'); },{"./buffer":6,"./node":8,"./reporter":9}],8:[function(require,module,exports){ 'use strict'; const Reporter = require('../base/reporter').Reporter; const EncoderBuffer = require('../base/buffer').EncoderBuffer; const DecoderBuffer = require('../base/buffer').DecoderBuffer; const assert = require('minimalistic-assert'); // Supported tags const 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 const methods = [ 'key', 'obj', 'use', 'optional', 'explicit', 'implicit', 'def', 'choice', 'any', 'contains' ].concat(tags); // Overrided methods list const overrided = [ '_peekTag', '_decodeTag', '_use', '_decodeStr', '_decodeObjid', '_decodeTime', '_decodeNull', '_decodeInt', '_decodeBool', '_decodeList', '_encodeComposite', '_encodeStr', '_encodeObjid', '_encodeTime', '_encodeNull', '_encodeInt', '_encodeBool' ]; function Node(enc, parent, name) { const state = {}; this._baseState = state; state.name = name; 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; const stateProps = [ 'enc', 'parent', 'children', 'tag', 'args', 'reverseArgs', 'choice', 'optional', 'any', 'obj', 'use', 'alteredUse', 'key', 'default', 'explicit', 'implicit', 'contains' ]; Node.prototype.clone = function clone() { const state = this._baseState; const cstate = {}; stateProps.forEach(function(prop) { cstate[prop] = state[prop]; }); const res = new this.constructor(cstate.parent); res._baseState = cstate; return res; }; Node.prototype._wrap = function wrap() { const state = this._baseState; methods.forEach(function(method) { this[method] = function _wrappedMethod() { const clone = new this.constructor(this); state.children.push(clone); return clone[method].apply(clone, arguments); }; }, this); }; Node.prototype._init = function init(body) { const 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) { const state = this._baseState; // Filter children and args const 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; const res = {}; Object.keys(arg).forEach(function(key) { if (key == (key | 0)) key |= 0; const value = arg[key]; res[value] = key; }); return res; }); } }; // // Overrided methods // overrided.forEach(function(method) { Node.prototype[method] = function _overrided() { const state = this._baseState; throw new Error(method + ' not implemented for encoding: ' + state.enc); }; }); // // Public methods // tags.forEach(function(tag) { Node.prototype[tag] = function _tagMethod() { const state = this._baseState; const 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); const state = this._baseState; assert(state.use === null); state.use = item; return this; }; Node.prototype.optional = function optional() { const state = this._baseState; state.optional = true; return this; }; Node.prototype.def = function def(val) { const state = this._baseState; assert(state['default'] === null); state['default'] = val; state.optional = true; return this; }; Node.prototype.explicit = function explicit(num) { const state = this._baseState; assert(state.explicit === null && state.implicit === null); state.explicit = num; return this; }; Node.prototype.implicit = function implicit(num) { const state = this._baseState; assert(state.explicit === null && state.implicit === null); state.implicit = num; return this; }; Node.prototype.obj = function obj() { const state = this._baseState; const args = Array.prototype.slice.call(arguments); state.obj = true; if (args.length !== 0) this._useArgs(args); return this; }; Node.prototype.key = function key(newKey) { const state = this._baseState; assert(state.key === null); state.key = newKey; return this; }; Node.prototype.any = function any() { const state = this._baseState; state.any = true; return this; }; Node.prototype.choice = function choice(obj) { const 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) { const state = this._baseState; assert(state.use === null); state.contains = item; return this; }; // // Decoding // Node.prototype._decode = function decode(input, options) { const state = this._baseState; // Decode root node if (state.parent === null) return input.wrapResult(state.children[0]._decode(input, options)); let result = state['default']; let present = true; let prevKey = null; if (state.key !== null) prevKey = input.enterKey(state.key); // Check if tag is there if (state.optional) { let 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 const 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 let prevObj; if (state.obj && present) prevObj = input.enterObject(); if (present) { // Unwrap explicit values if (state.explicit !== null) { const explicit = this._decodeTag(input, state.explicit); if (input.isError(explicit)) return explicit; input = explicit; } const start = input.offset; // Unwrap implicit and normal values if (state.use === null && state.choice === null) { let save; if (state.any) save = input.save(); const 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) { // no-op } 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')) { const 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) { const 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) { const 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) { const state = this._baseState; let result = null; let match = false; Object.keys(state.choice).some(function(key) { const save = input.save(); const node = state.choice[key]; try { const 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) { const state = this._baseState; if (state['default'] !== null && state['default'] === data) return; const 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) { const state = this._baseState; // Decode root node if (state.parent === null) return state.children[0]._encode(data, reporter || new Reporter()); let 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 let content = null; let 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'); const prevKey = reporter.enterKey(child._baseState.key); if (typeof data !== 'object') return reporter.error('Child expected, but input is not object'); const 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'); const child = this.clone(); child._baseState.implicit = null; content = this._createEncoderBuffer(data.map(function(item) { const 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 if (!state.any && state.choice === null) { const tag = state.implicit !== null ? state.implicit : state.tag; const 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) { const state = this._baseState; const 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) { const 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); }; },{"../base/buffer":6,"../base/reporter":9,"minimalistic-assert":163}],9:[function(require,module,exports){ 'use strict'; const inherits = require('inherits'); function Reporter(options) { this._reporterState = { obj: null, path: [], options: options || {}, errors: [] }; } exports.Reporter = Reporter; Reporter.prototype.isError = function isError(obj) { return obj instanceof ReporterError; }; Reporter.prototype.save = function save() { const state = this._reporterState; return { obj: state.obj, pathLen: state.path.length }; }; Reporter.prototype.restore = function restore(data) { const state = this._reporterState; state.obj = data.obj; state.path = state.path.slice(0, data.pathLen); }; Reporter.prototype.enterKey = function enterKey(key) { return this._reporterState.path.push(key); }; Reporter.prototype.exitKey = function exitKey(index) { const state = this._reporterState; state.path = state.path.slice(0, index - 1); }; Reporter.prototype.leaveKey = function leaveKey(index, key, value) { const state = this._reporterState; this.exitKey(index); if (state.obj !== null) state.obj[key] = value; }; Reporter.prototype.path = function path() { return this._reporterState.path.join('/'); }; Reporter.prototype.enterObject = function enterObject() { const state = this._reporterState; const prev = state.obj; state.obj = {}; return prev; }; Reporter.prototype.leaveObject = function leaveObject(prev) { const state = this._reporterState; const now = state.obj; state.obj = prev; return now; }; Reporter.prototype.error = function error(msg) { let err; const state = this._reporterState; const inherited = msg instanceof ReporterError; if (inherited) { err = msg; } else { err = new ReporterError(state.path.map(function(elem) { return '[' + JSON.stringify(elem) + ']'; }).join(''), msg.message || msg, msg.stack); } if (!state.options.partial) throw err; if (!inherited) state.errors.push(err); return err; }; Reporter.prototype.wrapResult = function wrapResult(result) { const state = this._reporterState; if (!state.options.partial) return result; return { result: this.isError(result) ? null : result, errors: state.errors }; }; function ReporterError(path, msg) { this.path = path; this.rethrow(msg); } inherits(ReporterError, Error); ReporterError.prototype.rethrow = function rethrow(msg) { this.message = msg + ' at: ' + (this.path || '(shallow)'); if (Error.captureStackTrace) Error.captureStackTrace(this, ReporterError); if (!this.stack) { try { // IE only adds stack when thrown throw new Error(this.message); } catch (e) { this.stack = e.stack; } } return this; }; },{"inherits":120}],10:[function(require,module,exports){ 'use strict'; // Helper function reverse(map) { const res = {}; Object.keys(map).forEach(function(key) { // Convert key to integer if it is stringified if ((key | 0) == key) key = key | 0; const value = map[key]; res[value] = key; }); return res; } exports.tagClass = { 0: 'universal', 1: 'application', 2: 'context', 3: 'private' }; exports.tagClassByName = reverse(exports.tagClass); exports.tag = { 0x00: 'end', 0x01: 'bool', 0x02: 'int', 0x03: 'bitstr', 0x04: 'octstr', 0x05: 'null_', 0x06: 'objid', 0x07: 'objDesc', 0x08: 'external', 0x09: 'real', 0x0a: 'enum', 0x0b: 'embed', 0x0c: 'utf8str', 0x0d: 'relativeOid', 0x10: 'seq', 0x11: 'set', 0x12: 'numstr', 0x13: 'printstr', 0x14: 't61str', 0x15: 'videostr', 0x16: 'ia5str', 0x17: 'utctime', 0x18: 'gentime', 0x19: 'graphstr', 0x1a: 'iso646str', 0x1b: 'genstr', 0x1c: 'unistr', 0x1d: 'charstr', 0x1e: 'bmpstr' }; exports.tagByName = reverse(exports.tag); },{}],11:[function(require,module,exports){ 'use strict'; const constants = exports; // Helper constants._reverse = function reverse(map) { const res = {}; Object.keys(map).forEach(function(key) { // Convert key to integer if it is stringified if ((key | 0) == key) key = key | 0; const value = map[key]; res[value] = key; }); return res; }; constants.der = require('./der'); },{"./der":10}],12:[function(require,module,exports){ 'use strict'; const inherits = require('inherits'); const bignum = require('bn.js'); const DecoderBuffer = require('../base/buffer').DecoderBuffer; const Node = require('../base/node'); // Import DER constants const der = require('../constants/der'); function DERDecoder(entity) { this.enc = 'der'; this.name = entity.name; this.entity = entity; // Construct base tree this.tree = new DERNode(); this.tree._init(entity.body); } module.exports = DERDecoder; DERDecoder.prototype.decode = function decode(data, options) { if (!DecoderBuffer.isDecoderBuffer(data)) { data = new DecoderBuffer(data, options); } return this.tree._decode(data, options); }; // Tree methods function DERNode(parent) { Node.call(this, 'der', parent); } inherits(DERNode, Node); DERNode.prototype._peekTag = function peekTag(buffer, tag, any) { if (buffer.isEmpty()) return false; const state = buffer.save(); const decodedTag =