Merge pull request #236 from roblabla/feature-cleanup

Clean up the mess in create/parse Packet. Deprecate ID and [state; ID]
This commit is contained in:
Robin Lambertz 2015-09-20 00:58:15 +02:00
commit 3bbb1eae6a
6 changed files with 124 additions and 197 deletions

View File

@ -66,30 +66,6 @@ function Client(isServer) {
util.inherits(Client, EventEmitter); util.inherits(Client, EventEmitter);
// Transform weird "packet" types into string representing their type. Should be mostly retro-compatible
Client.prototype.on = function(type, func) {
var direction = this.isServer ? 'toServer' : 'toClient';
if(Array.isArray(type)) {
arguments[0] = packetIndexes.packetNames[type[0]][direction][type[1]];
} else if(typeof type === "number") {
arguments[0] = packetIndexes.packetNames[this.state][direction][type];
}
EventEmitter.prototype.on.apply(this, arguments);
};
Client.prototype.onRaw = function(type, func) {
var arg = "raw.";
if(Array.isArray(type)) {
arg += packetIndexes.packetNames[type[0]][direction][type[1]];
} else if(typeof type === "number") {
arg += packetIndexes.packetNames[this.state][direction][type];
} else {
arg += type;
}
arguments[0] = arg;
EventEmitter.prototype.on.apply(this, arguments);
};
Client.prototype.setSocket = function(socket) { Client.prototype.setSocket = function(socket) {
var ended = false; var ended = false;
@ -130,12 +106,10 @@ Client.prototype.setSocket = function(socket) {
this.serializer.pipe(this.framer).pipe(this.socket); this.serializer.pipe(this.framer).pipe(this.socket);
this.deserializer.on('data', (parsed) => { this.deserializer.on('data', (parsed) => {
var packet = parsed.results; this.emit('packet', parsed.data, parsed.metadata);
var packetName = packetIndexes.packetNames[packet.state][this.isServer ? 'toServer' : 'toClient'][packet.id]; this.emit(parsed.metadata.name, parsed.data, parsed.metadata);
this.emit('packet', packet); this.emit('raw.' + parsed.metadata.name, parsed.buffer, parsed.metadata);
this.emit(packetName, packet); this.emit('raw', parsed.buffer, parsed.metadata);
this.emit('raw.' + packetName, parsed.buffer, packet.state);
this.emit('raw', parsed.buffer, packet.state);
}); });
}; };
@ -173,18 +147,10 @@ Client.prototype.setCompressionThreshold = function(threshold) {
} }
} }
Client.prototype.write = function(packetId, params) { Client.prototype.write = function(packetName, params) {
if(Array.isArray(packetId)) { debug("writing packet " + this.state + "." + packetName);
if(packetId[0] !== this.state)
return false;
packetId = packetId[1];
}
if(typeof packetId === "string")
packetId = packetIndexes.packetIds[this.state][this.isServer ? "toClient" : "toServer"][packetId];
var packetName = packetIndexes.packetNames[this.state][this.isServer ? "toClient" : "toServer"][packetId];
debug("writing packetId " + this.state + "." + packetName + " (0x" + packetId.toString(16) + ")");
debug(params); debug(params);
this.serializer.write({ packetId, params }); this.serializer.write({ packetName, params });
}; };
Client.prototype.writeRaw = function(buffer) { Client.prototype.writeRaw = function(buffer) {

View File

@ -104,26 +104,26 @@ function readContainer(buffer, offset, typeArgs, rootNode) {
}; };
var backupThis = rootNode.this; var backupThis = rootNode.this;
rootNode.this = results.value; rootNode.this = results.value;
for(var index in typeArgs) { typeArgs.forEach((typeArg) => {
var readResults; var readResults;
tryCatch(() => { tryCatch(() => {
readResults = this.read(buffer, offset, typeArgs[index].type, rootNode); readResults = this.read(buffer, offset, typeArg.type, rootNode);
}, (e) => {
if (typeArgs && typeArgs[index] && typeArgs[index].name)
addErrorField(e, typeArgs[index].name);
else
addErrorField(e, "unknown");
throw e;
});
results.size += readResults.size; results.size += readResults.size;
offset += readResults.size; offset += readResults.size;
if (typeArgs[index].anon) { if (typeArg.anon) {
Object.keys(readResults.value).forEach(function(key) { Object.keys(readResults.value).forEach(function(key) {
results.value[key] = readResults.value[key]; results.value[key] = readResults.value[key];
}); });
} else } else
results.value[typeArgs[index].name] = readResults.value; results.value[typeArg.name] = readResults.value;
} }, (e) => {
if (typeArgs && typeArg && typeArg.name)
addErrorField(e, typeArg.name);
else
addErrorField(e, "unknown");
throw e;
});
});
rootNode.this = backupThis; rootNode.this = backupThis;
return results; return results;
} }
@ -131,20 +131,20 @@ function readContainer(buffer, offset, typeArgs, rootNode) {
function writeContainer(value, buffer, offset, typeArgs, rootNode) { function writeContainer(value, buffer, offset, typeArgs, rootNode) {
var backupThis = rootNode.this; var backupThis = rootNode.this;
rootNode.this = value; rootNode.this = value;
for(var index in typeArgs) { typeArgs.forEach((typeArg) => {
tryCatch(() => { tryCatch(() => {
if (typeArgs[index].anon) if (typeArg.anon)
offset = this.write(value, buffer, offset, typeArgs[index].type, rootNode); offset = this.write(value, buffer, offset, typeArg.type, rootNode);
else else
offset = this.write(value[typeArgs[index].name], buffer, offset, typeArgs[index].type, rootNode); offset = this.write(value[typeArg.name], buffer, offset, typeArg.type, rootNode);
}, (e) => { }, (e) => {
if (typeArgs && typeArgs[index] && typeArgs[index].name) if (typeArgs && typeArg && typeArg.name)
addErrorField(e, typeArgs[index].name); addErrorField(e, typeArg.name);
else else
addErrorField(e, "unknown"); addErrorField(e, "unknown");
throw e; throw e;
}); });
} });
rootNode.this = backupThis; rootNode.this = backupThis;
return offset; return offset;
} }
@ -153,20 +153,20 @@ function sizeOfContainer(value, typeArgs, rootNode) {
var size = 0; var size = 0;
var backupThis = rootNode.this; var backupThis = rootNode.this;
rootNode.this = value; rootNode.this = value;
for(var index in typeArgs) { typeArgs.forEach((typeArg) => {
tryCatch(() => { tryCatch(() => {
if (typeArgs[index].anon) if (typeArg.anon)
size += this.sizeOf(value, typeArgs[index].type, rootNode); size += this.sizeOf(value, typeArg.type, rootNode);
else else
size += this.sizeOf(value[typeArgs[index].name], typeArgs[index].type, rootNode); size += this.sizeOf(value[typeArg.name], typeArg.type, rootNode);
}, (e) => { }, (e) => {
if (typeArgs && typeArgs[index] && typeArgs[index].name) if (typeArgs && typeArg && typeArg.name)
addErrorField(e, typeArgs[index].name); addErrorField(e, typeArg.name);
else else
addErrorField(e, "unknown"); addErrorField(e, "unknown");
throw e; throw e;
}); });
} });
rootNode.this = backupThis; rootNode.this = backupThis;
return size; return size;
} }

