optimize parsers.

This commit is contained in:
Christopher Jeffrey 2016-07-27 04:56:07 -07:00
parent 0fa972dcb3
commit 20d69b7cbc
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 142 additions and 122 deletions

View File

@ -80,7 +80,8 @@ function BIP151Stream(cipher, key) {
this.pending = []; this.pending = [];
this.total = 0; this.total = 0;
this.waiting = 0; this.waiting = 4;
this.hasSize = false;
} }
utils.inherits(BIP151Stream, EventEmitter); utils.inherits(BIP151Stream, EventEmitter);
@ -178,7 +179,7 @@ BIP151Stream.prototype.sequence = function sequence() {
*/ */
BIP151Stream.prototype.update = function update() { BIP151Stream.prototype.update = function update() {
this.iv.writeUInt32LE(this.seq, 4, true); this.iv.writeUInt32LE(this.seq, 0, true);
return this.iv; 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. * Potentially emits a `packet` event.
* @param {Buffer} data * @param {Buffer} data
*/ */
BIP151Stream.prototype.feed = function feed(data) { BIP151Stream.prototype.feed = function feed(data) {
var chunk, size, payload, tag, p, cmd, body; var chunk, off, len;
while (data.length) { this.total += data.length;
if (!this.waiting) { this.pending.push(data);
this.total += data.length;
this.pending.push(data);
if (this.total < 4) while (this.total >= this.waiting) {
break; chunk = new Buffer(this.waiting);
off = 0;
len = 0;
chunk = concat(this.pending); while (off < chunk.length) {
len = this.pending[0].copy(chunk, off);
this.total = 0; if (len === this.pending[0].length)
this.pending.length = 0; this.pending.shift();
else
size = this.decryptSize(chunk); this.pending[0] = this.pending[0].slice(len);
data = chunk.slice(4); off += len;
// 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;
} }
this.total += data.length; assert.equal(off, chunk.length);
this.pending.push(data);
if (this.total < this.waiting) this.total -= chunk.length;
break; this.parse(chunk);
}
};
chunk = concat(this.pending); /**
payload = chunk.slice(0, this.waiting - 16); * Parse a ciphertext payload chunk.
tag = chunk.slice(this.waiting - 16, this.waiting); * Potentially emits a `packet` event.
data = chunk.slice(this.waiting); * @param {Buffer} data
*/
this.total = 0; BIP151Stream.prototype.parse = function parse(data) {
this.pending.length = 0; var size, payload, tag, p, cmd, body;
this.waiting = 0;
// Authenticate payload before decrypting. if (!this.hasSize) {
// This ensures the cipher state isn't altered size = this.decryptSize(data);
// if the payload integrity has been compromised.
this.auth(payload);
this.finish();
if (!this.verify(tag)) { // Allow 3 batched packets of max message size (12mb).
this.sequence(); // Not technically standard, but this protects us
this.emit('error', new Error('Bad tag.')); // from buffering tons of data due to either an
continue; // 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.sequence();
this.emit('error', new Error('Bad tag.'));
return;
}
p = bcoin.reader(payload, true); this.decrypt(payload);
this.sequence();
while (p.left()) { p = bcoin.reader(payload, true);
try {
cmd = p.readVarString('ascii');
body = p.readBytes(p.readU32());
} catch (e) {
this.emit('error', e);
break;
}
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); return this.output.packet(cmd, body);
}; };
/*
* Helpers
*/
function concat(buffers) {
return buffers.length > 1
? Buffer.concat(buffers)
: buffers[0];
}
/* /*
* Expose * Expose
*/ */

View File

@ -93,9 +93,7 @@ Parser.prototype.feed = function feed(data) {
this.pending.push(data); this.pending.push(data);
while (this.total >= this.waiting) { while (this.total >= this.waiting) {
// Concat chunks
chunk = new Buffer(this.waiting); chunk = new Buffer(this.waiting);
off = 0; off = 0;
len = 0; len = 0;
@ -110,7 +108,6 @@ Parser.prototype.feed = function feed(data) {
assert.equal(off, chunk.length); assert.equal(off, chunk.length);
// Slice buffers
this.total -= chunk.length; this.total -= chunk.length;
this.parse(chunk); this.parse(chunk);
} }

View File

@ -951,57 +951,83 @@ function Parser() {
utils.inherits(Parser, EventEmitter); utils.inherits(Parser, EventEmitter);
Parser.prototype.feed = function feed(data) { Parser.prototype.feed = function feed(data) {
var chunk, header, body; var chunk, off, len;
while (data) { this.total += data.length;
this.total += data.length; this.pending.push(data);
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;
}
while (this.total >= this.waiting) {
if (this.waiting === 0) {
this.parse(new Buffer(0));
continue; continue;
} }
header = this.header; if (this.pending[0].length > this.waiting) {
chunk = this.pending[0].slice(0, this.waiting);
this.pending.length = 0; this.pending[0] = this.pending[0].slice(this.waiting);
this.total = 0; this.total -= chunk.length;
this.waiting = 12; this.parse(chunk);
this.header = null;
try {
body = this.parseBody(chunk);
} catch (e) {
this.emit('error', e);
continue; 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) { Parser.prototype.parseHeader = function parseHeader(data) {
return { return {
magic: data.readUInt32LE(0, true), magic: data.readUInt32LE(0, true),
@ -1107,12 +1133,6 @@ function fromError(err) {
return [err.message, err.stack + '', err.type]; return [err.message, err.stack + '', err.type];
} }
function concat(buffers) {
return buffers.length > 1
? Buffer.concat(buffers)
: buffers[0];
}
/* /*
* Expose * Expose
*/ */