/** * Working with messages. * @see {@link https://pybitmessage.rtfd.io/en/v0.6/protocol.html#message-types} * @see {@link https://wiki.bitmessage.org/w/index.php?title=Protocol_specification&oldid=23#Message_types} * @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 addr = messages.addr.decodePayload(decoded.payload); * console.log(addr.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 */ // broken link: https://bitmessage.org/Bitmessage%20Technical%20Paper.pdf "use strict"; var objectAssign = Object.assign || require("object-assign"); var bufferEqual = require("buffer-equal"); 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; var IPv4_MAPPING = util.IPv4_MAPPING; var inet_pton = util.inet_pton; /** * 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://pybitmessage.rtfd.io/en/v0.6/protocol.html#version} * @namespace * @static */ 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 = Buffer.alloc(8); buf.copy(nonce, 0, 72, 80); var decodedUa = UserAgent.decode(buf.slice(80)); assert(decodedUa.length <= 5000, "User agent is too long"); var decodedStreams = structs.var_int_list.decode(decodedUa.rest); assert(decodedStreams.list.length <= 160000, "Too many streams"); 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 = UserAgent.encode(opts.userAgent || UserAgent.SELF); assert(userAgent.length <= 5000, "User agent is too long"); var streams = opts.streams || [1]; assert(streams.length <= 160000, "Too many streams"); // Start encoding. var protoVersion = Buffer.alloc(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, structs.var_int_list.encode(streams), ]); }, }; var IPv6_LOOPBACK = Buffer.from("00000000000000000000000000000001", "hex"); // Check whether given encoded IPv6 or IPv4-mapped IPv6 is in private // network range. See // for details. // TODO(Kagami): Do we also need to filter multicasts and other reserved // ranges? function isPrivateIp(buf) { // IPv4. if (bufferEqual(buf.slice(0, 12), IPv4_MAPPING)) { buf = buf.slice(12); if (buf[0] === 127) { return true; } else if (buf[0] === 10) { return true; } else if (buf[0] === 192 && buf[1] === 168) { return true; // XXX(Kagami): ignore:start and ignore:end doesn't ignore this for // some reason. Probably related: // . } else if (buf[0] === 172 && (buf[1] & 0xf0) === 0x10) {//jshint ignore:line return true; } else if (buf[0] === 169 && buf[1] === 254) { return true; } else { return false; } // IPv6. } else { if (bufferEqual(buf, IPv6_LOOPBACK)) { return true; } else if (buf[0] === 0xfe && (buf[1] & 0xc0) === 0x80) {//jshint ignore:line return true; } else if ((buf[0] & 0xfe) === 0xfc) { // jshint ignore:line return true; } else { return false; } } } // Helper to make it easier to filter out private IPs. function checkAddrOpts(opts) { return !isPrivateIp(inet_pton(opts.host)); } /** * `addr` message. Provide information on known nodes of the network. * @see {@link https://pybitmessage.rtfd.io/en/v0.6/protocol.html#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; // NOTE(Kagami): Check length before filtering private IPs because // we shouldn't even receive them. 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 = []; var addrBuf; for (var i = 0; i < listLength; i++) { addrBuf = rest.slice(i*38, (i+1)*38); if (!isPrivateIp(addrBuf.slice(20, 36))) { addrs.push(structs.net_addr.decode(addrBuf)); } } 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) { addrs = addrs.filter(checkAddrOpts); 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://pybitmessage.rtfd.io/en/v0.6/protocol.html#inv} * @namespace * @static */ var inv = exports.inv = { /** * @typedef {Object} DecodeResult * @property {Buffer[]} vectors - 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 vectors"); var length = decoded.length + listLength * 32; assert(buf.length >= length, "Buffer is too small"); var rest = decoded.rest; var vectors = new Array(listLength); for (var i = 0; i < listLength; i++) { vectors[i] = rest.slice(i*32, (i+1)*32); } return { vectors: vectors, // Real data length. length: length, }; }, /** * Encode `inv` message. * @param {Buffer[]} vectors - [Inventory vector]{@link * module:bitmessage/structs.inv_vect} list * @return {Buffer} Encoded message. */ encode: function(vectors) { var payload = inv.encodePayload(vectors); return message.encode("inv", payload); }, /** * Encode `inv` message payload. * The same as [encode]{@link module:bitmessage/messages.inv.encode}. */ encodePayload: function(vectors) { assert(vectors.length <= 50000, "Too many vectors"); // TODO(Kagami): Validate vectors length. var bufs = [structs.var_int.encode(vectors.length)].concat(vectors); 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://pybitmessage.rtfd.io/en/v0.6/protocol.html#getdata} * @namespace */ exports.getdata = objectAssign({}, inv, { /** * @typedef {Object} DecodeResult * @property {Buffer[]} vectors - 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[]} vectors - [Inventory vector]{@link * module:bitmessage/structs.inv_vect} list * @return {Buffer} Encoded message. * @memberof module:bitmessage/messages.getdata */ encode: function(vectors) { var payload = inv.encodePayload(vectors); 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 = Buffer.alloc(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 || Buffer.alloc(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), ]); }, };