var assert = require('assert'); var util = require('util'); var EventEmitter = require('events').EventEmitter; var bn = require('bn.js'); var bcoin = require('../../bcoin'); var utils = bcoin.utils; var constants = require('./constants'); var readU32 = utils.readU32; var readU64 = utils.readU64; function Parser() { if (!(this instanceof Parser)) return new Parser(); EventEmitter.call(this); this.pending = []; this.pendingTotal = 0; this.waiting = 24; this.packet = null; } util.inherits(Parser, EventEmitter); module.exports = Parser; Parser.prototype.feed = function feed(data) { this.pendingTotal += data.length; this.pending.push(data); while (this.pendingTotal >= this.waiting) { // Concat chunks var chunk = new Array(this.waiting); for (var i = 0, off = 0, len = 0; off < chunk.length; i++) { len = utils.copy(this.pending[0], chunk, off); if (len === this.pending[0].length) this.pending.shift(); else this.pending[0] = this.pending[0].slice(len); off += len; } assert.equal(off, chunk.length); // Slice buffers this.pendingTotal -= chunk.length; this.parse(chunk); } }; Parser.prototype.parse = function parse(chunk) { if (this.packet === null) { this.packet = this.parseHeader(chunk); } else { this.packet.payload = chunk; if (readU32(utils.checksum(this.packet.payload)) !== this.packet.checksum) return this.emit('error', new Error('Invalid checksum')); this.packet.payload = this.parsePayload(this.packet.cmd, this.packet.payload); if (this.packet.payload) this.emit('packet', this.packet); this.waiting = 24; this.packet = null; } }; Parser.prototype.parseHeader = function parseHeader(h) { var magic = readU32(h, 0); if (magic !== constants.magic) { return this.emit('error', new Error('Invalid magic value: ' + magic.toString(16))); } // Count length of the cmd for (var i = 0; h[i + 4] !== 0 && i < 12; i++); if (i === 12) return this.emit('error', new Error('Not NULL-terminated cmd')); var cmd = utils.stringify(h.slice(4, 4 + i)); this.waiting = readU32(h, 16); return { cmd: cmd, length: this.waiting, checksum: readU32(h, 20) }; }; Parser.prototype.parsePayload = function parsePayload(cmd, p) { if (cmd === 'version') return this.parseVersion(p); else if (cmd === 'getdata' || cmd === 'inv' || cmd === 'notfound') return this.parseInvList(p); else if (cmd === 'merkleblock') return this.parseMerkleBlock(p); else if (cmd === 'block') return this.parseBlock(p); else if (cmd === 'tx') return this.parseTx(p); else return p; }; 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); 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 }; }; function readIntv(p, off) { if (!off) off = 0; var r, bytes; if (p[off] < 0xfd) { r = p[off]; bytes = 1; } else if (p[off] === 0xfd) { r = p[off + 1] | (p[off + 2] << 8); bytes = 3; } else if (p[off] === 0xfe) { r = readU32(p, off + 1); bytes = 5; } else { r = 0; bytes = 9; } return { off: off + bytes, r: r }; } Parser.prototype.parseInvList = function parseInvList(p) { var count = readIntv(p, 0); p = p.slice(count.off); count = count.r; if (p.length < count * 36) return this.emit('error', new Error('Invalid getdata size')); var items = []; for (var i = 0, off = 0; i < count; i++, off += 36) { items.push({ type: constants.invByVal[readU32(p, off)], hash: p.slice(off + 4, off + 36) }); } return items; }; Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) { if (p.length < 86) return this.emit('error', new Error('Invalid merkleblock size')); var hashCount = readIntv(p, 84); var off = hashCount.off; hashCount = hashCount.r; if (off + 32 * hashCount + 1 > p.length) return this.emit('error', new Error('Invalid hash count')); var hashes = new Array(hashCount); for (var i = 0; i < hashCount; i++) hashes[i] = p.slice(off + i * 32, off + (i + 1) * 32); off = off + 32 * hashCount; var flagCount = readIntv(p, off); off = flagCount.off; flagCount = flagCount.r; if (off + flagCount > p.length) return this.emit('error', new Error('Invalid flag count')); var flags = p.slice(off, off + flagCount); return { version: readU32(p, 0), prevBlock: p.slice(4, 36), merkleRoot: p.slice(36, 68), ts: readU32(p, 68), bits: readU32(p, 72), nonce: readU32(p, 76), totalTx: readU32(p, 80), hashes: hashes, flags: flags }; }; Parser.prototype.parseBlock = function parseBlock(p) { if (p.length < 84) return this.emit('error', new Error('Invalid block size')); return { version: readU32(p, 0), prevBlock: p.slice(4, 36), merkleRoot: p.slice(36, 68), ts: readU32(p, 68), bits: readU32(p, 72), nonce: readU32(p, 76), totalTx: readU32(p, 80) }; }; Parser.prototype.parseTxIn = function parseTxIn(p) { if (p.length < 41) return this.emit('error', new Error('Invalid tx_in size')); var scriptLen = readIntv(p, 36); var off = scriptLen.off; scriptLen = scriptLen.r; if (off + scriptLen + 4 > p.length) return this.emit('error', new Error('Invalid tx_in script length')); return { size: off + scriptLen + 4, out: { hash: p.slice(0, 32), index: readU32(p, 32) }, script: p.slice(off, off + scriptLen), seq: readU32(p, off + scriptLen) }; }; Parser.prototype.parseTxOut = function parseTxOut(p) { if (p.length < 9) return this.emit('error', new Error('Invalid tx_out size')); var scriptLen = readIntv(p, 8); var off = scriptLen.off; scriptLen = scriptLen.r; if (off + scriptLen > p.length) return this.emit('error', new Error('Invalid tx_out script length')); return { size: off + scriptLen, value: new bn(p.slice(0, 8).reverse()), script: p.slice(off, off + scriptLen) }; }; Parser.prototype.parseTx = function parseTx(p) { if (p.length < 60) return this.emit('error', new Error('Invalid tx size')); var inCount = readIntv(p, 4); var off = inCount.off; inCount = inCount.r; if (inCount <= 0) return this.emit('error', new Error('Invalid tx_in count')); if (off + 41 * inCount + 14 > p.length) return this.emit('error', new Error('Invalid tx_in count')); var txIn = new Array(inCount); for (var i = 0; i < inCount; i++) { var tx = this.parseTxIn(p.slice(off)); if (!tx) return; txIn[i] = tx; off += tx.size; if (off + 14 > p.length) return this.emit('error', new Error('Invalid tx_in offset')); } var outCount = readIntv(p, off); var off = outCount.off; outCount = outCount.r; if (outCount <= 0) return this.emit('error', new Error('Invalid tx_out count')); if (off + 9 * outCount + 4 > p.length) return this.emit('error', new Error('Invalid tx_out count')); var txOut = new Array(outCount); for (var i = 0; i < outCount; i++) { var tx = this.parseTxOut(p.slice(off)); if (!tx) return; txOut[i] = tx; off += tx.size; if (off + 4 > p.length) return this.emit('error', new Error('Invalid tx_out offset')); } return { _raw: p, version: readU32(p, 0), inputs: txIn, outputs: txOut, lock: readU32(p, off) }; };