lib: protocol initial

This commit is contained in:
Fedor Indutny 2014-04-28 17:43:13 +04:00
parent 8a3c9d2dcd
commit b6a572f0c0
6 changed files with 169 additions and 56 deletions

View File

@ -3,4 +3,5 @@ var elliptic = require('elliptic');
bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1);
bcoin.utils = require('./bcoin/utils');
bcoin.protocol = require('./bcoin/protocol');
bcoin.wallet = require('./bcoin/wallet');

View File

@ -4,12 +4,12 @@ var bcoin = require('../../bcoin');
var constants = require('./constants');
var utils = bcoin.utils;
function Framer(peer) {
if (!(this instanceof Framer))
return new Framer(peer);
var writeU32 = utils.writeU32;
var writeAscii = utils.writeAscii;
this.peer = peer;
this.network = peer.network;
function Framer() {
if (!(this instanceof Framer))
return new Framer();
}
module.exports = Framer;
@ -17,64 +17,69 @@ Framer.prototype.header = function header(cmd, payload) {
assert(cmd.length < 12);
assert(payload.length <= 0xffffffff);
var h = new Buffer(24);
var h = new Array(24);
// Magic value
h.writeUInt32LE(constants.magic, 0, true);
writeU32(h, constants.magic, 0);
// Command
var len = h.write(cmd, 4);
var len = writeAscii(h, cmd, 4);
for (var i = 4 + len; i < 4 + 12; i++)
h[i] = 0;
// Payload length
h.writeUInt32LE(payload.length, 16, true);
writeU32(h, payload.length, 16);
// Checksum
h.writeUInt32LE(utils.checksum(payload), 20, true);
utils.copy(utils.checksum(payload), h, 20);
return h;
};
Framer.prototype.packet = function packet(cmd, payload) {
var h = this.header('version', payload);
return Buffer.concat([ h, payload ], h.length + payload.length);
return h.concat(payload);
};
Framer.prototype._addr = function addr(buf, off, addr) {
Framer.prototype._addr = function addr(buf, off) {
writeU32(buf, 1, off);
writeU32(buf, 0, off + 4);
writeU32(buf, 0, off + 8);
writeU32(buf, 0, off + 12);
writeU32(buf, 0xffff0000, off + 16);
writeU32(buf, 0, off + 20);
buf[off + 24] = 0;
buf[off + 25] = 0;
};
Framer.prototype.version = function version() {
var local = this.network.externalAddr;
var remote = this.peer.addr;
var p = new Buffer(86);
var p = new Array(86);
// Version
p.writeUInt32LE(constants.version, 0, true);
writeU32(p, constants.version, 0);
// Services
p.writeUInt32LE(constants.services.network, 4, true);
p.writeUInt32LE(0, 8, true);
writeU32(p, constants.services.network, 4);
writeU32(p, 0, 8);
// Timestamp
var ts = ((+new Date) / 1000) | 0;
p.writeUInt32LE(ts, 12, true);
p.writeUInt32LE(0, 16, true);
writeU32(p, ts, 12);
writeU32(p, 0, 16);
// Remote and local addresses
this._addr(p, 20, remote);
this._addr(p, 46, local);
this._addr(p, 20);
this._addr(p, 46);
// Nonce, very dramatic
p.writeUInt32LE(0xdeadbeef, 72, true);
p.writeUInt32LE(0xabbadead, 76, true);
writeU32(p, 0xdeadbeef, 72);
writeU32(p, 0xabbadead, 76);
// No user-agent
p[80] = 0;
// Start height
p.writeUInt32LE(0x0, 81, true);
writeU32(p, 0x0, 81);
// Relay
p[85] = 0;

View File

@ -1,39 +1,38 @@
var assert = require('assert');
var util = require('util');
var Transform = require('stream').Transform;
var EventEmitter = require('events').EventEmitter;
var bspv = require('../../bspv');
var utils = bspv.utils;
var bcoin = require('../../bcoin');
var utils = bcoin.utils;
var constants = require('./constants');
function Parser(peer) {
var readU32 = utils.readU32;
var readU64 = utils.readU64;
function Parser() {
if (!(this instanceof Parser))
return new Parser(peer);
return new Parser();
Transform.call(this);
this._readableState.objectMode = true;
this.peer = peer;
EventEmitter.call(this);
this.pending = [];
this.pendingTotal = 0;
this.waiting = 24;
this.packet = null;
}
util.inherits(Parser, Transform);
util.inherits(Parser, EventEmitter);
module.exports = Parser;
Parser.prototype._transform = function(data, enc, cb) {
Parser.prototype.execute = function(data) {
if (data) {
this.pendingTotal += data.length;
this.pending.push(data);
}
while (this.pendingTotal >= this.waiting) {
// Concat chunks
var chunk = new Buffer(this.waiting);
console.log(this.pending);
var chunk = new Array(this.waiting);
for (var i = 0, off = 0, len = 0; off < chunk.length; i++) {
len = this.pending[i].copy(chunk, off);
len = utils.copy(this.pending[i], chunk, off);
off += len;
}
assert.equal(off, chunk.length);
@ -46,7 +45,6 @@ Parser.prototype._transform = function(data, enc, cb) {
this.parse(chunk);
}
cb();
};
Parser.prototype.parse = function parse(chunk) {
@ -54,9 +52,11 @@ Parser.prototype.parse = function parse(chunk) {
this.packet = this.parseHeader(chunk);
} else {
this.packet.payload = chunk;
if (utils.checksum(this.packet.payload) !== this.packet.checksum)
if (readU32(utils.checksum(this.packet.payload)) !== this.packet.checksum)
return this.emit('error', new Error('Invalid checksum'));
this.push(this.packet);
this.packet.payload = this.parsePayload(this.packet.cmd,
this.packet.payload);
this.emit('packet', this.packet);
this.waiting = 24;
this.packet = null;
@ -64,7 +64,7 @@ Parser.prototype.parse = function parse(chunk) {
};
Parser.prototype.parseHeader = function parseHeader(h) {
var magic = h.readUInt32LE(0);
var magic = readU32(h, 0);
if (magic !== constants.magic) {
return this.emit('error',
new Error('Invalid magic value: ' + magic.toString(16)));
@ -75,12 +75,51 @@ Parser.prototype.parseHeader = function parseHeader(h) {
if (i === 12)
return this.emit('error', new Error('Not NULL-terminated cmd'));
var cmd = h.slice(4, 4 + i).toString();
this.waiting = h.readUInt32LE(16);
var cmd = utils.stringify(h.slice(4, 4 + i));
this.waiting = readU32(h, 16);
return {
cmd: cmd,
length: this.waiting,
checksum: h.readUInt32LE(20)
checksum: readU32(h, 20)
};
};
Parser.prototype.parsePayload = function parsePayload(cmd, p) {
if (cmd === 'version')
return this.parseVersion(p);
else
return this.emit('error', new Error('Unknown packet: ' + cmd));
};
Parser.prototype.parseVersion = function parseVersion(p) {
if (p.length < 85)
return this.emit('error', new Error('version packet is too small'));
var v = readU32(p, 0);
if (v < constants.minVersion)
return this.emit('error', new Error('version number is too small'));
var services = readU64(p, 4);
// Timestamp
var ts = readU64(p, 12);
// Nonce, very dramatic
var nonce = { lo: readU32(p, 72), hi: readU32(p, 76) };
// Start height
var weight = readU32(p, 81);
// Relay
var relay = p.length >= 86 ? p[85] === 1 : true;
return {
v: v,
services: services,
ts: ts,
nonce: nonce,
weight: weight,
relay: relay
};
};

View File

@ -77,14 +77,6 @@ utils.fromBase58 = function fromBase58(str) {
return z.concat(res.toArray());
};
utils.sha256 = function sha256(data, enc) {
return hash.sha256().update(data, enc).digest();
};
utils.readU32 = function readU32(arr) {
return arr[0] | (arr[1] << 8) | (arr[2] << 16) | (arr[3] << 24);
};
utils.ripesha = function ripesha(data, enc) {
return hash.ripemd160().update(utils.sha256(data, enc)).digest();
};
@ -92,3 +84,56 @@ utils.ripesha = function ripesha(data, enc) {
utils.checksum = function checksum(data, enc) {
return utils.sha256(utils.sha256(data, enc)).slice(0, 4);
};
utils.sha256 = function sha256(data, enc) {
return hash.sha256().update(data, enc).digest();
};
utils.readU32 = function readU32(arr, off) {
if (!off)
off = 0;
var r = arr[off] |
(arr[off + 1] << 8) |
(arr[off + 2] << 16) |
(arr[off + 3] << 24);
if (r < 0)
r += 0x100000000;
return r;
};
utils.readU64 = function readU64(arr, off) {
if (!off)
off = 0;
return utils.readU32(arr, off) + utils.readU32(arr, off + 4) * 0x100000000;
};
utils.writeU32 = function writeU32(dst, num, off) {
if (!off)
off = 0;
dst[off] = num & 0xff;
dst[off + 1] = (num >>> 8) & 0xff;
dst[off + 2] = (num >>> 16) & 0xff;
dst[off + 3] = (num >>> 24) & 0xff;
};
utils.writeAscii = function writeAscii(dst, str, off) {
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
dst[off + i] = c & 0xff;
}
return i;
};
utils.copy = function copy(src, dst, off) {
var len = Math.min(dst.length - off, src.length);
for (var i = 0; i < len; i++)
dst[i + off] = src[i];
return i;
};
utils.stringify = function stringify(arr) {
var res = '';
for (var i = 0; i < arr.length; i++)
res += String.fromCharCode(arr[i]);
return res;
};

View File

@ -29,7 +29,7 @@ Wallet.prototype.validateAddress = function validateAddress(addr) {
if (addr[0] !== 0)
return false;
var chk = utils.checksum(addr.slice(0, -4));
if (utils.readU32(chk) !== utils.readU32(addr.slice(21)))
if (utils.readU32(chk, 0) !== utils.readU32(addr, 21))
return false;
return true;

23
test/protocol-test.js Normal file
View File

@ -0,0 +1,23 @@
var assert = require('assert');
var bcoin = require('../');
describe('Protocol', function() {
var parser;
var framer;
beforeEach(function() {
parser = bcoin.protocol.parser();
framer = bcoin.protocol.framer();
});
it('should encode/decode version packet', function(cb) {
var ver = framer.version();
parser.once('packet', function(packet) {
assert.equal(packet.cmd, 'version');
assert.equal(packet.payload.v, 70002);
assert.equal(packet.payload.relay, false);
cb();
});
parser.execute(ver);
});
});