3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Merge branch 'dev/1.1.0' into bungeequack-integrate

Dieser Commit ist enthalten in:
Andrew Steinborn 2019-11-26 15:52:53 -05:00
Commit b08f27b5c4
47 geänderte Dateien mit 901 neuen und 377 gelöschten Zeilen

4
.gitignore vendored
Datei anzeigen

@ -87,5 +87,5 @@ run/
plugins/ plugins/
### Natives stuff ### ### Natives stuff ###
natives/mbedtls native/mbedtls
natives/zlib-ng native/zlib-ng

Datei anzeigen

@ -7,4 +7,5 @@ cache:
- $HOME/.gradle/caches/ - $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/ - $HOME/.gradle/wrapper/
jdk: jdk:
- openjdk8 - openjdk8
- openjdk11

Datei anzeigen

@ -73,6 +73,9 @@ javadoc {
// Disable the crazy super-strict doclint tool in Java 8 // Disable the crazy super-strict doclint tool in Java 8
options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('Xdoclint:none', '-quiet')
// Mark sources as Java 8 source compatible
options.source = '8'
} }
publishing { publishing {

Datei anzeigen

@ -147,10 +147,12 @@ public final class KickedFromServerEvent implements
*/ */
public static final class RedirectPlayer implements ServerKickResult { public static final class RedirectPlayer implements ServerKickResult {
private final Component message;
private final RegisteredServer server; private final RegisteredServer server;
private RedirectPlayer(RegisteredServer server) { private RedirectPlayer(RegisteredServer server, @Nullable Component message) {
this.server = Preconditions.checkNotNull(server, "server"); this.server = Preconditions.checkNotNull(server, "server");
this.message = message;
} }
@Override @Override
@ -162,14 +164,23 @@ public final class KickedFromServerEvent implements
return server; return server;
} }
@Nullable
public Component getMessage() {
return message;
}
/** /**
* Creates a new redirect result to forward the player to the specified {@code server}. * Creates a new redirect result to forward the player to the specified {@code server}.
* *
* @param server the server to send the player to * @param server the server to send the player to
* @return the redirect result * @return the redirect result
*/ */
public static RedirectPlayer create(RegisteredServer server) { public static RedirectPlayer create(RegisteredServer server, @Nullable Component message) {
return new RedirectPlayer(server); return new RedirectPlayer(server, message);
}
public static ServerKickResult create(RegisteredServer server) {
return create(server, null);
} }
} }

Datei anzeigen

@ -0,0 +1,51 @@
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Fired when a player has finished connecting to the proxy and we need to choose the first server
* to connect to.
*/
public class PlayerChooseInitialServerEvent {
private final Player player;
private @Nullable RegisteredServer initialServer;
/**
* Constructs a PlayerChooseInitialServerEvent.
* @param player the player that was connected
* @param initialServer the initial server selected, may be {@code null}
*/
public PlayerChooseInitialServerEvent(Player player, @Nullable RegisteredServer initialServer) {
this.player = Preconditions.checkNotNull(player, "player");
this.initialServer = initialServer;
}
public Player getPlayer() {
return player;
}
public Optional<RegisteredServer> getInitialServer() {
return Optional.ofNullable(initialServer);
}
/**
* Sets the new initial server.
* @param server the initial server the player should connect to
*/
public void setInitialServer(RegisteredServer server) {
this.initialServer = server;
}
@Override
public String toString() {
return "PlayerChooseInitialServerEvent{"
+ "player=" + player
+ ", initialServer=" + initialServer
+ '}';
}
}

Datei anzeigen

@ -3,6 +3,8 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* This event is fired once the player has successfully connected to the target server and the * This event is fired once the player has successfully connected to the target server and the
@ -12,10 +14,24 @@ public final class ServerConnectedEvent {
private final Player player; private final Player player;
private final RegisteredServer server; private final RegisteredServer server;
private final @Nullable RegisteredServer previousServer;
public ServerConnectedEvent(Player player, RegisteredServer server) { /**
* Constructs a ServerConnectedEvent.
* @param player the player that was connected
* @param server the server the player was connected to
* @param previousServer the server the player was previously connected to, null if none
*/
public ServerConnectedEvent(Player player, RegisteredServer server,
@Nullable RegisteredServer previousServer) {
this.player = Preconditions.checkNotNull(player, "player"); this.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server"); this.server = Preconditions.checkNotNull(server, "server");
this.previousServer = previousServer;
}
@Deprecated
public ServerConnectedEvent(Player player, RegisteredServer server) {
this(player, server, null);
} }
public Player getPlayer() { public Player getPlayer() {
@ -26,11 +42,16 @@ public final class ServerConnectedEvent {
return server; return server;
} }
public Optional<RegisteredServer> getPreviousServer() {
return Optional.ofNullable(previousServer);
}
@Override @Override
public String toString() { public String toString() {
return "ServerConnectedEvent{" return "ServerConnectedEvent{"
+ "player=" + player + "player=" + player
+ ", server=" + server + ", server=" + server
+ ", previousServer=" + previousServer
+ '}'; + '}';
} }
} }

Datei anzeigen

@ -33,7 +33,8 @@ public enum ProtocolVersion {
MINECRAFT_1_14_1(480, "1.14.1"), MINECRAFT_1_14_1(480, "1.14.1"),
MINECRAFT_1_14_2(485, "1.14.2"), MINECRAFT_1_14_2(485, "1.14.2"),
MINECRAFT_1_14_3(490, "1.14.3"), MINECRAFT_1_14_3(490, "1.14.3"),
MINECRAFT_1_14_4(498, "1.14.4"); MINECRAFT_1_14_4(498, "1.14.4"),
MINECRAFT_1_15(566, "1.15-pre2");
private final int protocol; private final int protocol;
private final String name; private final String name;

Datei anzeigen

@ -0,0 +1,195 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2018 Jon Chambers
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.velocitypowered.api.util;
import java.util.UUID;
/**
* This is a modified FastUUID implementation. The primary difference is that it does not dash its
* UUIDs. As the native Java 9+ UUID.toString() implementation dashes its UUIDs, we use the FastUUID
* internal method, which ought to be faster than a String.replace().
*/
class FastUuidSansHyphens {
private static final int MOJANG_BROKEN_UUID_LENGTH = 32;
private static final char[] HEX_DIGITS =
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private static final long[] HEX_VALUES = new long[128];
static {
for (int i = 0; i < HEX_VALUES.length; i++) {
HEX_VALUES[i] = -1;
}
HEX_VALUES['0'] = 0x0;
HEX_VALUES['1'] = 0x1;
HEX_VALUES['2'] = 0x2;
HEX_VALUES['3'] = 0x3;
HEX_VALUES['4'] = 0x4;
HEX_VALUES['5'] = 0x5;
HEX_VALUES['6'] = 0x6;
HEX_VALUES['7'] = 0x7;
HEX_VALUES['8'] = 0x8;
HEX_VALUES['9'] = 0x9;
HEX_VALUES['a'] = 0xa;
HEX_VALUES['b'] = 0xb;
HEX_VALUES['c'] = 0xc;
HEX_VALUES['d'] = 0xd;
HEX_VALUES['e'] = 0xe;
HEX_VALUES['f'] = 0xf;
HEX_VALUES['A'] = 0xa;
HEX_VALUES['B'] = 0xb;
HEX_VALUES['C'] = 0xc;
HEX_VALUES['D'] = 0xd;
HEX_VALUES['E'] = 0xe;
HEX_VALUES['F'] = 0xf;
}
private FastUuidSansHyphens() {
// A private constructor prevents callers from accidentally instantiating FastUUID instances
}
/**
* Parses a UUID from the given character sequence. The character sequence must represent a
* Mojang UUID.
*
* @param uuidSequence the character sequence from which to parse a UUID
*
* @return the UUID represented by the given character sequence
*
* @throws IllegalArgumentException if the given character sequence does not conform to the string
* representation of a Mojang UUID.
*/
static UUID parseUuid(final CharSequence uuidSequence) {
if (uuidSequence.length() != MOJANG_BROKEN_UUID_LENGTH) {
throw new IllegalArgumentException("Illegal UUID string: " + uuidSequence);
}
long mostSignificantBits = getHexValueForChar(uuidSequence.charAt(0)) << 60;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(1)) << 56;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(2)) << 52;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(3)) << 48;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(4)) << 44;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(5)) << 40;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(6)) << 36;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(7)) << 32;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(8)) << 28;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(9)) << 24;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(10)) << 20;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(11)) << 16;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(12)) << 12;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(13)) << 8;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(14)) << 4;
mostSignificantBits |= getHexValueForChar(uuidSequence.charAt(15));
long leastSignificantBits = getHexValueForChar(uuidSequence.charAt(16)) << 60;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(17)) << 56;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(18)) << 52;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(19)) << 48;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(20)) << 44;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(21)) << 40;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(22)) << 36;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(23)) << 32;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(24)) << 28;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(25)) << 24;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(26)) << 20;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(27)) << 16;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(28)) << 12;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(29)) << 8;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(30)) << 4;
leastSignificantBits |= getHexValueForChar(uuidSequence.charAt(31));
return new UUID(mostSignificantBits, leastSignificantBits);
}
/**
* Returns a string representation of the given UUID. The returned string is formatted as a
* Mojang-style UUID.
*
* @param uuid the UUID to represent as a string
*
* @return a string representation of the given UUID
*/
public static String toString(final UUID uuid) {
final long mostSignificantBits = uuid.getMostSignificantBits();
final long leastSignificantBits = uuid.getLeastSignificantBits();
final char[] uuidChars = new char[MOJANG_BROKEN_UUID_LENGTH];
uuidChars[0] = HEX_DIGITS[(int) ((mostSignificantBits & 0xf000000000000000L) >>> 60)];
uuidChars[1] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0f00000000000000L) >>> 56)];
uuidChars[2] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00f0000000000000L) >>> 52)];
uuidChars[3] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000f000000000000L) >>> 48)];
uuidChars[4] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000f00000000000L) >>> 44)];
uuidChars[5] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000f0000000000L) >>> 40)];
uuidChars[6] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000f000000000L) >>> 36)];
uuidChars[7] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000f00000000L) >>> 32)];
uuidChars[8] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000f0000000L) >>> 28)];
uuidChars[9] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000f000000L) >>> 24)];
uuidChars[10] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000000f00000L) >>> 20)];
uuidChars[11] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000000f0000L) >>> 16)];
uuidChars[12] = HEX_DIGITS[(int) ((mostSignificantBits & 0x000000000000f000L) >>> 12)];
uuidChars[13] = HEX_DIGITS[(int) ((mostSignificantBits & 0x0000000000000f00L) >>> 8)];
uuidChars[14] = HEX_DIGITS[(int) ((mostSignificantBits & 0x00000000000000f0L) >>> 4)];
uuidChars[15] = HEX_DIGITS[(int) (mostSignificantBits & 0x000000000000000fL)];
uuidChars[16] = HEX_DIGITS[(int) ((leastSignificantBits & 0xf000000000000000L) >>> 60)];
uuidChars[17] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0f00000000000000L) >>> 56)];
uuidChars[18] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00f0000000000000L) >>> 52)];
uuidChars[19] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000f000000000000L) >>> 48)];
uuidChars[20] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000f00000000000L) >>> 44)];
uuidChars[21] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000f0000000000L) >>> 40)];
uuidChars[22] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000f000000000L) >>> 36)];
uuidChars[23] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000f00000000L) >>> 32)];
uuidChars[24] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000f0000000L) >>> 28)];
uuidChars[25] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000000f000000L) >>> 24)];
uuidChars[26] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000000f00000L) >>> 20)];
uuidChars[27] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000000f0000L) >>> 16)];
uuidChars[28] = HEX_DIGITS[(int) ((leastSignificantBits & 0x000000000000f000L) >>> 12)];
uuidChars[29] = HEX_DIGITS[(int) ((leastSignificantBits & 0x0000000000000f00L) >>> 8)];
uuidChars[30] = HEX_DIGITS[(int) ((leastSignificantBits & 0x00000000000000f0L) >>> 4)];
uuidChars[31] = HEX_DIGITS[(int) (leastSignificantBits & 0x000000000000000fL)];
return new String(uuidChars);
}
private static long getHexValueForChar(final char c) {
try {
if (HEX_VALUES[c] < 0) {
throw new IllegalArgumentException("Illegal hexadecimal digit: " + c);
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException("Illegal hexadecimal digit: " + c);
}
return HEX_VALUES[c];
}
}

