diff --git a/native/compile-native.sh b/native/compile-native.sh index 1baca889..f6e6a35a 100755 --- a/native/compile-native.sh +++ b/native/compile-native.sh @@ -1,3 +1,6 @@ #!/bin/sh -g++ -shared -fPIC -O3 -Werror -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ src/main/c/NativeCipherImpl.cpp -o src/main/resources/native-cipher.so -lcrypto +CXX="g++ -std=c++11 -shared -fPIC -O3 -Wall -Werror -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/" + +$CXX src/main/c/NativeCipherImpl.cpp -o src/main/resources/native-cipher.so -lcrypto +$CXX src/main/c/NativeCompressImpl.cpp -o src/main/resources/native-compress.so -lz diff --git a/native/src/main/c/NativeCipherImpl.cpp b/native/src/main/c/NativeCipherImpl.cpp index 70aff5c8..cff3dea4 100644 --- a/native/src/main/c/NativeCipherImpl.cpp +++ b/native/src/main/c/NativeCipherImpl.cpp @@ -1,11 +1,12 @@ #include -#include "net_md_5_bungee_NativeCipherImpl.h" +#include "net_md_5_bungee_jni_cipher_NativeCipherImpl.h" typedef unsigned char byte; -jlong JNICALL Java_net_md_15_bungee_NativeCipherImpl_init(JNIEnv* env, jobject obj, jboolean forEncryption, jbyteArray key) { +jlong JNICALL Java_net_md_15_bungee_jni_cipher_NativeCipherImpl_init(JNIEnv* env, jobject obj, jboolean forEncryption, jbyteArray key) { jbyte *keyBytes = env->GetByteArrayElements(key, NULL); + // TODO: Perhaps we need to throw some exceptions in the unlikely event this fails? EVP_CIPHER_CTX *cipherCtx = EVP_CIPHER_CTX_new(); EVP_CipherInit(cipherCtx, EVP_aes_128_cfb8(), (byte*) keyBytes, (byte*) keyBytes, forEncryption); @@ -13,10 +14,12 @@ jlong JNICALL Java_net_md_15_bungee_NativeCipherImpl_init(JNIEnv* env, jobject o return (jlong) cipherCtx; } -void Java_net_md_15_bungee_NativeCipherImpl_free(JNIEnv* env, jobject obj, jlong ctx) { +void Java_net_md_15_bungee_jni_cipher_NativeCipherImpl_free(JNIEnv* env, jobject obj, jlong ctx) { + // TODO: Perhaps we need to throw some exceptions in the unlikely event this fails? EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*) ctx); } -void Java_net_md_15_bungee_NativeCipherImpl_cipher(JNIEnv* env, jobject obj, jlong ctx, jlong in, jlong out, jint length) { +void Java_net_md_15_bungee_jni_cipher_NativeCipherImpl_cipher(JNIEnv* env, jobject obj, jlong ctx, jlong in, jlong out, jint length) { + // TODO: Perhaps we need to throw some exceptions in the unlikely event this fails? EVP_CipherUpdate((EVP_CIPHER_CTX*) ctx, (byte*) out, &length, (byte*) in, length); } diff --git a/native/src/main/c/NativeCompressImpl.cpp b/native/src/main/c/NativeCompressImpl.cpp new file mode 100644 index 00000000..0dc668c9 --- /dev/null +++ b/native/src/main/c/NativeCompressImpl.cpp @@ -0,0 +1,82 @@ +#include +#include +#include "net_md_5_bungee_jni_zlib_NativeCompressImpl.h" + +using namespace std; +typedef unsigned char byte; + +jint throwException(JNIEnv *env, string message) { + jclass exClass = env->FindClass("java/lang/RuntimeException"); + // Can never actually happen on a sane JVM, but better be safe anyway + if (exClass == NULL) { + return -1; + } + + return env->ThrowNew(exClass, message.c_str()); +} + +static jfieldID consumedID; +static jfieldID finishedID; + +void JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_initFields(JNIEnv* env, jclass clazz) { + // We trust that these fields will be there + consumedID = env->GetFieldID(clazz, "consumed", "I"); + finishedID = env->GetFieldID(clazz, "finished", "Z"); +} + +void JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_reset(JNIEnv* env, jobject obj, jlong ctx, jboolean compress) { + z_stream* stream = (z_stream*) ctx; + int ret = (compress) ? deflateReset(stream) : inflateReset(stream); + + if (ret != Z_OK) { + throwException(env, "Could not reset z_stream: " + to_string(ret)); + } +} + +void JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_end(JNIEnv* env, jobject obj, jlong ctx, jboolean compress) { + z_stream* stream = (z_stream*) ctx; + int ret = (compress) ? deflateEnd(stream) : inflateEnd(stream); + + free(stream); + + if (ret != Z_OK) { + throwException(env, "Could not free z_stream: " + to_string(ret)); + } +} + +jlong JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_init(JNIEnv* env, jobject obj, jboolean compress, jint level) { + z_stream* stream = (z_stream*) calloc(1, sizeof (z_stream)); + int ret = (compress) ? deflateInit(stream, level) : inflateInit(stream); + + if (ret != Z_OK) { + throwException(env, "Could not init z_stream: " + to_string(ret)); + } + + return (jlong) stream; +} + +jint JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_process(JNIEnv* env, jobject obj, jlong ctx, jlong in, jint inLength, jlong out, jint outLength, jboolean compress) { + z_stream* stream = (z_stream*) ctx; + + stream->avail_in = inLength; + stream->next_in = (byte*) in; + + stream->avail_out = outLength; + stream->next_out = (byte*) out; + + int ret = (compress) ? deflate(stream, Z_FINISH) : inflate(stream, Z_PARTIAL_FLUSH); + + switch (ret) { + case Z_STREAM_END: + env->SetBooleanField(obj, finishedID, true); + break; + case Z_OK: + break; + default: + throwException(env, "Unknown z_stream return code: " + to_string(ret)); + } + + env->SetIntField(obj, consumedID, inLength - stream->avail_in); + + return outLength - stream->avail_out; +} diff --git a/native/src/main/c/net_md_5_bungee_NativeCipherImpl.h b/native/src/main/c/net_md_5_bungee_NativeCipherImpl.h deleted file mode 100644 index c4187cc9..00000000 --- a/native/src/main/c/net_md_5_bungee_NativeCipherImpl.h +++ /dev/null @@ -1,37 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class net_md_5_bungee_NativeCipherImpl */ - -#ifndef _Included_net_md_5_bungee_NativeCipherImpl -#define _Included_net_md_5_bungee_NativeCipherImpl -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: net_md_5_bungee_NativeCipherImpl - * Method: init - * Signature: (Z[B)J - */ -JNIEXPORT jlong JNICALL Java_net_md_15_bungee_NativeCipherImpl_init - (JNIEnv *, jobject, jboolean, jbyteArray); - -/* - * Class: net_md_5_bungee_NativeCipherImpl - * Method: free - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_net_md_15_bungee_NativeCipherImpl_free - (JNIEnv *, jobject, jlong); - -/* - * Class: net_md_5_bungee_NativeCipherImpl - * Method: cipher - * Signature: (JJJI)V - */ -JNIEXPORT void JNICALL Java_net_md_15_bungee_NativeCipherImpl_cipher - (JNIEnv *, jobject, jlong, jlong, jlong, jint); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/native/src/main/c/net_md_5_bungee_jni_cipher_NativeCipherImpl.h b/native/src/main/c/net_md_5_bungee_jni_cipher_NativeCipherImpl.h new file mode 100644 index 00000000..87cb45f2 --- /dev/null +++ b/native/src/main/c/net_md_5_bungee_jni_cipher_NativeCipherImpl.h @@ -0,0 +1,37 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class net_md_5_bungee_jni_cipher_NativeCipherImpl */ + +#ifndef _Included_net_md_5_bungee_jni_cipher_NativeCipherImpl +#define _Included_net_md_5_bungee_jni_cipher_NativeCipherImpl +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: net_md_5_bungee_jni_cipher_NativeCipherImpl + * Method: init + * Signature: (Z[B)J + */ +JNIEXPORT jlong JNICALL Java_net_md_15_bungee_jni_cipher_NativeCipherImpl_init + (JNIEnv *, jobject, jboolean, jbyteArray); + +/* + * Class: net_md_5_bungee_jni_cipher_NativeCipherImpl + * Method: free + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_net_md_15_bungee_jni_cipher_NativeCipherImpl_free + (JNIEnv *, jobject, jlong); + +/* + * Class: net_md_5_bungee_jni_cipher_NativeCipherImpl + * Method: cipher + * Signature: (JJJI)V + */ +JNIEXPORT void JNICALL Java_net_md_15_bungee_jni_cipher_NativeCipherImpl_cipher + (JNIEnv *, jobject, jlong, jlong, jlong, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/native/src/main/c/net_md_5_bungee_jni_zlib_NativeCompressImpl.h b/native/src/main/c/net_md_5_bungee_jni_zlib_NativeCompressImpl.h new file mode 100644 index 00000000..befb7dcc --- /dev/null +++ b/native/src/main/c/net_md_5_bungee_jni_zlib_NativeCompressImpl.h @@ -0,0 +1,53 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class net_md_5_bungee_jni_zlib_NativeCompressImpl */ + +#ifndef _Included_net_md_5_bungee_jni_zlib_NativeCompressImpl +#define _Included_net_md_5_bungee_jni_zlib_NativeCompressImpl +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: net_md_5_bungee_jni_zlib_NativeCompressImpl + * Method: initFields + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_initFields + (JNIEnv *, jclass); + +/* + * Class: net_md_5_bungee_jni_zlib_NativeCompressImpl + * Method: end + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_end + (JNIEnv *, jobject, jlong, jboolean); + +/* + * Class: net_md_5_bungee_jni_zlib_NativeCompressImpl + * Method: reset + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_reset + (JNIEnv *, jobject, jlong, jboolean); + +/* + * Class: net_md_5_bungee_jni_zlib_NativeCompressImpl + * Method: init + * Signature: (ZI)J + */ +JNIEXPORT jlong JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_init + (JNIEnv *, jobject, jboolean, jint); + +/* + * Class: net_md_5_bungee_jni_zlib_NativeCompressImpl + * Method: process + * Signature: (JJIJIZ)I + */ +JNIEXPORT jint JNICALL Java_net_md_15_bungee_jni_zlib_NativeCompressImpl_process + (JNIEnv *, jobject, jlong, jlong, jint, jlong, jint, jboolean); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/native/src/main/java/net/md_5/bungee/jni/NativeCode.java b/native/src/main/java/net/md_5/bungee/jni/NativeCode.java new file mode 100644 index 00000000..8fe93b29 --- /dev/null +++ b/native/src/main/java/net/md_5/bungee/jni/NativeCode.java @@ -0,0 +1,84 @@ +package net.md_5.bungee.jni; + +import com.google.common.io.ByteStreams; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import net.md_5.bungee.jni.cipher.BungeeCipher; + +public final class NativeCode +{ + + private final String name; + private final Class javaImpl; + private final Class nativeImpl; + // + private boolean loaded; + + public NativeCode(String name, Class javaImpl, Class nativeImpl) + { + this.name = name; + this.javaImpl = javaImpl; + this.nativeImpl = nativeImpl; + + load(); + } + + public T newInstance() + { + try + { + return ( loaded ) ? nativeImpl.newInstance() : javaImpl.newInstance(); + } catch ( IllegalAccessException | InstantiationException ex ) + { + throw new RuntimeException( "Error getting instance", ex ); + } + } + + public boolean load() + { + if ( !loaded && isSupported() ) + { + String fullName = "bungeecord-" + name; + + try + { + System.loadLibrary( fullName ); + loaded = true; + } catch ( Throwable t ) + { + } + + if ( !loaded ) + { + try ( InputStream soFile = BungeeCipher.class.getClassLoader().getResourceAsStream( name + ".so" ) ) + { + // Else we will create and copy it to a temp file + File temp = File.createTempFile( fullName, ".so" ); + // Don't leave cruft on filesystem + temp.deleteOnExit(); + + try ( OutputStream outputStream = new FileOutputStream( temp ) ) + { + ByteStreams.copy( soFile, outputStream ); + } + + System.load( temp.getPath() ); + loaded = true; + } catch ( IOException ex ) + { + // Can't write to tmp? + } + } + } + + return loaded; + } + + public static boolean isSupported() + { + return "Linux".equals( System.getProperty( "os.name" ) ) && "amd64".equals( System.getProperty( "os.arch" ) ); + } +} diff --git a/native/src/main/java/net/md_5/bungee/BungeeCipher.java b/native/src/main/java/net/md_5/bungee/jni/cipher/BungeeCipher.java similarity index 93% rename from native/src/main/java/net/md_5/bungee/BungeeCipher.java rename to native/src/main/java/net/md_5/bungee/jni/cipher/BungeeCipher.java index a165d5ad..ebfbb5d5 100644 --- a/native/src/main/java/net/md_5/bungee/BungeeCipher.java +++ b/native/src/main/java/net/md_5/bungee/jni/cipher/BungeeCipher.java @@ -1,4 +1,4 @@ -package net.md_5.bungee; +package net.md_5.bungee.jni.cipher; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; diff --git a/native/src/main/java/net/md_5/bungee/FallbackCipher.java b/native/src/main/java/net/md_5/bungee/jni/cipher/JavaCipher.java similarity index 94% rename from native/src/main/java/net/md_5/bungee/FallbackCipher.java rename to native/src/main/java/net/md_5/bungee/jni/cipher/JavaCipher.java index 1b2fc267..7b2249f8 100644 --- a/native/src/main/java/net/md_5/bungee/FallbackCipher.java +++ b/native/src/main/java/net/md_5/bungee/jni/cipher/JavaCipher.java @@ -1,4 +1,4 @@ -package net.md_5.bungee; +package net.md_5.bungee.jni.cipher; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; @@ -8,7 +8,7 @@ import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; import java.security.GeneralSecurityException; -public class FallbackCipher implements BungeeCipher +public class JavaCipher implements BungeeCipher { private final Cipher cipher; @@ -25,7 +25,7 @@ public class FallbackCipher implements BungeeCipher } } - public FallbackCipher() throws GeneralSecurityException + public JavaCipher() throws GeneralSecurityException { this.cipher = Cipher.getInstance( "AES/CFB8/NoPadding" ); } diff --git a/native/src/main/java/net/md_5/bungee/NativeCipher.java b/native/src/main/java/net/md_5/bungee/jni/cipher/NativeCipher.java similarity index 58% rename from native/src/main/java/net/md_5/bungee/NativeCipher.java rename to native/src/main/java/net/md_5/bungee/jni/cipher/NativeCipher.java index 28a1c712..dea5e846 100644 --- a/native/src/main/java/net/md_5/bungee/NativeCipher.java +++ b/native/src/main/java/net/md_5/bungee/jni/cipher/NativeCipher.java @@ -1,15 +1,10 @@ -package net.md_5.bungee; +package net.md_5.bungee.jni.cipher; import com.google.common.base.Preconditions; -import com.google.common.io.ByteStreams; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import javax.crypto.SecretKey; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; import java.security.GeneralSecurityException; public class NativeCipher implements BungeeCipher @@ -18,54 +13,8 @@ public class NativeCipher implements BungeeCipher @Getter private final NativeCipherImpl nativeCipher = new NativeCipherImpl(); /*============================================================================*/ - private static boolean loaded; private long ctx; - public static boolean isSupported() - { - return "Linux".equals( System.getProperty( "os.name" ) ) && "amd64".equals( System.getProperty( "os.arch" ) ); - } - - public static boolean load() - { - if ( !loaded && isSupported() ) - { - try - { - System.loadLibrary( "bungeecord-native-cipher" ); - loaded = true; - } catch ( Throwable t ) - { - } - - if ( !loaded ) - { - try ( InputStream lib = BungeeCipher.class.getClassLoader().getResourceAsStream( "native-cipher.so" ) ) - { - // Else we will create and copy it to a temp file - File temp = File.createTempFile( "bungeecord-native-cipher", ".so" ); - temp.deleteOnExit(); - - try ( OutputStream outputStream = new FileOutputStream( temp ) ) - { - ByteStreams.copy( lib, outputStream ); - System.load( temp.getPath() ); - } - loaded = true; - } catch ( Throwable t ) - { - } - } - } - - return loaded; - } - - public static boolean isLoaded() - { - return loaded; - } - @Override public void init(boolean forEncryption, SecretKey key) throws GeneralSecurityException { diff --git a/native/src/main/java/net/md_5/bungee/NativeCipherImpl.java b/native/src/main/java/net/md_5/bungee/jni/cipher/NativeCipherImpl.java similarity index 83% rename from native/src/main/java/net/md_5/bungee/NativeCipherImpl.java rename to native/src/main/java/net/md_5/bungee/jni/cipher/NativeCipherImpl.java index 2167b1c4..df784080 100644 --- a/native/src/main/java/net/md_5/bungee/NativeCipherImpl.java +++ b/native/src/main/java/net/md_5/bungee/jni/cipher/NativeCipherImpl.java @@ -1,4 +1,4 @@ -package net.md_5.bungee; +package net.md_5.bungee.jni.cipher; class NativeCipherImpl { diff --git a/native/src/main/java/net/md_5/bungee/jni/zlib/BungeeZlib.java b/native/src/main/java/net/md_5/bungee/jni/zlib/BungeeZlib.java new file mode 100644 index 00000000..0c98ad40 --- /dev/null +++ b/native/src/main/java/net/md_5/bungee/jni/zlib/BungeeZlib.java @@ -0,0 +1,14 @@ +package net.md_5.bungee.jni.zlib; + +import io.netty.buffer.ByteBuf; +import java.util.zip.DataFormatException; + +public interface BungeeZlib +{ + + void init(boolean compress, int level); + + void free(); + + void process(ByteBuf in, ByteBuf out) throws DataFormatException; +} diff --git a/native/src/main/java/net/md_5/bungee/jni/zlib/JavaZlib.java b/native/src/main/java/net/md_5/bungee/jni/zlib/JavaZlib.java new file mode 100644 index 00000000..59810a5b --- /dev/null +++ b/native/src/main/java/net/md_5/bungee/jni/zlib/JavaZlib.java @@ -0,0 +1,76 @@ +package net.md_5.bungee.jni.zlib; + +import io.netty.buffer.ByteBuf; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +public class JavaZlib implements BungeeZlib +{ + + private final byte[] buffer = new byte[ 8192 ]; + // + private boolean compress; + private Deflater deflater; + private Inflater inflater; + + @Override + public void init(boolean compress, int level) + { + this.compress = compress; + free(); + + if ( compress ) + { + deflater = new Deflater( level ); + } else + { + inflater = new Inflater(); + } + } + + @Override + public void free() + { + if ( deflater != null ) + { + deflater.end(); + } + if ( inflater != null ) + { + inflater.end(); + } + } + + @Override + public void process(ByteBuf in, ByteBuf out) throws DataFormatException + { + byte[] inData = new byte[ in.readableBytes() ]; + in.readBytes( inData ); + + if ( compress ) + { + deflater.setInput( inData ); + deflater.finish(); + + while ( !deflater.finished() ) + { + int count = deflater.deflate( buffer ); + out.writeBytes( buffer, 0, count ); + } + + deflater.reset(); + } else + { + inflater.setInput( inData ); + + while ( !inflater.finished() ) + { + int count = inflater.inflate( buffer ); + out.writeBytes( buffer, 0, count ); + } + + inflater.reset(); + } + } +} diff --git a/native/src/main/java/net/md_5/bungee/jni/zlib/NativeCompressImpl.java b/native/src/main/java/net/md_5/bungee/jni/zlib/NativeCompressImpl.java new file mode 100644 index 00000000..3bcc6ddb --- /dev/null +++ b/native/src/main/java/net/md_5/bungee/jni/zlib/NativeCompressImpl.java @@ -0,0 +1,23 @@ +package net.md_5.bungee.jni.zlib; + +public class NativeCompressImpl +{ + + int consumed; + boolean finished; + + static + { + initFields(); + } + + static native void initFields(); + + native void end(long ctx, boolean compress); + + native void reset(long ctx, boolean compress); + + native long init(boolean compress, int compressionLevel); + + native int process(long ctx, long in, int inLength, long out, int outLength, boolean compress); +} diff --git a/native/src/main/java/net/md_5/bungee/jni/zlib/NativeZlib.java b/native/src/main/java/net/md_5/bungee/jni/zlib/NativeZlib.java new file mode 100644 index 00000000..9b334b4f --- /dev/null +++ b/native/src/main/java/net/md_5/bungee/jni/zlib/NativeZlib.java @@ -0,0 +1,61 @@ +package net.md_5.bungee.jni.zlib; + +import com.google.common.base.Preconditions; +import io.netty.buffer.ByteBuf; +import java.util.zip.DataFormatException; +import lombok.Getter; + +public class NativeZlib implements BungeeZlib +{ + + @Getter + private final NativeCompressImpl nativeCompress = new NativeCompressImpl(); + /*============================================================================*/ + private boolean compress; + private long ctx; + + @Override + public void init(boolean compress, int level) + { + free(); + + this.compress = compress; + this.ctx = nativeCompress.init( compress, level ); + } + + @Override + public void free() + { + if ( ctx != 0 ) + { + nativeCompress.end( ctx, compress ); + ctx = 0; + } + + nativeCompress.consumed = 0; + nativeCompress.finished = false; + } + + @Override + public void process(ByteBuf in, ByteBuf out) throws DataFormatException + { + // Smoke tests + in.memoryAddress(); + out.memoryAddress(); + Preconditions.checkState( ctx != 0, "Invalid pointer to compress!" ); + + while ( !nativeCompress.finished ) + { + out.ensureWritable( 8192 ); + + int processed = nativeCompress.process( ctx, in.memoryAddress() + in.readerIndex(), in.readableBytes(), out.memoryAddress() + out.writerIndex(), out.writableBytes(), compress ); + + in.readerIndex( in.readerIndex() + nativeCompress.consumed ); + out.writerIndex( out.writerIndex() + processed ); + } + + nativeCompress.reset( ctx, compress ); + nativeCompress.consumed = 0; + nativeCompress.finished = false; + } +} diff --git a/native/src/main/resources/native-cipher.so b/native/src/main/resources/native-cipher.so index 3bea4ebb..d50d7bbc 100755 Binary files a/native/src/main/resources/native-cipher.so and b/native/src/main/resources/native-cipher.so differ diff --git a/native/src/main/resources/native-compress.so b/native/src/main/resources/native-compress.so new file mode 100755 index 00000000..3a679b76 Binary files /dev/null and b/native/src/main/resources/native-compress.so differ diff --git a/native/src/test/java/net/md_5/bungee/NativeCipherTest.java b/native/src/test/java/net/md_5/bungee/NativeCipherTest.java index b8857cb0..d6af2d95 100644 --- a/native/src/test/java/net/md_5/bungee/NativeCipherTest.java +++ b/native/src/test/java/net/md_5/bungee/NativeCipherTest.java @@ -1,5 +1,8 @@ package net.md_5.bungee; +import net.md_5.bungee.jni.cipher.NativeCipher; +import net.md_5.bungee.jni.cipher.JavaCipher; +import net.md_5.bungee.jni.cipher.BungeeCipher; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.junit.Assert; @@ -8,6 +11,7 @@ import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import net.md_5.bungee.jni.NativeCode; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class NativeCipherTest @@ -20,13 +24,15 @@ public class NativeCipherTest }; private final SecretKey secret = new SecretKeySpec( new byte[ 16 ], "AES" ); private static final int BENCHMARK_COUNT = 50000; + // + private static final NativeCode factory = new NativeCode( "native-cipher", JavaCipher.class, NativeCipher.class ); @Test public void testOpenSSL() throws Exception { - if ( NativeCipher.isSupported() ) + if ( NativeCode.isSupported() ) { - boolean loaded = NativeCipher.load(); + boolean loaded = factory.load(); Assert.assertTrue( "Native cipher failed to load!", loaded ); NativeCipher cipher = new NativeCipher(); @@ -38,9 +44,9 @@ public class NativeCipherTest @Test public void testOpenSSLBenchmark() throws Exception { - if ( NativeCipher.isSupported() ) + if ( NativeCode.isSupported() ) { - boolean loaded = NativeCipher.load(); + boolean loaded = factory.load(); Assert.assertTrue( "Native cipher failed to load!", loaded ); NativeCipher cipher = new NativeCipher(); @@ -54,7 +60,7 @@ public class NativeCipherTest public void testJDK() throws Exception { // Create JDK cipher - BungeeCipher cipher = new FallbackCipher(); + BungeeCipher cipher = new JavaCipher(); System.out.println( "Testing Java cipher..." ); testACipher( cipher ); @@ -64,7 +70,7 @@ public class NativeCipherTest public void testJDKBenchmark() throws Exception { // Create JDK cipher - BungeeCipher cipher = new FallbackCipher(); + BungeeCipher cipher = new JavaCipher(); System.out.println( "Benchmarking Java cipher..." ); testBenchmark( cipher ); diff --git a/native/src/test/java/net/md_5/bungee/NativeZlibTest.java b/native/src/test/java/net/md_5/bungee/NativeZlibTest.java new file mode 100644 index 00000000..3bba0160 --- /dev/null +++ b/native/src/test/java/net/md_5/bungee/NativeZlibTest.java @@ -0,0 +1,65 @@ +package net.md_5.bungee; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.util.Arrays; +import java.util.Random; +import java.util.zip.DataFormatException; +import net.md_5.bungee.jni.NativeCode; +import net.md_5.bungee.jni.zlib.BungeeZlib; +import net.md_5.bungee.jni.zlib.JavaZlib; +import net.md_5.bungee.jni.zlib.NativeZlib; +import org.junit.Assert; +import org.junit.Test; + +public class NativeZlibTest +{ + + private final NativeCode factory = new NativeCode( "native-compress", JavaZlib.class, NativeZlib.class ); + + @Test + public void doTest() throws DataFormatException + { + test( factory.newInstance() ); + test( new JavaZlib() ); + } + + private void test(BungeeZlib zlib) throws DataFormatException + { + System.out.println( "Testing: " + zlib ); + long start = System.currentTimeMillis(); + + byte[] dataBuf = new byte[ 1 << 25 ]; // 32 megabytes + new Random().nextBytes( dataBuf ); + + zlib.init( true, 9 ); + + ByteBuf originalBuf = Unpooled.directBuffer(); + originalBuf.writeBytes( dataBuf ); + + ByteBuf compressed = Unpooled.directBuffer(); + + zlib.process( originalBuf, compressed ); + + // Repeat here to test .reset() + originalBuf = Unpooled.directBuffer(); + originalBuf.writeBytes( dataBuf ); + + compressed = Unpooled.directBuffer(); + + zlib.process( originalBuf, compressed ); + + ByteBuf uncompressed = Unpooled.directBuffer(); + + zlib.init( false, 0 ); + zlib.process( compressed, uncompressed ); + + byte[] check = new byte[ uncompressed.readableBytes() ]; + uncompressed.readBytes( check ); + + long elapsed = System.currentTimeMillis() - start; + System.out.println( "Took: " + elapsed + "ms" ); + + Assert.assertTrue( "Results do not match", Arrays.equals( dataBuf, check ) ); + } +} diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java b/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java index c6441445..c8d75c35 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java @@ -36,7 +36,11 @@ public class Varint21FrameDecoder extends ByteToMessageDecoder return; } else { - out.add( in.readBytes( length ) ); + // TODO: Really should be a slice! + ByteBuf dst = ctx.alloc().directBuffer( length ); + in.readBytes( dst ); + + out.add( dst ); return; } } diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index d2fdcd26..50ab8d2c 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -68,6 +68,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.command.*; +import net.md_5.bungee.compress.CompressFactory; import net.md_5.bungee.conf.YamlConfig; import net.md_5.bungee.forge.ForgeConstants; import net.md_5.bungee.log.LoggingOutputStream; @@ -198,13 +199,20 @@ public class BungeeCord extends ProxyServer System.setErr( new PrintStream( new LoggingOutputStream( logger, Level.SEVERE ), true ) ); System.setOut( new PrintStream( new LoggingOutputStream( logger, Level.INFO ), true ) ); - if ( NativeCipher.load() ) + if ( EncryptionUtil.nativeFactory.load() ) { logger.info( "Using OpenSSL based native cipher." ); } else { logger.info( "Using standard Java JCE cipher. To enable the OpenSSL based native cipher, please make sure you are using 64 bit Ubuntu or Debian with libssl installed." ); } + if ( CompressFactory.zlib.load() ) + { + logger.info( "Using native code compressor" ); + } else + { + logger.info( "Using standard Java compressor. To enable zero copy compression, run on 64 bit Linux" ); + } } /** diff --git a/proxy/src/main/java/net/md_5/bungee/EncryptionUtil.java b/proxy/src/main/java/net/md_5/bungee/EncryptionUtil.java index 2a5ebecf..d464e926 100644 --- a/proxy/src/main/java/net/md_5/bungee/EncryptionUtil.java +++ b/proxy/src/main/java/net/md_5/bungee/EncryptionUtil.java @@ -14,6 +14,10 @@ import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import lombok.Getter; +import net.md_5.bungee.jni.NativeCode; +import net.md_5.bungee.jni.cipher.BungeeCipher; +import net.md_5.bungee.jni.cipher.JavaCipher; +import net.md_5.bungee.jni.cipher.NativeCipher; import net.md_5.bungee.protocol.packet.EncryptionResponse; import net.md_5.bungee.protocol.packet.EncryptionRequest; @@ -27,6 +31,7 @@ public class EncryptionUtil public static final KeyPair keys; @Getter private static final SecretKey secret = new SecretKeySpec( new byte[ 16 ], "AES" ); + public static final NativeCode nativeFactory = new NativeCode( "native-cipher", JavaCipher.class, NativeCipher.class ); static { @@ -65,14 +70,7 @@ public class EncryptionUtil public static BungeeCipher getCipher(boolean forEncryption, SecretKey shared) throws GeneralSecurityException { - BungeeCipher cipher; - if ( NativeCipher.isLoaded() ) - { - cipher = new NativeCipher(); - } else - { - cipher = new FallbackCipher(); - } + BungeeCipher cipher = nativeFactory.newInstance(); cipher.init( forEncryption, shared ); return cipher; diff --git a/proxy/src/main/java/net/md_5/bungee/compress/CompressFactory.java b/proxy/src/main/java/net/md_5/bungee/compress/CompressFactory.java new file mode 100644 index 00000000..32b2b3e6 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/compress/CompressFactory.java @@ -0,0 +1,12 @@ +package net.md_5.bungee.compress; + +import net.md_5.bungee.jni.NativeCode; +import net.md_5.bungee.jni.zlib.BungeeZlib; +import net.md_5.bungee.jni.zlib.JavaZlib; +import net.md_5.bungee.jni.zlib.NativeZlib; + +public class CompressFactory +{ + + public static final NativeCode zlib = new NativeCode( "native-compress", JavaZlib.class, NativeZlib.class ); +} diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/PacketCompressor.java b/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java similarity index 52% rename from protocol/src/main/java/net/md_5/bungee/protocol/PacketCompressor.java rename to proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java index 216a92d3..d07cf462 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/PacketCompressor.java +++ b/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java @@ -1,20 +1,32 @@ -package net.md_5.bungee.protocol; +package net.md_5.bungee.compress; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; -import lombok.Setter; - import java.util.zip.Deflater; +import lombok.Setter; +import net.md_5.bungee.jni.zlib.BungeeZlib; +import net.md_5.bungee.protocol.DefinedPacket; public class PacketCompressor extends MessageToByteEncoder { - private final byte[] buffer = new byte[ 8192 ]; - private final Deflater deflater = new Deflater(); + private final BungeeZlib zlib = CompressFactory.zlib.newInstance(); @Setter private int threshold = 256; + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception + { + zlib.init( true, Deflater.DEFAULT_COMPRESSION ); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception + { + zlib.free(); + } + @Override protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { @@ -25,19 +37,9 @@ public class PacketCompressor extends MessageToByteEncoder out.writeBytes( msg ); } else { - byte[] data = new byte[ origSize ]; - msg.readBytes( data ); + DefinedPacket.writeVarInt( origSize, out ); - DefinedPacket.writeVarInt( data.length, out ); - - deflater.setInput( data ); - deflater.finish(); - while ( !deflater.finished() ) - { - int count = deflater.deflate( buffer ); - out.writeBytes( buffer, 0, count ); - } - deflater.reset(); + zlib.process( msg, out ); } } } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/PacketDecompressor.java b/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java similarity index 50% rename from protocol/src/main/java/net/md_5/bungee/protocol/PacketDecompressor.java rename to proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java index 5496a604..c1770016 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/PacketDecompressor.java +++ b/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java @@ -1,17 +1,28 @@ -package net.md_5.bungee.protocol; +package net.md_5.bungee.compress; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; - import java.util.List; -import java.util.zip.Inflater; +import net.md_5.bungee.jni.zlib.BungeeZlib; +import net.md_5.bungee.protocol.DefinedPacket; public class PacketDecompressor extends ByteToMessageDecoder { - private final Inflater inflater = new Inflater(); + private final BungeeZlib zlib = CompressFactory.zlib.newInstance(); + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception + { + zlib.init( false, 0 ); + } + + @Override + public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception + { + zlib.free(); + } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception @@ -27,14 +38,10 @@ public class PacketDecompressor extends ByteToMessageDecoder out.add( in.readBytes( in.readableBytes() ) ); } else { - byte[] compressedData = new byte[ in.readableBytes() ]; - in.readBytes( compressedData ); - inflater.setInput( compressedData ); + ByteBuf decompressed = ctx.alloc().directBuffer(); + zlib.process( in, decompressed ); - byte[] data = new byte[ size ]; - inflater.inflate( data ); - out.add( Unpooled.wrappedBuffer( data ) ); - inflater.reset(); + out.add( decompressed ); } } } diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index 8dfcc3cb..12b90b57 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -48,6 +48,7 @@ import net.md_5.bungee.protocol.packet.Kick; import net.md_5.bungee.api.AbstractReconnectHandler; import net.md_5.bungee.api.event.PlayerHandshakeEvent; import net.md_5.bungee.api.event.PreLoginEvent; +import net.md_5.bungee.jni.cipher.BungeeCipher; import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.packet.LegacyHandshake; diff --git a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java index 9106f937..06d19c3c 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java @@ -1,7 +1,7 @@ package net.md_5.bungee.netty; -import net.md_5.bungee.protocol.PacketCompressor; -import net.md_5.bungee.protocol.PacketDecompressor; +import net.md_5.bungee.compress.PacketCompressor; +import net.md_5.bungee.compress.PacketDecompressor; import net.md_5.bungee.protocol.PacketWrapper; import com.google.common.base.Preconditions; import io.netty.channel.Channel; diff --git a/proxy/src/main/java/net/md_5/bungee/netty/cipher/CipherDecoder.java b/proxy/src/main/java/net/md_5/bungee/netty/cipher/CipherDecoder.java index 7fcc323a..9838be86 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/cipher/CipherDecoder.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/cipher/CipherDecoder.java @@ -4,7 +4,7 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; import lombok.RequiredArgsConstructor; -import net.md_5.bungee.BungeeCipher; +import net.md_5.bungee.jni.cipher.BungeeCipher; import java.util.List; @RequiredArgsConstructor diff --git a/proxy/src/main/java/net/md_5/bungee/netty/cipher/CipherEncoder.java b/proxy/src/main/java/net/md_5/bungee/netty/cipher/CipherEncoder.java index df89b99b..fc19ded0 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/cipher/CipherEncoder.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/cipher/CipherEncoder.java @@ -4,7 +4,7 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import lombok.RequiredArgsConstructor; -import net.md_5.bungee.BungeeCipher; +import net.md_5.bungee.jni.cipher.BungeeCipher; @RequiredArgsConstructor public class CipherEncoder extends MessageToByteEncoder