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:
parent
e6da9cbba8
commit
0d569ac0d1
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
82
native/src/main/c/NativeCompressImpl.cpp
Normal file
82
native/src/main/c/NativeCompressImpl.cpp
Normal 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;
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
84
native/src/main/java/net/md_5/bungee/jni/NativeCode.java
Normal file
84
native/src/main/java/net/md_5/bungee/jni/NativeCode.java
Normal 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" ) );
|
||||
}
|
||||
}
|
@ -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;
|
@ -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" );
|
||||
}
|
@ -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
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
package net.md_5.bungee;
|
||||
package net.md_5.bungee.jni.cipher;
|
||||
|
||||
class NativeCipherImpl
|
||||
{
|
@ -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;
|
||||
}
|
76
native/src/main/java/net/md_5/bungee/jni/zlib/JavaZlib.java
Normal file
76
native/src/main/java/net/md_5/bungee/jni/zlib/JavaZlib.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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.
BIN
native/src/main/resources/native-compress.so
Executable file
BIN
native/src/main/resources/native-compress.so
Executable file
Binary file not shown.
@ -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 );
|
||||
|
65
native/src/test/java/net/md_5/bungee/NativeZlibTest.java
Normal file
65
native/src/test/java/net/md_5/bungee/NativeZlibTest.java
Normal 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 ) );
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user