/** * parser.js - packet parser for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * https://github.com/indutny/bcoin */ var inherits = require('inherits'); var EventEmitter = require('events').EventEmitter; var bn = require('bn.js'); var bcoin = require('../../bcoin'); var utils = bcoin.utils; var assert = utils.assert; var constants = require('./constants'); var network = require('./network'); /** * Parser */ function Parser() { if (!(this instanceof Parser)) return new Parser(); EventEmitter.call(this); this.pending = []; this.pendingTotal = 0; this.waiting = 24; this.packet = null; } inherits(Parser, EventEmitter); Parser.prototype._error = function _error(str) { this.emit('error', new Error(str)); }; Parser.prototype.feed = function feed(data) { var chunk, i, off, len; this.pendingTotal += data.length; this.pending.push(data); while (this.pendingTotal >= this.waiting) { // Concat chunks chunk = new Buffer(this.waiting); i = 0; off = 0; len = 0; for (; off < chunk.length; i++) { len = this.pending[0].copy(chunk, off, 0, this.pending[0].length); 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) || {}; return; } this.packet.payload = chunk; if (utils.readU32(utils.checksum(this.packet.payload)) !== this.packet.checksum) return this._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 i, magic, cmd; magic = utils.readU32(h, 0); if (magic !== network.magic) { return this._error('Invalid magic value: ' + magic.toString(16)); } // Count length of the cmd for (i = 0; h[i + 4] !== 0 && i < 12; i++); if (i === 12) return this._error('Not NULL-terminated cmd'); cmd = utils.stringify(h.slice(4, 4 + i)); this.waiting = utils.readU32(h, 16); return { cmd: cmd, length: this.waiting, checksum: utils.readU32(h, 20) }; }; Parser.prototype.parsePayload = function parsePayload(cmd, p) { if (cmd === 'version') return this.parseVersion(p); if (cmd === 'getdata' || cmd === 'inv' || cmd === 'notfound') return this.parseInvList(p); if (cmd === 'merkleblock') return this.parseMerkleBlock(p); if (cmd === 'headers') return this.parseHeaders(p); if (cmd === 'block') return this.parseBlock(p); if (cmd === 'tx') return this.parseTX(p); if (cmd === 'reject') return this.parseReject(p); if (cmd === 'addr') return this.parseAddr(p); if (cmd === 'ping') return this.parsePing(p); if (cmd === 'pong') return this.parsePong(p); return p; }; Parser.prototype.parsePing = function parsePing(p) { if (p.length < 8) return this._error('pong packet is too small'); return { nonce: utils.readU64(p, 0) }; }; Parser.prototype.parsePong = function parsePong(p) { if (p.length < 8) return this._error('ping packet is too small'); return { nonce: utils.readU64(p, 0) }; }; Parser.prototype.parseVersion = function parseVersion(p) { var v, services, ts, recv, from, nonce, result, off, agent, height, relay; if (p.length < 85) return this._error('version packet is too small'); v = utils.readU32(p, 0); services = utils.readU64(p, 4); // Timestamp ts = utils.read64(p, 12); // Our address (recv) recv = this._parseAddr(p, 20); // Their Address (from) from = this._parseAddr(p, 46); // Nonce, very dramatic nonce = utils.readU64(p, 72); // User agent length result = utils.readIntv(p, 80); off = result.off; agent = p.slice(off, off + result.r); off += result.r; // Start height height = utils.readU32(p, off); off += 4; // Relay relay = p.length > off ? p[off] === 1 : true; try { ts = ts.toNumber(); } catch (e) { ts = 0; } try { services = services.toNumber(); } catch (e) { services = 1; } return { v: v, services: services, ts: ts, local: recv, remote: from, nonce: nonce, agent: utils.stringify(agent), height: height, relay: relay }; }; Parser.prototype.parseInvList = function parseInvList(p) { var items = []; var i, off, count; count = utils.readIntv(p, 0); p = p.slice(count.off); count = count.r; if (p.length < count * 36) return this._error('Invalid getdata size'); for (i = 0, off = 0; i < count; i++, off += 36) { items.push({ type: constants.invByVal[utils.readU32(p, off)], hash: utils.toArray(p.slice(off + 4, off + 36)) }); } return items; }; Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) { var i, hashCount, off, hashes, flagCount, flags; if (p.length < 86) return this._error('Invalid merkleblock size'); hashCount = utils.readIntv(p, 84); off = hashCount.off; hashCount = hashCount.r; if (off + 32 * hashCount + 1 > p.length) return this._error('Invalid hash count'); hashes = new Array(hashCount); for (i = 0; i < hashCount; i++) hashes[i] = utils.toArray(p.slice(off + i * 32, off + (i + 1) * 32)); off = off + 32 * hashCount; flagCount = utils.readIntv(p, off); off = flagCount.off; flagCount = flagCount.r; if (off + flagCount > p.length) return this._error('Invalid flag count'); flags = utils.toArray(p.slice(off, off + flagCount)); return { version: utils.read32(p, 0), prevBlock: utils.toArray(p.slice(4, 36)), merkleRoot: utils.toArray(p.slice(36, 68)), ts: utils.readU32(p, 68), bits: utils.readU32(p, 72), nonce: utils.readU32(p, 76), totalTX: utils.readU32(p, 80), hashes: hashes, flags: flags, _raw: p.slice(0, 80), _size: p.length }; }; Parser.prototype.parseHeaders = function parseHeaders(p) { var headers = []; var i, result, off, count, header, start, r; if (p.length < 81) return this._error('Invalid headers size'); result = utils.readIntv(p, 0); off = result.off; count = result.r; if (p.length < count * 80) return this._error('Invalid headers size'); for (i = 0; i < count; i++) { header = {}; start = off; header.version = utils.read32(p, off); off += 4; header.prevBlock = utils.toArray(p.slice(off, off + 32)); off += 32; header.merkleRoot = utils.toArray(p.slice(off, off + 32)); off += 32; header.ts = utils.readU32(p, off); off += 4; header.bits = utils.readU32(p, off); off += 4; header.nonce = utils.readU32(p, off); off += 4; r = utils.readIntv(p, off); header.totalTX = r.r; off = r.off; header._raw = p.slice(start, start + 80); headers.push(header); } return headers; }; Parser.prototype.parseBlock = function parseBlock(p) { var txs = []; var i, result, off, totalTX, tx; if (p.length < 81) return this._error('Invalid block size'); result = utils.readIntv(p, 80); off = result.off; totalTX = result.r; for (i = 0; i < totalTX; i++) { tx = this.parseTX(p.slice(off)); if (!tx) return this._error('Invalid tx count for block'); off += tx._off; txs.push(tx); } return { version: utils.read32(p, 0), prevBlock: utils.toArray(p.slice(4, 36)), merkleRoot: utils.toArray(p.slice(36, 68)), ts: utils.readU32(p, 68), bits: utils.readU32(p, 72), nonce: utils.readU32(p, 76), totalTX: totalTX, txs: txs, _raw: p, _size: p.length }; }; Parser.prototype.parseTXIn = function parseTXIn(p) { var scriptLen, off; if (p.length < 41) return this._error('Invalid tx_in size'); scriptLen = utils.readIntv(p, 36); off = scriptLen.off; scriptLen = scriptLen.r; if (off + scriptLen + 4 > p.length) return this._error('Invalid tx_in script length'); return { size: off + scriptLen + 4, prevout: { hash: utils.toArray(p.slice(0, 32)), index: utils.readU32(p, 32) }, script: bcoin.script.decode(utils.toArray(p.slice(off, off + scriptLen))), sequence: utils.readU32(p, off + scriptLen) }; }; Parser.prototype.parseTXOut = function parseTXOut(p) { var scriptLen, off; if (p.length < 9) return this._error('Invalid tx_out size'); scriptLen = utils.readIntv(p, 8); off = scriptLen.off; scriptLen = scriptLen.r; if (off + scriptLen > p.length) return this._error('Invalid tx_out script length'); return { size: off + scriptLen, value: utils.read64(p, 0), script: bcoin.script.decode(utils.toArray(p.slice(off, off + scriptLen))) }; }; Parser.prototype.parseTX = function parseTX(p) { var inCount, off, txIn, tx; var outCount, txOut; var i; if (p.length < 10) return this._error('Invalid tx size'); inCount = utils.readIntv(p, 4); off = inCount.off; inCount = inCount.r; if (inCount < 0) return this._error('Invalid tx_in count (negative)'); if (off + 41 * inCount + 5 > p.length) return this._error('Invalid tx_in count (too big)'); txIn = new Array(inCount); for (i = 0; i < inCount; i++) { tx = this.parseTXIn(p.slice(off)); if (!tx) return; txIn[i] = tx; off += tx.size; if (off + 5 > p.length) return this._error('Invalid tx_in offset'); } outCount = utils.readIntv(p, off); off = outCount.off; outCount = outCount.r; if (outCount < 0) return this._error('Invalid tx_out count (negative)'); if (off + 9 * outCount + 4 > p.length) return this._error('Invalid tx_out count (too big)'); txOut = new Array(outCount); for (i = 0; i < outCount; i++) { tx = this.parseTXOut(p.slice(off)); if (!tx) return; txOut[i] = tx; off += tx.size; if (off + 4 > p.length) return this._error('Invalid tx_out offset'); } return { _raw: p.slice(0, off + 4), version: utils.read32(p, 0), inputs: txIn, outputs: txOut, locktime: utils.readU32(p, off), _off: off + 4, _size: p.length }; }; Parser.prototype.parseReject = function parseReject(p) { var messageLen, off, message, ccode, reasonLen, reason, data; if (p.length < 3) return this._error('Invalid reject size'); messageLen = utils.readIntv(p, 0); off = messageLen.off; messageLen = messageLen.r; if (off + messageLen + 2 > p.length) return this._error('Invalid reject message'); message = utils.stringify(p.slice(off, off + messageLen)); off += messageLen; ccode = utils.readU8(p, off); off++; reasonLen = utils.readIntv(p, off); off = reasonLen.off; reasonLen = reasonLen.r; if (off + reasonLen > p.length) return this._error('Invalid reject reason'); reason = utils.stringify(p.slice(off, off + reasonLen)); off += reasonLen; data = utils.toArray(p.slice(off, off + 32)); return { message: message, ccode: constants.rejectByVal[ccode] || ccode, reason: reason, data: data }; }; Parser.prototype._parseAddr = function _parseAddr(p, off, full) { var ts, services, ip, port; if (!off) off = 0; if (full) { ts = utils.readU32(p, off); off += 4; } else { ts = 0; } services = utils.readU64(p, off); off += 8; ip = utils.toArray(p.slice(off, off + 16)); off += 16; port = utils.readU16BE(p, off); off += 2; try { services = services.toNumber(); } catch (e) { services = 1; } return { ts: ts, services: services, ipv6: utils.array2ip(ip, 6), ipv4: utils.array2ip(ip, 4), port: port }; }; Parser.prototype.parseAddr = function parseAddr(p) { if (p.length < 31) return this._error('Invalid addr size'); var addrs = []; var i, off, count; count = utils.readIntv(p, 0); off = count.off; count = count.r; for (i = 0; i < count && off < p.length; i++) { addrs.push(this._parseAddr(p, off, true)); off += 30; } return addrs; }; Parser.prototype.parseMempool = function parseMempool(p) { if (p.length > 0) return this._error('Invalid mempool size'); return {}; }; /** * Expose */ module.exports = Parser;