iris-js-sdk
Version:
JavaScript SDK for Iris Platform
1,756 lines (1,415 loc) • 3.59 MB
JavaScript
(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 =