Datei anzeigen

@ -1,7 +1,6 @@
package com.velocitypowered.api.util; package com.velocitypowered.api.util;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@ -23,11 +22,7 @@ public final class UuidUtils {
*/ */
public static UUID fromUndashed(final String string) { public static UUID fromUndashed(final String string) {
Objects.requireNonNull(string, "string"); Objects.requireNonNull(string, "string");
Preconditions.checkArgument(string.length() == 32, "Length is incorrect"); return FastUuidSansHyphens.parseUuid(string);
return new UUID(
Long.parseUnsignedLong(string.substring(0, 16), 16),
Long.parseUnsignedLong(string.substring(16), 16)
);
} }
/** /**
@ -38,10 +33,7 @@ public final class UuidUtils {
*/ */
public static String toUndashed(final UUID uuid) { public static String toUndashed(final UUID uuid) {
Preconditions.checkNotNull(uuid, "uuid"); Preconditions.checkNotNull(uuid, "uuid");
String msbStr = Long.toHexString(uuid.getMostSignificantBits()); return FastUuidSansHyphens.toString(uuid);
String lsbStr = Long.toHexString(uuid.getLeastSignificantBits());
return Strings.padStart(msbStr, 16, '0') + Strings.padStart(lsbStr,
16, '0');
} }
/** /**

Datei anzeigen

@ -24,7 +24,7 @@ allprojects {
junitVersion = '5.3.0-M1' junitVersion = '5.3.0-M1'
slf4jVersion = '1.7.25' slf4jVersion = '1.7.25'
log4jVersion = '2.11.2' log4jVersion = '2.11.2'
nettyVersion = '4.1.38.Final' nettyVersion = '4.1.43.Final'
guavaVersion = '25.1-jre' guavaVersion = '25.1-jre'
checkerFrameworkVersion = '2.7.0' checkerFrameworkVersion = '2.7.0'
configurateVersion = '3.6' configurateVersion = '3.6'

Datei anzeigen

@ -1,5 +1,7 @@
package com.velocitypowered.natives; package com.velocitypowered.natives;
import com.velocitypowered.natives.util.BufferPreference;
public interface Native { public interface Native {
boolean isNative(); BufferPreference preferredBufferType();
} }

Datei anzeigen

@ -0,0 +1,139 @@
package com.velocitypowered.natives.compression;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFER_SIZE;
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
import com.velocitypowered.natives.util.BufferPreference;
import io.netty.buffer.ByteBuf;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.ByteBuffer;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
public class Java11VelocityCompressor implements VelocityCompressor {
public static final VelocityCompressorFactory FACTORY = Java11VelocityCompressor::new;
// The use of MethodHandle is intentional. Velocity targets Java 8, and these methods don't exist
// in Java 8. This was also the most performant solution I could find, only slightly slower than a
// direct method call without long warmup times, requiring bytecode generation through ASM, or
// other stuff.
private static final MethodHandle DEFLATE_SET_INPUT;
private static final MethodHandle INFLATE_SET_INPUT;
private static final MethodHandle DEFLATE_CALL;
private static final MethodHandle INFLATE_CALL;
static {
try {
DEFLATE_SET_INPUT = MethodHandles.lookup().findVirtual(Deflater.class, "setInput",
MethodType.methodType(void.class, ByteBuffer.class));
INFLATE_SET_INPUT = MethodHandles.lookup().findVirtual(Inflater.class, "setInput",
MethodType.methodType(void.class, ByteBuffer.class));
DEFLATE_CALL = MethodHandles.lookup().findVirtual(Deflater.class, "deflate",
MethodType.methodType(int.class, ByteBuffer.class));
INFLATE_CALL = MethodHandles.lookup().findVirtual(Inflater.class, "inflate",
MethodType.methodType(int.class, ByteBuffer.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError("Can't use Java 11 compressor on your version of Java");
}
}
private final Deflater deflater;
private final Inflater inflater;
private boolean disposed = false;
private Java11VelocityCompressor(int level) {
this.deflater = new Deflater(level);
this.inflater = new Inflater();
}
@Override
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
ensureNotDisposed();
// We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly.
checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers");
checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers");
try {
int origIdx = source.readerIndex();
INFLATE_SET_INPUT.invokeExact(inflater, source.nioBuffer());
while (!inflater.finished() && inflater.getBytesRead() < source.readableBytes()) {
if (!destination.isWritable()) {
ensureMaxSize(destination, max);
destination.ensureWritable(ZLIB_BUFFER_SIZE);
}
ByteBuffer destNioBuf = destination.nioBuffer(destination.writerIndex(),
destination.writableBytes());
int produced = (int) INFLATE_CALL.invokeExact(inflater, destNioBuf);
source.readerIndex(origIdx + inflater.getTotalIn());
destination.writerIndex(destination.writerIndex() + produced);
}
inflater.reset();
} catch (Throwable e) {
if (e instanceof DataFormatException) {
throw (DataFormatException) e;
}
throw new RuntimeException(e);
}
}
@Override
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed();
// We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly.
checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers");
checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers");
try {
int origIdx = source.readerIndex();
DEFLATE_SET_INPUT.invokeExact(deflater, source.nioBuffer());
deflater.finish();
while (!deflater.finished()) {
if (!destination.isWritable()) {
destination.ensureWritable(ZLIB_BUFFER_SIZE);
}
ByteBuffer destNioBuf = destination.nioBuffer(destination.writerIndex(),
destination.writableBytes());
int produced = (int) DEFLATE_CALL.invokeExact(deflater, destNioBuf);
source.readerIndex(origIdx + deflater.getTotalIn());
destination.writerIndex(destination.writerIndex() + produced);
}
deflater.reset();
} catch (Throwable e) {
if (e instanceof DataFormatException) {
throw (DataFormatException) e;
}
throw new RuntimeException(e);
}
}
@Override
public void dispose() {
disposed = true;
deflater.end();
inflater.end();
}
private void ensureNotDisposed() {
checkState(!disposed, "Object already disposed");
}
@Override
public BufferPreference preferredBufferType() {
return BufferPreference.DIRECT_PREFERRED;
}
}

Datei anzeigen

@ -4,6 +4,7 @@ import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFE
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize; import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.BufferPreference;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;
@ -15,19 +16,37 @@ public class JavaVelocityCompressor implements VelocityCompressor {
private final Deflater deflater; private final Deflater deflater;
private final Inflater inflater; private final Inflater inflater;
private final byte[] buf; private byte[] buf = new byte[0];
private boolean disposed = false; private boolean disposed = false;
private JavaVelocityCompressor(int level) { private JavaVelocityCompressor(int level) {
this.deflater = new Deflater(level); this.deflater = new Deflater(level);
this.inflater = new Inflater(); this.inflater = new Inflater();
this.buf = new byte[ZLIB_BUFFER_SIZE];
} }
@Override @Override
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException { public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
ensureNotDisposed(); ensureNotDisposed();
final int available = source.readableBytes();
this.setInflaterInput(source);
if (destination.hasArray()) {
this.inflateDestinationIsHeap(destination, available, max);
} else {
if (buf.length == 0) {
buf = new byte[ZLIB_BUFFER_SIZE];
}
while (!inflater.finished() && inflater.getBytesRead() < available) {
ensureMaxSize(destination, max);
int read = inflater.inflate(buf);
destination.writeBytes(buf, 0, read);
}
}
inflater.reset();
}
private void setInflaterInput(ByteBuf source) {
final int available = source.readableBytes(); final int available = source.readableBytes();
if (source.hasArray()) { if (source.hasArray()) {
inflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(), available); inflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(), available);
@ -36,19 +55,45 @@ public class JavaVelocityCompressor implements VelocityCompressor {
source.readBytes(inData); source.readBytes(inData);
inflater.setInput(inData); inflater.setInput(inData);
} }
}
private void inflateDestinationIsHeap(ByteBuf destination, int available, int max)
throws DataFormatException {
while (!inflater.finished() && inflater.getBytesRead() < available) { while (!inflater.finished() && inflater.getBytesRead() < available) {
if (!destination.isWritable()) {
ensureMaxSize(destination, max);
destination.ensureWritable(ZLIB_BUFFER_SIZE);
}
ensureMaxSize(destination, max); ensureMaxSize(destination, max);
int read = inflater.inflate(buf); int produced = inflater.inflate(destination.array(), destination.arrayOffset()
destination.writeBytes(buf, 0, read); + destination.writerIndex(), destination.writableBytes());
destination.writerIndex(destination.writerIndex() + produced);
} }
inflater.reset();
} }
@Override @Override
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed(); ensureNotDisposed();
this.setDeflaterInput(source);
deflater.finish();
if (destination.hasArray()) {
this.deflateDestinationIsHeap(destination);
} else {
if (buf.length == 0) {
buf = new byte[ZLIB_BUFFER_SIZE];
}
while (!deflater.finished()) {
int bytes = deflater.deflate(buf);
destination.writeBytes(buf, 0, bytes);
}
}
deflater.reset();
}
private void setDeflaterInput(ByteBuf source) {
if (source.hasArray()) { if (source.hasArray()) {
deflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(), deflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(),
source.readableBytes()); source.readableBytes());
@ -57,12 +102,18 @@ public class JavaVelocityCompressor implements VelocityCompressor {
source.readBytes(inData); source.readBytes(inData);
deflater.setInput(inData); deflater.setInput(inData);
} }
deflater.finish(); }
private void deflateDestinationIsHeap(ByteBuf destination) {
while (!deflater.finished()) { while (!deflater.finished()) {
int bytes = deflater.deflate(buf); if (!destination.isWritable()) {
destination.writeBytes(buf, 0, bytes); destination.ensureWritable(ZLIB_BUFFER_SIZE);
}
int produced = deflater.deflate(destination.array(), destination.arrayOffset()
+ destination.writerIndex(), destination.writableBytes());
destination.writerIndex(destination.writerIndex() + produced);
} }
deflater.reset();
} }
@Override @Override
@ -77,7 +128,7 @@ public class JavaVelocityCompressor implements VelocityCompressor {
} }
@Override @Override
public boolean isNative() { public BufferPreference preferredBufferType() {
return false; return BufferPreference.HEAP_PREFERRED;
} }
} }

Datei anzeigen

@ -4,6 +4,7 @@ import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFE
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize; import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.BufferPreference;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
@ -82,7 +83,7 @@ public class NativeVelocityCompressor implements VelocityCompressor {
} }
@Override @Override
public boolean isNative() { public BufferPreference preferredBufferType() {
return true; return BufferPreference.DIRECT_REQUIRED;
} }
} }

Datei anzeigen

@ -1,6 +1,7 @@
package com.velocitypowered.natives.encryption; package com.velocitypowered.natives.encryption;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.BufferPreference;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -34,50 +35,25 @@ public class JavaVelocityCipher implements VelocityCipher {
} }
@Override @Override
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException { public void process(ByteBuf source) {
ensureNotDisposed(); ensureNotDisposed();
Preconditions.checkArgument(source.hasArray(), "No source array");
int inBytes = source.readableBytes(); int inBytes = source.readableBytes();
byte[] asBytes = ByteBufUtil.getBytes(source); int baseOffset = source.arrayOffset() + source.readerIndex();
int outputSize = cipher.getOutputSize(inBytes);
byte[] outBuf = new byte[outputSize];
cipher.update(asBytes, 0, inBytes, outBuf);
destination.writeBytes(outBuf);
}
@Override
public ByteBuf process(ChannelHandlerContext ctx, ByteBuf source) throws ShortBufferException {
ensureNotDisposed();
int inBytes = source.readableBytes();
ByteBuf asHeapBuf = toHeap(source);
ByteBuf out = ctx.alloc().heapBuffer(cipher.getOutputSize(inBytes));
try { try {
out.writerIndex( cipher.update(source.array(), baseOffset, inBytes, source.array(), baseOffset);
cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset() + asHeapBuf.readerIndex(), } catch (ShortBufferException ex) {
inBytes, out.array(), out.arrayOffset() + out.writerIndex())); /* This _really_ shouldn't happen - AES CFB8 will work in place.
return out; If you run into this, that means that for whatever reason the Java Runtime has determined
} catch (ShortBufferException e) { that the output buffer needs more bytes than the input buffer. When we are working with
out.release(); AES-CFB8, the output size is equal to the input size. See the problem? */
throw e; throw new AssertionError("Cipher update did not operate in place and requested a larger "
} finally { + "buffer than the source buffer");
asHeapBuf.release();
} }
} }
private static ByteBuf toHeap(ByteBuf src) {
if (src.hasArray()) {
return src.retain();
}
// Copy into a temporary heap buffer. We could use a local buffer, but Netty pools all buffers,
// so we'd lose more than we gain.
ByteBuf asHeapBuf = src.alloc().heapBuffer(src.readableBytes());
asHeapBuf.writeBytes(src);
return asHeapBuf;
}
@Override @Override
public void dispose() { public void dispose() {
disposed = true; disposed = true;
@ -88,7 +64,7 @@ public class JavaVelocityCipher implements VelocityCipher {
} }
@Override @Override
public boolean isNative() { public BufferPreference preferredBufferType() {
return false; return BufferPreference.HEAP_REQUIRED;
} }
} }

