From f45c6dff49cbc12903ac2c1b9d408e23d029c290 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sat, 3 Oct 2015 02:14:01 +0200 Subject: [PATCH 1/3] use protodef: * move general datatypes to protodef along with their tests * move states to states.js file * use one protodef serializer by state and direction instead of one big serializer for everything (same thing for the deserializer) * define a packet as a protodef type using a switch and a container, and adding each minecraft packet as a type (packet_ + name) * use mapper type from protodef to convert id to name in packet definition * use general string type : pstring * divide by 10 the number of iteration in the benchmark to get back to a reasonable test execution time --- doc/README.md | 4 +- examples/proxy/proxy.js | 10 +- .../server_helloworld/server_helloworld.js | 4 + package.json | 3 +- src/browser.js | 4 - src/client.js | 97 +++++-- src/createClient.js | 3 +- src/createServer.js | 3 +- src/datatypes/conditional.js | 69 ----- src/datatypes/minecraft.js | 7 +- src/datatypes/numeric.js | 50 ---- src/datatypes/structures.js | 182 ------------- src/datatypes/utils.js | 212 --------------- src/index.js | 4 +- src/packets.js | 43 --- src/ping.js | 2 +- src/protocol.js | 120 --------- src/server.js | 2 +- src/states.js | 8 + src/transforms/compression.js | 2 +- src/transforms/framing.js | 2 +- src/transforms/serializer.js | 222 ++++------------ test/benchmark.js | 8 +- test/dataTypes/numeric.js | 248 ------------------ test/dataTypes/utils.js | 205 --------------- test/packetTest.js | 18 +- 26 files changed, 169 insertions(+), 1363 deletions(-) delete mode 100644 src/datatypes/conditional.js delete mode 100644 src/datatypes/numeric.js delete mode 100644 src/datatypes/structures.js delete mode 100644 src/datatypes/utils.js delete mode 100644 src/packets.js delete mode 100644 src/protocol.js create mode 100644 src/states.js delete mode 100644 test/dataTypes/numeric.js delete mode 100644 test/dataTypes/utils.js diff --git a/doc/README.md b/doc/README.md index 0c60e1c..f7ea50a 100644 --- a/doc/README.md +++ b/doc/README.md @@ -119,12 +119,12 @@ The user's session, as returned by the Yggdrasil system. ### `packet` event Called with every packet parsed. Takes two params, the JSON data we parsed, -and the packet metadata (name, id, state) +and the packet metadata (name, state) ### `raw` event Called with every packet, but as a buffer. Takes two params, the buffer -and the packet metadata (name, id, state) +and the packet metadata (name, state) ### `state` event diff --git a/examples/proxy/proxy.js b/examples/proxy/proxy.js index 7e44b83..2aa22b5 100644 --- a/examples/proxy/proxy.js +++ b/examples/proxy/proxy.js @@ -108,7 +108,7 @@ srv.on('login', function(client) { if(targetClient.state == states.PLAY && meta.state == states.PLAY) { if(shouldDump(meta.name, "o")) { console.log("client->server:", - client.state + ".0x" + meta.id.toString(16) + " :", + client.state + " "+ meta.name + " :", JSON.stringify(data)); } if(!endedTargetClient) @@ -132,8 +132,8 @@ srv.on('login', function(client) { targetClient.on('raw', function(buffer, meta) { if(client.state != states.PLAY || meta.state != states.PLAY) return; - var packetData = targetClient.deserializer.parsePacketData(buffer).data; - var packetBuff = client.serializer.createPacketBuffer(meta.name, packetData); + var packetData = targetClient.deserializer.parsePacketBuffer(buffer).data.params; + var packetBuff = client.serializer.createPacketBuffer({name:meta.name, params:packetData}); if(!bufferEqual(buffer, packetBuff)) { console.log("client<-server: Error in packet " + state + "." + meta.name); console.log(buffer.toString('hex')); @@ -152,8 +152,8 @@ srv.on('login', function(client) { client.on('raw', function(buffer, meta) { if(meta.state != states.PLAY || targetClient.state != states.PLAY) return; - var packetData = client.deserializer.parsePacketData(buffer).data; - var packetBuff = targetClient.serializer.createPacketBuffer(meta.name, packetData); + var packetData = client.deserializer.parsePacketBuffer(buffer).data.params; + var packetBuff = targetClient.serializer.createPacketBuffer({name:meta.name, params:packetData}); if(!bufferEqual(buffer, packetBuff)) { console.log("client->server: Error in packet " + state + "." + meta.name); console.log(buffer.toString('hex')); diff --git a/examples/server_helloworld/server_helloworld.js b/examples/server_helloworld/server_helloworld.js index 5f5cb3a..d88c566 100644 --- a/examples/server_helloworld/server_helloworld.js +++ b/examples/server_helloworld/server_helloworld.js @@ -14,6 +14,10 @@ server.on('login', function(client) { console.log('Connection closed', '(' + addr + ')'); }); + client.on('error', function(error) { + console.log('Error:', error); + }); + // send init data so client will start rendering world client.write('login', { entityId: client.id, diff --git a/package.json b/package.json index 622003e..f7ae3ae 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "superagent": "~0.10.0", "ursa-purejs": "0.0.3", "uuid": "^2.0.1", - "yggdrasil": "0.1.0" + "yggdrasil": "0.1.0", + "protodef":"0.2.0" }, "optionalDependencies": { "ursa": "~0.9.1" diff --git a/src/browser.js b/src/browser.js index 91c7e13..31d89bb 100644 --- a/src/browser.js +++ b/src/browser.js @@ -1,10 +1,6 @@ -var readPackets = require("./packets").readPackets; var utils = require("./utils"); -var serializer = require("./transforms/serializer"); module.exports = { Client: require('./client'), - protocol: require('./protocol'), - readPackets:readPackets, supportedVersions:require("./version").supportedVersions }; diff --git a/src/client.js b/src/client.js index c555b70..3d3d118 100644 --- a/src/client.js +++ b/src/client.js @@ -1,11 +1,12 @@ var EventEmitter = require('events').EventEmitter; var debug = require('./debug'); -var serializer = require('./transforms/serializer'); var compression = require('./transforms/compression'); var framing = require('./transforms/framing'); var crypto = require('crypto'); -var states = serializer.states; +var states = require("./states"); +var createSerializer=require("./transforms/serializer").createSerializer; +var createDeserializer=require("./transforms/serializer").createDeserializer; class Client extends EventEmitter { @@ -19,12 +20,14 @@ class Client extends EventEmitter decompressor=null; deserializer; isServer; + version; + protocolState=states.HANDSHAKING; constructor(isServer,version) { super(); - this.serializer = serializer.createSerializer({ isServer, version:version}); - this.deserializer = serializer.createDeserializer({ isServer, packetsToParse: this.packetsToParse, version:version}); + this.version=version; this.isServer = !!isServer; + this.setSerializer(states.HANDSHAKING); this.on('newListener', function(event, listener) { var direction = this.isServer ? 'toServer' : 'toClient'; @@ -38,13 +41,76 @@ class Client extends EventEmitter } get state(){ - return this.serializer.protocolState; + return this.protocolState; + } + + + setSerializer(state) { + this.serializer = createSerializer({ isServer:this.isServer, version:this.version, state: state}); + this.deserializer = createDeserializer({ isServer:this.isServer, version:this.version, state: state, packetsToParse: + this.packetsToParse}); + + + this.serializer.on('error', (e) => { + var parts=e.field.split("."); + parts.shift(); + var serializerDirection = !this.isServer ? 'toServer' : 'toClient'; + e.field = [this.protocolState, serializerDirection].concat(parts).join("."); + e.message = `Serialization error for ${e.field} : ${e.message}`; + this.emit('error',e); + }); + + + this.deserializer.on('error', (e) => { + var parts=e.field.split("."); + parts.shift(); + var deserializerDirection = this.isServer ? 'toServer' : 'toClient'; + e.field = [this.protocolState, deserializerDirection].concat(parts).join("."); + e.message = `Deserialization error for ${e.field} : ${e.message}`; + this.emit('error',e); + }); + + this.deserializer.on('data', (parsed) => { + parsed.metadata.name=parsed.data.name; + parsed.data=parsed.data.params; + parsed.metadata.state=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); + }); } set state(newProperty) { - var oldProperty = this.serializer.protocolState; - this.serializer.protocolState = newProperty; - this.deserializer.protocolState = newProperty; + var oldProperty = this.protocolState; + this.protocolState = newProperty; + + if(!this.compressor) + { + this.serializer.unpipe(this.framer); + this.splitter.unpipe(this.deserializer); + } + else + { + this.serializer.unpipe(this.compressor); + this.decompressor.unpipe(this.deserializer); + } + + this.serializer.removeAllListeners(); + this.deserializer.removeAllListeners(); + this.setSerializer(this.protocolState); + + if(!this.compressor) + { + this.serializer.pipe(this.framer); + this.splitter.pipe(this.deserializer); + } + else + { + this.serializer.pipe(this.compressor); + this.decompressor.pipe(this.deserializer); + } + this.emit('state', newProperty, oldProperty); } @@ -87,20 +153,11 @@ class Client extends EventEmitter this.socket.on('close', endSocket); this.socket.on('end', endSocket); this.socket.on('timeout', endSocket); - this.serializer.on('error', onError); - this.deserializer.on('error', onError); this.framer.on('error', onError); this.splitter.on('error', onError); this.socket.pipe(this.splitter).pipe(this.deserializer); this.serializer.pipe(this.framer).pipe(this.socket); - - this.deserializer.on('data', (parsed) => { - 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); - }); } end(reason) { @@ -137,10 +194,10 @@ class Client extends EventEmitter } } - write(packetName, params) { - debug("writing packet " + this.state + "." + packetName); + write(name, params) { + debug("writing packet " + this.state + "." + name); debug(params); - this.serializer.write({ packetName, params }); + this.serializer.write({ name, params }); } writeRaw(buffer) { diff --git a/src/createClient.js b/src/createClient.js index de0986f..aa5fc0e 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -6,8 +6,7 @@ var assert = require('assert'); var crypto = require('crypto'); var yggdrasil = require('yggdrasil')({}); var yggserver = require('yggdrasil').server({}); -var serializer = require("./transforms/serializer"); -var states = serializer.states; +var states = require("./states"); var debug = require("./debug"); var uuid = require('uuid'); diff --git a/src/createServer.js b/src/createServer.js index 3b6689c..814a1d4 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,8 +1,7 @@ var ursa=require("./ursa"); var crypto = require('crypto'); var yggserver = require('yggdrasil').server({}); -var serializer = require("./transforms/serializer"); -var states = serializer.states; +var states = require("./states"); var bufferEqual = require('buffer-equal'); var Server = require('./server'); diff --git a/src/datatypes/conditional.js b/src/datatypes/conditional.js deleted file mode 100644 index d7e13ff..0000000 --- a/src/datatypes/conditional.js +++ /dev/null @@ -1,69 +0,0 @@ -var { getField, getFieldInfo } = require('../utils'); - -module.exports = { - 'switch': [readSwitch, writeSwitch, sizeOfSwitch], - 'option': [readOption, writeOption, sizeOfOption], -}; - -function readSwitch(buffer, offset, typeArgs, rootNode) { - var compareTo = getField(typeArgs.compareTo, rootNode); - var fieldInfo; - if (typeof typeArgs.fields[compareTo] === 'undefined' && typeof typeArgs.default === "undefined") - throw new Error(compareTo + " has no associated fieldInfo in switch"); - else if (typeof typeArgs.fields[compareTo] === 'undefined') - fieldInfo = getFieldInfo(typeArgs.default); - else - fieldInfo = getFieldInfo(typeArgs.fields[compareTo]); - return this.read(buffer, offset, fieldInfo, rootNode); -} - -function writeSwitch(value, buffer, offset, typeArgs, rootNode) { - var compareTo = getField(typeArgs.compareTo, rootNode); - var fieldInfo; - if (typeof typeArgs.fields[compareTo] === 'undefined' && typeof typeArgs.default === "undefined") - throw new Error(compareTo + " has no associated fieldInfo in switch"); - else if (typeof typeArgs.fields[compareTo] === 'undefined') - fieldInfo = getFieldInfo(typeArgs.default); - else - fieldInfo = getFieldInfo(typeArgs.fields[compareTo]); - return this.write(value, buffer, offset, fieldInfo, rootNode); -} - -function sizeOfSwitch(value, typeArgs, rootNode) { - var compareTo = getField(typeArgs.compareTo, rootNode); - var fieldInfo; - if (typeof typeArgs.fields[compareTo] === 'undefined' && typeof typeArgs.default === "undefined") - throw new Error(compareTo + " has no associated fieldInfo in switch"); - else if (typeof typeArgs.fields[compareTo] === 'undefined') - fieldInfo = getFieldInfo(typeArgs.default); - else - fieldInfo = getFieldInfo(typeArgs.fields[compareTo]); - return this.sizeOf(value, fieldInfo, rootNode); -} - -function readOption(buffer, offset, typeArgs, context) { - var val = buffer.readUInt8(offset++); - if (val !== 0) { - var retval = this.read(buffer, offset, typeArgs, context); - retval.size++; - return retval; - } else { - return { - size: 1 - }; - } -} - -function writeOption(value, buffer, offset, typeArgs, context) { - if (value != null) { - buffer.writeUInt8(1, offset++); - this.write(value, buffer, offset, typeArgs, context); - } else { - buffer.writeUInt8(0, offset++); - } - return offset; -} - -function sizeOfOption(value, typeArgs, context) { - return value == null ? 1 : this.sizeOf(value, typeArgs, context) + 1; -} diff --git a/src/datatypes/minecraft.js b/src/datatypes/minecraft.js index 7baa461..b4c3999 100644 --- a/src/datatypes/minecraft.js +++ b/src/datatypes/minecraft.js @@ -1,13 +1,12 @@ var nbt = require('prismarine-nbt'); -var utils = require("./utils"); -var numeric = require("./numeric"); +var types=require("protodef").types; var uuid = require('node-uuid'); // TODO : remove type-specific, replace with generic containers and arrays. module.exports = { 'UUID': [readUUID, writeUUID, 16], 'slot': [readSlot, writeSlot, sizeOfSlot], - 'nbt': [readNbt, utils.buffer[1], utils.buffer[2]], + 'nbt': [readNbt, types.buffer[1], types.buffer[2]], 'restBuffer': [readRestBuffer, writeRestBuffer, sizeOfRestBuffer], 'entityMetadataLoop': [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata] }; @@ -26,7 +25,7 @@ function writeUUID(value, buffer, offset) { function readSlot(buffer, offset) { var value = {}; - var results = numeric.short[0](buffer, offset); + var results = types.short[0](buffer, offset); if(!results) return null; value.blockId = results.value; diff --git a/src/datatypes/numeric.js b/src/datatypes/numeric.js deleted file mode 100644 index 045cb64..0000000 --- a/src/datatypes/numeric.js +++ /dev/null @@ -1,50 +0,0 @@ -function readLong(buffer, offset) { - if(offset + 8 > buffer.length) return null; - return { - value: [buffer.readInt32BE(offset), buffer.readInt32BE(offset + 4)], - size: 8, - }; -} - -function writeLong(value, buffer, offset) { - buffer.writeInt32BE(value[0], offset); - buffer.writeInt32BE(value[1], offset + 4); - return offset + 8; -} - -function generateFunctions(bufferReader,bufferWriter,size) -{ - var reader=function(buffer, offset) - { - if(offset + size > buffer.length) return null; - var value = buffer[bufferReader](offset); - return { - value: value, - size: size - }; - }; - var writer=function(value, buffer, offset) { - buffer[bufferWriter](value, offset); - return offset + size; - }; - return [reader, writer, size]; -} - -var nums= { - 'byte': ["readInt8", "writeInt8", 1], - 'ubyte': ["readUInt8", "writeUInt8", 1], - 'short': ["readInt16BE", "writeInt16BE", 2], - 'ushort': ["readUInt16BE", "writeUInt16BE", 2], - 'int': ["readInt32BE", "writeInt32BE", 4], - 'float': ["readFloatBE", "writeFloatBE", 4], - 'double': ["readDoubleBE", "writeDoubleBE", 8] -}; - -var types=Object.keys(nums).reduce(function(types,num){ - types[num]=generateFunctions(nums[num][0], nums[num][1], nums[num][2]); - return types; -},{}); -types["long"]=[readLong, writeLong, 8]; - - -module.exports = types; diff --git a/src/datatypes/structures.js b/src/datatypes/structures.js deleted file mode 100644 index 8e3b13d..0000000 --- a/src/datatypes/structures.js +++ /dev/null @@ -1,182 +0,0 @@ -var { getField, tryCatch, addErrorField } = require("../utils"); -var debug = require("../debug"); - -module.exports = { - 'array': [readArray, writeArray, sizeOfArray], - 'count': [readCount, writeCount, sizeOfCount], - 'container': [readContainer, writeContainer, sizeOfContainer] -}; - - -function evalCount(count, fields) { - if(fields[count["field"]] in count["map"]) - return count["map"][fields[count["field"]]]; - return count["default"]; -} - -function readArray(buffer, offset, typeArgs, rootNode) { - var results = { - value: [], - size: 0 - }; - var count; - if(typeof typeArgs.count === "object") - count = evalCount(typeArgs.count, rootNode); - else if (typeof typeArgs.count !== "undefined") - count = getField(typeArgs.count, rootNode); - else if (typeof typeArgs.countType !== "undefined") { - var countResults; - tryCatch(() => { - countResults = this.read(buffer, offset, { type: typeArgs.countType, typeArgs: typeArgs.countTypeArgs }, rootNode); - }, (e) => { - addErrorField(e, "$count"); - throw e; - }); - results.size += countResults.size; - offset += countResults.size; - count = countResults.value; - } else // TODO : broken schema, should probably error out. - count = 0; - for(var i = 0; i < count; i++) { - var readResults; - tryCatch(() => { - readResults = this.read(buffer, offset, typeArgs.type, rootNode); - }, (e) => { - addErrorField(e, i); - throw e; - }); - results.size += readResults.size; - offset += readResults.size; - results.value.push(readResults.value); - } - return results; -} - -function writeArray(value, buffer, offset, typeArgs, rootNode) { - if (typeof typeArgs.count === "undefined" && - typeof typeArgs.countType !== "undefined") { - tryCatch(() => { - offset = this.write(value.length, buffer, offset, { type: typeArgs.countType, typeArgs: typeArgs.countTypeArgs }, rootNode); - }, (e) => { - addErrorField(e, "$count"); - throw e; - }); - } else if (typeof typeArgs.count === "undefined") { // Broken schema, should probably error out - } - for(var index in value) { - tryCatch(() => { - offset = this.write(value[index], buffer, offset, typeArgs.type, rootNode); - }, (e) => { - addErrorField(e, index); - throw e; - }); - } - return offset; -} - -function sizeOfArray(value, typeArgs, rootNode) { - var size = 0; - if (typeof typeArgs.count === "undefined" && - typeof typeArgs.countType !== "undefined") { - tryCatch(() => { - size = this.sizeOf(value.length, { type: typeArgs.countType, typeArgs: typeArgs.countTypeArgs }, rootNode); - }, (e) => { - addErrorField(e, "$count"); - throw e; - }); - } - for(var index in value) { - tryCatch(() => { - size += this.sizeOf(value[index], typeArgs.type, rootNode); - }, (e) => { - addErrorField(e, index); - throw e; - }); - } - return size; -} - - -function readContainer(buffer, offset, typeArgs, context) { - var results = { - value: { "..": context }, - size: 0 - }; - typeArgs.forEach((typeArg) => { - tryCatch(() => { - var readResults = this.read(buffer, offset, typeArg.type, results.value); - 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 && typeArg && typeArg.name) - addErrorField(e, typeArg.name); - else - addErrorField(e, "unknown"); - throw e; - }); - }); - delete results.value[".."]; - return results; -} - -function writeContainer(value, buffer, offset, typeArgs, context) { - value[".."] = context; - typeArgs.forEach((typeArg) => { - tryCatch(() => { - if (typeArg.anon) - offset = this.write(value, buffer, offset, typeArg.type, value); - else - offset = this.write(value[typeArg.name], buffer, offset, typeArg.type, value); - }, (e) => { - if (typeArgs && typeArg && typeArg.name) - addErrorField(e, typeArg.name); - else - addErrorField(e, "unknown"); - throw e; - }); - }); - delete value[".."]; - return offset; -} - -function sizeOfContainer(value, typeArgs, context) { - value[".."] = context; - var size = 0; - typeArgs.forEach((typeArg) => { - tryCatch(() => { - if (typeArg.anon) - size += this.sizeOf(value, typeArg.type, value); - else - size += this.sizeOf(value[typeArg.name], typeArg.type, value); - }, (e) => { - if (typeArgs && typeArg && typeArg.name) - addErrorField(e, typeArg.name); - else - addErrorField(e, "unknown"); - throw e; - }); - }); - delete value[".."]; - return size; -} - -function readCount(buffer, offset, typeArgs, rootNode) { - return this.read(buffer, offset, typeArgs.type, rootNode); -} - -function writeCount(value, buffer, offset, typeArgs, rootNode) { - // Actually gets the required field, and writes its length. Value is unused. - // TODO : a bit hackityhack. - return this.write(getField(typeArgs.countFor, rootNode).length, buffer, offset, typeArgs.type, rootNode); -} - -function sizeOfCount(value, typeArgs, rootNode) { - // TODO : should I use value or getField().length ? - return this.sizeOf(getField(typeArgs.countFor, rootNode).length, typeArgs.type, rootNode); -} diff --git a/src/datatypes/utils.js b/src/datatypes/utils.js deleted file mode 100644 index d6ea8fe..0000000 --- a/src/datatypes/utils.js +++ /dev/null @@ -1,212 +0,0 @@ -var assert = require('assert'); - -var getField = require("../utils").getField; - -module.exports = { - 'varint': [readVarInt, writeVarInt, sizeOfVarInt], - 'bool': [readBool, writeBool, 1], - 'string': [readString, writeString, sizeOfString], - 'buffer': [readBuffer, writeBuffer, sizeOfBuffer], - 'void': [readVoid, writeVoid, 0], - 'bitfield': [readBitField, writeBitField, sizeOfBitField] -}; - -function readVarInt(buffer, offset) { - var result = 0; - var shift = 0; - var cursor = offset; - - while(true) { - if(cursor + 1 > buffer.length) return null; - var b = buffer.readUInt8(cursor); - result |= ((b & 0x7f) << shift); // Add the bits to our number, except MSB - cursor++; - if(!(b & 0x80)) { // If the MSB is not set, we return the number - return { - value: result, - size: cursor - offset - }; - } - shift += 7; // we only have 7 bits, MSB being the return-trigger - assert.ok(shift < 64, "varint is too big"); // Make sure our shift don't overflow. - } -} - -function sizeOfVarInt(value) { - var cursor = 0; - while(value & ~0x7F) { - value >>>= 7; - cursor++; - } - return cursor + 1; -} - -function writeVarInt(value, buffer, offset) { - var cursor = 0; - while(value & ~0x7F) { - buffer.writeUInt8((value & 0xFF) | 0x80, offset + cursor); - cursor++; - value >>>= 7; - } - buffer.writeUInt8(value, offset + cursor); - return offset + cursor + 1; -} - - -function readString(buffer, offset) { - var length = readVarInt(buffer, offset); - if(!!!length) return null; - var cursor = offset + length.size; - var stringLength = length.value; - var strEnd = cursor + stringLength; - if(strEnd > buffer.length) return null; - - var value = buffer.toString('utf8', cursor, strEnd); - cursor = strEnd; - - return { - value: value, - size: cursor - offset, - }; -} - -function writeString(value, buffer, offset) { - var length = Buffer.byteLength(value, 'utf8'); - offset = writeVarInt(length, buffer, offset); - buffer.write(value, offset, length, 'utf8'); - return offset + length; -} - - -function sizeOfString(value) { - var length = Buffer.byteLength(value, 'utf8'); - return sizeOfVarInt(length) + length; -} - -function readBool(buffer, offset) { - if(offset + 1 > buffer.length) return null; - var value = buffer.readInt8(offset); - return { - value: !!value, - size: 1, - }; -} - -function writeBool(value, buffer, offset) { - buffer.writeInt8(+value, offset); - return offset + 1; -} - - -function readBuffer(buffer, offset, typeArgs, rootNode) { - var size = 0; - var count; - if (typeof typeArgs.count !== "undefined") - count = getField(typeArgs.count, rootNode); - else if (typeof typeArgs.countType !== "undefined") { - var countResults = this.read(buffer, offset, { type: typeArgs.countType, typeArgs: typeArgs.countTypeArgs }, rootNode); - size += countResults.size; - offset += countResults.size; - count = countResults.value; - } - return { - value: buffer.slice(offset, offset + count), - size: size + count - }; -} - -function writeBuffer(value, buffer, offset, typeArgs, rootNode) { - if (typeof typeArgs.count === "undefined" && - typeof typeArgs.countType !== "undefined") { - offset = this.write(value.length, buffer, offset, { type: typeArgs.countType, typeArgs: typeArgs.countTypeArgs }, rootNode); - } else if (typeof typeArgs.count === "undefined") { // Broken schema, should probably error out - } - value.copy(buffer, offset); - return offset + value.length; -} - -function sizeOfBuffer(value, typeArgs, rootNode) { - var size = 0; - if (typeof typeArgs.count === "undefined" && - typeof typeArgs.countType !== "undefined") { - size = this.sizeOf(value.length, { type: typeArgs.countType, typeArgs: typeArgs.countTypeArgs }, rootNode); - } - return size + value.length; -} - -function readVoid() { - return { - value: undefined, - size: 0, - }; -} - -function writeVoid(value, buffer, offset) { - return offset; -} - -function generateBitMask(n) { - return (1 << n) - 1; -} - -function readBitField(buffer, offset, typeArgs, context) { - var beginOffset = offset; - var curVal = null; - var bits = 0; - var results = {}; - results.value = typeArgs.reduce(function(acc, item) { - var size = item.size; - var val = 0; - while (size > 0) { - if (bits == 0) { - curVal = buffer[offset++]; - bits = 8; - } - var bitsToRead = Math.min(size, bits); - val = (val << bitsToRead) | (curVal & generateBitMask(bits)) >> (bits - bitsToRead); - bits -= bitsToRead; - size -= bitsToRead; - } - if (item.signed && val >= 1 << (item.size - 1)) - val -= 1 << item.size; - acc[item.name] = val; - return acc; - }, {}); - results.size = offset - beginOffset; - return results; -} -function writeBitField(value, buffer, offset, typeArgs, context) { - var toWrite = 0; - var bits = 0; - typeArgs.forEach(function(item) { - var val = value[item.name]; - var size = item.size; - var signed = item.signed; - if ((!item.signed && val < 0) || (item.signed && val < -(1 << (size - 1)))) - throw new Error(value + " < " + item.signed ? (-(1 << (size - 1))) : 0); - else if ((!item.signed && val >= 1 << size) - || (item.signed && val >= (1 << (size - 1)) - 1)) - throw new Error(value + " >= " + iteme.signed ? (1 << size) : ((1 << (size - 1)) - 1)); - while (size > 0) { - var writeBits = Math.min(8 - bits, size); - toWrite = toWrite << writeBits | - ((val >> (size - writeBits)) & generateBitMask(writeBits)); - size -= writeBits; - bits += writeBits; - if (bits === 8) { - buffer[offset++] = toWrite; - bits = 0; - toWrite = 0; - } - } - }); - if (bits != 0) - buffer[offset++] = toWrite << (8 - bits); - return offset; -} - -function sizeOfBitField(value, typeArgs, context) { - return Math.ceil(typeArgs.reduce(function(acc, item) { - return acc + item.size; - }, 0) / 8); -} diff --git a/src/index.js b/src/index.js index 59251c1..2b9ce7d 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,6 @@ var Client = require('./client'); var Server = require('./server'); var serializer = require("./transforms/serializer"); var utils = require("./utils"); -var readPackets = require("./packets").readPackets; var createClient = require("./createClient"); var createServer = require("./createServer"); @@ -11,10 +10,9 @@ module.exports = { createServer: createServer, Client: Client, Server: Server, - states: serializer.states, + states: require("./states"), createSerializer:serializer.createSerializer, createDeserializer:serializer.createDeserializer, - readPackets:readPackets, ping: require('./ping'), supportedVersions:require("./version").supportedVersions }; diff --git a/src/packets.js b/src/packets.js deleted file mode 100644 index 8cc3d70..0000000 --- a/src/packets.js +++ /dev/null @@ -1,43 +0,0 @@ -var assert = require("assert"); - -module.exports = {readPackets: readPackets}; - -function readPackets(packets, states) { - var packetFields = {}; - var packetNames = {}; - var packetIds = {}; - var packetStates = {toClient: {}, toServer: {}}; - for(var stateName in states) { - var state = states[stateName]; - - packetFields[state] = {toClient: [], toServer: []}; - packetNames[state] = {toClient: [], toServer: []}; - packetIds[state] = {toClient: [], toServer: []}; - - ['toClient', 'toServer'].forEach(function(direction) { - for(var name in packets[state][direction]) { - var info = packets[state][direction][name]; - var id = parseInt(info.id); - var fields = info.fields; - - assert(id !== undefined, 'missing id for packet ' + name); - 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(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][name] = fields; - packetStates[direction][name] = state; - } - }); - } - return { - packetFields: packetFields, - packetNames: packetNames, - packetIds: packetIds, - packetStates: packetStates - }; -} diff --git a/src/ping.js b/src/ping.js index 77c2e3f..cf03f2f 100644 --- a/src/ping.js +++ b/src/ping.js @@ -1,6 +1,6 @@ var net = require('net'); var Client = require('./client'); -var states = require('./transforms/serializer').states; +var states = require("./states"); module.exports = ping; diff --git a/src/protocol.js b/src/protocol.js deleted file mode 100644 index 3ea20ac..0000000 --- a/src/protocol.js +++ /dev/null @@ -1,120 +0,0 @@ -var { getFieldInfo } = require('./utils'); -var reduce = require('lodash.reduce'); - -function isFieldInfo(type) { - return typeof type === "string" - || (Array.isArray(type) && typeof type[0] === "string") - || type.type; -} - -function findArgs(acc, v, k) { - if (typeof v === "string" && v.charAt(0) === '$') - acc.push({ "path": k, "val": v.substr(1) }); - else if (Array.isArray(v) || typeof v === "object") - acc = acc.concat(reduce(v, findArgs, []).map((v) => ({ "path": k + "." + v.path, "val": v.val }))); - return acc; -} - - -function setField(path, val, into) { - var c = path.split('.').reverse(); - while (c.length > 1) { - into = into[c.pop()]; - } - into[c.pop()] = val; -} - -function extendType(functions, defaultTypeArgs) { - var argPos = reduce(defaultTypeArgs, findArgs, []); - return [function read(buffer, offset, typeArgs, context) { - var args = JSON.parse(JSON.stringify(defaultTypeArgs)); - argPos.forEach((v) => { - setField(v.path, typeArgs[v.val], args); - }); - return functions[0].call(this, buffer, offset, args, context); - }, function write(value, buffer, offset, typeArgs, context) { - var args = JSON.parse(JSON.stringify(defaultTypeArgs)); - argPos.forEach((v) => { - setField(v.path, typeArgs[v.val], args); - }); - return functions[1].call(this, value, buffer, offset, args, context); - }, function sizeOf(value, typeArgs, context) { - var args = JSON.parse(JSON.stringify(defaultTypeArgs)); - argPos.forEach((v) => { - setField(v.path, typeArgs[v.val], args); - }); - if (typeof functions[2] === "function") - return functions[2].call(this, value, args, context); - else - return functions[2]; - }]; -} - -class NMProtocols -{ - types={}; - - constructor() { - - } - - addType(name, functions) { - if (functions === "native") - return; - else if (isFieldInfo(functions)) { - var fieldInfo = getFieldInfo(functions); - this.types[name] = extendType(this.types[fieldInfo.type], fieldInfo.typeArgs); - } - else - this.types[name] = functions; - } - - addTypes(types) { - var self = this; - Object.keys(types).forEach(function(name) { - self.addType(name, types[name]); - }); - } - - read(buffer, cursor, _fieldInfo, rootNodes) { - let fieldInfo = getFieldInfo(_fieldInfo); - var type = this.types[fieldInfo.type]; - if(!type) { - return { - error: new Error("missing data type: " + fieldInfo.type) - }; - } - var readResults = type[0].call(this, buffer, cursor, fieldInfo.typeArgs, rootNodes); - if(readResults == null) { - throw new Error("Reader returned null : " + JSON.stringify(fieldInfo)); - } - if(readResults && readResults.error) return {error: readResults.error}; - return readResults; - } - - write(value, buffer, offset, _fieldInfo, rootNode) { - let fieldInfo = getFieldInfo(_fieldInfo); - var type = this.types[fieldInfo.type]; - if(!type) { - return { - error: new Error("missing data type: " + fieldInfo.type) - }; - } - return type[1].call(this, value, buffer, offset, fieldInfo.typeArgs, rootNode); - } - - sizeOf(value, _fieldInfo, rootNode) { - let fieldInfo = getFieldInfo(_fieldInfo); - var type = this.types[fieldInfo.type]; - if(!type) { - throw new Error("missing data type: " + fieldInfo.type); - } - if(typeof type[2] === 'function') { - return type[2].call(this, value, fieldInfo.typeArgs, rootNode); - } else { - return type[2]; - } - } -} - -module.exports = NMProtocols; diff --git a/src/server.js b/src/server.js index 81471b8..acc76f4 100644 --- a/src/server.js +++ b/src/server.js @@ -1,7 +1,7 @@ var net = require('net'); var EventEmitter = require('events').EventEmitter; var Client = require('./client'); -var states = require('./transforms/serializer').states; +var states = require("./states"); class Server extends EventEmitter { diff --git a/src/states.js b/src/states.js new file mode 100644 index 0000000..a50cafc --- /dev/null +++ b/src/states.js @@ -0,0 +1,8 @@ +var states = { + "HANDSHAKING": "handshaking", + "STATUS": "status", + "LOGIN": "login", + "PLAY": "play" +}; + +module.exports=states; diff --git a/src/transforms/compression.js b/src/transforms/compression.js index 7049542..44f3f90 100644 --- a/src/transforms/compression.js +++ b/src/transforms/compression.js @@ -1,4 +1,4 @@ -var [readVarInt, writeVarInt, sizeOfVarInt] = require("../datatypes/utils").varint; +var [readVarInt, writeVarInt, sizeOfVarInt] = require("protodef").types.varint; var zlib = require("zlib"); var Transform = require("readable-stream").Transform; diff --git a/src/transforms/framing.js b/src/transforms/framing.js index 5e8d45b..0a13e6d 100644 --- a/src/transforms/framing.js +++ b/src/transforms/framing.js @@ -1,4 +1,4 @@ -var [readVarInt, writeVarInt, sizeOfVarInt] = require("../datatypes/utils").varint; +var [readVarInt, writeVarInt, sizeOfVarInt] = require("protodef").types.varint; var Transform = require("readable-stream").Transform; module.exports.createSplitter = function() { diff --git a/src/transforms/serializer.js b/src/transforms/serializer.js index 1f030ff..76852b4 100644 --- a/src/transforms/serializer.js +++ b/src/transforms/serializer.js @@ -1,185 +1,61 @@ -var [readVarInt, writeVarInt, sizeOfVarInt] = require("../datatypes/utils").varint; -var protocol = require("../protocol"); -var Transform = require("readable-stream").Transform; -var debug = require("../debug"); -var assert = require('assert'); -var { getFieldInfo, tryCatch, addErrorField } = require('../utils'); +var ProtoDef = require("protodef").ProtoDef; +var Serializer = require("protodef").Serializer; +var Parser = require("protodef").Parser; -module.exports.createSerializer = function(obj) { - return new Serializer(obj); -}; - -module.exports.createDeserializer = function(obj) { - return new Deserializer(obj); -}; - -// This is really just for the client. -var states = { - "HANDSHAKING": "handshaking", - "STATUS": "status", - "LOGIN": "login", - "PLAY": "play" -}; -module.exports.states = states; - -var NMProtocols = require("../protocol"); -var numeric = require("../datatypes/numeric"); -var utils = require("../datatypes/utils"); var minecraft = require("../datatypes/minecraft"); -var structures = require("../datatypes/structures"); -var conditional = require("../datatypes/conditional"); -var readPackets = require("../packets").readPackets; +var states = require("../states"); - -function createProtocol(types) +function createProtocol(types,packets) { - var proto = new NMProtocols(); - proto.addTypes(numeric); - proto.addTypes(utils); + var proto = new ProtoDef(); + proto.addType("string",["pstring",{ + countType:"varint" + }]); proto.addTypes(minecraft); - proto.addTypes(structures); - proto.addTypes(conditional); proto.addTypes(types); + + Object.keys(packets).forEach(function(name) { + proto.addType("packet_"+name,["container",packets[name].fields]); + }); + + proto.addType("packet",["container", [ + { "name": "name", "type":["mapper",{"type": "varint" , + "mappings":Object.keys(packets).reduce(function(acc,name){ + acc[parseInt(packets[name].id)]=name; + return acc; + },{}) + }]}, + { "name": "params", "type": ["switch", { + "compareTo": "name", + "fields": Object.keys(packets).reduce(function(acc,name){ + acc[name]="packet_"+name; + return acc; + },{}) + }]} + ]]); return proto; } - -class Serializer extends Transform { - constructor({ state = states.HANDSHAKING, isServer = false , version} = {}) { - super({ writableObjectMode: true }); - this.protocolState = state; - this.isServer = isServer; - this.version = version; - - var mcData=require("minecraft-data")(version); - var packets = mcData.protocol.states; - var packetIndexes = readPackets(packets, states); - - this.proto=createProtocol(mcData.protocol.types); - - this.packetFields = packetIndexes.packetFields; - this.packetIds = packetIndexes.packetIds; - } - - // TODO : This does NOT contain the length prefix anymore. - createPacketBuffer(packetName, params) { - var direction = !this.isServer ? 'toServer' : 'toClient'; - var packetId = this.packetIds[this.protocolState][direction][packetName]; - assert.notEqual(packetId, undefined, `${this.protocolState}.${direction}.${packetName} : ${packetId}`); - var packet = this.packetFields[this.protocolState][direction][packetName]; - packet=packet ? packet : null; - - assert.notEqual(packet, null); - - var length = utils.varint[2](packetId); - tryCatch(() => { - length += structures.container[2].call(this.proto, params, packet, {}); - //length += proto.sizeOf(params, ["container", packet], {}); - }, (e) => { - e.field = [this.protocolState, direction, packetName, e.field].join("."); - e.message = `SizeOf error for ${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(this.proto, params, buffer, offset, packet, {}); - //offset = proto.write(params, buffer, offset, ["container", packet], {}); - }, (e) => { - e.field = [this.protocolState, direction, packetName, e.field].join("."); - e.message = `Write error for ${e.field} : ${e.message}`; - throw e; - }); - return buffer; - } - - _transform(chunk, enc, cb) { - try { - var buf = this.createPacketBuffer(chunk.packetName, chunk.params); - this.push(buf); - return cb(); - } catch (e) { - return cb(e); - } - } +function createSerializer({ state = states.HANDSHAKING, isServer = false , version} = {}) +{ + var mcData=require("minecraft-data")(version); + var direction = !isServer ? 'toServer' : 'toClient'; + var packets = mcData.protocol.states[state][direction]; + var proto=createProtocol(mcData.protocol.types,packets); + return new Serializer(proto,"packet"); } -class Deserializer extends Transform { - constructor({ state = states.HANDSHAKING, isServer = false, packetsToParse = {"packet": true}, version } = {}) { - super({ readableObjectMode: true }); - this.protocolState = state; - this.isServer = isServer; - this.packetsToParse = packetsToParse; - this.version = version; - - - var mcData=require("minecraft-data")(version); - var packets = mcData.protocol.states; - var packetIndexes = readPackets(packets, states); - - this.proto=createProtocol(mcData.protocol.types); - - this.packetFields = packetIndexes.packetFields; - this.packetNames = packetIndexes.packetNames; - } - - parsePacketData(buffer) { - var { value: packetId, size: cursor } = utils.varint[0](buffer, 0); - - var direction = this.isServer ? "toServer" : "toClient"; - var packetName = this.packetNames[this.protocolState][direction][packetId]; - var results = { - metadata: { - name: packetName, - id: packetId, - state:this.protocolState - }, - data: {}, - buffer - }; - - // Only parse the packet if there is a need for it, AKA if there is a listener - // attached to it. - var shouldParse = - (this.packetsToParse.hasOwnProperty(packetName) && this.packetsToParse[packetName] > 0) || - (this.packetsToParse.hasOwnProperty("packet") && this.packetsToParse["packet"] > 0); - if (!shouldParse) - return results; - - var packetInfo = this.packetFields[this.protocolState][direction][packetName]; - packetInfo=packetInfo ? packetInfo : null; - if(packetInfo === null) - throw new Error("Unrecognized packetId: " + packetId + " (0x" + packetId.toString(16) + ")") - else - debug("read packetId " + this.protocolState + "." + packetName + " (0x" + packetId.toString(16) + ")"); - - var res; - tryCatch(() => { - res = this.proto.read(buffer, cursor, ["container", packetInfo], {}); - }, (e) => { - e.field = [this.protocolState, 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 : - ${JSON.stringify(results)}`); - debug(results); - return results; - } - - - _transform(chunk, enc, cb) { - var packet; - try { - packet = this.parsePacketData(chunk); - } catch (e) { - return cb(e); - } - this.push(packet); - return cb(); - } +function createDeserializer({ state = states.HANDSHAKING, isServer = false, + packetsToParse = {"packet": true}, version } = {}) +{ + var mcData=require("minecraft-data")(version); + var direction = isServer ? "toServer" : "toClient"; + var packets = mcData.protocol.states[state][direction]; + var proto=createProtocol(mcData.protocol.types,packets); + return new Parser(proto,"packet"); } + +module.exports = { + createSerializer:createSerializer, + createDeserializer:createDeserializer +}; diff --git a/test/benchmark.js b/test/benchmark.js index 54fe36f..c1454d2 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -1,4 +1,4 @@ -var ITERATIONS = 100000; +var ITERATIONS = 10000; var mc = require("../"); var util = require('util'); @@ -15,7 +15,7 @@ mc.supportedVersions.forEach(function(supportedVersion){ var mcData=require("minecraft-data")(supportedVersion); var version=mcData.version; describe("benchmark "+version.minecraftVersion,function(){ - this.timeout(20 * 1000); + this.timeout(60 * 1000); var inputData = []; it("bench serializing",function(done){ var serializer=new mc.createSerializer({state:states.PLAY,isServer:false,version:version.majorVersion}); @@ -24,7 +24,7 @@ mc.supportedVersions.forEach(function(supportedVersion){ start = Date.now(); for(i = 0; i < ITERATIONS; i++) { for(j = 0; j < testDataWrite.length; j++) { - inputData.push(serializer.createPacketBuffer(testDataWrite[j].name, testDataWrite[j].params)); + inputData.push(serializer.createPacketBuffer(testDataWrite[j])); } } var result=(Date.now() - start) / 1000; @@ -37,7 +37,7 @@ mc.supportedVersions.forEach(function(supportedVersion){ console.log('Beginning read test'); start = Date.now(); for (j = 0; j < inputData.length; j++) { - deserializer.parsePacketData(inputData[j]); + deserializer.parsePacketBuffer(inputData[j]); } console.log('Finished read test in ' + (Date.now() - start) / 1000 + ' seconds'); done(); diff --git a/test/dataTypes/numeric.js b/test/dataTypes/numeric.js deleted file mode 100644 index 049bdd5..0000000 --- a/test/dataTypes/numeric.js +++ /dev/null @@ -1,248 +0,0 @@ -var expect = require('chai').expect; - -var numeric = require('../../dist/datatypes/numeric'); -var getReader = function(dataType) { return dataType[0]; }; -var getWriter = function(dataType) { return dataType[1]; }; -var getSizeOf = function(dataType) { return dataType[2]; }; -/*var getReader = require('../../lib/utils').getReader; -var getWriter = require('../../lib/utils').getWriter; -var getSizeOf = require('../../lib/utils').getSizeOf;*/ - -var testData = { - 'byte': { - 'readPos': { - 'buffer': new Buffer([0x3d]), - 'value': 61 - }, - 'readNeg': { - 'buffer': new Buffer([0x86]), - 'value': -122 - }, - 'writePos': { - 'buffer': new Buffer([0x00]), - 'value': 32, - 'bufferAfter': new Buffer([0x20]) - }, - 'writeNeg': { - 'buffer': new Buffer([0x00]), - 'value': -122, - 'bufferAfter': new Buffer([0x86]) - }, - 'sizeof': { - 'value': 0x2d, - 'size': 1, - } - }, - 'ubyte': { - 'readPos': { - 'buffer': new Buffer([0x3d]), - 'value': 61 - }, - 'readNeg': { - 'buffer': new Buffer([0x86]), - 'value': 134 - }, - 'writePos': { - 'buffer': new Buffer([0x00]), - 'value': 61, - 'bufferAfter': new Buffer([0x3d]) - }, - 'writeNeg': { - 'buffer': new Buffer([0x00]), - 'value': 134, - 'bufferAfter': new Buffer([0x86]) - }, - 'sizeof': { - 'value': 0x2d, - 'size': 1, - } - }, - 'short': { - 'readPos': { - 'buffer': new Buffer([0x30, 0x87]), - 'value': 12423 - }, - 'readNeg': { - 'buffer': new Buffer([0xef, 0x77]), - 'value': -4233 - }, - 'writePos': { - 'buffer': new Buffer([0x00, 0x00]), - 'value': 12423, - 'bufferAfter': new Buffer([0x30, 0x87]), - }, - 'writeNeg': { - 'buffer': new Buffer([0x00, 0x00]), - 'value': -4233, - 'bufferAfter': new Buffer([0xef, 0x77]) - }, - 'sizeof': { - 'value': 0x2d, - 'size': 2, - } - }, - 'ushort': { - 'readPos': { - 'buffer': new Buffer([0x30, 0x87]), - 'value': 12423 - }, - 'readNeg': { - 'buffer': new Buffer([0xef, 0x77]), - 'value': 61303 - }, - 'writePos': { - 'buffer': new Buffer([0x00, 0x00]), - 'value': 12423, - 'bufferAfter': new Buffer([0x30, 0x87]), - }, - 'writeNeg': { - 'buffer': new Buffer([0x00, 0x00]), - 'value': 61303, - 'bufferAfter': new Buffer([0xef, 0x77]) - }, - 'sizeof': { - 'value': 0x2d, - 'size': 2, - } - }, - 'int': { - 'readPos': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0xea]), - 'value': 234 - }, - 'readNeg': { - 'buffer': new Buffer([0xff, 0xff, 0xfc, 0x00]), - 'value': -1024 - }, - 'writePos': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0x00]), - 'value': 234, - 'bufferAfter': new Buffer([0x00, 0x00, 0x00, 0xea]) - }, - 'writeNeg': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0x00]), - 'value': -1024, - 'bufferAfter': new Buffer([0xff, 0xff, 0xfc, 0x00]) - }, - 'sizeof': { - 'value': 0x2d, - 'size': 4 - } - }, - 'uint': { - 'readPos': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0xea]), - 'value': 234 - }, - 'readNeg': { - 'buffer': new Buffer([0xff, 0xff, 0xfc, 0x00]), - 'value': 4294966272 - }, - 'writePos': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0x00]), - 'value': 234, - 'bufferAfter': new Buffer([0x00, 0x00, 0x00, 0xea]) - }, - 'writeNeg': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0x00]), - 'value': 4294966272, - 'bufferAfter': new Buffer([0xff, 0xff, 0xfc, 0x00]) - }, - 'sizeof': { - 'value': 0x2d, - 'size': 4 - } - }, - 'float': { - 'readPos': { - 'buffer': new Buffer([0x47, 0x05, 0xc3, 0x00]), - 'value': 34243 - }, - 'readNeg': { - 'buffer': new Buffer([0xc6, 0x42, 0x4c, 0x00]), - 'value': -12435 - }, - 'writePos': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0x00]), - 'value': 34243, - 'bufferAfter': new Buffer([0x47, 0x05, 0xc3, 0x00]) - }, - 'writeNeg': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0x00]), - 'value': -12435, - 'bufferAfter': new Buffer([0xc6, 0x42, 0x4c, 0x00]) - }, - 'sizeof': { - 'value': 0x2d, - 'size': 4 - } - }, - 'double': { - 'readPos': { - 'buffer': new Buffer([0x40, 0xe0, 0xb8, 0x60, 0x00, 0x00, 0x00, 0x00]), - 'value': 34243 - }, - 'readNeg': { - 'buffer': new Buffer([0xc0, 0xc8, 0x49, 0x80, 0x00, 0x00, 0x00, 0x00]), - 'value': -12435 - }, - 'writePos': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), - 'value': 34243, - 'bufferAfter': new Buffer([0x40, 0xe0, 0xb8, 0x60, 0x00, 0x00, 0x00, 0x00]), - }, - 'writeNeg': { - 'buffer': new Buffer([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), - 'value': -12435, - 'bufferAfter': new Buffer([0xc0, 0xc8, 0x49, 0x80, 0x00, 0x00, 0x00, 0x00]), - }, - 'sizeof': { - 'value': 0x2d, - 'size': 8 - } - } -}; - -describe('Numeric', function() { - for (var key in testData) { - if (testData.hasOwnProperty(key) && numeric.hasOwnProperty(key)) { - var value = testData[key]; - describe('.' + key, function() { - var reader; - var writer; - var sizeof; - before(function() { - reader = getReader(numeric[key]); - writer = getWriter(numeric[key]); - sizeof = getSizeOf(numeric[key]); - }); - it('Returns null if not enough data is provided', function() { - expect(reader(new Buffer(0), 0)).to.eql(null); - }); - it('Reads positive values', function() { - expect(reader(value.readPos.buffer, 0).value).to.deep.eql(value.readPos.value); - }); - it('Reads big/negative values', function() { - expect(reader(value.readNeg.buffer, 0).value).to.deep.eql(value.readNeg.value); - }); - it('Writes positive values', function() { - writer(value.writePos.value, value.writePos.buffer, 0); - expect(value.writePos.buffer).to.deep.eql(value.writePos.bufferAfter); - }); - it('Writes negative values', function() { - writer(value.writeNeg.value, value.writeNeg.buffer, 0); - expect(value.writeNeg.buffer).to.deep.eql(value.writeNeg.bufferAfter); - }); - it('Calculates size', function() { - var size; - if (typeof sizeof === "function") { - size = sizeof(value.sizeof.value); - } else { - size = sizeof; - } - expect(size).to.eql(value.sizeof.size); - }); - }); - } - } -}); diff --git a/test/dataTypes/utils.js b/test/dataTypes/utils.js deleted file mode 100644 index c7a7efb..0000000 --- a/test/dataTypes/utils.js +++ /dev/null @@ -1,205 +0,0 @@ -var assert = require('power-assert'); -var expect = require('chai').expect; - -var utils = require('../../dist/datatypes/utils'); -var getReader = function(dataType) { return dataType[0]; }; -var getWriter = function(dataType) { return dataType[1]; }; -var getSizeOf = function(dataType) { return dataType[2]; }; - -describe('Utils', function() { - describe('.bool', function() { - it('Reads false value for binary 0', function() { - assert.deepEqual(getReader(utils.bool)(new Buffer([0]), 0), {value: false, size: 1}); - }); - it('Reads true for every other binary value', function() { - var buf = new Buffer([0]); - var i = 1; - while (i < 256) { - buf[0] = i++; - assert.deepEqual(getReader(utils.bool)(buf, 0), {value: true, size: 1}); - } - }); - it('Writes false', function() { - var buffer = new Buffer(1); - getWriter(utils.bool)(false, buffer, 0); - assert.deepEqual(buffer, new Buffer([0])); - }); - it('Writes true', function() { - var buffer = new Buffer(1); - getWriter(utils.bool)(true, buffer, 0); - assert.notDeepEqual(buffer, new Buffer([0])); - }); - it('Has a size of 1', function() { - assert.equal(typeof getSizeOf(utils.bool), "number"); - assert.equal(getSizeOf(utils.bool), 1); - }); - }); - describe('.varint', function() { - it.skip('Has no tests', function() { - }); - }); - describe('.buffer', function() { - it.skip('Has no tests', function() { - }); - }); - describe('.string', function() { - it.skip('Has no tests', function() { - }); - }); - describe('.void', function() { - it.skip('Has no tests', function() { - }); - }); - describe('.bitfield', function() { - it('Reads an unsigned 8 bit number', function() { - var buf = new Buffer([0xff]); - var typeArgs = [ - { "name": "one", "size": 8, "signed": false } - ]; - expect(getReader(utils.bitfield)(buf, 0, typeArgs, {})).to.deep.equal({ - value: { "one": 255 }, - size: 1 - }); - }); - it('Reads a signed 8 bit number', function() { - var buf = new Buffer([0xff]); - var typeArgs = [ - { "name": "one", "size": 8, "signed": true } - ]; - expect(getReader(utils.bitfield)(buf, 0, typeArgs, {})).to.deep.equal({ - value: { "one": -1 }, - size: 1 - }); - }); - it('Reads multiple signed 8 bit numbers', function() { - var buf = new Buffer([0xff, 0x80, 0x12]); - var typeArgs = [ - { "name": "one", "size": 8, "signed": true }, - { "name": "two", "size": 8, "signed": true }, - { "name": "three", "size": 8, "signed": true } - ]; - expect(getReader(utils.bitfield)(buf, 0, typeArgs, {})).to.deep.equal({ - value: { "one": -1, "two": -128, "three": 18 }, - size: 3 - }); - }); - it('Reads multiple unsigned 4 bit numbers', function() { - var buf = new Buffer([0xff, 0x80]); - var typeArgs = [ - { "name": "one", "size": 4, "signed": false }, - { "name": "two", "size": 4, "signed": false }, - { "name": "three", "size": 4, "signed": false } - ]; - expect(getReader(utils.bitfield)(buf, 0, typeArgs, {})).to.deep.equal({ - value: { "one": 15, "two": 15, "three": 8 }, - size: 2 - }); - }); - it('Reads multiple signed 4 bit numbers', function() { - var buf = new Buffer([0xff, 0x80]); - var typeArgs = [ - { "name": "one", "size": 4, "signed": true }, - { "name": "two", "size": 4, "signed": true }, - { "name": "three", "size": 4, "signed": true } - ]; - expect(getReader(utils.bitfield)(buf, 0, typeArgs, {})).to.deep.equal({ - value: { "one": -1, "two": -1, "three": -8 }, - size: 2 - }); - }); - it('Reads an unsigned 12 bit number', function() { - var buf = new Buffer([0xff, 0x80]); - var typeArgs = [ - { "name": "one", "size": 12, "signed": false } - ]; - assert.deepEqual(getReader(utils.bitfield)(buf, 0, typeArgs, {}), { - value: { "one": 4088 }, - size: 2 - }); - }); - it('Reads a complex structure', function() { - var buf = new Buffer([0x00, 0x00, 0x03, 0x05, 0x30, 0x42, 0xE0, 0x65]); - var typeArgs = [ - { "name": "x", "size": 26, "signed": true }, - { "name": "y", "size": 12, "signed": true }, - { "name": "z", "size": 26, "signed": true } - ]; - var value = { x: 12, y: 332, z: 4382821 }; - assert.deepEqual(getReader(utils.bitfield)(buf, 0, typeArgs, {}), { - value: value, - size: 8 - }); - }); - it('Writes an unsigned 8 bit number', function() { - var buf = new Buffer(1); - var typeArgs = [ - { "name": "one", "size": 8, "signed": false } - ]; - var value = { "one": 0xff }; - assert.equal(getWriter(utils.bitfield)(value, buf, 0, typeArgs, {}), 1); - assert.deepEqual(buf, new Buffer([0xff])); - }); - it('Writes a signed 8 bit number', function() { - var buf = new Buffer(1); - var typeArgs = [ - { "name": "one", "size": 8, "signed": true } - ]; - var value = { "one": -1 }; - assert.equal(getWriter(utils.bitfield)(value, buf, 0, typeArgs, {}), 1); - assert.deepEqual(buf, new Buffer([0xff])); - }); - it('Writes multiple signed 8 bit numbers', function() { - var buf = new Buffer(3); - var typeArgs = [ - { "name": "one", "size": 8, "signed": true }, - { "name": "two", "size": 8, "signed": true }, - { "name": "three", "size": 8, "signed": true } - ]; - var value = { "one": -1, "two": -128, "three": 18 }; - assert.equal(getWriter(utils.bitfield)(value, buf, 0, typeArgs, {}), 3); - assert.deepEqual(buf, new Buffer([0xff, 0x80, 0x12])); - }); - it('Writes multiple unsigned 4 bit numbers', function() { - var buf = new Buffer(2); - var typeArgs = [ - { "name": "one", "size": 4, "signed": false }, - { "name": "two", "size": 4, "signed": false }, - { "name": "three", "size": 4, "signed": false } - ]; - var value = { "one": 15, "two": 15, "three": 8 }; - assert.equal(getWriter(utils.bitfield)(value, buf, 0, typeArgs, {}), 2); - assert.deepEqual(buf, new Buffer([0xff, 0x80])); - }); - it('Writes multiple signed 4 bit numbers', function() { - var buf = new Buffer(2); - var typeArgs = [ - { "name": "one", "size": 4, "signed": true }, - { "name": "two", "size": 4, "signed": true }, - { "name": "three", "size": 4, "signed": true } - ]; - var value = { "one": -1, "two": -1, "three": -8 }; - assert.equal(getWriter(utils.bitfield)(value, buf, 0, typeArgs, {}), 2); - assert.deepEqual(buf, new Buffer([0xff, 0x80])); - }); - it('Writes an unsigned 12 bit number', function() { - var buf = new Buffer(2); - var typeArgs = [ - { "name": "one", "size": 12, "signed": false } - ]; - var value = { "one": 4088 }; - assert.equal(getWriter(utils.bitfield)(value, buf, 0, typeArgs, {}), 2); - assert.deepEqual(buf, new Buffer([0xff, 0x80])); - }); - it('Writes a complex structure', function() { - var buf = new Buffer(8); - var typeArgs = [ - { "name": "x", "size": 26, "signed": true }, - { "name": "y", "size": 12, "signed": true }, - { "name": "z", "size": 26, "signed": true } - ]; - var value = { x: 12, y: 332, z: 4382821 }; - assert.equal(getWriter(utils.bitfield)(value, buf, 0, typeArgs, {}), 8); - assert.deepEqual(buf, new Buffer([0x00, 0x00, 0x03, 0x05, 0x30, 0x42, 0xE0, 0x65])); - }); - }); -}); diff --git a/test/packetTest.js b/test/packetTest.js index 39ebdec..cb60ca7 100644 --- a/test/packetTest.js +++ b/test/packetTest.js @@ -107,8 +107,6 @@ mc.supportedVersions.forEach(function(supportedVersion){ var mcData=require("minecraft-data")(supportedVersion); var version=mcData.version; var packets = mcData.protocol.states; - var packetIndexes = mc.readPackets(packets, states); - var packetFields = packetIndexes.packetFields; describe("packets "+version.minecraftVersion, function() { var client, server, serverClient; @@ -132,18 +130,18 @@ mc.supportedVersions.forEach(function(supportedVersion){ client.end(); }); var packetName, packetInfo, field; - for(state in packetFields) { - if(!packetFields.hasOwnProperty(state)) continue; - for(packetName in packetFields[state].toServer) { - if(!packetFields[state].toServer.hasOwnProperty(packetName)) continue; - packetInfo = packetFields[state]["toServer"][packetName]; + for(state in packets) { + if(!packets.hasOwnProperty(state)) continue; + for(packetName in packets[state].toServer) { + if(!packets[state].toServer.hasOwnProperty(packetName)) continue; + packetInfo = packets[state]["toServer"][packetName].fields; packetInfo=packetInfo ? packetInfo : null; it(state + ",ServerBound," + packetName, callTestPacket(packetName, packetInfo, state, true)); } - for(packetName in packetFields[state].toClient) { - if(!packetFields[state].toClient.hasOwnProperty(packetName)) continue; - packetInfo = packetFields[state]["toClient"][packetName]; + for(packetName in packets[state].toClient) { + if(!packets[state].toClient.hasOwnProperty(packetName)) continue; + packetInfo = packets[state]["toClient"][packetName].fields; packetInfo=packetInfo ? packetInfo : null; it(state + ",ClientBound," + packetName, callTestPacket(packetName, packetInfo, state, false)); From 23b077b76f0548dfcd86f671e863aeda9968c755 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sat, 7 Nov 2015 01:25:12 +0100 Subject: [PATCH 2/3] fix compression in proxy, fix #292 --- examples/proxy/proxy.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/proxy/proxy.js b/examples/proxy/proxy.js index 2aa22b5..d5baf4e 100644 --- a/examples/proxy/proxy.js +++ b/examples/proxy/proxy.js @@ -122,10 +122,11 @@ srv.on('login', function(client) { targetClient.state + "." + meta.name + " :" + JSON.stringify(data)); } - if(!endedClient) + if(!endedClient) { client.write(meta.name, data); - if (meta.name === 'set_compression' || meta.name === 'compression') // Set compression - client.compressionThreshold = data.threshold; + if (meta.name === 'set_compression') // Set compression + client.compressionThreshold = data.threshold; + } } }); var bufferEqual = require('buffer-equal'); From 787f8d3423a8950b60603c36b6d13f8ea7ffbc04 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sat, 7 Nov 2015 22:06:49 +0100 Subject: [PATCH 3/3] fix gamemode3 in proxy : fix #146 * correctly generate the same uuidv3 than the vanilla server does in offline mode : fix #282 * remove "one player online mode" in the proxy : it doesn't make sense to identify all players as the user/passwd given in the cli. Now both servers in offline mode. --- examples/proxy/proxy.js | 10 ++-------- package.json | 5 +++-- src/createServer.js | 20 +++++++++++++++++++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/examples/proxy/proxy.js b/examples/proxy/proxy.js index d5baf4e..38d9a4c 100644 --- a/examples/proxy/proxy.js +++ b/examples/proxy/proxy.js @@ -2,7 +2,7 @@ var mc = require('../../'); var states = mc.states; function printHelpAndExit(exitCode) { - console.log("usage: node proxy.js [...] [] []"); + console.log("usage: node proxy.js [...] []"); console.log("options:"); console.log(" --dump name"); console.log(" print to stdout messages with the specified name."); @@ -35,8 +35,6 @@ process.argv.forEach(function(val, index, array) { var args = process.argv.slice(2); var host; var port = 25565; -var user; -var passwd; var version; var printAllNames = false; @@ -62,8 +60,6 @@ var printNameBlacklist = {}; } if(!(i + 2 <= args.length && args.length <= i + 4)) printHelpAndExit(1); host = args[i++]; - user = args[i++]; - passwd = args[i++]; version = args[i++]; })(); @@ -98,9 +94,7 @@ srv.on('login', function(client) { var targetClient = mc.createClient({ host: host, port: port, - username: user, - password: passwd, - 'online-mode': passwd != null ? true : false, + username: client.username, keepAlive:false, version:version }); diff --git a/package.json b/package.json index f7ae3ae..9cc1583 100644 --- a/package.json +++ b/package.json @@ -46,12 +46,13 @@ "minecraft-data": "^0.13.0", "node-uuid": "~1.4.1", "prismarine-nbt": "0.0.1", + "protodef": "0.2.0", "readable-stream": "^1.1.0", "superagent": "~0.10.0", "ursa-purejs": "0.0.3", "uuid": "^2.0.1", - "yggdrasil": "0.1.0", - "protodef":"0.2.0" + "uuid-1345": "^0.99.6", + "yggdrasil": "0.1.0" }, "optionalDependencies": { "ursa": "~0.9.1" diff --git a/src/createServer.js b/src/createServer.js index 814a1d4..543300a 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -4,6 +4,7 @@ var yggserver = require('yggdrasil').server({}); var states = require("./states"); var bufferEqual = require('buffer-equal'); var Server = require('./server'); +var UUID = require('uuid-1345'); module.exports=createServer; @@ -194,10 +195,27 @@ function createServer(options) { } } + + // https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/UUID.java#L163 + function javaUUID(s) + { + var hash = crypto.createHash("md5"); + hash.update(s, 'utf8'); + var buffer = hash.digest(); + buffer[6] = (buffer[6] & 0x0f) | 0x30; + buffer[8] = (buffer[8] & 0x3f) | 0x80; + return buffer; + } + + function nameToMcOfflineUUID(name) + { + return (new UUID(javaUUID("OfflinePlayer:"+name))).toString(); + } + function loginClient() { var isException = !!server.onlineModeExceptions[client.username.toLowerCase()]; if(onlineMode == false || isException) { - client.uuid = "0-0-0-0-0"; + client.uuid = nameToMcOfflineUUID(client.username); } client.write('compress', { threshold: 256 }); // Default threshold is 256 client.compressionThreshold = 256;