Source: transport/messages.js

'use strict';
/**
 * @namespace Transport.Message
 */
/* jshint curly: false */

var Buffers = require('buffers');
var Put = require('bufferput');
var util = require('util');

var BlockHeaderModel = require('../blockheader');
var BlockModel = require('../block');
var BufferReader = require('../encoding/bufferreader');
var BufferUtil = require('../util/buffer');
var Hash = require('../crypto/hash');
var Random = require('../crypto/random');
var TransactionModel = require('../transaction');

var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8);
var PROTOCOL_VERSION = 70000;

/**
 * Static helper for consuming a data buffer until the next message.
 *
 * @name Transport.Message#parseMessage
 * @param{Network} network - the network object
 * @param{Buffer} dataBuffer - the buffer to read from
 * @returns{Message|undefined} A message or undefined if there is nothing to read.
 */
var parseMessage = function(network, dataBuffer) {
  if (dataBuffer.length < 20) return;

  // Search the next magic number
  if (!discardUntilNextMessage(network, dataBuffer)) return;

  var PAYLOAD_START = 16;
  var payloadLen = (dataBuffer.get(PAYLOAD_START)) +
    (dataBuffer.get(PAYLOAD_START + 1) << 8) +
    (dataBuffer.get(PAYLOAD_START + 2) << 16) +
    (dataBuffer.get(PAYLOAD_START + 3) << 24);

  var messageLength = 24 + payloadLen;
  if (dataBuffer.length < messageLength) return;

  var command = dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, '');
  var payload = dataBuffer.slice(24, messageLength);
  var checksum = dataBuffer.slice(20, 24);

  var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4);
  if (!BufferUtil.equals(checksumConfirm, checksum)) {
    dataBuffer.skip(messageLength);
    return;
  }

  dataBuffer.skip(messageLength);
  return Message.buildMessage(command, payload);
};

module.exports.parseMessage = parseMessage;

/**
 * @desc Internal function that discards data until founds the next message.
 * @name Transport.Message#discardUntilNextMessage
 */
function discardUntilNextMessage(network, dataBuffer) {
  var magicNumber = network.networkMagic;

  var i = 0;
  for (;;) {
    // check if it's the beginning of a new message
    var packageNumber = dataBuffer.slice(0, 4);
    if (BufferUtil.equals(packageNumber, magicNumber)) {
      dataBuffer.skip(i);
      return true;
    }

    // did we reach the end of the buffer?
    if (i > (dataBuffer.length - 4)) {
      dataBuffer.skip(i);
      return false;
    }

    i++; // continue scanning
  }
}

/**
 * Abstract Message that knows how to parse and serialize itself.
 * Concret subclases should implement {fromBuffer} and {getPayload} methods.
 * @name Transport.Message
 */
function Message() {}

/**
 * @value
 * @name Transport.Message.COMMANDS
 */
Message.COMMANDS = {};

/**
 * Look up a message type by command name and instantiate the correct Message
 * @name Transport.Message#buildMessage
 */
Message.buildMessage = function(command, payload) {
  try {
    var CommandClass = Message.COMMANDS[command];
    return new CommandClass().fromBuffer(payload);
  } catch (err) {
    console.log('Error while parsing message', err);
  }
};

/**
 * Parse instance state from buffer.
 *
 * @param{Buffer} payload - the buffer to read from
 * @returns{Message} The same message instance
 */
Message.prototype.fromBuffer = function(payload) {
  /* jshint unused: false */
  return this;
};

/**
 * Serialize the payload into a buffer.
 *
 * @returns{Buffer} the serialized payload
 */
Message.prototype.getPayload = function() {
  return BufferUtil.EMPTY_BUFFER;
};

/**
 * Serialize the message into a buffer.
 *
 * @returns{Buffer} the serialized message
 */
Message.prototype.serialize = function(network) {
  var magic = network.networkMagic;
  var commandBuf = new Buffer(this.command, 'ascii');
  if (commandBuf.length > 12) throw 'Command name too long';

  var payload = this.getPayload();
  var checksum = Hash.sha256sha256(payload).slice(0, 4);

  // -- HEADER --
  var message = new Put();
  message.put(magic);
  message.put(commandBuf);
  message.pad(12 - commandBuf.length); // zero-padded
  message.word32le(payload.length);
  message.put(checksum);

  // -- BODY --
  message.put(payload);

  return message.buffer();
}

/**
 * Version Message
 *
 * @name Transport.Message.Version
 * @param{string} subversion - version of the client
 * @param{Buffer} nonce - a random 8 bytes buffer
 */
function Version(subversion, nonce) {
  this.command = 'version';
  this.version = PROTOCOL_VERSION;
  this.subversion = subversion || '/BitcoinX:0.1/';
  this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Version, Message);

Version.prototype.fromBuffer = function(payload) {
  var parser = new BufferReader(payload);
  this.version = parser.readUInt32LE();
  this.services = parser.readUInt64LEBN();
  this.timestamp = parser.readUInt64LEBN();
  this.addr_me = parser.read(26);
  this.addr_you = parser.read(26);
  this.nonce = parser.read(8);
  this.subversion = parser.readVarintBuf().toString();
  this.start_height = parser.readUInt32LE();

  return this;
};

Version.prototype.getPayload = function() {
  var put = new Put();
  put.word32le(this.version); // version
  put.word64le(1); // services
  put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp
  put.pad(26); // addr_me
  put.pad(26); // addr_you
  put.put(this.nonce);
  put.varint(this.subversion.length);
  put.put(new Buffer(this.subversion, 'ascii'));
  put.word32le(0);

  return put.buffer();
};

module.exports.Version = Message.COMMANDS.version = Version;

/**
 * Inv Message
 *
 * @name Transport.Message.Inventory
 * @param{Array} inventory - reported elements
 */
function Inventory(inventory) {
  this.command = 'inv';
  this.inventory = inventory || [];
}
util.inherits(Inventory, Message);

Inventory.prototype.fromBuffer = function(payload) {
  var parser = new BufferReader(payload);
  var count = parser.readVarintNum();
  for (var i = 0; i < count; i++) {
    this.inventory.push({
      type: parser.readUInt32LE(),
      hash: parser.read(32)
    });
  }

  return this;
};

Inventory.prototype.getPayload = function() {
  var put = new Put();
  
  put.varint(this.inventory.length);
  this.inventory.forEach(function(value) {
    put.word32le(value.type);
    put.put(value.hash);
  });
  
  return put.buffer();
};

module.exports.Inventory = Message.COMMANDS.inv = Inventory;

/**
 * Getdata Message
 *
 * @name Transport.Message.GetData
 * @param{Array} inventory - requested elements
 */
function GetData(inventory) {
  this.command = 'getdata';
  this.inventory = inventory || [];
}

util.inherits(GetData, Inventory);
module.exports.GetData = GetData;

/**
 * Ping Message
 *
 * @name Transport.Message.Ping
 * @param{Buffer} nonce - a random 8 bytes buffer
 */
function Ping(nonce) {
  this.command = 'ping';
  this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Ping, Message);

Ping.prototype.fromBuffer = function(payload) {
  this.nonce = new BufferReader(payload).read(8);
  return this;
};

Ping.prototype.getPayload = function() {
  return this.nonce;
};

module.exports.Ping = Message.COMMANDS.ping = Ping;

/**
 * Pong Message
 *
 * @name Transport.Message.Pong
 * @param{Buffer} nonce - a random 8 bytes buffer
 */
function Pong(nonce) {
  this.command = 'pong';
  this.nonce = nonce || CONNECTION_NONCE;
}

util.inherits(Pong, Ping);
module.exports.Pong = Message.COMMANDS.pong = Pong;

/**
 * Addr Message
 *
 * @name Transport.Message.Addressess
 * @param{Array} addresses - array of know addresses
 */
function Addresses(addresses) {
  this.command = 'addr';
  this.addresses = addresses || [];
}
util.inherits(Addresses, Message);

Addresses.prototype.fromBuffer = function(payload) {
  var parser = new BufferReader(payload);
  var addrCount = Math.min(parser.readVarintNum(), 1000);

  this.addresses = [];
  for (var i = 0; i < addrCount; i++) {
    // TODO: Time actually depends on the version of the other peer (>=31402)
    this.addresses.push({
      time: parser.readUInt32LE(),
      services: parser.readUInt64LEBN(),
      ip: parser.read(16),
      port: parser.readUInt16BE()
    });
  }

  return this;
};

Addresses.prototype.getPayload = function() {
  var put = new Put();
  put.varint(this.addresses.length);

  for (var i = 0; i < this.addresses.length; i++) {
    put.word32le(this.addresses[i].time);
    put.word64le(this.addresses[i].services);
    put.put(this.addresses[i].ip);
    put.word16be(this.addresses[i].port);
  }

  return put.buffer();
};

module.exports.Addresses = Message.COMMANDS.addr = Addresses;

/**
 * GetAddr Message
 *
 * @name Transport.Message.GetAddresses
 */
function GetAddresses() {
  this.command = 'getaddr';
}

util.inherits(GetAddresses, Message);
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses;

/**
 * Verack Message
 *
 * @name Transport.Message.VerAck
 */
function VerAck() {
  this.command = 'verack';
}

util.inherits(VerAck, Message);
module.exports.VerAck = Message.COMMANDS.verack = VerAck;

/**
 * Reject Message
 *
 * @name Transport.Message.Reject
 */
function Reject() {
  this.command = 'reject';
}
util.inherits(Reject, Message);

// TODO: Parse REJECT message

module.exports.Reject = Message.COMMANDS.reject = Reject;

/**
 * Alert Message
 *
 * @name Transport.Message.Alert
 */
function Alert(payload, signature) {
  this.command = 'alert';
  this.payload = payload || new Buffer(32);
  this.signature = signature || new Buffer(32);
}
util.inherits(Alert, Message);

Alert.prototype.fromBuffer = function(payload) {
  var parser = new BufferReader(payload);
  this.payload = parser.readVarintBuf(); // TODO: Use current format
  this.signature = parser.readVarintBuf();
  return this;
};

Alert.prototype.getPayload = function() {
  var put = new Put();
  put.varint(this.payload.length);
  put.put(this.payload);

  put.varint(this.signature.length);
  put.put(this.signature);

  return put.buffer();
};

module.exports.Alert = Message.COMMANDS.alert = Alert;

/**
 * Headers Message
 *
 * @name Transport.Message.Headers
 * @param{Array} blockheaders - array of block headers
 */
function Headers(blockheaders) {
  this.command = 'headers';
  this.headers = blockheaders || [];
}
util.inherits(Headers, Message);

Headers.prototype.fromBuffer = function(payload) {
  var parser = new BufferReader(payload);
  var count = parser.readVarintNum();

  this.headers = [];
  for (var i = 0; i < count; i++) {
    var header = BlockHeaderModel._fromBufferReader(parser);
    this.headers.push(header);
  }

  return this;
};

Headers.prototype.getPayload = function() {
  var put = new Put();
  put.varint(this.headers.length);

  for (var i = 0; i < this.headers.length; i++) {
    var buffer = this.headers[i].toBuffer();
    put.put(buffer);
  }

  return put.buffer();
};

module.exports.Headers = Message.COMMANDS.headers = Headers;

/**
 * Block Message
 *
 * @name Transport.Message.Block
 * @param{Block} block
 */
function Block(block) {
  this.command = 'block';
  this.block = block;
}
util.inherits(Block, Message);

Block.prototype.fromBuffer = function(payload) {
  this.block = BlockModel(payload);
  return this;
};

Block.prototype.getPayload = function() {
  return this.block.toBuffer();
};

module.exports.Block = Message.COMMANDS.block = Block;

/**
 * Tx Message
 *
 * @name Transport.Message.Transaction
 * @param{Transaction} transaction
 */
function Transaction(transaction) {
  this.command = 'tx';
  this.transaction = transaction;
}
util.inherits(Transaction, Message);

Transaction.prototype.fromBuffer = function(payload) {
  this.transaction = TransactionModel(payload);
  return this;
};

Transaction.prototype.getPayload = function() {
  return this.transaction.toBuffer();
};

module.exports.Transaction = Message.COMMANDS.tx = Transaction;

/**
 * Getblocks Message
 *
 * @name Transport.Message.GetBlocks
 * @param{Array} starts - array of buffers with the starting block hashes
 * @param{Buffer} [stop] - hash of the last block
 */
function GetBlocks(starts, stop) {
  this.command = 'getblocks';
  this.version = PROTOCOL_VERSION;
  this.starts = starts || [];
  this.stop = stop || BufferUtil.NULL_HASH;
}
util.inherits(GetBlocks, Message);

GetBlocks.prototype.fromBuffer = function(payload) {
  var parser = new BufferReader(payload);
  this.version = parser.readUInt32LE();

  var startCount = Math.min(parser.readVarintNum(), 500);
  this.starts = [];
  for (var i = 0; i < startCount; i++) {
    this.starts.push(parser.read(32));
  }
  this.stop = parser.read(32);

  return this;
};

GetBlocks.prototype.getPayload = function() {
  var put = new Put();
  put.word32le(this.version);
  put.varint(this.starts.length);

  for (var i = 0; i < this.starts.length; i++) {
    if (this.starts[i].length !== 32) {
      throw new Error('Invalid hash length');
    }
    put.put(this.starts[i]);
  }

  if (this.stop.length !== 32) {
    throw new Error('Invalid hash length');
  }
  put.put(this.stop);

  return put.buffer();
};

module.exports.GetBlocks = Message.COMMANDS.getblocks = GetBlocks;

/**
 * Getheaders Message
 *
 * @name Transport.Message.GetHeaders
 * @param{Array} starts - array of buffers with the starting block hashes
 * @param{Buffer} [stop] - hash of the last block
 */
function GetHeaders(starts, stop) {
  this.command = 'getheaders';
  this.version = PROTOCOL_VERSION;
  this.starts = starts || [];
  this.stop = stop || BufferUtil.NULL_HASH;
}

util.inherits(GetHeaders, GetBlocks);
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders;


// TODO: Remove this PATCH (yemel)
Buffers.prototype.skip = function (i) {
  if (i === 0) return;

  if (i === this.length) {
    this.buffers = [];
    this.length = 0;
    return;
  }

  var pos = this.pos(i);
  this.buffers = this.buffers.slice(pos.buf);
  this.buffers[0] = new Buffer(this.buffers[0].slice(pos.offset));
  this.length -= i;
};