Datei anzeigen

@ -1,6 +1,7 @@
package com.velocitypowered.natives.encryption; package com.velocitypowered.natives.encryption;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.BufferPreference;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@ -32,40 +33,14 @@ public class NativeVelocityCipher implements VelocityCipher {
} }
@Override @Override
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException { public void process(ByteBuf source) {
ensureNotDisposed(); ensureNotDisposed();
source.memoryAddress(); source.memoryAddress();
destination.memoryAddress();
// The exact amount we read in is also the amount we write out. long base = source.memoryAddress() + source.readerIndex();
int len = source.readableBytes(); int len = source.readableBytes();
destination.ensureWritable(len);
impl.process(ctx, source.memoryAddress() + source.readerIndex(), len, impl.process(ctx, base, len, base, encrypt);
destination.memoryAddress() + destination.writerIndex(), encrypt);
source.skipBytes(len);
destination.writerIndex(destination.writerIndex() + len);
}
@Override
public ByteBuf process(ChannelHandlerContext ctx, ByteBuf source) throws ShortBufferException {
ensureNotDisposed();
source.memoryAddress(); // sanity check
int len = source.readableBytes();
ByteBuf out = ctx.alloc().directBuffer(len);
try {
impl.process(this.ctx, source.memoryAddress() + source.readerIndex(), len,
out.memoryAddress(), encrypt);
source.skipBytes(len);
out.writerIndex(len);
return out;
} catch (Exception e) {
out.release();
throw e;
}
} }
@Override @Override
@ -81,7 +56,7 @@ public class NativeVelocityCipher implements VelocityCipher {
} }
@Override @Override
public boolean isNative() { public BufferPreference preferredBufferType() {
return true; return BufferPreference.DIRECT_REQUIRED;
} }
} }

Datei anzeigen

@ -7,8 +7,5 @@ import io.netty.channel.ChannelHandlerContext;
import javax.crypto.ShortBufferException; import javax.crypto.ShortBufferException;
public interface VelocityCipher extends Disposable, Native { public interface VelocityCipher extends Disposable, Native {
void process(ByteBuf source);
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
ByteBuf process(ChannelHandlerContext ctx, ByteBuf source) throws ShortBufferException;
} }

Datei anzeigen

@ -0,0 +1,20 @@
package com.velocitypowered.natives.util;
public enum BufferPreference {
/**
* A heap buffer is required.
*/
HEAP_REQUIRED,
/**
* A heap buffer is preferred (but not required).
*/
HEAP_PREFERRED,
/**
* A direct buffer is preferred (but not required).
*/
DIRECT_PREFERRED,
/**
* A direct buffer is required.
*/
DIRECT_REQUIRED
}

Datei anzeigen

