From fb4e6fd8f1478e88c756d58ab093f9f0535970f4 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 1 Aug 2018 22:22:09 -0400 Subject: [PATCH] =?UTF-8?q?JNI=20native=20zlib=20compression=20?= =?UTF-8?q?=F0=9F=94=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- native/README.md | 17 +++ native/build.gradle | 2 + native/compile-linux.sh | 4 + native/compile-osx.sh | 5 + native/src/main/c/jni_util.c | 12 ++ native/src/main/c/jni_util.h | 4 + native/src/main/c/jni_zlib_deflate.c | 124 ++++++++++++++++++ native/src/main/c/jni_zlib_inflate.c | 120 +++++++++++++++++ .../compression/JavaVelocityCompressor.java | 2 +- .../compression/NativeVelocityCompressor.java | 67 ++++++++++ .../compression/NativeZlibDeflate.java | 24 ++++ .../compression/NativeZlibInflate.java | 23 ++++ .../compression/VelocityCompressor.java | 2 + .../natives/util/NativeCodeLoader.java | 92 +++++++++++++ .../velocitypowered/natives/util/Natives.java | 48 +++++++ .../resources/linux_x64/velocity-compress.so | Bin 0 -> 13968 bytes .../resources/macosx/velocity-compress.dylib | Bin 0 -> 14428 bytes .../compression/VelocityCompressorTest.java | 56 ++++++++ .../proxy/VelocityServer.java | 3 + .../proxy/connection/MinecraftConnection.java | 4 +- .../netty/MinecraftCompressDecoder.java | 1 + .../netty/MinecraftCompressEncoder.java | 3 +- 22 files changed, 610 insertions(+), 3 deletions(-) create mode 100644 native/README.md create mode 100755 native/compile-linux.sh create mode 100755 native/compile-osx.sh create mode 100644 native/src/main/c/jni_util.c create mode 100644 native/src/main/c/jni_util.h create mode 100644 native/src/main/c/jni_zlib_deflate.c create mode 100644 native/src/main/c/jni_zlib_inflate.c create mode 100644 native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java create mode 100644 native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java create mode 100644 native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java create mode 100644 native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java create mode 100644 native/src/main/java/com/velocitypowered/natives/util/Natives.java create mode 100755 native/src/main/resources/linux_x64/velocity-compress.so create mode 100755 native/src/main/resources/macosx/velocity-compress.dylib create mode 100644 native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java diff --git a/native/README.md b/native/README.md new file mode 100644 index 000000000..0cd280f87 --- /dev/null +++ b/native/README.md @@ -0,0 +1,17 @@ +# velocity-natives + +This directory contains native acceleration code for Velocity, along with +traditional Java fallbacks. + +## Compression + +* **Supported platforms**: macOS 10.13, Linux amd64 (precompiled binary is built on Debian 9 with JDK 8) +* **Rationale**: Using a native zlib wrapper, we can avoid multiple trips into Java just to copy memory around. + +## Encryption + +* No natives available yet, this will use the support inside your Java install. + +## OS support + +If you are on Alpine Linux, `apk add libc6-compat` will enable native support. \ No newline at end of file diff --git a/native/build.gradle b/native/build.gradle index 13647ccf9..089fba5e9 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -5,4 +5,6 @@ plugins { dependencies { compile "com.google.guava:guava:${guavaVersion}" compile "io.netty:netty-buffer:${nettyVersion}" + testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" + testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" } \ No newline at end of file diff --git a/native/compile-linux.sh b/native/compile-linux.sh new file mode 100755 index 000000000..d3d7abab3 --- /dev/null +++ b/native/compile-linux.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Modify as you need. +gcc -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -shared -lz src/main/c/*.c -o src/main/resources/linux_x64/velocity-compress.so \ No newline at end of file diff --git a/native/compile-osx.sh b/native/compile-osx.sh new file mode 100755 index 000000000..d7a53149f --- /dev/null +++ b/native/compile-osx.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Modify as you need. +export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home +clang -I$JAVA_HOME/include/ -I$JAVA_HOME/include/darwin/ -shared -lz src/main/c/*.c -o src/main/resources/macosx/velocity-compress.dylib \ No newline at end of file diff --git a/native/src/main/c/jni_util.c b/native/src/main/c/jni_util.c new file mode 100644 index 000000000..e5356c93f --- /dev/null +++ b/native/src/main/c/jni_util.c @@ -0,0 +1,12 @@ +#include +#include "jni_util.h" + +JNIEXPORT void JNICALL +throwException(JNIEnv *env, const char *type, const char *msg) +{ + jclass klazz = (*env)->FindClass(env, type); + + if (klazz != 0) { + (*env)->ThrowNew(env, klazz, msg); + } +} \ No newline at end of file diff --git a/native/src/main/c/jni_util.h b/native/src/main/c/jni_util.h new file mode 100644 index 000000000..8938b26c8 --- /dev/null +++ b/native/src/main/c/jni_util.h @@ -0,0 +1,4 @@ +#include + +JNIEXPORT void JNICALL +throwException(JNIEnv *env, const char *type, const char *msg); \ 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 new file mode 100644 index 000000000..4ef0fe34b --- /dev/null +++ b/native/src/main/c/jni_zlib_deflate.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include "jni_util.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) { + // Out of memory! + throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream"); + return 0; + } + + char *msg; + int ret = deflateInit(stream, level); + + switch (ret) { + case Z_OK: + return (jlong) stream; + case Z_MEM_ERROR: + free(stream); + throwException(env, "java/lang/OutOfMemoryError", "zlib init"); + return 0; + case Z_STREAM_ERROR: + free(stream); + char message[32]; + snprintf(message, 32, "invalid level %d", level); + throwException(env, "java/lang/IllegalArgumentException", message); + return 0; + default: + msg = stream->msg; + free(stream); + throwException(env, "java/util/zip/DataFormatException", msg); + return 0; + } +} + +JNIEXPORT void JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env, + jobject obj, + jlong ctx) +{ + z_stream* stream = (z_stream*) ctx; + int ret = deflateEnd(stream); + char *msg = stream->msg; + free((void*) ctx); + + switch (ret) { + case Z_OK: + break; + case Z_STREAM_ERROR: + if (msg == NULL) { + msg = "stream state inconsistent"; + } + case Z_DATA_ERROR: + if (msg == NULL) { + msg = "data was discarded"; + } + throwException(env, "java/lang/IllegalArgumentException", msg); + break; + } +} + +JNIEXPORT int JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env, + jobject obj, + jlong ctx, + jlong sourceAddress, + jint sourceLength, + jlong destinationAddress, + jint destinationLength, + jboolean flush) +{ + 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, flush ? 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); +} \ 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 new file mode 100644 index 000000000..0695e4c91 --- /dev/null +++ b/native/src/main/c/jni_zlib_inflate.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include "jni_util.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) { + // Out of memory! + throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream"); + return 0; + } + + char *msg; + int ret = inflateInit(stream); + + switch (ret) { + case Z_OK: + return (jlong) stream; + case Z_MEM_ERROR: + free(stream); + throwException(env, "java/lang/OutOfMemoryError", "zlib init"); + return 0; + case Z_STREAM_ERROR: + free(stream); + throwException(env, "java/lang/IllegalArgumentException", "stream clobbered?"); + return 0; + default: + msg = stream->msg; + free(stream); + throwException(env, "java/util/zip/DataFormatException", msg); + return 0; + } +} + +JNIEXPORT void JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env, + jobject obj, + jlong ctx) +{ + z_stream* stream = (z_stream*) ctx; + int ret = inflateEnd(stream); + char *msg = stream->msg; + free((void*) ctx); + + switch (ret) { + case Z_OK: + break; + case Z_STREAM_ERROR: + if (msg == NULL) { + msg = "stream state inconsistent"; + } + case Z_DATA_ERROR: + if (msg == NULL) { + msg = "data was discarded"; + } + throwException(env, "java/lang/IllegalArgumentException", msg); + break; + } +} + +JNIEXPORT int JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env, + jobject obj, + jlong ctx, + jlong sourceAddress, + jint sourceLength, + jlong destinationAddress, + jint destinationLength) +{ + 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 = 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; + } +} + +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/JavaVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java index 6f04a4aec..e66ca01ee 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java @@ -16,7 +16,7 @@ public class JavaVelocityCompressor implements VelocityCompressor { public JavaVelocityCompressor() { this.deflater = new Deflater(); this.inflater = new Inflater(); - this.buf = new byte[8192]; + this.buf = new byte[ZLIB_BUFFER_SIZE]; } @Override diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java new file mode 100644 index 000000000..f14f016ed --- /dev/null +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java @@ -0,0 +1,67 @@ +package com.velocitypowered.natives.compression; + +import io.netty.buffer.ByteBuf; + +import java.util.zip.DataFormatException; + +public class NativeVelocityCompressor implements VelocityCompressor { + private final NativeZlibInflate inflate = new NativeZlibInflate(); + private final long inflateCtx; + private final NativeZlibDeflate deflate = new NativeZlibDeflate(); + private final long deflateCtx; + private boolean disposed = false; + + public NativeVelocityCompressor() { + this.inflateCtx = inflate.init(); + this.deflateCtx = deflate.init(7); + } + + @Override + public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + source.memoryAddress(); + destination.memoryAddress(); + + while (!inflate.finished && source.isReadable()) { + if (!destination.isWritable()) { + 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 { + 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(), !source.isReadable()); + source.readerIndex(source.readerIndex() + deflate.consumed); + destination.writerIndex(destination.writerIndex() + produced); + } + + deflate.reset(deflateCtx); + deflate.consumed = 0; + deflate.finished = false; + } + + @Override + public void dispose() { + if (!disposed) { + inflate.free(inflateCtx); + deflate.free(deflateCtx); + } + disposed = true; + } +} diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java new file mode 100644 index 000000000..7eb92c570 --- /dev/null +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java @@ -0,0 +1,24 @@ +package com.velocitypowered.natives.compression; + +/** + * Represents a native interface for zlib's deflate functions. + */ +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 flush); + + native void reset(long ctx); + + static { + initIDs(); + } + + static native void initIDs(); +} diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java new file mode 100644 index 000000000..310b4c3b9 --- /dev/null +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java @@ -0,0 +1,23 @@ +package com.velocitypowered.natives.compression; + +/** + * Represents a native interface for zlib's inflate functions. + */ +public 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(); + } + + static native void initIDs(); +} 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 c8056a54e..18dce695d 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java @@ -6,6 +6,8 @@ import io.netty.buffer.ByteBuf; import java.util.zip.DataFormatException; public interface VelocityCompressor extends Disposable { + int ZLIB_BUFFER_SIZE = 8192; + void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException; void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; diff --git a/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java b/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java new file mode 100644 index 000000000..6119b9f42 --- /dev/null +++ b/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java @@ -0,0 +1,92 @@ +package com.velocitypowered.natives.util; + +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +public class NativeCodeLoader { + private final List> variants; + private Variant selected; + + public NativeCodeLoader(List> variants) { + this.variants = ImmutableList.copyOf(variants); + } + + public Supplier supply() { + if (selected == null) { + selected = select(); + } + return selected.supplier; + } + + private Variant select() { + for (Variant variant : variants) { + T got = variant.get(); + if (got == null) { + continue; + } + return variant; + } + throw new IllegalArgumentException("Can't find any suitable variants"); + } + + public String getLoadedVariant() { + for (Variant variant : variants) { + T got = variant.get(); + if (got == null) { + continue; + } + return variant.name; + } + throw new IllegalArgumentException("Can't find any suitable variants"); + } + + static class Variant { + private boolean available; + private final Runnable setup; + private final String name; + private final Supplier supplier; + private boolean hasBeenSetup = false; + + Variant(BooleanSupplier available, Runnable setup, String name, Supplier supplier) { + this.available = available.getAsBoolean(); + this.setup = setup; + this.name = name; + this.supplier = supplier; + } + + public boolean setup() { + if (available && !hasBeenSetup) { + try { + setup.run(); + hasBeenSetup = true; + } catch (Exception e) { + //logger.error("Unable to set up {}", name, e); + available = false; + } + } + return hasBeenSetup; + } + + public T get() { + if (!hasBeenSetup) { + setup(); + } + + if (available) { + return supplier.get(); + } + + return null; + } + } + + public static final BooleanSupplier MACOS = () -> System.getProperty("os.name").equalsIgnoreCase("Mac OS X") && + System.getProperty("os.arch").equals("x86_64"); + public static final BooleanSupplier LINUX = () -> System.getProperties().getProperty("os.name").equalsIgnoreCase("Linux") && + System.getProperty("os.arch").equals("amd64"); + public static final BooleanSupplier MAC_AND_LINUX = () -> MACOS.getAsBoolean() || LINUX.getAsBoolean(); + public static final BooleanSupplier ALWAYS = () -> true; +} diff --git a/native/src/main/java/com/velocitypowered/natives/util/Natives.java b/native/src/main/java/com/velocitypowered/natives/util/Natives.java new file mode 100644 index 000000000..86fb3651f --- /dev/null +++ b/native/src/main/java/com/velocitypowered/natives/util/Natives.java @@ -0,0 +1,48 @@ +package com.velocitypowered.natives.util; + +import com.google.common.collect.ImmutableList; +import com.velocitypowered.natives.compression.JavaVelocityCompressor; +import com.velocitypowered.natives.compression.NativeVelocityCompressor; +import com.velocitypowered.natives.compression.VelocityCompressor; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +public class Natives { + private Natives() { + throw new AssertionError(); + } + + private static Runnable copyAndLoadNative(String path) { + return () -> { + try { + Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.'))); + Files.copy(Natives.class.getResourceAsStream(path), tempFile, StandardCopyOption.REPLACE_EXISTING); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + Files.deleteIfExists(tempFile); + } catch (IOException ignored) { + // Well, it doesn't matter... + } + })); + System.load(tempFile.toAbsolutePath().toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + } + + public static final NativeCodeLoader compressor = new NativeCodeLoader<>( + ImmutableList.of( + new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS, + copyAndLoadNative("/macosx/velocity-compress.dylib"), "native compression (macOS)", + NativeVelocityCompressor::new), + new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX, + copyAndLoadNative("/linux_x64/velocity-compress.so"), "native compression (Linux amd64)", + NativeVelocityCompressor::new), + new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java compression", JavaVelocityCompressor::new) + ) + ); +} diff --git a/native/src/main/resources/linux_x64/velocity-compress.so b/native/src/main/resources/linux_x64/velocity-compress.so new file mode 100755 index 0000000000000000000000000000000000000000..be9509931035f4b29515b26e02af503b246003c6 GIT binary patch literal 13968 zcmeHOeQ;dWb-ycFwyI)mu83{L*lfVj)2nHMWhwRP!YiD z@7#OO+qX})3+~qK zm5NKPjt4{C9oe_5VWS+M9i2U5=5K%Rg;Q2Q3eebc(hIPlz|Nh=pkB`4z{pyeYyV`hf?TgQU<&!TyHJUpJ6D3O=j*}nm zat#QEn+W_aI9^QtrV@6pLI;b<8ztyxO4%tvUs-|N(h~k#CG0$2!q49+VgK7D z>~AO`-wyf>__)h32rERDm|CtlcWQwN*sm4WiZR2`6a}7@{#S_;S1Npu=GIAns>C9i zlqicKM%~cz{V|APD6_ER!$RbsCm{5nlP zqv_GBl!32mJ7;yf?tU$o_OB3=%e0^d)Z7ZOTSXh{C-5q8ylnC{QlU69v-O&<>$3oqK1Cfy8 z73Y|}Ljf`nh=h+oaOWX&x35OhI&JTRyZCLI|GLT zW^X)d9tuU`yA2hCW(3Lgq3i8q)CC6f>~J0)!&jJ1aPA^|I8!fC6mHThwf z5xs#3To6HJx($|1k&F!{!ZE9Fp$+sULLs*7iv<^IZZHw=MNHV@Ld_w*A&ZUemWXN6 zz+r0i1;UZbwci%|P)5mVMue43H9VI&d)S~H6>V?1Hl{`eGryOX z(MDm0f&nXlReT_sREroSOspO4ZQEMR4c-mjChQ1U{Q8?}3X>%LRVF+i;O{ZHcPW+B z%J3Cr={#su_&ti>e@^QCd{)0_F?~?eM+`&iF`e=As}-HzWt@IaIp|BJruxb{=z7bO zj2Q==-*t#F>!9ncP|DY9f9M@WZ<&&AIOxvj`^^rz4uOueu+Or zIE5_NCGqbOPE#z`Eb-HXQ|NMy68}5GDP%cA;x_>AI}ThH|M=!_fZ!jmAkgDqJL^xs z;vb!@K2(P={dk26(-n8mP4(5RZWZG0rzt9Z-kQ~4VEus9Z`!ByFOmLJm&*O=8UIt+ zEq>1#|8tkDdKmkLHddvKDSfxSk_x{Piw=q zFpQ9A;vOpRXaK@Lf#8~^s>*@f=e#J_1vy|{GCJKb#0Fc6OUTKJYWBbwA zDKvI6-Sg;^gBYLh(mc&R^4{E>?RaVpjGuyyQ_@t^83O6E+24TBDM*Xl&+(N>JxV@2 zPDUX6gcgcHDDAtL@m(C9y=3jc0$Msc`>s`;`y$CfA$L+&$mGPkXje6MpK9z*ZRas? zRAVC!`QxBwdLH$U`zF%Qq1Dep`bmQX(r2Y~_Akidc+Z5g+O$KP>t}P*w~`Zb;L#>ghY=L`f~f+R9?R=SP4jGfc_!keT!T@up!(I-C6(tWA54 z=ZBTt%1_R8$7E~T_k;Fy$3(_gKL#gBU3$|qi8{Jmi{4U8{F54;WN4 zMvk5n){@h-D@|9N#TZHY0J%XXbU^kH!mrNS!c9uJSqo2rqXw|(GW?EIl*^E&`v$G` zH>_n3D$REplw!;99f+4$hFxI2?=pOmrY-N_a$)9}l3#`iNjjTd0c#&@8OqT8atn}x z`6R6v3jeA9;gR}3SKRU{nrgq?ihFTcUgv|ZxId+56kJ;l*W{4egY&*C?tax1d&T`b z%$z=~R@_&}Y)$l|shE{N@=LE|BPlB!X*d!dY-q(lpP$;s*{;)?~~7r}*oW>#sEaeDt>C@42r0cFK{{ z8D{JatPa3|i)!%=y*W2G3H&+08NfTRdTOzGd>*Tf@@jS1sk#6gu~`oSjshMBoB=!u zSb-<{B;bR9Gk|}EHyO^4>-3}W9N8^AL$#jks+Lre?i#eS3ZIz^b8}OGqPli_b^Xqo zWrr(A#Fmw}|K=@gZXh!0x8m~%e0HAiNcS#$9zq{vj&y5v?Xj|!<%`R@(Sovl5BNU? zKME*p{(#MY5adsw&oU>-{PNp9BtZ6`z-K#RA#;h$FMGh&{~7rI2)@jHGQa#9TmN0~ zY46d5@Re!#z*yIWqY2mBfhKcV494YzCfISux4@c0+fmk6Fmh4f0n^R19R|93~?mt)q?@I-eg zl#69zQpeLlhCF`9ElQvN`=zp9Ek^2`7F4}l)Y$hPoZNE!B0R+lz`-aNSK9l4gA9LG z*z3QLeibep7*3L`m*efL*!(OPb%KAFa4MAx+#b;Dy^wyb;CWU^ub=mOHu1{^?`C;v zTE~mT>vMUEs!@17V!r=mp{z*!X;htAM zNuNJ&J3$vk)=@y)88ZwLuuD|Z=g;R+(CI#e``*Jdn(n^$P>l!Q3fpSFVo3V@dmas- z8+kFi_L&lP_JCgOUdTQvKYyR0eU|)p-xqnfg#CXgK|fc5{&ETWw4_%F_x+3pJb=j` z_x+5drn~R2oR)MuEOzDD67+uq-LR$dl~+s1zbWZe!hJvH(~Bt1^Y6!O0iFDJ-)o`# zZ?SQ|U)yodH%b&2lmC{s@4lzdirJ|Fy_o!3DL?;y&mibGL?9Y6gQ;kA7%C1-UVwcFR-eW$s-r?aKIZC9shdT-sd;g-CGd_JdyY$J?p-@UcN zXZku@=}wW157&dqxH%Ar1?ehL>z$ojJK9|QiCF|=Pa-p$9G)t3dfLvg2Tb;Yjo>2~xqq6>pXF9lwv2RxB&4+`!U z7LK0qCWoU|-~gbNP;h{2vA7lT_Qz7*1F3K%xGo$N5*r942ZT2`9D}BURzh(Og%Whf zumCZ^ON1f;QqZ-*h$Xz_obaOR?T@2kg@*7gm#;Swm-n)~p#i-`4+LR^F{P^ZYNf&T zgS`+#Gl6Kh7e2%-G7kz}^77)2m+<1LLBFHePpE}e=zKuA2fVJx!sl3qYc!n!9>WEN z&Jjon$624xy9^^*8jn#~`2Ag@xY;jon#{LP?`u>THflv`*Mkr1^D(d)G!o(V`TWd~ z^ED8Wg={#B9|+}6Sf9_?3|qA@`_FO=d%&k$4C8zrXJ}}BxBr?ff~XLW_4(Y+a6$`m z`|ke7wEi|N$LD*7oPQ#HIug0{hd`s4aK4N4Z45b21rb@uS7%8Aaq4rvfZ>!=81-6> zGyFVor#|Nu7_z@?#*Kf;r9Y+f4-AjG6u4csH}2Bs{31inyRp2x|9_(OdHgt^!SEGk zXafct((vnm)PELp|KEpuox9urwo9M$BMhG)|Jkq{L}X!{;lF@NE^_;JUgr8jd4=WYFXNM_(A;Hx&foC=>rCqYbKJOH zo(F#jU1$58=Q+PxwQ#cn_BZP@RQZN{`@%4ktOg?rv%gu7;Y;8++voHBh@n(ijt#i+ zEEq)N{_{C&%upIPD`0=S^?wcywclt37qE3FR#X>~FXJ{{m2Z=IH)$Gu@p5VQB+is%55xa5K3)~jFlpBp%y|dYF#O9*A-1^DkW}0xc=Vk z?A_i$NQ(KZ+L317$IQHW^XAPP-^}{_C;zxUmodj2#@JoR3z3^j80%%_z!>`~@+Ajj zin2v|b_-|a7p{Dg2^umP#PCcl6eS+m6(`I{h4Py2G5DkN3`lgbXv;w{OIk@qiNzDG zu`H`l-r}W3)ke{RXiOuUN`*X{GbKZw(h&%C1)`kJpYJvy@5j6~V?-AToP!(m^Tp!P zV7OhhDb(*nA@3cblu4T+^JIgmD2edSVA!t&!)*~x=j%7P%;+f#0Yn$d5LFz|y6)xc z>Z#jOXQ^=s+*p3b3TRqa@)dJ!6eS!|V!JzABO#?L9)+GamuHG2dDP!bWdAag092Ir zh{a8AkXP<98kdSbL>J0)iaG*{67t?kzY6rH)L{Tdv{fF>H>*2vMQM%YypHJn`6khy zl8cu{VU_2w@Cl`LpfyvJhWd?9OP+djk5yAGk~z4bzb7V6dRb+W4V#cF3iz?8ud34< z3|B=1u}C873&cSBBC%anI|8AIFBsqbs4vplh1yu9e|IR@ivE>Syuldr><1XYWIw+Ca%hAvo34RGqSc=);3qm z8rWLmYUYBhN>g&Gdx|-G?#J|HO+p2LyGyj0LN`5=R;9~w>P&UZSU*ofzDw>~vIf6& z<-W370_JF10#eq=+VQLS(P{{iphH5J)o}~~dFWVTbezjOmh8b#-R$VeTt6t8memp; zRj1ClTE?8qJdE-2U%Zlp%ER;vS{)tk0;p3*8nm+C;Fo=)Hm^Z##N4|_Fz&G0JS3~< z8`N?AIIa;e>7$tRQReKWAMA2pZ4d7IpweLiTK9;4>Ut(4pOq$1%cdW|2;d&o5<_UC zqRj~!Oz(y$ReD8}UQtI=)7Mn#u-bUg*|U!NP)AKVoYJqWQopljvxV0?;_Us2Nn^@K zq0Yqg32+SJtX@qz_SOEAgC)yMr5>^>rCQq4)yN4K80tjLg@(S8k9o)Lv`1^iw8v$2 zbdc%4#9a-9Ug34kX_}@g?bk1SW|=8&I}9Z>H(Wj}z0-SO7e9gk61=wWL z(-JT^LmJLNH$rk>$wHc@Dy6xM{Hiih1w+1pk)awKy*aJ#zXmI&&bV8~+H(Gxy0UGb zRN>K@%Q3C1hWkT(wegZil_ueyuvsI~BY>A6XGB)#0|lGVr_fcJ1U=$W>q>!_3*1F( z;MvzwA@%&B&iV3~pl?K(sF=r1s zjj+rhtkAQ;LZ@^GwuQ5IyU9C28w*Q!l?Q~RjB7Lu3<}#=TkJ+)UA?BGij|6>PsW5WS81_ZF&_%pOvnmWYctI414at9jqZ=8j-c8YJL5unT%+o zN!MWAZM6CG`~`lH7vFOJ^0#8{FRAX!%z1D$`L9g!&kpDDQQ4Q4Q=cH%J;(>$&_sjw z)gIuW>`j1^-$#x90=hdF2nWc8JtO#`w8lS}n&KY;Q=2ar|BQ}vdB>7{+?_=9y)pj9 zsJbBjVZT^|pL#myX6I$~qW)W2l5Bi8L)`$ThA{RP0W7Ef-hX%in8CE!^habC>^Iot zH8eQM0`#oVt2m`{V4-s}RVfzVtgs>kG_5@+O2pb?Q z46H2}4#qkHezukQBH>t~6KFkq&bz}~74nALt2QO#o7x%!ossBnDH@GL*&aNNxV#~} z7J1_V7hW|3-cD9ixw^8Z#;Ak9I17e%ctb(ID-^)Pl54r&BC$Rc3bcDebxUkQ8T!5x7Z)$x}ujIC_sysem0s7N` zelJk6LiiS=H*<=@l+E{V!r_K^|8n46ihA>uMNCE^91)Li?+Q36;2#C-5^#@zJpvvO zP#5s9fUgU9Ou*v;7LIGvwgqep*cPxYU|YbpfNcTW0=5Ng3)mL0Enr)~w!odYz@i5$ zT1e_*w?d!5dwtI+Q_woBBqSh~RoA)h5x^tuy_7g|W zaq4mLq!6zu$J@+ralm^PV%wsD0M&0py|_amvLilABDgJ?D@FCpv46a0x{1%GzqeNEY(`7DY(vUKpmGXg*@^2yKPY(7zNvWZ3 zt5NjVqL^BT;>csra$C}oo|AS=6(t=>lxfEZ-8mh?<mE$2g14BQkq9nh2d> yCSK1EM3XX3E#nZ(9)Bjny9JGR1<_4VMz { } Preconditions.checkState(uncompressedSize >= threshold, "Uncompressed size %s doesn't make sense with threshold %s", uncompressedSize, threshold); + // Try to use the uncompressed size, but place a cap if it might be too big (possibly malicious). ByteBuf uncompressed = ctx.alloc().buffer(Math.min(uncompressedSize, MAXIMUM_INITIAL_BUFFER_SIZE)); try { compressor.inflate(msg, uncompressed); 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 226c36832..ac3eac297 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 @@ -25,7 +25,8 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder { return; } - ByteBuf compressedBuffer = ctx.alloc().buffer(); + // in other words, see if a plain 8KiB buffer fits us well + ByteBuf compressedBuffer = ctx.alloc().buffer(8192); try { int uncompressed = msg.readableBytes(); compressor.deflate(msg, compressedBuffer);