/** * Working with messages. * @see {@link https://bitmessage.org/wiki/Protocol_specification#Message_types} * @see {@link https://bitmessage.org/wiki/Protocol_specification_v3#Message_types} * @see {@link https://bitmessage.org/Bitmessage%20Technical%20Paper.pdf} * @module bitmessage/messages * @example * var structs = require("bitmessage").structs; * var messages = require("bitmessage").messages; * * // Simple encoding and decoding: * var vermsg = messages.version.encode({ * remoteHost: "1.1.1.1", * remotePort: 8444, * }); * console.log(messages.version.decode(vermsg).remoteHost); // 1.1.1.1 * * // Low-level encoding and decoding: * var addrPayload = messages.addr.encodePayload([ * {host: "2.2.2.2", port: 28444}, * ]); * var addrmsg = structs.message.encode("addr", addrPayload); * var decoded = structs.message.decode(addrmsg); * console.log(decoded.command); // addr * var payload = decoded.payload; * var decodedPayload = messages.addr.decodePayload(payload); * console.log(decodedPayload.addrs[0].host); // 2.2.2.2 * * // Encode with empty payload: * var verackmsg = structs.message.encode("verack"); * console.log(structs.message.decode(verackmsg).command); // verack */ "use strict"; var objectAssign = Object.assign || require("object-assign"); var assert = require("./_util").assert; var structs = require("./structs"); var bmcrypto = require("./crypto"); var UserAgent = require("./user-agent"); var util = require("./_util"); var message = structs.message; var ServicesBitfield = structs.ServicesBitfield; /** * Try to get command of the given encoded message. * Note that this function doesn't do any validation because it is * already provided by * [message.decode]{@link module:bitmessage/structs.message.decode} * routine. * @param {Buffer} buf - Buffer that starts with encoded message * @return {?string} Message's command if any. */ exports.getCommand = function(buf) { if (buf.length < 16) { return; } var command = buf.slice(4, 16); var firstNonNull = 0; for (var i = 11; i >=0; i--) { if (command[i] !== 0) { firstNonNull = i + 1; break; } } return command.slice(0, firstNonNull).toString("ascii"); }; /** * `version` message. * @see {@link https://bitmessage.org/wiki/Protocol_specification#version} * @namespace * @static */ // TODO(Kagami): User agent and stream numbers size limits per // . var version = exports.version = { /** * @typedef {Object} DecodeResult * @property {number} protoVersion - Identifies protocol version being * used by the node. Should equal 3. Nodes should disconnect if the * remote node's version is lower but continue with the connection if * it is higher. * @property {Object} services - * [Service]{@link module:bitmessage/structs.ServicesBitfield} * features to be enabled for this connection * @property {Date} time - Node time * @property {string} remoteHost - IPv4/IPv6 address of the node * receiving this message * @property {number} remotePort - Port of the node receiving this * message * @property {number} port - Incoming port of the node sending this * message * @property {Buffer} nonce - An 8-byte random nonce used to detect * connection to self * @property {string} userAgent - [User agent]{@link * module:bitmessage/user-agent} of the node * @property {number[]} streams - Streams accepted by the node * @property {number} length - Real data length * @memberof module:bitmessage/messages.version */ /** * Random nonce used to detect connections to self. * @constant {Buffer} */ randomNonce: bmcrypto.randomBytes(8), /** * Decode `version` message. * NOTE: `nonce` is copied. * @param {Buffer} buf - Message * @return {DecodeResult} * [Decoded `version` structure.]{@link module:bitmessage/messages.version.DecodeResult} */ decode: function(buf) { var decoded = message.decode(buf); assert(decoded.command === "version", "Bad command"); return version.decodePayload(decoded.payload); }, /** * Decode `version` message payload. * The same as [decode]{@link module:bitmessage/messages.version.decode}. */ decodePayload: function(buf) { // 4 + 8 + 8 + 26 + 26 + 8 + (1+) + (1+) assert(buf.length >= 82, "Buffer is too small"); var protoVersion = buf.readUInt32BE(0, true); var services = ServicesBitfield(buf.slice(4, 12), {copy: true}); var time = util.readTime64BE(buf, 12); var short = {short: true}; var addrRecv = structs.net_addr.decode(buf.slice(20, 46), short); var addrFrom = structs.net_addr.decode(buf.slice(46, 72), short); var nonce = new Buffer(8); buf.copy(nonce, 0, 72, 80); var decodedUa = UserAgent.decode(buf.slice(80)); var decodedStreams = structs.var_int_list.decode(decodedUa.rest); return { protoVersion: protoVersion, services: services, time: time, remoteHost: addrRecv.host, remotePort: addrRecv.port, port: addrFrom.port, nonce: nonce, userAgent: decodedUa.str, streams: decodedStreams.list, // NOTE(Kagami): Real data length. It may be some gap between end // of stream numbers list and end of payload: // [payload..............[stream numbers]xxxx] // We are currently ignoring that. length: 80 + decodedUa.length + decodedStreams.length, }; }, /** * Encode `version` message. * @param {Object} opts - Version options * @param {Object=} opts.services - * [Service]{@link module:bitmessage/structs.ServicesBitfield} * features to be enabled for this connection (`NODE_NETWORK` by * default) * @param {Date=} opts.time - Node time (current time by default) * @param {string} opts.remoteHost - IPv4/IPv6 address of the node * receiving this message * @param {number} opts.remotePort - Port of the node receiving this * message * @param {number=} opts.port - Incoming port of the node (8444 by * default) * @param {Buffer=} opts.nonce - An 8-byte random nonce used to detect * connection to self (unique per node.js process by default) * @param {(Array|string|Buffer)=} opts.userAgent - * [User agent]{@link module:bitmessage/user-agent} of the node * (user agent of bitmessage library by default) * @param {Array=} opts.streams - Streams accepted by the node * ([1] by default) * @return {Buffer} Encoded message. */ encode: function(opts) { var payload = version.encodePayload(opts); return message.encode("version", payload); }, /** * Encode `version` message payload. * The same as [encode]{@link module:bitmessage/messages.version.encode}. */ encodePayload: function(opts) { // Deal with default options. var services = opts.services || ServicesBitfield().set(ServicesBitfield.NODE_NETWORK); var time = opts.time || new Date(); var nonce = opts.nonce || version.randomNonce; assert(nonce.length === 8, "Bad nonce"); var port = opts.port || 8444; var userAgent = opts.userAgent || UserAgent.SELF; var streams = opts.streams || [1]; // Start encoding. var protoVersion = new Buffer(4); protoVersion.writeUInt32BE(util.PROTOCOL_VERSION, 0); var addrRecv = structs.net_addr.encode({ services: services, host: opts.remoteHost, port: opts.remotePort, short: true, }); var addrFrom = structs.net_addr.encode({ services: services, host: "127.0.0.1", port: port, short: true, }); return Buffer.concat([ protoVersion, services.buffer, util.writeTime64BE(null, time), addrRecv, addrFrom, nonce, UserAgent.encode(userAgent), structs.var_int_list.encode(streams), ]); }, }; /** * `addr` message. Provide information on known nodes of the network. * @see {@link https://bitmessage.org/wiki/Protocol_specification#addr} * @namespace * @static */ var addr = exports.addr = { /** * @typedef {Object} DecodeResult * @property {Object[]} addrs - List of * [decoded `net_addr` structures]{@link module:bitmessage/structs.net_addr.DecodeResult} * @property {number} length - Real data length * @memberof module:bitmessage/messages.addr */ /** * Decode `addr` message. * @param {Buffer} buf - Message * @return {DecodeResult} * [Decoded `addr` structure.]{@link module:bitmessage/messages.addr.DecodeResult} */ decode: function(buf) { var decoded = message.decode(buf); assert(decoded.command === "addr", "Bad command"); return addr.decodePayload(decoded.payload); }, /** * Decode `addr` message payload. * The same as [decode]{@link module:bitmessage/messages.addr.decode}. */ decodePayload: function(buf) { var decoded = structs.var_int.decode(buf); var listLength = decoded.value; assert(listLength <= 1000, "Too many address entires"); var length = decoded.length + listLength * 38; assert(buf.length >= length, "Buffer is too small"); var rest = decoded.rest; var addrs = new Array(listLength); for (var i = 0; i < listLength; i++) { addrs[i] = structs.net_addr.decode(rest.slice(i*38, (i+1)*38)); } return { addrs: addrs, // Real data length. length: length, }; }, /** * Encode `addr` message. * @param {Object[]} addrs - List of * [net_addr encode options]{@link module:bitmessage/structs.net_addr.encode} * @return {Buffer} Encoded message. */ encode: function(addrs) { var payload = addr.encodePayload(addrs); return message.encode("addr", payload); }, /** * Encode `addr` message payload. * The same as [encode]{@link module:bitmessage/messages.addr.encode}. */ encodePayload: function(addrs) { assert(addrs.length <= 1000, "Too many address entires"); var addrBufs = addrs.map(structs.net_addr.encode); var bufs = [structs.var_int.encode(addrs.length)].concat(addrBufs); return Buffer.concat(bufs); }, }; /** * `inv` message. Allows a node to advertise its knowledge of one or * more objects. * @see {@link https://bitmessage.org/wiki/Protocol_specification#inv} * @namespace * @static */ var inv = exports.inv = { /** * @typedef {Object} DecodeResult * @property {Buffer[]} inventory - List of * [inventory vectors]{@link module:bitmessage/structs.inv_vect} * @property {number} length - Real data length * @memberof module:bitmessage/messages.inv */ /** * Decode `inv` message. * @param {Buffer} buf - Message * @return {DecodeResult} * [Decoded `inv` structure.]{@link module:bitmessage/messages.inv.DecodeResult} */ decode: function(buf) { var decoded = message.decode(buf); assert(decoded.command === "inv", "Bad command"); return inv.decodePayload(decoded.payload); }, /** * Decode `inv` message payload. * The same as [decode]{@link module:bitmessage/messages.inv.decode}. */ decodePayload: function(buf) { var decoded = structs.var_int.decode(buf); var listLength = decoded.value; assert(listLength <= 50000, "Too many inventory entires"); var length = decoded.length + listLength * 32; assert(buf.length >= length, "Buffer is too small"); var rest = decoded.rest; var inventory = new Array(listLength); for (var i = 0; i < listLength; i++) { inventory[i] = rest.slice(i*32, (i+1)*32); } return { inventory: inventory, // Real data length. length: length, }; }, /** * Encode `inv` message. * @param {Buffer[]} inventory - * [Inventory vector]{@link module:bitmessage/structs.inv_vect} list * @return {Buffer} Encoded message. */ encode: function(inventory) { var payload = inv.encodePayload(inventory); return message.encode("inv", payload); }, /** * Encode `inv` message payload. * The same as [encode]{@link module:bitmessage/messages.inv.encode}. */ encodePayload: function(inventory) { assert(inventory.length <= 50000, "Too many inventory entires"); // TODO(Kagami): Validate vectors length. var bufs = [structs.var_int.encode(inventory.length)].concat(inventory); return Buffer.concat(bufs); }, }; /** * `getdata` message. `getdata` is used in response to an * [inv]{@link module:bitmessage/messages.inv} message to retrieve the * content of a specific object after filtering known elements. * @see {@link https://bitmessage.org/wiki/Protocol_specification#getdata} * @namespace */ exports.getdata = objectAssign({}, inv, { /** * @typedef {Object} DecodeResult * @property {Buffer[]} inventory - List of * [inventory vectors]{@link module:bitmessage/structs.inv_vect} * @property {number} length - Real data length * @memberof module:bitmessage/messages.getdata */ /** * Decode `getdata` message. * @param {Buffer} buf - Message * @return {DecodeResult} * [Decoded `getdata` structure.]{@link module:bitmessage/messages.getdata.DecodeResult} * @memberof module:bitmessage/messages.getdata */ decode: function(buf) { var decoded = message.decode(buf); assert(decoded.command === "getdata", "Bad command"); return inv.decodePayload(decoded.payload); }, /** * Decode `getdata` message payload. * The same as [decode]{@link module:bitmessage/messages.getdata.decode}. * @function decodePayload * @memberof module:bitmessage/messages.getdata */ /** * Encode `getdata` message. * @param {Buffer[]} inventory - * [Inventory vector]{@link module:bitmessage/structs.inv_vect} list * @return {Buffer} Encoded message. * @memberof module:bitmessage/messages.getdata */ encode: function(inventory) { var payload = inv.encodePayload(inventory); return message.encode("getdata", payload); }, /** * Encode `getdata` message payload. * The same as [encode]{@link module:bitmessage/messages.getdata.encode}. * @function encodePayload * @memberof module:bitmessage/messages.getdata */ }); /** * `error` message. * @see {@link https://bitmessage.org/wiki/Protocol_specification_v3#error} * @namespace * @static */ var error = exports.error = { /** * Just a warning. * @constant {number} */ WARNING: 0, /** * It's an error, something was going wrong (e.g. an object got lost). * @constant {number} */ ERROR: 1, /** * It's a fatal error. The node will drop the line for that error and * maybe ban you for some time. * @constant {number} */ FATAL: 2, /** * Convert error type to a human-readable string. * @param {number} type - Type of the error * @return {string} */ type2str: function(type) { switch (type) { case error.WARNING: return "warning"; case error.ERROR: return "error"; case error.FATAL: return "fatal"; default: return "unknown"; } }, /** * @typedef {Object} DecodeResult * @property {number} type - Type of the error * @property {number} banTime - The other node informs that it will * not accept further connections for this number of seconds * @property {?Buffer} vector - * [Inventory vector]{@link module:bitmessage/structs.inv_vect} * related to the error * @property {string} errorText - A human-readable error description * @property {number} length - Real data length * @memberof module:bitmessage/messages.error */ /** * Decode `error` message. * @param {Buffer} buf - Message * @return {DecodeResult} * [Decoded `error` structure.]{@link module:bitmessage/messages.error.DecodeResult} */ decode: function(buf) { var decoded = message.decode(buf); assert(decoded.command === "error", "Bad command"); return error.decodePayload(decoded.payload); }, /** * Decode `error` message payload. * The same as [decode]{@link module:bitmessage/messages.error.decode}. */ decodePayload: function(buf) { assert(buf.length >= 4, "Buffer is too small"); var decodedType = structs.var_int.decode(buf); var decodedBanTime = structs.var_int.decode(decodedType.rest); var decodedVectorLength = structs.var_int.decode(decodedBanTime.rest); // NOTE(Kagami): Inventory vector should be only 32-byte in size but // currently we don't ensure it. var vectorLength = decodedVectorLength.value; var rest = decodedVectorLength.rest; assert(rest.length >= vectorLength, "Buffer is too small"); var vector = null; if (vectorLength) { vector = new Buffer(vectorLength); rest.copy(vector); rest = rest.slice(vectorLength); } var decodedErrorText = structs.var_str.decode(rest); var length = ( decodedType.length + decodedBanTime.length + decodedVectorLength.length + vectorLength + decodedErrorText.length ); return { type: decodedType.value, banTime: decodedBanTime.value, vector: vector, errorText: decodedErrorText.str, // Real data length. length: length, }; }, /** * Encode `error` message. * @param {Object} opts - Error options * @param {number=} opts.type - Type of the error * ([warning]{@link module:bitmessage/messages.error.WARNING} by * default) * @param {number=} opts.banTime - Inform the other node, that you * will not accept further connections for this number of seconds (0 * by default) * @param {Buffer=} opts.vector - A 32-byte * [inventory vector]{@link module:bitmessage/structs.inv_vect} * related to the error (empty by default) * @param {string} opts.errorText - A human-readable error description * @return {Buffer} Encoded message. */ encode: function(opts) { var payload = error.encodePayload(opts); return message.encode("error", payload); }, /** * Encode `error` message payload. * The same as [encode]{@link module:bitmessage/messages.error.encode}. */ encodePayload: function(opts) { var type = opts.type || error.WARNING; var banTime = opts.banTime || 0; // TODO(Kagami): Validate vector length. var vector = opts.vector || new Buffer(0); return Buffer.concat([ structs.var_int.encode(type), structs.var_int.encode(banTime), structs.var_int.encode(vector.length), vector, structs.var_str.encode(opts.errorText), ]); }, };