From a49a77a7ef9221b62be89e0e5cddc947bbb984a4 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 11 Sep 2020 01:46:59 -0400 Subject: [PATCH 01/24] Working on improvements to natives. Not done yet. --- native/src/main/c/jni_cipher.c | 6 +++--- native/src/main/c/jni_zlib_deflate.c | 6 +++--- native/src/main/c/jni_zlib_inflate.c | 6 +++--- .../compression/LibdeflateVelocityCompressor.java | 14 ++++++-------- .../natives/compression/NativeZlibDeflate.java | 6 +++--- .../natives/compression/NativeZlibInflate.java | 8 ++++---- .../natives/encryption/NativeVelocityCipher.java | 8 +++----- .../natives/encryption/OpenSslCipherImpl.java | 6 +++--- 8 files changed, 28 insertions(+), 32 deletions(-) diff --git a/native/src/main/c/jni_cipher.c b/native/src/main/c/jni_cipher.c index e0b0b84e8..1872865b7 100644 --- a/native/src/main/c/jni_cipher.c +++ b/native/src/main/c/jni_cipher.c @@ -8,7 +8,7 @@ typedef unsigned char byte; JNIEXPORT jlong JNICALL Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, - jobject obj, + jclass clazz, jbyteArray key, jboolean encrypt) { @@ -41,7 +41,7 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, JNIEXPORT void JNICALL Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_free(JNIEnv *env, - jobject obj, + jclass clazz, jlong ptr) { EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *) ptr); @@ -49,7 +49,7 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_free(JNIEnv *env, JNIEXPORT void JNICALL Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_process(JNIEnv *env, - jobject obj, + jclass clazz, jlong ptr, jlong source, jint len, diff --git a/native/src/main/c/jni_zlib_deflate.c b/native/src/main/c/jni_zlib_deflate.c index 7a84a5b04..9a954081c 100644 --- a/native/src/main/c/jni_zlib_deflate.c +++ b/native/src/main/c/jni_zlib_deflate.c @@ -7,7 +7,7 @@ JNIEXPORT jlong JNICALL Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env, - jobject obj, + jclass clazz, jint level) { struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level); @@ -21,7 +21,7 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env, JNIEXPORT void JNICALL Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env, - jobject obj, + jclass clazz, jlong ctx) { libdeflate_free_compressor((struct libdeflate_compressor *) ctx); @@ -29,7 +29,7 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env, JNIEXPORT jboolean JNICALL Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env, - jobject obj, + jclass clazz, jlong ctx, jlong sourceAddress, jint sourceLength, diff --git a/native/src/main/c/jni_zlib_inflate.c b/native/src/main/c/jni_zlib_inflate.c index 9a81f0db8..d6a0c09df 100644 --- a/native/src/main/c/jni_zlib_inflate.c +++ b/native/src/main/c/jni_zlib_inflate.c @@ -7,7 +7,7 @@ JNIEXPORT jlong JNICALL Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env, - jobject obj) + jclass clazz) { struct libdeflate_decompressor *decompress = libdeflate_alloc_decompressor(); if (decompress == NULL) { @@ -21,7 +21,7 @@ Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env, JNIEXPORT void JNICALL Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env, - jobject obj, + jclass clazz, jlong ctx) { libdeflate_free_decompressor((struct libdeflate_decompressor *) ctx); @@ -29,7 +29,7 @@ Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env, JNIEXPORT jboolean JNICALL Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env, - jobject obj, + jclass clazz, jlong ctx, jlong sourceAddress, jint sourceLength, diff --git a/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java index e748fd70b..ab3e79108 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java @@ -9,9 +9,7 @@ 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; @@ -21,8 +19,8 @@ public class LibdeflateVelocityCompressor implements VelocityCompressor { throw new IllegalArgumentException("Invalid compression level " + level); } - this.inflateCtx = inflate.init(); - this.deflateCtx = deflate.init(correctedLevel); + this.inflateCtx = NativeZlibInflate.init(); + this.deflateCtx = NativeZlibDeflate.init(correctedLevel); } @Override @@ -40,7 +38,7 @@ public class LibdeflateVelocityCompressor implements VelocityCompressor { long sourceAddress = source.memoryAddress() + source.readerIndex(); long destinationAddress = destination.memoryAddress() + destination.writerIndex(); - inflate.process(inflateCtx, sourceAddress, source.readableBytes(), destinationAddress, + NativeZlibInflate.process(inflateCtx, sourceAddress, source.readableBytes(), destinationAddress, uncompressedSize); destination.writerIndex(destination.writerIndex() + uncompressedSize); } @@ -53,7 +51,7 @@ public class LibdeflateVelocityCompressor implements VelocityCompressor { long sourceAddress = source.memoryAddress() + source.readerIndex(); long destinationAddress = destination.memoryAddress() + destination.writerIndex(); - int produced = deflate.process(deflateCtx, sourceAddress, source.readableBytes(), + int produced = NativeZlibDeflate.process(deflateCtx, sourceAddress, source.readableBytes(), destinationAddress, destination.writableBytes()); if (produced > 0) { destination.writerIndex(destination.writerIndex() + produced); @@ -72,8 +70,8 @@ public class LibdeflateVelocityCompressor implements VelocityCompressor { @Override public void close() { if (!disposed) { - inflate.free(inflateCtx); - deflate.free(deflateCtx); + NativeZlibInflate.free(inflateCtx); + NativeZlibDeflate.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 index eb89412cb..83cdc5cd1 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java @@ -5,10 +5,10 @@ package com.velocitypowered.natives.compression; */ class NativeZlibDeflate { - native long init(int level); + static native long init(int level); - native long free(long ctx); + static native long free(long ctx); - native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, + static native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength); } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java index fc6e9787f..1c6c42594 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java @@ -7,10 +7,10 @@ import java.util.zip.DataFormatException; */ class NativeZlibInflate { - native long init(); + static native long init(); - native long free(long ctx); + static native long free(long ctx); - native boolean process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, - int destinationLength) throws DataFormatException; + static native boolean process(long ctx, long sourceAddress, int sourceLength, + long destinationAddress, int destinationLength) throws DataFormatException; } diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/NativeVelocityCipher.java b/native/src/main/java/com/velocitypowered/natives/encryption/NativeVelocityCipher.java index bee59ac7a..814971c4e 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/NativeVelocityCipher.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/NativeVelocityCipher.java @@ -19,13 +19,11 @@ public class NativeVelocityCipher implements VelocityCipher { return new NativeVelocityCipher(false, key); } }; - private static final OpenSslCipherImpl impl = new OpenSslCipherImpl(); - private final long ctx; private boolean disposed = false; private NativeVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException { - this.ctx = impl.init(key.getEncoded(), encrypt); + this.ctx = OpenSslCipherImpl.init(key.getEncoded(), encrypt); } @Override @@ -35,13 +33,13 @@ public class NativeVelocityCipher implements VelocityCipher { long base = source.memoryAddress() + source.readerIndex(); int len = source.readableBytes(); - impl.process(ctx, base, len, base); + OpenSslCipherImpl.process(ctx, base, len, base); } @Override public void close() { if (!disposed) { - impl.free(ctx); + OpenSslCipherImpl.free(ctx); } disposed = true; } diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/OpenSslCipherImpl.java b/native/src/main/java/com/velocitypowered/natives/encryption/OpenSslCipherImpl.java index da5fc6b50..f97eef3df 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/OpenSslCipherImpl.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/OpenSslCipherImpl.java @@ -4,9 +4,9 @@ import java.security.GeneralSecurityException; class OpenSslCipherImpl { - native long init(byte[] key, boolean encrypt) throws GeneralSecurityException; + static native long init(byte[] key, boolean encrypt) throws GeneralSecurityException; - native void process(long ctx, long source, int len, long dest); + static native void process(long ctx, long source, int len, long dest); - native void free(long ptr); + static native void free(long ptr); } From bb31226e091da3738b6fcfa80f572e4a3163f738 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 16 Sep 2020 00:07:52 -0400 Subject: [PATCH 02/24] Clean up more cipher code and make it -Wall + -Werror clean --- native/compile-linux.sh | 12 +++++++++--- native/src/main/c/jni_cipher.c | 19 +++++++++++-------- native/src/main/c/jni_zlib_deflate.c | 3 +-- native/src/main/c/jni_zlib_inflate.c | 4 ++++ 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/native/compile-linux.sh b/native/compile-linux.sh index 6d92b6462..40744df9b 100755 --- a/native/compile-linux.sh +++ b/native/compile-linux.sh @@ -1,5 +1,11 @@ #!/bin/bash +if [ ! "$CC" ]; then + # The libdeflate authors recommend that we build using GCC as it produces "slightly faster binaries": + # https://github.com/ebiggers/libdeflate#for-unix + export CC=gcc +fi + if [ ! -d libdeflate ]; then echo "Cloning libdeflate..." git clone https://github.com/ebiggers/libdeflate.git @@ -10,10 +16,10 @@ cd libdeflate || exit CFLAGS="-fPIC -O2" make cd .. -CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack" +CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -Wall -Werror" ARCH=$(uname -m) 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 \ +$CC $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 src/main/c/jni_util.c src/main/c/jni_cipher.c \ +$CC $CFLAGS -shared src/main/c/jni_util.c src/main/c/jni_cipher.c \ -o src/main/resources/linux_$ARCH/velocity-cipher.so -lcrypto \ No newline at end of file diff --git a/native/src/main/c/jni_cipher.c b/native/src/main/c/jni_cipher.c index 1872865b7..83515be52 100644 --- a/native/src/main/c/jni_cipher.c +++ b/native/src/main/c/jni_cipher.c @@ -12,25 +12,28 @@ Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, jbyteArray key, jboolean encrypt) { + jsize keyLen = (*env)->GetArrayLength(env, key); + if (keyLen != 16) { + throwException(env, "java/lang/IllegalArgumentException", "cipher not 16 bytes"); + return 0; + } + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { throwException(env, "java/lang/OutOfMemoryError", "allocate cipher"); return 0; } - jsize keyLen = (*env)->GetArrayLength(env, key); - jbyte* keyBytes = (*env)->GetPrimitiveArrayCritical(env, key, NULL); - if (keyBytes == NULL) { - EVP_CIPHER_CTX_free(ctx); - throwException(env, "java/lang/OutOfMemoryError", "cipher get key"); + // Since we know the array size is always bounded, we can just use GetArrayRegion + // and save ourselves some error-checking headaches. + jbyte keyBytes[16]; + (*env)->GetByteArrayRegion(env, key, 0, keyLen, (jbyte*) keyBytes); + if ((*env)->ExceptionCheck(env)) { return 0; } int result = EVP_CipherInit(ctx, EVP_aes_128_cfb8(), (byte*) keyBytes, (byte*) keyBytes, encrypt); - // Release the key byte array now - we won't need it - (*env)->ReleasePrimitiveArrayCritical(env, key, keyBytes, 0); - if (result != 1) { EVP_CIPHER_CTX_free(ctx); throwException(env, "java/security/GeneralSecurityException", "openssl initialize cipher"); diff --git a/native/src/main/c/jni_zlib_deflate.c b/native/src/main/c/jni_zlib_deflate.c index 9a954081c..8dcd60f09 100644 --- a/native/src/main/c/jni_zlib_deflate.c +++ b/native/src/main/c/jni_zlib_deflate.c @@ -34,8 +34,7 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *e jlong sourceAddress, jint sourceLength, jlong destinationAddress, - jint destinationLength, - jboolean finish) + jint destinationLength) { struct libdeflate_compressor *compressor = (struct libdeflate_compressor *) ctx; size_t produced = libdeflate_zlib_compress(compressor, (void *) sourceAddress, sourceLength, diff --git a/native/src/main/c/jni_zlib_inflate.c b/native/src/main/c/jni_zlib_inflate.c index d6a0c09df..d91319089 100644 --- a/native/src/main/c/jni_zlib_inflate.c +++ b/native/src/main/c/jni_zlib_inflate.c @@ -53,5 +53,9 @@ Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *e // These cases are the same for us. We expect the full uncompressed size to be known. throwException(env, "java/util/zip/DataFormatException", "uncompressed size is inaccurate"); return JNI_FALSE; + default: + // Unhandled case + throwException(env, "java/util/zip/DataFormatException", "unknown libdeflate return code"); + return JNI_FALSE; } } \ No newline at end of file From 278930a0083707de2b64080f2f9e252844de339a Mon Sep 17 00:00:00 2001 From: A248 Date: Mon, 26 Oct 2020 13:52:04 -0400 Subject: [PATCH 03/24] Handle exceptions relating to CompletableFuture operations Solves #374 --- .../velocitypowered/proxy/VelocityServer.java | 5 ++- .../backend/BackendPlaySessionHandler.java | 12 +++++-- .../client/ClientPlaySessionHandler.java | 36 ++++++++++++++++--- .../connection/client/ConnectedPlayer.java | 8 ++++- .../client/LoginSessionHandler.java | 22 ++++++++++-- .../client/StatusSessionHandler.java | 12 +++++-- .../proxy/plugin/VelocityEventManager.java | 8 ++--- .../proxy/protocol/netty/GS4QueryHandler.java | 8 ++++- 8 files changed, 91 insertions(+), 20 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 54c446a88..863a481c7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -425,8 +425,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { .toArray((IntFunction[]>) CompletableFuture[]::new)); playersTeardownFuture.get(10, TimeUnit.SECONDS); - } catch (TimeoutException | ExecutionException e) { + } catch (TimeoutException e) { timedOut = true; + } catch (ExecutionException e) { + timedOut = true; + logger.error("Exception while tearing down player connections", e); } eventManager.fireShutdownEvent(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index bbba322d9..abd1f7b91 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -149,7 +149,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { Unpooled.wrappedBuffer(copy)); playerConnection.write(copied); } - }, playerConnection.eventLoop()); + }, playerConnection.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while handling plugin message {}", packet, ex); + return null; + }); return true; } @@ -186,7 +190,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { server.getEventManager().fire( new PlayerAvailableCommandsEvent(serverConn.getPlayer(), rootNode)) - .thenAcceptAsync(event -> playerConnection.write(commands), playerConnection.eventLoop()); + .thenAcceptAsync(event -> playerConnection.write(commands), playerConnection.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while handling available commands for {}", playerConnection, ex); + return null; + }); return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index c2f31eb98..956772000 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -157,7 +157,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { smc.write(packet); } } - }, smc.eventLoop()); + }, smc.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while handling player chat for {}", player, ex); + return null; + }); } return true; } @@ -225,7 +229,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { Unpooled.wrappedBuffer(copy)); backendConn.write(message); } - }, backendConn.eventLoop()); + }, backendConn.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while handling plugin message packet for {}", + player, ex); + return null; + }); } } } @@ -423,7 +432,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { resp.getOffers().addAll(offers); player.getConnection().write(resp); } - }, player.getConnection().eventLoop()); + }, player.getConnection().eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while handling command tab completion for player {} executing {}", + player, command, ex); + return null; + }); return true; // Sorry, handler; we're just gonna have to lie to you here. } @@ -475,7 +489,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getUsername(), command, e); } - }, player.getConnection().eventLoop()); + }, player.getConnection().eventLoop()) + .exceptionally((ex) -> { + logger.error( + "Exception while finishing command tab completion, with request {} and response {}", + request, response, ex); + return null; + }); } private void finishRegularTabComplete(TabCompleteRequest request, TabCompleteResponse response) { @@ -490,7 +510,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { response.getOffers().add(new Offer(s)); } player.getConnection().write(response); - }, player.getConnection().eventLoop()); + }, player.getConnection().eventLoop()) + .exceptionally((ex) -> { + logger.error( + "Exception while finishing regular tab completion, with request {} and response{}", + request, response, ex); + return null; + }); } private CompletableFuture processCommandExecuteResult(String originalCommand, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 2cad65f05..f635babf5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -757,7 +757,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } DisconnectEvent event = new DisconnectEvent(this, status); - server.getEventManager().fire(event).thenRun(() -> this.teardownFuture.complete(null)); + server.getEventManager().fire(event).whenComplete((val, ex) -> { + if (ex == null) { + this.teardownFuture.complete(null); + } else { + this.teardownFuture.completeExceptionally(ex); + } + }); } public CompletableFuture getTeardownFuture() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index 826baec66..fbfca874f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -183,7 +183,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } else { initializePlayer(GameProfile.forOfflinePlayer(login.getUsername()), false); } - }, mcConnection.eventLoop()); + }, mcConnection.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception in pre-login stage", ex); + return null; + }); } private EncryptionRequest generateEncryptionRequest() { @@ -202,6 +206,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { server.getConfiguration().getPlayerInfoForwardingMode()); GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(inbound, profile, onlineMode); + final GameProfile finalProfile = profile; server.getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> { if (mcConnection.isClosed()) { @@ -229,6 +234,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler { completeLoginProtocolPhaseAndInitialize(player); } }, mcConnection.eventLoop()); + }).exceptionally((ex) -> { + logger.error("Exception during connection of {}", finalProfile, ex); + return null; }); } @@ -274,7 +282,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler { server.getEventManager().fire(new PostLoginEvent(player)) .thenRun(() -> connectToInitialServer(player)); } - }, mcConnection.eventLoop()); + }, mcConnection.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while completing login initialisation phase for {}", player, ex); + return null; + }); } private void connectToInitialServer(ConnectedPlayer player) { @@ -291,7 +303,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler { return; } player.createConnectionRequest(toTry.get()).fireAndForget(); - }, mcConnection.eventLoop()); + }, mcConnection.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while connecting {} to initial server", player, ex); + return null; + }); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java index cffc71377..2b44c654b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java @@ -163,7 +163,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler { .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping))) .thenAcceptAsync(event -> connection.closeWith( LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())), - connection.eventLoop()); + connection.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while handling legacy ping {}", packet, ex); + return null; + }); return true; } @@ -189,7 +193,11 @@ public class StatusSessionHandler implements MinecraftSessionHandler { .toJson(event.getPing(), json); connection.write(new StatusResponse(json)); }, - connection.eventLoop()); + connection.eventLoop()) + .exceptionally((ex) -> { + logger.error("Exception while handling status request {}", packet, ex); + return null; + }); return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java index 8d9b40bbf..5d0d58551 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java @@ -115,12 +115,10 @@ public class VelocityEventManager implements EventManager { return CompletableFuture.completedFuture(event); } - CompletableFuture eventFuture = new CompletableFuture<>(); - service.execute(() -> { + return CompletableFuture.supplyAsync(() -> { fireEvent(event); - eventFuture.complete(event); - }); - return eventFuture; + return event; + }, service); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java index 888f6ab29..ffe2a24b4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java @@ -29,6 +29,7 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; +import org.apache.logging.log4j.LogManager; public class GS4QueryHandler extends SimpleChannelInboundHandler { @@ -162,7 +163,12 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler // Send the response DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender()); ctx.writeAndFlush(responsePacket, ctx.voidPromise()); - }, ctx.channel().eventLoop()); + }, ctx.channel().eventLoop()) + .exceptionally((ex) -> { + LogManager.getLogger(getClass()).error( + "Exception while writing GS4 response for query from {}", senderAddress, ex); + return null; + }); break; } default: From 8fbce8423f3e714f4e68cac7c9da071538643dfd Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 26 Oct 2020 17:20:52 -0400 Subject: [PATCH 04/24] Fix typo in ServerPing#asBuilder() Javadoc Fixes #375 --- .../com/velocitypowered/api/proxy/server/ServerPing.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index f1979d491..dcb44f7ff 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -133,8 +133,9 @@ public final class ServerPing { /** * Returns a copy of this {@link ServerPing} instance as a builder so that it can be modified. - * It is guaranteed that {@code ping.asBuilder().ping().equals(ping)}: that is, if no other - * changes are made to the returned builder, the built instance will equal the original instance. + * It is guaranteed that {@code ping.asBuilder().build().equals(ping)} is true: that is, if no + * other changes are made to the returned builder, the built instance will equal the original + * instance. * * @return a copy of this instance as a {@link Builder} */ From 6331e1af3ef9a848cb654ce8c63b782c3a36e10a Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Fri, 23 Oct 2020 18:08:02 +0200 Subject: [PATCH 05/24] Velocity Dump WIP --- .../command/builtin/VelocityCommand.java | 49 ++++++ .../proxy/config/VelocityConfiguration.java | 55 ++++--- .../proxy/util/InformationUtils.java | 154 ++++++++++++++++++ 3 files changed, 231 insertions(+), 27 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 02c085241..52aa7468a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -3,19 +3,27 @@ package com.velocitypowered.proxy.command.builtin; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; + +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonObject; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.util.InformationUtils; + import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; + import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -52,6 +60,7 @@ public class VelocityCommand implements SimpleCommand { .put("version", new Info(server)) .put("plugins", new Plugins(server)) .put("reload", new Reload(server)) + .put("dump", new Dump(server)) .build(); } @@ -289,4 +298,44 @@ public class VelocityCommand implements SimpleCommand { return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE; } } + + private static class Dump implements SubCommand { + + private final ProxyServer server; + + private Dump(ProxyServer server) { + this.server = server; + } + + @Override + public void execute(CommandSource source, String @NonNull [] args) { + if (args.length != 0) { + source.sendMessage(Identity.nil(), Component.text("/velocity dump", NamedTextColor.RED)); + return; + } + + Collection allServers = ImmutableSet.copyOf(server.getAllServers()); + JsonObject servers = new JsonObject(); + for (RegisteredServer iter : allServers) { + servers.add(iter.getServerInfo().getName(), + InformationUtils.collectServerInfo(iter)); + } + + JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.getConfiguration()); + proxyConfig.add("servers", servers); + + JsonObject dump = new JsonObject(); + dump.add("versionInfo", InformationUtils.collectProxyInfo(server.getVersion())); + dump.add("platform", InformationUtils.collectEnvironmentInfo()); + dump.add("config", proxyConfig); + dump.add("plugins", InformationUtils.collectPluginInfo(server)); + + // TODO: Finish + } + + @Override + public boolean hasPermission(final CommandSource source, final String @NonNull [] args) { + return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index a3f7537f2..5660b913e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -7,6 +7,7 @@ import com.electronwill.nightconfig.toml.TomlFormat; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.gson.annotations.Expose; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; @@ -42,20 +43,20 @@ public class VelocityConfiguration implements ProxyConfig { private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class); - private String bind = "0.0.0.0:25577"; - private String motd = "&3A Velocity Server"; - private int showMaxPlayers = 500; - private boolean onlineMode = true; - private boolean preventClientProxyConnections = false; - private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; + @Expose private String bind = "0.0.0.0:25577"; + @Expose private String motd = "&3A Velocity Server"; + @Expose private int showMaxPlayers = 500; + @Expose private boolean onlineMode = true; + @Expose private boolean preventClientProxyConnections = false; + @Expose private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); - private boolean announceForge = false; - private boolean onlineModeKickExistingPlayers = false; - private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; + @Expose private boolean announceForge = false; + @Expose private boolean onlineModeKickExistingPlayers = false; + @Expose private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; private final Servers servers; private final ForcedHosts forcedHosts; - private final Advanced advanced; - private final Query query; + @Expose private final Advanced advanced; + @Expose private final Query query; private final Metrics metrics; private final Messages messages; private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent; @@ -622,18 +623,18 @@ public class VelocityConfiguration implements ProxyConfig { private static class Advanced { - private int compressionThreshold = 256; - private int compressionLevel = -1; - private int loginRatelimit = 3000; - private int connectionTimeout = 5000; - private int readTimeout = 30000; - private boolean proxyProtocol = false; - private boolean tcpFastOpen = false; - private boolean bungeePluginMessageChannel = true; - private boolean showPingRequests = false; - private boolean failoverOnUnexpectedServerDisconnect = true; - private boolean announceProxyCommands = true; - private boolean logCommandExecutions = false; + @Expose private int compressionThreshold = 256; + @Expose private int compressionLevel = -1; + @Expose private int loginRatelimit = 3000; + @Expose private int connectionTimeout = 5000; + @Expose private int readTimeout = 30000; + @Expose private boolean proxyProtocol = false; + @Expose private boolean tcpFastOpen = false; + @Expose private boolean bungeePluginMessageChannel = true; + @Expose private boolean showPingRequests = false; + @Expose private boolean failoverOnUnexpectedServerDisconnect = true; + @Expose private boolean announceProxyCommands = true; + @Expose private boolean logCommandExecutions = false; private Advanced() { } @@ -725,10 +726,10 @@ public class VelocityConfiguration implements ProxyConfig { private static class Query { - private boolean queryEnabled = false; - private int queryPort = 25577; - private String queryMap = "Velocity"; - private boolean showPlugins = false; + @Expose private boolean queryEnabled = false; + @Expose private int queryPort = 25577; + @Expose private String queryMap = "Velocity"; + @Expose private boolean showPlugins = false; private Query() { } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java new file mode 100644 index 000000000..ebc1dd015 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -0,0 +1,154 @@ +package com.velocitypowered.proxy.util; + +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.meta.PluginDependency; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.config.ProxyConfig; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.util.ProxyVersion; + +import io.netty.channel.unix.DomainSocketAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; +import joptsimple.internal.Strings; + +public enum InformationUtils { + ; + + /** + * Retrieves a {@link JsonArray} containing basic information about all + * running plugins on the {@link ProxyServer} instance. + * + * @param proxy the proxy instance to retrieve from + * @return {@link JsonArray} containing zero or more {@link JsonObject} + */ + public static JsonArray collectPluginInfo(ProxyServer proxy) { + List allPlugins = ImmutableList.copyOf( + proxy.getPluginManager().getPlugins()); + JsonArray plugins = new JsonArray(); + + for (PluginContainer plugin : allPlugins) { + PluginDescription desc = plugin.getDescription(); + JsonObject current = new JsonObject(); + current.addProperty("id", desc.getId()); + if (desc.getName().isPresent()) { + current.addProperty("name", desc.getName().get()); + } + if (desc.getVersion().isPresent()) { + current.addProperty("version", desc.getVersion().get()); + } + if (!desc.getAuthors().isEmpty()) { + current.addProperty("authors", + Strings.join(desc.getAuthors(), ",")); + } + if (desc.getDescription().isPresent()) { + current.addProperty("description", desc.getDescription().get()); + } + if (desc.getUrl().isPresent()) { + current.addProperty("url", desc.getUrl().get()); + } + if (!desc.getDependencies().isEmpty()) { + JsonArray dependencies = new JsonArray(); + for (PluginDependency dependency : desc.getDependencies()) { + dependencies.add(dependency.getId()); + } + current.add("dependencies", dependencies); + } + plugins.add(current); + } + return plugins; + } + + /** + * Creates a {@link JsonObject} containing information about the + * current environment the project is run under. + * + * @return {@link JsonObject} containing environment info + */ + public static JsonObject collectEnvironmentInfo() { + JsonObject envInfo = new JsonObject(); + envInfo.addProperty("operatingSystemType", System.getProperty("os.name")); + envInfo.addProperty("operatingSystemVersion", System.getProperty("os.version")); + envInfo.addProperty("operatingSystemArchitecture", System.getProperty("os.arch")); + envInfo.addProperty("javaVersion", System.getProperty("java.version")); + envInfo.addProperty("javaVendor", System.getProperty("java.vendor")); + return envInfo; + } + + /** + * Creates a {@link JsonObject} containing most relevant + * information of the {@link RegisteredServer} for diagnosis. + * + * @param server the server to evaluate + * @return {@link JsonObject} containing server and diagnostic info + */ + public static JsonObject collectServerInfo(RegisteredServer server) { + JsonObject info = new JsonObject(); + info.addProperty("currentPlayers", server.getPlayersConnected().size()); + SocketAddress address = server.getServerInfo().getAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress iaddr = (InetSocketAddress) address; + info.addProperty("socketType", "EventLoop"); + info.addProperty("unresolved", iaddr.isUnresolved()); + // Greetings form Netty 4aa10db9 + info.addProperty("host", iaddr.getHostString()); + info.addProperty("port", iaddr.getPort()); + } else if (address instanceof DomainSocketAddress) { + DomainSocketAddress daddr = (DomainSocketAddress) address; + info.addProperty("socketType", "Unix/Epoll"); + info.addProperty("host", daddr.path()); + } else { + info.addProperty("socketType", "Unknown/Generic"); + info.addProperty("host", address.toString()); + } + return info; + } + + /** + * Creates a {@link JsonObject} containing information about the + * current environment the project is run under. + * + * @param version the proxy instance to retrieve from + * @return {@link JsonObject} containing environment info + */ + public static JsonObject collectProxyInfo(ProxyVersion version) { + return (JsonObject) serializeObject(version, false); + } + + /** + * Creates a {@link JsonObject} containing most relevant + * information of the {@link ProxyConfig} for diagnosis. + * + * @param config the config instance to retrieve from + * @return {@link JsonObject} containing select config values + */ + public static JsonObject collectProxyConfig(ProxyConfig config) { + return (JsonObject) serializeObject(config, true); + } + + private static JsonElement serializeObject(Object toSerialize, boolean withExcludes) { + return JsonParser.parseString( + withExcludes ? GSON_WITH_EXCLUDES.toJson(toSerialize) : + GSON_WITHOUT_EXCLUDES.toJson(toSerialize)); + } + + private static final Gson GSON_WITH_EXCLUDES = new GsonBuilder() + .setPrettyPrinting() + .excludeFieldsWithoutExposeAnnotation() + .create(); + + private static final Gson GSON_WITHOUT_EXCLUDES = new GsonBuilder() + .setPrettyPrinting() + .create(); + + +} From 140eaaf5abe79270e6cf3bac25a8182524c27b8c Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sun, 25 Oct 2020 20:04:52 +0100 Subject: [PATCH 06/24] Velocity Dump WIP Part 2 --- .../command/builtin/VelocityCommand.java | 104 ++++++++++++++++- .../proxy/util/InformationUtils.java | 110 +++++++++++++++++- 2 files changed, 208 insertions(+), 6 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 52aa7468a..08ce75991 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -5,7 +5,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.io.CharStreams; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.permission.Tristate; @@ -17,6 +21,12 @@ import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.util.InformationUtils; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -320,9 +330,18 @@ public class VelocityCommand implements SimpleCommand { servers.add(iter.getServerInfo().getName(), InformationUtils.collectServerInfo(iter)); } + JsonArray connectOrder = new JsonArray(); + List attemptedConnectionOrder = ImmutableList.copyOf( + server.getConfiguration().getAttemptConnectionOrder()); + for (int i = 0; i < attemptedConnectionOrder.size(); i++) { + connectOrder.add(attemptedConnectionOrder.get(i)); + } JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.getConfiguration()); proxyConfig.add("servers", servers); + proxyConfig.add("connectOrder", connectOrder); + proxyConfig.add("forcedHosts", + InformationUtils.collectForcedHosts(server.getConfiguration())); JsonObject dump = new JsonObject(); dump.add("versionInfo", InformationUtils.collectProxyInfo(server.getVersion())); @@ -330,7 +349,90 @@ public class VelocityCommand implements SimpleCommand { dump.add("config", proxyConfig); dump.add("plugins", InformationUtils.collectPluginInfo(server)); - // TODO: Finish + source.sendMessage(Component.text().content("Uploading gathered information...").build()); + + HttpURLConnection upload = null; + try { + upload = (HttpURLConnection) new URL("https://dump.velocitypowered.com/documents") + .openConnection(); + } catch (IOException e1) { + // Couldn't open connection; + source.sendMessage( + Component.text() + .content("Failed to open a connection!") + .color(NamedTextColor.RED).build()); + return; + } + upload.setRequestProperty("Content-Type", "text/plain"); + upload.addRequestProperty( + "User-Agent", server.getVersion().getName() + "/" + + server.getVersion().getVersion()); + try { + upload.setRequestMethod("POST"); + upload.setDoOutput(true); + + OutputStream uploadStream = upload.getOutputStream(); + uploadStream.write( + InformationUtils.toHumanReadableString(dump).getBytes(StandardCharsets.UTF_8)); + uploadStream.close(); + } catch (IOException e2) { + // Couldn't POST the Data + source.sendMessage( + Component.text() + .content("Couldn't upload the data!") + .color(NamedTextColor.RED).build()); + return; + } + String rawResponse = null; + try { + rawResponse = CharStreams.toString( + new InputStreamReader(upload.getInputStream(), StandardCharsets.UTF_8)); + upload.getInputStream().close(); + } catch (IOException e3) { + // Couldn't read response + source.sendMessage( + Component.text() + .content("Invalid server response received!") + .color(NamedTextColor.RED).build()); + } + JsonObject returned = null; + try { + returned = InformationUtils.parseString(rawResponse); + if (returned == null || !returned.has("key")) { + throw new JsonParseException("Invalid json response"); + } + } catch (JsonSyntaxException e4) { + // Mangled json + source.sendMessage( + Component.text() + .content("Server responded with invalid data!") + .color(NamedTextColor.RED).build()); + return; + } catch (JsonParseException e5) { + // Backend error? + source.sendMessage( + Component.text() + .content("Data was uploaded successfully but couldn't be posted") + .color(NamedTextColor.RED).build()); + return; + } + TextComponent response = Component.text() + .content("Created an anonymised report containing useful information about") + .append(Component.newline() + .append( + Component.text("this proxy. If a developer requested it" + + ", you may share the")) + .append(Component.newline()) + .append(Component.text("following link with them:")) + .append(Component.newline()) + .append(Component.text("https://dump.velocitypowered.com/" + + returned.get("key").getAsString() + ".json") + .color(NamedTextColor.GREEN))) + .append(Component.newline()) + .append(Component.text("Note: This link is only valid for a few days")) + .build(); + source.sendMessage(response); + } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java index ebc1dd015..9016ae3ff 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -1,6 +1,7 @@ package com.velocitypowered.proxy.util; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -16,9 +17,15 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.ProxyVersion; import io.netty.channel.unix.DomainSocketAddress; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.List; +import java.util.Map; + import joptsimple.internal.Strings; public enum InformationUtils { @@ -84,6 +91,75 @@ public enum InformationUtils { return envInfo; } + /** + * Creates a {@link JsonObject} containing information about the + * forced hosts of the {@link ProxyConfig} instance. + * + * @return {@link JsonArray} containing forced hosts + */ + public static JsonObject collectForcedHosts(ProxyConfig config) { + JsonObject forcedHosts = new JsonObject(); + Map> allForcedHosts = ImmutableMap.copyOf( + config.getForcedHosts()); + for (Map.Entry> entry : allForcedHosts.entrySet()) { + JsonArray host = new JsonArray(); + for (int i = 0; i < entry.getValue().size(); i++) { + host.add(entry.getValue().get(i)); + } + forcedHosts.add(entry.getKey(), host); + } + return forcedHosts; + } + + /** + * Anonymises or redacts a given {@link InetAddress} + * public address bits. + * + * @param address The address to redact + * @return {@link String} address with public parts redacted + */ + public static String anonymizeInetAddress(InetAddress address) { + if (address instanceof Inet4Address) { + Inet4Address v4 = (Inet4Address) address; + if (v4.isAnyLocalAddress() || v4.isLoopbackAddress() + || v4.isLinkLocalAddress() + || v4.isSiteLocalAddress()) { + return address.getHostAddress(); + } else { + byte[] addr = v4.getAddress(); + return (addr[0] & 0xff) + "." + (addr[1] & 0xff) + ".XXX.XXX"; + } + } else if (address instanceof Inet6Address) { + Inet6Address v6 = (Inet6Address) address; + if (v6.isAnyLocalAddress() || v6.isLoopbackAddress() + || v6.isSiteLocalAddress() + || v6.isSiteLocalAddress()) { + return address.getHostAddress(); + } else { + String[] bits = v6.getHostAddress().split(":"); + String ret = ""; + boolean flag = false; + for (int iter = 0; iter < bits.length; iter++) { + if (flag) { + ret += ":X"; + continue; + } + if (!bits[iter].equals("0")) { + if (iter == 0) { + ret = bits[iter]; + } else { + ret = "::" + bits[iter]; + } + flag = true; + } + } + return ret; + } + } else { + return address.getHostAddress(); + } + } + /** * Creates a {@link JsonObject} containing most relevant * information of the {@link RegisteredServer} for diagnosis. @@ -97,18 +173,22 @@ public enum InformationUtils { SocketAddress address = server.getServerInfo().getAddress(); if (address instanceof InetSocketAddress) { InetSocketAddress iaddr = (InetSocketAddress) address; - info.addProperty("socketType", "EventLoop"); + info.addProperty("socketType", "EventLoop/NIO"); info.addProperty("unresolved", iaddr.isUnresolved()); - // Greetings form Netty 4aa10db9 - info.addProperty("host", iaddr.getHostString()); + if (iaddr.isUnresolved()) { + // Greetings form Netty 4aa10db9 + info.addProperty("host", iaddr.getHostString()); + } else { + info.addProperty("host", anonymizeInetAddress(iaddr.getAddress())); + } info.addProperty("port", iaddr.getPort()); } else if (address instanceof DomainSocketAddress) { DomainSocketAddress daddr = (DomainSocketAddress) address; info.addProperty("socketType", "Unix/Epoll"); - info.addProperty("host", daddr.path()); + info.addProperty("path", daddr.path()); } else { info.addProperty("socketType", "Unknown/Generic"); - info.addProperty("host", address.toString()); + info.addProperty("info", address.toString()); } return info; } @@ -135,6 +215,26 @@ public enum InformationUtils { return (JsonObject) serializeObject(config, true); } + /** + * Creates a human-readable String from a {@link JsonElement}. + * + * @param json the {@link JsonElement} object + * @return the human-readable String + */ + public static String toHumanReadableString(JsonElement json) { + return GSON_WITHOUT_EXCLUDES.toJson(json); + } + + /** + * Creates a {@link JsonObject} from a String. + * + * @param toParse the String to parse + * @return {@link JsonObject} object + */ + public static JsonObject parseString(String toParse) { + return GSON_WITHOUT_EXCLUDES.fromJson(toParse, JsonObject.class); + } + private static JsonElement serializeObject(Object toSerialize, boolean withExcludes) { return JsonParser.parseString( withExcludes ? GSON_WITH_EXCLUDES.toJson(toSerialize) : From 01070f8fd27643d229a16d1472b75f672cb33d82 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Tue, 27 Oct 2020 01:09:43 +0100 Subject: [PATCH 07/24] Velocity Dump Cleanup --- .../command/builtin/VelocityCommand.java | 178 +++++++++--------- .../proxy/util/InformationUtils.java | 4 - 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 08ce75991..3218960b9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -5,7 +5,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.io.CharStreams; +import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; @@ -21,17 +21,15 @@ import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.util.InformationUtils; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; +import java.net.ConnectException; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import net.kyori.adventure.identity.Identity; @@ -43,6 +41,10 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Response; import org.checkerframework.checker.nullness.qual.NonNull; public class VelocityCommand implements SimpleCommand { @@ -311,6 +313,7 @@ public class VelocityCommand implements SimpleCommand { private static class Dump implements SubCommand { + private static final Logger logger = LogManager.getLogger(Dump.class); private final ProxyServer server; private Dump(ProxyServer server) { @@ -350,89 +353,92 @@ public class VelocityCommand implements SimpleCommand { dump.add("plugins", InformationUtils.collectPluginInfo(server)); source.sendMessage(Component.text().content("Uploading gathered information...").build()); + AsyncHttpClient httpClient = ((VelocityServer) server).getAsyncHttpClient(); - HttpURLConnection upload = null; - try { - upload = (HttpURLConnection) new URL("https://dump.velocitypowered.com/documents") - .openConnection(); - } catch (IOException e1) { - // Couldn't open connection; - source.sendMessage( - Component.text() - .content("Failed to open a connection!") - .color(NamedTextColor.RED).build()); - return; - } - upload.setRequestProperty("Content-Type", "text/plain"); - upload.addRequestProperty( - "User-Agent", server.getVersion().getName() + "/" - + server.getVersion().getVersion()); - try { - upload.setRequestMethod("POST"); - upload.setDoOutput(true); + BoundRequestBuilder request = + httpClient.preparePost("https://dump.velocitypowered.com/documents"); + request.setHeader("Content-Type", "text/plain"); + request.addHeader("User-Agent", server.getVersion().getName() + "/" + + server.getVersion().getVersion()); + request.setBody( + InformationUtils.toHumanReadableString(dump).getBytes(StandardCharsets.UTF_8)); - OutputStream uploadStream = upload.getOutputStream(); - uploadStream.write( - InformationUtils.toHumanReadableString(dump).getBytes(StandardCharsets.UTF_8)); - uploadStream.close(); - } catch (IOException e2) { - // Couldn't POST the Data - source.sendMessage( - Component.text() - .content("Couldn't upload the data!") - .color(NamedTextColor.RED).build()); - return; - } - String rawResponse = null; - try { - rawResponse = CharStreams.toString( - new InputStreamReader(upload.getInputStream(), StandardCharsets.UTF_8)); - upload.getInputStream().close(); - } catch (IOException e3) { - // Couldn't read response - source.sendMessage( - Component.text() - .content("Invalid server response received!") - .color(NamedTextColor.RED).build()); - } - JsonObject returned = null; - try { - returned = InformationUtils.parseString(rawResponse); - if (returned == null || !returned.has("key")) { - throw new JsonParseException("Invalid json response"); + ListenableFuture future = request.execute(); + future.addListener(() -> { + try { + Response response = future.get(); + if (response.getStatusCode() != 200) { + source.sendMessage(Component.text() + .content("An error occurred while communicating with the Velocity servers. " + + "The servers may be temporarily unavailable or there is an issue " + + "with your network settings. You can find more information in the " + + "log or console of your Velocity server.") + .color(NamedTextColor.RED).build()); + logger.error("Invalid status code while POST-ing Velocity dump: " + + response.getStatusCode()); + logger.error("Headers: \n--------------BEGIN HEADERS--------------\n" + + response.getHeaders().toString() + + "\n---------------END HEADERS---------------"); + return; + } + JsonObject key = InformationUtils.parseString( + response.getResponseBody(StandardCharsets.UTF_8)); + if (!key.has("key")) { + throw new JsonSyntaxException("Missing Dump-Url-response"); + } + String url = "https://dump.velocitypowered.com/" + + key.get("key").getAsString() + ".json"; + source.sendMessage(Component.text() + .content("Created an anonymised report containing useful information about " + + "this proxy. If a developer requested it, you may share the " + + "following link with them:") + .append(Component.newline()) + .append(Component.text(">> " + url) + .color(NamedTextColor.GREEN) + .clickEvent(ClickEvent.openUrl(url))) + .append(Component.newline()) + .append(Component.text("Note: This link is only valid for a few days") + .color(NamedTextColor.GRAY) + ).build()); + } catch (InterruptedException e) { + source.sendMessage(Component.text() + .content("Could not complete the request, the command was interrupted." + + "Please refer to the proxy-log or console for more information.") + .color(NamedTextColor.RED).build()); + logger.error("Failed to complete dump command, " + + "the executor was interrupted: " + e.getMessage()); + e.printStackTrace(); + } catch (ExecutionException e) { + TextComponent.Builder message = Component.text() + .content("An error occurred while attempting to upload the gathered " + + "information to the Velocity servers.") + .append(Component.newline()) + .color(NamedTextColor.RED); + if (e.getCause() instanceof UnknownHostException + || e.getCause() instanceof ConnectException) { + message.append(Component.text( + "Likely cause: Invalid system DNS settings or no internet connection")); + } + source.sendMessage(message + .append(Component.newline() + .append(Component.text( + "Error details can be found in the proxy log / console")) + ).build()); + + logger.error("Failed to complete dump command, " + + "the executor encountered an Exception: " + e.getCause().getMessage()); + e.getCause().printStackTrace(); + } catch (JsonParseException e) { + source.sendMessage(Component.text() + .content("An error occurred on the Velocity-servers and the dump could not " + + "be completed. Please contact the Velocity staff about this problem. " + + "If you do, provide the details about this error from the Velocity " + + "console or server log.") + .color(NamedTextColor.RED).build()); + logger.error("Invalid response from the Velocity servers: " + e.getMessage()); + e.printStackTrace(); } - } catch (JsonSyntaxException e4) { - // Mangled json - source.sendMessage( - Component.text() - .content("Server responded with invalid data!") - .color(NamedTextColor.RED).build()); - return; - } catch (JsonParseException e5) { - // Backend error? - source.sendMessage( - Component.text() - .content("Data was uploaded successfully but couldn't be posted") - .color(NamedTextColor.RED).build()); - return; - } - TextComponent response = Component.text() - .content("Created an anonymised report containing useful information about") - .append(Component.newline() - .append( - Component.text("this proxy. If a developer requested it" - + ", you may share the")) - .append(Component.newline()) - .append(Component.text("following link with them:")) - .append(Component.newline()) - .append(Component.text("https://dump.velocitypowered.com/" - + returned.get("key").getAsString() + ".json") - .color(NamedTextColor.GREEN))) - .append(Component.newline()) - .append(Component.text("Note: This link is only valid for a few days")) - .build(); - source.sendMessage(response); - + }, MoreExecutors.directExecutor()); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java index 9016ae3ff..c75597f4d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -173,8 +173,6 @@ public enum InformationUtils { SocketAddress address = server.getServerInfo().getAddress(); if (address instanceof InetSocketAddress) { InetSocketAddress iaddr = (InetSocketAddress) address; - info.addProperty("socketType", "EventLoop/NIO"); - info.addProperty("unresolved", iaddr.isUnresolved()); if (iaddr.isUnresolved()) { // Greetings form Netty 4aa10db9 info.addProperty("host", iaddr.getHostString()); @@ -184,10 +182,8 @@ public enum InformationUtils { info.addProperty("port", iaddr.getPort()); } else if (address instanceof DomainSocketAddress) { DomainSocketAddress daddr = (DomainSocketAddress) address; - info.addProperty("socketType", "Unix/Epoll"); info.addProperty("path", daddr.path()); } else { - info.addProperty("socketType", "Unknown/Generic"); info.addProperty("info", address.toString()); } return info; From 536049995d30f7c8fb0f70f689a86bc8d22470d3 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 26 Oct 2020 20:58:20 -0400 Subject: [PATCH 08/24] Fix SpotBugs complaint --- .../proxy/util/InformationUtils.java | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java index c75597f4d..f53e3b5c8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -16,13 +16,10 @@ import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.ProxyVersion; -import io.netty.channel.unix.DomainSocketAddress; - import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.util.List; import java.util.Map; @@ -170,22 +167,14 @@ public enum InformationUtils { public static JsonObject collectServerInfo(RegisteredServer server) { JsonObject info = new JsonObject(); info.addProperty("currentPlayers", server.getPlayersConnected().size()); - SocketAddress address = server.getServerInfo().getAddress(); - if (address instanceof InetSocketAddress) { - InetSocketAddress iaddr = (InetSocketAddress) address; - if (iaddr.isUnresolved()) { - // Greetings form Netty 4aa10db9 - info.addProperty("host", iaddr.getHostString()); - } else { - info.addProperty("host", anonymizeInetAddress(iaddr.getAddress())); - } - info.addProperty("port", iaddr.getPort()); - } else if (address instanceof DomainSocketAddress) { - DomainSocketAddress daddr = (DomainSocketAddress) address; - info.addProperty("path", daddr.path()); + InetSocketAddress iaddr = server.getServerInfo().getAddress(); + if (iaddr.isUnresolved()) { + // Greetings form Netty 4aa10db9 + info.addProperty("host", iaddr.getHostString()); } else { - info.addProperty("info", address.toString()); + info.addProperty("host", anonymizeInetAddress(iaddr.getAddress())); } + info.addProperty("port", iaddr.getPort()); return info; } From 9f424522ac64400f24be83a62de023ecce8906e6 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 26 Oct 2020 21:00:08 -0400 Subject: [PATCH 09/24] Authors should be a JSON array --- .../com/velocitypowered/proxy/util/InformationUtils.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java index f53e3b5c8..89f24ab9c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -23,8 +23,6 @@ import java.net.InetSocketAddress; import java.util.List; import java.util.Map; -import joptsimple.internal.Strings; - public enum InformationUtils { ; @@ -51,8 +49,11 @@ public enum InformationUtils { current.addProperty("version", desc.getVersion().get()); } if (!desc.getAuthors().isEmpty()) { - current.addProperty("authors", - Strings.join(desc.getAuthors(), ",")); + JsonArray authorsArray = new JsonArray(); + for (String author : desc.getAuthors()) { + authorsArray.add(author); + } + current.add("authors", authorsArray); } if (desc.getDescription().isPresent()) { current.addProperty("description", desc.getDescription().get()); From 188758cf0e1780e80b2c4d31ea3c04a0d7b79ae3 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 26 Oct 2020 21:05:09 -0400 Subject: [PATCH 10/24] Drop 1.16.4 snapshot support in anticipation for full 1.16.4 release --- .../java/com/velocitypowered/api/network/ProtocolVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index 830911c0e..ce5130078 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -41,7 +41,7 @@ public enum ProtocolVersion { MINECRAFT_1_16_1(736, "1.16.1"), MINECRAFT_1_16_2(751, "1.16.2"), MINECRAFT_1_16_3(753, "1.16.3"), - MINECRAFT_1_16_4(754, 2, "1.16.4"); + MINECRAFT_1_16_4(754, "1.16.4"); private static final int SNAPSHOT_BIT = 30; From 0f919d7163249a3131f38d40f4656e9f24e01a7d Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 27 Oct 2020 01:44:14 -0400 Subject: [PATCH 11/24] Recompile natives for Linux x86_64 Debian 9 was used to compile the libdeflate native and OpenSSL 1.1.x crypto, CentOS for OpenSSL 1.0.x crypto only. --- native/compile-linux.sh | 4 ++-- .../linux_x86_64/velocity-cipher-ossl10x.so | Bin 8640 -> 8640 bytes .../linux_x86_64/velocity-cipher-ossl11x.so | Bin 8712 -> 8712 bytes .../linux_x86_64/velocity-compress.so | Bin 44624 -> 49056 bytes 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/native/compile-linux.sh b/native/compile-linux.sh index 40744df9b..ceaa58251 100755 --- a/native/compile-linux.sh +++ b/native/compile-linux.sh @@ -13,10 +13,10 @@ fi echo "Compiling libdeflate..." cd libdeflate || exit -CFLAGS="-fPIC -O2" make +CFLAGS="-fPIC -O2 -fomit-frame-pointer" make cd .. -CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -Wall -Werror" +CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -Wall -Werror -fomit-frame-pointer" ARCH=$(uname -m) mkdir -p src/main/resources/linux_$ARCH $CC $CFLAGS -Ilibdeflate src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \ diff --git a/native/src/main/resources/linux_x86_64/velocity-cipher-ossl10x.so b/native/src/main/resources/linux_x86_64/velocity-cipher-ossl10x.so index f69edf145f3b0f87eed283786d017889eed52f62..828c5757bdba5a8b97c3d661c386cfadccc5b745 100755 GIT binary patch delta 697 zcmX@$e871^0_PbX1~53YF*Bc0#JA(<%LOw;A`3#lBtHsTCKI?d=Krnd}}A2F*jJ>c5R#j=C7-lOxHW2j?@W9OMrkIt*X9-Y5En%@X`bhH1T z5W&j8(ELN7M9ZUBl$nu%!K2qS21IquXi2e0H?!d-&&~%s1%Qt9=zIs%_=hm4V#Vw}>RFIWemZ+bTn3t~a znUj;6o|xlUlwO*fnpfglk(^pkl9`{!Fu9mJkXHa``wyUc1t2Xj`673HpbL;!1H>6X zyWaw71}8p&J|qAS}7jBp_aZiWq$BSrTEQ;?*XIWPrn4ifWYXY`xAP*$G{rsDzE eKCQ-)8kC@e%1h_YIvFu>2ckDdp80y)1)iESA*rW6J3$K6w|9i9^@a#Oo z-?9v7xJNg$Pv<+2&inr-M6iOnhL>OhAJ~8btp_|hkMg%H0;+xS_wWDzuKe4Md33(? z=zQhTdCaHt`3ni4iH@DefCd?!1d75m{{U)sY_9*u=+pV2R3JLmF~%|0G43$Gd;`c! zAdL(lr}T<`268=mO}Q9>ROkH{w}5s)R2v>Zs0XT>2vIi+L;=-p!m18r?h2p`P<;0{<327RxSmpa*(#>BNB&!g3WI@fJ_b` z69gtRbL;RWXBK3n7Ad5smMCPWR!-LAmY-a~?ZfK;G)@BO^Z+1jF!>^PeSHd0M-32X z0L>Ex$}l+b3G^|!@=5eEyYeaYusHH*w6Qw!88ovoRdexaIPxht@ks#HIPr0KaK|Hy zxPU~$F+_kyf`AARKLKI|U@&rk1R!AYdLCBRKR~v`=AAtJjFVsRMt~B76JH)Pqrl{c zf}+g43<@B6^F2W=CdL<=IYrDFB?Y))`XTg2pu>e2xEUTyjuhPwOj(j*=D?J-IY`Wp oow086LRoz-m<|E%$+u+91QnnL!03j_igNW#B8;0C$;mMT0NL&Cm;e9( diff --git a/native/src/main/resources/linux_x86_64/velocity-cipher-ossl11x.so b/native/src/main/resources/linux_x86_64/velocity-cipher-ossl11x.so index 37451294afb6df641f1781dc90ba671942e51ca4..179b1c9e60c305a9033772c1ad83936787e0cb87 100755 GIT binary patch delta 793 zcmYjPT}TvB6u#Hd+`ndLYbO4^o!un(=13C6_i%fnRTrWoco>o&3Dc{bMF}p378|y9ND(zLAfgkm;*tLSGZQMiR>c87ef?mI8zl-vy5A(E_$ znLy|DoxcskpC*ZE;Z&R4D!0k)Q%-LHA$<>gAh*&%I-^`8L!5s$NEgNgXqC{9kZ7p2 zn;;(IvawW%5bdAzrw3;Hp6QFfAeAoFJD*Pzx=85DM*2yA0wkSmqVDtt)P151-e2*A+ofsAnpi-T~mvwz#*MK{jKPDlVCY z6Vt_0z$TV05LzJ2T4%K9Y`9m7cPo%jtB+1vzkBJ=$m*4Tur2rs}Ya!#bO z&#=>A6;qxqs~EpyCGG~SvXFZm=u)u=My^I2hmrdsMquPp9oudf;yBCf@L4q*oB{?q f&mq@BZnuF?S=8gR{u}IvpwV)zZ=fTU@p!pEwy+uZ delta 809 zcmZuvOK1~O6rGo`nxB(KGEFNsi3rg}H3TZQxaq`*Q^+DzrG+-Oeqxgf;&&k;2~8>; zY9!&gD7p~EE);hIZZoB|5GWEvaT)E#2?ULHQ#E!x(N=BgQQE49FGoh!BP5SRX0UdDd;< z30+~mq$-;>-D^n*(hK3(@qX#`QBnG)PONL1IHlOqk5ONgo{KYVNctd3B`rJD0%nHw zziblK%^;?fereH`eh9hR3G-pwv!#&qE+nmp(vm2BRMqdACLo2g0JdpfgIP$elY}U# zS6hc8!iW$NPAu5mN&RBFu?nM@;aBx-X1|%}Y)O3rcG$1{t!6&Z~3MiqjP4@_Cej2T5MkcnW`9(8jG*|M^pa;hdc|`tdX2g)ZW^<6?;WdY+7L8FU=sKiRc3)6JXI0chju-U2@swW`6)K_7j8v diff --git a/native/src/main/resources/linux_x86_64/velocity-compress.so b/native/src/main/resources/linux_x86_64/velocity-compress.so index 29b48a8f16d05ef8867c5e40a1a057b56e1db6b8..123a838b1e6d09f2e12936c441216730970aa581 100755 GIT binary patch literal 49056 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 Date: Tue, 27 Oct 2020 01:48:25 -0400 Subject: [PATCH 12/24] Recompile natives for Linux aarch64 --- .../linux_aarch64/velocity-cipher.so | Bin 9200 -> 9072 bytes .../linux_aarch64/velocity-compress.so | Bin 37136 -> 41184 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/native/src/main/resources/linux_aarch64/velocity-cipher.so b/native/src/main/resources/linux_aarch64/velocity-cipher.so index ba4d9644bc72646aaf8131f27ccedcf06ef0ba43..155d8db6cf5bd6e3d10b0ddae09ce74eed6a38c2 100755 GIT binary patch literal 9072 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 literal 9200 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 diff --git a/native/src/main/resources/linux_aarch64/velocity-compress.so b/native/src/main/resources/linux_aarch64/velocity-compress.so index 2ecbffd664371f87f96b9dded6d0194b9e6fece3..52ddff982c9f22a36d10cf04bdf6d4fda96ad553 100755 GIT binary patch literal 41184 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 literal 37136 zcmeIbdwf*owebJ!*^>)N2uVQ2z%esP5QrLYD8ZuHnFP6cBS~ATo=zqgAPx{nf`*G` z0#VV?BZIWI*!K`Xs)@bW9(zvG)3+p`s9-HZ>+$rQQziifjd!$?iq898d-e{K0QLOZ z^ZES#Ia?Qd_S(;7J?mL(J!`G!nN0JHn{ISynzAol{fm;PE0UrZ!+LSs!`26GhAL3; z{5wu%nq}iozdGa7or|I<`>xCy(SPH46l=?0aIg8Rg_Iu)%si{GMx-v+q(?u6airtEIpH?v-X0ETx{bajx;XtZ~_Qi!77I zG1Sw6nYVmhy>roH&!6bLp!ZjsQhsvHga7hk-spnE(uKSq2}m;uK#QhkY0C9j;!Vz) z^>K#zE(82nVEvl)E$XLJj(cYO{@po`u6*oY4{u!apS}aL)eHF#{Q3{y9Cyp*mp$<2 z^7-St9{niw?(*rI-Z}U|s_To%y$3><^nUAuCsxwLU%YfRLN$#zvxu(J)rZ#n*l))} zu+TO9y7CWUYO^bfb6s2^Ul#u&N=YHch_0k5tTR_op+q0{8i_`*MZMa$;wEM zc2XX_sT$}>Q|GISWkSK}tJuV!r{)jC|K6l0SyiM&%RXmS5j{CuJMmWe<5s_utnxQo z_%y4XSr)$f9PNax^7F0owm!|V$`7aWSG1RL7J2x_Im(N^WT+JNJ+DQGb=zg)lU3NF z-|qK53;#U}Z_~f*9P}%%cj1z{rQU`{Uwxz3tGo+pm)0up?DCtv)iw1s3u_x1YwF8y zE?!)>w5Hrwxwt0!?(A>8RZTu=z_++|6$R(q?Jc!hC|>MqXsBsWjf?8*?wQe4RkN(I zwr;7K{T~%%4%4K!;&}+4Ksjsp2?wSU9x2(RVp@G8QTTI;Ti)$-O zY8EW^HP(0`wefFlV?ljQ&EML_viiCzi2Q4tnZ5LHACBF|-!dG#4SP6CYL-+jTdBA; z)HJFkzQxRuJT0layGAh$Rrw8d`PV9MO|`GlhagopG(_bn_Or;8`KD%RwfWh)b5k>K zn(Zm}UX_34R7~&MO7Oq4~ zKaN}W*T#3->$+uMY`kGTch{ltTpI1fuIn$y;A7Xr{W191CE(2%yhV!HZa4;SnG_R0 z%j#e3uB>PFbz2PHB0c)b!aJZ-)u!+5UyDLYFGF>5J7p&vfhFH>8~p!DHL^r++zfAg!-3%2VZv*c>Om`RqX%1z+GoRym&W?Fb;@K1PoXT@$%yS0MYh#|z=XqPqb0*KbVxBMJ zxi9+M25sv2%mG8G=Rb`^F0kp|3SGSW&|RU06yP4OLT;t@6&9$E+LbEj{eE|N=0Fkk zTq^4gsV8GPqaJnKr-~nUpDGFhkM~f18+AP&&K#)Zvq4>{XV?E?^{-7no=8>$|3v>4 zl||DGOhs)A_W71nqJXVap%>~>{b(q>q9A#@{<=WED*dG{H)cror!TOeb(y9owF!&tjg%x=~T-jSz{*GW~%b{);U5qjaH#& z-71s>jgx?fW*BCjohs3POpQohI74;j#U&+6{hf(w`Ao_@?RqeC=IVu=?>Li^J=*Dh zpIKSknM{AruAJQY()>wHHXW0wduqGU^@Gow4_;0E|5W3HzU&t>3ssW8I8Bw$N>}B5 z3yrR#92NQo?{_J6O?HXV*$}VNcSRyoCV=~A*TKv!YPA0o-I@0(bND&3cp>=D^gwCO zXjMMnspjpDSM$ausL=Nk#s;yuV?;i-=wtiCPE{V5Xf(NXeU3Y=`}RUz_xC!qx!YD% zbjmzxuAWSf8lC5&jmkTwbzZ10Oq!-H%yWMkDc|B4?VpSs?7Hv1gT4B=jtboo6uRD6 zl~uO$%g7V*-p%;jwDaj5MeSi-%loX#(>~xYX0A$@E78Z-Ox+fGeoh^!>$#($v+~c8 zYs@jywzOln-$%dlKAW*$OuMJ_u^oF{zsuYUeTy7p<~qS4QyIdSe#SppPfB{)^>SvG z8sRq_s$AyYk*vzYdSYHL`l_5Nz15+Tx4XJCOIBBRwm7xqW~KRMPJ8YgTXsZ`&vPs7 zsy#|ib}5xtamVCN;n!B-&&1zl60s(0nwICg!_~>En0!bn|1;EWV@_y$UZJl1>SUzc zrK>p}pe|Y%;Ghmask|Z$EmfF&5#~*?4I4?_pg{ z*+F|!wgLHRb|m?IpF~XA@zSrx=N8>qiFe#trPNi~l%wo{64wiv&J&R-dXu};jc#mV z>_Y$J^c$-a>yJgs$5Z|tWbqrkt5QanwDUfnWghm>j?BRpo)7B@dBWf4_$+!Pb#CDE ziLzf{d-V=<5qZ$sKSfUA>2spTs#^<*{)P+Hz-r|TstOglRyl%y*0qjP)DiujM!gH5 zL3BKFwHoMsVnq8}-&6zPxjY+c`66Vo+mX31Ydq^&kt#QI?2GHA&7V22&1;Geszl?! zvBbZ->ULcP!%0O4Pkvc^Fblm6xK#NkDQceKRM&*BHU=KN54=j~e;GMRPzn1CJt5fR zV4X}+A6*8{;WSoiX(}|^zpyE+XXh2VU4CDZDxZA4Dyz`P?<>?)|GU=;Et>iHkr5(q z<2(BF5gk9!M+C1^BZ8lO5qUz!AiQuerhmIZP5lfT{2F}9V!nD4SdZh4Pe7xaj%pJ^-hnYQ9^ ze4up8bwlTSvL2o97Us^)Jm0QsLFM}0=1oa|chy+rOieU4wxP3O=gFzsc$KjU8#i`h z&*pI4hu3b=$MsiCR^>myKB_f|Wzu%wL{`BfY>h2XlfH;Bhhy^aC&v`&#vnZ- zRxXvE^`C0_SmbmPw0rgQ`!(jI7df)uiC*uPP8Ru z2i2S9T}J%LsZQ!8t{Gc)OgZN&==($^&MnD(VRLrQ%bD16MYwfq(6ZowyexLAc+bT!ao;O{t%fwc*0 zAiLE#>A7E>%u-4HVvkNShwDT)SSQ4ug~zil(ta;`H6zz3n2@UqTAjwU)&w=JWQOH=8(wa(^3 z&z7X9&$NqqV(cH)vbKBfVT^+^>gZry$~Xkg+xF%bjExi@+?1;3$vT^L6Y>bJcA+Oi zgGa^9^(YlY7lZIVl#QL#=tJzm&b{t}t?)}nhmWF1Z4Nznc&;(<7W@-kKBC95?w!}s zc2z>C5_*4(9TFX_ID&j9%tMXPU!~!ys4;DGatf+gADflaUm35;w_H0^=Uz#%ZLG+e z>Ik5FVq>GS20zE$cTjACdbOy_mR-@Wtm)_rx;Fxy%EE33;#7H7iCT^flvmK-PWtu1 zKNo7kY& zzKmSp7^gDYJUKznr7B%x+*Rn=k$Y4ZI#PDnuevr}gwGV2d9dwNi!6P9+0y67n1lZ>I=qhZz3__I z-Sl5eEPl1RdN#Y^SE0!-=Ma7!fnWAJ^geXN;+M~|!*0elg|YpTw!`=>!|nTL4z0ts zr*&kOQvedD!Y#7pU|$@!MBLf;_w|h ztmhNNkAWYpJ3!86lf-P&{#u?zwm5F)=_rTI}L@OCFp})t!ea zQ>bCDD7Ve@_cgz>>Eh0}bUkkt&rynn zT%_VU+)D4znAaA-v7xA4qpsAs7&**APFR!nxf4~!7Nz%V)T`=l-Mlq-4{NI)bfl;O zscZAeS&mI2j}lLuEP0C@eDdr`MprBLh4RVSlT=q>iRvs=@%<8q;b)d+r6o-!?$47p zM%-p}9i^Vs7e90xb+;2+X5Z@=w{DF-&Z~6)BIRJM(dT+x>oX6jxc+0<$R7CRUXxO$ zUl=IOrryNy&T%c2k0{N*`*ZP=qU}fYjMq<;{i=O5d}z4)q73ojPvYbD>7(;L(W7xY zF#xf`n2tZ;t9U-S@1V%)m*dpHXZZXcWcE8F)D$-NB(6Eoy~=0CC|S&p+R&Q0)1mp- z;uo<7rmtP)>P%#e&GBmaLy7nm8PT}iMSF_(+3=>>q5OI9V~*xXUPRgNIMfvM?0{`g zT2@tdQZ}@cayislw<^2y=R&FJ#S zsd;OAyq)sCR_t7{o$&97H949gK3(vsokj$x!U-A{gv*Vvd z%KroTlxOj4B>p-I|387Zhw+^x29ul@yoav1jxR?`9t%8k5~9C~WHm)%k#6Fb5s7LZ z@^c_BPKDxW54%&!ewt~A51~^oc-~8_EdGT*NeeDaat3|OSM{pu&Pi*M%CaVx_}8({ z?|`qpx-&0J#rga7gbo8-vx@h(aX)6&UzC&(Tul9is}`E|-=qFc>KBu%kow1{e_T)L zs4hRhEGJiGR4HekyZro&DtL8_yIUnB>+rM6dZ&Z0Vs7Ob{mb()uO8;vt2z1&=oVT; zmwUuFfCiyoXcs)bUb==%OCDg6NmF&F;CkVwj8*8AF-k5H9_B5iZZ&m<-xUs{OXaG1 zRs?iH8-WzhAm&!2_iU z7yV=DTfHr%arkP!RaKpVyQ&H*U&fA!41GCTWG5O^_3c(6(LL{cGY^n{2;JI- zZ2DH!b{2zY`){5b{vDpGx_ql9cRI-nwrMA?%|!;VJ<&W0u|sj97Azvi;1>T#sji}? zicaiVdQp?BGgm)3wE|x0Yvvr(Xsd$pDCQO&qCe$#Pg2ZHZs&S4);d2=qYZLK9d?^7 zc-8Sp_>qFZAB698Bacatqv#t#u)z4@743zFh?{mn*J%w$q!q4~JULiW3o`r35 zLqn_hYm5`QTD~Vuz21U;9YTj9&8d^c=k7+Qdk?b~C8}Vt)97*&_luwH;l2YKyMuLR zLU&fDn>JQ_8kurcIPC@wjv+W#|iFhDenSS z@>$l$BaAsmjS0zGdZO&tthpLGt2%n{|5Yybk-Y3k^t0NL5vp1{Hnf)em*i4UlxCvB8ZS7i?PA?vr)FC*U8SbKEV9tUd`e4eXVcaWQ8>{wSZ>rC}O z;O8Y7U6l@FKx1vHQO0yE)|j!bF=j4Fxl}Ya3?C$C)Jv{#KzDR(Ll$mKF6sIl z-h}brzQeQV^qmup(2dE?uD&|eMIP%3;dNk3LRZn2#nXCA$PMLSho8JOBskXEX%6I* zd3}95Ia#mMxqK~nS^Gp598P3lJ9V>`OzUBd?j;vlJnphk3%F|PW>Xh^e7$;x)K$x+ ztmGr5ZdSm#JY1y8%W@`#ind=Gs_?jib&TJ6!^Q*ax6JJF=+0nZ`^>KU%QhZxGNAu z?qRMDqstYdADZ$PL5t+^UYew?(Uo&<^CY!=Ha^q?U-NVczjVv~zon<|)@d0?uis}&zXbgNo9 zlh;v4+Rs6+9+>LrBA4KAqwIV5W5~ul?EPGL7rL=&NoP)?9-L_UIJ%h&(^Y&jZOkoc zx~x;iaD?$}mHew3vv1wZjR&+j8xIugda#6?L@&Hjr#z1bo=xbgfL8_Rgxmv9Cv*ia zpFJ&ni@K(W=W98Vv}@M|A#}9VL!PUJ_YTHxeBv!IcDsUNk5sM^Doi84`{Ar<7082> z(coVT^Hs=vw!z!A%$d*Q4jRnq=O-gi1fHJRwU>6q_ZlH{3w_v%WM=^@2KK<_Z^ELx z3Urki%pO53&A7=2d{pGGD)9I#J7ulQO>>Ovg|8B89A%GbW=$&jFSm`nKm=d1 zBuyLF0zETwMufx;XuR7gb}U6p-U?oxn|XHg%({{+WsRK0(}ccRH7Z0*obF_9W?!8a zGA3Rcn)7=VilZIjL)>`=^cmB6(3risyL|Dj4H!UCA(6{wWW+p#wN_ee*23y1lTjt z{OismrsH|r#p1u5hi`U4M>aIg&$%p=L~bNZyN9uVy<(&4#J^Tk7B$~{a1Y~19PzEt z%;ax{5?`MZI<|jGXqKKB{7ttOdbL7@_JvhwZ#XXWN_PTz?5Lmgv)Ky@W}vG(oJQza zN_;Rs-kJVfVCJ-==uZatTURdc{D;}g3wEhdbK_Rmbl&PHpZ1ZS#=G?3vf0ZDK0wEJ zkaK)Z8=JkZ9+#czL`TW>2m7x&)I%2>zIu5 z2<^v7+e+IfZA-h&gX3JcIgtC}W_+liv@5ioJqP{L;C@v+cIFU!p(FkyV%nw!_{bCS zdGJe2TULyH!cL_ZV~ZZW+SB!8_Wwj*-qT0r?WXQed2XhD)rMf^QtV$Vb62%!Tv;mb zs?_+a4vkRDbFgje(VIuFc69yP%zs2>)Psyxl9%dZT9Cz+Gl5WRrW=uBtpYO8(R;I zv-EIh3HjrTRJrKFVQ8?&E&IWnh#RHvBk=Vd`d-KQu{-p#3ja&}t zd)FT$0y7ggmHUT-__a5m~DNKaf?m}9j_qIeQRbM+(}+^ z9X8j)+_n6f{b_LQ1DSJo#GFs3sXqts^ECWD@hePU<5!U>;x~LsPUsVCWV1eoz0(Uj z8Xn)2DYmUyAMKa;`|t%@GN%<^9o+8FlkUdOtp>jpIr~d8-$$JI@3bqn@AipBq1$&~ z6#5Oe?p18vK5X4yY~5@f8~H6&Kk?oR%3SPWd6DngBiYAB786(8w=pqYg(hJqv&r2} zV}CvOptoScVQ)byyotxJx%`B;Ky0C7b!F#kTKTkeeN=D{wj&KY@*4K91e>@AJrVmT zG9xx`t%^@h&3_Y}F7qi%AjDm}TCoQudi`?RM0v*+5LG44)cHNU~tW0D)`7O>+ zI(vRU#C|VZxvcYc_VE)LSL*7eoySz#Tqola-)#ame2+>@zKwGNI~dPHvug`Hyl-WU zd)#|Ahg~mkUT50x_>QPOkJ|F5IENr(68ZV>+H|q!VozHbmu=5CB1gH*iTI*%&@FWB zfbTo7*#T$}UA6VI8{6;2f6GoVra756ow-^|*=@>^T*F!u<#(JZp8?9|B*T|cD&)lP z(pOh?wo_kAmGDsfPuQpJXDPTp{FR;BQ?#$QO1Ms;AZ~_e_^>JQFUob4g3h5 z(Nfs!OO$-6+9&bNiLxJ=c~XV0zeVgJIX20)NRCC~6Ny#4F5@HF?}iL>KRe-18xI^^ zs0L=Z0;N-r6m{XBZ0=E7FyKN=kMB6in8IJ9EPKCq zLUZ^YK3`iI3O7XWDlI)E`%QvVY3@+?4(qeQ=b85BD?{Nb*4ip(DD36V9u}RF--*q zy7MNQcUQ%4S7uj?TG>o2;&XPdtRS{cB8F1ajmNjMMpV(yQ?(VHt=yZH)8FPLAEs%3 z>1!`BgNt%odG9vgYyP%xtMcj_uL~N}jEyb1MWGz4eeGoP!DdbO`>$gUjlIm?$YUAm zsUmWjDg7G#Yt^GxW^*Q?jX3Df=8B&<1T4%yoo3#WUD!{G)lj z%!xu5K7byP!(TJL_i6j6o{{$!vM^blw~w=$W*)E*-ad=I3vXFV%eT_zX-+l4P!I$D4L4Tnt_mM*szPXt{;afg+@|i#3XQU^q$>wA4r;+lH^|(BlPxc_u zk*J*SBByEUaQB$d7UbR|&t*ne)~ST$waDJH4s@co^#C&2{~k6^<~Wae^z!`Uw`4vf zM?J3NI5`K|8$K-0NAW?J^Y_qsJMSy{nnXQYzuc4&84)|=WIn~_=**{s`P9xbpHAje zXFeUwr)Juu=zMyaPqB+eD6m=dwGEk*`INIkbNXaHlbBEP-y0t`=ks_k=kJj7wKpQ) z@JM{PExWRs0)1IcHc!@(3zu=r7-T#$4)+f>mR2~O>DZfkYz=z~Et$Oy2Dz(bzkB~p zUA>PJV~`^%hDR?xzkyuKbEWrdZ69wl+!FJrV|)CSsrYnG)m1At=$q8lRe!ICd@tu) ziV`KxR-Ju!7z3;s{Ss#@&bju@M5nqh2xtig zsQsLbO*d%AwAa+vC@b~*s*C~W5$ev@i0Rds7V5E|S;qcIzxxN|T&W|rv0}}=2Ybut zGx1xYq`n6ZsO~AD5xX8Zkc|FUz*`Q)1;cgNWVd?#(`Lnbq3!!r&cN!)9dn#*ABRU( zbj;>{Y}7@QD@Gz`er$1_oJW4(z+uXXZMcp1>=kr{d%aEXvFFj(<8AstAK6je>h09B zT|qVfg0ij1HRHc(0&!ynWq05MIEWX1Iyrk^C1ZM5AJy?-oUt;f87teeCz9(RPNtqB zMph~**oKTJrpfssN8XJpAu=ry z`tvfcld%_%h%M4m`nM7zgyBmKFor!7fTR)L=y3Cm$Uc>WN z;`?_d-qxN5zt_#GDQH9Yg-%aOT+pjV_is(g3Ff4fbX7rDPKvWjXcxM*^(9>*GunD* zR~UIRpf!%zc_;psM(n(moVRQKeFrBHGmE_2?TK$<*2CWrTN!?wxCFYe$;-pYr|TMI z4Vt%Rj|tWPIr7Ag>`9?MWKU=mf8(j_OGEm|K&i-77JiG!luBF2`BG>9aG8?v{gC|e zTI6y)aw#&|ifkT6UL}SzXro2s5}gtGG}}viAk(W7`(5~n(sl(h*8`oxv$%(!JRog4 z7*i`Tg7EZ4+K)?b`&i_)Xi+u%a|E|1I?Qt{iagI?o8*1$HrwvX>D)@2RrqaNS-(DE zT<#Lj!3x@Y3VpZNv%|>Y{d`sFU2MhLHF0GgXBM$*T%ITKzJrzYGh_X{t{u$5jP>bV zmDod(oys!RC1(JAtbx_o>PB?pmEW_b5Nl7yHoFC|&vo?mO8f}qH&FU=ycW#h&N^S3 z$~b%l%?H_ISSe+*REA&5e<*t*8#ZRXLfPG`dYfcz?!fk5$=(7wS6^C3(_Y0R#uy7{*TS}He^cQc6-lKPS*J_`2^X^c2iyh#1>Yv#tN^5 zH^M7f?}b+{!oyU?-J++?EnLH1DeH96tfeP^IcMp~LY3L?R+)PP(C1d&f8aduAKYv7 zG8gd{Yq)79nmm?0KFz7prHxw_`3hvNeu)m>PTc9$Q~f>UueRu#U(PDap6)K_ z&mxAE^IG_u%eTSP-lLefS2uSM@cC&+stwQ;HlJ+@I;m z3I1}9ufQ?e$N8^JzXB(BIjbP;9j3o%dujfU%(gaUil1S(wU2U3>GOTcZGxs;=$+qYbz09PHTcz%OAKhRNko{t3-gmegn%Z|iK1(1z>VKnu zD-)ny{A=-7L{7x+yhvYS+e`2(irCi-C~dCG8Avv;xnX>aEbf4idI>z+(dX0f2+G1jw@_?+_Y z80%aG_U0t@R-F=Fm9-bLM#HP}F!e^0`#gKD>UU;-mT@1Db6G;?iL#%y&v3y*eA9g8 z2-h7;r`6G zh_55W?U93uczY!AeBg{&+Y8<1ybx=jBp+}G@vK1}C`?SNSSQ7o>%||HGw>3NmyuhO z*x6u@s*BjWbeZ{xawMOP}IQnw-gz7+U1-$ke3v z_X{tWe1bUs&W|Hc9GN9do$hm*b+}1BH=XZ4+{;=qm$SMa<782h!P?|tO?B*DOPlJJq3f#bvmMeLoP$Z~ zm^_B{ns%j4yWdCo?oNb$4eqixEN#Dlt=mAp_ZYExXup?S8h*&y)=Z&GXe!mTVCeyG zr`M70??zXiqRm#b%_HVm-eVnmDNRjbJoC<0r-gCYV{-FZ`fR^dmA^1J9%2jS+$;UI z-86JOt&9g7;Af4)k8zsg;(TYGm|SF@!mktAaXaxBBp=6`E1xyiT#0uTpDS2%B|bew9K4n@ z{(_e{_#yHXVPt-nLz%I#=;(W_Vb5yk?~}Or(YGZI<~$90^bYs8$%EzoDH;#IP0SED z&N&gs$bQkeUTm${vBSiq_#kp7vy_0g>pk@CgYE3WJ*&F@?X$=e??J~0+l?-%_x`ic zXQ=W&q4yb#+3Xh`N+JF}q$T@pJ$fw^%oJVv0G+q>Y3pE|eZg7d>}JL~Ja;lY)-Gah z$L5fq#D5h1mbFQ8y2AU(d{=0nBYCdyUu>oqzeju?kySTqc+oz0Fc)68!r$Cs{>^&M ziuJNa%_zRa+~@p(*O51w@6hGWsx0^+u~3WCneNWL?_d$OZ36Qs{`dsuZbmZs7%N8F zN{qCXd6>c3>PsZwK@6PxQC7jly;%iC$jbDVtj-@@j!l8K8R!9RgmyW`&V7-*&3WkU zjo1yDI}dFe3E1z8*iXi;ytOKab0=C}3$~^TojBx(>rcS0w2G|(j`2PoyAr_0xYwkY zRh%#9A+$Vlwab0tYh&}zmZvHt?i5S1unRlT17~^?{ zu}L0E>}MO}nuGoPgt1ld`4`wu*_#pj*}E#QDb8hZX5hTMqw#UU=dhhev7K`7xe&is zi}#PjZWLl8e=|yziyamF*t_azli)4`hb$})r?MY{9h!j6NzoI6JK%AGe|=so&U3mg+t`au zmOkIZSCl^6xJ#SIiEr-5E{+crse>~{zJftU? zwsClyH{;vpa(+zeNZsLeTF{w`{Pmp&82=$X!>{oyeNSdxc*ZbJ?qy6_T2g-nwmeZw z+AFjqYTbWGOb71O%jRf_$q~NS8MTi|{&)4bj$_!2hp^MbX?YJD`hrqZWL!dzj7P>H z=M3dLjITOU_Pv3wR${Yy`n&~VC;K^H+DG}KWG$GBZc#5i7u`yu4(G@CZm5EqeWKmMSmdOi&N{Ec(qDLN7R+re0r+ zY|9>`*m#lq7i09=gRE^w2ezp5O`YC>{EH89Y*n_Y)6XOKE70jMaiYk%$bI4epkBM- zq3E>(zf<&DWVE}-vugIA@C)!M_x{EM$YQoh$ybn+Ra$dp$T&wl;Zcfjug-yMVJ+*&Vd z;X8VIp6L8Dv+g>1V9s6C%jn0q1AkH8x81~b+C^r}9nq5fZ?iW3eAZnBFVDHF!0Sl# z`*@zk-nzuxKVP({fW3jxACc!j5_5|l`S@;WL`(F)!&x(lxu?P-@!KUA@&WVxTBs$n zOJtC1bQR66Jo&<$O8hPS6YNkQ>*ZXDH(5(17Z73XYJWab zUNXC~pl42H0eMxwn`hP&%4qC~YkBSe#Ct2|+=p)XKk^ndWRU_OAY*5?3BnzuYOB3rh(otKExk66L$8P=C@`{ zBf2Z&7u^-T6@3*S+Wx!;8ebTs(el%vvG?Z@{5P9M3lEKM!4J}C;i1vTn2zXar}Het z&aV^8lauIgVa|^blWsJ5n_=7UKG=FP5#x77uO#OFOf*ZE&Z50 z3%+U3Ph?I`$MN;h{)T6QIlq#J5Fhgz^PawUA@`RU*ZuH*aJ^t$TVIQ?juf$u6rIL1 zuHN5(XI%Hg`@!;z%MJcPc>j(`dy;<_V~Mc7y!~pV{N*_d3+|u2upmo|^ZR%{3hjx| zE^@fOwx&RQjJ^05bC3znx%rFs3H@>YV|tt^v)@1_W$q;IJo9@|(fN}#PGpsSPSPK= zCH9lEV_zjH&!%Y|d9K*^E@W?qO57{o`#Dke)4@14?{A4?9~>ITw%Tzl@onUU#Id8= z<$G{vi)9CM3|1^V%Z_DNm&Ei`x(MAT(7PHFwAp0yO)XTazX|jX(RfETWV&CHU<0IPN zxjeaD;^*+yf$PVCmpJ*v)RFB!dOz~S@yBPkhp+Cw{+-L`w!csO`u5bD+YhnVbjh$7 zc7hedc3Uy5lQqcPJ0^zRWyP>JS~2X`tr&JswGqmpoQL)2UF?eZnBs%=aDN0J%)?rw zNt_tHbIsKR6ry$46!}zs1wB^F58pW9m#oDR+ ztN7lNZu)5(Mo0a$5wsb`M|*&_c6(Su$R!YW?d_h3?~R|P@Y582+8(8n!+bYV_HVLS zi{8dRmA0kb!}Q%u-#3Gky$;1aOs-V?rXFlq2z}gvPjD5!O$+^8xwa+K2W_K}moGJC z`D+=bzvi=iz{8SHm$BlL-cKycnC6|WP7721mam4o6sX;lQD_E)=iFh^r@MItmM(owpW~WmYC{XG3=3; z81@h`>_fz_lE)Gs`6cF}7r%HnvE+-c5oOL<3km|nphd;*pdt1!Rf<0$*ivVUMCaJF)M zG=BYHRenc%bw!{mkSpW#n&?e79&~P};hl&tjKkJq^!ZaB}yY1*U!4ik*Cm`mZuqTl5rb zA1K~0XLKo3K-q$3rTi^_;BPxv{>kB5+0*14BRcyC>-!e=SX%x)x_?A&*j(RVX8jH0 ze>{NSu^V5+wuL?ahOFU#Jb>S^8(+l6Yl6rBcmTg+H@=9CFCwmb0GszF^!Mtwoz7{a zci$$zbawymb<_Vlw|^0R-veDj|p*Yfql zvwisbKHg{ayOF0}eEl0tzD9k0*(ZCO@q9enSJ2JAkC(kbAI~m}uOC}{{UdxG^!3?W zm3=wM3yZJ+G<=mcWwCkB*t^B_bAH{Pg)WM0Y2W+)0ri6?4`>d~gt2cd-(7M)7uc*E zfdhZ|kiQ{C?!KGv6Swev$HGh1^0%?qqmh|oypw%}*zX;CMA7W?7G5T`?3DbOxTjJU+>~RNV zec?ODEsJUjK1L=gvQ?-mdqfESn0e@I_F@XLBR$lq%1#Sq)3@N7S-VW!w)BwDI0ToS z5%O5L({;z3QP-D!S*V7!U^t(AvHVEj{C3i5epJ$KEI)n)&B8mWBm8>-87k(x@HYR1 zj@a_@H-TcyJCVg=>U;uzNWaMLI^NmsWDjdc&U;Gva3r!{sp~`Qm^Y8i8*`S!TxBz7 zvbPpnMvsF}?48Ayk-upWTSofa1wFB4z`yK6s@Yj-gUPsgKU8 z6J`I}&iBPTo=#iK-<)u?&rIA}FZ*>T^|+3K)q1;I87tk+?%UZfDx0|G=_ilzy`Kpx zVeV1qjO3@+ILnUmoeFY{b0?^{xjQIxKi|)qaOtdrJFzh?Xpz6W#`y9EZSU&h_7i0< znmiQxC-7bOo#WMV)`0S@UtT`-`$_DBj#aN8&C%f7g&p!;^-pyt-=mK2`2Gi`P09;= z5xM3Y*rnfdzWh%UW#5zYi1<1#`npCxKUl`!XmI=Q=DV@ow_|_(@;#Pcum}9+=aJ`s z;TW^;JY5aQUWoMX9mC&?VSayYJxd$H8^JFEZ$I0A58`>s3E#GijlNg#UdqdN@#VYU z@*QvCqkI=%`1l#T{SRvY&~aX{fpJ05CXKOHFt(n%uIaRSZGV4V=+kd~ zvQ&HQ@lt%#|3ZM1PfI{Kj*Q^uT4{TPEOrWdJXra=hq&X!u@T& zM|L&$NndU$y^8xhU>WcraG_J(+Nb#55O=Ri>i2ATcr#~2I@sF_PRQ!IrH1d|%^$z* zmW%m370{f!rsqy_Ep0OPmN&borTU+N8y-MRhW`m)HtE;&+&=het~-1rEdJ# zZ+ldH@*dg>u-@$8?%|%DkjPz6b|sAccG0@K^Ly8>$ZrWW=Jx~|@_m8&{H=lI`9*~IPH-kbK37pr{tiV8>y`^XXT#Gl{PnGAdolpHPt_e%K=C8m zxRMQOT!rB@|0adZx6E5umHd_b3lXae)cNW>HHILSuQIbmK1!wXSK8&P zgGp)xrYS+it2pIUx^iF??Y}i@|7}?Q9C3B@uR^=bzfHXVllrJC*8mo~?d$O>7I4dnW>;0MsE1~b?q{gP zgc0#cammgUeWW8*8x^f6|7w5(U{e-GME?LWPB^SlFi62Bj%otQKnlR&aG0oIrTh_e zCEv(^xzX#)wlvz&Xv0B02W54pMOP#P)s+B$Kt=h07N(&G&^TGt0%&|2L(>802&KgX ziAhRJ0n(C{HWqNDDD4Ws7^$=hAdsrGZlTJcH-nxGdNJt1pml?m4d^f!j{z+P<1rYI z!Fc44_8W}HU_1unF`&_4JO(0TAUFo%V<1EZqGTXY2J{;Um;4ZefuI?Pn}N_7h@L?_ zX)rcJWdk`tE-(SO7?=o50wx1_z$L(?z-2%_a5-=Va3ydRa5XRmxCXcumwz19LO}j20z5!5Py);VZUklmvw+#a9AGYR6L2$d3verN8!!(bBdyASa^UO0 zH-K*f^MP*xw*z+ocLIM8d>ilr6@U+@1gd~)paxg~ECdz-wZL6~A6N`50hR)Fz%pPt zP!BW!jlc@vZr~oE30MiN0#*ZSfO~=afMy^7v;b>?bwDd{KkxwXAg~^I2zVF>0*?TX z0{;L!25bOAz((M4U=#2i;Jd&R!1sXf1Dk;-fgb?>2lz)|3(y8^1-1d(fv12Szz>0^ zfoFhrpaXao_!019;3vROf#-mq0Y3-+33wj(1@O z{{j3b@L#|mfkQwT=mq+K!@v>XDDW0=3^)$F4ZH*N1MdRw0q+AJ03QM$0UrY=fKPx= zfjXSp_s;AJ+lff!)AidURm*qOcvo2q9nrgmtN^L_Kap1<$-3{L#4Bi@!Ik zIkoXJuq%NUY? znOB&0vIsy9fFxjw73Nl9QWa)VVcHbtNnv6XYOYWq5W{ zTnQ8bKA;8Y0W{70Z5ft9KI;I3&{&hnTe1uSth?G+zy(|Z7(j*k-|K%l@V^}RUk>~) z2mXKNfc-l#-4Zv&T$pw9nucAMOTsC+?BDT{wLz}$kyw^X)(E-m-K`%(N*vC`%k}&)Jc&}t7F@#2+N|#C1IXilPr2{a@SkW67tE#Qe$2$Mdo$9F4~|~ z47W3Sjj`^wcOz@5T%8udHvVPngFTK9qs7eiQ9j!JeHn?;|BO{WVm;e@{Lp${WW67- zo^3jEp=U+e|7#9mt9$K zNp(%-v=vuQjv`!FU2)~qE3Ucn+S9ep{z=D+*@d@f>TYzF-@OQKZ8@1)6l z9Z&8VOHYTEPgV05#bTnLb-YYlk7M8JBpq!19g08mcl_kNPIBUGa;W3Ic3OHlw4E`^ z*4Lr<^VHeoNGI7bf7zK6ubZB6Yzz3DJUZ1!k)c>+#XM*7b0!7(X^ufY+x8+FBb_;4 zQT&M1iF35G4ZMbzKKyqSqwOTCS+=3HTKg$?8P{<9 z&n$dkSos&sdS~+gC9C}Kb>qLlYcca;&rYSR)jTZh<3qRI!h}tZCn}zSR z@b>!lV+%j)qG-XVtoeG$!hh(F;%8ay^jY|V>?q>6h1aPkbh>h)i0PJtcY%ekxIBu# z%_@Jjg@4Y%KQb!%VxEOJ3Zu_2OJTBTtk{pt29&+7GdEv8-KV`kG|B7}B?i|vLTKg5{U4K8c{0rt|vYJ24KG^t* zbKn=91HbGX_@;B<1Lwf6KL>upF#NgJyD0r<`Yn&ZAC1kC;r>jt{F&?HX0!a6cwyVw z=J$t`mwD{IJ<8|l(VKeN!Y{Mzj~yqyY2lx<%G-DcfsM2iu*%!^aJ+?|Z{ewKUehdm zn$?bNPreRblMSC?{!gVoxZgO}Do>)?yzIEE*1}J+ z+PTzf|3R~zGk+)fajX5|{%5<@j;(k5t#)=tg7$osn2%@r?H^Fy z-g>l}HgApu!6&QZmcKE{dO6#~$CiwK{(Gyu?I+lFeyfEytai$+s?ULUo!N|f6}Tai0UvAJ#Pa7PiLA zqj@!1c-tSFW#NBi;VY~Oe9pq__%$x;bL=ex{Mmjt`vUN1qjQpp$KG1)+w?3xM>~xc zemMUF;Lk?SrgPx`*&NrI@rb&7MMM4N?CM{>u&U~EpRc}Z(X~@9TU@(zMbl*qm#(<{ z+RO9vt^dz_SykUyn_s7Ln^YBt3B32z`25}lOaIm;m(nCHH#DwTupqyRUqzl%TePmyKr$`rEjsfy0Nal!RuQARs1UU;+n>q>inrw zuD<&0h0GuF_WJ7UeJi~+OB?G~ss;7FB{kma6-$<^q)N<_`8(P}ir%%f)+>h@AX7fB zf{*+hxcMv9u_)^>1Ssnzh3w-EAS+kYF0M9F)ivHm^T)%_EGO;yRxEBDY=YnWuBxNL z;+mxm-eooQOI9@U!{2pF^RK@0D*h~&^;SBf5Wfvh-3C>+U;)2TZTDqk7uPn@%5ba< ztO4>)ANlY`f7{x-fYTYVX!$YiDAJ}oW^{{ce9LMVtn@alsBDzq5VwomQ(wzZlUMRv z*?uo&m|4Xf%CBBWaaD_Is{E>JTUob)pGc1>Vg9tcHwIx>S%~D@)ld{P=kA)t zu{C{*YZor9skX#C_KhjwrOGO6q{e2=e(BJI@&dqOcOcCfMM^VlMvh7uo;YfW%DV@>5^~VQgrj|;@ASi>JB3` zO33MPK#JE}esi%s@iT9_)l+nn_tqP4ESpj8EidxiG{bvF>x0aWnTV))iY;o+n_ZNC z;7`@Em4-3%rrDli?^XF%PE~U_@g`jHA`Ep^wT&y6)!kE5j}$D$oZem2Fvu|xnOjWU z?etj^HS_3fT~$p(gS5P)?(UlZiKb^q&(4|4jcQy}Uw6;x({bL}uVO)HZ<1M z!!=n0%gsf@>#b~P@HRAJrcC{*YVyhI#%!$`N|(1rj^!zH>4WuBOYE*!MHx6`42%oG z8iM*8Ic7@D-`Ga&(%QzqcIIsM+B4_%E?mOY4)P6F-~-eQW!YazuiaNvQ!FmW%#-rY zx!YT6O=j_8Uqb`_j5)_=one*FQizAZy|F%eUu1t;TGv>UkIR;Cx)YbxRx9&qk*{Hq z%CBCzl$z0dV}10^-8J)YdB3Qp>KC5Rl5-edzX_bsML2C<_I|W|xAPR%YqKx=v)mhi*!siwHQS_6%#~(+w(s}x99#bi zYrn+4FA@gFTsHkSs+AA1_3iz0`)>DRmls~eUJvsi^#|wA%A48yd(RE)aM&ZqMTge6 z^JVsZcz?s6e?Wan@!Ilb?-PcXfaDt9pZ#p#pP`JTj_vyPe&X@}X8q@>E91B8+xuQ0 zenow%m=}Loz?2=kzMW?}Y1QwR#<}cq+jQIWvWs`I{o8q@&N{Oi$D)T`c76N)(y;pW ze&=zk{`{fU2l;8&-#@Iro&PhIMJw8RW;bKkv!CDKgW&A(+xdt2R{i1SEq2(|IZ7KH z|L}3z^)DMN8U0|_kG_SkKl+!s6)eL510 Date: Tue, 27 Oct 2020 01:53:43 -0400 Subject: [PATCH 13/24] Native cipher test should only be enabled on Linux --- .../velocitypowered/natives/encryption/VelocityCipherTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/native/src/test/java/com/velocitypowered/natives/encryption/VelocityCipherTest.java b/native/src/test/java/com/velocitypowered/natives/encryption/VelocityCipherTest.java index 0e76bdd0a..7fe7e777b 100644 --- a/native/src/test/java/com/velocitypowered/natives/encryption/VelocityCipherTest.java +++ b/native/src/test/java/com/velocitypowered/natives/encryption/VelocityCipherTest.java @@ -2,6 +2,7 @@ package com.velocitypowered.natives.encryption; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.OS.LINUX; import com.velocitypowered.natives.util.Natives; import io.netty.buffer.ByteBuf; @@ -13,6 +14,7 @@ import java.util.function.Supplier; import javax.crypto.spec.SecretKeySpec; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; class VelocityCipherTest { @@ -29,6 +31,7 @@ class VelocityCipherTest { } @Test + @EnabledOnOs({LINUX}) void nativeIntegrityCheck() throws GeneralSecurityException { VelocityCipherFactory factory = Natives.cipher.get(); if (factory == JavaVelocityCipher.FACTORY) { From 4ccbb2cde2be71cf83ec540dba677a9f12680575 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 27 Oct 2020 07:29:08 -0400 Subject: [PATCH 14/24] Fix TabCompleteResponse not using Adventure to read tooltips. --- .../proxy/protocol/packet/TabCompleteResponse.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java index 859cda0a9..d63ac9279 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java @@ -10,8 +10,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.ArrayList; import java.util.List; -import net.kyori.text.Component; -import net.kyori.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; public class TabCompleteResponse implements MinecraftPacket { @@ -68,8 +67,8 @@ public class TabCompleteResponse implements MinecraftPacket { int offersAvailable = ProtocolUtils.readVarInt(buf); for (int i = 0; i < offersAvailable; i++) { String offer = ProtocolUtils.readString(buf); - Component tooltip = buf.readBoolean() ? GsonComponentSerializer.INSTANCE.deserialize( - ProtocolUtils.readString(buf)) : null; + Component tooltip = buf.readBoolean() ? ProtocolUtils.getJsonChatSerializer(version) + .deserialize(ProtocolUtils.readString(buf)) : null; offers.add(new Offer(offer, tooltip)); } } else { @@ -91,7 +90,8 @@ public class TabCompleteResponse implements MinecraftPacket { ProtocolUtils.writeString(buf, offer.text); buf.writeBoolean(offer.tooltip != null); if (offer.tooltip != null) { - ProtocolUtils.writeString(buf, GsonComponentSerializer.INSTANCE.serialize(offer.tooltip)); + ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version) + .serialize(offer.tooltip)); } } } else { From 3da2621e485bd0dc5964d209ca989cab0774c2d2 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 10:48:10 -0400 Subject: [PATCH 15/24] =?UTF-8?q?Velocity=201.1.0=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 47f223b28..056db3040 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ allprojects { apply plugin: "com.github.spotbugs" group 'com.velocitypowered' - version '1.1.0-SNAPSHOT' + version '1.1.0' ext { // dependency versions From ce92e2e36b0afc7ee617906003cda1a3cca0cc53 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 10:58:45 -0400 Subject: [PATCH 16/24] Fix typo causing failed deploy. This is Velocity 1.1.0. --- gradle/publish.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/publish.gradle b/gradle/publish.gradle index cb94e0aa1..7c79945e9 100644 --- a/gradle/publish.gradle +++ b/gradle/publish.gradle @@ -8,7 +8,7 @@ publishing { name = 'velocity-nexus' def base = 'https://nexus.velocitypowered.com/repository/velocity-artifacts' - def releasesRepoUrl = "$base-releases/" + def releasesRepoUrl = "$base-release/" def snapshotsRepoUrl = "$base-snapshots/" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl } From 9360fab6a5fdaebc3a345e7a26dd18b34452ddb1 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 11:00:09 -0400 Subject: [PATCH 17/24] Forgot about this. Can we just publish this already? --- api/build.gradle | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/api/build.gradle b/api/build.gradle index 4a7fd9130..ddbdbe951 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -99,19 +99,4 @@ publishing { artifact javadocJar } } - - repositories { - maven { - credentials { - username System.getenv("NEXUS_USERNAME") - password System.getenv("NEXUS_PASSWORD") - } - - name = 'velocity-nexus' - def base = 'https://nexus.velocitypowered.com/repository/velocity-artifacts' - def releasesRepoUrl = "$base-releases/" - def snapshotsRepoUrl = "$base-snapshots/" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - } - } } From e3f17eeb245b8d570f16c1f2aff5e7eafb698d5e Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 11:01:49 -0400 Subject: [PATCH 18/24] Velocity 1.1.1-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 056db3040..0f469b336 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ allprojects { apply plugin: "com.github.spotbugs" group 'com.velocitypowered' - version '1.1.0' + version '1.1.1-SNAPSHOT' ext { // dependency versions From 36ff6f63ae33577b5c3044f1a21a0d2f6aea94a7 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 17:11:19 -0400 Subject: [PATCH 19/24] Use the fallback description if the backend server description is null This is technically incorrect but it seems like this is the best we can do... --- .../proxy/connection/client/StatusSessionHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java index 2b44c654b..70b54f7b5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java @@ -120,6 +120,10 @@ public class StatusSessionHandler implements MinecraftSessionHandler { continue; } + if (response.getDescriptionComponent() == null) { + continue; + } + return new ServerPing( fallback.getVersion(), fallback.getPlayers().orElse(null), From 7bec4b2f126ab0d02f170be11073525dbba6f122 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 18:54:51 -0400 Subject: [PATCH 20/24] Don't let clients fake being the BungeeCord plugin message channel --- .../connection/backend/BungeeCordMessageResponder.java | 10 +++++++--- .../connection/client/ClientPlaySessionHandler.java | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java index e8896cf63..f9f61a7a4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java @@ -27,7 +27,7 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @SuppressFBWarnings(value = "OS_OPEN_STREAM", justification = "Most methods in this class open " + "instances of ByteBufDataOutput backed by heap-allocated ByteBufs. Closing them does " + "nothing.") -class BungeeCordMessageResponder { +public class BungeeCordMessageResponder { private static final MinecraftChannelIdentifier MODERN_CHANNEL = MinecraftChannelIdentifier .create("bungeecord", "main"); @@ -42,6 +42,11 @@ class BungeeCordMessageResponder { this.player = player; } + public static boolean isBungeeCordMessage(PluginMessage message) { + return MODERN_CHANNEL.getId().equals(message.getChannel()) && !LEGACY_CHANNEL.getId() + .equals(message.getChannel()); + } + private void processConnect(ByteBufDataInput in) { String serverName = in.readUTF(); proxy.getServer(serverName).ifPresent(server -> player.createConnectionRequest(server) @@ -307,8 +312,7 @@ class BungeeCordMessageResponder { return false; } - if (!MODERN_CHANNEL.getId().equals(message.getChannel()) && !LEGACY_CHANNEL.getId() - .equals(message.getChannel())) { + if (!isBungeeCordMessage(message)) { return false; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 956772000..b2cb097ea 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -16,6 +16,7 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases; +import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; @@ -194,6 +195,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } else if (PluginMessageUtil.isMcBrand(packet)) { backendConn.write(PluginMessageUtil .rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion())); + } else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) { + return true; } else { if (serverConn.getPhase() == BackendConnectionPhases.IN_TRANSITION) { // We must bypass the currently-connected server when forwarding Forge packets. From dd23203139747351c3f13b788050145bf5b58363 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 19:02:37 -0400 Subject: [PATCH 21/24] Add some missing convenience APIs to MinecraftChannelIdentifier --- .../messages/MinecraftChannelIdentifier.java | 34 +++++++++++++++++++ .../MinecraftChannelIdentifierTest.java | 22 ++++++++++++ 2 files changed, 56 insertions(+) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java index b63b6b1ac..01a582035 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java @@ -4,6 +4,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import java.util.Objects; import java.util.regex.Pattern; +import net.kyori.minecraft.Key; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -50,6 +51,35 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier { return new MinecraftChannelIdentifier(namespace, name); } + /** + * Creates an channel identifier from the specified Minecraft identifier. + * + * @param identifier the Minecraft identifier + * @return a new channel identifier + */ + public static MinecraftChannelIdentifier from(String identifier) { + int colonPos = identifier.indexOf(':'); + if (colonPos == -1) { + throw new IllegalArgumentException("Identifier does not contain a colon."); + } + if (colonPos + 1 == identifier.length()) { + throw new IllegalArgumentException("Identifier is empty."); + } + String namespace = identifier.substring(0, colonPos); + String name = identifier.substring(colonPos + 1); + return create(namespace, name); + } + + /** + * Creates an channel identifier from the specified Minecraft identifier. + * + * @param key the Minecraft key to use + * @return a new channel identifier + */ + public static MinecraftChannelIdentifier from(Key key) { + return create(key.namespace(), key.value()); + } + public String getNamespace() { return namespace; } @@ -58,6 +88,10 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier { return name; } + public Key asKey() { + return Key.of(namespace, name); + } + @Override public String toString() { return namespace + ":" + name + " (modern)"; diff --git a/api/src/test/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifierTest.java b/api/src/test/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifierTest.java index 00699ba5e..dfa738198 100644 --- a/api/src/test/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifierTest.java +++ b/api/src/test/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifierTest.java @@ -1,7 +1,9 @@ package com.velocitypowered.api.proxy.messages; import static com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier.create; +import static com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier.from; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; @@ -26,4 +28,24 @@ class MinecraftChannelIdentifierTest { () -> assertThrows(IllegalArgumentException.class, () -> create("minecraft", null)) ); } + + @Test + void fromIdentifierIsCorrect() { + MinecraftChannelIdentifier expected = MinecraftChannelIdentifier.create("velocity", "test"); + assertEquals(expected, MinecraftChannelIdentifier.from("velocity:test")); + } + + @Test + void fromIdentifierThrowsOnBadValues() { + assertAll( + () -> assertThrows(IllegalArgumentException.class, () -> from("")), + () -> assertThrows(IllegalArgumentException.class, () -> from(":")), + () -> assertThrows(IllegalArgumentException.class, () -> from(":a")), + () -> assertThrows(IllegalArgumentException.class, () -> from("a:")), + () -> assertThrows(IllegalArgumentException.class, () -> from("hello:$$$$$$")), + () -> assertThrows(IllegalArgumentException.class, () -> from("hello::")) + ); + } + + } \ No newline at end of file From 303815285217745e14d6d2acf807ba71ac0c37a5 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 19:04:10 -0400 Subject: [PATCH 22/24] Velocity 1.1.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0f469b336..d8784c062 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ allprojects { apply plugin: "com.github.spotbugs" group 'com.velocitypowered' - version '1.1.1-SNAPSHOT' + version '1.1.1' ext { // dependency versions From ba8d6fe42a939d0cdf244d0d3866ddf4c9148354 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 19:06:43 -0400 Subject: [PATCH 23/24] Really Velocity 1.1.1. Fix a missed case of 7bec4b2f1. --- .../proxy/connection/client/InitialConnectSessionHandler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java index 1d1485ef8..188ae3537 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java @@ -1,6 +1,7 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; @@ -25,6 +26,8 @@ public class InitialConnectSessionHandler implements MinecraftSessionHandler { player.getKnownChannels().addAll(PluginMessageUtil.getChannels(packet)); } else if (PluginMessageUtil.isUnregister(packet)) { player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet)); + } else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) { + return true; } serverConn.ensureConnected().write(packet.retain()); } From f3e1a7e01d24194835645373a8bebf6b182981b7 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 28 Oct 2020 19:15:29 -0400 Subject: [PATCH 24/24] 1.1.2-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d8784c062..06f791981 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ allprojects { apply plugin: "com.github.spotbugs" group 'com.velocitypowered' - version '1.1.1' + version '1.1.2-SNAPSHOT' ext { // dependency versions