bitcore-p2p
Version:
Interface to the bitcoin P2P network for bitcore
112 lines (92 loc) • 3.14 kB
JavaScript
'use strict';
var bitcore = require('bitcore-lib');
var BufferUtil = bitcore.util.buffer;
var Hash = bitcore.crypto.Hash;
var $ = bitcore.util.preconditions;
/**
* A factory to build Bitcoin protocol messages.
* @param {Object=} options
* @param {Network=} options.network
* @param {Function=} options.Block - A block constructor
* @param {Function=} options.BlockHeader - A block header constructor
* @param {Function=} options.MerkleBlock - A merkle block constructor
* @param {Function=} options.Transaction - A transaction constructor
* @constructor
*/
function Messages(options) {
this.builder = Messages.builder(options);
// map message constructors by name
for(var key in this.builder.commandsMap) {
var name = this.builder.commandsMap[key];
this[name] = this.builder.commands[key];
}
if (!options) {
options = {};
}
this.network = options.network || bitcore.Networks.defaultNetwork;
}
Messages.MINIMUM_LENGTH = 20;
Messages.PAYLOAD_START = 16;
Messages.Message = require('./message');
Messages.builder = require('./builder');
/**
* @param {Buffers} dataBuffer
*/
Messages.prototype.parseBuffer = function(dataBuffer) {
/* jshint maxstatements: 18 */
if (dataBuffer.length < Messages.MINIMUM_LENGTH) {
return;
}
// Search the next magic number
if (!this._discardUntilNextMessage(dataBuffer)) {
return;
}
var payloadLen = (dataBuffer.get(Messages.PAYLOAD_START)) +
(dataBuffer.get(Messages.PAYLOAD_START + 1) << 8) +
(dataBuffer.get(Messages.PAYLOAD_START + 2) << 16) +
(dataBuffer.get(Messages.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 this._buildFromBuffer(command, payload);
};
Messages.prototype._discardUntilNextMessage = function(dataBuffer) {
$.checkArgument(dataBuffer);
$.checkState(this.network, 'network must be set');
var i = 0;
for (;;) {
// check if it's the beginning of a new message
var packageNumber = dataBuffer.slice(0, 4).toString('hex');
if (packageNumber === this.network.networkMagic.toString('hex')) {
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
}
};
Messages.prototype._buildFromBuffer = function(command, payload) {
if (!this.builder.commands[command]) {
throw new Error('Unsupported message command: ' + command);
}
return this.builder.commands[command].fromBuffer(payload);
};
Messages.prototype.add = function(key, name, Command) {
this.builder.add(key, Command);
this[name] = this.builder.commands[key];
};
module.exports = Messages;