Refactor native code and implement our own JNI wrapper around zlib.

The previous native cipher code has been refactored so that it may be loaded and used slightly more generically, allowing more native components to be easily added as time goes on.
I have also written a new native code compression module, which wraps around zlib in the same manner that Inflater / Deflater does, however it operates directly on the memory addresses of it's input / output buffers which means that we can save one, or maybe even two copies. To support this, the VarInt decoder has been adjusted to always use a native buffer.
This commit is contained in:
md_5 2015-02-01 21:05:16 +11:00
parent e6da9cbba8
commit 0d569ac0d1
29 changed files with 599 additions and 148 deletions

View File

@ -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

View File

@ -1,11 +1,12 @@
#include <openssl/evp.h>
#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);
}

View File

@ -0,0 +1,82 @@
#include <string>
#include <zlib.h>
#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;
}

View File

@ -1,37 +0,0 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

View File

@ -0,0 +1,37 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

View File

@ -0,0 +1,53 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

View File

@ -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<T>
{
private final String name;
private final Class<T> javaImpl;
private final Class<T> nativeImpl;
//
private boolean loaded;
public NativeCode(String name, Class<T> javaImpl, Class<T> 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" ) );
}
}

View File

@ -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;

View File

@ -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" );
}

View File

@ -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
{

View File

@ -1,4 +1,4 @@
package net.md_5.bungee;
package net.md_5.bungee.jni.cipher;
class NativeCipherImpl
{

View File

@ -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;
}

View File

@ -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();
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

Binary file not shown.

View File

@ -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<BungeeCipher> 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 );

View File

@ -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<BungeeZlib> 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 ) );
}
}

View File

@ -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;
}
}

View File

@ -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" );
}
}
/**

View File

@ -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<BungeeCipher> 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;

View File

@ -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<BungeeZlib> zlib = new NativeCode( "native-compress", JavaZlib.class, NativeZlib.class );
}

View File

@ -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<ByteBuf>
{
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<ByteBuf>
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 );
}
}
}

View File

@ -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<Object> 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 );
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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<ByteBuf>