@ -20,28 +20,30 @@ public class MoreByteBufUtils {
* @return a buffer compatible with the native * @return a buffer compatible with the native
*/ */
public static ByteBuf ensureCompatible(ByteBufAllocator alloc, Native nativeStuff, ByteBuf buf) { public static ByteBuf ensureCompatible(ByteBufAllocator alloc, Native nativeStuff, ByteBuf buf) {
if (!nativeStuff.isNative() || buf.hasMemoryAddress()) { if (isCompatible(nativeStuff, buf)) {
// Will always work in either case. JNI code demands a memory address, and if we have a Java
// fallback, it uses byte arrays in all cases.
return buf.retain(); return buf.retain();
} }
// It's not, so we must make a direct copy. // It's not, so we must make a direct copy.
ByteBuf newBuf = alloc.directBuffer(buf.readableBytes()); ByteBuf newBuf = preferredBuffer(alloc, nativeStuff, buf.readableBytes());
newBuf.writeBytes(buf); newBuf.writeBytes(buf);
return newBuf; return newBuf;
} }
/** private static boolean isCompatible(Native nativeStuff, ByteBuf buf) {
* Creates a {@link ByteBuf} that will have the best performance with the specified BufferPreference preferred = nativeStuff.preferredBufferType();
* {@code nativeStuff}. switch (preferred) {
* case DIRECT_PREFERRED:
* @param alloc the {@link ByteBufAllocator} to use case HEAP_PREFERRED:
* @param nativeStuff the native we are working with // The native prefers this type, but doesn't strictly require we provide it.
* @return a buffer compatible with the native return true;
*/ case DIRECT_REQUIRED:
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff) { return buf.hasMemoryAddress();
return nativeStuff.isNative() ? alloc.directBuffer() : alloc.heapBuffer(); case HEAP_REQUIRED:
return buf.hasArray();
default:
throw new AssertionError("Preferred buffer type unknown");
}
} }
/** /**
@ -55,7 +57,15 @@ public class MoreByteBufUtils {
*/ */
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff, public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff,
int initialCapacity) { int initialCapacity) {
return nativeStuff.isNative() ? alloc.directBuffer(initialCapacity) : alloc switch (nativeStuff.preferredBufferType()) {
.heapBuffer(initialCapacity); case HEAP_REQUIRED:
case HEAP_PREFERRED:
return alloc.heapBuffer(initialCapacity);
case DIRECT_PREFERRED:
case DIRECT_REQUIRED:
return alloc.directBuffer(initialCapacity);
default:
throw new AssertionError("Preferred buffer type unknown");
}
} }
} }

Datei anzeigen

@ -15,7 +15,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
@Override @Override
public T get() { public T get() {
return selected.object; return selected.constructed;
} }
private static <T> Variant<T> getVariant(List<Variant<T>> variants) { private static <T> Variant<T> getVariant(List<Variant<T>> variants) {
@ -38,9 +38,14 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
private Status status; private Status status;
private final Runnable setup; private final Runnable setup;
private final String name; private final String name;
private final T object; private final Supplier<T> object;
private T constructed;
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) { Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) {
this(possiblyAvailable, setup, name, () -> object);
}
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, Supplier<T> object) {
this.status = this.status =
possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE; possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE;
this.setup = setup; this.setup = setup;
@ -57,6 +62,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
if (status == Status.POSSIBLY_AVAILABLE) { if (status == Status.POSSIBLY_AVAILABLE) {
try { try {
setup.run(); setup.run();
constructed = object.get();
status = Status.SETUP; status = Status.SETUP;
} catch (Exception e) { } catch (Exception e) {
status = Status.SETUP_FAILURE; status = Status.SETUP_FAILURE;
@ -64,7 +70,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
} }
} }
return object; return constructed;
} }
} }

Datei anzeigen

@ -6,6 +6,7 @@ import java.util.function.BooleanSupplier;
public class NativeConstraints { public class NativeConstraints {
private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled"); private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled");
private static final boolean IS_AMD64;
private static final boolean CAN_GET_MEMORYADDRESS; private static final boolean CAN_GET_MEMORYADDRESS;
static { static {
@ -15,19 +16,28 @@ public class NativeConstraints {
} finally { } finally {
test.release(); test.release();
} }
String osArch = System.getProperty("os.arch", "");
// HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere
// give amd64.
IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64");
} }
static final BooleanSupplier MACOS = () -> { static final BooleanSupplier MACOS = () -> {
return NATIVES_ENABLED return NATIVES_ENABLED
&& CAN_GET_MEMORYADDRESS && CAN_GET_MEMORYADDRESS
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") && System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
&& System.getProperty("os.arch", "").equals("x86_64"); && IS_AMD64;
}; };
static final BooleanSupplier LINUX = () -> { static final BooleanSupplier LINUX = () -> {
return NATIVES_ENABLED return NATIVES_ENABLED
&& CAN_GET_MEMORYADDRESS && CAN_GET_MEMORYADDRESS
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux") && System.getProperty("os.name", "").equalsIgnoreCase("Linux")
&& System.getProperty("os.arch", "").equals("amd64"); && IS_AMD64;
};
static final BooleanSupplier JAVA_11 = () -> {
return Double.parseDouble(System.getProperty("java.specification.version")) >= 11;
}; };
} }

Datei anzeigen

@ -2,6 +2,7 @@ package com.velocitypowered.natives.util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.natives.NativeSetupException; import com.velocitypowered.natives.NativeSetupException;
import com.velocitypowered.natives.compression.Java11VelocityCompressor;
import com.velocitypowered.natives.compression.JavaVelocityCompressor; import com.velocitypowered.natives.compression.JavaVelocityCompressor;
import com.velocitypowered.natives.compression.NativeVelocityCompressor; import com.velocitypowered.natives.compression.NativeVelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressorFactory; import com.velocitypowered.natives.compression.VelocityCompressorFactory;
@ -52,6 +53,8 @@ public class Natives {
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX, new NativeCodeLoader.Variant<>(NativeConstraints.LINUX,
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)", copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
NativeVelocityCompressor.FACTORY), NativeVelocityCompressor.FACTORY),
new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> {
}, "Java 11", () -> Java11VelocityCompressor.FACTORY),
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> { new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
}, "Java", JavaVelocityCompressor.FACTORY) }, "Java", JavaVelocityCompressor.FACTORY)
) )

Datei anzeigen

@ -6,6 +6,7 @@ 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.LINUX;
import static org.junit.jupiter.api.condition.OS.MAC; import static org.junit.jupiter.api.condition.OS.MAC;
import com.velocitypowered.natives.util.BufferPreference;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
@ -17,7 +18,9 @@ import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.JRE;
class VelocityCompressorTest { class VelocityCompressorTest {
@ -39,7 +42,7 @@ class VelocityCompressorTest {
@EnabledOnOs({MAC, LINUX}) @EnabledOnOs({MAC, LINUX})
void nativeIntegrityCheck() throws DataFormatException { void nativeIntegrityCheck() throws DataFormatException {
VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION); VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION);
if (compressor instanceof JavaVelocityCompressor) { if (compressor.preferredBufferType() != BufferPreference.DIRECT_REQUIRED) {
compressor.dispose(); compressor.dispose();
fail("Loaded regular compressor"); fail("Loaded regular compressor");
} }
@ -60,6 +63,22 @@ class VelocityCompressorTest {
check(compressor, () -> Unpooled.buffer(TEST_DATA.length + 32)); check(compressor, () -> Unpooled.buffer(TEST_DATA.length + 32));
} }
@Test
@EnabledOnJre(JRE.JAVA_11)
void java11IntegrityCheckDirect() throws DataFormatException {
VelocityCompressor compressor = Java11VelocityCompressor.FACTORY
.create(Deflater.DEFAULT_COMPRESSION);
check(compressor, () -> Unpooled.directBuffer(TEST_DATA.length + 32));
}
@Test
@EnabledOnJre(JRE.JAVA_11)
void java11IntegrityCheckHeap() throws DataFormatException {
VelocityCompressor compressor = Java11VelocityCompressor.FACTORY
.create(Deflater.DEFAULT_COMPRESSION);
check(compressor, () -> Unpooled.buffer(TEST_DATA.length + 32));
}
private void check(VelocityCompressor compressor, Supplier<ByteBuf> bufSupplier) private void check(VelocityCompressor compressor, Supplier<ByteBuf> bufSupplier)
throws DataFormatException { throws DataFormatException {
ByteBuf source = bufSupplier.get(); ByteBuf source = bufSupplier.get();

Datei anzeigen

@ -56,20 +56,18 @@ class VelocityCipherTest {
VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(AES_KEY, "AES")); VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(AES_KEY, "AES"));
ByteBuf source = bufSupplier.get(); ByteBuf source = bufSupplier.get();
ByteBuf dest = bufSupplier.get();
ByteBuf decryptionBuf = bufSupplier.get();
source.writeBytes(TEST_DATA); source.writeBytes(TEST_DATA);
ByteBuf workingBuf = source.copy();
try { try {
encrypt.process(source, dest); encrypt.process(workingBuf);
decrypt.process(dest, decryptionBuf); decrypt.process(workingBuf);
source.readerIndex(0); assertTrue(ByteBufUtil.equals(source, workingBuf));
assertTrue(ByteBufUtil.equals(source, decryptionBuf));
} finally { } finally {
source.release(); source.release();
dest.release(); workingBuf.release();
decryptionBuf.release();
decrypt.dispose(); decrypt.dispose();
encrypt.dispose(); encrypt.dispose();
} }

Datei anzeigen

@ -48,6 +48,8 @@ dependencies {
compile "io.netty:netty-handler:${nettyVersion}" compile "io.netty:netty-handler:${nettyVersion}"
compile "io.netty:netty-transport-native-epoll:${nettyVersion}" compile "io.netty:netty-transport-native-epoll:${nettyVersion}"
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64" compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}"
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64"
compile "io.netty:netty-resolver-dns:${nettyVersion}" compile "io.netty:netty-resolver-dns:${nettyVersion}"
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}" compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
@ -65,7 +67,7 @@ dependencies {
compile 'com.mojang:brigadier:1.0.15' compile 'com.mojang:brigadier:1.0.15'
compile 'org.asynchttpclient:async-http-client:2.10.1' compile 'org.asynchttpclient:async-http-client:2.10.4'
compile 'com.spotify:completable-futures:0.3.2' compile 'com.spotify:completable-futures:0.3.2'

Datei anzeigen

@ -72,7 +72,10 @@ public class VelocityCommand implements Command {
@Override @Override
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) { public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
if (currentArgs.length == 0) { if (currentArgs.length == 0) {
return ImmutableList.copyOf(subcommands.keySet()); return subcommands.entrySet().stream()
.filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey)
.collect(ImmutableList.toImmutableList());
} }
if (currentArgs.length == 1) { if (currentArgs.length == 1) {
@ -81,7 +84,7 @@ public class VelocityCommand implements Command {
currentArgs[0].length())) currentArgs[0].length()))
.filter(e -> e.getValue().hasPermission(source, new String[0])) .filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.collect(Collectors.toList()); .collect(ImmutableList.toImmutableList());
} }
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US)); Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));

