UNPKG

@browser-network/database

Version:

A type of distributed database built on top of the distributed browser-network

1,389 lines (1,244 loc) 772 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Db = f()}})(function(){var define,module,exports;return (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){ (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports"], factory); } })(function (require, exports) { "use strict"; exports.__esModule = true; exports.LocalDB = void 0; var buildStorageShim = function () { // @ts-ignore var localStorageShim = {}; localStorageShim.setItem = function (key, val) { localStorageShim[key] = val; }; localStorageShim.getItem = function (key) { return localStorageShim[key]; }; localStorageShim.removeItem = function (key) { delete localStorageShim[key]; }; return localStorageShim; }; var LocalDB = /** @class */ (function () { function LocalDB(appId) { var _this = this; this.keyPrefix = 'db'; this.getByKey = function (key) { var gotten = _this.localStorage.getItem(key); if (!gotten) return null; return JSON.parse(gotten); }; this.appId = appId; this.localStorage = globalThis.localStorage || buildStorageShim(); } LocalDB.prototype.set = function (val, address) { var key = this.buildKey(address); this.localStorage.setItem(key, JSON.stringify(val)); }; LocalDB.prototype.get = function (address) { var key = this.buildKey(address); return this.getByKey(key); }; LocalDB.prototype.getAll = function () { var _this = this; var lsKeys = Object.keys(this.localStorage); var ourKeys = lsKeys.filter(function (key) { return key.indexOf(_this.buildKey('')) > -1; }); return ourKeys.map(this.getByKey); }; // Remove a single item LocalDB.prototype.remove = function (address) { var key = this.buildKey(address); this.localStorage.removeItem(key); }; // Remove all items LocalDB.prototype.clear = function () { var _this = this; this.getAll().forEach(function (wrapped) { _this.remove(wrapped.id); }); }; LocalDB.prototype.buildKey = function (address) { return "".concat(this.keyPrefix, "-").concat(this.appId, "-").concat(address); }; return LocalDB; }()); exports.LocalDB = LocalDB; }); },{}],2:[function(require,module,exports){ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "@browser-network/crypto", "./LocalDB", "./util"], factory); } })(function (require, exports) { "use strict"; exports.__esModule = true; var bnc = __importStar(require("@browser-network/crypto")); var LocalDB_1 = require("./LocalDB"); var util_1 = require("./util"); var debug = (0, util_1.debugFactory)('Db'); // convenience var wrapState = function (state, address, pub, priv) { return __awaiter(void 0, void 0, void 0, function () { var wrapp, signature, wrapped; return __generator(this, function (_a) { switch (_a.label) { case 0: wrapp = { id: address, timestamp: Date.now(), state: state, publicKey: pub }; return [4 /*yield*/, bnc.sign(priv, wrapp)]; case 1: signature = _a.sent(); wrapped = Object.assign(wrapp, { signature: signature }); return [2 /*return*/, wrapped]; } }); }); }; // convenience var verifySignature = function (update) { var signature = update.signature, wrapp = __rest(update, ["signature"]); return bnc.verifySignature(wrapp, signature, update.publicKey); }; // TODO I've seen a loop before, where state-offering messages are being spammed. // I don't know how this would have happened as it seems to be hard coded to send only // once per 5 seconds. var Db = /** @class */ (function () { function Db(_a) { var secret = _a.secret, appId = _a.appId, network = _a.network; var _this = this; this._denyList = {}; this._allowList = {}; this._onChangeHandlers = []; /** * @description Get all entries from our local DB, wrapped in the DB's * WrappedState type. * * @TODO: Does it need to be wrapped? Does the user ever care about this * wrapping or should they just be able to go straight to their state? */ this.getAll = function () { return _this.localDB.getAll(); }; /** * @description Clear the local storage of everyone's items. Essentially resets the machine * to as if it's never seen the network before. If it is connected still, it will * rapidly start to repopulate. */ this.clear = function () { _this.localDB.clear(); _this.runChangeHandlers(); }; /** * @description Effectively blocks a user. Adds them to our deny list, which means we'll no longer * accept updates from them, which means we will no longer forward their updates as well. Also * removes their state from our storage. * * It's up to the developer to keep track of these (probably * within the state object that they store in this db), and repopulate this list on startup. * Calling deny with an address that's already blocked is a noop and O(1) time so don't worry about * spamming this call. */ this.deny = function (address) { if (_this._denyList[address]) { return; } _this._denyList[address] = true; _this.localDB.remove(address); }; /** * @description Unblock a user. Removes them from our deny list, at which point the DB will naturally * start to repopulate that user's state. */ this.undeny = function (address) { delete _this._denyList[address]; }; /** * @description Add a user to our allow list. Once a single user is on this list, _only users on the * allow list will be recorded in the database_. All other users will automatically be ignored. * * It's up to the developer to keep track of these (probably * within the state object that they store in this db), and repopulate this list on startup. * Calling allow with an address that's already on the list is a noop and O(1) time so don't worry about * spamming this call. */ this.allow = function (address) { if (_this._allowList[address]) { return; } _this._allowList[address] = true; }; /** * @description Remove a user from the allow list. Calling this will remove the user's state from * our storage, and that user's state will no longer be forwarded either. */ this.unallow = function (address) { delete _this._allowList[address]; _this.localDB.remove(address); }; this.onMessage = function (message) { switch (message.type) { case 'state-offering': { debug(5, 'received state-offering:', message); return _this.onStateOffering(message.data, message.address); } case 'state-request': { debug(5, 'received state-request:', message); return _this.broadcastStateUpdateByStateId(message.data); } case 'state-update': { debug(5, 'received state-update:', message); return _this.onStateUpdate(message.data); } } }; /** * We will periodically inform the network of what states we have * and how old they are. If someone else hears that we have a state * newer than what they have on record, they can send us a request for * what we have. */ this.broadcastStateOfferings = function () { var offerings = {}; for (var _i = 0, _a = _this.localDB.getAll(); _i < _a.length; _i++) { var state = _a[_i]; offerings[state.id] = state.timestamp; } _this.network.broadcast({ type: 'state-offering', appId: _this.appId, data: offerings }); }; this.runChangeHandlers = function () { _this._onChangeHandlers.forEach(function (handler) { return handler(); }); }; this.networkId = network.networkId; this.appId = appId; this.localDB = new LocalDB_1.LocalDB(appId); this.address = network.address; this.secret = secret; this.network = network; this.network.on('message', function (message) { if (message.appId !== _this.appId) return; _this.onMessage(message); }); // Here we derive the pub key from the private this.publicKey = bnc.derivePubKey(secret); setInterval(this.broadcastStateOfferings, 5000); } /** * @description This is how you write data to the network. This will put whatever * state you give it into a DB specific wrapper with your state in the `state` key. */ Db.prototype.set = function (state) { return __awaiter(this, void 0, void 0, function () { var data; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, wrapState(state, this.address, this.publicKey, this.secret)]; case 1: data = _a.sent(); this.setLocal(data); // send update into the network this.broadcastStateUpdate(data); return [2 /*return*/]; } }); }); }; /** * @description Get the state of the user whose address is passed in. */ Db.prototype.get = function (address) { return this.localDB.get(address); }; /** * @description This will fire every time we update our state. This way reactive * UIs can listen for changes and update based on the new state of the world */ Db.prototype.onChange = function (handler) { this._onChangeHandlers.push(handler); }; /** * @description clear the DB of all listeners. */ Db.prototype.removeChangeHandlers = function () { this._onChangeHandlers = []; }; /** * @description clear the DB of a specific listener. */ Db.prototype.removeChangeHandler = function (func) { var _this = this; var handlers = Array.from(this._onChangeHandlers); handlers.forEach(function (handler, i) { if (handler === func) { _this._onChangeHandlers.splice(i, 1); } }); }; Db.prototype.onStateOffering = function (offerings, sender) { var _this = this; var requestState = function (address) { _this.network.broadcast({ type: 'state-request', appId: _this.appId, destination: sender, data: address }); }; for (var remoteId in offerings) { var remoteStateTimestamp = offerings[remoteId]; var localState = this.localDB.get(remoteId); if (this.isForbidden(remoteId)) { continue; } if (!localState) { // we don't even have this state yet requestState(remoteId); } else if (localState.timestamp < remoteStateTimestamp) { // This means they have a newer offering, for which we will now ask requestState(remoteId); } // Otherwise we have a newer or equal version } }; Db.prototype.onStateUpdate = function (update) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.isForbidden(update.publicKey)) { return [2 /*return*/, debug(5, 'state update from pubKey not allowed:', update.publicKey)]; } debug(5, 'received update from another node:', update); return [4 /*yield*/, this.verify(update)]; case 1: if (!(_a.sent())) { return [2 /*return*/]; } this.setLocal(update); return [2 /*return*/]; } }); }); }; // We won't accept state if: // * The sender is on our deny list, or // * We have an allow list going and the sender is not on it Db.prototype.isForbidden = function (address) { var isForbidden = this._denyList[address] || (Object.keys(this._allowList).length > 0 && !this._allowList[address]); return isForbidden; }; // Broadcast an update for a specific state id Db.prototype.broadcastStateUpdate = function (data) { this.network.broadcast({ type: 'state-update', appId: this.appId, data: data }); }; Db.prototype.broadcastStateUpdateByStateId = function (stateId) { var data = this.localDB.get(stateId); if (!data) { return; } this.broadcastStateUpdate(data); }; Db.prototype.setLocal = function (wrapped) { this.localDB.set(wrapped, wrapped.id); // Every time we set local, we've updated our storage, and we // want to inform the user as such this.runChangeHandlers(); }; Db.prototype.verify = function (update) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: // 1. verify timestamp is newer than last if (!this.isNew(update)) { return [2 /*return*/, false]; } return [4 /*yield*/, verifySignature(update)]; case 1: // 2. check veracity of signature if (!(_a.sent())) { debug(1, 'update does not pass verification! update, local version:', update, this.localDB.get(update.id)); // TODO add motrucka to rude list return [2 /*return*/, false]; } return [2 /*return*/, true]; } }); }); }; Db.prototype.isNew = function (update) { var local = this.get(update.id); if (!local) return true; return update.timestamp > local.timestamp; }; return Db; }()); exports["default"] = Db; }); },{"./LocalDB":1,"./util":3,"@browser-network/crypto":4}],3:[function(require,module,exports){ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports"], factory); } })(function (require, exports) { "use strict"; exports.__esModule = true; exports.exhaustive = exports.isPromise = exports.debugFactory = void 0; var debugFactory = function (appName) { return function (logLevel) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } if (globalThis['DEBUG'] >= logLevel) { console.log.apply(console, __spreadArray(["[".concat(logLevel, "] ").concat(appName, ": ")], args, false)); } }; }; exports.debugFactory = debugFactory; var isPromise = function (promise) { return promise.hasOwnProperty('then'); }; exports.isPromise = isPromise; var exhaustive = function (arg) { throw new Error('can not have gotten here'); }; exports.exhaustive = exhaustive; }); },{}],4:[function(require,module,exports){ (function (Buffer){(function (){ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "crypto", "eccrypto"], factory); } })(function (require, exports) { "use strict"; exports.__esModule = true; exports.generateSecret = exports.derivePubKey = exports.verifySignature = exports.sign = exports.hash = exports.stob = exports.btos = void 0; var crypto_1 = require("crypto"); var eccrypto = __importStar(require("eccrypto")); // Buffer to string. eccrypto uses buffers for everything but we want to use strings. // This is to convert. var btos = function (buffer) { return buffer.toString('hex'); }; exports.btos = btos; // String to buffer. Takes a string, like a key, and turns it into a buffer that // eccrypto can use. var stob = function (str) { return Buffer.from(str, 'hex'); }; exports.stob = stob; // Generate a hash of any arbitrary data, so long as it's JSON stringifiable. var hash = function (data) { return (0, crypto_1.createHash)('sha256').update(JSON.stringify(data)).digest(); }; exports.hash = hash; // Take an object and create a signature for it based on a given private key. var sign = function (secret, obj) { return __awaiter(void 0, void 0, void 0, function () { var stringState, hashBuff, keyBuff, sigBuff, signature; return __generator(this, function (_a) { switch (_a.label) { case 0: stringState = JSON.stringify(obj); hashBuff = (0, exports.hash)(stringState); keyBuff = (0, exports.stob)(secret); return [4 /*yield*/, eccrypto.sign(keyBuff, hashBuff)]; case 1: sigBuff = _a.sent(); signature = (0, exports.btos)(sigBuff); return [2 /*return*/, signature]; } }); }); }; exports.sign = sign; // Take an object, signature and pub key, and ensure that the signature matches the object // given the public key. var verifySignature = function (object, signature, publicKey) { return __awaiter(void 0, void 0, void 0, function () { var stringObj, hashBuf, pubBuf, sigBuf, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: stringObj = JSON.stringify(object); hashBuf = (0, exports.hash)(stringObj); pubBuf = (0, exports.stob)(publicKey); sigBuf = (0, exports.stob)(signature); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, eccrypto.verify(pubBuf, hashBuf, sigBuf)]; case 2: _a.sent(); return [2 /*return*/, true]; case 3: e_1 = _a.sent(); return [2 /*return*/, false]; case 4: return [2 /*return*/]; } }); }); }; exports.verifySignature = verifySignature; // Derive an EC public key from a given private key. var derivePubKey = function (secret) { return (0, exports.btos)(eccrypto.getPublicCompressed((0, exports.stob)(secret))); }; exports.derivePubKey = derivePubKey; // Generate the type of private key that network uses for its // cryptography. Generating one of these is as good as creating a new identity // on the network. var generateSecret = function () { var privBuf = eccrypto.generatePrivate(); return (0, exports.btos)(privBuf); }; exports.generateSecret = generateSecret; }); // TODO encrypt, decrypt, diffie helman }).call(this)}).call(this,require("buffer").Buffer) },{"buffer":67,"crypto":75,"eccrypto":87}],5:[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":6,"./asn1/base":8,"./asn1/constants":12,"./asn1/decoders":14,"./asn1/encoders":17,"bn.js":19}],6:[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":14,"./encoders":17,"inherits":137}],7:[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":10,"inherits":137,"safer-buffer":166}],8:[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":7,"./node":9,"./reporter":10}],9:[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, p