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);
// 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) {
var ended = false;
@ -130,12 +106,10 @@ Client.prototype.setSocket = function(socket) {
this.serializer.pipe(this.framer).pipe(this.socket);
this.deserializer.on('data', (parsed) => {
var packet = parsed.results;
var packetName = packetIndexes.packetNames[packet.state][this.isServer ? 'toServer' : 'toClient'][packet.id];
this.emit('packet', packet);
this.emit(packetName, packet);
this.emit('raw.' + packetName, parsed.buffer, packet.state);
this.emit('raw', parsed.buffer, packet.state);
this.emit('packet', parsed.data, parsed.metadata);
this.emit(parsed.metadata.name, parsed.data, parsed.metadata);
this.emit('raw.' + parsed.metadata.name, parsed.buffer, parsed.metadata);
this.emit('raw', parsed.buffer, parsed.metadata);
});
};
@ -173,18 +147,10 @@ Client.prototype.setCompressionThreshold = function(threshold) {
}
}
Client.prototype.write = function(packetId, params) {
if(Array.isArray(packetId)) {
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) + ")");
Client.prototype.write = function(packetName, params) {
debug("writing packet " + this.state + "." + packetName);
debug(params);
this.serializer.write({ packetId, params });
this.serializer.write({ packetName, params });
};
Client.prototype.writeRaw = function(buffer) {

View File

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

View File

@ -24,12 +24,12 @@ function readPackets(packets, states) {
assert(fields !== undefined, 'missing fields for packet ' + 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(!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');
packetNames[state][direction][id] = name;
packetIds[state][direction][name] = id;
packetFields[state][direction][id] = fields;
packetFields[state][direction][name] = fields;
packetStates[direction][name] = state;
}
});

View File

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

View File

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

View File

@ -133,42 +133,38 @@ describe("packets", function() {
});
client.end();
});
var packetId, packetInfo, field;
var packetName, packetInfo, field;
for(state in mc.packetFields) {
if(!mc.packetFields.hasOwnProperty(state)) continue;
for(packetId in mc.packetFields[state].toServer) {
if(!mc.packetFields[state].toServer.hasOwnProperty(packetId)) continue;
packetId = parseInt(packetId, 10);
packetInfo = mc.get(packetId, state, true);
it(state + ",ServerBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2),
callTestPacket(packetId, packetInfo, state, true));
for(packetName in mc.packetFields[state].toServer) {
if(!mc.packetFields[state].toServer.hasOwnProperty(packetName)) continue;
packetInfo = mc.get(packetName, state, true);
it(state + ",ServerBound," + packetName,
callTestPacket(packetName, packetInfo, state, true));
}
for(packetId in mc.packetFields[state].toClient) {
if(!mc.packetFields[state].toClient.hasOwnProperty(packetId)) continue;
packetId = parseInt(packetId, 10);
packetInfo = mc.get(packetId, state, false);
it(state + ",ClientBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2),
callTestPacket(packetId, packetInfo, state, false));
for(packetName in mc.packetFields[state].toClient) {
if(!mc.packetFields[state].toClient.hasOwnProperty(packetName)) continue;
packetInfo = mc.get(packetName, state, false);
it(state + ",ClientBound," + packetName,
callTestPacket(packetName, packetInfo, state, false));
}
}
function callTestPacket(packetId, packetInfo, state, toServer) {
function callTestPacket(packetName, packetInfo, state, toServer) {
return function(done) {
client.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
var packet = {};
packetInfo.forEach(function(field) {
packet[field.name] = getValue(field.type, packet);
});
if(toServer) {
serverClient.once([state, packetId], function(receivedPacket) {
delete receivedPacket.id;
delete receivedPacket.state;
serverClient.once(packetName, function(receivedPacket) {
try {
assertPacketsMatch(packet, receivedPacket);
} catch (e) {
@ -177,15 +173,13 @@ describe("packets", function() {
}
done();
});
client.write(packetId, packet);
client.write(packetName, packet);
} else {
client.once([state, packetId], function(receivedPacket) {
delete receivedPacket.id;
delete receivedPacket.state;
client.once(packetName, function(receivedPacket) {
assertPacketsMatch(packet, receivedPacket);
done();
});
serverClient.write(packetId, packet);
serverClient.write(packetName, packet);
}
}
@ -327,7 +321,7 @@ describe("client", function() {
username: 'Player',
});
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);
gotKicked = true;
});