const crypto = require('crypto') const cbor = require('borc') const EventEmitter = require('events') const Buffer = require('safe-buffer').Buffer const TAGS = { id: 41, link: 42, func: 43, mod: 44 } /** * a cbor decoder for the objects * @type {Object} */ const decoder = new cbor.Decoder({ tags: { [TAGS.id]: val => new ID(val), [TAGS.func]: val => new FunctionRef({ identifier: val[0], params: val[1], actorID: val[2], gas: val[3] }), [TAGS.mod]: val => new ModuleRef(...val), [TAGS.link]: val => { return { '/': val } } } }) /** * an ID */ class ID { /* * @param {Buffer} id - the id as an buffer */ constructor (id) { this.id = id } toString () { return this.id.toString('hex') } toJSON () { return `0x${this.toString()}` } static fromJSON (arg) { const { fromHex } = require('./utils') return new ID(fromHex(arg)) } encodeCBOR (gen) { return gen.write(new cbor.Tagged(TAGS.id, this.id)) } } /** * A function reference */ class FunctionRef { /** * @param {Object} opts * @param {*} opts.identifier - the function's identifier * @param {ID} opts.actorID - the id of the actor * @param {Array} opts.params - the params of the function */ constructor (opts) { this.identifier = opts.identifier this.actorID = opts.actorID this.params = opts.params this.gas = opts.gas || 0 } encodeCBOR (gen) { return gen.write(new cbor.Tagged(TAGS.func, [ this.identifier, this.params, this.actorID, this.gas ])) } toJSON (includeParams = false) { const json = { '@FunctionRef': { actorID: this.actorID.toJSON(), private: this.identifier[0], name: this.identifier[1], gas: this.gas } } if (includeParams) { json['@FunctionRef'].params = this.params } return json } static fromJSON (arg) { const data = arg['@FunctionRef'] return new FunctionRef({ identifier: [data.private, data.name], actorID: ID.fromJSON(data.actorID), params: data.params, gas: data.gas }) } /** * Creates a copy of the funcRef * @returns {FunctionRef} */ copy () { return new FunctionRef({ identifier: this.identifier, actorID: this.actorID, params: this.params, gas: this.gas }) } } /** * A module reference */ class ModuleRef { /** * @param {Object} exports - a map of exported function to params for the funcion if any * @param {ID} id - the id of the actor */ constructor (exports, id) { this.exports = exports this.id = id } /** * return a function refernce given the name of the function * @param {string} name * @returns {FunctionRef} */ getFuncRef (name) { const params = this.exports[name] return new FunctionRef({ identifier: [false, name], params, actorID: this.id }) } toJSON (includeExports = false) { const json = { '@ModuleRef': { id: this.id.toJSON() } } if (includeExports) { json['@ModuleRef'].exports = this.exports } return json } static fromJSON (arg) { const data = arg['@ModuleRef'] return new ModuleRef(data.exports, ID.fromJSON(data.id)) } encodeCBOR (gen) { return gen.write(new cbor.Tagged(TAGS.mod, [this.exports, this.id])) } } /** * This implements Messages for Primea */ class Message extends EventEmitter { /** * @param {Object} opts * @param {ArrayBuffer} opts.data - the payload of the message * @param {Array} opts.caps - an array of capabilities to send in the message */ constructor (opts) { super() const defaults = this.constructor.defaults this._opts = Object.assign(defaults, opts) Object.keys(this._opts).forEach(key => { Object.defineProperty(this, key, { get: function () { return this._opts[key] }, set: function (y) { this._opts[key] = y } }) }) } static get defaults () { return { ticks: 0, funcRef: null, funcArguments: [], funcParameters: [], _fromId: new ID(Buffer.alloc(20)), _fromTicks: 0 } } } /** * returns the type that the object is * @param {*} obj * @return {String} */ function getType (obj) { if (obj) { if (Buffer.isBuffer(obj)) { return 'data' } else if (Array.isArray(obj)) { return 'elem' } else if (obj['/']) { return 'link' } else if (obj.constructor === Message) { return 'message' } else if (obj.constructor === ID) { return 'id' } else if (obj.constructor === FunctionRef) { return 'func' } else if (obj.constructor === ModuleRef) { return 'mod' } } return 'invalid' } /** * returns the ID of an actor * @param {Object} id * @param {Number} id.nonce - the actor's nonce * @param {ID} id.parent - the actor's parent's ID * @return {ID} */ function generateActorId (id) { const encoded = _encodeActorId(id) const hashed = _hash(encoded) return new ID(hashed) } function _encodeActorId (id) { if (id.parent) { return cbor.encode([id.nonce, id.parent.id]) } else { return cbor.encode([id.nonce, null]) } } function _hash (buf) { const hash = crypto.createHash('sha256') hash.update(buf) return hash.digest().slice(0, 20) } module.exports = { Message, ID, FunctionRef, ModuleRef, decoder, getType, generateActorId }