View File

@ -24,12 +24,12 @@ function readPackets(packets, states) {
assert(fields !== undefined, 'missing fields for packet ' + name); assert(fields !== undefined, 'missing fields for packet ' + name);
assert(!packetNames[state][direction].hasOwnProperty(id), 'duplicate packet id ' + id + ' for ' + name); assert(!packetNames[state][direction].hasOwnProperty(id), 'duplicate packet id ' + id + ' for ' + name);
assert(!packetIds[state][direction].hasOwnProperty(name), 'duplicate packet name ' + name + ' for ' + id); assert(!packetIds[state][direction].hasOwnProperty(name), 'duplicate packet name ' + name + ' for ' + id);
assert(!packetFields[state][direction].hasOwnProperty(id), 'duplicate packet id ' + id + ' for ' + name); assert(!packetFields[state][direction].hasOwnProperty(name), 'duplicate packet id ' + id + ' for ' + name);
assert(!packetStates[direction].hasOwnProperty(name), 'duplicate packet name ' + name + ' for ' + id + ', must be unique across all states'); assert(!packetStates[direction].hasOwnProperty(name), 'duplicate packet name ' + name + ' for ' + id + ', must be unique across all states');
packetNames[state][direction][id] = name; packetNames[state][direction][id] = name;
packetIds[state][direction][name] = id; packetIds[state][direction][name] = id;
packetFields[state][direction][id] = fields; packetFields[state][direction][name] = fields;
packetStates[direction][name] = state; packetStates[direction][name] = state;
} }
}); });

