diff --git a/lib/bcoin/bip151.js b/lib/bcoin/bip151.js index a904742d..92e412e3 100644 --- a/lib/bcoin/bip151.js +++ b/lib/bcoin/bip151.js @@ -80,7 +80,8 @@ function BIP151Stream(cipher, key) { this.pending = []; this.total = 0; - this.waiting = 0; + this.waiting = 4; + this.hasSize = false; } utils.inherits(BIP151Stream, EventEmitter); @@ -178,7 +179,7 @@ BIP151Stream.prototype.sequence = function sequence() { */ BIP151Stream.prototype.update = function update() { - this.iv.writeUInt32LE(this.seq, 4, true); + this.iv.writeUInt32LE(this.seq, 0, true); return this.iv; }; @@ -267,89 +268,101 @@ BIP151Stream.prototype.verify = function verify(tag) { }; /** - * Parse a ciphertext payload chunk. + * Parse ciphertext data and split into chunks. * Potentially emits a `packet` event. * @param {Buffer} data */ BIP151Stream.prototype.feed = function feed(data) { - var chunk, size, payload, tag, p, cmd, body; + var chunk, off, len; - while (data.length) { - if (!this.waiting) { - this.total += data.length; - this.pending.push(data); + this.total += data.length; + this.pending.push(data); - if (this.total < 4) - break; + while (this.total >= this.waiting) { + chunk = new Buffer(this.waiting); + off = 0; + len = 0; - chunk = concat(this.pending); - - this.total = 0; - this.pending.length = 0; - - size = this.decryptSize(chunk); - data = chunk.slice(4); - - // Allow 3 batched packets of max message size (12mb). - // Not technically standard, but this protects us - // from buffering tons of data due to either an - // potential dos'er or a cipher state mismatch. - // Note that 6 is the minimum size: - // cmd=varint(1) string(1) length(4) data(0) - if (size < 6 || size > constants.MAX_MESSAGE * 3) { - this.emit('error', new Error('Bad packet size.')); - continue; - } - - this.waiting = size + 16; - - continue; + while (off < chunk.length) { + len = this.pending[0].copy(chunk, off); + if (len === this.pending[0].length) + this.pending.shift(); + else + this.pending[0] = this.pending[0].slice(len); + off += len; } - this.total += data.length; - this.pending.push(data); + assert.equal(off, chunk.length); - if (this.total < this.waiting) - break; + this.total -= chunk.length; + this.parse(chunk); + } +}; - chunk = concat(this.pending); - payload = chunk.slice(0, this.waiting - 16); - tag = chunk.slice(this.waiting - 16, this.waiting); - data = chunk.slice(this.waiting); +/** + * Parse a ciphertext payload chunk. + * Potentially emits a `packet` event. + * @param {Buffer} data + */ - this.total = 0; - this.pending.length = 0; - this.waiting = 0; +BIP151Stream.prototype.parse = function parse(data) { + var size, payload, tag, p, cmd, body; - // Authenticate payload before decrypting. - // This ensures the cipher state isn't altered - // if the payload integrity has been compromised. - this.auth(payload); - this.finish(); + if (!this.hasSize) { + size = this.decryptSize(data); - if (!this.verify(tag)) { - this.sequence(); - this.emit('error', new Error('Bad tag.')); - continue; + // Allow 3 batched packets of max message size (12mb). + // Not technically standard, but this protects us + // from buffering tons of data due to either an + // potential dos'er or a cipher state mismatch. + // Note that 6 is the minimum size: + // cmd=varint(1) string(1) length(4) data(0) + if (size < 6 || size > constants.MAX_MESSAGE * 3) { + this.waiting = 4; + this.emit('error', new Error('Bad packet size.')); + return; } - this.decrypt(payload); + this.hasSize = true; + this.waiting = size + 16; + + return; + } + + payload = data.slice(0, this.waiting - 16); + tag = data.slice(this.waiting - 16, this.waiting); + + this.hasSize = false; + this.waiting = 4; + + // Authenticate payload before decrypting. + // This ensures the cipher state isn't altered + // if the payload integrity has been compromised. + this.auth(payload); + this.finish(); + + if (!this.verify(tag)) { this.sequence(); + this.emit('error', new Error('Bad tag.')); + return; + } - p = bcoin.reader(payload, true); + this.decrypt(payload); + this.sequence(); - while (p.left()) { - try { - cmd = p.readVarString('ascii'); - body = p.readBytes(p.readU32()); - } catch (e) { - this.emit('error', e); - break; - } + p = bcoin.reader(payload, true); - this.emit('packet', cmd, body); + while (p.left()) { + try { + cmd = p.readVarString('ascii'); + body = p.readBytes(p.readU32()); + } catch (e) { + this.emit('error', e); + return; } + + this.emit('packet', cmd, body); } }; @@ -626,16 +639,6 @@ BIP151.prototype.packet = function packet(cmd, body) { return this.output.packet(cmd, body); }; -/* - * Helpers - */ - -function concat(buffers) { - return buffers.length > 1 - ? Buffer.concat(buffers) - : buffers[0]; -} - /* * Expose */ diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 89a2cddf..fe801167 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -93,9 +93,7 @@ Parser.prototype.feed = function feed(data) { this.pending.push(data); while (this.total >= this.waiting) { - // Concat chunks chunk = new Buffer(this.waiting); - off = 0; len = 0; @@ -110,7 +108,6 @@ Parser.prototype.feed = function feed(data) { assert.equal(off, chunk.length); - // Slice buffers this.total -= chunk.length; this.parse(chunk); } diff --git a/lib/bcoin/workers.js b/lib/bcoin/workers.js index f00a27b4..cbf853a0 100644 --- a/lib/bcoin/workers.js +++ b/lib/bcoin/workers.js @@ -951,57 +951,83 @@ function Parser() { utils.inherits(Parser, EventEmitter); Parser.prototype.feed = function feed(data) { - var chunk, header, body; + var chunk, off, len; - while (data) { - this.total += data.length; - this.pending.push(data); - data = null; - - if (this.total < this.waiting) - break; - - chunk = concat(this.pending); - - if (chunk.length > this.waiting) { - data = chunk.slice(this.waiting); - chunk = chunk.slice(0, this.waiting); - } - - if (!this.header) { - this.header = this.parseHeader(chunk); - this.waiting = this.header.size; - this.pending.length = 0; - this.total = 0; - - if (this.header.magic !== 0xdeadbeef) { - this.header = null; - this.waiting = 12; - this.emit('error', new Error('Bad magic number.')); - continue; - } + this.total += data.length; + this.pending.push(data); + while (this.total >= this.waiting) { + if (this.waiting === 0) { + this.parse(new Buffer(0)); continue; } - header = this.header; - - this.pending.length = 0; - this.total = 0; - this.waiting = 12; - this.header = null; - - try { - body = this.parseBody(chunk); - } catch (e) { - this.emit('error', e); + if (this.pending[0].length > this.waiting) { + chunk = this.pending[0].slice(0, this.waiting); + this.pending[0] = this.pending[0].slice(this.waiting); + this.total -= chunk.length; + this.parse(chunk); continue; } - this.emit('packet', header.job, body); + if (this.pending[0].length === this.waiting) { + chunk = this.pending.shift(); + this.total -= chunk.length; + this.parse(chunk); + continue; + } + + chunk = new Buffer(this.waiting); + off = 0; + len = 0; + + while (off < chunk.length) { + len = this.pending[0].copy(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); + + this.total -= chunk.length; + this.parse(chunk); } }; +Parser.prototype.parse = function parse(data) { + var header, body; + + if (!this.header) { + header = this.parseHeader(data); + + if (header.magic !== 0xdeadbeef) { + this.emit('error', new Error('Bad magic number.')); + return; + } + + this.header = header; + this.waiting = header.size; + + return; + } + + header = this.header; + this.waiting = 12; + this.header = null; + + try { + body = this.parseBody(data); + } catch (e) { + this.emit('error', e); + return; + } + + this.emit('packet', header.job, body); +}; + Parser.prototype.parseHeader = function parseHeader(data) { return { magic: data.readUInt32LE(0, true), @@ -1107,12 +1133,6 @@ function fromError(err) { return [err.message, err.stack + '', err.type]; } -function concat(buffers) { - return buffers.length > 1 - ? Buffer.concat(buffers) - : buffers[0]; -} - /* * Expose */