diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..f104d0146 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +Thanks for taking the time to submit a contribution to Velocity! Your support +is greatly appreciated. + +In this document, we'll give you some tips on making it more likely your +contribution will be pulled. + +# Setting up a development environment + +This isn't as difficult as you may be led to believe. All you need to do is +clone the Velocity repository in your favorite IDE and have your backend test +servers set up to run behind Velocity. + +# Actually working on the code + +It is strongly recommended that you are familiar with the Minecraft protocol, +proficient with using Java, and have familiarity with the libraries used in +Velocity (particularly [Netty](https://netty.io), [Google Guava](https://github.com/google/guava), +and the [Checker Framework annotations](https://checkerframework.org/)). +While you can certainly work with the Velocity codebase without knowing any +of this, it can be risky to proceed. + +Velocity does not currently obey any one general code style at the moment. +Plans are [in the works](https://github.com/VelocityPowered/Velocity/issues/125) +to define the code style the project will follow. + +# Notes on the build + +To reduce bugs and ensure code quality, we run the following tools on all commits +and pull requests: + +* [Checker Framework](https://checkerframework.org/): an enhancement to Java's type + system that is designed to help catch bugs. Velocity runs the _Nullness Checker_ + and the _Optional Checker_. +* [Checkstyle](http://checkstyle.sourceforge.net/) (not currently in use): ensures + that your code is correctly formatted. \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle index 1c7d350a9..9fcc52975 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -4,6 +4,8 @@ plugins { id 'maven-publish' } +apply from: '../gradle/checkerframework.gradle' + sourceSets { ap { compileClasspath += main.compileClasspath + main.output @@ -17,7 +19,7 @@ dependencies { compile 'com.moandjiezana.toml:toml4j:0.7.2' compile "org.slf4j:slf4j-api:${slf4jVersion}" compile 'com.google.inject:guice:4.2.0' - compile 'org.checkerframework:checker-qual:2.5.4' + compile "org.checkerframework:checker-qual:${checkerFrameworkVersion}" testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" diff --git a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java index 925074cc2..d35b51674 100644 --- a/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/SerializedPluginDescription.java @@ -2,6 +2,7 @@ package com.velocitypowered.api.plugin.ap; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.velocitypowered.api.plugin.Plugin; import org.checkerframework.checker.nullness.qual.Nullable; @@ -29,8 +30,8 @@ public class SerializedPluginDescription { this.version = Strings.emptyToNull(version); this.description = Strings.emptyToNull(description); this.url = Strings.emptyToNull(url); - this.authors = authors == null || authors.isEmpty() ? null : authors; - this.dependencies = dependencies == null || dependencies.isEmpty() ? null : dependencies; + this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors; + this.dependencies = dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies; this.main = Preconditions.checkNotNull(main, "main"); } @@ -63,12 +64,12 @@ public class SerializedPluginDescription { return url; } - public @Nullable List getAuthors() { - return authors; + public List getAuthors() { + return authors == null ? ImmutableList.of() : authors; } - public @Nullable List getDependencies() { - return dependencies; + public List getDependencies() { + return dependencies == null ? ImmutableList.of() : dependencies; } public String getMain() { diff --git a/api/src/main/java/com/velocitypowered/api/command/Command.java b/api/src/main/java/com/velocitypowered/api/command/Command.java index ecbbcb379..6255b3bbc 100644 --- a/api/src/main/java/com/velocitypowered/api/command/Command.java +++ b/api/src/main/java/com/velocitypowered/api/command/Command.java @@ -15,7 +15,7 @@ public interface Command { * @param source the source of this command * @param args the arguments for this command */ - void execute(@NonNull CommandSource source, @NonNull String[] args); + void execute(CommandSource source, String @NonNull [] args); /** * Provides tab complete suggestions for a command for a specified {@link CommandSource}. @@ -23,7 +23,7 @@ public interface Command { * @param currentArgs the current, partial arguments for this command * @return tab complete suggestions */ - default List suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) { + default List suggest(CommandSource source, String @NonNull [] currentArgs) { return ImmutableList.of(); } @@ -38,7 +38,7 @@ public interface Command { * @param args the arguments for this command * @return whether the source has permission */ - default boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) { + default boolean hasPermission(CommandSource source, String @NonNull [] args) { return true; } } diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index 040c3d295..28d5bb0fb 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -1,7 +1,5 @@ package com.velocitypowered.api.command; -import org.checkerframework.checker.nullness.qual.NonNull; - /** * Represents an interface to register a command executor with the proxy. */ @@ -11,13 +9,13 @@ public interface CommandManager { * @param command the command to register * @param aliases the alias to use */ - void register(@NonNull Command command, String... aliases); + void register(Command command, String... aliases); /** * Unregisters a command. * @param alias the command alias to unregister */ - void unregister(@NonNull String alias); + void unregister(String alias); /** * Attempts to execute a command from the specified {@code cmdLine}. @@ -25,5 +23,5 @@ public interface CommandManager { * @param cmdLine the command to run * @return true if the command was found and executed, false if it was not */ - boolean execute(@NonNull CommandSource source, @NonNull String cmdLine); + boolean execute(CommandSource source, String cmdLine); } diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandSource.java b/api/src/main/java/com/velocitypowered/api/command/CommandSource.java index 70c11318e..20bcfde02 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandSource.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandSource.java @@ -2,7 +2,6 @@ package com.velocitypowered.api.command; import com.velocitypowered.api.permission.PermissionSubject; import net.kyori.text.Component; -import org.checkerframework.checker.nullness.qual.NonNull; /** * Represents something that can be used to run a {@link Command}. @@ -12,5 +11,5 @@ public interface CommandSource extends PermissionSubject { * Sends the specified {@code component} to the invoker. * @param component the text component to send */ - void sendMessage(@NonNull Component component); + void sendMessage(Component component); } diff --git a/api/src/main/java/com/velocitypowered/api/event/EventHandler.java b/api/src/main/java/com/velocitypowered/api/event/EventHandler.java index 146239cf7..4acf0eab2 100644 --- a/api/src/main/java/com/velocitypowered/api/event/EventHandler.java +++ b/api/src/main/java/com/velocitypowered/api/event/EventHandler.java @@ -1,12 +1,10 @@ package com.velocitypowered.api.event; -import org.checkerframework.checker.nullness.qual.NonNull; - /** * Represents an interface to perform direct dispatch of an event. This makes integration easier to achieve with platforms * such as RxJava. */ @FunctionalInterface public interface EventHandler { - void execute(@NonNull E event); + void execute(E event); } diff --git a/api/src/main/java/com/velocitypowered/api/event/EventManager.java b/api/src/main/java/com/velocitypowered/api/event/EventManager.java index 28b197e55..2a52239ad 100644 --- a/api/src/main/java/com/velocitypowered/api/event/EventManager.java +++ b/api/src/main/java/com/velocitypowered/api/event/EventManager.java @@ -1,7 +1,5 @@ package com.velocitypowered.api.event; -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.concurrent.CompletableFuture; /** @@ -13,7 +11,7 @@ public interface EventManager { * @param plugin the plugin to associate with the listener * @param listener the listener to register */ - void register(@NonNull Object plugin, @NonNull Object listener); + void register(Object plugin, Object listener); /** * Requests that the specified {@code handler} listen for events and associate it with the {@code plugin}. @@ -22,7 +20,7 @@ public interface EventManager { * @param handler the handler to register * @param the event type to handle */ - default void register(@NonNull Object plugin, @NonNull Class eventClass, @NonNull EventHandler handler) { + default void register(Object plugin, Class eventClass, EventHandler handler) { register(plugin, eventClass, PostOrder.NORMAL, handler); } @@ -34,7 +32,7 @@ public interface EventManager { * @param handler the handler to register * @param the event type to handle */ - void register(@NonNull Object plugin, @NonNull Class eventClass, @NonNull PostOrder postOrder, @NonNull EventHandler handler); + void register(Object plugin, Class eventClass, PostOrder postOrder, EventHandler handler); /** * Fires the specified event to the event bus asynchronously. This allows Velocity to continue servicing connections @@ -42,13 +40,13 @@ public interface EventManager { * @param event the event to fire * @return a {@link CompletableFuture} representing the posted event */ - @NonNull CompletableFuture fire(@NonNull E event); + CompletableFuture fire(E event); /** * Posts the specified event to the event bus and discards the result. * @param event the event to fire */ - default void fireAndForget(@NonNull Object event) { + default void fireAndForget(Object event) { fire(event); } @@ -56,14 +54,14 @@ public interface EventManager { * Unregisters all listeners for the specified {@code plugin}. * @param plugin the plugin to deregister listeners for */ - void unregisterListeners(@NonNull Object plugin); + void unregisterListeners(Object plugin); /** * Unregisters a specific listener for a specific plugin. * @param plugin the plugin associated with the listener * @param listener the listener to deregister */ - void unregisterListener(@NonNull Object plugin, @NonNull Object listener); + void unregisterListener(Object plugin, Object listener); /** * Unregisters a specific event handler for a specific plugin. @@ -71,5 +69,5 @@ public interface EventManager { * @param handler the handler to register * @param the event type to handle */ - void unregister(@NonNull Object plugin, @NonNull EventHandler handler); + void unregister(Object plugin, EventHandler handler); } diff --git a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java index 81b52938e..ade22cbd9 100644 --- a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java @@ -22,7 +22,7 @@ public interface ResultedEvent { * Sets the result of this event. The result must be non-null. * @param result the new result */ - void setResult(@NonNull R result); + void setResult(R result); /** * Represents a result for an event. @@ -106,7 +106,7 @@ public interface ResultedEvent { return ALLOWED; } - public static ComponentResult denied(@NonNull Component reason) { + public static ComponentResult denied(Component reason) { Preconditions.checkNotNull(reason, "reason"); return new ComponentResult(false, reason); } diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java index a590a83ad..e22db3d69 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java @@ -2,15 +2,14 @@ package com.velocitypowered.api.event.connection; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.InboundConnection; -import org.checkerframework.checker.nullness.qual.NonNull; /** * This event is fired when a handshake is established between a client and Velocity. */ public final class ConnectionHandshakeEvent { - private final @NonNull InboundConnection connection; + private final InboundConnection connection; - public ConnectionHandshakeEvent(@NonNull InboundConnection connection) { + public ConnectionHandshakeEvent(InboundConnection connection) { this.connection = Preconditions.checkNotNull(connection, "connection"); } diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/DisconnectEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/DisconnectEvent.java index 5a861dd51..d8cef27c7 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/DisconnectEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/DisconnectEvent.java @@ -2,16 +2,15 @@ package com.velocitypowered.api.event.connection; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.Player; -import org.checkerframework.checker.nullness.qual.NonNull; /** * This event is fired when a player disconnects from the proxy. Operations on the provided player, aside from basic * data retrieval operations, may behave in undefined ways. */ public final class DisconnectEvent { - private @NonNull final Player player; + private final Player player; - public DisconnectEvent(@NonNull Player player) { + public DisconnectEvent(Player player) { this.player = Preconditions.checkNotNull(player, "player"); } diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/LoginEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/LoginEvent.java index fa19da77e..cbb483f7c 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/LoginEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/LoginEvent.java @@ -3,7 +3,6 @@ package com.velocitypowered.api.event.connection; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.Player; -import org.checkerframework.checker.nullness.qual.NonNull; /** * This event is fired once the player has been authenticated but before they connect to a server on the proxy. @@ -12,7 +11,7 @@ public final class LoginEvent implements ResultedEvent reason; + private final @Nullable Component reason; private PreLoginComponentResult(Result result, @Nullable Component reason) { this.result = result; - this.reason = Optional.ofNullable(reason); + this.reason = reason; } @Override @@ -75,7 +74,7 @@ public final class PreLoginEvent implements ResultedEvent getReason() { - return reason; + return Optional.ofNullable(reason); } public boolean isOnlineModeAllowed() { @@ -88,19 +87,16 @@ public final class PreLoginEvent implements ResultedEvent getServer() { @@ -75,10 +72,10 @@ public final class ServerPreConnectEvent implements ResultedEventtrue or false, respectively. */ - public static @NonNull Tristate fromBoolean(boolean val) { + public static Tristate fromBoolean(boolean val) { return val ? TRUE : FALSE; } @@ -51,7 +51,7 @@ public enum Tristate { * @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value * is null, true or false, respectively. */ - public static @NonNull Tristate fromNullableBoolean(@Nullable Boolean val) { + public static Tristate fromNullableBoolean(@Nullable Boolean val) { return val == null ? UNDEFINED : val ? TRUE : FALSE; } diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java index 6ff5a6fee..b9c1cf2ad 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java @@ -1,7 +1,5 @@ package com.velocitypowered.api.plugin; -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.Optional; /** @@ -13,7 +11,7 @@ public interface PluginContainer { * * @return the plugin's description */ - @NonNull PluginDescription getDescription(); + PluginDescription getDescription(); /** * Returns the created plugin if it is available. diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java index ba015d0a6..744487d19 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java @@ -1,16 +1,12 @@ package com.velocitypowered.api.plugin; -import org.checkerframework.checker.nullness.qual.NonNull; - import java.nio.file.Path; import java.util.Collection; import java.util.Optional; -import java.util.logging.Logger; /** - * The class that manages plugins. This manager can retrieve - * {@link PluginContainer}s from {@link Plugin} instances, getting - * {@link Logger}s, etc. + * Manages plugins loaded on the proxy. This manager can retrieve {@link PluginContainer}s from plugin instances + * and inject arbitrary JAR files into the plugin classpath with {@link #addToClasspath(Object, Path)}. */ public interface PluginManager { /** @@ -19,7 +15,7 @@ public interface PluginManager { * @param instance the instance * @return the container */ - @NonNull Optional fromInstance(@NonNull Object instance); + Optional fromInstance(Object instance); /** * Retrieves a {@link PluginContainer} based on its ID. @@ -27,22 +23,22 @@ public interface PluginManager { * @param id the plugin ID * @return the plugin, if available */ - @NonNull Optional getPlugin(@NonNull String id); + Optional getPlugin(String id); /** * Gets a {@link Collection} of all {@link PluginContainer}s. * * @return the plugins */ - @NonNull Collection getPlugins(); + Collection getPlugins(); /** * Checks if a plugin is loaded based on its ID. * - * @param id the id of the {@link Plugin} + * @param id the id of the plugin * @return {@code true} if loaded */ - boolean isLoaded(@NonNull String id); + boolean isLoaded(String id); /** * Adds the specified {@code path} to the plugin classpath. @@ -51,5 +47,5 @@ public interface PluginManager { * @param path the path to the JAR you want to inject into the classpath * @throws UnsupportedOperationException if the operation is not applicable to this plugin */ - void addToClasspath(@NonNull Object plugin, @NonNull Path path); + void addToClasspath(Object plugin, Path path); } diff --git a/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java b/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java index 2229d6ed3..1238af8fa 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java @@ -1,6 +1,7 @@ package com.velocitypowered.api.plugin.meta; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Objects; import java.util.Optional; @@ -13,7 +14,8 @@ import static com.google.common.base.Strings.emptyToNull; */ public final class PluginDependency { private final String id; - @Nullable private final String version; + @Nullable + private final String version; private final boolean optional; @@ -53,7 +55,7 @@ public final class PluginDependency { } @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; PluginDependency that = (PluginDependency) o; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 844200ea5..d6ba45da4 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -13,7 +13,6 @@ import com.velocitypowered.api.util.title.Title; import java.util.List; import net.kyori.text.Component; -import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Optional; import java.util.UUID; @@ -62,7 +61,7 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage * Sends a chat message to the player's client. * @param component the chat message to send */ - default void sendMessage(@NonNull Component component) { + default void sendMessage(Component component) { sendMessage(component, MessagePosition.CHAT); } @@ -71,14 +70,14 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage * @param component the chat message to send * @param position the position for the message */ - void sendMessage(@NonNull Component component, @NonNull MessagePosition position); + void sendMessage(Component component, MessagePosition position); /** * Creates a new connection request so that the player can connect to another server. * @param server the server to connect to * @return a new connection request */ - ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server); + ConnectionRequestBuilder createConnectionRequest(RegisteredServer server); /** * Gets the player's profile properties. diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java index 9c6a2daf6..698c7466b 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -9,6 +9,7 @@ import com.velocitypowered.api.proxy.messages.ChannelRegistrar; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.scheduler.Scheduler; +import com.velocitypowered.api.util.ProxyVersion; import net.kyori.text.Component; import java.net.InetSocketAddress; @@ -131,4 +132,10 @@ public interface ProxyServer { * @return the proxy config * */ ProxyConfig getConfiguration(); + + /** + * Returns the version of the proxy. + * @return the proxy version + */ + ProxyVersion getVersion(); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java index bc91b1e24..7ccc00ae0 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/LegacyChannelIdentifier.java @@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy.messages; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Objects; @@ -27,7 +28,7 @@ public final class LegacyChannelIdentifier implements ChannelIdentifier { } @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; LegacyChannelIdentifier that = (LegacyChannelIdentifier) o; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java index 10d1deef6..c6899e0d0 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/MinecraftChannelIdentifier.java @@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy.messages; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Objects; import java.util.regex.Pattern; @@ -54,11 +55,11 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier { @Override public String toString() { - return getId() + " (modern)"; + return namespace + ":" + name + " (modern)"; } @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; MinecraftChannelIdentifier that = (MinecraftChannelIdentifier) o; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java index fcf49fe66..a517ba2de 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java @@ -3,6 +3,7 @@ package com.velocitypowered.api.proxy.player; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.util.GameProfile; import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collection; import java.util.Optional; @@ -47,5 +48,5 @@ public interface TabList { // Necessary because the TabListEntry implementation isn't in the api @Deprecated - TabListEntry buildEntry(GameProfile profile, Component displayName, int latency, int gameMode); + TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java index c0da1a5c2..dfabb458e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java @@ -1,9 +1,7 @@ package com.velocitypowered.api.proxy.player; -import com.google.common.base.Preconditions; import com.velocitypowered.api.util.GameProfile; import net.kyori.text.Component; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Optional; @@ -16,7 +14,7 @@ public interface TabListEntry { * Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}. * @return parent {@link TabList} */ - @NonNull TabList getTabList(); + TabList getTabList(); /** * Returns the {@link GameProfile} of the entry, which uniquely identifies the entry @@ -24,14 +22,14 @@ public interface TabListEntry { * as the player head in the tab list. * @return {@link GameProfile} of the entry */ - @NonNull GameProfile getProfile(); + GameProfile getProfile(); /** * Returns {@link Optional} text {@link Component}, which if present is the text displayed for * {@code this} entry in the {@link TabList}, otherwise {@link GameProfile#getName()} is shown. * @return {@link Optional} text {@link Component} of name displayed in the tab list */ - @NonNull Optional getDisplayName(); + Optional getDisplayName(); /** * Sets the text {@link Component} to be displayed for {@code this} {@link TabListEntry}. @@ -39,7 +37,7 @@ public interface TabListEntry { * @param displayName to show in the {@link TabList} for {@code this} entry * @return {@code this}, for chaining */ - @NonNull TabListEntry setDisplayName(@Nullable Component displayName); + TabListEntry setDisplayName(@Nullable Component displayName); /** * Returns the latency for {@code this} entry. @@ -63,7 +61,7 @@ public interface TabListEntry { * @param latency to changed to * @return {@code this}, for chaining */ - @NonNull TabListEntry setLatency(int latency); + TabListEntry setLatency(int latency); /** * Gets the game mode {@code this} entry has been set to. @@ -99,9 +97,9 @@ public interface TabListEntry { * @see TabListEntry */ class Builder { - private TabList tabList; - private GameProfile profile; - private Component displayName; + private @Nullable TabList tabList; + private @Nullable GameProfile profile; + private @Nullable Component displayName; private int latency = 0; private int gameMode = 0; @@ -167,9 +165,12 @@ public interface TabListEntry { * @return the constructed {@link TabListEntry} */ public TabListEntry build() { - Preconditions.checkState(tabList != null, "The Tablist must be set when building a TabListEntry"); - Preconditions.checkState(profile != null, "The GameProfile must be set when building a TabListEntry"); - + if (tabList == null) { + throw new IllegalStateException("The Tablist must be set when building a TabListEntry"); + } + if (profile == null) { + throw new IllegalStateException("The GameProfile must be set when building a TabListEntry"); + } return tabList.buildEntry(profile, displayName, latency, gameMode); } } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java index 3dffa7111..ea2d55ec3 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerInfo.java @@ -1,7 +1,7 @@ package com.velocitypowered.api.proxy.server; import com.google.common.base.Preconditions; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import java.net.InetSocketAddress; import java.util.Objects; @@ -10,24 +10,24 @@ import java.util.Objects; * ServerInfo represents a server that a player can connect to. This object is immutable and safe for concurrent access. */ public final class ServerInfo { - private final @NonNull String name; - private final @NonNull InetSocketAddress address; + private final String name; + private final InetSocketAddress address; /** * Creates a new ServerInfo object. * @param name the name for the server * @param address the address of the server to connect to */ - public ServerInfo(@NonNull String name, @NonNull InetSocketAddress address) { + public ServerInfo(String name, InetSocketAddress address) { this.name = Preconditions.checkNotNull(name, "name"); this.address = Preconditions.checkNotNull(address, "address"); } - public final @NonNull String getName() { + public final String getName() { return name; } - public final @NonNull InetSocketAddress getAddress() { + public final InetSocketAddress getAddress() { return address; } @@ -40,7 +40,7 @@ public final class ServerInfo { } @Override - public final boolean equals(Object o) { + public final boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ServerInfo that = (ServerInfo) o; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index bcd964572..c47a520c9 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -14,7 +14,7 @@ import java.util.*; */ public final class ServerPing { private final Version version; - private final Players players; + private final @Nullable Players players; private final Component description; private final @Nullable Favicon favicon; private final @Nullable ModInfo modinfo; @@ -89,14 +89,14 @@ public final class ServerPing { * A builder for {@link ServerPing} objects. */ public static final class Builder { - private Version version; + private Version version = new Version(0, "Unknown"); private int onlinePlayers; private int maximumPlayers; private final List samplePlayers = new ArrayList<>(); - private String modType; + private String modType = "FML"; private final List mods = new ArrayList<>(); - private Component description; - private Favicon favicon; + private @Nullable Component description; + private @Nullable Favicon favicon; private boolean nullOutPlayers; private boolean nullOutModinfo; @@ -165,6 +165,12 @@ public final class ServerPing { } public ServerPing build() { + if (this.version == null) { + throw new IllegalStateException("version not specified"); + } + if (this.description == null) { + throw new IllegalStateException("no server description supplied"); + } return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon, nullOutModinfo ? null : new ModInfo(modType, mods)); } @@ -185,12 +191,12 @@ public final class ServerPing { return samplePlayers; } - public Component getDescription() { - return description; + public Optional getDescription() { + return Optional.ofNullable(description); } - public Favicon getFavicon() { - return favicon; + public Optional getFavicon() { + return Optional.ofNullable(favicon); } public String getModType() { diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java index 2854a664c..2e8bfec06 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java @@ -1,5 +1,8 @@ package com.velocitypowered.api.scheduler; +import org.checkerframework.common.value.qual.IntRange; +import org.checkerframework.common.value.qual.IntRangeFromNonNegative; + import java.util.concurrent.TimeUnit; /** @@ -24,7 +27,7 @@ public interface Scheduler { * @param unit the unit of time for {@code time} * @return this builder, for chaining */ - TaskBuilder delay(long time, TimeUnit unit); + TaskBuilder delay(@IntRange(from = 0) long time, TimeUnit unit); /** * Specifies that the task should continue running after waiting for the specified amount, until it is cancelled. @@ -32,7 +35,7 @@ public interface Scheduler { * @param unit the unit of time for {@code time} * @return this builder, for chaining */ - TaskBuilder repeat(long time, TimeUnit unit); + TaskBuilder repeat(@IntRange(from = 0) long time, TimeUnit unit); /** * Clears the delay on this task. diff --git a/api/src/main/java/com/velocitypowered/api/util/Favicon.java b/api/src/main/java/com/velocitypowered/api/util/Favicon.java index 8fc1b2cb3..7ce0a4a99 100644 --- a/api/src/main/java/com/velocitypowered/api/util/Favicon.java +++ b/api/src/main/java/com/velocitypowered/api/util/Favicon.java @@ -1,7 +1,7 @@ package com.velocitypowered.api.util; import com.google.common.base.Preconditions; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -25,7 +25,7 @@ public final class Favicon { * of functions. * @param base64Url the url for use with this favicon */ - public Favicon(@NonNull String base64Url) { + public Favicon(String base64Url) { this.base64Url = Preconditions.checkNotNull(base64Url, "base64Url"); } @@ -38,7 +38,7 @@ public final class Favicon { } @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; Favicon favicon = (Favicon) o; @@ -62,7 +62,7 @@ public final class Favicon { * @param image the image to use for the favicon * @return the created {@link Favicon} instance */ - public static Favicon create(@NonNull BufferedImage image) { + public static Favicon create(BufferedImage image) { Preconditions.checkNotNull(image, "image"); Preconditions.checkArgument(image.getWidth() == 64 && image.getHeight() == 64, "Image does not have" + " 64x64 dimensions (found %sx%s)", image.getWidth(), image.getHeight()); @@ -81,7 +81,7 @@ public final class Favicon { * @return the created {@link Favicon} instance * @throws IOException if the file could not be read from the path */ - public static Favicon create(@NonNull Path path) throws IOException { + public static Favicon create(Path path) throws IOException { try (InputStream stream = Files.newInputStream(path)) { return create(ImageIO.read(stream)); } diff --git a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java index 91300d1ce..93968b026 100644 --- a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java +++ b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java @@ -2,7 +2,6 @@ package com.velocitypowered.api.util; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import org.checkerframework.checker.nullness.qual.NonNull; import java.util.List; import java.util.UUID; @@ -15,7 +14,7 @@ public final class GameProfile { private final String name; private final List properties; - public GameProfile(@NonNull String id, @NonNull String name, @NonNull List properties) { + public GameProfile(String id, String name, List properties) { this.id = Preconditions.checkNotNull(id, "id"); this.name = Preconditions.checkNotNull(name, "name"); this.properties = ImmutableList.copyOf(properties); @@ -42,7 +41,7 @@ public final class GameProfile { * @param username the username to use * @return the new offline-mode game profile */ - public static GameProfile forOfflinePlayer(@NonNull String username) { + public static GameProfile forOfflinePlayer(String username) { Preconditions.checkNotNull(username, "username"); String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username)); return new GameProfile(id, username, ImmutableList.of()); @@ -62,7 +61,7 @@ public final class GameProfile { private final String value; private final String signature; - public Property(@NonNull String name, @NonNull String value, @NonNull String signature) { + public Property(String name, String value, String signature) { this.name = Preconditions.checkNotNull(name, "name"); this.value = Preconditions.checkNotNull(value, "value"); this.signature = Preconditions.checkNotNull(signature, "signature"); diff --git a/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java b/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java new file mode 100644 index 000000000..248b83512 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java @@ -0,0 +1,57 @@ +package com.velocitypowered.api.util; + +import com.google.common.base.Preconditions; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Objects; + +/** + * Provides a version object for the proxy. + */ +public final class ProxyVersion { + private final String name; + private final String vendor; + private final String version; + + public ProxyVersion(String name, String vendor, String version) { + this.name = Preconditions.checkNotNull(name, "name"); + this.vendor = Preconditions.checkNotNull(vendor, "vendor"); + this.version = Preconditions.checkNotNull(version, "version"); + } + + public String getName() { + return name; + } + + public String getVendor() { + return vendor; + } + + public String getVersion() { + return version; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProxyVersion that = (ProxyVersion) o; + return Objects.equals(name, that.name) && + Objects.equals(vendor, that.vendor) && + Objects.equals(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(name, vendor, version); + } + + @Override + public String toString() { + return "ProxyVersion{" + + "name='" + name + '\'' + + ", vendor='" + vendor + '\'' + + ", version='" + version + '\'' + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java b/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java index 488ae7449..920384612 100644 --- a/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java +++ b/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java @@ -2,7 +2,6 @@ package com.velocitypowered.api.util; import com.google.common.base.Preconditions; import com.google.common.base.Strings; -import org.checkerframework.checker.nullness.qual.NonNull; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -21,7 +20,7 @@ public final class UuidUtils { * @param string the string to convert * @return the UUID object */ - public static @NonNull UUID fromUndashed(final @NonNull String string) { + public static UUID fromUndashed(final String string) { Objects.requireNonNull(string, "string"); Preconditions.checkArgument(string.length() == 32, "Length is incorrect"); return new UUID( @@ -35,7 +34,7 @@ public final class UuidUtils { * @param uuid the UUID to convert * @return the undashed UUID */ - public static @NonNull String toUndashed(final @NonNull UUID uuid) { + public static String toUndashed(final UUID uuid) { Preconditions.checkNotNull(uuid, "uuid"); return Strings.padStart(Long.toHexString(uuid.getMostSignificantBits()), 16, '0') + Strings.padStart(Long.toHexString(uuid.getLeastSignificantBits()), 16, '0'); @@ -46,7 +45,7 @@ public final class UuidUtils { * @param username the username to use * @return the offline mode UUID */ - public static @NonNull UUID generateOfflinePlayerUuid(@NonNull String username) { + public static UUID generateOfflinePlayerUuid(String username) { return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); } } diff --git a/api/src/main/java/com/velocitypowered/api/util/title/TextTitle.java b/api/src/main/java/com/velocitypowered/api/util/title/TextTitle.java index 6a3ffd0e5..0f1fca552 100644 --- a/api/src/main/java/com/velocitypowered/api/util/title/TextTitle.java +++ b/api/src/main/java/com/velocitypowered/api/util/title/TextTitle.java @@ -11,8 +11,8 @@ import java.util.Optional; * Represents a "full" title, including all components. This class is immutable. */ public final class TextTitle implements Title { - private final Component title; - private final Component subtitle; + private final @Nullable Component title; + private final @Nullable Component subtitle; private final int stay; private final int fadeIn; private final int fadeOut; @@ -94,7 +94,7 @@ public final class TextTitle implements Title { } @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; TextTitle textTitle = (TextTitle) o; @@ -193,12 +193,12 @@ public final class TextTitle implements Title { return this; } - public Component getTitle() { - return title; + public Optional getTitle() { + return Optional.ofNullable(title); } - public Component getSubtitle() { - return subtitle; + public Optional getSubtitle() { + return Optional.ofNullable(subtitle); } public int getStay() { diff --git a/build.gradle b/build.gradle index 821bf56d4..ec2d00c2b 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ allprojects { log4jVersion = '2.11.1' nettyVersion = '4.1.30.Final' guavaVersion = '25.1-jre' + checkerFrameworkVersion = '2.5.6' getCurrentBranchName = { new ByteArrayOutputStream().withStream { os -> diff --git a/gradle/checkerframework.gradle b/gradle/checkerframework.gradle new file mode 100644 index 000000000..dd225b272 --- /dev/null +++ b/gradle/checkerframework.gradle @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////// +/// Checker Framework pluggable type-checking +/// + +repositories { + mavenCentral() +} + +configurations { + checkerFrameworkCheckerJar { + description = 'the Checker Framework, including the Type Annotations compiler' + } + + checkerFrameworkAnnotatedJDK { + description = 'a copy of JDK classes with Checker Framework type qualifers inserted' + } +} + +// By default, use Checker Framework from Maven Central. +// Pass -PcfLocal to use a locally-built version of the Checker Framework. +dependencies { + if (!rootProject.hasProperty('cfLocal')) { + checkerFrameworkAnnotatedJDK "org.checkerframework:jdk8:${checkerFrameworkVersion}" + checkerFrameworkCheckerJar "org.checkerframework:checker:${checkerFrameworkVersion}" + implementation "org.checkerframework:checker-qual:${checkerFrameworkVersion}" + } else if (System.getenv("CHECKERFRAMEWORK") == null) { + throw new GradleException("Environment variable CHECKERFRAMEWORK is not set") + } else if (! file(System.getenv("CHECKERFRAMEWORK")).exists()) { + throw new GradleException("Environment variable CHECKERFRAMEWORK is set to non-existent directory " + System.getenv("CHECKERFRAMEWORK")); + } else { + ext.checkerframeworkdist = "$System.env.CHECKERFRAMEWORK/checker/dist" + checkerFrameworkAnnotatedJDK fileTree(dir: "${ext.checkerframeworkdist}", include: "jdk8.jar") + checkerFrameworkCheckerJar fileTree(dir: "${ext.checkerframeworkdist}", include: 'checker.jar') + implementation fileTree(dir: "${ext.checkerframeworkdist}", include: 'checker-qual.jar') + } +} + +// // To type-check all projects. +// allprojects { +// tasks.withType(JavaCompile).all { JavaCompile compile -> +// compile.doFirst { +// compile.options.compilerArgs = [ +// '-processor', 'org.checkerframework.checker.formatter.FormatterChecker,org.checkerframework.checker.index.IndexChecker,org.checkerframework.checker.lock.LockChecker,org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.signature.SignatureChecker', +// '-Xmaxerrs', '10000', +// '-Awarns', // -Awarns turns Checker Framework errors into warnings +// '-AcheckPurityAnnotations', +// '-processorpath', "${configurations.checkerFrameworkCheckerJar.asPath}", +// "-Xbootclasspath/p:${configurations.checkerFrameworkAnnotatedJDK.asPath}", +// "-Astubs=$System.env.CHECKERFRAMEWORK/checker/resources/javadoc.astub" // TODO: does not work when downloading from Maven Central +// ] +// } +// } +// } + +// To typecheck only the current project's main source set (in a multi-project +// build), use this instead: +compileJava { + doFirst { + options.compilerArgs = [ + '-processor', 'org.checkerframework.checker.nullness.NullnessChecker', + '-processor', 'org.checkerframework.checker.optional.OptionalChecker', + '-Xmaxerrs', '10000', + '-Xmaxwarns', '10000', + // '-Awarns', // -Awarns turns Checker Framework errors into warnings + //'-AcheckPurityAnnotations', // Disabled for Velocity, wish we could do better + '-processorpath', "${configurations.checkerFrameworkCheckerJar.asPath}", + "-Xbootclasspath/p:${configurations.checkerFrameworkAnnotatedJDK.asPath}" + ] + } +} diff --git a/native/build.gradle b/native/build.gradle index 089fba5e9..364ef5543 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -2,9 +2,13 @@ plugins { id 'java' } +apply from: '../gradle/checkerframework.gradle' + dependencies { compile "com.google.guava:guava:${guavaVersion}" compile "io.netty:netty-buffer:${nettyVersion}" + compile "org.checkerframework:checker-qual:${checkerFrameworkVersion}" + testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" } \ No newline at end of file diff --git a/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java b/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java index 4b84516da..00bda3454 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java +++ b/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java @@ -1,88 +1,64 @@ package com.velocitypowered.natives.util; -import com.google.common.collect.ImmutableList; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.List; import java.util.function.BooleanSupplier; import java.util.function.Supplier; -public class NativeCodeLoader implements Supplier { - private final List> variants; - private volatile Variant selected; +public final class NativeCodeLoader implements Supplier { + private final Variant selected; - public NativeCodeLoader(List> variants) { - this.variants = ImmutableList.copyOf(variants); + NativeCodeLoader(List> variants) { + this.selected = getVariant(variants); } @Override public T get() { - return tryLoad().object; + return selected.object; } - private Variant tryLoad() { - if (selected != null) { - return selected; - } - - synchronized (this) { - if (selected != null) { - return selected; + private static Variant getVariant(List> variants) { + for (Variant variant : variants) { + T got = variant.get(); + if (got == null) { + continue; } - - for (Variant variant : variants) { - T got = variant.get(); - if (got == null) { - continue; - } - selected = variant; - return selected; - } - throw new IllegalArgumentException("Can't find any suitable variants"); + return variant; } + throw new IllegalArgumentException("Can't find any suitable variants"); } public String getLoadedVariant() { - return tryLoad().name; + return selected.name; } static class Variant { - private volatile boolean available; + private Status status; private final Runnable setup; private final String name; private final T object; - private volatile boolean hasBeenSetup = false; - Variant(BooleanSupplier available, Runnable setup, String name, T object) { - this.available = available.getAsBoolean(); + Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) { + this.status = possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE; this.setup = setup; this.name = name; this.object = object; } - public T get() { - if (!available) { + public @Nullable T get() { + if (status == Status.NOT_AVAILABLE || status == Status.SETUP_FAILURE) { return null; } // Make sure setup happens only once - if (!hasBeenSetup) { - synchronized (this) { - // We change availability if need be below, may as well check it again here for sanity. - if (!available) { - return null; - } - - // Okay, now try the setup if we haven't done so yet. - if (!hasBeenSetup) { - try { - setup.run(); - hasBeenSetup = true; - return object; - } catch (Exception e) { - available = false; - return null; - } - } + if (status == Status.POSSIBLY_AVAILABLE) { + try { + setup.run(); + status = Status.SETUP; + } catch (Exception e) { + status = Status.SETUP_FAILURE; + return null; } } @@ -90,9 +66,16 @@ public class NativeCodeLoader implements Supplier { } } - static final BooleanSupplier MACOS = () -> System.getProperty("os.name").equalsIgnoreCase("Mac OS X") && + private enum Status { + NOT_AVAILABLE, + POSSIBLY_AVAILABLE, + SETUP, + SETUP_FAILURE + } + + static final BooleanSupplier MACOS = () -> System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") && System.getProperty("os.arch").equals("x86_64"); - static final BooleanSupplier LINUX = () -> System.getProperties().getProperty("os.name").equalsIgnoreCase("Linux") && + static final BooleanSupplier LINUX = () -> System.getProperties().getProperty("os.name", "").equalsIgnoreCase("Linux") && System.getProperty("os.arch").equals("amd64"); static final BooleanSupplier ALWAYS = () -> true; } diff --git a/native/src/main/java/com/velocitypowered/natives/util/Natives.java b/native/src/main/java/com/velocitypowered/natives/util/Natives.java index 5c22ae752..e50a70ca2 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/Natives.java +++ b/native/src/main/java/com/velocitypowered/natives/util/Natives.java @@ -8,6 +8,7 @@ import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.VelocityCipherFactory; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -21,7 +22,12 @@ public class Natives { return () -> { try { Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.'))); - Files.copy(Natives.class.getResourceAsStream(path), tempFile, StandardCopyOption.REPLACE_EXISTING); + InputStream nativeLib = Natives.class.getResourceAsStream(path); + if (nativeLib == null) { + throw new IllegalStateException("Native library " + path + " not found."); + } + + Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING); Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { Files.deleteIfExists(tempFile); diff --git a/proxy/build.gradle b/proxy/build.gradle index 0eb109cfe..0cfcf1de8 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -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'] } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index f2f05f49f..8e09f0665 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -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(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index f075a2c72..334d9af4d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -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 connectionsByUuid = new ConcurrentHashMap<>(); private final Map 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(), ""); + implVendor = MoreObjects.firstNonNull(pkg.getImplementationVendor(), "Velocity Contributors"); + } else { + implName = "Velocity"; + implVersion = ""; + 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 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 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(); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java index b466ae5f7..345c62e68 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -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 suggest(CommandSource source, String[] currentArgs) { + public List 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; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java index 210a9a026..3b4168dbb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java @@ -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(); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java index cabdac9af..acbe9f0cc 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -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 subcommands = ImmutableMap.builder() - .put("version", Info.INSTANCE) - .build(); + private final Map subcommands; + + public VelocityCommand(ProxyServer server) { + this.subcommands = ImmutableMap.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 suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) { + public List 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(), ""); - 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; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index 836d901b5..c48410bdd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -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 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> offerSuggestions(CommandSource source, String cmdLine) { + public List 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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java index 6c1dc1fd1..1f05ca1aa 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java @@ -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 map = (Map) field.get(dumpable); for (Entry 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 + '"'; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 5b935fd41..f77c3ddd8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index 677bdecba..0fd490649 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java index 90e390c22..15f9fc559 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java @@ -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]; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 411f4db0f..c4a1193bf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -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()); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index 5ed4a408b..f3234673b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index 56a275931..d7bc52c2f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index c7bf9c6c2..771f7367e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -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 clientPluginMsgChannels = new HashSet<>(); private final Queue 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 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> suggestions = server.getCommandManager().offerSuggestions(player, packet.getCommand().substring(1)); - if (suggestions.isPresent()) { + List 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 actuallyRegistered = new ArrayList<>(); - List 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 actuallyRegistered = new ArrayList<>(); + List 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 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 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 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 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> 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); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java index 235915f24..41c5a0da8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java @@ -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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index ee8e00710..acd7e0128 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -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 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 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 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 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 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 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 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 { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index 48a6859f6..6fa327c8a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -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 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 reason = event.getResult().getReason(); + if (reason.isPresent()) { + player.disconnect(reason.get()); + } else { + handleProxyLogin(player); + } }, inbound.eventLoop()); }); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java index 48ec7e128..077dce0aa 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java @@ -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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeUtil.java index 684b14f3d..34ba3cea4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeUtil.java @@ -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> readModList(PluginMessage message) { + public static List 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(); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index c3aff7c58..44e7bbfe2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -53,16 +53,14 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Comm .completer((reader, parsedLine, list) -> { try { boolean isCommand = parsedLine.line().indexOf(' ') == -1; - Optional> 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 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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index e7cf90874..41652415a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -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()); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/http/NettyHttpClient.java b/proxy/src/main/java/com/velocitypowered/proxy/network/http/NettyHttpClient.java index f14f10c40..dc83343d5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/http/NettyHttpClient.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/http/NettyHttpClient.java @@ -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()); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java index db2d2ad92..5805c5c74 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java @@ -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) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java index 2cfd51115..c26b04284 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java @@ -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> 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 bus; + private final MethodSubscriptionAdapter 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 void register(@NonNull Object plugin, @NonNull Class eventClass, @NonNull PostOrder postOrder, @NonNull EventHandler handler) { + @SuppressWarnings("type.argument.type.incompatible") + public void register(Object plugin, Class eventClass, PostOrder postOrder, EventHandler handler) { Preconditions.checkNotNull(plugin, "plugin"); Preconditions.checkNotNull(eventClass, "eventClass"); Preconditions.checkNotNull(postOrder, "postOrder"); @@ -72,8 +78,10 @@ public class VelocityEventManager implements EventManager { } @Override - public @NonNull CompletableFuture fire(@NonNull E event) { - Preconditions.checkNotNull(event, "event"); + public CompletableFuture 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 listeners = registeredListenersByPlugin.removeAll(plugin); - listeners.forEach(bus::unregister); + listeners.forEach(methodAdapter::unregister); Collection> 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 void unregister(@NonNull Object plugin, @NonNull EventHandler handler) { + public void unregister(Object plugin, EventHandler 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 { - private final MethodSubscriptionAdapter methodAdapter; - - VelocityEventBus(EventExecutor.@NonNull Factory factory, @NonNull MethodScanner 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 { @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 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); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java index 3c1d5b2c5..5556dca0f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -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 fromInstance(@NonNull Object instance) { + public Optional fromInstance(Object instance) { checkNotNull(instance, "instance"); if (instance instanceof PluginContainer) { @@ -97,23 +100,23 @@ public class VelocityPluginManager implements PluginManager { } @Override - public @NonNull Optional getPlugin(@NonNull String id) { + public Optional getPlugin(String id) { checkNotNull(id, "id"); return Optional.ofNullable(plugins.get(id)); } @Override - public @NonNull Collection getPlugins() { + public Collection 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"); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java index 1931f36ad..e1c184dca 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/JavaPluginLoader.java @@ -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 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( diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java index 0ee55c112..d20b515ad 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java @@ -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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtils.java index 090f1382e..ecb25f527 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtils.java @@ -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 sortCandidates(List candidates) { + public static List sortCandidates(List<@NonNull PluginDescription> candidates) { // Create our graph, we're going to be using this for Kahn's algorithm. MutableGraph graph = GraphBuilder.directed().allowsSelfLoops(false).build(); - Map candidateMap = Maps.uniqueIndex(candidates, PluginDescription::getId); + Map 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 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))) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java index 9737e3224..acba39c10 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java @@ -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); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index dec0466a7..25711266a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -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 LINKED_PROTOCOL_VERSIONS = new IntObjectHashMap<>(); @@ -165,19 +168,18 @@ public enum StateRegistry { } private final ProtocolConstants.Direction direction; - private final StateRegistry state; private final IntObjectMap 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 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> 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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java index 4b7e00232..6ffd51f4c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java @@ -13,14 +13,14 @@ import io.netty.handler.codec.MessageToMessageDecoder; import java.util.List; public class MinecraftDecoder extends MessageToMessageDecoder { - 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 { } } - 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; - } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java index 9607c371d..8efcaa398 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java @@ -10,14 +10,14 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class MinecraftEncoder extends MessageToByteEncoder { - 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 { 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; - } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java index 4b34f51e7..d2a11f41b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBar.java @@ -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: diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java index 2437ff099..1b8b8d88c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java @@ -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); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java index a0db1c064..d28c9a581 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java @@ -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); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java index 1eb7188c8..9828445f1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Disconnect.java @@ -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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequest.java index 8b3c12b7b..f44a39707 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequest.java @@ -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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java index cbefccf34..ae62ac128 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java @@ -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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Handshake.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Handshake.java index 37fcc83a7..3029854e6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Handshake.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Handshake.java @@ -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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java index a87719574..f33fe12a3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooter.java @@ -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"); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java index 25981a711..fab67338d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java @@ -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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java index 36cbe7845..354abec73 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingResponse.java @@ -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{" + diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessage.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessage.java index 948269012..bbef77d0c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessage.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessage.java @@ -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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponse.java index 55fb339d0..c0c637331 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponse.java @@ -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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java index cad08ae37..6e3533084 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java @@ -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 items; + private final List items = new ArrayList<>(); public PlayerListItem(int action, List 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 properties; + private String name = ""; + private List 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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java index d57598b8f..3afd67e25 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java @@ -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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 4aca25ed2..fbe530815 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -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() { } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java index c399df684..0868c65cc 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java @@ -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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccess.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccess.java index 8c0272ba3..d553c6566 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccess.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccess.java @@ -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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponse.java index e915fe812..e508e49c9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponse.java @@ -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); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java index d4bfb183b..f933d4dca 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java @@ -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); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TitlePacket.java index 52a9a5c49..58238862f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TitlePacket.java @@ -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; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java index d37abc0e3..03792bbae 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -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; }); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java b/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java index 8ceb23970..64a8b23fc 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java @@ -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 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()); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java index 7abd379f0..448cd43d4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -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 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 ping() { + if (server == null) { + throw new IllegalStateException("No Velocity proxy instance available"); + } CompletableFuture pingFuture = new CompletableFuture<>(); server.initializeGenericBootstrap() .handler(new ChannelInitializer() { @@ -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); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java index 153c1b339..59586d787 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -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 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; + } } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java index bc60d73ca..bdf116954 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java @@ -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; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java b/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java index 6095a56cd..1fff632e1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java @@ -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() diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityChannelRegistrar.java b/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityChannelRegistrar.java index f88d2697e..78b93cefe 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityChannelRegistrar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityChannelRegistrar.java @@ -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); } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java index cc227f471..2a3c7730d 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java @@ -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());