Checker Framework integration (#126)

This commit is contained in:
Andrew Steinborn
2018-10-27 21:45:42 -04:00
committed by GitHub
parent dccf688da8
commit 32829c5637
106 changed files with 1170 additions and 708 deletions

View File

@@ -4,6 +4,8 @@ plugins {
id 'de.sebastianboegl.shadow.transformer.log4j' version '2.1.1'
}
apply from: '../gradle/checkerframework.gradle'
compileJava {
options.compilerArgs += ['-proc:none']
}

View File

@@ -8,8 +8,6 @@ import java.text.DecimalFormat;
public class Velocity {
private static final Logger logger = LogManager.getLogger(Velocity.class);
private static long startTime;
static {
// We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient.
// Force AWT to work with its head chopped off.
@@ -17,8 +15,7 @@ public class Velocity {
}
public static void main(String... args) {
startTime = System.currentTimeMillis();
logger.info("Booting up Velocity {}...", Velocity.class.getPackage().getImplementationVersion());
long startTime = System.currentTimeMillis();
VelocityServer server = new VelocityServer();
server.start();

View File

@@ -1,5 +1,6 @@
package com.velocitypowered.proxy;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
@@ -7,12 +8,14 @@ import com.google.gson.GsonBuilder;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.api.util.ProxyVersion;
import com.velocitypowered.proxy.command.ServerCommand;
import com.velocitypowered.proxy.command.ShutdownCommand;
import com.velocitypowered.proxy.command.VelocityCommand;
@@ -39,6 +42,7 @@ import net.kyori.text.TextComponent;
import net.kyori.text.serializer.GsonComponentSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.*;
import java.net.InetSocketAddress;
import java.nio.file.Files;
@@ -58,36 +62,54 @@ public class VelocityServer implements ProxyServer {
.registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer())
.create();
private final ConnectionManager cm = new ConnectionManager(this);
private VelocityConfiguration configuration;
private NettyHttpClient httpClient;
private KeyPair serverKeyPair;
private final ServerMap servers = new ServerMap(this);
private @MonotonicNonNull ConnectionManager cm;
private @MonotonicNonNull VelocityConfiguration configuration;
private @MonotonicNonNull NettyHttpClient httpClient;
private @MonotonicNonNull KeyPair serverKeyPair;
private @MonotonicNonNull ServerMap servers;
private final VelocityCommandManager commandManager = new VelocityCommandManager();
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
private boolean shutdown = false;
private final VelocityPluginManager pluginManager = new VelocityPluginManager(this);
private @MonotonicNonNull VelocityPluginManager pluginManager;
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
private final VelocityConsole console = new VelocityConsole(this);
private Ratelimiter ipAttemptLimiter;
private VelocityEventManager eventManager;
private VelocityScheduler scheduler;
private VelocityChannelRegistrar channelRegistrar;
VelocityServer() {
commandManager.register(new VelocityCommand(), "velocity");
commandManager.register(new ServerCommand(this), "server");
commandManager.register(new ShutdownCommand(this), "shutdown", "end");
}
private @MonotonicNonNull VelocityConsole console;
private @MonotonicNonNull Ratelimiter ipAttemptLimiter;
private @MonotonicNonNull VelocityEventManager eventManager;
private @MonotonicNonNull VelocityScheduler scheduler;
private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar();
public KeyPair getServerKeyPair() {
if (serverKeyPair == null) {
throw new AssertionError();
}
return serverKeyPair;
}
public VelocityConfiguration getConfiguration() {
return configuration;
VelocityConfiguration cfg = this.configuration;
if (cfg == null) {
throw new IllegalStateException("Configuration not initialized!");
}
return cfg;
}
@Override
public ProxyVersion getVersion() {
Package pkg = VelocityServer.class.getPackage();
String implName, implVersion, implVendor;
if (pkg != null) {
implName = MoreObjects.firstNonNull(pkg.getImplementationTitle(), "Velocity");
implVersion = MoreObjects.firstNonNull(pkg.getImplementationVersion(), "<unknown>");
implVendor = MoreObjects.firstNonNull(pkg.getImplementationVendor(), "Velocity Contributors");
} else {
implName = "Velocity";
implVersion = "<unknown>";
implVendor = "Velocity Contributors";
}
return new ProxyVersion(implName, implVendor, implVersion);
}
@Override
@@ -95,7 +117,25 @@ public class VelocityServer implements ProxyServer {
return commandManager;
}
@EnsuresNonNull({"serverKeyPair", "servers", "pluginManager", "eventManager", "scheduler", "console", "cm", "configuration"})
public void start() {
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
pluginManager = new VelocityPluginManager(this);
eventManager = new VelocityEventManager(pluginManager);
scheduler = new VelocityScheduler(pluginManager);
console = new VelocityConsole(this);
cm = new ConnectionManager(this);
servers = new ServerMap(this);
cm.logChannelInformation();
// Initialize commands first
commandManager.register(new VelocityCommand(this), "velocity");
commandManager.register(new ServerCommand(this), "server");
commandManager.register(new ShutdownCommand(this), "shutdown", "end");
try {
Path configPath = Paths.get("velocity.toml");
configuration = VelocityConfiguration.read(configPath);
@@ -118,22 +158,13 @@ public class VelocityServer implements ProxyServer {
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
}
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit());
httpClient = new NettyHttpClient(this);
eventManager = new VelocityEventManager(pluginManager);
scheduler = new VelocityScheduler(pluginManager);
channelRegistrar = new VelocityChannelRegistrar();
loadPlugins();
try {
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance
// to fully initialize before we accept any connections to the server.
eventManager.fire(new ProxyInitializeEvent()).get();
} catch (InterruptedException | ExecutionException e) {
// Ignore, we don't care. InterruptedException is unlikely to happen (and if it does, you've got bigger
// issues) and there is almost no chance ExecutionException will be thrown.
}
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance
// to fully initialize before we accept any connections to the server.
eventManager.fire(new ProxyInitializeEvent()).join();
// init console permissions after plugins are loaded
console.setupPermissions();
@@ -145,6 +176,7 @@ public class VelocityServer implements ProxyServer {
}
}
@RequiresNonNull({"pluginManager", "eventManager"})
private void loadPlugins() {
logger.info("Loading plugins...");
@@ -166,18 +198,20 @@ public class VelocityServer implements ProxyServer {
}
// Register the plugin main classes so that we may proceed with firing the proxy initialize event
pluginManager.getPlugins().forEach(container -> {
container.getInstance().ifPresent(plugin -> eventManager.register(plugin, plugin));
});
for (PluginContainer plugin : pluginManager.getPlugins()) {
Optional<?> instance = plugin.getInstance();
if (instance.isPresent()) {
eventManager.register(instance.get(), instance.get());
}
}
logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
}
public ServerMap getServers() {
return servers;
}
public Bootstrap initializeGenericBootstrap() {
if (cm == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return this.cm.createWorker();
}
@@ -186,6 +220,10 @@ public class VelocityServer implements ProxyServer {
}
public void shutdown() {
if (eventManager == null || pluginManager == null || cm == null || scheduler == null) {
throw new AssertionError();
}
if (!shutdownInProgress.compareAndSet(false, true)) {
return;
}
@@ -210,10 +248,16 @@ public class VelocityServer implements ProxyServer {
}
public NettyHttpClient getHttpClient() {
if (httpClient == null) {
throw new IllegalStateException("HTTP client not initialized");
}
return httpClient;
}
public Ratelimiter getIpAttemptLimiter() {
if (ipAttemptLimiter == null) {
throw new IllegalStateException("Ratelimiter not initialized");
}
return ipAttemptLimiter;
}
@@ -268,41 +312,65 @@ public class VelocityServer implements ProxyServer {
@Override
public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "name");
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return servers.getServer(name);
}
@Override
public Collection<RegisteredServer> getAllServers() {
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return servers.getAllServers();
}
@Override
public RegisteredServer registerServer(ServerInfo server) {
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return servers.register(server);
}
@Override
public void unregisterServer(ServerInfo server) {
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
servers.unregister(server);
}
@Override
public VelocityConsole getConsoleCommandSource() {
if (console == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return console;
}
@Override
public PluginManager getPluginManager() {
if (pluginManager == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return pluginManager;
}
@Override
public EventManager getEventManager() {
if (eventManager == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return eventManager;
}
@Override
public VelocityScheduler getScheduler() {
if (scheduler == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return scheduler;
}
@@ -313,6 +381,9 @@ public class VelocityServer implements ProxyServer {
@Override
public InetSocketAddress getBoundAddress() {
if (configuration == null) {
throw new IllegalStateException("No configuration"); // even though you'll never get the chance... heh, heh
}
return configuration.getBind();
}
}

View File

@@ -27,7 +27,7 @@ public class ServerCommand implements Command {
}
@Override
public void execute(CommandSource source, String[] args) {
public void execute(CommandSource source, String @NonNull [] args) {
if (!(source instanceof Player)) {
source.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED));
return;
@@ -76,7 +76,7 @@ public class ServerCommand implements Command {
}
@Override
public List<String> suggest(CommandSource source, String[] currentArgs) {
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
if (currentArgs.length == 0) {
return server.getAllServers().stream()
.map(rs -> rs.getServerInfo().getName())
@@ -92,7 +92,7 @@ public class ServerCommand implements Command {
}
@Override
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE;
}
}

View File

@@ -15,7 +15,7 @@ public class ShutdownCommand implements Command {
}
@Override
public void execute(CommandSource source, String[] args) {
public void execute(CommandSource source, String @NonNull [] args) {
if (source != server.getConsoleCommandSource()) {
source.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED));
return;
@@ -24,7 +24,7 @@ public class ShutdownCommand implements Command {
}
@Override
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source == server.getConsoleCommandSource();
}
}

View File

@@ -1,12 +1,12 @@
package com.velocitypowered.proxy.command;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.util.ProxyVersion;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.format.TextColor;
@@ -20,9 +20,13 @@ import java.util.Map;
import java.util.stream.Collectors;
public class VelocityCommand implements Command {
private final Map<String, Command> subcommands = ImmutableMap.<String, Command>builder()
.put("version", Info.INSTANCE)
.build();
private final Map<String, Command> subcommands;
public VelocityCommand(ProxyServer server) {
this.subcommands = ImmutableMap.<String, Command>builder()
.put("version", new Info(server))
.build();
}
private void usage(CommandSource source) {
String commandText = "/velocity <" + String.join("|", subcommands.keySet()) + ">";
@@ -30,7 +34,7 @@ public class VelocityCommand implements Command {
}
@Override
public void execute(CommandSource source, String[] args) {
public void execute(CommandSource source, String @NonNull [] args) {
if (args.length == 0) {
usage(source);
return;
@@ -41,11 +45,13 @@ public class VelocityCommand implements Command {
usage(source);
return;
}
command.execute(source, Arrays.copyOfRange(args, 1, args.length));
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(args, 1, args.length);
command.execute(source, actualArgs);
}
@Override
public List<String> suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) {
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
if (currentArgs.length == 0) {
return ImmutableList.copyOf(subcommands.keySet());
}
@@ -60,11 +66,13 @@ public class VelocityCommand implements Command {
if (command == null) {
return ImmutableList.of();
}
return command.suggest(source, Arrays.copyOfRange(currentArgs, 1, currentArgs.length));
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(currentArgs, 1, currentArgs.length);
return command.suggest(source, actualArgs);
}
@Override
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
if (args.length == 0) {
return true;
}
@@ -72,29 +80,33 @@ public class VelocityCommand implements Command {
if (command == null) {
return true;
}
return command.hasPermission(source, Arrays.copyOfRange(args, 1, args.length));
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(args, 1, args.length);
return command.hasPermission(source, actualArgs);
}
private static class Info implements Command {
static final Info INSTANCE = new Info();
private Info() {}
private final ProxyServer server;
private Info(ProxyServer server) {
this.server = server;
}
@Override
public void execute(@NonNull CommandSource source, @NonNull String[] args) {
String implName = MoreObjects.firstNonNull(VelocityServer.class.getPackage().getImplementationTitle(), "Velocity");
String implVersion = MoreObjects.firstNonNull(VelocityServer.class.getPackage().getImplementationVersion(), "<unknown>");
String implVendor = MoreObjects.firstNonNull(VelocityServer.class.getPackage().getImplementationVendor(), "Velocity Contributors");
TextComponent velocity = TextComponent.builder(implName + " ")
public void execute(CommandSource source, String @NonNull [] args) {
ProxyVersion version = server.getVersion();
TextComponent velocity = TextComponent.builder(version.getName() + " ")
.decoration(TextDecoration.BOLD, true)
.color(TextColor.DARK_AQUA)
.append(TextComponent.of(implVersion).decoration(TextDecoration.BOLD, false))
.append(TextComponent.of(version.getVersion()).decoration(TextDecoration.BOLD, false))
.build();
TextComponent copyright = TextComponent.of("Copyright 2018 " + implVendor + ". " + implName + " is freely licensed under the terms of the " +
TextComponent copyright = TextComponent.of("Copyright 2018 " + version.getVendor() + ". " + version.getName() + " is freely licensed under the terms of the " +
"MIT License.");
source.sendMessage(velocity);
source.sendMessage(copyright);
if (implName.equals("Velocity")) {
if (version.getName().equals("Velocity")) {
TextComponent velocityWebsite = TextComponent.builder()
.content("Visit the ")
.append(TextComponent.builder("Velocity website")
@@ -112,7 +124,7 @@ public class VelocityCommand implements Command {
}
@Override
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source.getPermissionValue("velocity.command.info") != Tristate.FALSE;
}
}

View File

@@ -1,9 +1,11 @@
package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.*;
@@ -11,7 +13,7 @@ public class VelocityCommandManager implements CommandManager {
private final Map<String, Command> commands = new HashMap<>();
@Override
public void register(final Command command, final String... aliases) {
public void register(@NonNull final Command command, final String... aliases) {
Preconditions.checkNotNull(aliases, "aliases");
Preconditions.checkNotNull(command, "executor");
for (int i = 0, length = aliases.length; i < length; i++) {
@@ -22,13 +24,13 @@ public class VelocityCommandManager implements CommandManager {
}
@Override
public void unregister(final String alias) {
public void unregister(@NonNull final String alias) {
Preconditions.checkNotNull(alias, "name");
this.commands.remove(alias.toLowerCase(Locale.ENGLISH));
}
@Override
public boolean execute(CommandSource source, String cmdLine) {
public boolean execute(@NonNull CommandSource source, @NonNull String cmdLine) {
Preconditions.checkNotNull(source, "invoker");
Preconditions.checkNotNull(cmdLine, "cmdLine");
@@ -38,6 +40,7 @@ public class VelocityCommandManager implements CommandManager {
}
String alias = split[0];
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
if (command == null) {
@@ -60,13 +63,13 @@ public class VelocityCommandManager implements CommandManager {
return commands.containsKey(command);
}
public Optional<List<String>> offerSuggestions(CommandSource source, String cmdLine) {
public List<String> offerSuggestions(CommandSource source, String cmdLine) {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
String[] split = cmdLine.split(" ", -1);
if (split.length == 0) {
return Optional.empty();
return ImmutableList.of();
}
String alias = split[0];
@@ -78,21 +81,22 @@ public class VelocityCommandManager implements CommandManager {
availableCommands.add("/" + entry.getKey());
}
}
return Optional.of(availableCommands);
return availableCommands;
}
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
if (command == null) {
return Optional.empty();
return ImmutableList.of();
}
try {
if (!command.hasPermission(source, actualArgs)) {
return Optional.empty();
return ImmutableList.of();
}
return Optional.of(command.suggest(source, actualArgs));
return command.suggest(source, actualArgs);
} catch (Exception e) {
throw new RuntimeException("Unable to invoke suggestions for command " + alias + " for " + source, e);
}

View File

@@ -116,7 +116,7 @@ public abstract class AnnotatedConfig {
// Get a key name for config
ConfigKey key = field.getAnnotation(ConfigKey.class);
String name = key == null ? field.getName() : key.value(); // Use field name if @ConfigKey annotation is not present
String name = safeKey(key == null ? field.getName() : key.value()); // Use field name if @ConfigKey annotation is not present
// Check if field is table.
Table table = field.getAnnotation(Table.class);
@@ -130,7 +130,7 @@ public abstract class AnnotatedConfig {
@SuppressWarnings("unchecked")
Map<String, ?> map = (Map<String, ?>) field.get(dumpable);
for (Entry<String, ?> entry : map.entrySet()) {
lines.add(entry.getKey() + " = " + serialize(entry.getValue())); // Save map data
lines.add(safeKey(entry.getKey()) + " = " + serialize(entry.getValue())); // Save map data
}
lines.add(""); // Add empty line
continue;
@@ -193,7 +193,7 @@ public abstract class AnnotatedConfig {
return value != null ? value.toString() : "null";
}
private String safeKey(String key) {
private static String safeKey(String key) {
if(key.contains(".") && !(key.indexOf('"') == 0 && key.lastIndexOf('"') == (key.length() - 1))) {
return '"' + key + '"';
}

View File

@@ -10,6 +10,8 @@ import com.velocitypowered.proxy.util.AddressUtil;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException;
import java.io.Reader;
@@ -78,10 +80,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
private final Query query;
@Ignore
private Component motdAsComponent;
private @MonotonicNonNull Component motdAsComponent;
@Ignore
private Favicon favicon;
private @Nullable Favicon favicon;
public VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query) {
this.servers = servers;

View File

@@ -16,6 +16,7 @@ import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.util.ReferenceCountUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
@@ -35,10 +36,10 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
private final Channel channel;
private SocketAddress remoteAddress;
private StateRegistry state;
private MinecraftSessionHandler sessionHandler;
private @Nullable MinecraftSessionHandler sessionHandler;
private int protocolVersion;
private int nextProtocolVersion;
private MinecraftConnectionAssociation association;
private @Nullable MinecraftConnectionAssociation association;
private boolean isLegacyForge;
private final VelocityServer server;
private boolean canSendLegacyFMLResetPacket = false;
@@ -74,6 +75,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (sessionHandler == null) {
// No session handler available, do nothing
ReferenceCountUtil.release(msg);
return;
}
if (msg instanceof MinecraftPacket) {
if (sessionHandler.beforeHandle()) {
return;
@@ -197,6 +204,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
}
}
@Nullable
public MinecraftSessionHandler getSessionHandler() {
return sessionHandler;
}
@@ -243,6 +251,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
channel.pipeline().addBefore(FRAME_ENCODER, CIPHER_ENCODER, new MinecraftCipherEncoder(encryptionCipher));
}
@Nullable
public MinecraftConnectionAssociation getAssociation() {
return association;
}

View File

@@ -7,4 +7,6 @@ public class VelocityConstants {
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
public static final int FORWARDING_VERSION = 1;
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
}

View File

@@ -4,6 +4,7 @@ import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.forge.ForgeConstants;
@@ -22,7 +23,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) {
this.server = server;
this.serverConn = serverConn;
this.playerSessionHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getConnection().getSessionHandler();
MinecraftSessionHandler psh = serverConn.getPlayer().getConnection().getSessionHandler();
if (!(psh instanceof ClientPlaySessionHandler)) {
throw new IllegalStateException("Initializing BackendPlaySessionHandler with no backing client play session handler!");
}
this.playerSessionHandler = (ClientPlaySessionHandler) psh;
}
@Override
@@ -74,6 +80,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(PluginMessage packet) {
MinecraftConnection smc = serverConn.getConnection();
if (smc == null) {
return true;
}
if (!canForwardPluginMessage(packet)) {
return true;
}
@@ -105,9 +116,9 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
serverConn.getPlayer().getConnection().write(packet);
smc.write(packet);
}
}, serverConn.getConnection().eventLoop());
}, smc.eventLoop());
return true;
}
@@ -152,16 +163,18 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
}
private boolean canForwardPluginMessage(PluginMessage message) {
ClientPlaySessionHandler playerHandler =
(ClientPlaySessionHandler) serverConn.getPlayer().getConnection().getSessionHandler();
MinecraftConnection mc = serverConn.getConnection();
if (mc == null) {
return false;
}
boolean isMCOrFMLMessage;
if (serverConn.getConnection().getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) {
if (mc.getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) {
String channel = message.getChannel();
isMCOrFMLMessage = channel.startsWith("MC|") || channel.startsWith(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL);
} else {
isMCOrFMLMessage = message.getChannel().startsWith("minecraft:");
}
return isMCOrFMLMessage || playerHandler.getClientPluginMsgChannels().contains(message.getChannel()) ||
return isMCOrFMLMessage || playerSessionHandler.getClientPluginMsgChannels().contains(message.getChannel()) ||
server.getChannelRegistrar().registered(message.getChannel());
}
}

View File

@@ -6,6 +6,7 @@ import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
@@ -38,6 +39,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
this.resultFuture = resultFuture;
}
private MinecraftConnection ensureMinecraftConnection() {
MinecraftConnection mc = serverConn.getConnection();
if (mc == null) {
throw new IllegalStateException("Not connected to backend server!");
}
return mc;
}
@Override
public boolean handle(EncryptionRequest packet) {
throw new IllegalStateException("Backend server is online-mode!");
@@ -45,6 +54,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(LoginPluginMessage packet) {
MinecraftConnection mc = ensureMinecraftConnection();
VelocityConfiguration configuration = server.getConfiguration();
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && packet.getChannel()
.equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
@@ -54,7 +64,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
response.setData(createForwardingData(configuration.getForwardingSecret(),
serverConn.getPlayer().getRemoteAddress().getHostString(),
serverConn.getPlayer().getProfile()));
serverConn.getConnection().write(response);
mc.write(response);
informationForwarded = true;
} else {
// Don't understand
@@ -62,7 +72,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
response.setSuccess(false);
response.setId(packet.getId());
response.setData(Unpooled.EMPTY_BUFFER);
serverConn.getConnection().write(response);
mc.write(response);
}
return true;
}
@@ -76,7 +86,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(SetCompression packet) {
serverConn.getConnection().setCompressionThreshold(packet.getThreshold());
ensureMinecraftConnection().setCompressionThreshold(packet.getThreshold());
return true;
}
@@ -90,7 +100,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
}
// The player has been logged on to the backend server.
serverConn.getConnection().setState(StateRegistry.PLAY);
MinecraftConnection smc = ensureMinecraftConnection();
smc.setState(StateRegistry.PLAY);
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
if (existingConnection == null) {
// Strap on the play session handler
@@ -104,14 +115,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
existingConnection.disconnect();
}
serverConn.getConnection().getChannel().config().setAutoRead(false);
smc.getChannel().config().setAutoRead(false);
server.getEventManager().fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer()))
.whenCompleteAsync((x, error) -> {
resultFuture.complete(ConnectionRequestResults.SUCCESSFUL);
serverConn.getConnection().setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
serverConn.getPlayer().setConnectedServer(serverConn);
serverConn.getConnection().getChannel().config().setAutoRead(true);
}, serverConn.getConnection().eventLoop());
smc.getChannel().config().setAutoRead(true);
}, smc.eventLoop());
return true;
}

View File

@@ -1,6 +1,8 @@
package com.velocitypowered.proxy.connection.backend;
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import com.google.common.base.VerifyException;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
@@ -24,10 +26,12 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Verify.verify;
import static com.velocitypowered.proxy.VelocityServer.GSON;
import static com.velocitypowered.proxy.network.Connections.*;
@@ -35,7 +39,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
private final VelocityRegisteredServer registeredServer;
private final ConnectedPlayer proxyPlayer;
private final VelocityServer server;
private MinecraftConnection connection;
private @Nullable MinecraftConnection connection;
private boolean legacyForge = false;
private boolean hasCompletedJoin = false;
private boolean gracefulDisconnect = false;
@@ -72,6 +76,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
if (future.isSuccess()) {
connection = future.channel().pipeline().get(MinecraftConnection.class);
// This is guaranteed not to be null, but Checker Framework is whining about it anyway
if (connection == null) {
throw new VerifyException("MinecraftConnection not injected into pipeline");
}
// Kick off the connection process
connection.setSessionHandler(new LoginSessionHandler(server, VelocityServerConnection.this, result));
startHandshake();
@@ -93,6 +102,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
}
private void startHandshake() {
MinecraftConnection mc = connection;
if (mc == null) {
throw new IllegalStateException("No connection established!");
}
PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode();
// Initiate a handshake.
@@ -107,17 +121,15 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
}
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
connection.write(handshake);
mc.write(handshake);
int protocolVersion = proxyPlayer.getConnection().getNextProtocolVersion();
connection.setProtocolVersion(protocolVersion);
connection.setState(StateRegistry.LOGIN);
ServerLogin login = new ServerLogin();
login.setUsername(proxyPlayer.getUsername());
connection.write(login);
mc.setProtocolVersion(protocolVersion);
mc.setState(StateRegistry.LOGIN);
mc.write(new ServerLogin(proxyPlayer.getUsername()));
}
@Nullable
public MinecraftConnection getConnection() {
return connection;
}
@@ -154,10 +166,16 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data");
MinecraftConnection mc = connection;
if (mc == null) {
throw new IllegalStateException("Not connected to a server!");
}
PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId());
message.setData(data);
connection.write(message);
mc.write(message);
return true;
}

View File

@@ -1,10 +1,12 @@
package com.velocitypowered.proxy.connection.client;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.util.ModInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.forge.ForgeConstants;
@@ -19,6 +21,7 @@ import net.kyori.text.TextComponent;
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;
import java.util.*;
@@ -36,7 +39,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private final Set<String> clientPluginMsgChannels = new HashSet<>();
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
private final VelocityServer server;
private TabCompleteRequest outstandingTabComplete;
private @Nullable TabCompleteRequest outstandingTabComplete;
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
this.player = player;
@@ -53,8 +56,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public boolean handle(KeepAlive packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null && packet.getRandomId() == serverConnection.getLastPingId()) {
MinecraftConnection smc = serverConnection.getConnection();
if (smc == null) {
// eat the packet
return true;
}
player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent());
serverConnection.getConnection().write(packet);
smc.write(packet);
serverConnection.resetLastPingId();
}
return true;
@@ -84,15 +92,23 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
if (serverConnection == null) {
return true;
}
MinecraftConnection smc = serverConnection.getConnection();
if (smc == null) {
return true;
}
PlayerChatEvent event = new PlayerChatEvent(player, msg);
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().equals(PlayerChatEvent.ChatResult.allowed())){
serverConnection.getConnection().write(packet);
} else if (pme.getResult().isAllowed() && pme.getResult().getMessage().isPresent()){
serverConnection.getConnection().write(Chat.createServerbound(pme.getResult().getMessage().get()));
PlayerChatEvent.ChatResult chatResult = pme.getResult();
if (chatResult.isAllowed()) {
Optional<String> eventMsg = pme.getResult().getMessage();
if (eventMsg.isPresent()) {
smc.write(Chat.createServerbound(eventMsg.get()));
} else {
smc.write(packet);
}
}
}, serverConnection.getConnection().eventLoop());
}, smc.eventLoop());
}
return true;
}
@@ -105,10 +121,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
if (spacePos > 0) {
String cmd = packet.getCommand().substring(1, spacePos);
if (server.getCommandManager().hasCommand(cmd)) {
Optional<List<String>> suggestions = server.getCommandManager().offerSuggestions(player, packet.getCommand().substring(1));
if (suggestions.isPresent()) {
List<String> suggestions = server.getCommandManager().offerSuggestions(player, packet.getCommand().substring(1));
if (suggestions.size() > 0) {
TabCompleteResponse resp = new TabCompleteResponse();
resp.getOffers().addAll(suggestions.get());
resp.getOffers().addAll(suggestions);
player.getConnection().write(resp);
return true;
}
@@ -121,58 +137,68 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(PluginMessage packet) {
if (PluginMessageUtil.isMCRegister(packet)) {
List<String> actuallyRegistered = new ArrayList<>();
List<String> channels = PluginMessageUtil.getChannels(packet);
for (String channel : channels) {
if (clientPluginMsgChannels.size() >= MAX_PLUGIN_CHANNELS &&
!clientPluginMsgChannels.contains(channel)) {
throw new IllegalStateException("Too many plugin message channels registered");
VelocityServerConnection serverConn = player.getConnectedServer();
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
if (serverConn != null && backendConn != null) {
if (PluginMessageUtil.isMCRegister(packet)) {
List<String> actuallyRegistered = new ArrayList<>();
List<String> channels = PluginMessageUtil.getChannels(packet);
for (String channel : channels) {
if (clientPluginMsgChannels.size() >= MAX_PLUGIN_CHANNELS &&
!clientPluginMsgChannels.contains(channel)) {
throw new IllegalStateException("Too many plugin message channels registered");
}
if (clientPluginMsgChannels.add(channel)) {
actuallyRegistered.add(channel);
}
}
if (clientPluginMsgChannels.add(channel)) {
actuallyRegistered.add(channel);
}
}
if (actuallyRegistered.size() > 0) {
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(player.getConnectedServer()
.getConnection().getProtocolVersion(), actuallyRegistered);
player.getConnectedServer().getConnection().write(newRegisterPacket);
}
} else if (PluginMessageUtil.isMCUnregister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet);
clientPluginMsgChannels.removeAll(channels);
player.getConnectedServer().getConnection().write(packet);
} else if (PluginMessageUtil.isMCBrand(packet)) {
player.getConnectedServer().getConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
} else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
if (packet.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
if (!player.getModInfo().isPresent()) {
ForgeUtil.readModList(packet).ifPresent(mods -> player.setModInfo(new ModInfo("FML", mods)));
if (actuallyRegistered.size() > 0) {
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(backendConn
.getProtocolVersion(), actuallyRegistered);
backendConn.write(newRegisterPacket);
}
// Always forward the FML handshake to the remote server.
player.getConnectedServer().getConnection().write(packet);
return true;
} else if (PluginMessageUtil.isMCUnregister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet);
clientPluginMsgChannels.removeAll(channels);
backendConn.write(packet);
return true;
} else if (PluginMessageUtil.isMCBrand(packet)) {
backendConn.write(PluginMessageUtil.rewriteMCBrand(packet));
return true;
} else if (backendConn.isLegacyForge() && !serverConn.hasCompletedJoin()) {
if (packet.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
if (!player.getModInfo().isPresent()) {
List<ModInfo.Mod> mods = ForgeUtil.readModList(packet);
if (!mods.isEmpty()) {
player.setModInfo(new ModInfo("FML", mods));
}
}
// Always forward the FML handshake to the remote server.
backendConn.write(packet);
} else {
// The client is trying to send messages too early. This is primarily caused by mods, but it's further
// aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to
// be sent once the JoinGame packet has been received by the proxy.
loginPluginMessages.add(packet);
}
return true;
} else {
// The client is trying to send messages too early. This is primarily caused by mods, but it's further
// aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to
// be sent once the JoinGame packet has been received by the proxy.
loginPluginMessages.add(packet);
}
} else {
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
player.getConnectedServer().getConnection().write(packet);
} else {
PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(), id, packet.getData());
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
player.getConnectedServer().getConnection().write(packet);
}
}, player.getConnectedServer().getConnection().eventLoop());
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
backendConn.write(packet);
} else {
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, packet.getData());
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
backendConn.write(packet);
}, backendConn.eventLoop());
}
}
}
return true;
}
@@ -184,9 +210,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return;
}
// If we don't want to handle this packet, just forward it on.
if (serverConnection.hasCompletedJoin()) {
serverConnection.getConnection().write(packet);
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null && serverConnection.hasCompletedJoin()) {
smc.write(packet);
}
}
@@ -198,8 +224,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return;
}
if (serverConnection.hasCompletedJoin()) {
serverConnection.getConnection().write(buf.retain());
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null && serverConnection.hasCompletedJoin()) {
smc.write(buf.retain());
}
}
@@ -222,11 +249,23 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
VelocityServerConnection server = player.getConnectedServer();
if (server != null) {
boolean writable = player.getConnection().getChannel().isWritable();
server.getConnection().getChannel().config().setAutoRead(writable);
MinecraftConnection smc = server.getConnection();
if (smc != null) {
smc.getChannel().config().setAutoRead(writable);
}
}
}
public void handleBackendJoinGame(JoinGame joinGame) {
VelocityServerConnection serverConn = player.getConnectedServer();
if (serverConn == null) {
throw new IllegalStateException("No server connection for " + player + ", but JoinGame packet received");
}
MinecraftConnection serverMc = serverConn.getConnection();
if (serverMc == null) {
throw new IllegalStateException("Server connection for " + player + " is disconnected, but JoinGame packet received");
}
if (!spawned) {
// Nothing special to do with regards to spawning the player
spawned = true;
@@ -272,7 +311,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
serverBossBars.clear();
// Tell the server about this client's plugin message channels.
int serverVersion = player.getConnectedServer().getConnection().getProtocolVersion();
int serverVersion = serverMc.getProtocolVersion();
Collection<String> toRegister = new HashSet<>(clientPluginMsgChannels);
if (serverVersion >= ProtocolConstants.MINECRAFT_1_13) {
toRegister.addAll(server.getChannelRegistrar().getModernChannelIds());
@@ -280,14 +319,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
toRegister.addAll(server.getChannelRegistrar().getIdsForLegacyConnections());
}
if (!toRegister.isEmpty()) {
player.getConnectedServer().getConnection().delayedWrite(PluginMessageUtil.constructChannelsPacket(
serverVersion, toRegister));
serverMc.delayedWrite(PluginMessageUtil.constructChannelsPacket(serverVersion, toRegister));
}
// If we had plugin messages queued during login/FML handshake, send them now.
PluginMessage pm;
while ((pm = loginPluginMessages.poll()) != null) {
player.getConnectedServer().getConnection().delayedWrite(pm);
serverMc.delayedWrite(pm);
}
// Clear any title from the previous server.
@@ -295,9 +333,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Flush everything
player.getConnection().flush();
player.getConnectedServer().getConnection().flush();
player.getConnectedServer().setHasCompletedJoin(true);
if (player.getConnectedServer().isLegacyForge()) {
serverMc.flush();
serverConn.setHasCompletedJoin(true);
if (serverConn.isLegacyForge()) {
// We only need to indicate we can send a reset packet if we complete a handshake, that is,
// logged onto a Forge server.
//
@@ -326,8 +364,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
if (!outstandingTabComplete.isAssumeCommand()) {
String command = outstandingTabComplete.getCommand().substring(1);
try {
Optional<List<String>> offers = server.getCommandManager().offerSuggestions(player, command);
offers.ifPresent(strings -> response.getOffers().addAll(strings));
response.getOffers().addAll(server.getCommandManager().offerSuggestions(player, command));
} catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(),
command, e);

View File

@@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.player.SkinParts;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Locale;
@@ -11,7 +12,7 @@ public class ClientSettingsWrapper implements PlayerSettings {
private final ClientSettings settings;
private final SkinParts parts;
private Locale locale = null;
private @Nullable Locale locale;
ClientSettingsWrapper(ClientSettings settings) {
this.settings = settings;

View File

@@ -63,27 +63,28 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class);
private final MinecraftConnection connection;
private final InetSocketAddress virtualHost;
private @Nullable final InetSocketAddress virtualHost;
private GameProfile profile;
private PermissionFunction permissionFunction = null;
private PermissionFunction permissionFunction;
private int tryIndex = 0;
private long ping = -1;
private VelocityServerConnection connectedServer;
private VelocityServerConnection connectionInFlight;
private PlayerSettings settings;
private ModInfo modInfo;
private @Nullable VelocityServerConnection connectedServer;
private @Nullable VelocityServerConnection connectionInFlight;
private @Nullable PlayerSettings settings;
private @Nullable ModInfo modInfo;
private final VelocityTabList tabList;
private final VelocityServer server;
@MonotonicNonNull
private List<String> serversToTry = null;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, InetSocketAddress virtualHost) {
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, @Nullable InetSocketAddress virtualHost) {
this.server = server;
this.tabList = new VelocityTabList(connection);
this.profile = profile;
this.connection = connection;
this.virtualHost = virtualHost;
this.permissionFunction = (permission) -> Tristate.UNDEFINED;
}
@Override
@@ -123,8 +124,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
void setPlayerSettings(ClientSettings settings) {
this.settings = new ClientSettingsWrapper(settings);
server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, this.settings));
ClientSettingsWrapper cs = new ClientSettingsWrapper(settings);
this.settings = cs;
server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, cs));
}
public Optional<ModInfo> getModInfo() {
@@ -133,7 +135,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
void setModInfo(ModInfo modInfo) {
this.modInfo = modInfo;
server.getEventManager().fireAndForget(new PlayerModInfoEvent(this, this.modInfo));
server.getEventManager().fireAndForget(new PlayerModInfoEvent(this, modInfo));
}
@Override
@@ -161,7 +163,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
@Override
public void sendMessage(@NonNull Component component, @NonNull MessagePosition position) {
public void sendMessage(Component component, MessagePosition position) {
Preconditions.checkNotNull(component, "component");
Preconditions.checkNotNull(position, "position");
@@ -193,7 +195,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
@Override
public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) {
public ConnectionRequestBuilder createConnectionRequest(RegisteredServer server) {
return new ConnectionRequestBuilderImpl(server);
}
@@ -243,16 +245,19 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connection.delayedWrite(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion()));
}
if (tt.getTitle().isPresent()) {
Optional<Component> titleText = tt.getTitle();
if (titleText.isPresent()) {
TitlePacket titlePkt = new TitlePacket();
titlePkt.setAction(TitlePacket.SET_TITLE);
titlePkt.setComponent(ComponentSerializers.JSON.serialize(tt.getTitle().get()));
titlePkt.setComponent(ComponentSerializers.JSON.serialize(titleText.get()));
connection.delayedWrite(titlePkt);
}
if (tt.getSubtitle().isPresent()) {
Optional<Component> subtitleText = tt.getSubtitle();
if (subtitleText.isPresent()) {
TitlePacket titlePkt = new TitlePacket();
titlePkt.setAction(TitlePacket.SET_SUBTITLE);
titlePkt.setComponent(ComponentSerializers.JSON.serialize(tt.getSubtitle().get()));
titlePkt.setComponent(ComponentSerializers.JSON.serialize(subtitleText.get()));
connection.delayedWrite(titlePkt);
}
@@ -270,16 +275,23 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
@Nullable
public VelocityServerConnection getConnectedServer() {
return connectedServer;
}
public void handleConnectionException(RegisteredServer server, Throwable throwable) {
Throwable wrapped = throwable;
if (throwable instanceof CompletionException) {
wrapped = throwable.getCause();
if (throwable == null) {
throw new NullPointerException("throwable");
}
Throwable wrapped = throwable;
if (throwable instanceof CompletionException) {
Throwable cause = throwable.getCause();
if (cause != null) {
wrapped = cause;
}
}
String error = ThrowableUtils.briefDescription(wrapped);
String userMessage;
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
@@ -366,7 +378,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
String toTryName = serversToTry.get(tryIndex);
tryIndex++;
return server.getServers().getServer(toTryName);
return server.getServer(toTryName);
}
private Optional<ConnectionRequestBuilder.Status> checkServer(RegisteredServer server) {
@@ -390,13 +402,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer());
return server.getEventManager().fire(event)
.thenCompose((newEvent) -> {
if (!newEvent.getResult().isAllowed()) {
Optional<RegisteredServer> connectTo = newEvent.getResult().getServer();
if (!connectTo.isPresent()) {
return CompletableFuture.completedFuture(
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED)
);
}
RegisteredServer rs = newEvent.getResult().getServer().get();
RegisteredServer rs = connectTo.get();
Optional<ConnectionRequestBuilder.Status> lastCheck = checkServer(rs);
if (lastCheck.isPresent()) {
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(lastCheck.get()));
@@ -406,9 +419,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
public void setConnectedServer(VelocityServerConnection serverConnection) {
if (this.connectedServer != null && !serverConnection.getServerInfo().equals(connectedServer.getServerInfo())) {
VelocityServerConnection oldConnection = this.connectedServer;
if (oldConnection != null && !serverConnection.getServerInfo().equals(oldConnection.getServerInfo())) {
this.tryIndex = 0;
}
if (serverConnection == connectionInFlight) {
connectionInFlight = null;
}
this.connectedServer = serverConnection;
}
@@ -426,6 +443,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connection.closeWith(Disconnect.create(reason));
}
private MinecraftConnection ensureBackendConnection() {
VelocityServerConnection sc = this.connectedServer;
if (sc == null) {
throw new IllegalStateException("No backend connection");
}
MinecraftConnection mc = sc.getConnection();
if (mc == null) {
throw new IllegalStateException("Backend connection is not connected to a server");
}
return mc;
}
void teardown() {
if (connectionInFlight != null) {
connectionInFlight.disconnect();
@@ -439,11 +470,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override
public String toString() {
return "[connected player] " + getProfile().getName() + " (" + getRemoteAddress() + ")";
return "[connected player] " + profile.getName() + " (" + getRemoteAddress() + ")";
}
@Override
public @NonNull Tristate getPermissionValue(@NonNull String permission) {
public Tristate getPermissionValue(String permission) {
return permissionFunction.getPermissionValue(permission);
}
@@ -461,7 +492,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override
public void spoofChatInput(String input) {
Preconditions.checkArgument(input.length() <= Chat.MAX_SERVERBOUND_MESSAGE_LENGTH, "input cannot be greater than " + Chat.MAX_SERVERBOUND_MESSAGE_LENGTH + " characters in length");
connectedServer.getConnection().write(Chat.createServerbound(input));
ensureBackendConnection().write(Chat.createServerbound(input));
}
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {

View File

@@ -22,10 +22,12 @@ import com.velocitypowered.proxy.util.EncryptionUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
@@ -38,6 +40,8 @@ import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
public class LoginSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
@@ -47,10 +51,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
private final VelocityServer server;
private final MinecraftConnection inbound;
private final InboundConnection apiInbound;
private ServerLogin login;
private byte[] verify;
private @MonotonicNonNull ServerLogin login;
private byte[] verify = EMPTY_BYTE_ARRAY;
private int playerInfoId;
private ConnectedPlayer connectedPlayer;
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
public LoginSessionHandler(VelocityServer server, MinecraftConnection inbound, InboundConnection apiInbound) {
this.server = Preconditions.checkNotNull(server, "server");
@@ -62,12 +66,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
public boolean handle(ServerLogin packet) {
this.login = packet;
if (inbound.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) {
LoginPluginMessage message = new LoginPluginMessage();
playerInfoId = ThreadLocalRandom.current().nextInt();
message.setId(playerInfoId);
message.setChannel(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL);
message.setData(Unpooled.EMPTY_BUFFER);
inbound.write(message);
inbound.write(new LoginPluginMessage(playerInfoId, VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL,
Unpooled.EMPTY_BUFFER));
} else {
beginPreLogin();
}
@@ -92,6 +93,15 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(EncryptionResponse packet) {
ServerLogin login = this.login;
if (login == null) {
throw new IllegalStateException("No ServerLogin packet received yet.");
}
if (verify.length == 0) {
throw new IllegalStateException("No EncryptionRequest packet sent yet.");
}
try {
KeyPair serverKeyPair = server.getServerKeyPair();
byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, packet.getVerifyToken());
@@ -149,6 +159,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
}
private void beginPreLogin() {
ServerLogin login = this.login;
if (login == null) {
throw new IllegalStateException("No ServerLogin packet received yet.");
}
PreLoginEvent event = new PreLoginEvent(apiInbound, login.getUsername());
server.getEventManager().fire(event)
.thenRunAsync(() -> {
@@ -157,9 +171,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
return;
}
PreLoginComponentResult result = event.getResult();
if (!result.isAllowed()) {
Optional<Component> disconnectReason = result.getReason();
if (disconnectReason.isPresent()) {
// The component is guaranteed to be provided if the connection was denied.
inbound.closeWith(Disconnect.create(event.getResult().getReason().get()));
inbound.closeWith(Disconnect.create(disconnectReason.get()));
return;
}
@@ -212,13 +227,13 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// The player was disconnected
return;
}
if (!event.getResult().isAllowed()) {
// The component is guaranteed to be provided if the connection was denied.
player.disconnect(event.getResult().getReason().get());
return;
}
handleProxyLogin(player);
Optional<Component> reason = event.getResult().getReason();
if (reason.isPresent()) {
player.disconnect(reason.get());
} else {
handleProxyLogin(player);
}
}, inbound.eventLoop());
});

View File

@@ -49,9 +49,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
server.getEventManager().fire(event)
.thenRunAsync(() -> {
StatusResponse response = new StatusResponse();
response.setStatus(VelocityServer.GSON.toJson(event.getPing()));
connection.write(response);
connection.write(new StatusResponse(VelocityServer.GSON.toJson(event.getPing())));
}, connection.eventLoop());
return true;
}

View File

@@ -9,15 +9,13 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.List;
import java.util.Optional;
public class ForgeUtil {
private ForgeUtil() {
throw new AssertionError();
}
public static Optional<List<ModInfo.Mod>> readModList(PluginMessage message) {
public static List<ModInfo.Mod> readModList(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
Preconditions.checkArgument(message.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL),
"message is not a FML HS plugin message");
@@ -36,10 +34,10 @@ public class ForgeUtil {
mods.add(new ModInfo.Mod(id, version));
}
return Optional.of(mods.build());
return mods.build();
}
return Optional.empty();
return ImmutableList.of();
} finally {
byteBuf.release();
}

View File

@@ -53,16 +53,14 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Comm
.completer((reader, parsedLine, list) -> {
try {
boolean isCommand = parsedLine.line().indexOf(' ') == -1;
Optional<List<String>> o = this.server.getCommandManager().offerSuggestions(this, parsedLine.line());
o.ifPresent(offers -> {
for (String offer : offers) {
if (isCommand) {
list.add(new Candidate(offer.substring(1)));
} else {
list.add(new Candidate(offer));
}
List<String> offers = this.server.getCommandManager().offerSuggestions(this, parsedLine.line());
for (String offer : offers) {
if (isCommand) {
list.add(new Candidate(offer.substring(1)));
} else {
list.add(new Candidate(offer));
}
});
}
} catch (Exception e) {
logger.error("An error occurred while trying to perform tab completion.", e);
}

View File

@@ -12,6 +12,7 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.WriteBufferWaterMark;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import java.net.InetSocketAddress;
import java.util.HashSet;
@@ -27,16 +28,15 @@ public final class ConnectionManager {
private final VelocityServer server;
public final ServerChannelInitializerHolder serverChannelInitializer;
public ConnectionManager(final VelocityServer server) {
public ConnectionManager(VelocityServer server) {
this.server = server;
this.transportType = TransportType.bestType();
this.bossGroup = this.transportType.createEventLoopGroup(TransportType.Type.BOSS);
this.workerGroup = this.transportType.createEventLoopGroup(TransportType.Type.WORKER);
this.serverChannelInitializer = new ServerChannelInitializerHolder(new ServerChannelInitializer(this.server));
this.logChannelInformation();
}
private void logChannelInformation() {
public void logChannelInformation() {
LOGGER.info("Connections will use {} channels, {} compression, {} ciphers", this.transportType, Natives.compressor.getLoadedVariant(), Natives.cipher.getLoadedVariant());
}

View File

@@ -1,5 +1,6 @@
package com.velocitypowered.proxy.network.http;
import com.google.common.base.VerifyException;
import com.velocitypowered.proxy.VelocityServer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
@@ -61,6 +62,9 @@ public class NettyHttpClient {
.addListener(future -> {
if (future.isSuccess()) {
Channel channel = (Channel) future.getNow();
if (channel == null) {
throw new VerifyException("Null channel retrieved from pool!");
}
channel.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url.getPath() + "?" + url.getQuery());

View File

@@ -16,10 +16,13 @@ public class PluginClassLoader extends URLClassLoader {
public PluginClassLoader(URL[] urls) {
super(urls);
}
public void addToClassloaders() {
loaders.add(this);
}
public void addPath(Path path) {
void addPath(Path path) {
try {
addURL(path.toUri().toURL());
} catch (MalformedURLException e) {

View File

@@ -12,7 +12,6 @@ import com.velocitypowered.api.plugin.PluginManager;
import net.kyori.event.EventSubscriber;
import net.kyori.event.PostResult;
import net.kyori.event.SimpleEventBus;
import net.kyori.event.method.EventExecutor;
import net.kyori.event.method.MethodScanner;
import net.kyori.event.method.MethodSubscriptionAdapter;
import net.kyori.event.method.SimpleMethodSubscriptionAdapter;
@@ -20,12 +19,14 @@ import net.kyori.event.method.asm.ASMEventExecutorFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -38,20 +39,24 @@ public class VelocityEventManager implements EventManager {
.synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new));
private final ListMultimap<Object, EventHandler<?>> registeredHandlersByPlugin = Multimaps
.synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new));
private final VelocityEventBus bus = new VelocityEventBus(
new ASMEventExecutorFactory<>(new PluginClassLoader(new URL[0])),
new VelocityMethodScanner());
private final SimpleEventBus<Object> bus;
private final MethodSubscriptionAdapter<Object> methodAdapter;
private final ExecutorService service;
private final PluginManager pluginManager;
public VelocityEventManager(PluginManager pluginManager) {
PluginClassLoader cl = new PluginClassLoader(new URL[0]);
cl.addToClassloaders();
this.bus = new SimpleEventBus<>(Object.class);
this.methodAdapter = new SimpleMethodSubscriptionAdapter<>(bus, new ASMEventExecutorFactory<>(cl),
new VelocityMethodScanner());
this.pluginManager = pluginManager;
this.service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder()
.setNameFormat("Velocity Event Executor - #%d").setDaemon(true).build());
}
@Override
public void register(@NonNull Object plugin, @NonNull Object listener) {
public void register(Object plugin, Object listener) {
Preconditions.checkNotNull(plugin, "plugin");
Preconditions.checkNotNull(listener, "listener");
Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "Specified plugin is not loaded");
@@ -59,11 +64,12 @@ public class VelocityEventManager implements EventManager {
throw new IllegalArgumentException("Trying to register the plugin main instance. Velocity already takes care of this for you.");
}
registeredListenersByPlugin.put(plugin, listener);
bus.register(listener);
methodAdapter.register(listener);
}
@Override
public <E> void register(@NonNull Object plugin, @NonNull Class<E> eventClass, @NonNull PostOrder postOrder, @NonNull EventHandler<E> handler) {
@SuppressWarnings("type.argument.type.incompatible")
public <E> void register(Object plugin, Class<E> eventClass, PostOrder postOrder, EventHandler<E> handler) {
Preconditions.checkNotNull(plugin, "plugin");
Preconditions.checkNotNull(eventClass, "eventClass");
Preconditions.checkNotNull(postOrder, "postOrder");
@@ -72,8 +78,10 @@ public class VelocityEventManager implements EventManager {
}
@Override
public <E> @NonNull CompletableFuture<E> fire(@NonNull E event) {
Preconditions.checkNotNull(event, "event");
public <E> CompletableFuture<E> fire(E event) {
if (event == null) {
throw new NullPointerException("event");
}
if (!bus.hasSubscribers(event.getClass())) {
// Optimization: nobody's listening.
return CompletableFuture.completedFuture(event);
@@ -99,30 +107,30 @@ public class VelocityEventManager implements EventManager {
}
@Override
public void unregisterListeners(@NonNull Object plugin) {
public void unregisterListeners(Object plugin) {
Preconditions.checkNotNull(plugin, "plugin");
Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "Specified plugin is not loaded");
Collection<Object> listeners = registeredListenersByPlugin.removeAll(plugin);
listeners.forEach(bus::unregister);
listeners.forEach(methodAdapter::unregister);
Collection<EventHandler<?>> handlers = registeredHandlersByPlugin.removeAll(plugin);
handlers.forEach(bus::unregister);
handlers.forEach(handler -> bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST)));
}
@Override
public void unregisterListener(@NonNull Object plugin, @NonNull Object listener) {
public void unregisterListener(Object plugin, Object listener) {
Preconditions.checkNotNull(plugin, "plugin");
Preconditions.checkNotNull(listener, "listener");
Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "Specified plugin is not loaded");
registeredListenersByPlugin.remove(plugin, listener);
bus.unregister(listener);
methodAdapter.unregister(listener);
}
@Override
public <E> void unregister(@NonNull Object plugin, @NonNull EventHandler<E> handler) {
public <E> void unregister(Object plugin, EventHandler<E> handler) {
Preconditions.checkNotNull(plugin, "plugin");
Preconditions.checkNotNull(handler, "listener");
registeredHandlersByPlugin.remove(plugin, handler);
bus.unregister(handler);
bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST));
}
public boolean shutdown() throws InterruptedException {
@@ -130,27 +138,6 @@ public class VelocityEventManager implements EventManager {
return service.awaitTermination(10, TimeUnit.SECONDS);
}
private static class VelocityEventBus extends SimpleEventBus<Object> {
private final MethodSubscriptionAdapter<Object> methodAdapter;
VelocityEventBus(EventExecutor.@NonNull Factory<Object, Object> factory, @NonNull MethodScanner<Object> methodScanner) {
super(Object.class);
this.methodAdapter = new SimpleMethodSubscriptionAdapter<>(this, factory, methodScanner);
}
void register(Object listener) {
this.methodAdapter.register(listener);
}
void unregister(Object listener) {
this.methodAdapter.unregister(listener);
}
void unregister(EventHandler<?> handler) {
this.unregister(s -> s instanceof KyoriToVelocityHandler && ((KyoriToVelocityHandler<?>) s).getHandler().equals(handler));
}
}
private static class VelocityMethodScanner implements MethodScanner<Object> {
@Override
public boolean shouldRegister(@NonNull Object listener, @NonNull Method method) {
@@ -159,7 +146,11 @@ public class VelocityEventManager implements EventManager {
@Override
public int postOrder(@NonNull Object listener, @NonNull Method method) {
return method.getAnnotation(Subscribe.class).order().ordinal();
Subscribe annotation = method.getAnnotation(Subscribe.class);
if (annotation == null) {
throw new IllegalStateException("Trying to determine post order for listener without @Subscribe annotation");
}
return annotation.order().ordinal();
}
@Override
@@ -190,5 +181,18 @@ public class VelocityEventManager implements EventManager {
public EventHandler<E> getHandler() {
return handler;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
KyoriToVelocityHandler<?> that = (KyoriToVelocityHandler<?>) o;
return Objects.equals(handler, that.handler);
}
@Override
public int hashCode() {
return Objects.hash(handler);
}
}
}

View File

@@ -31,12 +31,15 @@ public class VelocityPluginManager implements PluginManager {
this.server = checkNotNull(server, "server");
}
private void registerPlugin(@NonNull PluginContainer plugin) {
private void registerPlugin(PluginContainer plugin) {
plugins.put(plugin.getDescription().getId(), plugin);
plugin.getInstance().ifPresent(instance -> pluginInstances.put(instance, plugin));
Optional<?> instance = plugin.getInstance();
if (instance.isPresent()) {
pluginInstances.put(instance.get(), plugin);
}
}
public void loadPlugins(@NonNull Path directory) throws IOException {
public void loadPlugins(Path directory) throws IOException {
checkNotNull(directory, "directory");
checkArgument(Files.isDirectory(directory), "provided path isn't a directory");
@@ -86,7 +89,7 @@ public class VelocityPluginManager implements PluginManager {
}
@Override
public @NonNull Optional<PluginContainer> fromInstance(@NonNull Object instance) {
public Optional<PluginContainer> fromInstance(Object instance) {
checkNotNull(instance, "instance");
if (instance instanceof PluginContainer) {
@@ -97,23 +100,23 @@ public class VelocityPluginManager implements PluginManager {
}
@Override
public @NonNull Optional<PluginContainer> getPlugin(@NonNull String id) {
public Optional<PluginContainer> getPlugin(String id) {
checkNotNull(id, "id");
return Optional.ofNullable(plugins.get(id));
}
@Override
public @NonNull Collection<PluginContainer> getPlugins() {
public Collection<PluginContainer> getPlugins() {
return Collections.unmodifiableCollection(plugins.values());
}
@Override
public boolean isLoaded(@NonNull String id) {
public boolean isLoaded(String id) {
return plugins.containsKey(id);
}
@Override
public void addToClasspath(@NonNull Object plugin, @NonNull Path path) {
public void addToClasspath(Object plugin, Path path) {
checkNotNull(plugin, "instance");
checkNotNull(path, "path");
checkArgument(pluginInstances.containsKey(plugin), "plugin is not loaded");

View File

@@ -12,6 +12,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.plugin.PluginClassLoader;
import com.velocitypowered.proxy.plugin.loader.java.JavaVelocityPluginDescription;
import com.velocitypowered.proxy.plugin.loader.java.VelocityPluginModule;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.BufferedInputStream;
import java.io.InputStreamReader;
@@ -51,6 +52,7 @@ public class JavaPluginLoader implements PluginLoader {
PluginClassLoader loader = new PluginClassLoader(
new URL[] {source.toUri().toURL() }
);
loader.addToClassloaders();
Class mainClass = loader.loadClass(pd.getMain());
return createDescription(pd, source, mainClass);
@@ -72,6 +74,10 @@ public class JavaPluginLoader implements PluginLoader {
Injector injector = Guice.createInjector(new VelocityPluginModule(server, javaDescription, baseDirectory));
Object instance = injector.getInstance(javaDescription.getMainClass());
if (instance == null) {
throw new IllegalStateException("Got nothing from injector for plugin " + javaDescription.getId());
}
return new VelocityPluginContainer(description, instance);
}
@@ -93,10 +99,8 @@ public class JavaPluginLoader implements PluginLoader {
private VelocityPluginDescription createDescription(SerializedPluginDescription description, Path source, Class mainClass) {
Set<PluginDependency> dependencies = new HashSet<>();
if (description.getDependencies() != null) {
for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) {
dependencies.add(toDependencyMeta(dependency));
}
for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) {
dependencies.add(toDependencyMeta(dependency));
}
return new JavaVelocityPluginDescription(

View File

@@ -33,7 +33,7 @@ public class VelocityPluginDescription implements PluginDescription {
this.description = Strings.emptyToNull(description);
this.url = Strings.emptyToNull(url);
this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors);
this.dependencies = Maps.uniqueIndex(dependencies, PluginDependency::getId);
this.dependencies = Maps.uniqueIndex(dependencies, d -> d == null ? null : d.getId());
this.source = source;
}

View File

@@ -8,14 +8,15 @@ import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.meta.PluginDependency;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.*;
public class PluginDependencyUtils {
public static List<PluginDescription> sortCandidates(List<PluginDescription> candidates) {
public static List<PluginDescription> sortCandidates(List<@NonNull PluginDescription> candidates) {
// Create our graph, we're going to be using this for Kahn's algorithm.
MutableGraph<PluginDescription> graph = GraphBuilder.directed().allowsSelfLoops(false).build();
Map<String, PluginDescription> candidateMap = Maps.uniqueIndex(candidates, PluginDescription::getId);
Map<String, PluginDescription> candidateMap = Maps.uniqueIndex(candidates, d -> d == null ? null : d.getId());
// Add edges
for (PluginDescription description : candidates) {
@@ -36,7 +37,7 @@ public class PluginDependencyUtils {
// Actually run Kahn's algorithm
List<PluginDescription> sorted = new ArrayList<>();
while (!noEdges.isEmpty()) {
PluginDescription candidate = noEdges.poll();
PluginDescription candidate = noEdges.remove();
sorted.add(candidate);
for (PluginDescription node : ImmutableSet.copyOf(graph.adjacentNodes(candidate))) {

View File

@@ -48,6 +48,10 @@ public enum ProtocolConstants { ;
public enum Direction {
SERVERBOUND,
CLIENTBOUND
CLIENTBOUND;
public StateRegistry.PacketRegistry.ProtocolVersion getProtocol(StateRegistry state, int protocolVersion) {
return (this == SERVERBOUND ? state.SERVERBOUND : state.CLIENTBOUND).getVersion(protocolVersion);
}
}
}

View File

@@ -1,12 +1,12 @@
package com.velocitypowered.proxy.protocol;
import com.google.common.base.Strings;
import com.google.common.primitives.ImmutableIntArray;
import com.velocitypowered.proxy.protocol.packet.*;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.function.Supplier;
@@ -36,6 +36,9 @@ public enum StateRegistry {
},
PLAY {
{
SERVERBOUND.fallback = false;
CLIENTBOUND.fallback = false;
SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new,
map(0x14, MINECRAFT_1_8, false),
map(0x01, MINECRAFT_1_9, false),
@@ -150,8 +153,8 @@ public enum StateRegistry {
public static final int STATUS_ID = 1;
public static final int LOGIN_ID = 2;
public final PacketRegistry CLIENTBOUND = new PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, this);
public final PacketRegistry SERVERBOUND = new PacketRegistry(ProtocolConstants.Direction.SERVERBOUND, this);
public final PacketRegistry CLIENTBOUND = new PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND);
public final PacketRegistry SERVERBOUND = new PacketRegistry(ProtocolConstants.Direction.SERVERBOUND);
public static class PacketRegistry {
private static final IntObjectMap<ImmutableIntArray> LINKED_PROTOCOL_VERSIONS = new IntObjectHashMap<>();
@@ -165,19 +168,18 @@ public enum StateRegistry {
}
private final ProtocolConstants.Direction direction;
private final StateRegistry state;
private final IntObjectMap<ProtocolVersion> versions = new IntObjectHashMap<>(16);
private boolean fallback = true;
public PacketRegistry(Direction direction, StateRegistry state) {
public PacketRegistry(Direction direction) {
this.direction = direction;
this.state = state;
ProtocolConstants.SUPPORTED_VERSIONS.forEach(version -> versions.put(version, new ProtocolVersion(version)));
}
public ProtocolVersion getVersion(final int version) {
ProtocolVersion result = versions.get(version);
if (result == null) {
if (state != PLAY) {
if (fallback) {
return getVersion(MINIMUM_GENERIC_VERSION);
}
throw new IllegalArgumentException("Could not find data for protocol version " + version);
@@ -224,7 +226,7 @@ public enum StateRegistry {
this.packetClassToId.defaultReturnValue(Integer.MIN_VALUE);
}
public MinecraftPacket createPacket(final int id) {
public @Nullable MinecraftPacket createPacket(final int id) {
final Supplier<? extends MinecraftPacket> supplier = this.packetIdToSupplier.get(id);
if (supplier == null) {
return null;
@@ -242,23 +244,6 @@ public enum StateRegistry {
}
return id;
}
@Override
public String toString() {
StringBuilder mappingAsString = new StringBuilder("{");
for (Object2IntMap.Entry<Class<? extends MinecraftPacket>> entry : packetClassToId.object2IntEntrySet()) {
mappingAsString.append(entry.getKey().getSimpleName()).append(" -> ")
.append("0x")
.append(Strings.padStart(Integer.toHexString(entry.getIntValue()), 2, '0'))
.append(", ");
}
mappingAsString.setLength(mappingAsString.length() - 2);
mappingAsString.append("}");
return "ProtocolVersion{" +
"id=" + id +
", packetClassToId=" + mappingAsString.toString() +
'}';
}
}
}
@@ -283,7 +268,7 @@ public enum StateRegistry {
}
@Override
public boolean equals(Object o) {
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PacketMapping that = (PacketMapping) o;

View File

@@ -13,14 +13,14 @@ import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
private StateRegistry state;
private final ProtocolConstants.Direction direction;
private StateRegistry state;
private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion;
public MinecraftDecoder(ProtocolConstants.Direction direction) {
this.state = StateRegistry.HANDSHAKE;
this.direction = Preconditions.checkNotNull(direction, "direction");
this.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION);
this.protocolVersion = direction.getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION);
this.state = StateRegistry.HANDSHAKE;
}
@Override
@@ -51,24 +51,12 @@ public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
}
}
public StateRegistry.PacketRegistry.ProtocolVersion getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = (this.direction == ProtocolConstants.Direction.CLIENTBOUND ? this.state.CLIENTBOUND : this.state.SERVERBOUND).getVersion(protocolVersion);
}
public StateRegistry getState() {
return state;
this.protocolVersion = direction.getProtocol(state, protocolVersion);
}
public void setState(StateRegistry state) {
this.state = state;
this.setProtocolVersion(protocolVersion.id);
}
public ProtocolConstants.Direction getDirection() {
return direction;
}
}

View File

@@ -10,14 +10,14 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
private StateRegistry state;
private final ProtocolConstants.Direction direction;
private StateRegistry state;
private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion;
public MinecraftEncoder(ProtocolConstants.Direction direction) {
this.state = StateRegistry.HANDSHAKE;
this.direction = Preconditions.checkNotNull(direction, "direction");
this.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION);
this.protocolVersion = direction.getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION);
this.state = StateRegistry.HANDSHAKE;
}
@Override
@@ -27,24 +27,12 @@ public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
msg.encode(out, direction, protocolVersion.id);
}
public StateRegistry.PacketRegistry.ProtocolVersion getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(final int protocolVersion) {
this.protocolVersion = (this.direction == ProtocolConstants.Direction.CLIENTBOUND ? this.state.CLIENTBOUND : this.state.SERVERBOUND).getVersion(protocolVersion);
}
public StateRegistry getState() {
return state;
this.protocolVersion = direction.getProtocol(state, protocolVersion);
}
public void setState(StateRegistry state) {
this.state = state;
this.setProtocolVersion(protocolVersion.id);
}
public ProtocolConstants.Direction getDirection() {
return direction;
}
}

View File

@@ -5,6 +5,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
@@ -15,15 +16,18 @@ public class BossBar implements MinecraftPacket {
public static final int UPDATE_NAME = 3;
public static final int UPDATE_STYLE = 4;
public static final int UPDATE_PROPERTIES = 5;
private UUID uuid;
private @Nullable UUID uuid;
private int action;
private String name;
private @Nullable String name;
private float percent;
private int color;
private int overlay;
private short flags;
public UUID getUuid() {
if (uuid == null) {
throw new IllegalStateException("No boss bar UUID specified");
}
return uuid;
}
@@ -39,7 +43,7 @@ public class BossBar implements MinecraftPacket {
this.action = action;
}
public String getName() {
public @Nullable String getName() {
return name;
}
@@ -124,10 +128,16 @@ public class BossBar implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (uuid == null) {
throw new IllegalStateException("No boss bar UUID specified");
}
ProtocolUtils.writeUuid(buf, uuid);
ProtocolUtils.writeVarInt(buf, action);
switch (action) {
case ADD:
if (name == null) {
throw new IllegalStateException("No name specified!");
}
ProtocolUtils.writeString(buf, name);
buf.writeFloat(percent);
ProtocolUtils.writeVarInt(buf, color);
@@ -140,6 +150,9 @@ public class BossBar implements MinecraftPacket {
buf.writeFloat(percent);
break;
case UPDATE_NAME:
if (name == null) {
throw new IllegalStateException("No name specified!");
}
ProtocolUtils.writeString(buf, name);
break;
case UPDATE_STYLE:

View File

@@ -8,12 +8,13 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.Nullable;
public class Chat implements MinecraftPacket {
public static final byte CHAT = (byte) 0;
public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256;
private String message;
private @Nullable String message;
private byte type;
public Chat() {
@@ -25,6 +26,9 @@ public class Chat implements MinecraftPacket {
}
public String getMessage() {
if (message == null) {
throw new IllegalStateException("Message is not specified");
}
return message;
}
@@ -58,6 +62,9 @@ public class Chat implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (message == null) {
throw new IllegalStateException("Message is not specified");
}
ProtocolUtils.writeString(buf, message);
if (direction == ProtocolConstants.Direction.CLIENTBOUND) {
buf.writeByte(type);

View File

@@ -5,10 +5,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientSettings implements MinecraftPacket {
private String locale;
private @Nullable String locale;
private byte viewDistance;
private int chatVisibility;
private boolean chatColors;
@@ -28,6 +29,9 @@ public class ClientSettings implements MinecraftPacket {
}
public String getLocale() {
if (locale == null) {
throw new IllegalStateException("No locale specified");
}
return locale;
}
@@ -102,6 +106,9 @@ public class ClientSettings implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (locale == null) {
throw new IllegalStateException("No locale specified");
}
ProtocolUtils.writeString(buf, locale);
buf.writeByte(viewDistance);
ProtocolUtils.writeVarInt(buf, chatVisibility);

View File

@@ -8,22 +8,26 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.Nullable;
public class Disconnect implements MinecraftPacket {
private String reason;
private @Nullable String reason;
public Disconnect() {
}
public Disconnect(String reason) {
this.reason = reason;
this.reason = Preconditions.checkNotNull(reason, "reason");
}
public String getReason() {
if (reason == null) {
throw new IllegalStateException("No reason specified");
}
return reason;
}
public void setReason(String reason) {
public void setReason(@Nullable String reason) {
this.reason = reason;
}
@@ -41,6 +45,9 @@ public class Disconnect implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (reason == null) {
throw new IllegalStateException("No reason specified.");
}
ProtocolUtils.writeString(buf, reason);
}

View File

@@ -8,10 +8,12 @@ import io.netty.buffer.ByteBuf;
import java.util.Arrays;
import static com.velocitypowered.proxy.connection.VelocityConstants.*;
public class EncryptionRequest implements MinecraftPacket {
private String serverId = "";
private byte[] publicKey;
private byte[] verifyToken;
private byte[] publicKey = EMPTY_BYTE_ARRAY;
private byte[] verifyToken = EMPTY_BYTE_ARRAY;
public byte[] getPublicKey() {
return publicKey;

View File

@@ -8,9 +8,11 @@ import io.netty.buffer.ByteBuf;
import java.util.Arrays;
import static com.velocitypowered.proxy.connection.VelocityConstants.*;
public class EncryptionResponse implements MinecraftPacket {
private byte[] sharedSecret;
private byte[] verifyToken;
private byte[] sharedSecret = EMPTY_BYTE_ARRAY;
private byte[] verifyToken = EMPTY_BYTE_ARRAY;
public byte[] getSharedSecret() {
return sharedSecret;

View File

@@ -8,7 +8,7 @@ import io.netty.buffer.ByteBuf;
public class Handshake implements MinecraftPacket {
private int protocolVersion;
private String serverAddress;
private String serverAddress = "";
private int port;
private int nextStatus;

View File

@@ -1,5 +1,6 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants.Direction;
@@ -11,36 +12,29 @@ import net.kyori.text.serializer.ComponentSerializers;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
public class HeaderAndFooter implements MinecraftPacket {
private static final HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}");
private static final String EMPTY_COMPONENT = "{\"translate\":\"\"}";
private static final HeaderAndFooter RESET = new HeaderAndFooter();
private String header;
private String footer;
public HeaderAndFooter() {
this(EMPTY_COMPONENT, EMPTY_COMPONENT);
}
public HeaderAndFooter(String header, String footer) {
this.header = header;
this.footer = footer;
this.header = Preconditions.checkNotNull(header, "header");
this.footer = Preconditions.checkNotNull(footer, "footer");
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getFooter() {
return footer;
}
public void setFooter(String footer) {
this.footer = footer;
}
@Override
public void decode(ByteBuf buf, Direction direction, int protocolVersion) {
throw new UnsupportedOperationException("Decode is not implemented");

View File

@@ -5,6 +5,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class JoinGame implements MinecraftPacket {
private int entityId;
@@ -12,7 +13,7 @@ public class JoinGame implements MinecraftPacket {
private int dimension;
private short difficulty;
private short maxPlayers;
private String levelType;
private @Nullable String levelType;
private boolean reducedDebugInfo;
public int getEntityId() {
@@ -56,6 +57,9 @@ public class JoinGame implements MinecraftPacket {
}
public String getLevelType() {
if (levelType == null) {
throw new IllegalStateException("No level type specified.");
}
return levelType;
}
@@ -110,6 +114,9 @@ public class JoinGame implements MinecraftPacket {
}
buf.writeByte(difficulty);
buf.writeByte(maxPlayers);
if (levelType == null) {
throw new IllegalStateException("No level type specified.");
}
ProtocolUtils.writeString(buf, levelType);
buf.writeBoolean(reducedDebugInfo);
}

View File

@@ -6,14 +6,11 @@ import net.kyori.text.serializer.ComponentSerializers;
public class LegacyPingResponse {
private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0, ImmutableList.of());
private int protocolVersion;
private String serverVersion;
private String motd;
private int playersOnline;
private int playersMax;
public LegacyPingResponse() {
}
private final int protocolVersion;
private final String serverVersion;
private final String motd;
private final int playersOnline;
private final int playersMax;
public LegacyPingResponse(int protocolVersion, String serverVersion, String motd, int playersOnline, int playersMax) {
this.protocolVersion = protocolVersion;
@@ -27,42 +24,22 @@ public class LegacyPingResponse {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public String getServerVersion() {
return serverVersion;
}
public void setServerVersion(String serverVersion) {
this.serverVersion = serverVersion;
}
public String getMotd() {
return motd;
}
public void setMotd(String motd) {
this.motd = motd;
}
public int getPlayersOnline() {
return playersOnline;
}
public void setPlayersOnline(int playersOnline) {
this.playersOnline = playersOnline;
}
public int getPlayersMax() {
return playersMax;
}
public void setPlayersMax(int playersMax) {
this.playersMax = playersMax;
}
@Override
public String toString() {
return "LegacyPingResponse{" +

View File

@@ -6,36 +6,38 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.checkerframework.checker.nullness.qual.Nullable;
public class LoginPluginMessage implements MinecraftPacket {
private int id;
private String channel;
private ByteBuf data;
private @Nullable String channel;
private ByteBuf data = Unpooled.EMPTY_BUFFER;
public LoginPluginMessage() {
}
public LoginPluginMessage(int id, @Nullable String channel, ByteBuf data) {
this.id = id;
this.channel = channel;
this.data = data;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getChannel() {
if (channel == null) {
throw new IllegalStateException("Channel is not specified!");
}
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public ByteBuf getData() {
return data;
}
public void setData(ByteBuf data) {
this.data = data;
}
@Override
public String toString() {
return "LoginPluginMessage{" +
@@ -59,6 +61,9 @@ public class LoginPluginMessage implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeVarInt(buf, id);
if (channel == null) {
throw new IllegalStateException("Channel is not specified!");
}
ProtocolUtils.writeString(buf, channel);
buf.writeBytes(data);
}

View File

@@ -10,7 +10,7 @@ import io.netty.buffer.Unpooled;
public class LoginPluginResponse implements MinecraftPacket {
private int id;
private boolean success;
private ByteBuf data;
private ByteBuf data = Unpooled.EMPTY_BUFFER;
public int getId() {
return id;

View File

@@ -1,5 +1,6 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
@@ -9,6 +10,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -21,11 +23,11 @@ public class PlayerListItem implements MinecraftPacket {
public static final int UPDATE_DISPLAY_NAME = 3;
public static final int REMOVE_PLAYER = 4;
private int action;
private List<Item> items;
private final List<Item> items = new ArrayList<>();
public PlayerListItem(int action, List<Item> items) {
this.action = action;
this.items = items;
this.items.addAll(items);
}
public PlayerListItem() {}
@@ -41,7 +43,6 @@ public class PlayerListItem implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
action = ProtocolUtils.readVarInt(buf);
items = new ArrayList<>();
int length = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < length; i++) {
@@ -57,7 +58,8 @@ public class PlayerListItem implements MinecraftPacket {
if (hasDisplayName) {
item.setDisplayName(ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf)));
}
} break;
break;
}
case UPDATE_GAMEMODE:
item.setGameMode(ProtocolUtils.readVarInt(buf));
break;
@@ -81,7 +83,7 @@ public class PlayerListItem implements MinecraftPacket {
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeVarInt(buf, action);
ProtocolUtils.writeVarInt(buf, items.size());
for (Item item: items) {
for (Item item : items) {
ProtocolUtils.writeUuid(buf, item.getUuid());
switch (action) {
case ADD_PLAYER:
@@ -113,7 +115,7 @@ public class PlayerListItem implements MinecraftPacket {
return handler.handle(this);
}
private void writeDisplayName(ByteBuf buf, Component displayName) {
private void writeDisplayName(ByteBuf buf, @Nullable Component displayName) {
buf.writeBoolean(displayName != null);
if (displayName != null) {
ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(displayName));
@@ -122,11 +124,11 @@ public class PlayerListItem implements MinecraftPacket {
public static class Item {
private final UUID uuid;
private String name;
private List<GameProfile.Property> properties;
private String name = "";
private List<GameProfile.Property> properties = ImmutableList.of();
private int gameMode;
private int latency;
private Component displayName;
private @Nullable Component displayName;
public Item(UUID uuid) {
this.uuid = uuid;
@@ -181,11 +183,11 @@ public class PlayerListItem implements MinecraftPacket {
return this;
}
public Component getDisplayName() {
public @Nullable Component getDisplayName() {
return displayName;
}
public Item setDisplayName(Component displayName) {
public Item setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
return this;
}

View File

@@ -6,12 +6,18 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import org.checkerframework.checker.nullness.qual.Nullable;
import static com.velocitypowered.proxy.connection.VelocityConstants.*;
public class PluginMessage implements MinecraftPacket {
private String channel;
private byte[] data;
private @Nullable String channel;
private byte[] data = EMPTY_BYTE_ARRAY;
public String getChannel() {
if (channel == null) {
throw new IllegalStateException("Channel is not specified.");
}
return channel;
}
@@ -44,6 +50,9 @@ public class PluginMessage implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (channel == null) {
throw new IllegalStateException("Channel is not specified.");
}
ProtocolUtils.writeString(buf, channel);
buf.writeBytes(data);
}

View File

@@ -10,7 +10,7 @@ public class Respawn implements MinecraftPacket {
private int dimension;
private short difficulty;
private short gamemode;
private String levelType;
private String levelType = "";
public Respawn() {
}

View File

@@ -1,20 +1,27 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerLogin implements MinecraftPacket {
private String username;
private @Nullable String username;
public String getUsername() {
return username;
public ServerLogin() {}
public ServerLogin(String username) {
this.username = Preconditions.checkNotNull(username, "username");
}
public void setUsername(String username) {
this.username = username;
public String getUsername() {
if (username == null) {
throw new IllegalStateException("No username found!");
}
return username;
}
@Override
@@ -31,6 +38,9 @@ public class ServerLogin implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (username == null) {
throw new IllegalStateException("No username found!");
}
ProtocolUtils.writeString(buf, username);
}

View File

@@ -5,14 +5,18 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
public class ServerLoginSuccess implements MinecraftPacket {
private UUID uuid;
private String username;
private @Nullable UUID uuid;
private @Nullable String username;
public UUID getUuid() {
if (uuid == null) {
throw new IllegalStateException("No UUID specified!");
}
return uuid;
}
@@ -21,6 +25,9 @@ public class ServerLoginSuccess implements MinecraftPacket {
}
public String getUsername() {
if (username == null) {
throw new IllegalStateException("No username specified!");
}
return username;
}
@@ -44,7 +51,13 @@ public class ServerLoginSuccess implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (uuid == null) {
throw new IllegalStateException("No UUID specified!");
}
ProtocolUtils.writeString(buf, uuid.toString());
if (username == null) {
throw new IllegalStateException("No username specified!");
}
ProtocolUtils.writeString(buf, username);
}

View File

@@ -5,16 +5,22 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class StatusResponse implements MinecraftPacket {
private String status;
private @Nullable String status;
public String getStatus() {
return status;
public StatusResponse() {}
public StatusResponse(String status) {
this.status = status;
}
public void setStatus(String status) {
this.status = status;
public String getStatus() {
if (status == null) {
throw new IllegalStateException("Status is not specified");
}
return status;
}
@Override
@@ -31,6 +37,9 @@ public class StatusResponse implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (status == null) {
throw new IllegalStateException("Status is not specified");
}
ProtocolUtils.writeString(buf, status);
}

View File

@@ -5,16 +5,20 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9;
public class TabCompleteRequest implements MinecraftPacket {
private String command;
private @Nullable String command;
private boolean assumeCommand;
private boolean hasPosition;
private long position;
public String getCommand() {
if (command == null) {
throw new IllegalStateException("Command is not specified");
}
return command;
}
@@ -70,6 +74,9 @@ public class TabCompleteRequest implements MinecraftPacket {
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (command == null) {
throw new IllegalStateException("Command is not specified");
}
ProtocolUtils.writeString(buf, command);
if (protocolVersion >= MINECRAFT_1_9) {
buf.writeBoolean(assumeCommand);

View File

@@ -5,6 +5,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class TitlePacket implements MinecraftPacket {
public static final int SET_TITLE = 0;
@@ -18,7 +19,7 @@ public class TitlePacket implements MinecraftPacket {
public static final int RESET_OLD = 4;
private int action;
private String component;
private @Nullable String component;
private int fadeIn;
private int stay;
private int fadeOut;
@@ -37,6 +38,9 @@ public class TitlePacket implements MinecraftPacket {
case SET_TITLE:
case SET_SUBTITLE:
case SET_ACTION_BAR:
if (component == null) {
throw new IllegalStateException("No component found for " + action);
}
ProtocolUtils.writeString(buf, component);
break;
case SET_TIMES:
@@ -52,6 +56,9 @@ public class TitlePacket implements MinecraftPacket {
switch (action) {
case SET_TITLE:
case SET_SUBTITLE:
if (component == null) {
throw new IllegalStateException("No component found for " + action);
}
ProtocolUtils.writeString(buf, component);
break;
case SET_TIMES_OLD:
@@ -74,11 +81,11 @@ public class TitlePacket implements MinecraftPacket {
this.action = action;
}
public String getComponent() {
public @Nullable String getComponent() {
return component;
}
public void setComponent(String component) {
public void setComponent(@Nullable String component) {
this.component = component;
}

View File

@@ -11,6 +11,7 @@ import com.velocitypowered.api.scheduler.Scheduler;
import com.velocitypowered.api.scheduler.TaskStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
import java.util.HashSet;
@@ -92,6 +93,7 @@ public class VelocityScheduler implements Scheduler {
public ScheduledTask schedule() {
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
tasksByPlugin.put(plugin, task);
task.schedule();
return task;
}
}
@@ -99,12 +101,19 @@ public class VelocityScheduler implements Scheduler {
private class VelocityTask implements Runnable, ScheduledTask {
private final Object plugin;
private final Runnable runnable;
private ScheduledFuture<?> future;
private volatile Thread currentTaskThread;
private final long delay;
private final long repeat;
private @Nullable ScheduledFuture<?> future;
private volatile @Nullable Thread currentTaskThread;
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
this.plugin = plugin;
this.runnable = runnable;
this.delay = delay;
this.repeat = repeat;
}
public void schedule() {
if (repeat == 0) {
this.future = timerExecutionService.schedule(this, delay, TimeUnit.MILLISECONDS);
} else {
@@ -155,14 +164,10 @@ public class VelocityScheduler implements Scheduler {
try {
runnable.run();
} catch (Exception e) {
// Since we can't catch InterruptedException separately...
if (e instanceof InterruptedException) {
onFinish();
} else {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
}
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
} finally {
currentTaskThread = null;
}
currentTaskThread = null;
});
}

View File

@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.VelocityServer;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
import java.util.Locale;
@@ -13,10 +14,10 @@ import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ServerMap {
private final VelocityServer server;
private final @Nullable VelocityServer server;
private final Map<String, RegisteredServer> servers = new ConcurrentHashMap<>();
public ServerMap(VelocityServer server) {
public ServerMap(@Nullable VelocityServer server) {
this.server = server;
}
@@ -49,7 +50,9 @@ public class ServerMap {
Preconditions.checkNotNull(serverInfo, "serverInfo");
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
RegisteredServer rs = servers.get(lowerName);
Preconditions.checkArgument(rs != null, "Server with name %s is not registered!", serverInfo.getName());
if (rs == null) {
throw new IllegalArgumentException("Server with name " + serverInfo.getName() + " is not registered!");
}
Preconditions.checkArgument(rs.getServerInfo().equals(serverInfo), "Trying to remove server %s with differing information", serverInfo.getName());
Preconditions.checkState(servers.remove(lowerName, rs), "Server with name %s replaced whilst unregistering", serverInfo.getName());
}

View File

@@ -1,5 +1,6 @@
package com.velocitypowered.proxy.server;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
@@ -22,6 +23,7 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
import java.util.Set;
@@ -32,13 +34,13 @@ import java.util.concurrent.TimeUnit;
import static com.velocitypowered.proxy.network.Connections.*;
public class VelocityRegisteredServer implements RegisteredServer {
private final VelocityServer server;
private final @Nullable VelocityServer server;
private final ServerInfo serverInfo;
private final Set<ConnectedPlayer> players = ConcurrentHashMap.newKeySet();
public VelocityRegisteredServer(VelocityServer server, ServerInfo serverInfo) {
public VelocityRegisteredServer(@Nullable VelocityServer server, ServerInfo serverInfo) {
this.server = server;
this.serverInfo = serverInfo;
this.serverInfo = Preconditions.checkNotNull(serverInfo, "serverInfo");
}
@Override
@@ -53,6 +55,9 @@ public class VelocityRegisteredServer implements RegisteredServer {
@Override
public CompletableFuture<ServerPing> ping() {
if (server == null) {
throw new IllegalStateException("No Velocity proxy instance available");
}
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.initializeGenericBootstrap()
.handler(new ChannelInitializer<Channel>() {
@@ -96,8 +101,8 @@ public class VelocityRegisteredServer implements RegisteredServer {
@Override
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
for (ConnectedPlayer player : players) {
if (player.getConnectedServer() != null && player.getConnectedServer().getServerInfo().equals(serverInfo)) {
ServerConnection connection = player.getConnectedServer();
ServerConnection connection = player.getConnectedServer();
if (connection != null && connection.getServerInfo().equals(serverInfo)) {
return connection.sendPluginMessage(identifier, data);
}
}

View File

@@ -9,6 +9,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -70,7 +71,7 @@ public class VelocityTabList implements TabList {
}
@Override
public TabListEntry buildEntry(GameProfile profile, Component displayName, int latency, int gameMode) {
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode) {
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode);
}
@@ -84,27 +85,46 @@ public class VelocityTabList implements TabList {
}
switch (packet.getAction()) {
case PlayerListItem.ADD_PLAYER:
case PlayerListItem.ADD_PLAYER: {
// ensure that name and properties are available
String name = item.getName();
List<GameProfile.Property> properties = item.getProperties();
if (name == null || properties == null) {
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
}
entries.put(item.getUuid(), TabListEntry.builder()
.tabList(this)
.profile(new GameProfile(UuidUtils.toUndashed(uuid), item.getName(), item.getProperties()))
.profile(new GameProfile(UuidUtils.toUndashed(uuid), name, properties))
.displayName(item.getDisplayName())
.latency(item.getLatency())
.gameMode(item.getGameMode())
.build());
break;
}
case PlayerListItem.REMOVE_PLAYER:
entries.remove(uuid);
break;
case PlayerListItem.UPDATE_DISPLAY_NAME:
entries.get(uuid).setDisplayName(item.getDisplayName());
case PlayerListItem.UPDATE_DISPLAY_NAME: {
TabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setDisplayName(item.getDisplayName());
}
break;
case PlayerListItem.UPDATE_LATENCY:
entries.get(uuid).setLatency(item.getLatency());
}
case PlayerListItem.UPDATE_LATENCY: {
TabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setLatency(item.getLatency());
}
break;
case PlayerListItem.UPDATE_GAMEMODE:
entries.get(uuid).setGameMode(item.getGameMode());
}
case PlayerListItem.UPDATE_GAMEMODE: {
TabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setLatency(item.getGameMode());
}
break;
}
}
}
}

View File

@@ -12,11 +12,11 @@ import java.util.Optional;
public class VelocityTabListEntry implements TabListEntry {
private final VelocityTabList tabList;
private final GameProfile profile;
private Component displayName;
private @Nullable Component displayName;
private int latency;
private int gameMode;
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, int latency, int gameMode) {
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, @Nullable Component displayName, int latency, int gameMode) {
this.tabList = tabList;
this.profile = profile;
this.displayName = displayName;

View File

@@ -21,7 +21,7 @@ public class Ratelimiter {
Ratelimiter(long timeoutMs, Ticker ticker) {
if (timeoutMs == 0) {
this.timeoutNanos = timeoutMs;
this.expiringCache = null;
this.expiringCache = CacheBuilder.newBuilder().maximumSize(0).build();
} else {
this.timeoutNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs);
this.expiringCache = CacheBuilder.newBuilder()

View File

@@ -6,6 +6,7 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@@ -57,7 +58,7 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
return identifierMap.containsKey(id);
}
public ChannelIdentifier getFromId(String id) {
public @Nullable ChannelIdentifier getFromId(String id) {
return identifierMap.get(id);
}
}

View File

@@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.*;
class PacketRegistryTest {
private StateRegistry.PacketRegistry setupRegistry() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE);
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND);
registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, false));
return registry;
}
@@ -34,14 +34,14 @@ class PacketRegistryTest {
@Test
void failOnNoMappings() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE);
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND);
assertThrows(IllegalArgumentException.class, () -> registry.register(Handshake.class, Handshake::new));
assertThrows(IllegalArgumentException.class, () -> registry.getVersion(0).getPacketId(new Handshake()));
}
@Test
void registrySuppliesCorrectPacketsByProtocol() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE);
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND);
registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, false),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, false));
assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12).createPacket(0x00).getClass());