Skip to content

Commit

Permalink
Dependency-free binary packet parser.
Browse files Browse the repository at this point in the history
No longer use the binary module to parse Blizzard.
Expect better performance.

The binary module is still used for bufferPut and
bufferComplete.
  • Loading branch information
reid committed Jun 14, 2012
1 parent ee398cd commit 4f71d02
Showing 1 changed file with 102 additions and 52 deletions.
154 changes: 102 additions & 52 deletions lib/blizzard/session.js
Expand Up @@ -404,63 +404,113 @@ BlizzardSession.prototype.nextSequence = function () {
BlizzardSession.prototype.setupBinaryParser = function (socket) {
var self = this;

// Parse the header.
function parseHeader(vars) {
if (vars.length === 0) {
if (vars.type === BlizzardSession.TYPE_BUFFER_RESPONSE) {
self.emit("bufferComplete", vars.id);
} else if (vars.type === BlizzardSession.TYPE_HANDSHAKE) {
self.emit("ready");
} else {
self.emit("fail", vars.id, BlizzardSession.ERROR_INVALID, "Unexpected 0-length header.");
function parser(onPacket) {
var emit,
buffer = null,
offset = 0,
payloadLength = 0,
id,
type,
state = 0;
return function emitter(chunk) {
var i = 0,
byte,
chunkPayloadLength,
chunkLength = chunk.length;
for (; i < chunkLength; i += 1) {
byte = chunk[i];
switch (state) {
// magic
case 0:
if (byte === BlizzardSession.MAGIC) {
state = 1;
}
break;
// type
case 1:
type = byte;
state = 2;
break;
// id - 4 bytes
case 2:
id |= byte << 24;
state = 3;
break;
case 3:
id |= byte << 16;
state = 4;
break;
case 4:
id |= byte << 8;
state = 5;
break;
case 5:
id |= byte;
state = 6;
break;
// length - 4 bytes
case 6:
payloadLength |= byte << 24;
state = 7;
break;
case 7:
payloadLength |= byte << 16;
state = 8;
break;
case 8:
payloadLength |= byte << 8;
state = 9;
break;
case 9:
payloadLength |= byte;
state = 10;
if (payloadLength > 0) {
buffer = new Buffer(payloadLength);
} else {
emit = true;
}
break;
// data
case 10:
emit = false;
chunkPayloadLength = chunkLength - i;
if (chunkPayloadLength + offset >= payloadLength) {
emit = true;
chunkPayloadLength = payloadLength - offset;
}
chunk.copy(buffer, offset, i, i + chunkPayloadLength);
offset += chunkPayloadLength;
i += chunkPayloadLength - 1;
break;
}
if (emit) {
onPacket(type, id, buffer);
id = 0;
state = 0;
payloadLength = 0;
offset = 0;
buffer = null;
emit = false;
}
}
}
};
}

// Parse the buffered data, if applicable.
function parsePacket(vars) {
var jsonString, message;

if (!vars.data.length) {
// Nothing to do.
return;
}

if (vars.type === BlizzardSession.TYPE_JSON) {
self.emit("json", vars.id, vars.data.toString("utf8"));
} else if (vars.type === BlizzardSession.TYPE_BUFFER_RESPONSE) {
self.emit("buffer", vars.id, vars.data);
socket.on("data", parser(function (type, id, buffer) {
if (type === BlizzardSession.TYPE_JSON) {
self.emit("json", id, buffer.toString("utf8"));
} else if (type === BlizzardSession.TYPE_BUFFER_RESPONSE) {
if (!buffer) {
self.emit("bufferComplete", id);
} else {
self.emit("buffer", id, buffer);
}
} else if (type === BlizzardSession.TYPE_HANDSHAKE) {
self.emit("ready");
} else {
self.emit("fail", vars.id, BlizzardSession.ERROR_INVALID, "Unknown packet type.");
self.emit("fail", 0, BlizzardSession.ERROR_INVALID, "Unknown packet type.");
}
}

(function start() {
Binary.stream(socket).loop(function (end) {
// Read the header.
this.word8("magic");
this.word8("type");
this.word32be("id");
this.word32be("length");
this.buffer("data", "length");

// Calling tap() is very expensive. Only do it once.
this.tap(function parse(vars) {
if (vars.magic === BlizzardSession.MAGIC) {
parseHeader(vars);
parsePacket(vars);
} else {
self.emit("fail", 0, BlizzardSession.ERROR_INVALID, "Unexpected value for magic byte.");
// Restart the parser.
end();
start();
}
});

// Clear variables for next loop.
this.flush();
});
}());
}));
};

module.exports = BlizzardSession;

0 comments on commit 4f71d02

Please sign in to comment.