@@ -1,8 +1,10 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'checkstyle'
|
||||
}
|
||||
|
||||
apply from: '../gradle/checkerframework.gradle'
|
||||
apply from: '../gradle/checkstyle.gradle'
|
||||
|
||||
dependencies {
|
||||
compile "com.google.guava:guava:${guavaVersion}"
|
||||
|
@@ -1,13 +1,16 @@
|
||||
package com.velocitypowered.natives;
|
||||
|
||||
/**
|
||||
* This marker interface indicates that this object should be explicitly disposed before the object can no longer be used.
|
||||
* Not disposing these objects will likely leak native resources and eventually lead to resource exhaustion.
|
||||
* This marker interface indicates that this object should be explicitly disposed before the object
|
||||
* can no longer be used. Not disposing these objects will likely leak native resources and
|
||||
* eventually lead to resource exhaustion.
|
||||
*/
|
||||
public interface Disposable {
|
||||
/**
|
||||
* Disposes this object. After this call returns, any use of this object becomes invalid. Multiple calls to
|
||||
* this function should be safe: there should be no side-effects once an object is disposed.
|
||||
*/
|
||||
void dispose();
|
||||
|
||||
/**
|
||||
* Disposes this object. After this call returns, any use of this object becomes invalid. Multiple
|
||||
* calls to this function should be safe: there should be no side-effects once an object is
|
||||
* disposed.
|
||||
*/
|
||||
void dispose();
|
||||
}
|
||||
|
@@ -2,62 +2,62 @@ package com.velocitypowered.natives.compression;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
public static final VelocityCompressorFactory FACTORY = JavaVelocityCompressor::new;
|
||||
|
||||
private final Deflater deflater;
|
||||
private final Inflater inflater;
|
||||
private final byte[] buf;
|
||||
private boolean disposed = false;
|
||||
public static final VelocityCompressorFactory FACTORY = JavaVelocityCompressor::new;
|
||||
|
||||
private JavaVelocityCompressor(int level) {
|
||||
this.deflater = new Deflater(level);
|
||||
this.inflater = new Inflater();
|
||||
this.buf = new byte[ZLIB_BUFFER_SIZE];
|
||||
private final Deflater deflater;
|
||||
private final Inflater inflater;
|
||||
private final byte[] buf;
|
||||
private boolean disposed = false;
|
||||
|
||||
private JavaVelocityCompressor(int level) {
|
||||
this.deflater = new Deflater(level);
|
||||
this.inflater = new Inflater();
|
||||
this.buf = new byte[ZLIB_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
|
||||
byte[] inData = new byte[source.readableBytes()];
|
||||
source.readBytes(inData);
|
||||
inflater.setInput(inData);
|
||||
while (!inflater.finished()) {
|
||||
int read = inflater.inflate(buf);
|
||||
destination.writeBytes(buf, 0, read);
|
||||
}
|
||||
inflater.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
|
||||
byte[] inData = new byte[source.readableBytes()];
|
||||
source.readBytes(inData);
|
||||
inflater.setInput(inData);
|
||||
while (!inflater.finished()) {
|
||||
int read = inflater.inflate(buf);
|
||||
destination.writeBytes(buf, 0, read);
|
||||
}
|
||||
inflater.reset();
|
||||
byte[] inData = new byte[source.readableBytes()];
|
||||
source.readBytes(inData);
|
||||
deflater.setInput(inData);
|
||||
deflater.finish();
|
||||
while (!deflater.finished()) {
|
||||
int bytes = deflater.deflate(buf);
|
||||
destination.writeBytes(buf, 0, bytes);
|
||||
}
|
||||
deflater.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
@Override
|
||||
public void dispose() {
|
||||
disposed = true;
|
||||
deflater.end();
|
||||
inflater.end();
|
||||
}
|
||||
|
||||
byte[] inData = new byte[source.readableBytes()];
|
||||
source.readBytes(inData);
|
||||
deflater.setInput(inData);
|
||||
deflater.finish();
|
||||
while (!deflater.finished()) {
|
||||
int bytes = deflater.deflate(buf);
|
||||
destination.writeBytes(buf, 0, bytes);
|
||||
}
|
||||
deflater.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
disposed = true;
|
||||
deflater.end();
|
||||
inflater.end();
|
||||
}
|
||||
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
}
|
||||
|
@@ -2,75 +2,78 @@ package com.velocitypowered.natives.compression;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
public class NativeVelocityCompressor implements VelocityCompressor {
|
||||
public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new;
|
||||
|
||||
private final NativeZlibInflate inflate = new NativeZlibInflate();
|
||||
private final long inflateCtx;
|
||||
private final NativeZlibDeflate deflate = new NativeZlibDeflate();
|
||||
private final long deflateCtx;
|
||||
private boolean disposed = false;
|
||||
public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new;
|
||||
|
||||
private NativeVelocityCompressor(int level) {
|
||||
this.inflateCtx = inflate.init();
|
||||
this.deflateCtx = deflate.init(level);
|
||||
private final NativeZlibInflate inflate = new NativeZlibInflate();
|
||||
private final long inflateCtx;
|
||||
private final NativeZlibDeflate deflate = new NativeZlibDeflate();
|
||||
private final long deflateCtx;
|
||||
private boolean disposed = false;
|
||||
|
||||
private NativeVelocityCompressor(int level) {
|
||||
this.inflateCtx = inflate.init();
|
||||
this.deflateCtx = deflate.init(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
while (!inflate.finished && source.isReadable()) {
|
||||
if (!destination.isWritable()) {
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(),
|
||||
source.readableBytes(),
|
||||
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes());
|
||||
source.readerIndex(source.readerIndex() + inflate.consumed);
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
inflate.reset(inflateCtx);
|
||||
inflate.consumed = 0;
|
||||
inflate.finished = false;
|
||||
}
|
||||
|
||||
while (!inflate.finished && source.isReadable()) {
|
||||
if (!destination.isWritable()) {
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(), source.readableBytes(),
|
||||
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes());
|
||||
source.readerIndex(source.readerIndex() + inflate.consumed);
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
inflate.reset(inflateCtx);
|
||||
inflate.consumed = 0;
|
||||
inflate.finished = false;
|
||||
while (!deflate.finished) {
|
||||
if (!destination.isWritable()) {
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(),
|
||||
source.readableBytes(),
|
||||
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(),
|
||||
!source.isReadable());
|
||||
source.readerIndex(source.readerIndex() + deflate.consumed);
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
deflate.reset(deflateCtx);
|
||||
deflate.consumed = 0;
|
||||
deflate.finished = false;
|
||||
}
|
||||
|
||||
while (!deflate.finished) {
|
||||
if (!destination.isWritable()) {
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(), source.readableBytes(),
|
||||
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(), !source.isReadable());
|
||||
source.readerIndex(source.readerIndex() + deflate.consumed);
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
|
||||
deflate.reset(deflateCtx);
|
||||
deflate.consumed = 0;
|
||||
deflate.finished = false;
|
||||
}
|
||||
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (!disposed) {
|
||||
inflate.free(inflateCtx);
|
||||
deflate.free(deflateCtx);
|
||||
}
|
||||
disposed = true;
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (!disposed) {
|
||||
inflate.free(inflateCtx);
|
||||
deflate.free(deflateCtx);
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
@@ -4,21 +4,23 @@ package com.velocitypowered.natives.compression;
|
||||
* Represents a native interface for zlib's deflate functions.
|
||||
*/
|
||||
class NativeZlibDeflate {
|
||||
boolean finished;
|
||||
int consumed;
|
||||
|
||||
native long init(int level);
|
||||
boolean finished;
|
||||
int consumed;
|
||||
|
||||
native long free(long ctx);
|
||||
native long init(int level);
|
||||
|
||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength,
|
||||
boolean flush);
|
||||
native long free(long ctx);
|
||||
|
||||
native void reset(long ctx);
|
||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
||||
int destinationLength,
|
||||
boolean flush);
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
native void reset(long ctx);
|
||||
|
||||
private static native void initIDs();
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
|
||||
private static native void initIDs();
|
||||
}
|
||||
|
@@ -4,20 +4,22 @@ package com.velocitypowered.natives.compression;
|
||||
* Represents a native interface for zlib's inflate functions.
|
||||
*/
|
||||
class NativeZlibInflate {
|
||||
boolean finished;
|
||||
int consumed;
|
||||
|
||||
native long init();
|
||||
boolean finished;
|
||||
int consumed;
|
||||
|
||||
native long free(long ctx);
|
||||
native long init();
|
||||
|
||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength);
|
||||
native long free(long ctx);
|
||||
|
||||
native void reset(long ctx);
|
||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
||||
int destinationLength);
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
native void reset(long ctx);
|
||||
|
||||
private static native void initIDs();
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
|
||||
private static native void initIDs();
|
||||
}
|
||||
|
@@ -2,19 +2,19 @@ package com.velocitypowered.natives.compression;
|
||||
|
||||
import com.velocitypowered.natives.Disposable;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
/**
|
||||
* Provides an interface to inflate and deflate {@link ByteBuf}s using zlib.
|
||||
*/
|
||||
public interface VelocityCompressor extends Disposable {
|
||||
/**
|
||||
* The default preferred output buffer size for zlib.
|
||||
*/
|
||||
int ZLIB_BUFFER_SIZE = 8192;
|
||||
|
||||
void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
/**
|
||||
* The default preferred output buffer size for zlib.
|
||||
*/
|
||||
int ZLIB_BUFFER_SIZE = 8192;
|
||||
|
||||
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
|
||||
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.velocitypowered.natives.compression;
|
||||
|
||||
public interface VelocityCompressorFactory {
|
||||
VelocityCompressor create(int level);
|
||||
|
||||
VelocityCompressor create(int level);
|
||||
}
|
||||
|
@@ -2,53 +2,54 @@ package com.velocitypowered.natives.encryption;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class JavaVelocityCipher implements VelocityCipher {
|
||||
public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() {
|
||||
@Override
|
||||
public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException {
|
||||
return new JavaVelocityCipher(true, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException {
|
||||
return new JavaVelocityCipher(false, key);
|
||||
}
|
||||
};
|
||||
|
||||
private final Cipher cipher;
|
||||
private boolean disposed = false;
|
||||
|
||||
private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException {
|
||||
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
|
||||
this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(key.getEncoded()));
|
||||
public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() {
|
||||
@Override
|
||||
public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException {
|
||||
return new JavaVelocityCipher(true, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
||||
ensureNotDisposed();
|
||||
|
||||
byte[] sourceAsBytes = new byte[source.readableBytes()];
|
||||
source.readBytes(sourceAsBytes);
|
||||
|
||||
int outputSize = cipher.getOutputSize(sourceAsBytes.length);
|
||||
byte[] destinationBytes = new byte[outputSize];
|
||||
cipher.update(sourceAsBytes, 0, sourceAsBytes.length, destinationBytes);
|
||||
destination.writeBytes(destinationBytes);
|
||||
public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException {
|
||||
return new JavaVelocityCipher(false, key);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
disposed = true;
|
||||
}
|
||||
private final Cipher cipher;
|
||||
private boolean disposed = false;
|
||||
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException {
|
||||
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
|
||||
this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key,
|
||||
new IvParameterSpec(key.getEncoded()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
||||
ensureNotDisposed();
|
||||
|
||||
byte[] sourceAsBytes = new byte[source.readableBytes()];
|
||||
source.readBytes(sourceAsBytes);
|
||||
|
||||
int outputSize = cipher.getOutputSize(sourceAsBytes.length);
|
||||
byte[] destinationBytes = new byte[outputSize];
|
||||
cipher.update(sourceAsBytes, 0, sourceAsBytes.length, destinationBytes);
|
||||
destination.writeBytes(destinationBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
package com.velocitypowered.natives.encryption;
|
||||
|
||||
public class MbedtlsAesImpl {
|
||||
native long init(byte[] key);
|
||||
|
||||
native void process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, boolean encrypt);
|
||||
native long init(byte[] key);
|
||||
|
||||
native void free(long ptr);
|
||||
native void process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
||||
boolean encrypt);
|
||||
|
||||
native void free(long ptr);
|
||||
}
|
||||
|
@@ -1,55 +1,55 @@
|
||||
package com.velocitypowered.natives.encryption;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class NativeVelocityCipher implements VelocityCipher {
|
||||
public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() {
|
||||
@Override
|
||||
public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException {
|
||||
return new NativeVelocityCipher(true, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException {
|
||||
return new NativeVelocityCipher(false, key);
|
||||
}
|
||||
};
|
||||
private static final MbedtlsAesImpl impl = new MbedtlsAesImpl();
|
||||
|
||||
private final long ctx;
|
||||
private final boolean encrypt;
|
||||
private boolean disposed = false;
|
||||
|
||||
private NativeVelocityCipher(boolean encrypt, SecretKey key) {
|
||||
this.encrypt = encrypt;
|
||||
this.ctx = impl.init(key.getEncoded());
|
||||
public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() {
|
||||
@Override
|
||||
public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException {
|
||||
return new NativeVelocityCipher(true, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
// The exact amount we read in is also the amount we write out.
|
||||
int len = source.readableBytes();
|
||||
destination.ensureWritable(len);
|
||||
|
||||
impl.process(ctx, source.memoryAddress() + source.readerIndex(), len,
|
||||
destination.memoryAddress() + destination.writerIndex(), encrypt);
|
||||
|
||||
source.skipBytes(len);
|
||||
destination.writerIndex(destination.writerIndex() + len);
|
||||
public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException {
|
||||
return new NativeVelocityCipher(false, key);
|
||||
}
|
||||
};
|
||||
private static final MbedtlsAesImpl impl = new MbedtlsAesImpl();
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (!disposed) {
|
||||
impl.free(ctx);
|
||||
}
|
||||
disposed = true;
|
||||
private final long ctx;
|
||||
private final boolean encrypt;
|
||||
private boolean disposed = false;
|
||||
|
||||
private NativeVelocityCipher(boolean encrypt, SecretKey key) {
|
||||
this.encrypt = encrypt;
|
||||
this.ctx = impl.init(key.getEncoded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
// The exact amount we read in is also the amount we write out.
|
||||
int len = source.readableBytes();
|
||||
destination.ensureWritable(len);
|
||||
|
||||
impl.process(ctx, source.memoryAddress() + source.readerIndex(), len,
|
||||
destination.memoryAddress() + destination.writerIndex(), encrypt);
|
||||
|
||||
source.skipBytes(len);
|
||||
destination.writerIndex(destination.writerIndex() + len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (!disposed) {
|
||||
impl.free(ctx);
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
@@ -2,9 +2,9 @@ package com.velocitypowered.natives.encryption;
|
||||
|
||||
import com.velocitypowered.natives.Disposable;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
public interface VelocityCipher extends Disposable {
|
||||
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
|
||||
|
||||
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
|
||||
}
|
||||
|
@@ -1,10 +1,11 @@
|
||||
package com.velocitypowered.natives.encryption;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.GeneralSecurityException;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public interface VelocityCipherFactory {
|
||||
VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException;
|
||||
|
||||
VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException;
|
||||
VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException;
|
||||
|
||||
VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException;
|
||||
}
|
||||
|
@@ -1,81 +1,85 @@
|
||||
package com.velocitypowered.natives.util;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public final class NativeCodeLoader<T> implements Supplier<T> {
|
||||
private final Variant<T> selected;
|
||||
|
||||
NativeCodeLoader(List<Variant<T>> variants) {
|
||||
this.selected = getVariant(variants);
|
||||
private final Variant<T> selected;
|
||||
|
||||
NativeCodeLoader(List<Variant<T>> variants) {
|
||||
this.selected = getVariant(variants);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
return selected.object;
|
||||
}
|
||||
|
||||
private static <T> Variant<T> getVariant(List<Variant<T>> variants) {
|
||||
for (Variant<T> variant : variants) {
|
||||
T got = variant.get();
|
||||
if (got == null) {
|
||||
continue;
|
||||
}
|
||||
return variant;
|
||||
}
|
||||
throw new IllegalArgumentException("Can't find any suitable variants");
|
||||
}
|
||||
|
||||
public String getLoadedVariant() {
|
||||
return selected.name;
|
||||
}
|
||||
|
||||
static class Variant<T> {
|
||||
|
||||
private Status status;
|
||||
private final Runnable setup;
|
||||
private final String name;
|
||||
private final T object;
|
||||
|
||||
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) {
|
||||
this.status =
|
||||
possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE;
|
||||
this.setup = setup;
|
||||
this.name = name;
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
return selected.object;
|
||||
}
|
||||
public @Nullable T get() {
|
||||
if (status == Status.NOT_AVAILABLE || status == Status.SETUP_FAILURE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static <T> Variant<T> getVariant(List<Variant<T>> variants) {
|
||||
for (Variant<T> variant : variants) {
|
||||
T got = variant.get();
|
||||
if (got == null) {
|
||||
continue;
|
||||
}
|
||||
return variant;
|
||||
// Make sure setup happens only once
|
||||
if (status == Status.POSSIBLY_AVAILABLE) {
|
||||
try {
|
||||
setup.run();
|
||||
status = Status.SETUP;
|
||||
} catch (Exception e) {
|
||||
status = Status.SETUP_FAILURE;
|
||||
return null;
|
||||
}
|
||||
throw new IllegalArgumentException("Can't find any suitable variants");
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
public String getLoadedVariant() {
|
||||
return selected.name;
|
||||
}
|
||||
private enum Status {
|
||||
NOT_AVAILABLE,
|
||||
POSSIBLY_AVAILABLE,
|
||||
SETUP,
|
||||
SETUP_FAILURE
|
||||
}
|
||||
|
||||
static class Variant<T> {
|
||||
private Status status;
|
||||
private final Runnable setup;
|
||||
private final String name;
|
||||
private final T object;
|
||||
|
||||
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) {
|
||||
this.status = possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE;
|
||||
this.setup = setup;
|
||||
this.name = name;
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
public @Nullable T get() {
|
||||
if (status == Status.NOT_AVAILABLE || status == Status.SETUP_FAILURE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make sure setup happens only once
|
||||
if (status == Status.POSSIBLY_AVAILABLE) {
|
||||
try {
|
||||
setup.run();
|
||||
status = Status.SETUP;
|
||||
} catch (Exception e) {
|
||||
status = Status.SETUP_FAILURE;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
private enum Status {
|
||||
NOT_AVAILABLE,
|
||||
POSSIBLY_AVAILABLE,
|
||||
SETUP,
|
||||
SETUP_FAILURE
|
||||
}
|
||||
|
||||
static final BooleanSupplier MACOS = () -> System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") &&
|
||||
System.getProperty("os.arch").equals("x86_64");
|
||||
static final BooleanSupplier LINUX = () -> System.getProperties().getProperty("os.name", "").equalsIgnoreCase("Linux") &&
|
||||
System.getProperty("os.arch").equals("amd64");
|
||||
static final BooleanSupplier ALWAYS = () -> true;
|
||||
static final BooleanSupplier MACOS = () ->
|
||||
System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") &&
|
||||
System.getProperty("os.arch").equals("x86_64");
|
||||
static final BooleanSupplier LINUX = () ->
|
||||
System.getProperties().getProperty("os.name", "").equalsIgnoreCase("Linux") &&
|
||||
System.getProperty("os.arch").equals("amd64");
|
||||
static final BooleanSupplier ALWAYS = () -> true;
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ import com.velocitypowered.natives.compression.NativeVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressorFactory;
|
||||
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
@@ -14,55 +13,58 @@ import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class Natives {
|
||||
private Natives() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
private static Runnable copyAndLoadNative(String path) {
|
||||
return () -> {
|
||||
try {
|
||||
Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.')));
|
||||
InputStream nativeLib = Natives.class.getResourceAsStream(path);
|
||||
if (nativeLib == null) {
|
||||
throw new IllegalStateException("Native library " + path + " not found.");
|
||||
}
|
||||
private Natives() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
try {
|
||||
Files.deleteIfExists(tempFile);
|
||||
} catch (IOException ignored) {
|
||||
// Well, it doesn't matter...
|
||||
}
|
||||
}));
|
||||
System.load(tempFile.toAbsolutePath().toString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
private static Runnable copyAndLoadNative(String path) {
|
||||
return () -> {
|
||||
try {
|
||||
Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.')));
|
||||
InputStream nativeLib = Natives.class.getResourceAsStream(path);
|
||||
if (nativeLib == null) {
|
||||
throw new IllegalStateException("Native library " + path + " not found.");
|
||||
}
|
||||
|
||||
public static final NativeCodeLoader<VelocityCompressorFactory> compressor = new NativeCodeLoader<>(
|
||||
ImmutableList.of(
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS,
|
||||
copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)",
|
||||
NativeVelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX,
|
||||
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
|
||||
NativeVelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java", JavaVelocityCompressor.FACTORY)
|
||||
)
|
||||
);
|
||||
Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
try {
|
||||
Files.deleteIfExists(tempFile);
|
||||
} catch (IOException ignored) {
|
||||
// Well, it doesn't matter...
|
||||
}
|
||||
}));
|
||||
System.load(tempFile.toAbsolutePath().toString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final NativeCodeLoader<VelocityCipherFactory> cipher = new NativeCodeLoader<>(
|
||||
ImmutableList.of(
|
||||
public static final NativeCodeLoader<VelocityCompressorFactory> compressor = new NativeCodeLoader<>(
|
||||
ImmutableList.of(
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS,
|
||||
copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)",
|
||||
NativeVelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX,
|
||||
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
|
||||
NativeVelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
||||
}, "Java", JavaVelocityCompressor.FACTORY)
|
||||
)
|
||||
);
|
||||
|
||||
public static final NativeCodeLoader<VelocityCipherFactory> cipher = new NativeCodeLoader<>(
|
||||
ImmutableList.of(
|
||||
/*new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS,
|
||||
copyAndLoadNative("/macosx/velocity-cipher.dylib"), "mbed TLS (macOS)",
|
||||
NativeVelocityCipher.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX,
|
||||
copyAndLoadNative("/linux_x64/velocity-cipher.so"), "mbed TLS (Linux amd64)",
|
||||
NativeVelocityCipher.FACTORY),*/
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java", JavaVelocityCipher.FACTORY)
|
||||
)
|
||||
);
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
||||
}, "Java", JavaVelocityCipher.FACTORY)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@@ -1,65 +1,66 @@
|
||||
package com.velocitypowered.natives.compression;
|
||||
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.condition.OS.LINUX;
|
||||
import static org.junit.jupiter.api.condition.OS.MAC;
|
||||
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.Random;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
|
||||
class VelocityCompressorTest {
|
||||
@BeforeAll
|
||||
static void checkNatives() {
|
||||
Natives.compressor.getLoadedVariant();
|
||||
|
||||
@BeforeAll
|
||||
static void checkNatives() {
|
||||
Natives.compressor.getLoadedVariant();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs({MAC, LINUX})
|
||||
void nativeIntegrityCheck() throws DataFormatException {
|
||||
VelocityCompressor compressor = Natives.compressor.get().create(Deflater.DEFAULT_COMPRESSION);
|
||||
if (compressor instanceof JavaVelocityCompressor) {
|
||||
compressor.dispose();
|
||||
fail("Loaded regular compressor");
|
||||
}
|
||||
check(compressor);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnOs({ MAC, LINUX })
|
||||
void nativeIntegrityCheck() throws DataFormatException {
|
||||
VelocityCompressor compressor = Natives.compressor.get().create(Deflater.DEFAULT_COMPRESSION);
|
||||
if (compressor instanceof JavaVelocityCompressor) {
|
||||
compressor.dispose();
|
||||
fail("Loaded regular compressor");
|
||||
}
|
||||
check(compressor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaIntegrityCheck() throws DataFormatException {
|
||||
VelocityCompressor compressor = JavaVelocityCompressor.FACTORY.create(Deflater.DEFAULT_COMPRESSION);
|
||||
check(compressor);
|
||||
}
|
||||
|
||||
private void check(VelocityCompressor compressor) throws DataFormatException {
|
||||
ByteBuf source = Unpooled.directBuffer();
|
||||
ByteBuf dest = Unpooled.directBuffer();
|
||||
ByteBuf decompressed = Unpooled.directBuffer();
|
||||
|
||||
Random random = new Random(1);
|
||||
byte[] randomBytes = new byte[1 << 16];
|
||||
random.nextBytes(randomBytes);
|
||||
source.writeBytes(randomBytes);
|
||||
|
||||
try {
|
||||
compressor.deflate(source, dest);
|
||||
compressor.inflate(dest, decompressed);
|
||||
source.readerIndex(0);
|
||||
assertTrue(ByteBufUtil.equals(source, decompressed));
|
||||
} finally {
|
||||
source.release();
|
||||
dest.release();
|
||||
decompressed.release();
|
||||
compressor.dispose();
|
||||
}
|
||||
@Test
|
||||
void javaIntegrityCheck() throws DataFormatException {
|
||||
VelocityCompressor compressor = JavaVelocityCompressor.FACTORY
|
||||
.create(Deflater.DEFAULT_COMPRESSION);
|
||||
check(compressor);
|
||||
}
|
||||
|
||||
private void check(VelocityCompressor compressor) throws DataFormatException {
|
||||
ByteBuf source = Unpooled.directBuffer();
|
||||
ByteBuf dest = Unpooled.directBuffer();
|
||||
ByteBuf decompressed = Unpooled.directBuffer();
|
||||
|
||||
Random random = new Random(1);
|
||||
byte[] randomBytes = new byte[1 << 16];
|
||||
random.nextBytes(randomBytes);
|
||||
source.writeBytes(randomBytes);
|
||||
|
||||
try {
|
||||
compressor.deflate(source, dest);
|
||||
compressor.inflate(dest, decompressed);
|
||||
source.readerIndex(0);
|
||||
assertTrue(ByteBufUtil.equals(source, decompressed));
|
||||
} finally {
|
||||
source.release();
|
||||
dest.release();
|
||||
decompressed.release();
|
||||
compressor.dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,71 +1,71 @@
|
||||
package com.velocitypowered.natives.encryption;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Random;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class VelocityCipherTest {
|
||||
private static final int ENCRYPT_DATA_SIZE = 1 << 16;
|
||||
|
||||
@BeforeAll
|
||||
static void checkNatives() {
|
||||
Natives.cipher.getLoadedVariant();
|
||||
private static final int ENCRYPT_DATA_SIZE = 1 << 16;
|
||||
|
||||
@BeforeAll
|
||||
static void checkNatives() {
|
||||
Natives.cipher.getLoadedVariant();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void nativeIntegrityCheck() throws GeneralSecurityException {
|
||||
VelocityCipherFactory factory = Natives.cipher.get();
|
||||
if (factory == JavaVelocityCipher.FACTORY) {
|
||||
fail("Loaded regular compressor");
|
||||
}
|
||||
check(factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void nativeIntegrityCheck() throws GeneralSecurityException {
|
||||
VelocityCipherFactory factory = Natives.cipher.get();
|
||||
if (factory == JavaVelocityCipher.FACTORY) {
|
||||
fail("Loaded regular compressor");
|
||||
}
|
||||
check(factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaIntegrityCheck() throws GeneralSecurityException {
|
||||
check(JavaVelocityCipher.FACTORY);
|
||||
}
|
||||
|
||||
private void check(VelocityCipherFactory factory) throws GeneralSecurityException {
|
||||
// Generate a random 16-byte key.
|
||||
Random random = new Random(1);
|
||||
byte[] key = new byte[16];
|
||||
random.nextBytes(key);
|
||||
|
||||
VelocityCipher decrypt = factory.forDecryption(new SecretKeySpec(key, "AES"));
|
||||
VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(key, "AES"));
|
||||
|
||||
ByteBuf source = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
|
||||
ByteBuf dest = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
|
||||
ByteBuf decryptionBuf = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
|
||||
|
||||
byte[] randomBytes = new byte[ENCRYPT_DATA_SIZE];
|
||||
random.nextBytes(randomBytes);
|
||||
source.writeBytes(randomBytes);
|
||||
|
||||
try {
|
||||
encrypt.process(source, dest);
|
||||
decrypt.process(dest, decryptionBuf);
|
||||
source.readerIndex(0);
|
||||
assertTrue(ByteBufUtil.equals(source, decryptionBuf));
|
||||
} finally {
|
||||
source.release();
|
||||
dest.release();
|
||||
decryptionBuf.release();
|
||||
decrypt.dispose();
|
||||
encrypt.dispose();
|
||||
}
|
||||
@Test
|
||||
void javaIntegrityCheck() throws GeneralSecurityException {
|
||||
check(JavaVelocityCipher.FACTORY);
|
||||
}
|
||||
|
||||
private void check(VelocityCipherFactory factory) throws GeneralSecurityException {
|
||||
// Generate a random 16-byte key.
|
||||
Random random = new Random(1);
|
||||
byte[] key = new byte[16];
|
||||
random.nextBytes(key);
|
||||
|
||||
VelocityCipher decrypt = factory.forDecryption(new SecretKeySpec(key, "AES"));
|
||||
VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(key, "AES"));
|
||||
|
||||
ByteBuf source = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
|
||||
ByteBuf dest = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
|
||||
ByteBuf decryptionBuf = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
|
||||
|
||||
byte[] randomBytes = new byte[ENCRYPT_DATA_SIZE];
|
||||
random.nextBytes(randomBytes);
|
||||
source.writeBytes(randomBytes);
|
||||
|
||||
try {
|
||||
encrypt.process(source, dest);
|
||||
decrypt.process(dest, decryptionBuf);
|
||||
source.readerIndex(0);
|
||||
assertTrue(ByteBufUtil.equals(source, decryptionBuf));
|
||||
} finally {
|
||||
source.release();
|
||||
dest.release();
|
||||
decryptionBuf.release();
|
||||
decrypt.dispose();
|
||||
encrypt.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user