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.utils = require('./bcoin/utils');
|
||||
bcoin.protocol = require('./bcoin/protocol');
|
||||
bcoin.wallet = require('./bcoin/wallet');
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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
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