Datei anzeigen

@ -100,7 +100,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
" configuration is used if no servers could be contacted." " configuration is used if no servers could be contacted."
}) })
@ConfigKey("ping-passthrough") @ConfigKey("ping-passthrough")
private PingPassthroughMode pingPassthrough; private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
@Table("[servers]") @Table("[servers]")
private final Servers servers; private final Servers servers;
@ -192,44 +192,38 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
} }
if (servers.getServers().isEmpty()) { if (servers.getServers().isEmpty()) {
logger.error("You have no servers configured. :("); logger.warn("You don't have any servers configured.");
valid = false; }
} else {
if (servers.getAttemptConnectionOrder().isEmpty()) { for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
logger.error("No fallback servers are configured!"); try {
AddressUtil.parseAddress(entry.getValue());
} catch (IllegalArgumentException e) {
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e);
valid = false; valid = false;
} }
}
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) { for (String s : servers.getAttemptConnectionOrder()) {
try { if (!servers.getServers().containsKey(s)) {
AddressUtil.parseAddress(entry.getValue()); logger.error("Fallback server " + s + " is not registered in your configuration!");
} catch (IllegalArgumentException e) { valid = false;
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e); }
valid = false; }
}
for (Map.Entry<String, List<String>> entry : forcedHosts.getForcedHosts().entrySet()) {
if (entry.getValue().isEmpty()) {
logger.error("Forced host '{}' does not contain any servers", entry.getKey());
valid = false;
continue;
} }
for (String s : servers.getAttemptConnectionOrder()) { for (String server : entry.getValue()) {
if (!servers.getServers().containsKey(s)) { if (!servers.getServers().containsKey(server)) {
logger.error("Fallback server " + s + " is not registered in your configuration!"); logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey());
valid = false; valid = false;
} }
} }
for (Map.Entry<String, List<String>> entry : forcedHosts.getForcedHosts().entrySet()) {
if (entry.getValue().isEmpty()) {
logger.error("Forced host '{}' does not contain any servers", entry.getKey());
valid = false;
continue;
}
for (String server : entry.getValue()) {
if (!servers.getServers().containsKey(server)) {
logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey());
valid = false;
}
}
}
} }
try { try {

Datei anzeigen

@ -34,14 +34,4 @@ public interface ConnectionType {
*/ */
GameProfile addGameProfileTokensIfRequired(GameProfile original, GameProfile addGameProfileTokensIfRequired(GameProfile original,
PlayerInfoForwarding forwardingType); PlayerInfoForwarding forwardingType);
/**
* Tests whether the hostname is the handshake packet is valid.
*
* @param address The address to check
* @return true if valid.
*/
default boolean checkServerAddressIsValid(String address) {
return true;
}
} }

Datei anzeigen

@ -80,7 +80,8 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// The goods are in hand! We got JoinGame. Let's transition completely to the new state. // The goods are in hand! We got JoinGame. Let's transition completely to the new state.
smc.setAutoReading(false); smc.setAutoReading(false);
server.getEventManager() server.getEventManager()
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer())) .fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer(),
existingConnection != null ? existingConnection.getServer() : null))
.whenCompleteAsync((x, error) -> { .whenCompleteAsync((x, error) -> {
// Strap on the ClientPlaySessionHandler if required. // Strap on the ClientPlaySessionHandler if required.
ClientPlaySessionHandler playHandler; ClientPlaySessionHandler playHandler;

Datei anzeigen

@ -304,26 +304,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Clear tab list to avoid duplicate entries // Clear tab list to avoid duplicate entries
player.getTabList().clearAll(); player.getTabList().clearAll();
// In order to handle switching to another server, you will need to send three packets: // In order to handle switching to another server, you will need to send two packets:
// //
// - The join game packet from the backend server // - The join game packet from the backend server, with a different dimension
// - A respawn packet with a different dimension // - A respawn with the correct dimension
// - Another respawn with the correct dimension
//
// The two respawns with different dimensions are required, otherwise the client gets
// confused.
// //
// Most notably, by having the client accept the join game packet, we can work around the need // Most notably, by having the client accept the join game packet, we can work around the need
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and // to perform entity ID rewrites, eliminating potential issues from rewriting packets and
// improving compatibility with mods. // improving compatibility with mods.
int realDim = joinGame.getDimension();
joinGame.setDimension(getFakeTemporaryDimensionId(realDim));
player.getConnection().delayedWrite(joinGame); player.getConnection().delayedWrite(joinGame);
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
player.getConnection().delayedWrite( player.getConnection().delayedWrite(
new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), new Respawn(realDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
joinGame.getLevelType())); joinGame.getGamemode(), joinGame.getLevelType()));
player.getConnection().delayedWrite(
new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(),
joinGame.getLevelType()));
} }
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to // Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
@ -360,11 +354,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
destination.completeJoin(); destination.completeJoin();
} }
private static int getFakeTemporaryDimensionId(int dim) {
return dim == 0 ? -1 : 0;
}
public List<UUID> getServerBossBars() { public List<UUID> getServerBossBars() {
return serverBossBars; return serverBossBars;
} }
private boolean handleCommandTabComplete(TabCompleteRequest packet) { private boolean handleCommandTabComplete(TabCompleteRequest packet) {
// In 1.13+, we need to do additional work for the richer suggestions available. // In 1.13+, we need to do additional work for the richer suggestions available.
String command = packet.getCommand().substring(1); String command = packet.getCommand().substring(1);
@ -389,31 +386,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
List<Offer> offers = new ArrayList<>(); List<Offer> offers = new ArrayList<>();
int longestLength = 0;
for (String suggestion : suggestions) { for (String suggestion : suggestions) {
offers.add(new Offer(suggestion)); offers.add(new Offer(suggestion));
if (suggestion.length() > longestLength) {
longestLength = suggestion.length();
}
} }
TabCompleteResponse resp = new TabCompleteResponse();
resp.setTransactionId(packet.getTransactionId());
int startPos = packet.getCommand().lastIndexOf(' ') + 1; int startPos = packet.getCommand().lastIndexOf(' ') + 1;
int length; if (startPos > 0) {
if (startPos == 0) { TabCompleteResponse resp = new TabCompleteResponse();
startPos = packet.getCommand().length() + 1; resp.setTransactionId(packet.getTransactionId());
length = longestLength; resp.setStart(startPos);
} else { resp.setLength(packet.getCommand().length() - startPos);
length = packet.getCommand().length() - startPos; resp.getOffers().addAll(offers);
player.getConnection().write(resp);
} }
resp.setStart(startPos);
resp.setLength(length);
resp.getOffers().addAll(offers);
player.getConnection().write(resp);
return true; return true;
} }

Datei anzeigen