View File

@ -60,112 +60,89 @@ var packetStates = packetIndexes.packetStates;
// TODO : This does NOT contain the length prefix anymore. // TODO : This does NOT contain the length prefix anymore.
function createPacketBuffer(packetId, state, params, isServer) { function createPacketBuffer(packetName, state, params, isServer) {
var length = 0;
var direction = !isServer ? 'toServer' : 'toClient'; var direction = !isServer ? 'toServer' : 'toClient';
if(typeof packetId === 'string' && typeof state !== 'string' && !params) { var packetId = packetIds[state][direction][packetName];
// simplified two-argument usage, createPacketBuffer(name, params)
params = state;
state = packetStates[direction][packetId];
}
if(typeof packetId === 'string') packetId = packetIds[state][direction][packetId];
assert.notEqual(packetId, undefined); assert.notEqual(packetId, undefined);
var packet = get(packetName, state, !isServer);
var packet = get(packetId, state, !isServer);
var packetName = packetNames[state][direction][packetId];
assert.notEqual(packet, null); assert.notEqual(packet, null);
packet.forEach(function(fieldInfo) {
var length = utils.varint[2](packetId);
tryCatch(() => { tryCatch(() => {
length += proto.sizeOf(params[fieldInfo.name], fieldInfo.type, params); length += structures.container[2].call(proto, params, packet, {});
//length += proto.sizeOf(params, ["container", packet], {});
}, (e) => { }, (e) => {
addErrorField(e, fieldInfo.name); e.field = [state, direction, packetName, e.field].join(".");
e.message = "sizeOf error for "+[state,direction,packetName,e.field].join(".")+"\n"+ e.message = `SizeOf error for ${e.field} : ${e.message}`;
" in packet 0x" + packetId.toString(16)+" "+JSON.stringify(params)+"\n"
+ e.message;
throw e; throw e;
}); });
});
length += utils.varint[2](packetId); var buffer = new Buffer(length);
var size = length;// + utils.varint[2](length); var offset = utils.varint[1](packetId, buffer, 0);
var buffer = new Buffer(size);
var offset = 0;//utils.varint[1](length, buffer, 0);
offset = utils.varint[1](packetId, buffer, offset);
packet.forEach(function(fieldInfo) {
var value = params[fieldInfo.name];
// TODO : This check belongs to the respective datatype.
if(typeof value === "undefined" && fieldInfo.type != "count")
debug(new Error("Missing Property " + fieldInfo.name).stack);
tryCatch(() => { tryCatch(() => {
offset = proto.write(value, buffer, offset, fieldInfo.type, params); offset = structures.container[1].call(proto, params, buffer, offset, packet, {});
//offset = proto.write(params, buffer, offset, ["container", packet], {});
}, (e) => { }, (e) => {
e.message = "Write error for " + packetName + "." + e.field + " : " + e.message; e.field = [state, direction, packetName, e.field].join(".");
e.message = `Write error for ${e.field} : ${e.message}`;
throw e; throw e;
}); });
});
return buffer; return buffer;
} }
function get(packetId, state, toServer) { function get(packetName, state, toServer) {
var direction = toServer ? "toServer" : "toClient"; var direction = toServer ? "toServer" : "toClient";
var packetInfo = packetFields[state][direction][packetId]; var packetInfo = packetFields[state][direction][packetName];
if(!packetInfo) { if(!packetInfo) {
return null; return null;
} }
return packetInfo; return packetInfo;
} }
// By default, parse every packets.
function parsePacketData(buffer, state, isServer, packetsToParse = {"packet": true}) { function parsePacketData(buffer, state, isServer, packetsToParse = {"packet": true}) {
var cursor = 0; var { value: packetId, size: cursor } = utils.varint[0](buffer, 0);
var { value: packetId, size: cursor } = utils.varint[0](buffer, cursor);
var results = {id: packetId, state: state}; var direction = isServer ? "toServer" : "toClient";
// Only parse the packet if there is a need for it, AKA if there is a listener attached to it var packetName = packetNames[state][direction][packetId];
var name = packetNames[state][isServer ? "toServer" : "toClient"][packetId]; var results = {
var shouldParse = (!packetsToParse.hasOwnProperty(name) || packetsToParse[name] <= 0) metadata: {
&& (!packetsToParse.hasOwnProperty("packet") || packetsToParse["packet"] <= 0); name: packetName,
if(shouldParse) { id: packetId,
return { state
buffer: buffer, },
results: results data: {},
buffer
}; };
}
var packetInfo = get(packetId, state, isServer); // Only parse the packet if there is a need for it, AKA if there is a listener
if(packetInfo === null) { // attached to it.
var shouldParse =
(packetsToParse.hasOwnProperty(packetName) && packetsToParse[packetName] > 0) ||
(packetsToParse.hasOwnProperty("packet") && packetsToParse["packet"] > 0);
if (!shouldParse)
return results;
var packetInfo = get(packetName, state, isServer);
if(packetInfo === null)
throw new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")") throw new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")")
} else { else
var packetName = packetNames[state][isServer ? "toServer" : "toClient"][packetId];
debug("read packetId " + state + "." + packetName + " (0x" + packetId.toString(16) + ")"); debug("read packetId " + state + "." + packetName + " (0x" + packetId.toString(16) + ")");
}
var packetName = packetNames[state][!isServer ? 'toClient' : 'toServer'][packetId]; var res;
var i, fieldInfo, readResults;
for(i = 0; i < packetInfo.length; ++i) {
fieldInfo = packetInfo[i];
tryCatch(() => { tryCatch(() => {
readResults = proto.read(buffer, cursor, fieldInfo.type, results); res = proto.read(buffer, cursor, ["container", packetInfo], {});
}, (e) => { }, (e) => {
e.message = "Read error for " + packetName + "." + e.field + " : " + e.message; e.field = [state, direction, packetName, e.field].join(".");
e.message = `Read error for ${e.field} : ${e.message}`;
throw e; throw e;
}); });
if(readResults === null) results.data = res.value;
throw new Error("A reader returned null. This is _not_ normal"); cursor += res.size;
if(readResults.error)
throw new Error("A reader returned an error using the old method.");
if (readResults.value != null)
results[fieldInfo.name] = readResults.value;
cursor += readResults.size;
}
if(buffer.length > cursor) if(buffer.length > cursor)
throw new Error("Packet data not entirely read"); throw new Error(`Read error for ${packetName} : Packet data not entirely read`);
debug(results); debug(results);
return { return results;
results: results,
buffer: buffer
};
} }
class Serializer extends Transform { class Serializer extends Transform {
@ -175,10 +152,9 @@ class Serializer extends Transform {
this.isServer = isServer; this.isServer = isServer;
} }
// TODO : Might make sense to make createPacketBuffer async.
_transform(chunk, enc, cb) { _transform(chunk, enc, cb) {
try { try {
var buf = createPacketBuffer(chunk.packetId, this.protocolState, chunk.params, this.isServer); var buf = createPacketBuffer(chunk.packetName, this.protocolState, chunk.params, this.isServer);
this.push(buf); this.push(buf);
return cb(); return cb();
} catch (e) { } catch (e) {
@ -202,15 +178,7 @@ class Deserializer extends Transform {
} catch (e) { } catch (e) {
return cb(e); return cb(e);
} }
if (packet.error)
{
packet.error.packet = packet;
return cb(packet.error)
}
else
{
this.push(packet); this.push(packet);
return cb(); return cb();
} }
} }
}

