From b3bd773feae1ce540f09de4254c8a17648a11b19 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 24 May 2020 10:56:26 -0400 Subject: [PATCH] Switch out Cloudflare zlib for libdeflate. libdeflate is significantly faster than vanilla zlib, zlib-ng, and Cloudflare zlib. It is also MIT-licensed (so no licensing concerns). In addition, it simplifies a lot of the native code (something that's been tricky to get right). While we're at it, I have also taken the time to fine-time compression in Velocity in general. Thanks to this work, native compression only requires one JNI call, an improvement from the more than 2 (sometimes up to 5) that were possible before. This optimization also extends to the existing Java compressors, though they require potentially two JNI calls. --- .gitignore | 1 + native/compile-linux-amd64.sh | 21 ---- native/compile-linux.sh | 23 ++--- native/src/main/c/jni_zlib_common.c | 29 ------ native/src/main/c/jni_zlib_common.h | 6 -- native/src/main/c/jni_zlib_deflate.c | 88 ++-------------- native/src/main/c/jni_zlib_inflate.c | 94 +++++------------- .../compression/Java11VelocityCompressor.java | 5 +- .../compression/JavaVelocityCompressor.java | 7 +- .../LibdeflateVelocityCompressor.java | 87 ++++++++++++++++ .../compression/NativeVelocityCompressor.java | 89 ----------------- .../compression/NativeZlibDeflate.java | 13 +-- .../compression/NativeZlibInflate.java | 17 +--- .../compression/VelocityCompressor.java | 6 +- .../velocitypowered/natives/util/Natives.java | 9 +- .../resources/linux_x64/velocity-compress.so | Bin 91024 -> 48584 bytes .../compression/VelocityCompressorTest.java | 3 +- .../netty/MinecraftCompressDecoder.java | 17 ++-- .../netty/MinecraftCompressEncoder.java | 7 +- 19 files changed, 173 insertions(+), 349 deletions(-) delete mode 100755 native/compile-linux-amd64.sh delete mode 100644 native/src/main/c/jni_zlib_common.c delete mode 100644 native/src/main/c/jni_zlib_common.h create mode 100644 native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java delete mode 100644 native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java diff --git a/.gitignore b/.gitignore index 29577f5bb..1cf6c5d69 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,4 @@ plugins/ native/mbedtls native/zlib-ng native/zlib-cf +native/libdeflate diff --git a/native/compile-linux-amd64.sh b/native/compile-linux-amd64.sh deleted file mode 100755 index 135fcb539..000000000 --- a/native/compile-linux-amd64.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -if [ ! -d zlib-cf ]; then - echo "Cloning Cloudflare zlib..." - git clone -b gcc.amd64 https://github.com/cloudflare/zlib.git zlib-cf -fi - -echo "Compiling Cloudflare zlib..." -cd zlib-cf -CFLAGS="-fPIC -O3 -flto" AR=gcc-ar RANLIB=gcc-ranlib ./configure --static -make clean && make -cd .. - -# Modify as you need. -MBEDTLS_ROOT=mbedtls -CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack" -gcc $CFLAGS -Izlib-cf src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \ - src/main/c/jni_zlib_common.c zlib-cf/libz.a -Wl,-z,noexecstack -o src/main/resources/linux_x64/velocity-compress.so -gcc $CFLAGS -I $MBEDTLS_ROOT/include -shared $MBEDTLS_ROOT/library/aes.c $MBEDTLS_ROOT/library/aesni.c \ - $MBEDTLS_ROOT/library/platform.c $MBEDTLS_ROOT/library/platform_util.c src/main/c/jni_util.c src/main/c/jni_cipher.c \ - -o src/main/resources/linux_x64/velocity-cipher.so diff --git a/native/compile-linux.sh b/native/compile-linux.sh index 4b759df20..78072b00b 100755 --- a/native/compile-linux.sh +++ b/native/compile-linux.sh @@ -1,21 +1,20 @@ #!/bin/bash -if [ ! -d zlib-ng ]; then - echo "Cloning zlib-ng..." - git clone https://github.com/zlib-ng/zlib-ng.git +if [ ! -d libdeflate ]; then + echo "Cloning libdeflate..." + git clone https://github.com/ebiggers/libdeflate.git fi -echo "Compiling zlib-ng..." -cd zlib-ng -CFLAGS="-fPIC -O3" ./configure --zlib-compat --static -make clean && make +echo "Compiling libdeflate..." +cd libdeflate || exit +make cd .. # Modify as you need. MBEDTLS_ROOT=mbedtls -CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared" -gcc $CFLAGS -Izlib-ng src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \ - src/main/c/jni_zlib_common.c zlib-ng/libz.a -o src/main/resources/linux_x64/velocity-compress.so -gcc $CFLAGS -I $MBEDTLS_ROOT/include -shared $MBEDTLS_ROOT/library/aes.c $MBEDTLS_ROOT/library/aesni.c \ +CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack" +gcc $CFLAGS -Ilibdeflate src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \ + libdeflate/libdeflate.a -o src/main/resources/linux_x64/velocity-compress.so +gcc $CFLAGS -I $MBEDTLS_ROOT/include -shared $MBEDTLS_ROOT/library/aes.c $MBEDTLS_ROOT/library/aesni.c \ $MBEDTLS_ROOT/library/platform.c $MBEDTLS_ROOT/library/platform_util.c src/main/c/jni_util.c src/main/c/jni_cipher.c \ - -o src/main/resources/linux_x64/velocity-cipher.so \ No newline at end of file + -o src/main/resources/linux_x64/velocity-cipher.so diff --git a/native/src/main/c/jni_zlib_common.c b/native/src/main/c/jni_zlib_common.c deleted file mode 100644 index fa5a0cbab..000000000 --- a/native/src/main/c/jni_zlib_common.c +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include -#include -#include -#include "jni_util.h" - -void JNICALL -check_zlib_free(JNIEnv *env, z_stream *stream, bool deflate) -{ - int ret = deflate ? deflateEnd(stream) : inflateEnd(stream); - const char *msg = stream->msg; - free((void*) stream); - - switch (ret) { - case Z_OK: - break; - case Z_STREAM_ERROR: - if (msg == NULL) { - msg = "stream state inconsistent"; - } - // fall-through - case Z_DATA_ERROR: - if (msg == NULL) { - msg = "data was discarded"; - } - throwException(env, "java/lang/IllegalArgumentException", msg); - break; - } -} \ No newline at end of file diff --git a/native/src/main/c/jni_zlib_common.h b/native/src/main/c/jni_zlib_common.h deleted file mode 100644 index ff4bc9a80..000000000 --- a/native/src/main/c/jni_zlib_common.h +++ /dev/null @@ -1,6 +0,0 @@ -#include -#include -#include - -void JNICALL -check_zlib_free(JNIEnv *env, z_stream *stream, bool deflate); \ No newline at end of file diff --git a/native/src/main/c/jni_zlib_deflate.c b/native/src/main/c/jni_zlib_deflate.c index 6e05f7cae..7a84a5b04 100644 --- a/native/src/main/c/jni_zlib_deflate.c +++ b/native/src/main/c/jni_zlib_deflate.c @@ -2,56 +2,21 @@ #include #include #include -#include +#include #include "jni_util.h" -#include "jni_zlib_common.h" - -static jfieldID finishedID; -static jfieldID consumedID; - -JNIEXPORT void JNICALL -Java_com_velocitypowered_natives_compression_NativeZlibDeflate_initIDs(JNIEnv *env, jclass cls) -{ - finishedID = (*env)->GetFieldID(env, cls, "finished", "Z"); - consumedID = (*env)->GetFieldID(env, cls, "consumed", "I"); -} JNIEXPORT jlong JNICALL Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env, jobject obj, jint level) { - z_stream* stream = calloc(1, sizeof(z_stream)); - - if (stream == 0) { + struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level); + if (compressor == NULL) { // Out of memory! - throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream"); - return 0; - } - - int ret = deflateInit(stream, level); - if (ret == Z_OK) { - return (jlong) stream; - } else { - const char *zlib_msg = stream->msg; - free(stream); - switch (ret) { - case Z_MEM_ERROR: - throwException(env, "java/lang/OutOfMemoryError", "zlib init"); - break; - case Z_STREAM_ERROR: { - // Thanks Ken and Ritchie! - char message[32]; - snprintf(message, 32, "invalid level %d", level); - throwException(env, "java/lang/IllegalArgumentException", message); - break; - } - default: - throwException(env, "java/util/zip/DataFormatException", zlib_msg); - break; - } + throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate compressor"); return 0; } + return (jlong) compressor; } JNIEXPORT void JNICALL @@ -59,11 +24,10 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env, jobject obj, jlong ctx) { - z_stream* stream = (z_stream*) ctx; - check_zlib_free(env, stream, true); + libdeflate_free_compressor((struct libdeflate_compressor *) ctx); } -JNIEXPORT int JNICALL +JNIEXPORT jboolean JNICALL Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env, jobject obj, jlong ctx, @@ -73,38 +37,8 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *e jint destinationLength, jboolean finish) { - z_stream* stream = (z_stream*) ctx; - stream->next_in = (Bytef *) sourceAddress; - stream->next_out = (Bytef *) destinationAddress; - stream->avail_in = sourceLength; - stream->avail_out = destinationLength; - - int res = deflate(stream, finish ? Z_FINISH : Z_NO_FLUSH); - switch (res) { - case Z_STREAM_END: - // The stream has ended. - (*env)->SetBooleanField(env, obj, finishedID, JNI_TRUE); - // fall-through - case Z_OK: - // Not yet completed, but progress has been made. Tell Java how many bytes we've processed. - (*env)->SetIntField(env, obj, consumedID, sourceLength - stream->avail_in); - return destinationLength - stream->avail_out; - case Z_BUF_ERROR: - // This is not fatal. Just say we need more data. Usually this applies to the next_out buffer, - // which NativeVelocityCompressor will notice and will expand the buffer. - return 0; - default: - throwException(env, "java/util/zip/DataFormatException", stream->msg); - return 0; - } -} - -JNIEXPORT void JNICALL -Java_com_velocitypowered_natives_compression_NativeZlibDeflate_reset(JNIEnv *env, - jobject obj, - jlong ctx) -{ - z_stream* stream = (z_stream*) ctx; - int ret = deflateReset(stream); - assert(ret == Z_OK); + struct libdeflate_compressor *compressor = (struct libdeflate_compressor *) ctx; + size_t produced = libdeflate_zlib_compress(compressor, (void *) sourceAddress, sourceLength, + (void *) destinationAddress, destinationLength); + return (jlong) produced; } \ No newline at end of file diff --git a/native/src/main/c/jni_zlib_inflate.c b/native/src/main/c/jni_zlib_inflate.c index 64804e160..9a81f0db8 100644 --- a/native/src/main/c/jni_zlib_inflate.c +++ b/native/src/main/c/jni_zlib_inflate.c @@ -2,50 +2,21 @@ #include #include #include -#include +#include #include "jni_util.h" -#include "jni_zlib_common.h" - -static jfieldID finishedID; -static jfieldID consumedID; - -JNIEXPORT void JNICALL -Java_com_velocitypowered_natives_compression_NativeZlibInflate_initIDs(JNIEnv *env, jclass cls) -{ - finishedID = (*env)->GetFieldID(env, cls, "finished", "Z"); - consumedID = (*env)->GetFieldID(env, cls, "consumed", "I"); -} JNIEXPORT jlong JNICALL Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env, jobject obj) { - z_stream* stream = calloc(1, sizeof(z_stream)); - - if (stream == 0) { + struct libdeflate_decompressor *decompress = libdeflate_alloc_decompressor(); + if (decompress == NULL) { // Out of memory! - throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream"); + throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate decompressor"); return 0; } - int ret = inflateInit(stream); - if (ret == Z_OK) { - return (jlong) stream; - } else { - const char *zlib_msg = stream->msg; - free(stream); - switch (ret) { - case Z_MEM_ERROR: - throwException(env, "java/lang/OutOfMemoryError", "zlib init"); - return 0; - case Z_STREAM_ERROR: - throwException(env, "java/lang/IllegalArgumentException", "stream clobbered?"); - return 0; - default: - throwException(env, "java/util/zip/DataFormatException", zlib_msg); - return 0; - } - } + return (jlong) decompress; } JNIEXPORT void JNICALL @@ -53,51 +24,34 @@ Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env, jobject obj, jlong ctx) { - z_stream* stream = (z_stream*) ctx; - check_zlib_free(env, stream, false); + libdeflate_free_decompressor((struct libdeflate_decompressor *) ctx); } -JNIEXPORT int JNICALL +JNIEXPORT jboolean JNICALL Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env, jobject obj, jlong ctx, jlong sourceAddress, jint sourceLength, jlong destinationAddress, - jint destinationLength) + jint destinationLength, + jlong maximumSize) { - z_stream* stream = (z_stream*) ctx; - stream->next_in = (Bytef *) sourceAddress; - stream->next_out = (Bytef *) destinationAddress; - stream->avail_in = sourceLength; - stream->avail_out = destinationLength; + struct libdeflate_decompressor *decompress = (struct libdeflate_decompressor *) ctx; + enum libdeflate_result result = libdeflate_zlib_decompress(decompress, (void *) sourceAddress, + sourceLength, (void *) destinationAddress, destinationLength, NULL); - int res = inflate(stream, Z_PARTIAL_FLUSH); - switch (res) { - case Z_STREAM_END: - // The stream has ended - (*env)->SetBooleanField(env, obj, finishedID, JNI_TRUE); - // fall-through - case Z_OK: - // Not yet completed, but progress has been made. Tell Java how many bytes we've processed. - (*env)->SetIntField(env, obj, consumedID, sourceLength - stream->avail_in); - return destinationLength - stream->avail_out; - case Z_BUF_ERROR: - // This is not fatal. Just say we need more data. Usually this applies to the next_out buffer, - // which NativeVelocityCompressor will notice and will expand the buffer. - return 0; - default: - throwException(env, "java/util/zip/DataFormatException", stream->msg); - return 0; + switch (result) { + case LIBDEFLATE_SUCCESS: + // We are happy + return JNI_TRUE; + case LIBDEFLATE_BAD_DATA: + throwException(env, "java/util/zip/DataFormatException", "inflate data is bad"); + return JNI_FALSE; + case LIBDEFLATE_SHORT_OUTPUT: + case LIBDEFLATE_INSUFFICIENT_SPACE: + // These cases are the same for us. We expect the full uncompressed size to be known. + throwException(env, "java/util/zip/DataFormatException", "uncompressed size is inaccurate"); + return JNI_FALSE; } -} - -JNIEXPORT void JNICALL -Java_com_velocitypowered_natives_compression_NativeZlibInflate_reset(JNIEnv *env, - jobject obj, - jlong ctx) -{ - z_stream* stream = (z_stream*) ctx; - int ret = inflateReset(stream); - assert(ret == Z_OK); } \ No newline at end of file diff --git a/native/src/main/java/com/velocitypowered/natives/compression/Java11VelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/Java11VelocityCompressor.java index 999d12caa..bc6206242 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/Java11VelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/Java11VelocityCompressor.java @@ -54,7 +54,8 @@ public class Java11VelocityCompressor implements VelocityCompressor { } @Override - public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException { + public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) + throws DataFormatException { ensureNotDisposed(); // We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly. @@ -67,7 +68,7 @@ public class Java11VelocityCompressor implements VelocityCompressor { while (!inflater.finished() && inflater.getBytesRead() < source.readableBytes()) { if (!destination.isWritable()) { - ensureMaxSize(destination, max); + ensureMaxSize(destination, uncompressedSize); destination.ensureWritable(ZLIB_BUFFER_SIZE); } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java index 6293dd770..f2e557d1c 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java @@ -25,20 +25,21 @@ public class JavaVelocityCompressor implements VelocityCompressor { } @Override - public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException { + public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) + throws DataFormatException { ensureNotDisposed(); final int available = source.readableBytes(); this.setInflaterInput(source); if (destination.hasArray()) { - this.inflateDestinationIsHeap(destination, available, max); + this.inflateDestinationIsHeap(destination, available, uncompressedSize); } else { if (buf.length == 0) { buf = new byte[ZLIB_BUFFER_SIZE]; } while (!inflater.finished() && inflater.getBytesRead() < available) { - ensureMaxSize(destination, max); + ensureMaxSize(destination, uncompressedSize); int read = inflater.inflate(buf); destination.writeBytes(buf, 0, read); } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java new file mode 100644 index 000000000..73e9ff7fc --- /dev/null +++ b/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java @@ -0,0 +1,87 @@ +package com.velocitypowered.natives.compression; + +import com.google.common.base.Preconditions; +import com.velocitypowered.natives.util.BufferPreference; +import io.netty.buffer.ByteBuf; +import java.util.zip.DataFormatException; + +public class LibdeflateVelocityCompressor implements VelocityCompressor { + + public static final VelocityCompressorFactory FACTORY = LibdeflateVelocityCompressor::new; + + private final NativeZlibInflate inflate = new NativeZlibInflate(); + private final long inflateCtx; + private final NativeZlibDeflate deflate = new NativeZlibDeflate(); + private final long deflateCtx; + private boolean disposed = false; + + private LibdeflateVelocityCompressor(int level) { + int correctedLevel = level == -1 ? 6 : level; + if (correctedLevel > 12 || correctedLevel < 1) { + throw new IllegalArgumentException("Invalid compression level " + level); + } + + this.inflateCtx = inflate.init(); + this.deflateCtx = deflate.init(level == -1 ? 6 : level); + } + + @Override + public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) + throws DataFormatException { + ensureNotDisposed(); + source.memoryAddress(); + destination.memoryAddress(); + + // libdeflate recommends we work with a known uncompressed size - so we work strictly within + // those parameters. If the uncompressed size doesn't match the compressed size, then we will + // throw an exception from native code. + destination.ensureWritable(uncompressedSize); + + long sourceAddress = source.memoryAddress() + source.readerIndex(); + long destinationAddress = destination.memoryAddress() + destination.writerIndex(); + + inflate.process(inflateCtx, sourceAddress, source.readableBytes(), destinationAddress, + uncompressedSize); + destination.writerIndex(destination.writerIndex() + uncompressedSize); + } + + @Override + public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + ensureNotDisposed(); + source.memoryAddress(); + destination.memoryAddress(); + + while (true) { + long sourceAddress = source.memoryAddress() + source.readerIndex(); + long destinationAddress = destination.memoryAddress() + destination.writerIndex(); + + int produced = deflate.process(deflateCtx, sourceAddress, source.readableBytes(), + destinationAddress, destination.writableBytes()); + if (produced > 0) { + destination.writerIndex(destination.writerIndex() + produced); + return; + } + + // Insufficient room - enlarge the buffer. + destination.capacity(destination.capacity() * 2); + } + } + + private void ensureNotDisposed() { + Preconditions.checkState(!disposed, "Object already disposed"); + } + + @Override + public void dispose() { + if (!disposed) { + inflate.free(inflateCtx); + deflate.free(deflateCtx); + } + disposed = true; + } + + @Override + public BufferPreference preferredBufferType() { + return BufferPreference.DIRECT_REQUIRED; + } +} diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java deleted file mode 100644 index b932579ae..000000000 --- a/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.velocitypowered.natives.compression; - -import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFER_SIZE; -import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize; - -import com.google.common.base.Preconditions; -import com.velocitypowered.natives.util.BufferPreference; -import io.netty.buffer.ByteBuf; -import java.util.zip.DataFormatException; - -public class NativeVelocityCompressor implements VelocityCompressor { - - public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new; - - private final NativeZlibInflate inflate = new NativeZlibInflate(); - private final long inflateCtx; - private final NativeZlibDeflate deflate = new NativeZlibDeflate(); - private final long deflateCtx; - private boolean disposed = false; - - private NativeVelocityCompressor(int level) { - this.inflateCtx = inflate.init(); - this.deflateCtx = deflate.init(level); - } - - @Override - public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException { - ensureNotDisposed(); - source.memoryAddress(); - destination.memoryAddress(); - - while (!inflate.finished && source.isReadable()) { - if (!destination.isWritable()) { - ensureMaxSize(destination, max); - destination.ensureWritable(ZLIB_BUFFER_SIZE); - } - int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(), - source.readableBytes(), destination.memoryAddress() + destination.writerIndex(), - destination.writableBytes()); - source.readerIndex(source.readerIndex() + inflate.consumed); - destination.writerIndex(destination.writerIndex() + produced); - } - - inflate.reset(inflateCtx); - inflate.consumed = 0; - inflate.finished = false; - } - - @Override - public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { - ensureNotDisposed(); - source.memoryAddress(); - destination.memoryAddress(); - - while (!deflate.finished) { - if (!destination.isWritable()) { - destination.ensureWritable(ZLIB_BUFFER_SIZE); - } - int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(), - source.readableBytes(), - destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(), - true); - source.readerIndex(source.readerIndex() + deflate.consumed); - destination.writerIndex(destination.writerIndex() + produced); - } - - deflate.reset(deflateCtx); - deflate.consumed = 0; - deflate.finished = false; - } - - private void ensureNotDisposed() { - Preconditions.checkState(!disposed, "Object already disposed"); - } - - @Override - public void dispose() { - if (!disposed) { - inflate.free(inflateCtx); - deflate.free(deflateCtx); - } - disposed = true; - } - - @Override - public BufferPreference preferredBufferType() { - return BufferPreference.DIRECT_REQUIRED; - } -} diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java index 1114cab49..eb89412cb 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java @@ -5,21 +5,10 @@ package com.velocitypowered.natives.compression; */ class NativeZlibDeflate { - boolean finished; - int consumed; - native long init(int level); native long free(long ctx); native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, - int destinationLength, boolean finish); - - native void reset(long ctx); - - static { - initIDs(); - } - - private static native void initIDs(); + int destinationLength); } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java index 7eabd9a05..fc6e9787f 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java @@ -1,25 +1,16 @@ package com.velocitypowered.natives.compression; +import java.util.zip.DataFormatException; + /** * Represents a native interface for zlib's inflate functions. */ class NativeZlibInflate { - boolean finished; - int consumed; - native long init(); native long free(long ctx); - native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, - int destinationLength); - - native void reset(long ctx); - - static { - initIDs(); - } - - private static native void initIDs(); + native boolean process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, + int destinationLength) throws DataFormatException; } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java index 839b64569..09cad102c 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java @@ -6,10 +6,12 @@ import io.netty.buffer.ByteBuf; import java.util.zip.DataFormatException; /** - * Provides an interface to inflate and deflate {@link ByteBuf}s using zlib. + * Provides an interface to inflate and deflate {@link ByteBuf}s using zlib or a compatible + * implementation. */ public interface VelocityCompressor extends Disposable, Native { - void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException; + void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) + throws DataFormatException; void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; } diff --git a/native/src/main/java/com/velocitypowered/natives/util/Natives.java b/native/src/main/java/com/velocitypowered/natives/util/Natives.java index e63c87c20..ea71c75ce 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/Natives.java +++ b/native/src/main/java/com/velocitypowered/natives/util/Natives.java @@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableList; import com.velocitypowered.natives.NativeSetupException; import com.velocitypowered.natives.compression.Java11VelocityCompressor; import com.velocitypowered.natives.compression.JavaVelocityCompressor; -import com.velocitypowered.natives.compression.NativeVelocityCompressor; +import com.velocitypowered.natives.compression.LibdeflateVelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressorFactory; import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.NativeVelocityCipher; @@ -64,10 +64,11 @@ public class Natives { ImmutableList.of( new NativeCodeLoader.Variant<>(NativeConstraints.MACOS, copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)", - NativeVelocityCompressor.FACTORY), + LibdeflateVelocityCompressor.FACTORY), new NativeCodeLoader.Variant<>(NativeConstraints.LINUX, - copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)", - NativeVelocityCompressor.FACTORY), + copyAndLoadNative("/linux_x64/velocity-compress.so"), + "libdeflate (Linux amd64)", + LibdeflateVelocityCompressor.FACTORY), new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> { }, "Java 11", () -> Java11VelocityCompressor.FACTORY), new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> { diff --git a/native/src/main/resources/linux_x64/velocity-compress.so b/native/src/main/resources/linux_x64/velocity-compress.so index c05574805d02c4cade49c3d73799199a2690b475..d72a66bfd2d7f84c71b3263f9a4af3535874f58a 100755 GIT binary patch literal 48584 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 literal 91024 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 { - private static final int SOFT_MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB + private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 16 * 1024 * 1024; // 16MiB + private static final int UNCOMPRESSED_CAP = + Boolean.getBoolean("velocity.increased-compression-cap") + ? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE; + private final int threshold; private final VelocityCompressor compressor; @@ -28,20 +32,21 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { int claimedUncompressedSize = ProtocolUtils.readVarInt(in); if (claimedUncompressedSize == 0) { - // Strip the now-useless uncompressed size, this message is already uncompressed. + // This message is not compressed. out.add(in.retainedSlice()); return; } checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than" + " threshold %s", claimedUncompressedSize, threshold); - int allowedMax = Math.min(claimedUncompressedSize, HARD_MAXIMUM_UNCOMPRESSED_SIZE); - int initialCapacity = Math.min(claimedUncompressedSize, SOFT_MAXIMUM_UNCOMPRESSED_SIZE); + checkFrame(claimedUncompressedSize <= UNCOMPRESSED_CAP, + "Uncompressed size %s exceeds hard threshold of %s", claimedUncompressedSize, + UNCOMPRESSED_CAP); ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); - ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity); + ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize); try { - compressor.inflate(compatibleIn, uncompressed, allowedMax); + compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); out.add(uncompressed); } catch (Exception e) { uncompressed.release(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java index 0c9105e92..c36bfd8a3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java @@ -38,8 +38,11 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder { @Override protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { - int initialBufferSize = msg.readableBytes() <= threshold ? msg.readableBytes() + 1 : - msg.readableBytes() / 3; + // Follow the advice of https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103 + // here for compression. The maximum buffer size if the data compresses well (which is almost + // always the case) is one less the input buffer. + int offset = msg.readableBytes() < threshold ? 1 : -1; + int initialBufferSize = msg.readableBytes() + offset; return MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, initialBufferSize); }