Source: messages.js

/**
 * 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 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
 */

"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://bitmessage.org/wiki/Protocol_specification#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 = new Buffer(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<number>=} 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 = 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,
      structs.var_int_list.encode(streams),
    ]);
  },
};

var IPv6_LOOPBACK = new Buffer(
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
);

// Check whether given encoded IPv6 or IPv4-mapped IPv6 is in private
// network range. See
// <https://en.wikipedia.org/wiki/Reserved_IP_addresses> 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:
    // <https://github.com/jshint/jshint/issues/1465>.
    } 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://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;
    // 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://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),
    ]);
  },
};