View File

@ -18,8 +18,7 @@ console.log('Beginning write test');
start = Date.now(); start = Date.now();
for(i = 0; i < ITERATIONS; i++) { for(i = 0; i < ITERATIONS; i++) {
for(j = 0; j < testDataWrite.length; j++) { for(j = 0; j < testDataWrite.length; j++) {
var id=mc.packetIds['play']['toServer'][testDataWrite[j].name]; inputData.push(mc.createPacketBuffer(testDataWrite[j].name, states.PLAY, testDataWrite[j].params, false));
inputData.push(mc.createPacketBuffer(id, states.PLAY, testDataWrite[j].params, false));
} }
} }
console.log('Finished write test in ' + (Date.now() - start) / 1000 + ' seconds'); console.log('Finished write test in ' + (Date.now() - start) / 1000 + ' seconds');

View File

@ -133,42 +133,38 @@ describe("packets", function() {
}); });
client.end(); client.end();
}); });
var packetId, packetInfo, field; var packetName, packetInfo, field;
for(state in mc.packetFields) { for(state in mc.packetFields) {
if(!mc.packetFields.hasOwnProperty(state)) continue; if(!mc.packetFields.hasOwnProperty(state)) continue;
for(packetId in mc.packetFields[state].toServer) { for(packetName in mc.packetFields[state].toServer) {
if(!mc.packetFields[state].toServer.hasOwnProperty(packetId)) continue; if(!mc.packetFields[state].toServer.hasOwnProperty(packetName)) continue;
packetId = parseInt(packetId, 10); packetInfo = mc.get(packetName, state, true);
packetInfo = mc.get(packetId, state, true); it(state + ",ServerBound," + packetName,
it(state + ",ServerBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2), callTestPacket(packetName, packetInfo, state, true));
callTestPacket(packetId, packetInfo, state, true));
} }
for(packetId in mc.packetFields[state].toClient) { for(packetName in mc.packetFields[state].toClient) {
if(!mc.packetFields[state].toClient.hasOwnProperty(packetId)) continue; if(!mc.packetFields[state].toClient.hasOwnProperty(packetName)) continue;
packetId = parseInt(packetId, 10); packetInfo = mc.get(packetName, state, false);
packetInfo = mc.get(packetId, state, false); it(state + ",ClientBound," + packetName,
it(state + ",ClientBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2), callTestPacket(packetName, packetInfo, state, false));
callTestPacket(packetId, packetInfo, state, false));
} }
} }
function callTestPacket(packetId, packetInfo, state, toServer) { function callTestPacket(packetName, packetInfo, state, toServer) {
return function(done) { return function(done) {
client.state = state; client.state = state;
serverClient.state = state; serverClient.state = state;
testPacket(packetId, packetInfo, state, toServer, done); testPacket(packetName, packetInfo, state, toServer, done);
}; };
} }
function testPacket(packetId, packetInfo, state, toServer, done) { function testPacket(packetName, packetInfo, state, toServer, done) {
// empty object uses default values // empty object uses default values
var packet = {}; var packet = {};
packetInfo.forEach(function(field) { packetInfo.forEach(function(field) {
packet[field.name] = getValue(field.type, packet); packet[field.name] = getValue(field.type, packet);
}); });
if(toServer) { if(toServer) {
serverClient.once([state, packetId], function(receivedPacket) { serverClient.once(packetName, function(receivedPacket) {
delete receivedPacket.id;
delete receivedPacket.state;
try { try {
assertPacketsMatch(packet, receivedPacket); assertPacketsMatch(packet, receivedPacket);
} catch (e) { } catch (e) {
@ -177,15 +173,13 @@ describe("packets", function() {
} }
done(); done();
}); });
client.write(packetId, packet); client.write(packetName, packet);
} else { } else {
client.once([state, packetId], function(receivedPacket) { client.once(packetName, function(receivedPacket) {
delete receivedPacket.id;
delete receivedPacket.state;
assertPacketsMatch(packet, receivedPacket); assertPacketsMatch(packet, receivedPacket);
done(); done();
}); });
serverClient.write(packetId, packet); serverClient.write(packetName, packet);
} }
} }
@ -327,7 +321,7 @@ describe("client", function() {
username: 'Player', username: 'Player',
}); });
var gotKicked = false; var gotKicked = false;
client.on([states.LOGIN, 0x00], function(packet) { client.on('disconnect', function(packet) {
assert.ok(packet.reason.indexOf('"Failed to verify username!"')!=-1); assert.ok(packet.reason.indexOf('"Failed to verify username!"')!=-1);
gotKicked = true; gotKicked = true;
}); });