lib: protocol initial
This commit is contained in:
parent
8a3c9d2dcd
commit
b6a572f0c0
@ -3,4 +3,5 @@ var elliptic = require('elliptic');
|
|||||||
|
|
||||||
bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1);
|
bcoin.ecdsa = elliptic.ecdsa(elliptic.nist.secp256k1);
|
||||||
bcoin.utils = require('./bcoin/utils');
|
bcoin.utils = require('./bcoin/utils');
|
||||||
|
bcoin.protocol = require('./bcoin/protocol');
|
||||||
bcoin.wallet = require('./bcoin/wallet');
|
bcoin.wallet = require('./bcoin/wallet');
|
||||||
|
|||||||
@ -4,12 +4,12 @@ var bcoin = require('../../bcoin');
|
|||||||
var constants = require('./constants');
|
var constants = require('./constants');
|
||||||
var utils = bcoin.utils;
|
var utils = bcoin.utils;
|
||||||
|
|
||||||
function Framer(peer) {
|
var writeU32 = utils.writeU32;
|
||||||
if (!(this instanceof Framer))
|
var writeAscii = utils.writeAscii;
|
||||||
return new Framer(peer);
|
|
||||||
|
|
||||||
this.peer = peer;
|
function Framer() {
|
||||||
this.network = peer.network;
|
if (!(this instanceof Framer))
|
||||||
|
return new Framer();
|
||||||
}
|
}
|
||||||
module.exports = Framer;
|
module.exports = Framer;
|
||||||
|
|
||||||
@ -17,64 +17,69 @@ Framer.prototype.header = function header(cmd, payload) {
|
|||||||
assert(cmd.length < 12);
|
assert(cmd.length < 12);
|
||||||
assert(payload.length <= 0xffffffff);
|
assert(payload.length <= 0xffffffff);
|
||||||
|
|
||||||
var h = new Buffer(24);
|
var h = new Array(24);
|
||||||
|
|
||||||
// Magic value
|
// Magic value
|
||||||
h.writeUInt32LE(constants.magic, 0, true);
|
writeU32(h, constants.magic, 0);
|
||||||
|
|
||||||
// Command
|
// Command
|
||||||
var len = h.write(cmd, 4);
|
var len = writeAscii(h, cmd, 4);
|
||||||
for (var i = 4 + len; i < 4 + 12; i++)
|
for (var i = 4 + len; i < 4 + 12; i++)
|
||||||
h[i] = 0;
|
h[i] = 0;
|
||||||
|
|
||||||
// Payload length
|
// Payload length
|
||||||
h.writeUInt32LE(payload.length, 16, true);
|
writeU32(h, payload.length, 16);
|
||||||
|
|
||||||
// Checksum
|
// Checksum
|
||||||
h.writeUInt32LE(utils.checksum(payload), 20, true);
|
utils.copy(utils.checksum(payload), h, 20);
|
||||||
|
|
||||||
return h;
|
return h;
|
||||||
};
|
};
|
||||||
|
|
||||||
Framer.prototype.packet = function packet(cmd, payload) {
|
Framer.prototype.packet = function packet(cmd, payload) {
|
||||||
var h = this.header('version', 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() {
|
Framer.prototype.version = function version() {
|
||||||
var local = this.network.externalAddr;
|
var p = new Array(86);
|
||||||
var remote = this.peer.addr;
|
|
||||||
|
|
||||||
var p = new Buffer(86);
|
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
p.writeUInt32LE(constants.version, 0, true);
|
writeU32(p, constants.version, 0);
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
p.writeUInt32LE(constants.services.network, 4, true);
|
writeU32(p, constants.services.network, 4);
|
||||||
p.writeUInt32LE(0, 8, true);
|
writeU32(p, 0, 8);
|
||||||
|
|
||||||
// Timestamp
|
// Timestamp
|
||||||
var ts = ((+new Date) / 1000) | 0;
|
var ts = ((+new Date) / 1000) | 0;
|
||||||
p.writeUInt32LE(ts, 12, true);
|
writeU32(p, ts, 12);
|
||||||
p.writeUInt32LE(0, 16, true);
|
writeU32(p, 0, 16);
|
||||||
|
|
||||||
// Remote and local addresses
|
// Remote and local addresses
|
||||||
this._addr(p, 20, remote);
|
this._addr(p, 20);
|
||||||
this._addr(p, 46, local);
|
this._addr(p, 46);
|
||||||
|
|
||||||
// Nonce, very dramatic
|
// Nonce, very dramatic
|
||||||
p.writeUInt32LE(0xdeadbeef, 72, true);
|
writeU32(p, 0xdeadbeef, 72);
|
||||||
p.writeUInt32LE(0xabbadead, 76, true);
|
writeU32(p, 0xabbadead, 76);
|
||||||
|
|
||||||
// No user-agent
|
// No user-agent
|
||||||
p[80] = 0;
|
p[80] = 0;
|
||||||
|
|
||||||
// Start height
|
// Start height
|
||||||
p.writeUInt32LE(0x0, 81, true);
|
writeU32(p, 0x0, 81);
|
||||||
|
|
||||||
// Relay
|
// Relay
|
||||||
p[85] = 0;
|
p[85] = 0;
|
||||||
|
|||||||
@ -1,39 +1,38 @@
|
|||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var Transform = require('stream').Transform;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
var bspv = require('../../bspv');
|
var bcoin = require('../../bcoin');
|
||||||
var utils = bspv.utils;
|
var utils = bcoin.utils;
|
||||||
var constants = require('./constants');
|
var constants = require('./constants');
|
||||||
|
|
||||||
function Parser(peer) {
|
var readU32 = utils.readU32;
|
||||||
|
var readU64 = utils.readU64;
|
||||||
|
|
||||||
|
function Parser() {
|
||||||
if (!(this instanceof Parser))
|
if (!(this instanceof Parser))
|
||||||
return new Parser(peer);
|
return new Parser();
|
||||||
|
|
||||||
Transform.call(this);
|
EventEmitter.call(this);
|
||||||
this._readableState.objectMode = true;
|
|
||||||
|
|
||||||
this.peer = peer;
|
|
||||||
|
|
||||||
this.pending = [];
|
this.pending = [];
|
||||||
this.pendingTotal = 0;
|
this.pendingTotal = 0;
|
||||||
this.waiting = 24;
|
this.waiting = 24;
|
||||||
this.packet = null;
|
this.packet = null;
|
||||||
}
|
}
|
||||||
util.inherits(Parser, Transform);
|
util.inherits(Parser, EventEmitter);
|
||||||
module.exports = Parser;
|
module.exports = Parser;
|
||||||
|
|
||||||
Parser.prototype._transform = function(data, enc, cb) {
|
Parser.prototype.execute = function(data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
this.pendingTotal += data.length;
|
this.pendingTotal += data.length;
|
||||||
this.pending.push(data);
|
this.pending.push(data);
|
||||||
}
|
}
|
||||||
while (this.pendingTotal >= this.waiting) {
|
while (this.pendingTotal >= this.waiting) {
|
||||||
// Concat chunks
|
// Concat chunks
|
||||||
var chunk = new Buffer(this.waiting);
|
var chunk = new Array(this.waiting);
|
||||||
console.log(this.pending);
|
|
||||||
for (var i = 0, off = 0, len = 0; off < chunk.length; i++) {
|
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;
|
off += len;
|
||||||
}
|
}
|
||||||
assert.equal(off, chunk.length);
|
assert.equal(off, chunk.length);
|
||||||
@ -46,7 +45,6 @@ Parser.prototype._transform = function(data, enc, cb) {
|
|||||||
|
|
||||||
this.parse(chunk);
|
this.parse(chunk);
|
||||||
}
|
}
|
||||||
cb();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype.parse = function parse(chunk) {
|
Parser.prototype.parse = function parse(chunk) {
|
||||||
@ -54,9 +52,11 @@ Parser.prototype.parse = function parse(chunk) {
|
|||||||
this.packet = this.parseHeader(chunk);
|
this.packet = this.parseHeader(chunk);
|
||||||
} else {
|
} else {
|
||||||
this.packet.payload = chunk;
|
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'));
|
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.waiting = 24;
|
||||||
this.packet = null;
|
this.packet = null;
|
||||||
@ -64,7 +64,7 @@ Parser.prototype.parse = function parse(chunk) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype.parseHeader = function parseHeader(h) {
|
Parser.prototype.parseHeader = function parseHeader(h) {
|
||||||
var magic = h.readUInt32LE(0);
|
var magic = readU32(h, 0);
|
||||||
if (magic !== constants.magic) {
|
if (magic !== constants.magic) {
|
||||||
return this.emit('error',
|
return this.emit('error',
|
||||||
new Error('Invalid magic value: ' + magic.toString(16)));
|
new Error('Invalid magic value: ' + magic.toString(16)));
|
||||||
@ -75,12 +75,51 @@ Parser.prototype.parseHeader = function parseHeader(h) {
|
|||||||
if (i === 12)
|
if (i === 12)
|
||||||
return this.emit('error', new Error('Not NULL-terminated cmd'));
|
return this.emit('error', new Error('Not NULL-terminated cmd'));
|
||||||
|
|
||||||
var cmd = h.slice(4, 4 + i).toString();
|
var cmd = utils.stringify(h.slice(4, 4 + i));
|
||||||
this.waiting = h.readUInt32LE(16);
|
this.waiting = readU32(h, 16);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
length: this.waiting,
|
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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -77,14 +77,6 @@ utils.fromBase58 = function fromBase58(str) {
|
|||||||
return z.concat(res.toArray());
|
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) {
|
utils.ripesha = function ripesha(data, enc) {
|
||||||
return hash.ripemd160().update(utils.sha256(data, enc)).digest();
|
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) {
|
utils.checksum = function checksum(data, enc) {
|
||||||
return utils.sha256(utils.sha256(data, enc)).slice(0, 4);
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@ -29,7 +29,7 @@ Wallet.prototype.validateAddress = function validateAddress(addr) {
|
|||||||
if (addr[0] !== 0)
|
if (addr[0] !== 0)
|
||||||
return false;
|
return false;
|
||||||
var chk = utils.checksum(addr.slice(0, -4));
|
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 false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
23
test/protocol-test.js
Normal file
23
test/protocol-test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user