@ -425,33 +425,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return; return;
} }
if (connectedServer == null) { boolean kickedFromCurrent = connectedServer == null || connectedServer.getServer().equals(rs);
Optional<RegisteredServer> nextServer = getNextServerToTry(rs); ServerKickResult result;
if (nextServer.isPresent()) { if (kickedFromCurrent) {
// There can't be any connection in flight now. Optional<RegisteredServer> next = getNextServerToTry(rs);
resetInFlightConnection(); result = next.<ServerKickResult>map(RedirectPlayer::create)
createConnectionRequest(nextServer.get()).fireAndForget(); .orElseGet(() -> DisconnectPlayer.create(friendlyReason));
} else {
disconnect(friendlyReason);
}
} else { } else {
boolean kickedFromCurrent = connectedServer.getServer().equals(rs); // If we were kicked by going to another server, the connection should not be in flight
ServerKickResult result; if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
if (kickedFromCurrent) { resetInFlightConnection();
Optional<RegisteredServer> next = getNextServerToTry(rs);
result = next.<ServerKickResult>map(RedirectPlayer::create)
.orElseGet(() -> DisconnectPlayer.create(friendlyReason));
} else {
// If we were kicked by going to another server, the connection should not be in flight
if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
resetInFlightConnection();
}
result = Notify.create(friendlyReason);
} }
KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason, result = Notify.create(friendlyReason);
!kickedFromCurrent, result);
handleKickEvent(originalEvent, friendlyReason);
} }
KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason,
!kickedFromCurrent, result);
handleKickEvent(originalEvent, friendlyReason);
} }
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) { private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) {
@ -471,7 +460,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
if (newResult == null || !newResult) { if (newResult == null || !newResult) {
disconnect(friendlyReason); disconnect(friendlyReason);
} else { } else {
sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason)); if (res.getMessage() == null) {
sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason));
} else {
sendMessage(res.getMessage());
}
} }
}, connection.eventLoop()); }, connection.eventLoop());
} else if (event.getResult() instanceof Notify) { } else if (event.getResult() instanceof Notify) {

Datei anzeigen

@ -26,9 +26,14 @@ import java.util.Optional;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent; import net.kyori.text.TranslatableComponent;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
public class HandshakeSessionHandler implements MinecraftSessionHandler { public class HandshakeSessionHandler implements MinecraftSessionHandler {
private static final Logger LOGGER = LogManager.getLogger(HandshakeSessionHandler.class);
private final MinecraftConnection connection; private final MinecraftConnection connection;
private final VelocityServer server; private final VelocityServer server;
@ -58,58 +63,72 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
public boolean handle(Handshake handshake) { public boolean handle(Handshake handshake) {
InitialInboundConnection ic = new InitialInboundConnection(connection, InitialInboundConnection ic = new InitialInboundConnection(connection,
cleanVhost(handshake.getServerAddress()), handshake); cleanVhost(handshake.getServerAddress()), handshake);
connection.setAssociation(ic); StateRegistry nextState = getStateForProtocol(handshake.getNextStatus());
switch (handshake.getNextStatus()) { if (nextState == null) {
LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus());
connection.close();
} else {
connection.setState(nextState);
connection.setProtocolVersion(handshake.getProtocolVersion());
connection.setAssociation(ic);
switch (nextState) {
case STATUS:
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
break;
case LOGIN:
this.handleLogin(handshake, ic);
break;
default:
// If you get this, it's a bug in Velocity.
throw new AssertionError("getStateForProtocol provided invalid state!");
}
}
return true;
}
private static @Nullable StateRegistry getStateForProtocol(int status) {
switch (status) {
case StateRegistry.STATUS_ID: case StateRegistry.STATUS_ID:
connection.setState(StateRegistry.STATUS); return StateRegistry.STATUS;
connection.setProtocolVersion(handshake.getProtocolVersion());
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
return true;
case StateRegistry.LOGIN_ID: case StateRegistry.LOGIN_ID:
connection.setState(StateRegistry.LOGIN); return StateRegistry.LOGIN;
connection.setProtocolVersion(handshake.getProtocolVersion());
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
connection.closeWith(Disconnect
.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
return true;
}
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
if (!server.getIpAttemptLimiter().attempt(address)) {
connection.closeWith(
Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
return true;
}
ConnectionType type = checkForForge(handshake);
connection.setType(type);
// Make sure legacy forwarding is not in use on this connection.
if (!type.checkServerAddressIsValid(handshake.getServerAddress())) {
connection.closeWith(Disconnect
.create(TextComponent.of("Running Velocity behind Velocity is unsupported.")));
return true;
}
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2
// and lower, otherwise IP information will never get forwarded.
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
connection.closeWith(Disconnect
.create(TextComponent.of("This server is only compatible with 1.13 and above.")));
return true;
}
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic));
connection.setSessionHandler(new LoginSessionHandler(server, connection, ic));
return true;
default: default:
throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus()); return null;
} }
} }
private ConnectionType checkForForge(Handshake handshake) { private void handleLogin(Handshake handshake, InitialInboundConnection ic) {
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
connection.closeWith(Disconnect
.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
return;
}
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
if (!server.getIpAttemptLimiter().attempt(address)) {
connection.closeWith(
Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
return;
}
connection.setType(getHandshakeConnectionType(handshake));
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2
// and lower, otherwise IP information will never get forwarded.
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
connection.closeWith(Disconnect
.create(TextComponent.of("This server is only compatible with 1.13 and above.")));
return;
}
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic));
connection.setSessionHandler(new LoginSessionHandler(server, connection, ic));
}
private ConnectionType getHandshakeConnectionType(Handshake handshake) {
// Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13). // Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13).
if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN) if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN)
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { && handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
@ -119,8 +138,8 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
// forge handshake attempts. Also sends a reset handshake packet on every transition. // forge handshake attempts. Also sends a reset handshake packet on every transition.
return ConnectionTypes.UNDETERMINED_17; return ConnectionTypes.UNDETERMINED_17;
} else { } else {
// For later: See if we can determine Forge 1.13+ here, else this will need to be UNDETERMINED // Note for future implementation: Forge 1.13+ identifies itself using a slightly different
// until later in the cycle (most likely determinable during the LOGIN phase) // hostname token.
return ConnectionTypes.VANILLA; return ConnectionTypes.VANILLA;
} }
} }

Datei anzeigen

@ -1,24 +1,21 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper; import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.proxy.VelocityServer.GSON; import static com.velocitypowered.proxy.VelocityServer.GSON;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
import static com.velocitypowered.proxy.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL;
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa; import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId; import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
import static org.asynchttpclient.Dsl.asyncHttpClient; import static org.asynchttpclient.Dsl.asyncHttpClient;
import static org.asynchttpclient.Dsl.config;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.net.UrlEscapers;
import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
@ -29,17 +26,12 @@ import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.protocol.packet.ServerLogin;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
import com.velocitypowered.proxy.protocol.packet.SetCompression; import com.velocitypowered.proxy.protocol.packet.SetCompression;
import com.velocitypowered.proxy.util.VelocityMessages; import com.velocitypowered.proxy.util.VelocityMessages;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyPair; import java.security.KeyPair;
import java.util.Arrays; import java.util.Arrays;
@ -50,7 +42,6 @@ import java.util.concurrent.ThreadLocalRandom;
import net.kyori.text.Component; import net.kyori.text.Component;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response; import org.asynchttpclient.Response;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -66,7 +57,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
private final InitialInboundConnection inbound; private final InitialInboundConnection inbound;
private @MonotonicNonNull ServerLogin login; private @MonotonicNonNull ServerLogin login;
private byte[] verify = EMPTY_BYTE_ARRAY; private byte[] verify = EMPTY_BYTE_ARRAY;
private int playerInfoId;
private @MonotonicNonNull ConnectedPlayer connectedPlayer; private @MonotonicNonNull ConnectedPlayer connectedPlayer;
LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection, LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
@ -79,29 +69,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(ServerLogin packet) { public boolean handle(ServerLogin packet) {
this.login = packet; this.login = packet;
if (mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0) { beginPreLogin();
// To make sure the connecting client isn't Velocity, send a plugin message that Velocity will
// recognize and respond to.
playerInfoId = ThreadLocalRandom.current().nextInt();
mcConnection.write(new LoginPluginMessage(playerInfoId, VELOCITY_IP_FORWARDING_CHANNEL,
Unpooled.EMPTY_BUFFER));
} else {
beginPreLogin();
}
return true;
}
@Override
public boolean handle(LoginPluginResponse packet) {
if (packet.getId() == playerInfoId) {
if (packet.isSuccess()) {
// Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening.
inbound.disconnect(VelocityMessages.NO_PROXY_BEHIND_PROXY);
} else {
// Proceed with the regular login process.
beginPreLogin();
}
}
return true; return true;
} }
@ -254,12 +222,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
} }
private void finishLogin(ConnectedPlayer player) { private void finishLogin(ConnectedPlayer player) {
Optional<RegisteredServer> toTry = player.getNextServerToTry();
if (!toTry.isPresent()) {
player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS);
return;
}
int threshold = server.getConfiguration().getCompressionThreshold(); int threshold = server.getConfiguration().getCompressionThreshold();
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
mcConnection.write(new SetCompression(threshold)); mcConnection.write(new SetCompression(threshold));
@ -292,11 +254,27 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player)); mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
server.getEventManager().fire(new PostLoginEvent(player)) server.getEventManager().fire(new PostLoginEvent(player))
.thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget()); .thenRun(() -> connectToInitialServer(player));
} }
}, mcConnection.eventLoop()); }, mcConnection.eventLoop());
} }
private void connectToInitialServer(ConnectedPlayer player) {
Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player,
initialFromConfig.orElse(null));
server.getEventManager().fire(event)
.thenRunAsync(() -> {
Optional<RegisteredServer> toTry = event.getInitialServer();
if (!toTry.isPresent()) {
player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS);
return;
}
player.createConnectionRequest(toTry.get()).fireAndForget();
}, mcConnection.eventLoop());
}
@Override @Override
public void handleUnknown(ByteBuf buf) { public void handleUnknown(ByteBuf buf) {
mcConnection.close(); mcConnection.close();

Datei anzeigen

@ -6,7 +6,7 @@ package com.velocitypowered.proxy.connection.forge.legacy;
public class LegacyForgeConstants { public class LegacyForgeConstants {
/** /**
* Clients attempting to connect to 1.8+ Forge servers will have * Clients attempting to connect to 1.8-1.12.2 Forge servers will have
* this token appended to the hostname in the initial handshake * this token appended to the hostname in the initial handshake
* packet. * packet.
*/ */

Datei anzeigen

@ -7,6 +7,11 @@ import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel; import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.kqueue.KQueueServerSocketChannel;
import io.netty.channel.kqueue.KQueueSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.ServerSocketChannel;
@ -22,7 +27,11 @@ enum TransportType {
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))), (name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class, EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
EpollDatagramChannel.class, EpollDatagramChannel.class,
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))); (name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))),
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class,
KQueueDatagramChannel.class,
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
final String name; final String name;
final Class<? extends ServerSocketChannel> serverSocketChannelClass; final Class<? extends ServerSocketChannel> serverSocketChannelClass;
@ -62,6 +71,8 @@ enum TransportType {
if (Epoll.isAvailable()) { if (Epoll.isAvailable()) {
return EPOLL; return EPOLL;
} else if (KQueue.isAvailable()) {
return KQUEUE;
} else { } else {
return NIO; return NIO;
} }

Datei anzeigen

@ -3,6 +3,7 @@ package com.velocitypowered.proxy.plugin;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Joiner;
import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
@ -101,6 +102,8 @@ public class VelocityPluginManager implements PluginManager {
continue; continue;
} }
logger.info("Loaded plugin {} {} by {}", plugin.getId(), plugin.getVersion()
.orElse("<UNKNOWN>"), Joiner.on(", ").join(plugin.getAuthors()));
registerPlugin(pluginObject); registerPlugin(pluginObject);
} }
} }

Datei anzeigen

@ -5,6 +5,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_15;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@ -120,51 +121,61 @@ public enum StateRegistry {
map(0x1F, MINECRAFT_1_14, false)); map(0x1F, MINECRAFT_1_14, false));
clientbound.register(BossBar.class, BossBar::new, clientbound.register(BossBar.class, BossBar::new,
map(0x0C, MINECRAFT_1_9, false)); map(0x0C, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_15, false));
clientbound.register(Chat.class, Chat::new, clientbound.register(Chat.class, Chat::new,
map(0x02, MINECRAFT_1_7_2, true), map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true), map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true)); map(0x0E, MINECRAFT_1_13, true),
map(0x0F, MINECRAFT_1_15, true));
clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new, clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new,
map(0x3A, MINECRAFT_1_7_2, false), map(0x3A, MINECRAFT_1_7_2, false),
map(0x0E, MINECRAFT_1_9, false), map(0x0E, MINECRAFT_1_9, false),
map(0x10, MINECRAFT_1_13, false)); map(0x10, MINECRAFT_1_13, false),
map(0x11, MINECRAFT_1_15, false));
clientbound.register(AvailableCommands.class, AvailableCommands::new, clientbound.register(AvailableCommands.class, AvailableCommands::new,
map(0x11, MINECRAFT_1_13, false)); map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false));
clientbound.register(PluginMessage.class, PluginMessage::new, clientbound.register(PluginMessage.class, PluginMessage::new,
map(0x3F, MINECRAFT_1_7_2, false), map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false), map(0x18, MINECRAFT_1_9, false),
map(0x19, MINECRAFT_1_13, false), map(0x19, MINECRAFT_1_13, false),
map(0x18, MINECRAFT_1_14, false)); map(0x18, MINECRAFT_1_14, false),
map(0x19, MINECRAFT_1_15, false));
clientbound.register(Disconnect.class, Disconnect::new, clientbound.register(Disconnect.class, Disconnect::new,
map(0x40, MINECRAFT_1_7_2, false), map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false), map(0x1A, MINECRAFT_1_9, false),
map(0x1B, MINECRAFT_1_13, false), map(0x1B, MINECRAFT_1_13, false),
map(0x1A, MINECRAFT_1_14, false)); map(0x1A, MINECRAFT_1_14, false),
map(0x1B, MINECRAFT_1_15, false));
clientbound.register(KeepAlive.class, KeepAlive::new, clientbound.register(KeepAlive.class, KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false), map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false), map(0x1F, MINECRAFT_1_9, false),
map(0x21, MINECRAFT_1_13, false), map(0x21, MINECRAFT_1_13, false),
map(0x20, MINECRAFT_1_14, false)); map(0x20, MINECRAFT_1_14, false),
map(0x21, MINECRAFT_1_15, false));
clientbound.register(JoinGame.class, JoinGame::new, clientbound.register(JoinGame.class, JoinGame::new,
map(0x01, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false), map(0x23, MINECRAFT_1_9, false),
map(0x25, MINECRAFT_1_13, false), map(0x25, MINECRAFT_1_13, false),
map(0x25, MINECRAFT_1_14, false)); map(0x25, MINECRAFT_1_14, false),
map(0x26, MINECRAFT_1_15, false));
clientbound.register(Respawn.class, Respawn::new, clientbound.register(Respawn.class, Respawn::new,
map(0x07, MINECRAFT_1_7_2, true), map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true), map(0x33, MINECRAFT_1_9, true),
map(0x34, MINECRAFT_1_12, true), map(0x34, MINECRAFT_1_12, true),
map(0x35, MINECRAFT_1_12_1, true), map(0x35, MINECRAFT_1_12_1, true),
map(0x38, MINECRAFT_1_13, true), map(0x38, MINECRAFT_1_13, true),
map(0x3A, MINECRAFT_1_14, true)); map(0x3A, MINECRAFT_1_14, true),
map(0x3B, MINECRAFT_1_15, true));
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
map(0x48, MINECRAFT_1_8, true), map(0x48, MINECRAFT_1_8, true),
map(0x32, MINECRAFT_1_9, true), map(0x32, MINECRAFT_1_9, true),
map(0x33, MINECRAFT_1_12, true), map(0x33, MINECRAFT_1_12, true),
map(0x34, MINECRAFT_1_12_1, true), map(0x34, MINECRAFT_1_12_1, true),
map(0x37, MINECRAFT_1_13, true), map(0x37, MINECRAFT_1_13, true),
map(0x39, MINECRAFT_1_14, true)); map(0x39, MINECRAFT_1_14, true),
map(0x3A, MINECRAFT_1_15, true));
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new, clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
map(0x47, MINECRAFT_1_8, true), map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true), map(0x48, MINECRAFT_1_9, true),
@ -172,20 +183,23 @@ public enum StateRegistry {
map(0x49, MINECRAFT_1_12, true), map(0x49, MINECRAFT_1_12, true),
map(0x4A, MINECRAFT_1_12_1, true), map(0x4A, MINECRAFT_1_12_1, true),
map(0x4E, MINECRAFT_1_13, true), map(0x4E, MINECRAFT_1_13, true),
map(0x53, MINECRAFT_1_14, true)); map(0x53, MINECRAFT_1_14, true),
map(0x54, MINECRAFT_1_15, true));
clientbound.register(TitlePacket.class, TitlePacket::new, clientbound.register(TitlePacket.class, TitlePacket::new,
map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true), map(0x45, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_12, true), map(0x47, MINECRAFT_1_12, true),
map(0x48, MINECRAFT_1_12_1, true), map(0x48, MINECRAFT_1_12_1, true),
map(0x4B, MINECRAFT_1_13, true), map(0x4B, MINECRAFT_1_13, true),
map(0x4F, MINECRAFT_1_14, true)); map(0x4F, MINECRAFT_1_14, true),
map(0x50, MINECRAFT_1_15, true));
clientbound.register(PlayerListItem.class, PlayerListItem::new, clientbound.register(PlayerListItem.class, PlayerListItem::new,
map(0x38, MINECRAFT_1_7_2, false), map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false), map(0x2D, MINECRAFT_1_9, false),
map(0x2E, MINECRAFT_1_12_1, false), map(0x2E, MINECRAFT_1_12_1, false),
map(0x30, MINECRAFT_1_13, false), map(0x30, MINECRAFT_1_13, false),
map(0x33, MINECRAFT_1_14, false)); map(0x33, MINECRAFT_1_14, false),
map(0x34, MINECRAFT_1_15, false));
} }
}, },
LOGIN { LOGIN {

Datei anzeigen

@ -18,6 +18,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.DatagramPacket;
import java.net.InetAddress; import java.net.InetAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@ -59,6 +60,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder() private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS) .expireAfterWrite(30, TimeUnit.SECONDS)
.build(); .build();
private final SecureRandom random;
private volatile @MonotonicNonNull List<QueryResponse.PluginInformation> pluginInformationList private volatile @MonotonicNonNull List<QueryResponse.PluginInformation> pluginInformationList
= null; = null;
@ -67,6 +69,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
public GS4QueryHandler(VelocityServer server) { public GS4QueryHandler(VelocityServer server) {
this.server = server; this.server = server;
this.random = new SecureRandom();
} }
private QueryResponse createInitialResponse() { private QueryResponse createInitialResponse() {
@ -111,7 +114,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
switch (type) { switch (type) {
case QUERY_TYPE_HANDSHAKE: { case QUERY_TYPE_HANDSHAKE: {
// Generate new challenge token and put it into the sessions cache // Generate new challenge token and put it into the sessions cache
int challengeToken = ThreadLocalRandom.current().nextInt(); int challengeToken = random.nextInt();
sessions.put(senderAddress, challengeToken); sessions.put(senderAddress, challengeToken);
// Respond with challenge token // Respond with challenge token

Datei anzeigen

@ -5,10 +5,10 @@ import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.natives.util.MoreByteBufUtils; import com.velocitypowered.natives.util.MoreByteBufUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List; import java.util.List;
public class MinecraftCipherDecoder extends ByteToMessageDecoder { public class MinecraftCipherDecoder extends MessageToMessageDecoder<ByteBuf> {
private final VelocityCipher cipher; private final VelocityCipher cipher;
@ -18,16 +18,19 @@ public class MinecraftCipherDecoder extends ByteToMessageDecoder {
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, in); ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, in).slice();
try { try {
out.add(cipher.process(ctx, compatible)); cipher.process(compatible);
} finally { out.add(compatible);
compatible.release(); in.skipBytes(in.readableBytes());
} catch (Exception e) {
compatible.release(); // compatible will never be used if we throw an exception
throw e;
} }
} }
@Override @Override
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
cipher.dispose(); cipher.dispose();
} }
} }

Datei anzeigen

@ -20,9 +20,11 @@ public class MinecraftCipherEncoder extends MessageToMessageEncoder<ByteBuf> {
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception { protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, msg); ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, msg);
try { try {
out.add(cipher.process(ctx, compatible)); cipher.process(compatible);
} finally { out.add(compatible);
compatible.release(); } catch (Exception e) {
compatible.release(); // compatible will never be used if we throw an exception
throw e;
} }
} }

Datei anzeigen

@ -13,7 +13,8 @@ import java.util.List;
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> { public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
private static final int MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB private static final int SOFT_MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB
private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 16 * 1024 * 1024; // 16MiB
private final int threshold; private final int threshold;
private final VelocityCompressor compressor; private final VelocityCompressor compressor;
@ -25,21 +26,23 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int expectedSize = ProtocolUtils.readVarInt(in); int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
if (expectedSize == 0) { if (claimedUncompressedSize == 0) {
// Strip the now-useless uncompressed size, this message is already uncompressed. // Strip the now-useless uncompressed size, this message is already uncompressed.
out.add(in.retainedSlice()); out.add(in.retainedSlice());
in.skipBytes(in.readableBytes()); in.skipBytes(in.readableBytes());
return; return;
} }
checkFrame(expectedSize >= threshold, "Uncompressed size %s is less than threshold %s", checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
expectedSize, threshold); + " threshold %s", claimedUncompressedSize, threshold);
int initialCapacity = Math.min(expectedSize, MAXIMUM_UNCOMPRESSED_SIZE); int allowedMax = Math.min(claimedUncompressedSize, HARD_MAXIMUM_UNCOMPRESSED_SIZE);
int initialCapacity = Math.min(claimedUncompressedSize, SOFT_MAXIMUM_UNCOMPRESSED_SIZE);
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity); ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
try { try {
compressor.inflate(compatibleIn, uncompressed, expectedSize); compressor.inflate(compatibleIn, uncompressed, allowedMax);
out.add(uncompressed); out.add(uncompressed);
} catch (Exception e) { } catch (Exception e) {
uncompressed.release(); uncompressed.release();

Datei anzeigen

@ -47,20 +47,21 @@ public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
packet.decode(msg, direction, registry.version); packet.decode(msg, direction, registry.version);
} catch (Exception e) { } catch (Exception e) {
throw new CorruptedFrameException( throw new CorruptedFrameException(
"Error decoding " + packet.getClass() + " Direction " + direction "Error decoding " + packet.getClass() + " " + getExtraConnectionDetail(packetId), e);
+ " Protocol " + registry.version + " State " + state + " ID " + Integer
.toHexString(packetId), e);
} }
if (msg.isReadable()) { if (msg.isReadable()) {
throw new CorruptedFrameException( throw new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " "
"Did not read full packet for " + packet.getClass() + " Direction " + direction + getExtraConnectionDetail(packetId));
+ " Protocol " + registry.version + " State " + state + " ID " + Integer
.toHexString(packetId));
} }
out.add(packet); out.add(packet);
} }
} }
private String getExtraConnectionDetail(int packetId) {
return "Direction " + direction + " Protocol " + registry.version + " State " + state
+ " ID " + Integer.toHexString(packetId);
}
public void setProtocolVersion(ProtocolVersion protocolVersion) { public void setProtocolVersion(ProtocolVersion protocolVersion) {
this.registry = direction.getProtocolRegistry(state, protocolVersion); this.registry = direction.getProtocolRegistry(state, protocolVersion);
} }

Datei anzeigen

@ -27,16 +27,13 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
// Make sure reader index of length buffer is returned to the beginning // Make sure reader index of length buffer is returned to the beginning
in.readerIndex(origReaderIndex); in.readerIndex(origReaderIndex);
int packetLength = ProtocolUtils.readVarInt(in); int packetLength = ProtocolUtils.readVarInt(in);
if (packetLength == 0) {
return;
}
if (in.readableBytes() < packetLength) { if (in.readableBytes() >= packetLength) {
out.add(in.readRetainedSlice(packetLength));
} else {
in.readerIndex(origReaderIndex); in.readerIndex(origReaderIndex);
return;
} }
out.add(in.readRetainedSlice(packetLength));
return; return;
} }
} }

Datei anzeigen

@ -1,5 +1,7 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
@ -11,6 +13,7 @@ import java.util.List;
public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> { public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder(); public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
private static final boolean IS_JAVA_CIPHER = Natives.cipher.get() == JavaVelocityCipher.FACTORY;
private MinecraftVarintLengthEncoder() { private MinecraftVarintLengthEncoder() {
} }
@ -18,7 +21,7 @@ public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBu
@Override @Override
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list)
throws Exception { throws Exception {
ByteBuf lengthBuf = ctx.alloc().buffer(5); // the maximum size of a varint ByteBuf lengthBuf = IS_JAVA_CIPHER ? ctx.alloc().heapBuffer(5) : ctx.alloc().directBuffer(5);
ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes()); ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes());
list.add(lengthBuf); list.add(lengthBuf);
list.add(buf.retain()); list.add(buf.retain());

Datei anzeigen

@ -12,11 +12,13 @@ public class JoinGame implements MinecraftPacket {
private int entityId; private int entityId;
private short gamemode; private short gamemode;
private int dimension; private int dimension;
private long partialHashedSeed; // 1.15+
private short difficulty; private short difficulty;
private short maxPlayers; private short maxPlayers;
private @Nullable String levelType; private @Nullable String levelType;
private int viewDistance; //1.14+ private int viewDistance; //1.14+
private boolean reducedDebugInfo; private boolean reducedDebugInfo;
private boolean mystery;
public int getEntityId() { public int getEntityId() {
return entityId; return entityId;
@ -42,6 +44,10 @@ public class JoinGame implements MinecraftPacket {
this.dimension = dimension; this.dimension = dimension;
} }
public long getPartialHashedSeed() {
return partialHashedSeed;
}
public short getDifficulty() { public short getDifficulty() {
return difficulty; return difficulty;
} }
@ -91,6 +97,7 @@ public class JoinGame implements MinecraftPacket {
+ "entityId=" + entityId + "entityId=" + entityId
+ ", gamemode=" + gamemode + ", gamemode=" + gamemode
+ ", dimension=" + dimension + ", dimension=" + dimension
+ ", partialHashedSeed=" + partialHashedSeed
+ ", difficulty=" + difficulty + ", difficulty=" + difficulty
+ ", maxPlayers=" + maxPlayers + ", maxPlayers=" + maxPlayers
+ ", levelType='" + levelType + '\'' + ", levelType='" + levelType + '\''
@ -111,6 +118,9 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
this.difficulty = buf.readUnsignedByte(); this.difficulty = buf.readUnsignedByte();
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
this.partialHashedSeed = buf.readLong();
}
this.maxPlayers = buf.readUnsignedByte(); this.maxPlayers = buf.readUnsignedByte();
this.levelType = ProtocolUtils.readString(buf, 16); this.levelType = ProtocolUtils.readString(buf, 16);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) {
@ -119,6 +129,9 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.reducedDebugInfo = buf.readBoolean(); this.reducedDebugInfo = buf.readBoolean();
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
this.mystery = buf.readBoolean();
}
} }
@Override @Override
@ -133,6 +146,9 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
buf.writeByte(difficulty); buf.writeByte(difficulty);
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
buf.writeLong(partialHashedSeed);
}
buf.writeByte(maxPlayers); buf.writeByte(maxPlayers);
if (levelType == null) { if (levelType == null) {
throw new IllegalStateException("No level type specified."); throw new IllegalStateException("No level type specified.");
@ -144,6 +160,9 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
buf.writeBoolean(reducedDebugInfo); buf.writeBoolean(reducedDebugInfo);
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
buf.writeBoolean(mystery);
}
} }
@Override @Override

Datei anzeigen

@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf;
public class Respawn implements MinecraftPacket { public class Respawn implements MinecraftPacket {
private int dimension; private int dimension;
private long partialHashedSeed;
private short difficulty; private short difficulty;
private short gamemode; private short gamemode;
private String levelType = ""; private String levelType = "";
@ -16,8 +17,10 @@ public class Respawn implements MinecraftPacket {
public Respawn() { public Respawn() {
} }
public Respawn(int dimension, short difficulty, short gamemode, String levelType) { public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType) {
this.dimension = dimension; this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty; this.difficulty = difficulty;
this.gamemode = gamemode; this.gamemode = gamemode;
this.levelType = levelType; this.levelType = levelType;
@ -31,6 +34,14 @@ public class Respawn implements MinecraftPacket {
this.dimension = dimension; this.dimension = dimension;
} }
public long getPartialHashedSeed() {
return partialHashedSeed;
}
public void setPartialHashedSeed(long partialHashedSeed) {
this.partialHashedSeed = partialHashedSeed;
}
public short getDifficulty() { public short getDifficulty() {
return difficulty; return difficulty;
} }
@ -59,6 +70,7 @@ public class Respawn implements MinecraftPacket {
public String toString() { public String toString() {
return "Respawn{" return "Respawn{"
+ "dimension=" + dimension + "dimension=" + dimension
+ ", partialHashedSeed=" + partialHashedSeed
+ ", difficulty=" + difficulty + ", difficulty=" + difficulty
+ ", gamemode=" + gamemode + ", gamemode=" + gamemode
+ ", levelType='" + levelType + '\'' + ", levelType='" + levelType + '\''
@ -71,6 +83,9 @@ public class Respawn implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
this.difficulty = buf.readUnsignedByte(); this.difficulty = buf.readUnsignedByte();
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
this.partialHashedSeed = buf.readLong();
}
this.gamemode = buf.readUnsignedByte(); this.gamemode = buf.readUnsignedByte();
this.levelType = ProtocolUtils.readString(buf, 16); this.levelType = ProtocolUtils.readString(buf, 16);
} }
@ -81,6 +96,9 @@ public class Respawn implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
buf.writeByte(difficulty); buf.writeByte(difficulty);
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
buf.writeLong(partialHashedSeed);
}
buf.writeByte(gamemode); buf.writeByte(gamemode);
ProtocolUtils.writeString(buf, levelType); ProtocolUtils.writeString(buf, levelType);
} }

Datei anzeigen

@ -17,7 +17,7 @@ public class VelocityNettyThreadFactory implements ThreadFactory {
@Override @Override
public Thread newThread(Runnable r) { public Thread newThread(Runnable r) {
String name = String.format(nameFormat, threadNumber.incrementAndGet()); String name = String.format(nameFormat, threadNumber.getAndIncrement());
return new FastThreadLocalThread(r, name); return new FastThreadLocalThread(r, name);
} }
} }