diff --git a/lib/bcoin.js b/lib/bcoin.js index a2ff4f6e..f12b9b09 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -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'); diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 66e53a7e..f453126d 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -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; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index f0423893..d30e0ea4 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -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 }; }; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 7e064ed1..b6eb8b44 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -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; +}; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 03b1d7cd..20f8d7b5 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -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; diff --git a/test/protocol-test.js b/test/protocol-test.js new file mode 100644 index 00000000..c1d5f920 --- /dev/null +++ b/test/protocol-test.js @@ -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); + }); +});