geforkt von Mirrors/Velocity
Merge branch 'decode-multiple' into dev/1.1.0
Dieser Commit ist enthalten in:
Commit
ab9115178b
2
.gitignore
vendored
2
.gitignore
vendored
@ -89,3 +89,5 @@ plugins/
|
|||||||
### Natives stuff ###
|
### Natives stuff ###
|
||||||
native/mbedtls
|
native/mbedtls
|
||||||
native/zlib-ng
|
native/zlib-ng
|
||||||
|
native/zlib-cf
|
||||||
|
native/libdeflate
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [ ! -d zlib-ng ]; then
|
if [ ! -d libdeflate ]; then
|
||||||
echo "Cloning zlib-ng..."
|
echo "Cloning libdeflate..."
|
||||||
git clone https://github.com/zlib-ng/zlib-ng.git
|
git clone https://github.com/ebiggers/libdeflate.git
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Compiling zlib-ng..."
|
echo "Compiling libdeflate..."
|
||||||
cd zlib-ng
|
cd libdeflate || exit
|
||||||
CFLAGS="-fPIC -O3" ./configure --zlib-compat --static
|
make
|
||||||
make clean && make
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# Modify as you need.
|
# Modify as you need.
|
||||||
MBEDTLS_ROOT=mbedtls
|
MBEDTLS_ROOT=mbedtls
|
||||||
CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared"
|
CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack"
|
||||||
gcc $CFLAGS -Izlib-ng src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \
|
ARCH=$(uname -m)
|
||||||
src/main/c/jni_zlib_common.c zlib-ng/libz.a -o src/main/resources/linux_x64/velocity-compress.so
|
mkdir -p src/main/resources/linux_$ARCH
|
||||||
|
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_$ARCH/velocity-compress.so
|
||||||
gcc $CFLAGS -I $MBEDTLS_ROOT/include -shared $MBEDTLS_ROOT/library/aes.c $MBEDTLS_ROOT/library/aesni.c \
|
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 \
|
$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
|
-o src/main/resources/linux_$ARCH/velocity-cipher.so
|
@ -1,23 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ ! -d zlib-ng ]; then
|
|
||||||
echo "Cloning zlib-ng..."
|
|
||||||
git clone https://github.com/zlib-ng/zlib-ng.git
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Compiling zlib-ng..."
|
|
||||||
cd zlib-ng
|
|
||||||
CFLAGS="-fPIC -O3" ./configure --zlib-compat --static
|
|
||||||
make clean && make
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# Modify as you need.
|
|
||||||
MBEDTLS_ROOT=mbedtls
|
|
||||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
|
|
||||||
CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/darwin/ -fPIC -shared"
|
|
||||||
|
|
||||||
clang $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/macosx/velocity-compress.dylib
|
|
||||||
clang $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/macosx/velocity-cipher.dylib
|
|
@ -1,29 +0,0 @@
|
|||||||
#include <jni.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <zlib.h>
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
#include <jni.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
void JNICALL
|
|
||||||
check_zlib_free(JNIEnv *env, z_stream *stream, bool deflate);
|
|
@ -2,56 +2,21 @@
|
|||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <zlib.h>
|
#include <libdeflate.h>
|
||||||
#include "jni_util.h"
|
#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
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env,
|
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env,
|
||||||
jobject obj,
|
jobject obj,
|
||||||
jint level)
|
jint level)
|
||||||
{
|
{
|
||||||
z_stream* stream = calloc(1, sizeof(z_stream));
|
struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level);
|
||||||
|
if (compressor == NULL) {
|
||||||
if (stream == 0) {
|
|
||||||
// Out of memory!
|
// Out of memory!
|
||||||
throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream");
|
throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate compressor");
|
||||||
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;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
return (jlong) compressor;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
@ -59,11 +24,10 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env,
|
|||||||
jobject obj,
|
jobject obj,
|
||||||
jlong ctx)
|
jlong ctx)
|
||||||
{
|
{
|
||||||
z_stream* stream = (z_stream*) ctx;
|
libdeflate_free_compressor((struct libdeflate_compressor *) ctx);
|
||||||
check_zlib_free(env, stream, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT int JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env,
|
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env,
|
||||||
jobject obj,
|
jobject obj,
|
||||||
jlong ctx,
|
jlong ctx,
|
||||||
@ -73,38 +37,8 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *e
|
|||||||
jint destinationLength,
|
jint destinationLength,
|
||||||
jboolean finish)
|
jboolean finish)
|
||||||
{
|
{
|
||||||
z_stream* stream = (z_stream*) ctx;
|
struct libdeflate_compressor *compressor = (struct libdeflate_compressor *) ctx;
|
||||||
stream->next_in = (Bytef *) sourceAddress;
|
size_t produced = libdeflate_zlib_compress(compressor, (void *) sourceAddress, sourceLength,
|
||||||
stream->next_out = (Bytef *) destinationAddress;
|
(void *) destinationAddress, destinationLength);
|
||||||
stream->avail_in = sourceLength;
|
return (jlong) produced;
|
||||||
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);
|
|
||||||
}
|
}
|
@ -2,50 +2,21 @@
|
|||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <zlib.h>
|
#include <libdeflate.h>
|
||||||
#include "jni_util.h"
|
#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
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env,
|
Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env,
|
||||||
jobject obj)
|
jobject obj)
|
||||||
{
|
{
|
||||||
z_stream* stream = calloc(1, sizeof(z_stream));
|
struct libdeflate_decompressor *decompress = libdeflate_alloc_decompressor();
|
||||||
|
if (decompress == NULL) {
|
||||||
if (stream == 0) {
|
|
||||||
// Out of memory!
|
// Out of memory!
|
||||||
throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream");
|
throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate decompressor");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = inflateInit(stream);
|
return (jlong) decompress;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
@ -53,51 +24,34 @@ Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env,
|
|||||||
jobject obj,
|
jobject obj,
|
||||||
jlong ctx)
|
jlong ctx)
|
||||||
{
|
{
|
||||||
z_stream* stream = (z_stream*) ctx;
|
libdeflate_free_decompressor((struct libdeflate_decompressor *) ctx);
|
||||||
check_zlib_free(env, stream, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT int JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env,
|
Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env,
|
||||||
jobject obj,
|
jobject obj,
|
||||||
jlong ctx,
|
jlong ctx,
|
||||||
jlong sourceAddress,
|
jlong sourceAddress,
|
||||||
jint sourceLength,
|
jint sourceLength,
|
||||||
jlong destinationAddress,
|
jlong destinationAddress,
|
||||||
jint destinationLength)
|
jint destinationLength,
|
||||||
|
jlong maximumSize)
|
||||||
{
|
{
|
||||||
z_stream* stream = (z_stream*) ctx;
|
struct libdeflate_decompressor *decompress = (struct libdeflate_decompressor *) ctx;
|
||||||
stream->next_in = (Bytef *) sourceAddress;
|
enum libdeflate_result result = libdeflate_zlib_decompress(decompress, (void *) sourceAddress,
|
||||||
stream->next_out = (Bytef *) destinationAddress;
|
sourceLength, (void *) destinationAddress, destinationLength, NULL);
|
||||||
stream->avail_in = sourceLength;
|
|
||||||
stream->avail_out = destinationLength;
|
|
||||||
|
|
||||||
int res = inflate(stream, Z_PARTIAL_FLUSH);
|
switch (result) {
|
||||||
switch (res) {
|
case LIBDEFLATE_SUCCESS:
|
||||||
case Z_STREAM_END:
|
// We are happy
|
||||||
// The stream has ended
|
return JNI_TRUE;
|
||||||
(*env)->SetBooleanField(env, obj, finishedID, JNI_TRUE);
|
case LIBDEFLATE_BAD_DATA:
|
||||||
// fall-through
|
throwException(env, "java/util/zip/DataFormatException", "inflate data is bad");
|
||||||
case Z_OK:
|
return JNI_FALSE;
|
||||||
// Not yet completed, but progress has been made. Tell Java how many bytes we've processed.
|
case LIBDEFLATE_SHORT_OUTPUT:
|
||||||
(*env)->SetIntField(env, obj, consumedID, sourceLength - stream->avail_in);
|
case LIBDEFLATE_INSUFFICIENT_SPACE:
|
||||||
return destinationLength - stream->avail_out;
|
// These cases are the same for us. We expect the full uncompressed size to be known.
|
||||||
case Z_BUF_ERROR:
|
throwException(env, "java/util/zip/DataFormatException", "uncompressed size is inaccurate");
|
||||||
// This is not fatal. Just say we need more data. Usually this applies to the next_out buffer,
|
return JNI_FALSE;
|
||||||
// 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);
|
|
||||||
}
|
|
@ -54,7 +54,8 @@ public class Java11VelocityCompressor implements VelocityCompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
|
||||||
|
throws DataFormatException {
|
||||||
ensureNotDisposed();
|
ensureNotDisposed();
|
||||||
|
|
||||||
// We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly.
|
// 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()) {
|
while (!inflater.finished() && inflater.getBytesRead() < source.readableBytes()) {
|
||||||
if (!destination.isWritable()) {
|
if (!destination.isWritable()) {
|
||||||
ensureMaxSize(destination, max);
|
ensureMaxSize(destination, uncompressedSize);
|
||||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,20 +25,21 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
|
||||||
|
throws DataFormatException {
|
||||||
ensureNotDisposed();
|
ensureNotDisposed();
|
||||||
|
|
||||||
final int available = source.readableBytes();
|
final int available = source.readableBytes();
|
||||||
this.setInflaterInput(source);
|
this.setInflaterInput(source);
|
||||||
|
|
||||||
if (destination.hasArray()) {
|
if (destination.hasArray()) {
|
||||||
this.inflateDestinationIsHeap(destination, available, max);
|
this.inflateDestinationIsHeap(destination, available, uncompressedSize);
|
||||||
} else {
|
} else {
|
||||||
if (buf.length == 0) {
|
if (buf.length == 0) {
|
||||||
buf = new byte[ZLIB_BUFFER_SIZE];
|
buf = new byte[ZLIB_BUFFER_SIZE];
|
||||||
}
|
}
|
||||||
while (!inflater.finished() && inflater.getBytesRead() < available) {
|
while (!inflater.finished() && inflater.getBytesRead() < available) {
|
||||||
ensureMaxSize(destination, max);
|
ensureMaxSize(destination, uncompressedSize);
|
||||||
int read = inflater.inflate(buf);
|
int read = inflater.inflate(buf);
|
||||||
destination.writeBytes(buf, 0, read);
|
destination.writeBytes(buf, 0, read);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,21 +5,10 @@ package com.velocitypowered.natives.compression;
|
|||||||
*/
|
*/
|
||||||
class NativeZlibDeflate {
|
class NativeZlibDeflate {
|
||||||
|
|
||||||
boolean finished;
|
|
||||||
int consumed;
|
|
||||||
|
|
||||||
native long init(int level);
|
native long init(int level);
|
||||||
|
|
||||||
native long free(long ctx);
|
native long free(long ctx);
|
||||||
|
|
||||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
||||||
int destinationLength, boolean finish);
|
int destinationLength);
|
||||||
|
|
||||||
native void reset(long ctx);
|
|
||||||
|
|
||||||
static {
|
|
||||||
initIDs();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static native void initIDs();
|
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,16 @@
|
|||||||
package com.velocitypowered.natives.compression;
|
package com.velocitypowered.natives.compression;
|
||||||
|
|
||||||
|
import java.util.zip.DataFormatException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a native interface for zlib's inflate functions.
|
* Represents a native interface for zlib's inflate functions.
|
||||||
*/
|
*/
|
||||||
class NativeZlibInflate {
|
class NativeZlibInflate {
|
||||||
|
|
||||||
boolean finished;
|
|
||||||
int consumed;
|
|
||||||
|
|
||||||
native long init();
|
native long init();
|
||||||
|
|
||||||
native long free(long ctx);
|
native long free(long ctx);
|
||||||
|
|
||||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
native boolean process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
||||||
int destinationLength);
|
int destinationLength) throws DataFormatException;
|
||||||
|
|
||||||
native void reset(long ctx);
|
|
||||||
|
|
||||||
static {
|
|
||||||
initIDs();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static native void initIDs();
|
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,12 @@ import io.netty.buffer.ByteBuf;
|
|||||||
import java.util.zip.DataFormatException;
|
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 {
|
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;
|
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import java.util.function.BooleanSupplier;
|
|||||||
public class NativeConstraints {
|
public class NativeConstraints {
|
||||||
private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled");
|
private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled");
|
||||||
private static final boolean IS_AMD64;
|
private static final boolean IS_AMD64;
|
||||||
|
private static final boolean IS_AARCH64;
|
||||||
private static final boolean CAN_GET_MEMORYADDRESS;
|
private static final boolean CAN_GET_MEMORYADDRESS;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -21,20 +22,21 @@ public class NativeConstraints {
|
|||||||
// HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere
|
// HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere
|
||||||
// give amd64.
|
// give amd64.
|
||||||
IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64");
|
IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64");
|
||||||
|
IS_AARCH64 = osArch.equals("aarch64");
|
||||||
}
|
}
|
||||||
|
|
||||||
static final BooleanSupplier MACOS = () -> {
|
static final BooleanSupplier NATIVE_BASE = () -> NATIVES_ENABLED && CAN_GET_MEMORYADDRESS;
|
||||||
return NATIVES_ENABLED
|
|
||||||
&& CAN_GET_MEMORYADDRESS
|
static final BooleanSupplier LINUX_X86_64 = () -> {
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
return NATIVE_BASE.getAsBoolean()
|
||||||
|
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
||||||
&& IS_AMD64;
|
&& IS_AMD64;
|
||||||
};
|
};
|
||||||
|
|
||||||
static final BooleanSupplier LINUX = () -> {
|
static final BooleanSupplier LINUX_AARCH64 = () -> {
|
||||||
return NATIVES_ENABLED
|
return NATIVE_BASE.getAsBoolean()
|
||||||
&& CAN_GET_MEMORYADDRESS
|
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
||||||
&& IS_AMD64;
|
&& IS_AARCH64;
|
||||||
};
|
};
|
||||||
|
|
||||||
static final BooleanSupplier JAVA_11 = () -> {
|
static final BooleanSupplier JAVA_11 = () -> {
|
||||||
|
@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
import com.velocitypowered.natives.NativeSetupException;
|
import com.velocitypowered.natives.NativeSetupException;
|
||||||
import com.velocitypowered.natives.compression.Java11VelocityCompressor;
|
import com.velocitypowered.natives.compression.Java11VelocityCompressor;
|
||||||
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
|
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.compression.VelocityCompressorFactory;
|
||||||
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
|
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
|
||||||
import com.velocitypowered.natives.encryption.NativeVelocityCipher;
|
import com.velocitypowered.natives.encryption.NativeVelocityCipher;
|
||||||
@ -62,12 +62,14 @@ public class Natives {
|
|||||||
|
|
||||||
public static final NativeCodeLoader<VelocityCompressorFactory> compress = new NativeCodeLoader<>(
|
public static final NativeCodeLoader<VelocityCompressorFactory> compress = new NativeCodeLoader<>(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
||||||
copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)",
|
copyAndLoadNative("/linux_x86_64/velocity-compress.so"),
|
||||||
NativeVelocityCompressor.FACTORY),
|
"libdeflate (Linux x86_64)",
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX,
|
LibdeflateVelocityCompressor.FACTORY),
|
||||||
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
||||||
NativeVelocityCompressor.FACTORY),
|
copyAndLoadNative("/linux_aarch64/velocity-compress.so"),
|
||||||
|
"libdeflate (Linux aarch64)",
|
||||||
|
LibdeflateVelocityCompressor.FACTORY),
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> {
|
new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> {
|
||||||
}, "Java 11", () -> Java11VelocityCompressor.FACTORY),
|
}, "Java 11", () -> Java11VelocityCompressor.FACTORY),
|
||||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
||||||
@ -77,12 +79,12 @@ public class Natives {
|
|||||||
|
|
||||||
public static final NativeCodeLoader<VelocityCipherFactory> cipher = new NativeCodeLoader<>(
|
public static final NativeCodeLoader<VelocityCipherFactory> cipher = new NativeCodeLoader<>(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
|
||||||
copyAndLoadNative("/macosx/velocity-cipher.dylib"), "mbed TLS (macOS)",
|
copyAndLoadNative("/linux_x86_64/velocity-cipher.so"),
|
||||||
NativeVelocityCipher.FACTORY),
|
"mbed TLS (Linux x86_64)", NativeVelocityCipher.FACTORY),
|
||||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX,
|
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
|
||||||
copyAndLoadNative("/linux_x64/velocity-cipher.so"), "mbed TLS (Linux amd64)",
|
copyAndLoadNative("/linux_aarch64/velocity-cipher.so"),
|
||||||
NativeVelocityCipher.FACTORY),
|
"mbed TLS (Linux aarch64)", NativeVelocityCipher.FACTORY),
|
||||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
||||||
}, "Java", JavaVelocityCipher.FACTORY)
|
}, "Java", JavaVelocityCipher.FACTORY)
|
||||||
)
|
)
|
||||||
|
BIN
native/src/main/resources/linux_aarch64/velocity-compress.so
Ausführbare Datei
BIN
native/src/main/resources/linux_aarch64/velocity-compress.so
Ausführbare Datei
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
BIN
native/src/main/resources/linux_x86_64/velocity-compress.so
Ausführbare Datei
BIN
native/src/main/resources/linux_x86_64/velocity-compress.so
Ausführbare Datei
Binäre Datei nicht angezeigt.
Binäre Datei nicht angezeigt.
@ -33,13 +33,13 @@ class VelocityCompressorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@EnabledOnOs({MAC, LINUX})
|
@EnabledOnOs({LINUX})
|
||||||
void sanityCheckNative() {
|
void sanityCheckNative() {
|
||||||
assertThrows(IllegalArgumentException.class, () -> Natives.compress.get().create(-42));
|
assertThrows(IllegalArgumentException.class, () -> Natives.compress.get().create(-42));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@EnabledOnOs({MAC, LINUX})
|
@EnabledOnOs({LINUX})
|
||||||
void nativeIntegrityCheck() throws DataFormatException {
|
void nativeIntegrityCheck() throws DataFormatException {
|
||||||
VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION);
|
VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION);
|
||||||
if (compressor.preferredBufferType() != BufferPreference.DIRECT_REQUIRED) {
|
if (compressor.preferredBufferType() != BufferPreference.DIRECT_REQUIRED) {
|
||||||
@ -86,10 +86,11 @@ class VelocityCompressorTest {
|
|||||||
ByteBuf decompressed = bufSupplier.get();
|
ByteBuf decompressed = bufSupplier.get();
|
||||||
|
|
||||||
source.writeBytes(TEST_DATA);
|
source.writeBytes(TEST_DATA);
|
||||||
|
int uncompressedData = source.readableBytes();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
compressor.deflate(source, dest);
|
compressor.deflate(source, dest);
|
||||||
compressor.inflate(dest, decompressed, Integer.MAX_VALUE);
|
compressor.inflate(dest, decompressed, uncompressedData);
|
||||||
source.readerIndex(0);
|
source.readerIndex(0);
|
||||||
assertTrue(ByteBufUtil.equals(source, decompressed));
|
assertTrue(ByteBufUtil.equals(source, decompressed));
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -48,8 +48,7 @@ dependencies {
|
|||||||
compile "io.netty:netty-handler:${nettyVersion}"
|
compile "io.netty:netty-handler:${nettyVersion}"
|
||||||
compile "io.netty:netty-transport-native-epoll:${nettyVersion}"
|
compile "io.netty:netty-transport-native-epoll:${nettyVersion}"
|
||||||
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
|
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
|
||||||
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}"
|
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch64"
|
||||||
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64"
|
|
||||||
compile "io.netty:netty-resolver-dns:${nettyVersion}"
|
compile "io.netty:netty-resolver-dns:${nettyVersion}"
|
||||||
|
|
||||||
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
|
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
|
||||||
|
@ -120,12 +120,12 @@ public class VelocityServer implements ProxyServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public KeyPair getServerKeyPair() {
|
public KeyPair getServerKeyPair() {
|
||||||
return ensureInitialized(serverKeyPair);
|
return serverKeyPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VelocityConfiguration getConfiguration() {
|
public VelocityConfiguration getConfiguration() {
|
||||||
return ensureInitialized(this.configuration);
|
return this.configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -229,7 +229,6 @@ public class VelocityServer implements ProxyServer {
|
|||||||
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
|
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull({"pluginManager", "eventManager"})
|
|
||||||
private void loadPlugins() {
|
private void loadPlugins() {
|
||||||
logger.info("Loading plugins...");
|
logger.info("Loading plugins...");
|
||||||
|
|
||||||
@ -443,18 +442,11 @@ public class VelocityServer implements ProxyServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AsyncHttpClient getAsyncHttpClient() {
|
public AsyncHttpClient getAsyncHttpClient() {
|
||||||
return ensureInitialized(cm).getHttpClient();
|
return cm.getHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ratelimiter getIpAttemptLimiter() {
|
public Ratelimiter getIpAttemptLimiter() {
|
||||||
return ensureInitialized(ipAttemptLimiter);
|
return ipAttemptLimiter;
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> T ensureInitialized(T o) {
|
|
||||||
if (o == null) {
|
|
||||||
throw new IllegalStateException("The proxy isn't fully initialized.");
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -465,7 +465,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
Advanced advanced = new Advanced(toml.getTable("advanced"));
|
Advanced advanced = new Advanced(toml.getTable("advanced"));
|
||||||
Query query = new Query(toml.getTable("query"));
|
Query query = new Query(toml.getTable("query"));
|
||||||
Metrics metrics = new Metrics(toml.getTable("metrics"));
|
Metrics metrics = new Metrics(toml.getTable("metrics"));
|
||||||
byte[] forwardingSecret = toml.getString("forwarding-secret", "5up3r53cr3t")
|
byte[] forwardingSecret = toml.getString("forwarding-secret", generateRandomString(12))
|
||||||
.getBytes(StandardCharsets.UTF_8);
|
.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN")
|
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN")
|
||||||
|
@ -16,7 +16,6 @@ import com.velocitypowered.natives.encryption.VelocityCipher;
|
|||||||
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
|
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
|
||||||
import com.velocitypowered.natives.util.Natives;
|
import com.velocitypowered.natives.util.Natives;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.network.netty.DiscardHandler;
|
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder;
|
||||||
@ -125,6 +124,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
if (sessionHandler != null) {
|
||||||
|
sessionHandler.readCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
if (ctx.channel().isActive()) {
|
if (ctx.channel().isActive()) {
|
||||||
@ -145,7 +151,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
installDiscardHandler(ctx);
|
|
||||||
ctx.close();
|
ctx.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,18 +166,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
Preconditions.checkState(this.channel.eventLoop().inEventLoop(), "Not in event loop");
|
Preconditions.checkState(this.channel.eventLoop().inEventLoop(), "Not in event loop");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installDiscardHandler(ChannelHandlerContext ctx) {
|
|
||||||
if (ctx.pipeline().get("discard") == null) {
|
|
||||||
ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void installDiscardHandler() {
|
|
||||||
if (channel.pipeline().get("discard") == null) {
|
|
||||||
channel.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EventLoop eventLoop() {
|
public EventLoop eventLoop() {
|
||||||
return channel.eventLoop();
|
return channel.eventLoop();
|
||||||
}
|
}
|
||||||
@ -212,9 +205,10 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
*/
|
*/
|
||||||
public void closeWith(Object msg) {
|
public void closeWith(Object msg) {
|
||||||
if (channel.isActive()) {
|
if (channel.isActive()) {
|
||||||
|
channel.eventLoop().execute(() -> {
|
||||||
knownDisconnect = true;
|
knownDisconnect = true;
|
||||||
installDiscardHandler();
|
|
||||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +217,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
*/
|
*/
|
||||||
public void close() {
|
public void close() {
|
||||||
if (channel.isActive()) {
|
if (channel.isActive()) {
|
||||||
installDiscardHandler();
|
|
||||||
channel.close();
|
channel.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,10 @@ public interface MinecraftSessionHandler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void readCompleted() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
default boolean handle(AvailableCommands commands) {
|
default boolean handle(AvailableCommands commands) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -188,12 +188,17 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
if (packet instanceof PluginMessage) {
|
if (packet instanceof PluginMessage) {
|
||||||
((PluginMessage) packet).retain();
|
((PluginMessage) packet).retain();
|
||||||
}
|
}
|
||||||
playerConnection.write(packet);
|
playerConnection.delayedWrite(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleUnknown(ByteBuf buf) {
|
public void handleUnknown(ByteBuf buf) {
|
||||||
playerConnection.write(buf.retain());
|
playerConnection.delayedWrite(buf.retain());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readCompleted() {
|
||||||
|
playerConnection.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -170,41 +170,32 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
public boolean handle(PluginMessage packet) {
|
public boolean handle(PluginMessage packet) {
|
||||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||||
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
|
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
|
||||||
if (serverConn != null && backendConn != null) {
|
if (serverConn == null || backendConn == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (backendConn.getState() != StateRegistry.PLAY) {
|
if (backendConn.getState() != StateRegistry.PLAY) {
|
||||||
logger.warn("A plugin message was received while the backend server was not "
|
logger.warn("A plugin message was received while the backend server was not "
|
||||||
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
|
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
|
||||||
} else if (PluginMessageUtil.isRegister(packet)) {
|
return true;
|
||||||
player.getKnownChannels().addAll(PluginMessageUtil.getChannels(packet));
|
}
|
||||||
backendConn.write(packet.retain());
|
|
||||||
} else if (PluginMessageUtil.isUnregister(packet)) {
|
if (this.tryHandleVanillaPluginMessageChannel(packet, backendConn)) {
|
||||||
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
|
return true;
|
||||||
backendConn.write(packet.retain());
|
}
|
||||||
} else if (PluginMessageUtil.isMcBrand(packet)) {
|
|
||||||
backendConn.write(PluginMessageUtil
|
|
||||||
.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion()));
|
|
||||||
} else {
|
|
||||||
if (serverConn.getPhase() == BackendConnectionPhases.IN_TRANSITION) {
|
if (serverConn.getPhase() == BackendConnectionPhases.IN_TRANSITION) {
|
||||||
// We must bypass the currently-connected server when forwarding Forge packets.
|
// We must bypass the currently-connected server when forwarding Forge packets.
|
||||||
VelocityServerConnection inFlight = player.getConnectionInFlight();
|
VelocityServerConnection inFlight = player.getConnectionInFlight();
|
||||||
if (inFlight != null) {
|
if (inFlight != null) {
|
||||||
player.getPhase().handle(player, packet, inFlight);
|
if (player.getPhase().handle(player, packet, inFlight)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else if (!this.tryHandleForgeMessage(packet, serverConn)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!player.getPhase().handle(player, packet, serverConn)) {
|
|
||||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
|
|
||||||
.consideredComplete()) {
|
|
||||||
// The client is trying to send messages too early. This is primarily caused by mods,
|
|
||||||
// but further aggravated by Velocity. To work around these issues, we will queue any
|
|
||||||
// non-FML handshake messages to be sent once the FML handshake has completed or the
|
|
||||||
// JoinGame packet has been received by the proxy, whichever comes first.
|
|
||||||
//
|
|
||||||
// We also need to make sure to retain these packets so they can be flushed
|
|
||||||
// appropriately.
|
|
||||||
loginPluginMessages.add(packet.retain());
|
|
||||||
} else {
|
|
||||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
backendConn.write(packet.retain());
|
backendConn.write(packet.retain());
|
||||||
@ -218,11 +209,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
backendConn.write(message);
|
backendConn.write(message);
|
||||||
}, backendConn.eventLoop());
|
}, backendConn.eventLoop());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,9 +263,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writabilityChanged() {
|
public void writabilityChanged() {
|
||||||
|
boolean writable = player.getConnection().getChannel().isWritable();
|
||||||
|
|
||||||
|
if (!writable) {
|
||||||
|
// We might have packets queued for the server, so flush them now to free up memory.
|
||||||
|
player.getConnection().flush();
|
||||||
|
}
|
||||||
|
|
||||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||||
if (serverConn != null) {
|
if (serverConn != null) {
|
||||||
boolean writable = player.getConnection().getChannel().isWritable();
|
|
||||||
MinecraftConnection smc = serverConn.getConnection();
|
MinecraftConnection smc = serverConn.getConnection();
|
||||||
if (smc != null) {
|
if (smc != null) {
|
||||||
smc.setAutoReading(writable);
|
smc.setAutoReading(writable);
|
||||||
@ -287,6 +279,44 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean tryHandleVanillaPluginMessageChannel(PluginMessage packet,
|
||||||
|
MinecraftConnection backendConn) {
|
||||||
|
if (PluginMessageUtil.isRegister(packet)) {
|
||||||
|
player.getKnownChannels().addAll(PluginMessageUtil.getChannels(packet));
|
||||||
|
backendConn.write(packet.retain());
|
||||||
|
return true;
|
||||||
|
} else if (PluginMessageUtil.isUnregister(packet)) {
|
||||||
|
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
|
||||||
|
backendConn.write(packet.retain());
|
||||||
|
return true;
|
||||||
|
} else if (PluginMessageUtil.isMcBrand(packet)) {
|
||||||
|
backendConn.write(PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(),
|
||||||
|
player.getProtocolVersion()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryHandleForgeMessage(PluginMessage packet, VelocityServerConnection serverConn) {
|
||||||
|
if (player.getPhase().handle(player, packet, serverConn)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) {
|
||||||
|
// The client is trying to send messages too early. This is primarily caused by mods,
|
||||||
|
// but further aggravated by Velocity. To work around these issues, we will queue any
|
||||||
|
// non-FML handshake messages to be sent once the FML handshake has completed or the
|
||||||
|
// JoinGame packet has been received by the proxy, whichever comes first.
|
||||||
|
//
|
||||||
|
// We also need to make sure to retain these packets so they can be flushed
|
||||||
|
// appropriately.
|
||||||
|
loginPluginMessages.add(packet.retain());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the {@code JoinGame} packet. This function is responsible for handling the client-side
|
* Handles the {@code JoinGame} packet. This function is responsible for handling the client-side
|
||||||
* switching servers in Velocity.
|
* switching servers in Velocity.
|
||||||
|
@ -6,7 +6,7 @@ import static org.asynchttpclient.Dsl.config;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.velocitypowered.natives.util.Natives;
|
import com.velocitypowered.natives.util.Natives;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.network.netty.DnsAddressResolverGroupNameResolverAdapter;
|
import com.velocitypowered.proxy.network.netty.SeparatePoolInetNameResolver;
|
||||||
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
|
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.bootstrap.ServerBootstrap;
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
@ -18,6 +18,7 @@ import io.netty.channel.WriteBufferWaterMark;
|
|||||||
import io.netty.channel.epoll.EpollChannelOption;
|
import io.netty.channel.epoll.EpollChannelOption;
|
||||||
import io.netty.resolver.dns.DnsAddressResolverGroup;
|
import io.netty.resolver.dns.DnsAddressResolverGroup;
|
||||||
import io.netty.resolver.dns.DnsNameResolverBuilder;
|
import io.netty.resolver.dns.DnsNameResolverBuilder;
|
||||||
|
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -48,7 +49,7 @@ public final class ConnectionManager {
|
|||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public final BackendChannelInitializerHolder backendChannelInitializer;
|
public final BackendChannelInitializerHolder backendChannelInitializer;
|
||||||
|
|
||||||
private final DnsAddressResolverGroup resolverGroup;
|
private final SeparatePoolInetNameResolver resolver;
|
||||||
private final AsyncHttpClient httpClient;
|
private final AsyncHttpClient httpClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,21 +66,16 @@ public final class ConnectionManager {
|
|||||||
new ServerChannelInitializer(this.server));
|
new ServerChannelInitializer(this.server));
|
||||||
this.backendChannelInitializer = new BackendChannelInitializerHolder(
|
this.backendChannelInitializer = new BackendChannelInitializerHolder(
|
||||||
new BackendChannelInitializer(this.server));
|
new BackendChannelInitializer(this.server));
|
||||||
this.resolverGroup = new DnsAddressResolverGroup(new DnsNameResolverBuilder()
|
this.resolver = new SeparatePoolInetNameResolver(GlobalEventExecutor.INSTANCE);
|
||||||
.channelType(this.transportType.datagramChannelClass)
|
|
||||||
.negativeTtl(15)
|
|
||||||
.ndots(1));
|
|
||||||
this.httpClient = asyncHttpClient(config()
|
this.httpClient = asyncHttpClient(config()
|
||||||
.setEventLoopGroup(this.workerGroup)
|
.setEventLoopGroup(this.workerGroup)
|
||||||
.setUserAgent(server.getVersion().getName() + "/" + server.getVersion().getVersion())
|
.setUserAgent(server.getVersion().getName() + "/" + server.getVersion().getVersion())
|
||||||
.addRequestFilter(new RequestFilter() {
|
.addRequestFilter(new RequestFilter() {
|
||||||
@Override
|
@Override
|
||||||
public <T> FilterContext<T> filter(FilterContext<T> ctx) throws FilterException {
|
public <T> FilterContext<T> filter(FilterContext<T> ctx) {
|
||||||
return new FilterContextBuilder<>(ctx)
|
return new FilterContextBuilder<>(ctx)
|
||||||
.request(new RequestBuilder(ctx.getRequest())
|
.request(new RequestBuilder(ctx.getRequest())
|
||||||
.setNameResolver(
|
.setNameResolver(resolver)
|
||||||
new DnsAddressResolverGroupNameResolverAdapter(resolverGroup, workerGroup)
|
|
||||||
)
|
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@ -162,7 +158,7 @@ public final class ConnectionManager {
|
|||||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
|
||||||
this.server.getConfiguration().getConnectTimeout())
|
this.server.getConfiguration().getConnectTimeout())
|
||||||
.group(group == null ? this.workerGroup : group)
|
.group(group == null ? this.workerGroup : group)
|
||||||
.resolver(this.resolverGroup);
|
.resolver(this.resolver.asGroup());
|
||||||
if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) {
|
if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) {
|
||||||
bootstrap.option(EpollChannelOption.TCP_FASTOPEN_CONNECT, true);
|
bootstrap.option(EpollChannelOption.TCP_FASTOPEN_CONNECT, true);
|
||||||
}
|
}
|
||||||
@ -194,6 +190,8 @@ public final class ConnectionManager {
|
|||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.resolver.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventLoopGroup getBossGroup() {
|
public EventLoopGroup getBossGroup() {
|
||||||
|
@ -7,11 +7,6 @@ import io.netty.channel.epoll.EpollDatagramChannel;
|
|||||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||||
import io.netty.channel.epoll.EpollSocketChannel;
|
import io.netty.channel.epoll.EpollSocketChannel;
|
||||||
import io.netty.channel.kqueue.KQueue;
|
|
||||||
import io.netty.channel.kqueue.KQueueDatagramChannel;
|
|
||||||
import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
|
||||||
import io.netty.channel.kqueue.KQueueServerSocketChannel;
|
|
||||||
import io.netty.channel.kqueue.KQueueSocketChannel;
|
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.DatagramChannel;
|
import io.netty.channel.socket.DatagramChannel;
|
||||||
import io.netty.channel.socket.ServerSocketChannel;
|
import io.netty.channel.socket.ServerSocketChannel;
|
||||||
@ -27,11 +22,7 @@ enum TransportType {
|
|||||||
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
|
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
|
||||||
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
|
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
|
||||||
EpollDatagramChannel.class,
|
EpollDatagramChannel.class,
|
||||||
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))),
|
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type)));
|
||||||
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class,
|
|
||||||
KQueueDatagramChannel.class,
|
|
||||||
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
|
|
||||||
|
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
final Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
||||||
@ -71,8 +62,6 @@ enum TransportType {
|
|||||||
|
|
||||||
if (Epoll.isAvailable()) {
|
if (Epoll.isAvailable()) {
|
||||||
return EPOLL;
|
return EPOLL;
|
||||||
} else if (KQueue.isAvailable()) {
|
|
||||||
return KQUEUE;
|
|
||||||
} else {
|
} else {
|
||||||
return NIO;
|
return NIO;
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package com.velocitypowered.proxy.network.netty;
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandler.Sharable;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
|
|
||||||
@Sharable
|
|
||||||
public class DiscardHandler extends ChannelInboundHandlerAdapter {
|
|
||||||
|
|
||||||
public static final DiscardHandler HANDLER = new DiscardHandler();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
||||||
ReferenceCountUtil.release(msg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package com.velocitypowered.proxy.network.netty;
|
|
||||||
|
|
||||||
import io.netty.channel.EventLoopGroup;
|
|
||||||
import io.netty.resolver.InetNameResolver;
|
|
||||||
import io.netty.resolver.dns.DnsAddressResolverGroup;
|
|
||||||
import io.netty.util.concurrent.EventExecutor;
|
|
||||||
import io.netty.util.concurrent.FutureListener;
|
|
||||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
|
||||||
import io.netty.util.concurrent.Promise;
|
|
||||||
import io.netty.util.internal.ThreadExecutorMap;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DnsAddressResolverGroupNameResolverAdapter extends InetNameResolver {
|
|
||||||
|
|
||||||
private final DnsAddressResolverGroup resolverGroup;
|
|
||||||
private final EventLoopGroup group;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a DnsAddressResolverGroupNameResolverAdapter.
|
|
||||||
* @param resolverGroup the resolver group to use
|
|
||||||
* @param group the event loop group
|
|
||||||
*/
|
|
||||||
public DnsAddressResolverGroupNameResolverAdapter(
|
|
||||||
DnsAddressResolverGroup resolverGroup, EventLoopGroup group) {
|
|
||||||
super(ImmediateEventExecutor.INSTANCE);
|
|
||||||
this.resolverGroup = resolverGroup;
|
|
||||||
this.group = group;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
|
|
||||||
EventExecutor executor = this.findExecutor();
|
|
||||||
resolverGroup.getResolver(executor).resolve(InetSocketAddress.createUnresolved(inetHost, 17))
|
|
||||||
.addListener((FutureListener<InetSocketAddress>) future -> {
|
|
||||||
if (future.isSuccess()) {
|
|
||||||
promise.trySuccess(future.getNow().getAddress());
|
|
||||||
} else {
|
|
||||||
promise.tryFailure(future.cause());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise)
|
|
||||||
throws Exception {
|
|
||||||
EventExecutor executor = this.findExecutor();
|
|
||||||
resolverGroup.getResolver(executor).resolveAll(InetSocketAddress.createUnresolved(inetHost, 17))
|
|
||||||
.addListener((FutureListener<List<InetSocketAddress>>) future -> {
|
|
||||||
if (future.isSuccess()) {
|
|
||||||
List<InetAddress> addresses = new ArrayList<>(future.getNow().size());
|
|
||||||
for (InetSocketAddress address : future.getNow()) {
|
|
||||||
addresses.add(address.getAddress());
|
|
||||||
}
|
|
||||||
promise.trySuccess(addresses);
|
|
||||||
} else {
|
|
||||||
promise.tryFailure(future.cause());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventExecutor findExecutor() {
|
|
||||||
EventExecutor current = ThreadExecutorMap.currentExecutor();
|
|
||||||
return current == null ? group.next() : current;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,79 @@
|
|||||||
|
package com.velocitypowered.proxy.network.netty;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import io.netty.resolver.AddressResolver;
|
||||||
|
import io.netty.resolver.AddressResolverGroup;
|
||||||
|
import io.netty.resolver.DefaultNameResolver;
|
||||||
|
import io.netty.resolver.InetNameResolver;
|
||||||
|
import io.netty.util.concurrent.EventExecutor;
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.concurrent.Promise;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
|
public final class SeparatePoolInetNameResolver extends InetNameResolver {
|
||||||
|
|
||||||
|
private final ExecutorService resolveExecutor;
|
||||||
|
private final InetNameResolver delegate;
|
||||||
|
private AddressResolverGroup<InetSocketAddress> resolverGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@code SeparatePoolInetNameResolver}.
|
||||||
|
*
|
||||||
|
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link
|
||||||
|
* Future} returned by {@link #resolve(String)}
|
||||||
|
*/
|
||||||
|
public SeparatePoolInetNameResolver(EventExecutor executor) {
|
||||||
|
super(executor);
|
||||||
|
this.resolveExecutor = Executors.newSingleThreadExecutor(
|
||||||
|
new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat("Velocity DNS Resolver")
|
||||||
|
.setDaemon(true)
|
||||||
|
.build());
|
||||||
|
this.delegate = new DefaultNameResolver(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
|
||||||
|
try {
|
||||||
|
resolveExecutor.execute(() -> this.delegate.resolve(inetHost, promise));
|
||||||
|
} catch (RejectedExecutionException e) {
|
||||||
|
promise.setFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise)
|
||||||
|
throws Exception {
|
||||||
|
try {
|
||||||
|
resolveExecutor.execute(() -> this.delegate.resolveAll(inetHost, promise));
|
||||||
|
} catch (RejectedExecutionException e) {
|
||||||
|
promise.setFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
this.resolveExecutor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a view of this resolver as a AddressResolverGroup.
|
||||||
|
*
|
||||||
|
* @return a view of this resolver as a AddressResolverGroup
|
||||||
|
*/
|
||||||
|
public AddressResolverGroup<InetSocketAddress> asGroup() {
|
||||||
|
if (this.resolverGroup == null) {
|
||||||
|
this.resolverGroup = new AddressResolverGroup<InetSocketAddress>() {
|
||||||
|
@Override
|
||||||
|
protected AddressResolver<InetSocketAddress> newResolver(EventExecutor executor) {
|
||||||
|
return asAddressResolver();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this.resolverGroup;
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,6 @@ public class MinecraftCipherDecoder extends MessageToMessageDecoder<ByteBuf> {
|
|||||||
try {
|
try {
|
||||||
cipher.process(compatible);
|
cipher.process(compatible);
|
||||||
out.add(compatible);
|
out.add(compatible);
|
||||||
in.skipBytes(in.readableBytes());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
compatible.release(); // compatible will never be used if we throw an exception
|
compatible.release(); // compatible will never be used if we throw an exception
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -13,9 +13,13 @@ import java.util.List;
|
|||||||
|
|
||||||
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||||
|
|
||||||
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 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 int threshold;
|
||||||
private final VelocityCompressor compressor;
|
private final VelocityCompressor compressor;
|
||||||
|
|
||||||
@ -28,21 +32,21 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
|||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
|
int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
|
||||||
if (claimedUncompressedSize == 0) {
|
if (claimedUncompressedSize == 0) {
|
||||||
// Strip the now-useless uncompressed size, this message is already uncompressed.
|
// This message is not compressed.
|
||||||
out.add(in.retainedSlice());
|
out.add(in.retainedSlice());
|
||||||
in.skipBytes(in.readableBytes());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
|
checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
|
||||||
+ " threshold %s", claimedUncompressedSize, threshold);
|
+ " threshold %s", claimedUncompressedSize, threshold);
|
||||||
int allowedMax = Math.min(claimedUncompressedSize, HARD_MAXIMUM_UNCOMPRESSED_SIZE);
|
checkFrame(claimedUncompressedSize <= UNCOMPRESSED_CAP,
|
||||||
int initialCapacity = Math.min(claimedUncompressedSize, SOFT_MAXIMUM_UNCOMPRESSED_SIZE);
|
"Uncompressed size %s exceeds hard threshold of %s", claimedUncompressedSize,
|
||||||
|
UNCOMPRESSED_CAP);
|
||||||
|
|
||||||
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
|
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
|
||||||
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
|
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize);
|
||||||
try {
|
try {
|
||||||
compressor.inflate(compatibleIn, uncompressed, allowedMax);
|
compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
|
||||||
out.add(uncompressed);
|
out.add(uncompressed);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
uncompressed.release();
|
uncompressed.release();
|
||||||
|
@ -38,8 +38,11 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
|
|||||||
@Override
|
@Override
|
||||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
|
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
int initialBufferSize = msg.readableBytes() <= threshold ? msg.readableBytes() + 1 :
|
// Follow the advice of https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103
|
||||||
msg.readableBytes() / 3;
|
// 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);
|
return MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, initialBufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,43 +1,88 @@
|
|||||||
package com.velocitypowered.proxy.protocol.netty;
|
package com.velocitypowered.proxy.protocol.netty;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.util.except.QuietException;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
import io.netty.handler.codec.CorruptedFrameException;
|
import io.netty.util.ByteProcessor;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||||
|
|
||||||
|
private static final QuietException BAD_LENGTH_CACHED = new QuietException("Bad packet length");
|
||||||
|
private static final QuietException VARINT_BIG_CACHED = new QuietException("VarInt too big");
|
||||||
|
private final VarintByteDecoder reader = new VarintByteDecoder();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||||
if (!in.isReadable()) {
|
if (!ctx.channel().isActive()) {
|
||||||
|
in.skipBytes(in.readableBytes());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int origReaderIndex = in.readerIndex();
|
while (in.isReadable()) {
|
||||||
for (int i = 0; i < 3; i++) {
|
reader.reset();
|
||||||
if (!in.isReadable()) {
|
|
||||||
in.readerIndex(origReaderIndex);
|
int varintEnd = in.forEachByte(reader);
|
||||||
|
if (varintEnd == -1) {
|
||||||
|
// We tried to go beyond the end of the buffer. This is probably a good sign that the
|
||||||
|
// buffer was too short to hold a proper varint.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte read = in.readByte();
|
if (reader.result == DecodeResult.SUCCESS) {
|
||||||
if (read >= 0) {
|
if (reader.readVarint < 0) {
|
||||||
// Make sure reader index of length buffer is returned to the beginning
|
throw BAD_LENGTH_CACHED;
|
||||||
in.readerIndex(origReaderIndex);
|
} else if (reader.readVarint == 0) {
|
||||||
int packetLength = ProtocolUtils.readVarInt(in);
|
// skip over the empty packet and ignore it
|
||||||
|
in.readerIndex(varintEnd + 1);
|
||||||
if (in.readableBytes() >= packetLength) {
|
} else {
|
||||||
out.add(in.readRetainedSlice(packetLength));
|
int minimumRead = reader.bytesRead + reader.readVarint;
|
||||||
|
if (in.isReadable(minimumRead)) {
|
||||||
|
out.add(in.retainedSlice(varintEnd + 1, reader.readVarint));
|
||||||
|
in.skipBytes(minimumRead);
|
||||||
} else {
|
} else {
|
||||||
in.readerIndex(origReaderIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (reader.result == DecodeResult.TOO_BIG) {
|
||||||
|
throw VARINT_BIG_CACHED;
|
||||||
|
} else if (reader.result == DecodeResult.TOO_SHORT) {
|
||||||
|
// No-op: we couldn't get a useful result.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw new CorruptedFrameException("VarInt too big");
|
private static class VarintByteDecoder implements ByteProcessor {
|
||||||
|
private int readVarint;
|
||||||
|
private int bytesRead;
|
||||||
|
private DecodeResult result = DecodeResult.TOO_SHORT;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(byte k) {
|
||||||
|
readVarint |= (k & 0x7F) << bytesRead++ * 7;
|
||||||
|
if (bytesRead > 3) {
|
||||||
|
result = DecodeResult.TOO_BIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((k & 0x80) != 128) {
|
||||||
|
result = DecodeResult.SUCCESS;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
readVarint = 0;
|
||||||
|
bytesRead = 0;
|
||||||
|
result = DecodeResult.TOO_SHORT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DecodeResult {
|
||||||
|
SUCCESS,
|
||||||
|
TOO_SHORT,
|
||||||
|
TOO_BIG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren