geforkt von Mirrors/Velocity
Merge branch 'dev/1.1.0' into bungeequack-integrate
Dieser Commit ist enthalten in:
Commit
b08f27b5c4
4
.gitignore
vendored
4
.gitignore
vendored
@ -87,5 +87,5 @@ run/
|
||||
plugins/
|
||||
|
||||
### Natives stuff ###
|
||||
natives/mbedtls
|
||||
natives/zlib-ng
|
||||
native/mbedtls
|
||||
native/zlib-ng
|
||||
|
@ -8,3 +8,4 @@ cache:
|
||||
- $HOME/.gradle/wrapper/
|
||||
jdk:
|
||||
- openjdk8
|
||||
- openjdk11
|
@ -73,6 +73,9 @@ javadoc {
|
||||
|
||||
// Disable the crazy super-strict doclint tool in Java 8
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
|
||||
// Mark sources as Java 8 source compatible
|
||||
options.source = '8'
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
@ -147,10 +147,12 @@ public final class KickedFromServerEvent implements
|
||||
*/
|
||||
public static final class RedirectPlayer implements ServerKickResult {
|
||||
|
||||
private final Component message;
|
||||
private final RegisteredServer server;
|
||||
|
||||
private RedirectPlayer(RegisteredServer server) {
|
||||
private RedirectPlayer(RegisteredServer server, @Nullable Component message) {
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -162,14 +164,23 @@ public final class KickedFromServerEvent implements
|
||||
return server;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Component getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new redirect result to forward the player to the specified {@code server}.
|
||||
*
|
||||
* @param server the server to send the player to
|
||||
* @return the redirect result
|
||||
*/
|
||||
public static RedirectPlayer create(RegisteredServer server) {
|
||||
return new RedirectPlayer(server);
|
||||
public static RedirectPlayer create(RegisteredServer server, @Nullable Component message) {
|
||||
return new RedirectPlayer(server, message);
|
||||
}
|
||||
|
||||
public static ServerKickResult create(RegisteredServer server) {
|
||||
return create(server, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
+ '}';
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ 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;
|
||||
|
||||
/**
|
||||
* 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 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.server = Preconditions.checkNotNull(server, "server");
|
||||
this.previousServer = previousServer;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ServerConnectedEvent(Player player, RegisteredServer server) {
|
||||
this(player, server, null);
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
@ -26,11 +42,16 @@ public final class ServerConnectedEvent {
|
||||
return server;
|
||||
}
|
||||
|
||||
public Optional<RegisteredServer> getPreviousServer() {
|
||||
return Optional.ofNullable(previousServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerConnectedEvent{"
|
||||
+ "player=" + player
|
||||
+ ", server=" + server
|
||||
+ ", previousServer=" + previousServer
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ public enum ProtocolVersion {
|
||||
MINECRAFT_1_14_1(480, "1.14.1"),
|
||||
MINECRAFT_1_14_2(485, "1.14.2"),
|
||||
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 String name;
|
||||
|
195
api/src/main/java/com/velocitypowered/api/util/FastUuidSansHyphens.java
Normale Datei
195
api/src/main/java/com/velocitypowered/api/util/FastUuidSansHyphens.java
Normale Datei
@ -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];
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package com.velocitypowered.api.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@ -23,11 +22,7 @@ public final class UuidUtils {
|
||||
*/
|
||||
public static UUID fromUndashed(final String string) {
|
||||
Objects.requireNonNull(string, "string");
|
||||
Preconditions.checkArgument(string.length() == 32, "Length is incorrect");
|
||||
return new UUID(
|
||||
Long.parseUnsignedLong(string.substring(0, 16), 16),
|
||||
Long.parseUnsignedLong(string.substring(16), 16)
|
||||
);
|
||||
return FastUuidSansHyphens.parseUuid(string);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,10 +33,7 @@ public final class UuidUtils {
|
||||
*/
|
||||
public static String toUndashed(final UUID uuid) {
|
||||
Preconditions.checkNotNull(uuid, "uuid");
|
||||
String msbStr = Long.toHexString(uuid.getMostSignificantBits());
|
||||
String lsbStr = Long.toHexString(uuid.getLeastSignificantBits());
|
||||
return Strings.padStart(msbStr, 16, '0') + Strings.padStart(lsbStr,
|
||||
16, '0');
|
||||
return FastUuidSansHyphens.toString(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ allprojects {
|
||||
junitVersion = '5.3.0-M1'
|
||||
slf4jVersion = '1.7.25'
|
||||
log4jVersion = '2.11.2'
|
||||
nettyVersion = '4.1.38.Final'
|
||||
nettyVersion = '4.1.43.Final'
|
||||
guavaVersion = '25.1-jre'
|
||||
checkerFrameworkVersion = '2.7.0'
|
||||
configurateVersion = '3.6'
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.velocitypowered.natives;
|
||||
|
||||
import com.velocitypowered.natives.util.BufferPreference;
|
||||
|
||||
public interface Native {
|
||||
boolean isNative();
|
||||
BufferPreference preferredBufferType();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFE
|
||||
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.util.BufferPreference;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
@ -15,19 +16,37 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
|
||||
private final Deflater deflater;
|
||||
private final Inflater inflater;
|
||||
private final byte[] buf;
|
||||
private byte[] buf = new byte[0];
|
||||
private boolean disposed = false;
|
||||
|
||||
private JavaVelocityCompressor(int level) {
|
||||
this.deflater = new Deflater(level);
|
||||
this.inflater = new Inflater();
|
||||
this.buf = new byte[ZLIB_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
||||
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();
|
||||
if (source.hasArray()) {
|
||||
inflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(), available);
|
||||
@ -36,19 +55,45 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
source.readBytes(inData);
|
||||
inflater.setInput(inData);
|
||||
}
|
||||
}
|
||||
|
||||
private void inflateDestinationIsHeap(ByteBuf destination, int available, int max)
|
||||
throws DataFormatException {
|
||||
while (!inflater.finished() && inflater.getBytesRead() < available) {
|
||||
if (!destination.isWritable()) {
|
||||
ensureMaxSize(destination, max);
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
ensureMaxSize(destination, max);
|
||||
int read = inflater.inflate(buf);
|
||||
destination.writeBytes(buf, 0, read);
|
||||
int produced = inflater.inflate(destination.array(), destination.arrayOffset()
|
||||
+ destination.writerIndex(), destination.writableBytes());
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
inflater.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
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()) {
|
||||
deflater.setInput(source.array(), source.arrayOffset() + source.readerIndex(),
|
||||
source.readableBytes());
|
||||
@ -57,12 +102,18 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
source.readBytes(inData);
|
||||
deflater.setInput(inData);
|
||||
}
|
||||
deflater.finish();
|
||||
}
|
||||
|
||||
private void deflateDestinationIsHeap(ByteBuf destination) {
|
||||
while (!deflater.finished()) {
|
||||
int bytes = deflater.deflate(buf);
|
||||
destination.writeBytes(buf, 0, bytes);
|
||||
if (!destination.isWritable()) {
|
||||
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
|
||||
@ -77,7 +128,7 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNative() {
|
||||
return false;
|
||||
public BufferPreference preferredBufferType() {
|
||||
return BufferPreference.HEAP_PREFERRED;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFE
|
||||
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.util.BufferPreference;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
@ -82,7 +83,7 @@ public class NativeVelocityCompressor implements VelocityCompressor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNative() {
|
||||
return true;
|
||||
public BufferPreference preferredBufferType() {
|
||||
return BufferPreference.DIRECT_REQUIRED;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.velocitypowered.natives.encryption;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.util.BufferPreference;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@ -34,50 +35,25 @@ public class JavaVelocityCipher implements VelocityCipher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
||||
public void process(ByteBuf source) {
|
||||
ensureNotDisposed();
|
||||
Preconditions.checkArgument(source.hasArray(), "No source array");
|
||||
|
||||
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 {
|
||||
out.writerIndex(
|
||||
cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset() + asHeapBuf.readerIndex(),
|
||||
inBytes, out.array(), out.arrayOffset() + out.writerIndex()));
|
||||
return out;
|
||||
} catch (ShortBufferException e) {
|
||||
out.release();
|
||||
throw e;
|
||||
} finally {
|
||||
asHeapBuf.release();
|
||||
cipher.update(source.array(), baseOffset, inBytes, source.array(), baseOffset);
|
||||
} catch (ShortBufferException ex) {
|
||||
/* This _really_ shouldn't happen - AES CFB8 will work in place.
|
||||
If you run into this, that means that for whatever reason the Java Runtime has determined
|
||||
that the output buffer needs more bytes than the input buffer. When we are working with
|
||||
AES-CFB8, the output size is equal to the input size. See the problem? */
|
||||
throw new AssertionError("Cipher update did not operate in place and requested a larger "
|
||||
+ "buffer than the source buffer");
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
public void dispose() {
|
||||
disposed = true;
|
||||
@ -88,7 +64,7 @@ public class JavaVelocityCipher implements VelocityCipher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNative() {
|
||||
return false;
|
||||
public BufferPreference preferredBufferType() {
|
||||
return BufferPreference.HEAP_REQUIRED;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.velocitypowered.natives.encryption;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.util.BufferPreference;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import java.security.GeneralSecurityException;
|
||||
@ -32,40 +33,14 @@ public class NativeVelocityCipher implements VelocityCipher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
||||
public void process(ByteBuf source) {
|
||||
ensureNotDisposed();
|
||||
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();
|
||||
destination.ensureWritable(len);
|
||||
|
||||
impl.process(ctx, source.memoryAddress() + source.readerIndex(), len,
|
||||
destination.memoryAddress() + destination.writerIndex(), encrypt);
|
||||
|
||||
source.skipBytes(len);
|
||||
destination.writerIndex(destination.writerIndex() + len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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;
|
||||
}
|
||||
impl.process(ctx, base, len, base, encrypt);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -81,7 +56,7 @@ public class NativeVelocityCipher implements VelocityCipher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNative() {
|
||||
return true;
|
||||
public BufferPreference preferredBufferType() {
|
||||
return BufferPreference.DIRECT_REQUIRED;
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,5 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
public interface VelocityCipher extends Disposable, Native {
|
||||
|
||||
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
|
||||
|
||||
ByteBuf process(ChannelHandlerContext ctx, ByteBuf source) throws ShortBufferException;
|
||||
void process(ByteBuf source);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -20,28 +20,30 @@ public class MoreByteBufUtils {
|
||||
* @return a buffer compatible with the native
|
||||
*/
|
||||
public static ByteBuf ensureCompatible(ByteBufAllocator alloc, Native nativeStuff, ByteBuf buf) {
|
||||
if (!nativeStuff.isNative() || buf.hasMemoryAddress()) {
|
||||
// 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.
|
||||
if (isCompatible(nativeStuff, buf)) {
|
||||
return buf.retain();
|
||||
}
|
||||
|
||||
// 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);
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ByteBuf} that will have the best performance with the specified
|
||||
* {@code nativeStuff}.
|
||||
*
|
||||
* @param alloc the {@link ByteBufAllocator} to use
|
||||
* @param nativeStuff the native we are working with
|
||||
* @return a buffer compatible with the native
|
||||
*/
|
||||
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff) {
|
||||
return nativeStuff.isNative() ? alloc.directBuffer() : alloc.heapBuffer();
|
||||
private static boolean isCompatible(Native nativeStuff, ByteBuf buf) {
|
||||
BufferPreference preferred = nativeStuff.preferredBufferType();
|
||||
switch (preferred) {
|
||||
case DIRECT_PREFERRED:
|
||||
case HEAP_PREFERRED:
|
||||
// The native prefers this type, but doesn't strictly require we provide it.
|
||||
return true;
|
||||
case DIRECT_REQUIRED:
|
||||
return buf.hasMemoryAddress();
|
||||
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,
|
||||
int initialCapacity) {
|
||||
return nativeStuff.isNative() ? alloc.directBuffer(initialCapacity) : alloc
|
||||
.heapBuffer(initialCapacity);
|
||||
switch (nativeStuff.preferredBufferType()) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
return selected.object;
|
||||
return selected.constructed;
|
||||
}
|
||||
|
||||
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 final Runnable setup;
|
||||
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) {
|
||||
this(possiblyAvailable, setup, name, () -> object);
|
||||
}
|
||||
|
||||
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, Supplier<T> object) {
|
||||
this.status =
|
||||
possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE;
|
||||
this.setup = setup;
|
||||
@ -57,6 +62,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
|
||||
if (status == Status.POSSIBLY_AVAILABLE) {
|
||||
try {
|
||||
setup.run();
|
||||
constructed = object.get();
|
||||
status = Status.SETUP;
|
||||
} catch (Exception e) {
|
||||
status = Status.SETUP_FAILURE;
|
||||
@ -64,7 +70,7 @@ public final class NativeCodeLoader<T> implements Supplier<T> {
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
return constructed;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import java.util.function.BooleanSupplier;
|
||||
|
||||
public class NativeConstraints {
|
||||
private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled");
|
||||
private static final boolean IS_AMD64;
|
||||
private static final boolean CAN_GET_MEMORYADDRESS;
|
||||
|
||||
static {
|
||||
@ -15,19 +16,28 @@ public class NativeConstraints {
|
||||
} finally {
|
||||
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 = () -> {
|
||||
return NATIVES_ENABLED
|
||||
&& CAN_GET_MEMORYADDRESS
|
||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
||||
&& System.getProperty("os.arch", "").equals("x86_64");
|
||||
&& IS_AMD64;
|
||||
};
|
||||
|
||||
static final BooleanSupplier LINUX = () -> {
|
||||
return NATIVES_ENABLED
|
||||
&& CAN_GET_MEMORYADDRESS
|
||||
&& 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;
|
||||
};
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.velocitypowered.natives.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.natives.NativeSetupException;
|
||||
import com.velocitypowered.natives.compression.Java11VelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.NativeVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressorFactory;
|
||||
@ -52,6 +53,8 @@ public class Natives {
|
||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX,
|
||||
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
|
||||
NativeVelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> {
|
||||
}, "Java 11", () -> Java11VelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
||||
}, "Java", JavaVelocityCompressor.FACTORY)
|
||||
)
|
||||
|
@ -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.MAC;
|
||||
|
||||
import com.velocitypowered.natives.util.BufferPreference;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
@ -17,7 +18,9 @@ import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnJre;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
class VelocityCompressorTest {
|
||||
|
||||
@ -39,7 +42,7 @@ class VelocityCompressorTest {
|
||||
@EnabledOnOs({MAC, LINUX})
|
||||
void nativeIntegrityCheck() throws DataFormatException {
|
||||
VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION);
|
||||
if (compressor instanceof JavaVelocityCompressor) {
|
||||
if (compressor.preferredBufferType() != BufferPreference.DIRECT_REQUIRED) {
|
||||
compressor.dispose();
|
||||
fail("Loaded regular compressor");
|
||||
}
|
||||
@ -60,6 +63,22 @@ class VelocityCompressorTest {
|
||||
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)
|
||||
throws DataFormatException {
|
||||
ByteBuf source = bufSupplier.get();
|
||||
|
@ -56,20 +56,18 @@ class VelocityCipherTest {
|
||||
VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(AES_KEY, "AES"));
|
||||
|
||||
ByteBuf source = bufSupplier.get();
|
||||
ByteBuf dest = bufSupplier.get();
|
||||
ByteBuf decryptionBuf = bufSupplier.get();
|
||||
|
||||
source.writeBytes(TEST_DATA);
|
||||
|
||||
ByteBuf workingBuf = source.copy();
|
||||
|
||||
try {
|
||||
encrypt.process(source, dest);
|
||||
decrypt.process(dest, decryptionBuf);
|
||||
source.readerIndex(0);
|
||||
assertTrue(ByteBufUtil.equals(source, decryptionBuf));
|
||||
encrypt.process(workingBuf);
|
||||
decrypt.process(workingBuf);
|
||||
assertTrue(ByteBufUtil.equals(source, workingBuf));
|
||||
} finally {
|
||||
source.release();
|
||||
dest.release();
|
||||
decryptionBuf.release();
|
||||
workingBuf.release();
|
||||
decrypt.dispose();
|
||||
encrypt.dispose();
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ dependencies {
|
||||
compile "io.netty:netty-handler:${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-kqueue:${nettyVersion}"
|
||||
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64"
|
||||
compile "io.netty:netty-resolver-dns:${nettyVersion}"
|
||||
|
||||
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
|
||||
@ -65,7 +67,7 @@ dependencies {
|
||||
|
||||
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'
|
||||
|
||||
|
@ -72,7 +72,10 @@ public class VelocityCommand implements Command {
|
||||
@Override
|
||||
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||
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) {
|
||||
@ -81,7 +84,7 @@ public class VelocityCommand implements Command {
|
||||
currentArgs[0].length()))
|
||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));
|
||||
|
@ -100,7 +100,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
" configuration is used if no servers could be contacted."
|
||||
})
|
||||
@ConfigKey("ping-passthrough")
|
||||
private PingPassthroughMode pingPassthrough;
|
||||
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||
|
||||
@Table("[servers]")
|
||||
private final Servers servers;
|
||||
@ -192,44 +192,38 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
}
|
||||
|
||||
if (servers.getServers().isEmpty()) {
|
||||
logger.error("You have no servers configured. :(");
|
||||
valid = false;
|
||||
} else {
|
||||
if (servers.getAttemptConnectionOrder().isEmpty()) {
|
||||
logger.error("No fallback servers are configured!");
|
||||
logger.warn("You don't have any servers configured.");
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
|
||||
try {
|
||||
AddressUtil.parseAddress(entry.getValue());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
|
||||
try {
|
||||
AddressUtil.parseAddress(entry.getValue());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e);
|
||||
valid = false;
|
||||
}
|
||||
for (String s : servers.getAttemptConnectionOrder()) {
|
||||
if (!servers.getServers().containsKey(s)) {
|
||||
logger.error("Fallback server " + s + " is not registered in your configuration!");
|
||||
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()) {
|
||||
if (!servers.getServers().containsKey(s)) {
|
||||
logger.error("Fallback server " + s + " is not registered in your configuration!");
|
||||
for (String server : entry.getValue()) {
|
||||
if (!servers.getServers().containsKey(server)) {
|
||||
logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey());
|
||||
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 {
|
||||
|
@ -34,14 +34,4 @@ public interface ConnectionType {
|
||||
*/
|
||||
GameProfile addGameProfileTokensIfRequired(GameProfile original,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
smc.setAutoReading(false);
|
||||
server.getEventManager()
|
||||
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer()))
|
||||
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer(),
|
||||
existingConnection != null ? existingConnection.getServer() : null))
|
||||
.whenCompleteAsync((x, error) -> {
|
||||
// Strap on the ClientPlaySessionHandler if required.
|
||||
ClientPlaySessionHandler playHandler;
|
||||
|
@ -304,26 +304,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
// Clear tab list to avoid duplicate entries
|
||||
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
|
||||
// - A respawn packet with a different dimension
|
||||
// - Another respawn with the correct dimension
|
||||
//
|
||||
// The two respawns with different dimensions are required, otherwise the client gets
|
||||
// confused.
|
||||
// - The join game packet from the backend server, with a different dimension
|
||||
// - A respawn with the correct dimension
|
||||
//
|
||||
// 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
|
||||
// improving compatibility with mods.
|
||||
int realDim = joinGame.getDimension();
|
||||
joinGame.setDimension(getFakeTemporaryDimensionId(realDim));
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(),
|
||||
joinGame.getLevelType()));
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(),
|
||||
joinGame.getLevelType()));
|
||||
new Respawn(realDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
|
||||
joinGame.getGamemode(), joinGame.getLevelType()));
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
private static int getFakeTemporaryDimensionId(int dim) {
|
||||
return dim == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
public List<UUID> getServerBossBars() {
|
||||
return serverBossBars;
|
||||
}
|
||||
|
||||
|
||||
private boolean handleCommandTabComplete(TabCompleteRequest packet) {
|
||||
// In 1.13+, we need to do additional work for the richer suggestions available.
|
||||
String command = packet.getCommand().substring(1);
|
||||
@ -389,31 +386,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
List<Offer> offers = new ArrayList<>();
|
||||
int longestLength = 0;
|
||||
for (String suggestion : suggestions) {
|
||||
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 length;
|
||||
if (startPos == 0) {
|
||||
startPos = packet.getCommand().length() + 1;
|
||||
length = longestLength;
|
||||
} else {
|
||||
length = packet.getCommand().length() - startPos;
|
||||
if (startPos > 0) {
|
||||
TabCompleteResponse resp = new TabCompleteResponse();
|
||||
resp.setTransactionId(packet.getTransactionId());
|
||||
resp.setStart(startPos);
|
||||
resp.setLength(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;
|
||||
}
|
||||
|
||||
|
@ -425,33 +425,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectedServer == null) {
|
||||
Optional<RegisteredServer> nextServer = getNextServerToTry(rs);
|
||||
if (nextServer.isPresent()) {
|
||||
// There can't be any connection in flight now.
|
||||
resetInFlightConnection();
|
||||
createConnectionRequest(nextServer.get()).fireAndForget();
|
||||
} else {
|
||||
disconnect(friendlyReason);
|
||||
}
|
||||
boolean kickedFromCurrent = connectedServer == null || connectedServer.getServer().equals(rs);
|
||||
ServerKickResult result;
|
||||
if (kickedFromCurrent) {
|
||||
Optional<RegisteredServer> next = getNextServerToTry(rs);
|
||||
result = next.<ServerKickResult>map(RedirectPlayer::create)
|
||||
.orElseGet(() -> DisconnectPlayer.create(friendlyReason));
|
||||
} else {
|
||||
boolean kickedFromCurrent = connectedServer.getServer().equals(rs);
|
||||
ServerKickResult result;
|
||||
if (kickedFromCurrent) {
|
||||
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);
|
||||
// If we were kicked by going to another server, the connection should not be in flight
|
||||
if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
|
||||
resetInFlightConnection();
|
||||
}
|
||||
KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason,
|
||||
!kickedFromCurrent, result);
|
||||
handleKickEvent(originalEvent, friendlyReason);
|
||||
result = Notify.create(friendlyReason);
|
||||
}
|
||||
KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason,
|
||||
!kickedFromCurrent, result);
|
||||
handleKickEvent(originalEvent, friendlyReason);
|
||||
}
|
||||
|
||||
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) {
|
||||
@ -471,7 +460,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
if (newResult == null || !newResult) {
|
||||
disconnect(friendlyReason);
|
||||
} 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());
|
||||
} else if (event.getResult() instanceof Notify) {
|
||||
|
@ -26,9 +26,14 @@ import java.util.Optional;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
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 {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(HandshakeSessionHandler.class);
|
||||
|
||||
private final MinecraftConnection connection;
|
||||
private final VelocityServer server;
|
||||
|
||||
@ -58,58 +63,72 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
public boolean handle(Handshake handshake) {
|
||||
InitialInboundConnection ic = new InitialInboundConnection(connection,
|
||||
cleanVhost(handshake.getServerAddress()), handshake);
|
||||
connection.setAssociation(ic);
|
||||
switch (handshake.getNextStatus()) {
|
||||
StateRegistry nextState = getStateForProtocol(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:
|
||||
connection.setState(StateRegistry.STATUS);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
|
||||
return true;
|
||||
return StateRegistry.STATUS;
|
||||
case StateRegistry.LOGIN_ID:
|
||||
connection.setState(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;
|
||||
return StateRegistry.LOGIN;
|
||||
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).
|
||||
if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN)
|
||||
&& 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.
|
||||
return ConnectionTypes.UNDETERMINED_17;
|
||||
} else {
|
||||
// For later: See if we can determine Forge 1.13+ here, else this will need to be UNDETERMINED
|
||||
// until later in the cycle (most likely determinable during the LOGIN phase)
|
||||
// Note for future implementation: Forge 1.13+ identifies itself using a slightly different
|
||||
// hostname token.
|
||||
return ConnectionTypes.VANILLA;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,21 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
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.proxy.VelocityServer.GSON;
|
||||
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.generateServerId;
|
||||
import static org.asynchttpclient.Dsl.asyncHttpClient;
|
||||
import static org.asynchttpclient.Dsl.config;
|
||||
|
||||
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.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||
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.util.GameProfile;
|
||||
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.EncryptionRequest;
|
||||
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.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import com.velocitypowered.proxy.util.VelocityMessages;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
@ -50,7 +42,6 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
import net.kyori.text.Component;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.asynchttpclient.Dsl;
|
||||
import org.asynchttpclient.ListenableFuture;
|
||||
import org.asynchttpclient.Response;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@ -66,7 +57,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
private final InitialInboundConnection inbound;
|
||||
private @MonotonicNonNull ServerLogin login;
|
||||
private byte[] verify = EMPTY_BYTE_ARRAY;
|
||||
private int playerInfoId;
|
||||
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
|
||||
|
||||
LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
|
||||
@ -79,29 +69,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public boolean handle(ServerLogin packet) {
|
||||
this.login = packet;
|
||||
if (mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0) {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
beginPreLogin();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -254,12 +222,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
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();
|
||||
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
||||
mcConnection.write(new SetCompression(threshold));
|
||||
@ -292,11 +254,27 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
server.getEventManager().fire(new PostLoginEvent(player))
|
||||
.thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
|
||||
.thenRun(() -> connectToInitialServer(player));
|
||||
}
|
||||
}, 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
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
mcConnection.close();
|
||||
|
@ -6,7 +6,7 @@ package com.velocitypowered.proxy.connection.forge.legacy;
|
||||
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
|
||||
* packet.
|
||||
*/
|
||||
|
@ -7,6 +7,11 @@ import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
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.socket.DatagramChannel;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
@ -22,7 +27,11 @@ enum TransportType {
|
||||
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
|
||||
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.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 Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
||||
@ -62,6 +71,8 @@ enum TransportType {
|
||||
|
||||
if (Epoll.isAvailable()) {
|
||||
return EPOLL;
|
||||
} else if (KQueue.isAvailable()) {
|
||||
return KQUEUE;
|
||||
} else {
|
||||
return NIO;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package com.velocitypowered.proxy.plugin;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
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.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
@ -101,6 +102,8 @@ public class VelocityPluginManager implements PluginManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.info("Loaded plugin {} {} by {}", plugin.getId(), plugin.getVersion()
|
||||
.orElse("<UNKNOWN>"), Joiner.on(", ").join(plugin.getAuthors()));
|
||||
registerPlugin(pluginObject);
|
||||
}
|
||||
}
|
||||
|
@ -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_13;
|
||||
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_8;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
|
||||
@ -120,51 +121,61 @@ public enum StateRegistry {
|
||||
map(0x1F, MINECRAFT_1_14, false));
|
||||
|
||||
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,
|
||||
map(0x02, MINECRAFT_1_7_2, 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,
|
||||
map(0x3A, MINECRAFT_1_7_2, 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,
|
||||
map(0x11, MINECRAFT_1_13, false));
|
||||
map(0x11, MINECRAFT_1_13, false),
|
||||
map(0x12, MINECRAFT_1_15, false));
|
||||
clientbound.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x3F, MINECRAFT_1_7_2, false),
|
||||
map(0x18, MINECRAFT_1_9, 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,
|
||||
map(0x40, MINECRAFT_1_7_2, false),
|
||||
map(0x1A, MINECRAFT_1_9, 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,
|
||||
map(0x00, MINECRAFT_1_7_2, false),
|
||||
map(0x1F, MINECRAFT_1_9, 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,
|
||||
map(0x01, MINECRAFT_1_7_2, false),
|
||||
map(0x23, MINECRAFT_1_9, 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,
|
||||
map(0x07, MINECRAFT_1_7_2, true),
|
||||
map(0x33, MINECRAFT_1_9, true),
|
||||
map(0x34, MINECRAFT_1_12, true),
|
||||
map(0x35, MINECRAFT_1_12_1, 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,
|
||||
map(0x48, MINECRAFT_1_8, true),
|
||||
map(0x32, MINECRAFT_1_9, true),
|
||||
map(0x33, MINECRAFT_1_12, true),
|
||||
map(0x34, MINECRAFT_1_12_1, 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,
|
||||
map(0x47, MINECRAFT_1_8, true),
|
||||
map(0x48, MINECRAFT_1_9, true),
|
||||
@ -172,20 +183,23 @@ public enum StateRegistry {
|
||||
map(0x49, MINECRAFT_1_12, true),
|
||||
map(0x4A, MINECRAFT_1_12_1, 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,
|
||||
map(0x45, MINECRAFT_1_8, true),
|
||||
map(0x45, MINECRAFT_1_9, true),
|
||||
map(0x47, MINECRAFT_1_12, true),
|
||||
map(0x48, MINECRAFT_1_12_1, 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,
|
||||
map(0x38, MINECRAFT_1_7_2, false),
|
||||
map(0x2D, MINECRAFT_1_9, false),
|
||||
map(0x2E, MINECRAFT_1_12_1, 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 {
|
||||
|
@ -18,6 +18,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
@ -59,6 +60,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
private final SecureRandom random;
|
||||
|
||||
private volatile @MonotonicNonNull List<QueryResponse.PluginInformation> pluginInformationList
|
||||
= null;
|
||||
@ -67,6 +69,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
|
||||
public GS4QueryHandler(VelocityServer server) {
|
||||
this.server = server;
|
||||
this.random = new SecureRandom();
|
||||
}
|
||||
|
||||
private QueryResponse createInitialResponse() {
|
||||
@ -111,7 +114,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
switch (type) {
|
||||
case QUERY_TYPE_HANDSHAKE: {
|
||||
// Generate new challenge token and put it into the sessions cache
|
||||
int challengeToken = ThreadLocalRandom.current().nextInt();
|
||||
int challengeToken = random.nextInt();
|
||||
sessions.put(senderAddress, challengeToken);
|
||||
|
||||
// Respond with challenge token
|
||||
|
@ -5,10 +5,10 @@ import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
import com.velocitypowered.natives.util.MoreByteBufUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import java.util.List;
|
||||
|
||||
public class MinecraftCipherDecoder extends ByteToMessageDecoder {
|
||||
public class MinecraftCipherDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
|
||||
private final VelocityCipher cipher;
|
||||
|
||||
@ -18,16 +18,19 @@ public class MinecraftCipherDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
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 {
|
||||
out.add(cipher.process(ctx, compatible));
|
||||
} finally {
|
||||
compatible.release();
|
||||
cipher.process(compatible);
|
||||
out.add(compatible);
|
||||
in.skipBytes(in.readableBytes());
|
||||
} catch (Exception e) {
|
||||
compatible.release(); // compatible will never be used if we throw an exception
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
cipher.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,11 @@ public class MinecraftCipherEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
||||
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, msg);
|
||||
try {
|
||||
out.add(cipher.process(ctx, compatible));
|
||||
} finally {
|
||||
compatible.release();
|
||||
cipher.process(compatible);
|
||||
out.add(compatible);
|
||||
} catch (Exception e) {
|
||||
compatible.release(); // compatible will never be used if we throw an exception
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,8 @@ import java.util.List;
|
||||
|
||||
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 VelocityCompressor compressor;
|
||||
@ -25,21 +26,23 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
int expectedSize = ProtocolUtils.readVarInt(in);
|
||||
if (expectedSize == 0) {
|
||||
int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
|
||||
if (claimedUncompressedSize == 0) {
|
||||
// Strip the now-useless uncompressed size, this message is already uncompressed.
|
||||
out.add(in.retainedSlice());
|
||||
in.skipBytes(in.readableBytes());
|
||||
return;
|
||||
}
|
||||
|
||||
checkFrame(expectedSize >= threshold, "Uncompressed size %s is less than threshold %s",
|
||||
expectedSize, threshold);
|
||||
int initialCapacity = Math.min(expectedSize, MAXIMUM_UNCOMPRESSED_SIZE);
|
||||
checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
|
||||
+ " threshold %s", claimedUncompressedSize, threshold);
|
||||
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 uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
|
||||
try {
|
||||
compressor.inflate(compatibleIn, uncompressed, expectedSize);
|
||||
compressor.inflate(compatibleIn, uncompressed, allowedMax);
|
||||
out.add(uncompressed);
|
||||
} catch (Exception e) {
|
||||
uncompressed.release();
|
||||
|
@ -47,20 +47,21 @@ public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
packet.decode(msg, direction, registry.version);
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException(
|
||||
"Error decoding " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + registry.version + " State " + state + " ID " + Integer
|
||||
.toHexString(packetId), e);
|
||||
"Error decoding " + packet.getClass() + " " + getExtraConnectionDetail(packetId), e);
|
||||
}
|
||||
if (msg.isReadable()) {
|
||||
throw new CorruptedFrameException(
|
||||
"Did not read full packet for " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + registry.version + " State " + state + " ID " + Integer
|
||||
.toHexString(packetId));
|
||||
throw new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " "
|
||||
+ getExtraConnectionDetail(packetId));
|
||||
}
|
||||
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) {
|
||||
this.registry = direction.getProtocolRegistry(state, protocolVersion);
|
||||
}
|
||||
|
@ -27,16 +27,13 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
// Make sure reader index of length buffer is returned to the beginning
|
||||
in.readerIndex(origReaderIndex);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
out.add(in.readRetainedSlice(packetLength));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
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 io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
@ -11,6 +13,7 @@ import java.util.List;
|
||||
public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||
|
||||
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
||||
private static final boolean IS_JAVA_CIPHER = Natives.cipher.get() == JavaVelocityCipher.FACTORY;
|
||||
|
||||
private MinecraftVarintLengthEncoder() {
|
||||
}
|
||||
@ -18,7 +21,7 @@ public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBu
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list)
|
||||
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());
|
||||
list.add(lengthBuf);
|
||||
list.add(buf.retain());
|
||||
|
@ -12,11 +12,13 @@ public class JoinGame implements MinecraftPacket {
|
||||
private int entityId;
|
||||
private short gamemode;
|
||||
private int dimension;
|
||||
private long partialHashedSeed; // 1.15+
|
||||
private short difficulty;
|
||||
private short maxPlayers;
|
||||
private @Nullable String levelType;
|
||||
private int viewDistance; //1.14+
|
||||
private boolean reducedDebugInfo;
|
||||
private boolean mystery;
|
||||
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
@ -42,6 +44,10 @@ public class JoinGame implements MinecraftPacket {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public long getPartialHashedSeed() {
|
||||
return partialHashedSeed;
|
||||
}
|
||||
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
@ -91,6 +97,7 @@ public class JoinGame implements MinecraftPacket {
|
||||
+ "entityId=" + entityId
|
||||
+ ", gamemode=" + gamemode
|
||||
+ ", dimension=" + dimension
|
||||
+ ", partialHashedSeed=" + partialHashedSeed
|
||||
+ ", difficulty=" + difficulty
|
||||
+ ", maxPlayers=" + maxPlayers
|
||||
+ ", levelType='" + levelType + '\''
|
||||
@ -111,6 +118,9 @@ public class JoinGame implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
this.partialHashedSeed = buf.readLong();
|
||||
}
|
||||
this.maxPlayers = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
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) {
|
||||
this.reducedDebugInfo = buf.readBoolean();
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
this.mystery = buf.readBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -133,6 +146,9 @@ public class JoinGame implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||
buf.writeByte(difficulty);
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
buf.writeLong(partialHashedSeed);
|
||||
}
|
||||
buf.writeByte(maxPlayers);
|
||||
if (levelType == null) {
|
||||
throw new IllegalStateException("No level type specified.");
|
||||
@ -144,6 +160,9 @@ public class JoinGame implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
||||
buf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
buf.writeBoolean(mystery);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf;
|
||||
public class Respawn implements MinecraftPacket {
|
||||
|
||||
private int dimension;
|
||||
private long partialHashedSeed;
|
||||
private short difficulty;
|
||||
private short gamemode;
|
||||
private String levelType = "";
|
||||
@ -16,8 +17,10 @@ public class Respawn implements MinecraftPacket {
|
||||
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.partialHashedSeed = partialHashedSeed;
|
||||
this.difficulty = difficulty;
|
||||
this.gamemode = gamemode;
|
||||
this.levelType = levelType;
|
||||
@ -31,6 +34,14 @@ public class Respawn implements MinecraftPacket {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public long getPartialHashedSeed() {
|
||||
return partialHashedSeed;
|
||||
}
|
||||
|
||||
public void setPartialHashedSeed(long partialHashedSeed) {
|
||||
this.partialHashedSeed = partialHashedSeed;
|
||||
}
|
||||
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
@ -59,6 +70,7 @@ public class Respawn implements MinecraftPacket {
|
||||
public String toString() {
|
||||
return "Respawn{"
|
||||
+ "dimension=" + dimension
|
||||
+ ", partialHashedSeed=" + partialHashedSeed
|
||||
+ ", difficulty=" + difficulty
|
||||
+ ", gamemode=" + gamemode
|
||||
+ ", levelType='" + levelType + '\''
|
||||
@ -71,6 +83,9 @@ public class Respawn implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
this.partialHashedSeed = buf.readLong();
|
||||
}
|
||||
this.gamemode = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
@ -81,6 +96,9 @@ public class Respawn implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||
buf.writeByte(difficulty);
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
buf.writeLong(partialHashedSeed);
|
||||
}
|
||||
buf.writeByte(gamemode);
|
||||
ProtocolUtils.writeString(buf, levelType);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ public class VelocityNettyThreadFactory implements ThreadFactory {
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
String name = String.format(nameFormat, threadNumber.incrementAndGet());
|
||||
String name = String.format(nameFormat, threadNumber.getAndIncrement());
|
||||
return new FastThreadLocalThread(r, name);
|
||||
}
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren