From cfa30d9e1d81255a76729be1a791a4c0f9036ca1 Mon Sep 17 00:00:00 2001 From: Will Franzen Date: Tue, 30 Dec 2014 02:08:21 -0600 Subject: [PATCH 1/3] Add packets to protocol and threshold to client --- lib/client.js | 1 + lib/protocol.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/lib/client.js b/lib/client.js index 23d5576..bb9b501 100644 --- a/lib/client.js +++ b/lib/client.js @@ -30,6 +30,7 @@ function Client(isServer) { this.encryptionEnabled = false; this.cipher = null; this.decipher = null; + this.compressionThreshold = -1; this.packetsToParse = {}; this.on('newListener', function(event, listener) { var direction = this.isServer ? 'toServer' : 'toClient'; diff --git a/lib/protocol.js b/lib/protocol.js index 0d0cece..87cb3d3 100644 --- a/lib/protocol.js +++ b/lib/protocol.js @@ -58,6 +58,9 @@ var packets = { success: {id: 0x02, fields: [ { name: "uuid", type: "string" }, { name: "username", type: "string" } + ]}, + compress: { id: 0x03, fields: [ + { name: "threshold", type: "varint"} ]} }, toServer: { @@ -534,6 +537,9 @@ var packets = { ]}, kick_disconnect: {id: 0x40, fields: [ { name: "reason", type: "string" } + ]}, + compress: { id: 0x46, fields: [ + { name: "threshold", type: "varint"} ]} }, toServer: { From b73884e2365ea444bc57fd5f62821760352d8c87 Mon Sep 17 00:00:00 2001 From: Will Franzen Date: Tue, 30 Dec 2014 02:15:08 -0600 Subject: [PATCH 2/3] Add listener for compression request --- index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/index.js b/index.js index 5e117d7..a63a88c 100644 --- a/index.js +++ b/index.js @@ -141,6 +141,8 @@ function createServer(options) { hash = crypto.createHash("sha1"); hash.update(serverId); client.once([states.LOGIN, 0x01], onEncryptionKeyResponse); + client.once([states.LOGIN, 0x03], onCompressionRequest); + client.on( [states.PLAY, 0x46], onCompressionRequest); client.write(0x01, { serverId: serverId, publicKey: client.publicKey, @@ -193,6 +195,10 @@ function createServer(options) { } } + function onCompressionRequest(packet) { + client.compressionThreshold = packet.threshold; + } + function loginClient() { var isException = !!server.onlineModeExceptions[client.username.toLowerCase()]; if (onlineMode == false || isException) { From 675d358dec51d1d3e56068768062881c51b681d1 Mon Sep 17 00:00:00 2001 From: Will Franzen Date: Tue, 30 Dec 2014 02:55:04 -0600 Subject: [PATCH 3/3] Finish adding compression to pipeline (see note) It's 3 AM and I know theres a pretty big chance none of this code makes sense. Everything needs to be tested, as well as I need to figure out if encryption comes after compression or not. --- lib/client.js | 35 ++++++++++++++++++++++++++++------- lib/protocol.js | 15 +++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/lib/client.js b/lib/client.js index bb9b501..c9f20dd 100644 --- a/lib/client.js +++ b/lib/client.js @@ -4,6 +4,7 @@ var net = require('net') , protocol = require('./protocol') , dns = require('dns') , createPacketBuffer = protocol.createPacketBuffer + , compressPacketBuffer = protocol.compressPacketBuffer , parsePacket = protocol.parsePacket , states = protocol.states , debug = protocol.debug @@ -154,18 +155,38 @@ Client.prototype.write = function(packetId, params) { packetId = packetId[1]; } + // TODO: Which comes first, encryption or compression? + + var finishWriting = function(buffer) { + debug("writing packetId " + packetId + " (0x" + packetId.toString(16) + ")"); + debug(params); + var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; + this.socket.write(out); + return true; + } + var buffer = createPacketBuffer(packetId, this.state, params, this.isServer); - debug("writing packetId " + packetId + " (0x" + packetId.toString(16) + ")"); - debug(params); - var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; - this.socket.write(out); - return true; + if(this.compressionThreshold != -1 && buffer.length > this.compressionThreshold) { + compressPacketBuffer(buffer, finishWriting); + } else { + finishWriting(buffer); + } + }; Client.prototype.writeRaw = function(buffer, shouldEncrypt) { if (shouldEncrypt === null) { shouldEncrypt = true; } - var out = (shouldEncrypt && this.encryptionEnabled) ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; - this.socket.write(out); + + var finishWriting = function(buffer) { + var out = (shouldEncrypt && this.encryptionEnabled) ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; + this.socket.write(out); + }; + + if(this.compressionThreshold != -1 && buffer.length > this.compressionThreshold) { + compressPacketBuffer(buffer, finishWriting); + } else { + finishWriting(buffer); + } }; diff --git a/lib/protocol.js b/lib/protocol.js index 87cb3d3..ec417c9 100644 --- a/lib/protocol.js +++ b/lib/protocol.js @@ -1,5 +1,6 @@ var assert = require('assert'); var util = require('util'); +var zlib = require('zlib'); var STRING_MAX_LENGTH = 240; var SRV_STRING_MAX_LENGTH = 32767; @@ -1289,6 +1290,19 @@ function createPacketBuffer(packetId, state, params, isServer) { return buffer; } +function compressPacketBuffer(buffer, callback) { + var dataLength = buffer.length; + var packetLength = dataLength + sizeOfVarInt(dataLength); + var data = zlib.deflateRaw(buffer, function(compressedBuffer) { + var size = sizeOfVarInt(packetLength) + sizeOfVarInt(packetLength) + compressedBuffer.length; + var packetBuffer = new Buffer(size); + var offset = writeVarInt(packetLength, packetBuffer, 0); + offset = writeVarInt(dataLength, packetBuffer, offset); + writeVarInt(compressedBuffer, packetBuffer, offset); + callback(packetBuffer); + }); +} + function parsePacket(buffer, state, isServer, packetsToParse) { if (state == null) state = states.PLAY; var cursor = 0; @@ -1365,6 +1379,7 @@ module.exports = { sessionVersion: 13, parsePacket: parsePacket, createPacketBuffer: createPacketBuffer, + compressPacketBuffer: compressPacketBuffer, STRING_MAX_LENGTH: STRING_MAX_LENGTH, packetIds: packetIds, packetNames: packetNames,