From 25b5e00125782b715e8cf2aafde90a94a2cd6b37 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 27 Oct 2018 23:45:35 -0400 Subject: [PATCH] Reformat with Google code style and enforce Checkstyle. Fixes #125 --- api/build.gradle | 2 + .../plugin/ap/PluginAnnotationProcessor.java | 131 +- .../ap/SerializedPluginDescription.java | 247 ++-- .../velocitypowered/api/command/Command.java | 68 +- .../api/command/CommandManager.java | 40 +- .../api/command/CommandSource.java | 12 +- .../api/event/EventHandler.java | 7 +- .../api/event/EventManager.java | 128 +- .../velocitypowered/api/event/PostOrder.java | 5 +- .../api/event/ResultedEvent.java | 191 +-- .../velocitypowered/api/event/Subscribe.java | 12 +- .../connection/ConnectionHandshakeEvent.java | 27 +- .../api/event/connection/DisconnectEvent.java | 31 +- .../api/event/connection/LoginEvent.java | 52 +- .../event/connection/PluginMessageEvent.java | 159 +-- .../api/event/connection/PostLoginEvent.java | 30 +- .../api/event/connection/PreLoginEvent.java | 244 ++-- .../permission/PermissionsSetupEvent.java | 84 +- .../event/player/GameProfileRequestEvent.java | 114 +- .../event/player/KickedFromServerEvent.java | 195 +-- .../api/event/player/PlayerChatEvent.java | 139 +- .../api/event/player/PlayerModInfoEvent.java | 31 +- .../player/PlayerSettingsChangedEvent.java | 25 +- .../event/player/ServerConnectedEvent.java | 43 +- .../event/player/ServerPreConnectEvent.java | 130 +- .../api/event/proxy/ProxyInitializeEvent.java | 12 +- .../api/event/proxy/ProxyPingEvent.java | 45 +- .../api/event/proxy/ProxyShutdownEvent.java | 13 +- .../api/event/query/ProxyQueryEvent.java | 140 +- .../api/permission/PermissionFunction.java | 42 +- .../api/permission/PermissionProvider.java | 15 +- .../api/permission/PermissionSubject.java | 33 +- .../api/permission/Tristate.java | 109 +- .../api/plugin/Dependency.java | 28 +- .../api/plugin/InvalidPluginException.java | 25 +- .../velocitypowered/api/plugin/Plugin.java | 91 +- .../api/plugin/PluginContainer.java | 29 +- .../api/plugin/PluginDescription.java | 160 ++- .../api/plugin/PluginManager.java | 76 +- .../api/plugin/annotation/DataDirectory.java | 6 +- .../api/plugin/meta/PluginDependency.java | 129 +- .../api/proxy/ConnectionRequestBuilder.java | 155 +-- .../api/proxy/InboundConnection.java | 45 +- .../com/velocitypowered/api/proxy/Player.java | 232 ++-- .../api/proxy/ProxyServer.java | 223 ++-- .../api/proxy/ServerConnection.java | 34 +- .../api/proxy/config/ProxyConfig.java | 193 +-- .../api/proxy/messages/ChannelIdentifier.java | 12 +- .../proxy/messages/ChannelMessageSink.java | 16 +- .../proxy/messages/ChannelMessageSource.java | 1 + .../api/proxy/messages/ChannelRegistrar.java | 26 +- .../messages/LegacyChannelIdentifier.java | 65 +- .../messages/MinecraftChannelIdentifier.java | 133 +- .../api/proxy/messages/package-info.java | 3 +- .../api/proxy/player/PlayerSettings.java | 87 +- .../api/proxy/player/SkinParts.java | 51 +- .../api/proxy/player/TabList.java | 79 +- .../api/proxy/player/TabListEntry.java | 346 ++--- .../api/proxy/server/QueryResponse.java | 566 ++++---- .../api/proxy/server/RegisteredServer.java | 35 +- .../api/proxy/server/ServerInfo.java | 82 +- .../api/proxy/server/ServerPing.java | 535 ++++---- .../api/scheduler/ScheduledTask.java | 33 +- .../api/scheduler/Scheduler.java | 90 +- .../api/scheduler/TaskStatus.java | 24 +- .../com/velocitypowered/api/util/Favicon.java | 139 +- .../velocitypowered/api/util/GameProfile.java | 132 +- .../api/util/MessagePosition.java | 27 +- .../com/velocitypowered/api/util/ModInfo.java | 93 +- .../api/util/ProxyVersion.java | 82 +- .../velocitypowered/api/util/UuidUtils.java | 73 +- .../api/util/title/TextTitle.java | 413 +++--- .../velocitypowered/api/util/title/Title.java | 1 + .../api/util/title/Titles.java | 80 +- .../api/util/UuidUtilsTest.java | 110 +- config/checkstyle/checkstyle.xml | 256 ++++ gradle/checkerframework.gradle | 2 +- gradle/checkstyle.gradle | 4 + native/build.gradle | 2 + .../velocitypowered/natives/Disposable.java | 17 +- .../compression/JavaVelocityCompressor.java | 90 +- .../compression/NativeVelocityCompressor.java | 117 +- .../compression/NativeZlibDeflate.java | 24 +- .../compression/NativeZlibInflate.java | 22 +- .../compression/VelocityCompressor.java | 14 +- .../VelocityCompressorFactory.java | 3 +- .../encryption/JavaVelocityCipher.java | 73 +- .../natives/encryption/MbedtlsAesImpl.java | 8 +- .../encryption/NativeVelocityCipher.java | 80 +- .../natives/encryption/VelocityCipher.java | 4 +- .../encryption/VelocityCipherFactory.java | 7 +- .../natives/util/NativeCodeLoader.java | 134 +- .../velocitypowered/natives/util/Natives.java | 86 +- .../compression/VelocityCompressorTest.java | 107 +- .../encryption/VelocityCipherTest.java | 110 +- proxy/build.gradle | 10 +- .../com/velocitypowered/proxy/Velocity.java | 34 +- .../velocitypowered/proxy/VelocityServer.java | 586 +++++---- .../proxy/command/ServerCommand.java | 144 +- .../proxy/command/ShutdownCommand.java | 32 +- .../proxy/command/VelocityCommand.java | 205 +-- .../proxy/command/VelocityCommandManager.java | 164 +-- .../proxy/config/AnnotatedConfig.java | 354 ++--- .../proxy/config/PlayerInfoForwarding.java | 6 +- .../proxy/config/VelocityConfiguration.java | 1157 +++++++++-------- .../proxy/connection/MinecraftConnection.java | 507 ++++---- .../MinecraftConnectionAssociation.java | 1 + .../connection/MinecraftSessionHandler.java | 195 ++- .../proxy/connection/VelocityConstants.java | 13 +- .../backend/BackendPlaySessionHandler.java | 298 +++-- .../backend/LoginSessionHandler.java | 288 ++-- .../backend/VelocityServerConnection.java | 347 ++--- .../client/ClientPlaySessionHandler.java | 665 +++++----- .../client/ClientSettingsWrapper.java | 85 +- .../connection/client/ConnectedPlayer.java | 975 +++++++------- .../client/HandshakeSessionHandler.java | 253 ++-- .../client/InitialConnectSessionHandler.java | 27 +- .../client/InitialInboundConnection.java | 51 +- .../client/LoginSessionHandler.java | 470 +++---- .../client/StatusSessionHandler.java | 78 +- .../connection/forge/ForgeConstants.java | 27 +- .../proxy/connection/forge/ForgeUtil.java | 51 +- .../connection/util/ConnectionMessages.java | 19 +- .../util/ConnectionRequestResults.java | 70 +- .../proxy/console/VelocityConsole.java | 121 +- .../proxy/network/ConnectionManager.java | 160 +-- .../proxy/network/Connections.java | 31 +- .../network/ServerChannelInitializer.java | 68 +- .../ServerChannelInitializerHolder.java | 33 +- .../proxy/network/TransportType.java | 114 +- .../proxy/network/http/NettyHttpClient.java | 133 +- .../network/http/SimpleHttpResponse.java | 39 +- .../http/SimpleHttpResponseCollector.java | 72 +- .../proxy/plugin/PluginClassLoader.java | 86 +- .../proxy/plugin/VelocityEventManager.java | 317 ++--- .../proxy/plugin/VelocityPluginManager.java | 198 +-- .../proxy/plugin/loader/JavaPluginLoader.java | 173 +-- .../proxy/plugin/loader/PluginLoader.java | 6 +- .../loader/VelocityPluginContainer.java | 30 +- .../loader/VelocityPluginDescription.java | 145 ++- .../java/JavaVelocityPluginDescription.java | 36 +- .../loader/java/VelocityPluginModule.java | 42 +- .../plugin/util/PluginDependencyUtils.java | 161 +-- .../proxy/protocol/MinecraftPacket.java | 7 +- .../proxy/protocol/ProtocolConstants.java | 95 +- .../proxy/protocol/ProtocolUtils.java | 180 +-- .../proxy/protocol/StateRegistry.java | 631 +++++---- .../proxy/protocol/netty/GS4QueryHandler.java | 459 +++---- .../protocol/netty/LegacyPingDecoder.java | 34 +- .../protocol/netty/LegacyPingEncoder.java | 26 +- .../netty/MinecraftCipherDecoder.java | 38 +- .../netty/MinecraftCipherEncoder.java | 34 +- .../netty/MinecraftCompressDecoder.java | 69 +- .../netty/MinecraftCompressEncoder.java | 58 +- .../protocol/netty/MinecraftDecoder.java | 85 +- .../protocol/netty/MinecraftEncoder.java | 44 +- .../netty/MinecraftVarintFrameDecoder.java | 66 +- .../netty/MinecraftVarintLengthEncoder.java | 22 +- .../proxy/protocol/packet/BossBar.java | 320 ++--- .../proxy/protocol/packet/Chat.java | 125 +- .../proxy/protocol/packet/ClientSettings.java | 213 +-- .../proxy/protocol/packet/Disconnect.java | 77 +- .../protocol/packet/EncryptionRequest.java | 82 +- .../protocol/packet/EncryptionResponse.java | 76 +- .../proxy/protocol/packet/Handshake.java | 111 +- .../protocol/packet/HeaderAndFooter.java | 83 +- .../proxy/protocol/packet/JoinGame.java | 193 +-- .../proxy/protocol/packet/KeepAlive.java | 67 +- .../protocol/packet/LegacyDisconnect.java | 43 +- .../protocol/packet/LegacyHandshake.java | 25 +- .../proxy/protocol/packet/LegacyPing.java | 25 +- .../protocol/packet/LegacyPingResponse.java | 93 +- .../protocol/packet/LoginPluginMessage.java | 107 +- .../protocol/packet/LoginPluginResponse.java | 97 +- .../proxy/protocol/packet/PlayerListItem.java | 336 ++--- .../proxy/protocol/packet/PluginMessage.java | 85 +- .../proxy/protocol/packet/Respawn.java | 127 +- .../proxy/protocol/packet/ServerLogin.java | 64 +- .../protocol/packet/ServerLoginSuccess.java | 92 +- .../proxy/protocol/packet/SetCompression.java | 60 +- .../proxy/protocol/packet/StatusPing.java | 27 +- .../proxy/protocol/packet/StatusRequest.java | 39 +- .../proxy/protocol/packet/StatusResponse.java | 64 +- .../protocol/packet/TabCompleteRequest.java | 137 +- .../protocol/packet/TabCompleteResponse.java | 54 +- .../proxy/protocol/packet/TitlePacket.java | 252 ++-- .../protocol/util/FaviconSerializer.java | 25 +- .../protocol/util/PluginMessageUtil.java | 128 +- .../proxy/scheduler/VelocityScheduler.java | 314 ++--- .../proxy/server/ServerMap.java | 84 +- .../server/VelocityRegisteredServer.java | 164 +-- .../proxy/server/ping/PingSessionHandler.java | 91 +- .../proxy/tablist/VelocityTabList.java | 231 ++-- .../proxy/tablist/VelocityTabListEntry.java | 105 +- .../proxy/util/AddressUtil.java | 13 +- .../proxy/util/EncryptionUtils.java | 61 +- .../proxy/util/Ratelimiter.java | 64 +- .../proxy/util/ThrowableUtils.java | 8 +- .../proxy/util/VelocityChannelRegistrar.java | 76 +- proxy/src/main/resources/log4j2.xml | 52 +- .../util/PluginDependencyUtilsTest.java | 136 +- .../proxy/protocol/PacketRegistryTest.java | 95 +- .../scheduler/VelocitySchedulerTest.java | 72 +- .../proxy/testutil/FakePluginManager.java | 113 +- .../proxy/util/EncryptionUtilsTest.java | 36 +- .../proxy/util/RatelimiterTest.java | 57 +- .../proxy/util/ServerMapTest.java | 69 +- settings.gradle | 2 +- 208 files changed, 12851 insertions(+), 11620 deletions(-) create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 gradle/checkstyle.gradle diff --git a/api/build.gradle b/api/build.gradle index 9fcc52975..86bb9b32f 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -2,9 +2,11 @@ plugins { id 'java' id 'com.github.johnrengelman.shadow' version '2.0.4' id 'maven-publish' + id 'checkstyle' } apply from: '../gradle/checkerframework.gradle' +apply from: '../gradle/checkstyle.gradle' sourceSets { ap { diff --git a/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java b/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java index 6a8310c72..7806a03e4 100644 --- a/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java +++ b/api/src/ap/java/com/velocitypowered/api/plugin/ap/PluginAnnotationProcessor.java @@ -3,7 +3,11 @@ package com.velocitypowered.api.plugin.ap; import com.google.gson.Gson; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.PluginDescription; - +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Objects; +import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; @@ -16,73 +20,76 @@ import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.Writer; -import java.util.Objects; -import java.util.Set; @SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"}) public class PluginAnnotationProcessor extends AbstractProcessor { - private ProcessingEnvironment environment; - private String pluginClassFound; - private boolean warnedAboutMultiplePlugins; - @Override - public synchronized void init(ProcessingEnvironment processingEnv) { - this.environment = processingEnv; + private ProcessingEnvironment environment; + private String pluginClassFound; + private boolean warnedAboutMultiplePlugins; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + this.environment = processingEnv; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + return false; } - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - if (roundEnv.processingOver()) { - return false; - } - - for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) { - if (element.getKind() != ElementKind.CLASS) { - environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with " - + Plugin.class.getCanonicalName()); - return false; - } - - Name qualifiedName = ((TypeElement) element).getQualifiedName(); - - if (Objects.equals(pluginClassFound, qualifiedName.toString())) { - if (!warnedAboutMultiplePlugins) { - environment.getMessager().printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support " + - "multiple plugins. We are using " + pluginClassFound + " for your plugin's main class."); - warnedAboutMultiplePlugins = true; - } - return false; - } - - Plugin plugin = element.getAnnotation(Plugin.class); - if (!PluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) { - environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin " - + qualifiedName + ". IDs must start alphabetically, have alphanumeric characters, and can " + - "contain dashes or underscores."); - return false; - } - - // All good, generate the velocity-plugin.json. - SerializedPluginDescription description = SerializedPluginDescription.from(plugin, qualifiedName.toString()); - try { - FileObject object = environment.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json"); - try (Writer writer = new BufferedWriter(object.openWriter())) { - new Gson().toJson(description, writer); - } - pluginClassFound = qualifiedName.toString(); - } catch (IOException e) { - environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file"); - } - } - + for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) { + if (element.getKind() != ElementKind.CLASS) { + environment.getMessager() + .printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with " + + Plugin.class.getCanonicalName()); return false; + } + + Name qualifiedName = ((TypeElement) element).getQualifiedName(); + + if (Objects.equals(pluginClassFound, qualifiedName.toString())) { + if (!warnedAboutMultiplePlugins) { + environment.getMessager() + .printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support " + + "multiple plugins. We are using " + pluginClassFound + + " for your plugin's main class."); + warnedAboutMultiplePlugins = true; + } + return false; + } + + Plugin plugin = element.getAnnotation(Plugin.class); + if (!PluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) { + environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin " + + qualifiedName + + ". IDs must start alphabetically, have alphanumeric characters, and can " + + "contain dashes or underscores."); + return false; + } + + // All good, generate the velocity-plugin.json. + SerializedPluginDescription description = SerializedPluginDescription + .from(plugin, qualifiedName.toString()); + try { + FileObject object = environment.getFiler() + .createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json"); + try (Writer writer = new BufferedWriter(object.openWriter())) { + new Gson().toJson(description, writer); + } + pluginClassFound = qualifiedName.toString(); + } catch (IOException e) { + environment.getMessager() + .printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file"); + } } + + return false; + } } 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 d35b51674..0a52e340d 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 @@ -4,149 +4,162 @@ 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; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.Nullable; public class SerializedPluginDescription { - // @Nullable is used here to make GSON skip these in the serialized file - private final String id; - private final @Nullable String name; - private final @Nullable String version; - private final @Nullable String description; - private final @Nullable String url; - private final @Nullable List authors; - private final @Nullable List dependencies; - private final String main; - public SerializedPluginDescription(String id, String name, String version, String description, String url, - List authors, List dependencies, String main) { - this.id = Preconditions.checkNotNull(id, "id"); - this.name = Strings.emptyToNull(name); - this.version = Strings.emptyToNull(version); - this.description = Strings.emptyToNull(description); - this.url = Strings.emptyToNull(url); - this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors; - this.dependencies = dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies; - this.main = Preconditions.checkNotNull(main, "main"); + // @Nullable is used here to make GSON skip these in the serialized file + private final String id; + private final @Nullable String name; + private final @Nullable String version; + private final @Nullable String description; + private final @Nullable String url; + private final @Nullable List authors; + private final @Nullable List dependencies; + private final String main; + + public SerializedPluginDescription(String id, String name, String version, String description, + String url, + List authors, List dependencies, String main) { + this.id = Preconditions.checkNotNull(id, "id"); + this.name = Strings.emptyToNull(name); + this.version = Strings.emptyToNull(version); + this.description = Strings.emptyToNull(description); + this.url = Strings.emptyToNull(url); + this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors; + this.dependencies = + dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies; + this.main = Preconditions.checkNotNull(main, "main"); + } + + public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) { + List dependencies = new ArrayList<>(); + for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { + dependencies.add(new Dependency(dependency.id(), dependency.optional())); } + return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), + plugin.description(), plugin.url(), + Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()) + .collect(Collectors.toList()), dependencies, qualifiedName); + } - public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) { - List dependencies = new ArrayList<>(); - for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { - dependencies.add(new Dependency(dependency.id(), dependency.optional())); - } - return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.description(), plugin.url(), - Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), dependencies, qualifiedName); + public String getId() { + return id; + } + + public @Nullable String getName() { + return name; + } + + public @Nullable String getVersion() { + return version; + } + + public @Nullable String getDescription() { + return description; + } + + public @Nullable String getUrl() { + return url; + } + + public List getAuthors() { + return authors == null ? ImmutableList.of() : authors; + } + + public List getDependencies() { + return dependencies == null ? ImmutableList.of() : dependencies; + } + + public String getMain() { + return main; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SerializedPluginDescription that = (SerializedPluginDescription) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name) && + Objects.equals(version, that.version) && + Objects.equals(description, that.description) && + Objects.equals(url, that.url) && + Objects.equals(authors, that.authors) && + Objects.equals(dependencies, that.dependencies) && + Objects.equals(main, that.main); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, version, description, url, authors, dependencies); + } + + @Override + public String toString() { + return "SerializedPluginDescription{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", version='" + version + '\'' + + ", description='" + description + '\'' + + ", url='" + url + '\'' + + ", authors=" + authors + + ", dependencies=" + dependencies + + ", main='" + main + '\'' + + '}'; + } + + public static class Dependency { + + private final String id; + private final boolean optional; + + public Dependency(String id, boolean optional) { + this.id = id; + this.optional = optional; } public String getId() { - return id; + return id; } - public @Nullable String getName() { - return name; - } - - public @Nullable String getVersion() { - return version; - } - - public @Nullable String getDescription() { - return description; - } - - public @Nullable String getUrl() { - return url; - } - - public List getAuthors() { - return authors == null ? ImmutableList.of() : authors; - } - - public List getDependencies() { - return dependencies == null ? ImmutableList.of() : dependencies; - } - - public String getMain() { - return main; + public boolean isOptional() { + return optional; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SerializedPluginDescription that = (SerializedPluginDescription) o; - return Objects.equals(id, that.id) && - Objects.equals(name, that.name) && - Objects.equals(version, that.version) && - Objects.equals(description, that.description) && - Objects.equals(url, that.url) && - Objects.equals(authors, that.authors) && - Objects.equals(dependencies, that.dependencies) && - Objects.equals(main, that.main); + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Dependency that = (Dependency) o; + return optional == that.optional && + Objects.equals(id, that.id); } @Override public int hashCode() { - return Objects.hash(id, name, version, description, url, authors, dependencies); + return Objects.hash(id, optional); } @Override public String toString() { - return "SerializedPluginDescription{" + - "id='" + id + '\'' + - ", name='" + name + '\'' + - ", version='" + version + '\'' + - ", description='" + description + '\'' + - ", url='" + url + '\'' + - ", authors=" + authors + - ", dependencies=" + dependencies + - ", main='" + main + '\'' + - '}'; - } - - public static class Dependency { - private final String id; - private final boolean optional; - - public Dependency(String id, boolean optional) { - this.id = id; - this.optional = optional; - } - - public String getId() { - return id; - } - - public boolean isOptional() { - return optional; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Dependency that = (Dependency) o; - return optional == that.optional && - Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(id, optional); - } - - @Override - public String toString() { - return "Dependency{" + - "id='" + id + '\'' + - ", optional=" + optional + - '}'; - } + return "Dependency{" + + "id='" + id + '\'' + + ", optional=" + optional + + '}'; } + } } 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 6255b3bbc..f9a077262 100644 --- a/api/src/main/java/com/velocitypowered/api/command/Command.java +++ b/api/src/main/java/com/velocitypowered/api/command/Command.java @@ -1,44 +1,46 @@ package com.velocitypowered.api.command; import com.google.common.collect.ImmutableList; +import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.List; - /** - * Represents a command that can be executed by a {@link CommandSource}, such as a {@link com.velocitypowered.api.proxy.Player} - * or the console. + * Represents a command that can be executed by a {@link CommandSource}, such as a {@link + * com.velocitypowered.api.proxy.Player} or the console. */ public interface Command { - /** - * Executes the command for the specified {@link CommandSource}. - * @param source the source of this command - * @param args the arguments for this command - */ - void execute(CommandSource source, String @NonNull [] args); - /** - * Provides tab complete suggestions for a command for a specified {@link CommandSource}. - * @param source the source to run the command for - * @param currentArgs the current, partial arguments for this command - * @return tab complete suggestions - */ - default List suggest(CommandSource source, String @NonNull [] currentArgs) { - return ImmutableList.of(); - } + /** + * Executes the command for the specified {@link CommandSource}. + * + * @param source the source of this command + * @param args the arguments for this command + */ + void execute(CommandSource source, String @NonNull [] args); - /** - * Tests to check if the {@code source} has permission to use this command - * with the provided {@code args}. - * - *

If this method returns false, the handling will be forwarded onto - * the players current server.

- * - * @param source the source of the command - * @param args the arguments for this command - * @return whether the source has permission - */ - default boolean hasPermission(CommandSource source, String @NonNull [] args) { - return true; - } + /** + * Provides tab complete suggestions for a command for a specified {@link CommandSource}. + * + * @param source the source to run the command for + * @param currentArgs the current, partial arguments for this command + * @return tab complete suggestions + */ + default List suggest(CommandSource source, String @NonNull [] currentArgs) { + return ImmutableList.of(); + } + + /** + * Tests to check if the {@code source} has permission to use this command with the provided + * {@code args}. + * + *

If this method returns false, the handling will be forwarded onto + * the players current server.

+ * + * @param source the source of the command + * @param args the arguments for this command + * @return whether the source has permission + */ + 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 28d5bb0fb..69d1eb791 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -4,24 +4,28 @@ package com.velocitypowered.api.command; * Represents an interface to register a command executor with the proxy. */ public interface CommandManager { - /** - * Registers the specified command with the manager with the specified aliases. - * @param command the command to register - * @param aliases the alias to use - */ - void register(Command command, String... aliases); - /** - * Unregisters a command. - * @param alias the command alias to unregister - */ - void unregister(String alias); + /** + * Registers the specified command with the manager with the specified aliases. + * + * @param command the command to register + * @param aliases the alias to use + */ + void register(Command command, String... aliases); - /** - * Attempts to execute a command from the specified {@code cmdLine}. - * @param source the command's source - * @param cmdLine the command to run - * @return true if the command was found and executed, false if it was not - */ - boolean execute(CommandSource source, String cmdLine); + /** + * Unregisters a command. + * + * @param alias the command alias to unregister + */ + void unregister(String alias); + + /** + * Attempts to execute a command from the specified {@code cmdLine}. + * + * @param source the command's source + * @param cmdLine the command to run + * @return true if the command was found and executed, false if it was not + */ + 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 20bcfde02..f74a7fc2b 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandSource.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandSource.java @@ -7,9 +7,11 @@ import net.kyori.text.Component; * Represents something that can be used to run a {@link Command}. */ public interface CommandSource extends PermissionSubject { - /** - * Sends the specified {@code component} to the invoker. - * @param component the text component to send - */ - void sendMessage(Component component); + + /** + * Sends the specified {@code component} to the invoker. + * + * @param component the text component to send + */ + 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 4acf0eab2..f6005cdf2 100644 --- a/api/src/main/java/com/velocitypowered/api/event/EventHandler.java +++ b/api/src/main/java/com/velocitypowered/api/event/EventHandler.java @@ -1,10 +1,11 @@ package com.velocitypowered.api.event; /** - * Represents an interface to perform direct dispatch of an event. This makes integration easier to achieve with platforms - * such as RxJava. + * 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(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 2a52239ad..97ff4e188 100644 --- a/api/src/main/java/com/velocitypowered/api/event/EventManager.java +++ b/api/src/main/java/com/velocitypowered/api/event/EventManager.java @@ -6,68 +6,82 @@ import java.util.concurrent.CompletableFuture; * Allows plugins to register and deregister listeners for event handlers. */ public interface EventManager { - /** - * Requests that the specified {@code listener} listen for events and associate it with the {@code plugin}. - * @param plugin the plugin to associate with the listener - * @param listener the listener to register - */ - void register(Object plugin, Object listener); - /** - * Requests that the specified {@code handler} listen for events and associate it with the {@code plugin}. - * @param plugin the plugin to associate with the handler - * @param eventClass the class for the event handler to register - * @param handler the handler to register - * @param the event type to handle - */ - default void register(Object plugin, Class eventClass, EventHandler handler) { - register(plugin, eventClass, PostOrder.NORMAL, handler); - } + /** + * Requests that the specified {@code listener} listen for events and associate it with the {@code + * plugin}. + * + * @param plugin the plugin to associate with the listener + * @param listener the listener to register + */ + void register(Object plugin, Object listener); - /** - * Requests that the specified {@code handler} listen for events and associate it with the {@code plugin}. - * @param plugin the plugin to associate with the handler - * @param eventClass the class for the event handler to register - * @param postOrder the order in which events should be posted to the handler - * @param handler the handler to register - * @param the event type to handle - */ - void register(Object plugin, Class eventClass, PostOrder postOrder, EventHandler handler); + /** + * Requests that the specified {@code handler} listen for events and associate it with the {@code + * plugin}. + * + * @param plugin the plugin to associate with the handler + * @param eventClass the class for the event handler to register + * @param handler the handler to register + * @param the event type to handle + */ + default void register(Object plugin, Class eventClass, EventHandler handler) { + register(plugin, eventClass, PostOrder.NORMAL, handler); + } - /** - * Fires the specified event to the event bus asynchronously. This allows Velocity to continue servicing connections - * while a plugin handles a potentially long-running operation such as a database query. - * @param event the event to fire - * @return a {@link CompletableFuture} representing the posted event - */ - CompletableFuture fire(E event); + /** + * Requests that the specified {@code handler} listen for events and associate it with the {@code + * plugin}. + * + * @param plugin the plugin to associate with the handler + * @param eventClass the class for the event handler to register + * @param postOrder the order in which events should be posted to the handler + * @param handler the handler to register + * @param the event type to handle + */ + void register(Object plugin, Class eventClass, PostOrder postOrder, + EventHandler handler); - /** - * Posts the specified event to the event bus and discards the result. - * @param event the event to fire - */ - default void fireAndForget(Object event) { - fire(event); - } + /** + * Fires the specified event to the event bus asynchronously. This allows Velocity to continue + * servicing connections while a plugin handles a potentially long-running operation such as a + * database query. + * + * @param event the event to fire + * @return a {@link CompletableFuture} representing the posted event + */ + CompletableFuture fire(E event); - /** - * Unregisters all listeners for the specified {@code plugin}. - * @param plugin the plugin to deregister listeners for - */ - void unregisterListeners(Object plugin); + /** + * Posts the specified event to the event bus and discards the result. + * + * @param event the event to fire + */ + default void fireAndForget(Object event) { + fire(event); + } - /** - * Unregisters a specific listener for a specific plugin. - * @param plugin the plugin associated with the listener - * @param listener the listener to deregister - */ - void unregisterListener(Object plugin, Object listener); + /** + * Unregisters all listeners for the specified {@code plugin}. + * + * @param plugin the plugin to deregister listeners for + */ + void unregisterListeners(Object plugin); - /** - * Unregisters a specific event handler for a specific plugin. - * @param plugin the plugin to associate with the handler - * @param handler the handler to register - * @param the event type to handle - */ - void unregister(Object plugin, EventHandler handler); + /** + * Unregisters a specific listener for a specific plugin. + * + * @param plugin the plugin associated with the listener + * @param listener the listener to deregister + */ + void unregisterListener(Object plugin, Object listener); + + /** + * Unregisters a specific event handler for a specific plugin. + * + * @param plugin the plugin to associate with the handler + * @param handler the handler to register + * @param the event type to handle + */ + void unregister(Object plugin, EventHandler handler); } diff --git a/api/src/main/java/com/velocitypowered/api/event/PostOrder.java b/api/src/main/java/com/velocitypowered/api/event/PostOrder.java index a9bffe331..7c31807f4 100644 --- a/api/src/main/java/com/velocitypowered/api/event/PostOrder.java +++ b/api/src/main/java/com/velocitypowered/api/event/PostOrder.java @@ -1,11 +1,10 @@ package com.velocitypowered.api.event; /** - * Represents the order an event will be posted to a listener method, relative - * to other listeners. + * Represents the order an event will be posted to a listener method, relative to other listeners. */ public enum PostOrder { - FIRST, EARLY, NORMAL, LATE, LAST; + FIRST, EARLY, NORMAL, LATE, LAST; } 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 6888260e6..bb09e00aa 100644 --- a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java @@ -1,114 +1,119 @@ package com.velocitypowered.api.event; import com.google.common.base.Preconditions; +import java.util.Optional; import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializers; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Optional; - /** * Indicates an event that has a result attached to it. */ public interface ResultedEvent { - /** - * Returns the result associated with this event. - * @return the result of this event - */ - R getResult(); + + /** + * Returns the result associated with this event. + * + * @return the result of this event + */ + R getResult(); + + /** + * Sets the result of this event. The result must be non-null. + * + * @param result the new result + */ + void setResult(R result); + + /** + * Represents a result for an event. + */ + interface Result { /** - * Sets the result of this event. The result must be non-null. - * @param result the new result + * Returns whether or not the event is allowed to proceed. Plugins may choose to skip denied + * events, and the proxy will respect the result of this method. + * + * @return whether or not the event is allowed to proceed */ - void setResult(R result); + boolean isAllowed(); + } - /** - * Represents a result for an event. - */ - interface Result { - /** - * Returns whether or not the event is allowed to proceed. Plugins may choose to skip denied events, and the - * proxy will respect the result of this method. - * @return whether or not the event is allowed to proceed - */ - boolean isAllowed(); + /** + * A generic "allowed/denied" result. + */ + final class GenericResult implements Result { + + private static final GenericResult ALLOWED = new GenericResult(true); + private static final GenericResult DENIED = new GenericResult(false); + + private final boolean status; + + private GenericResult(boolean b) { + this.status = b; } - /** - * A generic "allowed/denied" result. - */ - final class GenericResult implements Result { - private static final GenericResult ALLOWED = new GenericResult(true); - private static final GenericResult DENIED = new GenericResult(false); - - private final boolean status; - - private GenericResult(boolean b) { - this.status = b; - } - - @Override - public boolean isAllowed() { - return status; - } - - @Override - public String toString() { - return status ? "allowed" : "denied"; - } - - public static GenericResult allowed() { - return ALLOWED; - } - - public static GenericResult denied() { - return DENIED; - } + @Override + public boolean isAllowed() { + return status; } - /** - * Represents an "allowed/denied" result with a reason allowed for denial. - */ - final class ComponentResult implements Result { - private static final ComponentResult ALLOWED = new ComponentResult(true, null); - - private final boolean status; - private final @Nullable Component reason; - - protected ComponentResult(boolean status, @Nullable Component reason) { - this.status = status; - this.reason = reason; - } - - @Override - public boolean isAllowed() { - return status; - } - - public Optional getReason() { - return Optional.ofNullable(reason); - } - - @Override - public String toString() { - if (status) { - return "allowed"; - } - if (reason != null) { - return "denied: " + ComponentSerializers.PLAIN.serialize(reason); - } - return "denied"; - } - - public static ComponentResult allowed() { - return ALLOWED; - } - - public static ComponentResult denied(Component reason) { - Preconditions.checkNotNull(reason, "reason"); - return new ComponentResult(false, reason); - } + @Override + public String toString() { + return status ? "allowed" : "denied"; } + + public static GenericResult allowed() { + return ALLOWED; + } + + public static GenericResult denied() { + return DENIED; + } + } + + /** + * Represents an "allowed/denied" result with a reason allowed for denial. + */ + final class ComponentResult implements Result { + + private static final ComponentResult ALLOWED = new ComponentResult(true, null); + + private final boolean status; + private final @Nullable Component reason; + + protected ComponentResult(boolean status, @Nullable Component reason) { + this.status = status; + this.reason = reason; + } + + @Override + public boolean isAllowed() { + return status; + } + + public Optional getReason() { + return Optional.ofNullable(reason); + } + + @Override + public String toString() { + if (status) { + return "allowed"; + } + if (reason != null) { + return "denied: " + ComponentSerializers.PLAIN.serialize(reason); + } + return "denied"; + } + + public static ComponentResult allowed() { + return ALLOWED; + } + + 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/Subscribe.java b/api/src/main/java/com/velocitypowered/api/event/Subscribe.java index adb45b1ca..4e6299d39 100644 --- a/api/src/main/java/com/velocitypowered/api/event/Subscribe.java +++ b/api/src/main/java/com/velocitypowered/api/event/Subscribe.java @@ -12,11 +12,11 @@ import java.lang.annotation.Target; @Target(ElementType.METHOD) public @interface Subscribe { - /** - * The order events will be posted to this listener. - * - * @return the order - */ - PostOrder order() default PostOrder.NORMAL; + /** + * The order events will be posted to this listener. + * + * @return the order + */ + PostOrder order() default PostOrder.NORMAL; } 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 e22db3d69..2fbd8c891 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 @@ -7,20 +7,21 @@ import com.velocitypowered.api.proxy.InboundConnection; * This event is fired when a handshake is established between a client and Velocity. */ public final class ConnectionHandshakeEvent { - private final InboundConnection connection; - public ConnectionHandshakeEvent(InboundConnection connection) { - this.connection = Preconditions.checkNotNull(connection, "connection"); - } + private final InboundConnection connection; - public InboundConnection getConnection() { - return connection; - } + public ConnectionHandshakeEvent(InboundConnection connection) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + } - @Override - public String toString() { - return "ConnectionHandshakeEvent{" + - "connection=" + connection + - '}'; - } + public InboundConnection getConnection() { + return connection; + } + + @Override + public String toString() { + return "ConnectionHandshakeEvent{" + + "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 d8cef27c7..471e639c5 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 @@ -4,24 +4,25 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.Player; /** - * 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. + * 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 final Player player; - public DisconnectEvent(Player player) { - this.player = Preconditions.checkNotNull(player, "player"); - } + private final Player player; - public Player getPlayer() { - return player; - } + public DisconnectEvent(Player player) { + this.player = Preconditions.checkNotNull(player, "player"); + } - @Override - public String toString() { - return "DisconnectEvent{" + - "player=" + player + - '}'; - } + public Player getPlayer() { + return player; + } + + @Override + public String toString() { + return "DisconnectEvent{" + + "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 cbb483f7c..5958bfd04 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 @@ -5,36 +5,38 @@ import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.Player; /** - * This event is fired once the player has been authenticated but before they connect to a server on the proxy. + * This event is fired once the player has been authenticated but before they connect to a server on + * the proxy. */ public final class LoginEvent implements ResultedEvent { - private final Player player; - private ComponentResult result; - public LoginEvent(Player player) { - this.player = Preconditions.checkNotNull(player, "player"); - this.result = ComponentResult.allowed(); - } + private final Player player; + private ComponentResult result; - public Player getPlayer() { - return player; - } + public LoginEvent(Player player) { + this.player = Preconditions.checkNotNull(player, "player"); + this.result = ComponentResult.allowed(); + } - @Override - public ComponentResult getResult() { - return result; - } + public Player getPlayer() { + return player; + } - @Override - public void setResult(ComponentResult result) { - this.result = Preconditions.checkNotNull(result, "result"); - } + @Override + public ComponentResult getResult() { + return result; + } - @Override - public String toString() { - return "LoginEvent{" + - "player=" + player + - ", result=" + result + - '}'; - } + @Override + public void setResult(ComponentResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + @Override + public String toString() { + return "LoginEvent{" + + "player=" + player + + ", result=" + result + + '}'; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/PluginMessageEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/PluginMessageEvent.java index 2ff72a55a..c26413bbe 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/PluginMessageEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PluginMessageEvent.java @@ -7,99 +7,100 @@ import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; -import org.checkerframework.checker.nullness.qual.NonNull; - import java.util.Arrays; /** - * This event is fired when a plugin message is sent to the proxy, either from a client ({@link com.velocitypowered.api.proxy.Player}) - * or a server ({@link com.velocitypowered.api.proxy.ServerConnection}). + * This event is fired when a plugin message is sent to the proxy, either from a client ({@link + * com.velocitypowered.api.proxy.Player}) or a server ({@link com.velocitypowered.api.proxy.ServerConnection}). */ public final class PluginMessageEvent implements ResultedEvent { - private final ChannelMessageSource source; - private final ChannelMessageSink target; - private final ChannelIdentifier identifier; - private final byte[] data; - private ForwardResult result; - public PluginMessageEvent(ChannelMessageSource source, ChannelMessageSink target, ChannelIdentifier identifier, byte[] data) { - this.source = Preconditions.checkNotNull(source, "source"); - this.target = Preconditions.checkNotNull(target, "target"); - this.identifier = Preconditions.checkNotNull(identifier, "identifier"); - this.data = Preconditions.checkNotNull(data, "data"); - this.result = ForwardResult.forward(); + private final ChannelMessageSource source; + private final ChannelMessageSink target; + private final ChannelIdentifier identifier; + private final byte[] data; + private ForwardResult result; + + public PluginMessageEvent(ChannelMessageSource source, ChannelMessageSink target, + ChannelIdentifier identifier, byte[] data) { + this.source = Preconditions.checkNotNull(source, "source"); + this.target = Preconditions.checkNotNull(target, "target"); + this.identifier = Preconditions.checkNotNull(identifier, "identifier"); + this.data = Preconditions.checkNotNull(data, "data"); + this.result = ForwardResult.forward(); + } + + @Override + public ForwardResult getResult() { + return result; + } + + @Override + public void setResult(ForwardResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + public ChannelMessageSource getSource() { + return source; + } + + public ChannelMessageSink getTarget() { + return target; + } + + public ChannelIdentifier getIdentifier() { + return identifier; + } + + public byte[] getData() { + return Arrays.copyOf(data, data.length); + } + + public ByteArrayDataInput dataAsDataStream() { + return ByteStreams.newDataInput(data); + } + + @Override + public String toString() { + return "PluginMessageEvent{" + + "source=" + source + + ", target=" + target + + ", identifier=" + identifier + + ", data=" + Arrays.toString(data) + + ", result=" + result + + '}'; + } + + /** + * A result determining whether or not to forward this message on. + */ + public static final class ForwardResult implements ResultedEvent.Result { + + private static final ForwardResult ALLOWED = new ForwardResult(true); + private static final ForwardResult DENIED = new ForwardResult(false); + + private final boolean status; + + private ForwardResult(boolean b) { + this.status = b; } @Override - public ForwardResult getResult() { - return result; - } - - @Override - public void setResult(ForwardResult result) { - this.result = Preconditions.checkNotNull(result, "result"); - } - - public ChannelMessageSource getSource() { - return source; - } - - public ChannelMessageSink getTarget() { - return target; - } - - public ChannelIdentifier getIdentifier() { - return identifier; - } - - public byte[] getData() { - return Arrays.copyOf(data, data.length); - } - - public ByteArrayDataInput dataAsDataStream() { - return ByteStreams.newDataInput(data); + public boolean isAllowed() { + return status; } @Override public String toString() { - return "PluginMessageEvent{" + - "source=" + source + - ", target=" + target + - ", identifier=" + identifier + - ", data=" + Arrays.toString(data) + - ", result=" + result + - '}'; + return status ? "forward to sink" : "handled message at proxy"; } - /** - * A result determining whether or not to forward this message on. - */ - public static final class ForwardResult implements ResultedEvent.Result { - private static final ForwardResult ALLOWED = new ForwardResult(true); - private static final ForwardResult DENIED = new ForwardResult(false); - - private final boolean status; - - private ForwardResult(boolean b) { - this.status = b; - } - - @Override - public boolean isAllowed() { - return status; - } - - @Override - public String toString() { - return status ? "forward to sink" : "handled message at proxy"; - } - - public static ForwardResult forward() { - return ALLOWED; - } - - public static ForwardResult handled() { - return DENIED; - } + public static ForwardResult forward() { + return ALLOWED; } + + public static ForwardResult handled() { + return DENIED; + } + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/PostLoginEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/PostLoginEvent.java index 653259f5f..06997fa14 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/PostLoginEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PostLoginEvent.java @@ -4,25 +4,25 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.Player; /** - * This event is fired once the player has been successfully authenticated and - * fully initialized and player will be connected to server after this event + * This event is fired once the player has been successfully authenticated and fully initialized and + * player will be connected to server after this event */ public final class PostLoginEvent { - private final Player player; + private final Player player; - public PostLoginEvent(Player player) { - this.player = Preconditions.checkNotNull(player, "player"); - } + public PostLoginEvent(Player player) { + this.player = Preconditions.checkNotNull(player, "player"); + } - public Player getPlayer() { - return player; - } + public Player getPlayer() { + return player; + } - @Override - public String toString() { - return "PostLoginEvent{" - + "player=" + player - + '}'; - } + @Override + public String toString() { + return "PostLoginEvent{" + + "player=" + player + + '}'; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java index 5919d0dee..0642f00de 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java @@ -3,147 +3,153 @@ package com.velocitypowered.api.event.connection; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.InboundConnection; +import java.util.Optional; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Optional; - /** - * This event is fired when a player has initiated a connection with the proxy but before the proxy authenticates the - * player with Mojang or before the player's proxy connection is fully established (for offline mode). + * This event is fired when a player has initiated a connection with the proxy but before the proxy + * authenticates the player with Mojang or before the player's proxy connection is fully established + * (for offline mode). */ public final class PreLoginEvent implements ResultedEvent { - private final InboundConnection connection; - private final String username; - private PreLoginComponentResult result; - public PreLoginEvent(InboundConnection connection, String username) { - this.connection = Preconditions.checkNotNull(connection, "connection"); - this.username = Preconditions.checkNotNull(username, "username"); - this.result = PreLoginComponentResult.allowed(); - } + private final InboundConnection connection; + private final String username; + private PreLoginComponentResult result; - public InboundConnection getConnection() { - return connection; - } + public PreLoginEvent(InboundConnection connection, String username) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + this.username = Preconditions.checkNotNull(username, "username"); + this.result = PreLoginComponentResult.allowed(); + } - public String getUsername() { - return username; + public InboundConnection getConnection() { + return connection; + } + + public String getUsername() { + return username; + } + + @Override + public PreLoginComponentResult getResult() { + return result; + } + + @Override + public void setResult(@NonNull PreLoginComponentResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + @Override + public String toString() { + return "PreLoginEvent{" + + "connection=" + connection + + ", username='" + username + '\'' + + ", result=" + result + + '}'; + } + + /** + * Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason + * allowed for denial. + */ + public static final class PreLoginComponentResult implements ResultedEvent.Result { + + private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult( + Result.ALLOWED, null); + private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult( + Result.FORCE_ONLINE, null); + private static final PreLoginComponentResult FORCE_OFFLINEMODE = new PreLoginComponentResult( + Result.FORCE_OFFLINE, null); + + private final Result result; + private final @Nullable Component reason; + + private PreLoginComponentResult(Result result, @Nullable Component reason) { + this.result = result; + this.reason = reason; } @Override - public PreLoginComponentResult getResult() { - return result; + public boolean isAllowed() { + return result != Result.DISALLOWED; } - @Override - public void setResult(@NonNull PreLoginComponentResult result) { - this.result = Preconditions.checkNotNull(result, "result"); + public Optional getReason() { + return Optional.ofNullable(reason); + } + + public boolean isOnlineModeAllowed() { + return result == Result.FORCE_ONLINE; + } + + public boolean isForceOfflineMode() { + return result == Result.FORCE_OFFLINE; } @Override public String toString() { - return "PreLoginEvent{" + - "connection=" + connection + - ", username='" + username + '\'' + - ", result=" + result + - '}'; + switch (result) { + case ALLOWED: + return "allowed"; + case FORCE_OFFLINE: + return "allowed with force offline mode"; + case FORCE_ONLINE: + return "allowed with online mode"; + default: + return "denied"; + } } /** - * Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason allowed for denial. + * Returns a result indicating the connection will be allowed through the proxy. + * + * @return the allowed result */ - public static final class PreLoginComponentResult implements ResultedEvent.Result { - - private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult(Result.ALLOWED, null); - private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(Result.FORCE_ONLINE, null); - private static final PreLoginComponentResult FORCE_OFFLINEMODE = new PreLoginComponentResult(Result.FORCE_OFFLINE, null); - - private final Result result; - private final @Nullable Component reason; - - private PreLoginComponentResult(Result result, @Nullable Component reason) { - this.result = result; - this.reason = reason; - } - - @Override - public boolean isAllowed() { - return result != Result.DISALLOWED; - } - - public Optional getReason() { - return Optional.ofNullable(reason); - } - - public boolean isOnlineModeAllowed() { - return result == Result.FORCE_ONLINE; - } - - public boolean isForceOfflineMode() { - return result == Result.FORCE_OFFLINE; - } - - @Override - public String toString() { - switch (result) { - case ALLOWED: - return "allowed"; - case FORCE_OFFLINE: - return "allowed with force offline mode"; - case FORCE_ONLINE: - return "allowed with online mode"; - default: - return "denied"; - } - } - - /** - * Returns a result indicating the connection will be allowed through - * the proxy. - * @return the allowed result - */ - public static PreLoginComponentResult allowed() { - return ALLOWED; - } - - /** - * Returns a result indicating the connection will be allowed through - * the proxy, but the connection will be forced to use online mode - * provided that the proxy is in offline mode. This acts similarly to - * {@link #allowed()} on an online-mode proxy. - * @return the result - */ - public static PreLoginComponentResult forceOnlineMode() { - return FORCE_ONLINEMODE; - } - - /** - * Returns a result indicating the connection will be allowed through - * the proxy, but the connection will be forced to use offline mode even - * when proxy running in online mode - * @return the result - */ - public static PreLoginComponentResult forceOfflineMode() { - return FORCE_OFFLINEMODE; - } - - /** - * Denies the login with the specified reason. - * @param reason the reason for disallowing the connection - * @return a new result - */ - public static PreLoginComponentResult denied(Component reason) { - Preconditions.checkNotNull(reason, "reason"); - return new PreLoginComponentResult(Result.DISALLOWED, reason); - } - - private enum Result { - ALLOWED, - FORCE_ONLINE, - FORCE_OFFLINE, - DISALLOWED - } + public static PreLoginComponentResult allowed() { + return ALLOWED; } + + /** + * Returns a result indicating the connection will be allowed through the proxy, but the + * connection will be forced to use online mode provided that the proxy is in offline mode. This + * acts similarly to {@link #allowed()} on an online-mode proxy. + * + * @return the result + */ + public static PreLoginComponentResult forceOnlineMode() { + return FORCE_ONLINEMODE; + } + + /** + * Returns a result indicating the connection will be allowed through the proxy, but the + * connection will be forced to use offline mode even when proxy running in online mode + * + * @return the result + */ + public static PreLoginComponentResult forceOfflineMode() { + return FORCE_OFFLINEMODE; + } + + /** + * Denies the login with the specified reason. + * + * @param reason the reason for disallowing the connection + * @return a new result + */ + public static PreLoginComponentResult denied(Component reason) { + Preconditions.checkNotNull(reason, "reason"); + return new PreLoginComponentResult(Result.DISALLOWED, reason); + } + + private enum Result { + ALLOWED, + FORCE_ONLINE, + FORCE_OFFLINE, + DISALLOWED + } + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/permission/PermissionsSetupEvent.java b/api/src/main/java/com/velocitypowered/api/event/permission/PermissionsSetupEvent.java index 82ec3cfed..6e30ad169 100644 --- a/api/src/main/java/com/velocitypowered/api/event/permission/PermissionsSetupEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/permission/PermissionsSetupEvent.java @@ -12,52 +12,52 @@ import org.checkerframework.checker.nullness.qual.Nullable; *

This event is only called once per subject, on initialisation.

*/ public final class PermissionsSetupEvent { - private final PermissionSubject subject; - private final PermissionProvider defaultProvider; - private PermissionProvider provider; - public PermissionsSetupEvent(PermissionSubject subject, PermissionProvider provider) { - this.subject = Preconditions.checkNotNull(subject, "subject"); - this.provider = this.defaultProvider = Preconditions.checkNotNull(provider, "provider"); - } + private final PermissionSubject subject; + private final PermissionProvider defaultProvider; + private PermissionProvider provider; - public PermissionSubject getSubject() { - return this.subject; - } + public PermissionsSetupEvent(PermissionSubject subject, PermissionProvider provider) { + this.subject = Preconditions.checkNotNull(subject, "subject"); + this.provider = this.defaultProvider = Preconditions.checkNotNull(provider, "provider"); + } - /** - * Uses the provider function to obtain a {@link PermissionFunction} for - * the subject. - * - * @param subject the subject - * @return the obtained permission function - */ - public PermissionFunction createFunction(PermissionSubject subject) { - return this.provider.createFunction(subject); - } + public PermissionSubject getSubject() { + return this.subject; + } - public PermissionProvider getProvider() { - return this.provider; - } + /** + * Uses the provider function to obtain a {@link PermissionFunction} for the subject. + * + * @param subject the subject + * @return the obtained permission function + */ + public PermissionFunction createFunction(PermissionSubject subject) { + return this.provider.createFunction(subject); + } - /** - * Sets the {@link PermissionFunction} that should be used for the subject. - * - *

Specifying null will reset the provider to the default - * instance given when the event was posted.

- * - * @param provider the provider - */ - public void setProvider(@Nullable PermissionProvider provider) { - this.provider = provider == null ? this.defaultProvider : provider; - } + public PermissionProvider getProvider() { + return this.provider; + } - @Override - public String toString() { - return "PermissionsSetupEvent{" + - "subject=" + subject + - ", defaultProvider=" + defaultProvider + - ", provider=" + provider + - '}'; - } + /** + * Sets the {@link PermissionFunction} that should be used for the subject. + * + *

Specifying null will reset the provider to the default + * instance given when the event was posted.

+ * + * @param provider the provider + */ + public void setProvider(@Nullable PermissionProvider provider) { + this.provider = provider == null ? this.defaultProvider : provider; + } + + @Override + public String toString() { + return "PermissionsSetupEvent{" + + "subject=" + subject + + ", defaultProvider=" + defaultProvider + + ", provider=" + provider + + '}'; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java index eabd5fc2d..c24e8c124 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/GameProfileRequestEvent.java @@ -6,65 +6,73 @@ import com.velocitypowered.api.util.GameProfile; import org.checkerframework.checker.nullness.qual.Nullable; /** - * This event is fired after the {@link com.velocitypowered.api.event.connection.PreLoginEvent} in order to set up the - * game profile for the user. This can be used to configure a custom profile for a user, i.e. skin replacement. + * This event is fired after the {@link com.velocitypowered.api.event.connection.PreLoginEvent} in + * order to set up the game profile for the user. This can be used to configure a custom profile for + * a user, i.e. skin replacement. */ public final class GameProfileRequestEvent { - private final String username; - private final InboundConnection connection; - private final GameProfile originalProfile; - private final boolean onlineMode; - private @Nullable GameProfile gameProfile; - public GameProfileRequestEvent(InboundConnection connection, GameProfile originalProfile, boolean onlineMode) { - this.connection = Preconditions.checkNotNull(connection, "connection"); - this.originalProfile = Preconditions.checkNotNull(originalProfile, "originalProfile"); - this.username = originalProfile.getName(); - this.onlineMode = onlineMode; - } + private final String username; + private final InboundConnection connection; + private final GameProfile originalProfile; + private final boolean onlineMode; + private @Nullable GameProfile gameProfile; - public InboundConnection getConnection() { - return connection; - } + public GameProfileRequestEvent(InboundConnection connection, GameProfile originalProfile, + boolean onlineMode) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + this.originalProfile = Preconditions.checkNotNull(originalProfile, "originalProfile"); + this.username = originalProfile.getName(); + this.onlineMode = onlineMode; + } - public String getUsername() { - return username; - } - - public GameProfile getOriginalProfile() { - return originalProfile; - } - - public boolean isOnlineMode() { - return onlineMode; - } + public InboundConnection getConnection() { + return connection; + } - /** - * Returns the game profile that will be used to initialize the connection with. Should no profile be currently - * specified, the one generated by the proxy (for offline mode) or retrieved from the Mojang session servers (for - * online mode) will be returned instead. - * @return the user's {@link GameProfile} - */ - public GameProfile getGameProfile() { - return gameProfile == null ? originalProfile : gameProfile; - } + public String getUsername() { + return username; + } + + public GameProfile getOriginalProfile() { + return originalProfile; + } + + public boolean isOnlineMode() { + return onlineMode; + } + + /** + * Returns the game profile that will be used to initialize the connection with. Should no profile + * be currently specified, the one generated by the proxy (for offline mode) or retrieved from the + * Mojang session servers (for online mode) will be returned instead. + * + * @return the user's {@link GameProfile} + */ + public GameProfile getGameProfile() { + return gameProfile == null ? originalProfile : gameProfile; + } + + /** + * Sets the game profile to use for this connection. It is invalid to use this method on an + * online-mode connection. + * + * @param gameProfile the profile to use for the connection, {@code null} uses the original + * profile + */ + public void setGameProfile(@Nullable GameProfile gameProfile) { + Preconditions + .checkState(!onlineMode, "Connection is in online mode, profiles can not be faked"); + this.gameProfile = gameProfile; + } + + @Override + public String toString() { + return "GameProfileRequestEvent{" + + "username=" + username + + ", gameProfile=" + gameProfile + + "}"; + } - /** - * Sets the game profile to use for this connection. It is invalid to use this method on an online-mode connection. - * @param gameProfile the profile to use for the connection, {@code null} uses the original profile - */ - public void setGameProfile(@Nullable GameProfile gameProfile) { - Preconditions.checkState(!onlineMode, "Connection is in online mode, profiles can not be faked"); - this.gameProfile = gameProfile; - } - @Override - public String toString() { - return "GameProfileRequestEvent{"+ - "username=" + username + - ", gameProfile=" + gameProfile + - "}"; - } - - } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/KickedFromServerEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/KickedFromServerEvent.java index c24ec0c58..fcc95c0eb 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/KickedFromServerEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/KickedFromServerEvent.java @@ -8,111 +8,120 @@ import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; /** - * Fired when a player is kicked from a server. You may either allow Velocity to kick the player (with an optional reason - * override) or redirect the player to a separate server. + * Fired when a player is kicked from a server. You may either allow Velocity to kick the player + * (with an optional reason override) or redirect the player to a separate server. */ -public final class KickedFromServerEvent implements ResultedEvent { - private final Player player; +public final class KickedFromServerEvent implements + ResultedEvent { + + private final Player player; + private final RegisteredServer server; + private final Component originalReason; + private final boolean duringLogin; + private ServerKickResult result; + + public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, + boolean duringLogin, Component fancyReason) { + this.player = Preconditions.checkNotNull(player, "player"); + this.server = Preconditions.checkNotNull(server, "server"); + this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason"); + this.duringLogin = duringLogin; + this.result = new DisconnectPlayer(fancyReason); + } + + @Override + public ServerKickResult getResult() { + return result; + } + + @Override + public void setResult(@NonNull ServerKickResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + public Player getPlayer() { + return player; + } + + public RegisteredServer getServer() { + return server; + } + + public Component getOriginalReason() { + return originalReason; + } + + public boolean kickedDuringLogin() { + return duringLogin; + } + + /** + * Represents the base interface for {@link KickedFromServerEvent} results. + */ + public interface ServerKickResult extends ResultedEvent.Result { + + } + + /** + * Tells the proxy to disconnect the player with the specified reason. + */ + public static final class DisconnectPlayer implements ServerKickResult { + + private final Component component; + + private DisconnectPlayer(Component component) { + this.component = Preconditions.checkNotNull(component, "component"); + } + + @Override + public boolean isAllowed() { + return true; + } + + public Component getReason() { + return component; + } + + /** + * Creates a new {@link DisconnectPlayer} with the specified reason. + * + * @param reason the reason to use when disconnecting the player + * @return the disconnect result + */ + public static DisconnectPlayer create(Component reason) { + return new DisconnectPlayer(reason); + } + } + + /** + * Tells the proxy to redirect the player to another server. No messages will be sent from the + * proxy when this result is used. + */ + public static final class RedirectPlayer implements ServerKickResult { + private final RegisteredServer server; - private final Component originalReason; - private final boolean duringLogin; - private ServerKickResult result; - public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, boolean duringLogin, Component fancyReason) { - this.player = Preconditions.checkNotNull(player, "player"); - this.server = Preconditions.checkNotNull(server, "server"); - this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason"); - this.duringLogin = duringLogin; - this.result = new DisconnectPlayer(fancyReason); + private RedirectPlayer(RegisteredServer server) { + this.server = Preconditions.checkNotNull(server, "server"); } @Override - public ServerKickResult getResult() { - return result; - } - - @Override - public void setResult(@NonNull ServerKickResult result) { - this.result = Preconditions.checkNotNull(result, "result"); - } - - public Player getPlayer() { - return player; + public boolean isAllowed() { + return false; } public RegisteredServer getServer() { - return server; - } - - public Component getOriginalReason() { - return originalReason; - } - - public boolean kickedDuringLogin() { - return duringLogin; + return server; } /** - * Represents the base interface for {@link KickedFromServerEvent} results. + * Creates a new redirect result to forward the player to the specified {@code server}. + * + * @param server the server to send the player to + * @return the redirect result */ - public interface ServerKickResult extends ResultedEvent.Result {} - - /** - * Tells the proxy to disconnect the player with the specified reason. - */ - public static final class DisconnectPlayer implements ServerKickResult { - private final Component component; - - private DisconnectPlayer(Component component) { - this.component = Preconditions.checkNotNull(component, "component"); - } - - @Override - public boolean isAllowed() { - return true; - } - - public Component getReason() { - return component; - } - - /** - * Creates a new {@link DisconnectPlayer} with the specified reason. - * @param reason the reason to use when disconnecting the player - * @return the disconnect result - */ - public static DisconnectPlayer create(Component reason) { - return new DisconnectPlayer(reason); - } - } - - /** - * Tells the proxy to redirect the player to another server. No messages will be sent from the proxy - * when this result is used. - */ - public static final class RedirectPlayer implements ServerKickResult { - private final RegisteredServer server; - - private RedirectPlayer(RegisteredServer server) { - this.server = Preconditions.checkNotNull(server, "server"); - } - - @Override - public boolean isAllowed() { - return false; - } - - public RegisteredServer getServer() { - return server; - } - - /** - * Creates a new redirect result to forward the player to the specified {@code server}. - * @param server the server to send the player to - * @return the redirect result - */ - public static RedirectPlayer create(RegisteredServer server) { - return new RedirectPlayer(server); - } + public static RedirectPlayer create(RegisteredServer server) { + return new RedirectPlayer(server); } + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerChatEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChatEvent.java index 7e3b6a987..03172f610 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/PlayerChatEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChatEvent.java @@ -3,95 +3,96 @@ package com.velocitypowered.api.event.player; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.Player; +import java.util.Optional; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Optional; - /** * This event is fired when a player types in a chat message. */ public final class PlayerChatEvent implements ResultedEvent { - private final Player player; - private final String message; - private ChatResult result; - public PlayerChatEvent(Player player, String message) { - this.player = Preconditions.checkNotNull(player, "player"); - this.message = Preconditions.checkNotNull(message, "message"); - this.result = ChatResult.allowed(); - } + private final Player player; + private final String message; + private ChatResult result; - public Player getPlayer() { - return player; - } + public PlayerChatEvent(Player player, String message) { + this.player = Preconditions.checkNotNull(player, "player"); + this.message = Preconditions.checkNotNull(message, "message"); + this.result = ChatResult.allowed(); + } - public String getMessage() { - return message; + public Player getPlayer() { + return player; + } + + public String getMessage() { + return message; + } + + @Override + public ChatResult getResult() { + return result; + } + + @Override + public void setResult(ChatResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + @Override + public String toString() { + return "PlayerChatEvent{" + + "player=" + player + + ", message=" + message + + ", result=" + result + + '}'; + } + + /** + * Represents the result of the {@link PlayerChatEvent}. + */ + public static final class ChatResult implements ResultedEvent.Result { + + private static final ChatResult ALLOWED = new ChatResult(true, null); + private static final ChatResult DENIED = new ChatResult(false, null); + + // The server can not accept formatted text from clients! + private @Nullable String message; + private final boolean status; + + protected ChatResult(boolean status, @Nullable String message) { + this.status = status; + this.message = message; } @Override - public ChatResult getResult() { - return result; - } - - @Override - public void setResult(ChatResult result) { - this.result = Preconditions.checkNotNull(result, "result"); + public boolean isAllowed() { + return status; } @Override public String toString() { - return "PlayerChatEvent{" + - "player=" + player + - ", message=" + message + - ", result=" + result + - '}'; + return status ? "allowed" : "denied"; } - /** - * Represents the result of the {@link PlayerChatEvent}. - */ - public static final class ChatResult implements ResultedEvent.Result { - private static final ChatResult ALLOWED = new ChatResult(true, null); - private static final ChatResult DENIED = new ChatResult(false, null); - - // The server can not accept formatted text from clients! - private @Nullable String message; - private final boolean status; - - protected ChatResult(boolean status, @Nullable String message) { - this.status = status; - this.message = message; - } - - @Override - public boolean isAllowed() { - return status; - } - - @Override - public String toString() { - return status ? "allowed" : "denied"; - } - - public static ChatResult allowed() { - return ALLOWED; - } - - public static ChatResult denied() { - return DENIED; - } - - public Optional getMessage() { - return Optional.ofNullable(message); - } - - public static ChatResult message(@NonNull String message) { - Preconditions.checkNotNull(message, "message"); - return new ChatResult(true, message); - } + public static ChatResult allowed() { + return ALLOWED; } + public static ChatResult denied() { + return DENIED; + } + + public Optional getMessage() { + return Optional.ofNullable(message); + } + + public static ChatResult message(@NonNull String message) { + Preconditions.checkNotNull(message, "message"); + return new ChatResult(true, message); + } + } + } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerModInfoEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerModInfoEvent.java index 9144fc441..ee88fd4ef 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/PlayerModInfoEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerModInfoEvent.java @@ -8,19 +8,20 @@ import com.velocitypowered.api.util.ModInfo; * This event is fired when the players ModInfo is changed. */ public final class PlayerModInfoEvent { - private final Player player; - private final ModInfo modInfo; - - public PlayerModInfoEvent(Player player, ModInfo modInfo) { - this.player = Preconditions.checkNotNull(player, "player"); - this.modInfo = Preconditions.checkNotNull(modInfo, "modInfo"); - } - - public Player getPlayer() { - return player; - } - - public ModInfo getModInfo() { - return modInfo; - } + + private final Player player; + private final ModInfo modInfo; + + public PlayerModInfoEvent(Player player, ModInfo modInfo) { + this.player = Preconditions.checkNotNull(player, "player"); + this.modInfo = Preconditions.checkNotNull(modInfo, "modInfo"); + } + + public Player getPlayer() { + return player; + } + + public ModInfo getModInfo() { + return modInfo; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerSettingsChangedEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerSettingsChangedEvent.java index 268bb0083..62b8bfb8c 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/PlayerSettingsChangedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerSettingsChangedEvent.java @@ -5,19 +5,20 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.player.PlayerSettings; public final class PlayerSettingsChangedEvent { - private final Player player; - private final PlayerSettings playerSettings; - public PlayerSettingsChangedEvent(Player player, PlayerSettings playerSettings) { - this.player = Preconditions.checkNotNull(player, "player"); - this.playerSettings = Preconditions.checkNotNull(playerSettings, "playerSettings"); - } + private final Player player; + private final PlayerSettings playerSettings; - public Player getPlayer() { - return player; - } + public PlayerSettingsChangedEvent(Player player, PlayerSettings playerSettings) { + this.player = Preconditions.checkNotNull(player, "player"); + this.playerSettings = Preconditions.checkNotNull(playerSettings, "playerSettings"); + } - public PlayerSettings getPlayerSettings() { - return playerSettings; - } + public Player getPlayer() { + return player; + } + + public PlayerSettings getPlayerSettings() { + return playerSettings; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java index 33db0878f..0018b6011 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/ServerConnectedEvent.java @@ -5,31 +5,32 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; /** - * This event is fired once the player has successfully connected to the target server and the connection to the previous - * server has been de-established. + * This event is fired once the player has successfully connected to the target server and the + * connection to the previous server has been de-established. */ public final class ServerConnectedEvent { - private final Player player; - private final RegisteredServer server; - public ServerConnectedEvent(Player player, RegisteredServer server) { - this.player = Preconditions.checkNotNull(player, "player"); - this.server = Preconditions.checkNotNull(server, "server"); - } + private final Player player; + private final RegisteredServer server; - public Player getPlayer() { - return player; - } + public ServerConnectedEvent(Player player, RegisteredServer server) { + this.player = Preconditions.checkNotNull(player, "player"); + this.server = Preconditions.checkNotNull(server, "server"); + } - public RegisteredServer getServer() { - return server; - } + public Player getPlayer() { + return player; + } - @Override - public String toString() { - return "ServerConnectedEvent{" + - "player=" + player + - ", server=" + server + - '}'; - } + public RegisteredServer getServer() { + return server; + } + + @Override + public String toString() { + return "ServerConnectedEvent{" + + "player=" + player + + ", server=" + server + + '}'; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java index 769c78507..eca7692a1 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/ServerPreConnectEvent.java @@ -4,87 +4,89 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Optional; +import org.checkerframework.checker.nullness.qual.Nullable; /** * This event is fired before the player connects to a server. */ -public final class ServerPreConnectEvent implements ResultedEvent { - private final Player player; - private final RegisteredServer originalServer; - private ServerResult result; +public final class ServerPreConnectEvent implements + ResultedEvent { - public ServerPreConnectEvent(Player player, RegisteredServer originalServer) { - this.player = Preconditions.checkNotNull(player, "player"); - this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer"); - this.result = ServerResult.allowed(originalServer); - } + private final Player player; + private final RegisteredServer originalServer; + private ServerResult result; - public Player getPlayer() { - return player; + public ServerPreConnectEvent(Player player, RegisteredServer originalServer) { + this.player = Preconditions.checkNotNull(player, "player"); + this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer"); + this.result = ServerResult.allowed(originalServer); + } + + public Player getPlayer() { + return player; + } + + @Override + public ServerResult getResult() { + return result; + } + + @Override + public void setResult(ServerResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + public RegisteredServer getOriginalServer() { + return originalServer; + } + + @Override + public String toString() { + return "ServerPreConnectEvent{" + + "player=" + player + + ", originalServer=" + originalServer + + ", result=" + result + + '}'; + } + + /** + * Represents the result of the {@link ServerPreConnectEvent}. + */ + public static class ServerResult implements ResultedEvent.Result { + + private static final ServerResult DENIED = new ServerResult(null); + + private final @Nullable RegisteredServer server; + + private ServerResult(@Nullable RegisteredServer server) { + this.server = server; } @Override - public ServerResult getResult() { - return result; + public boolean isAllowed() { + return server != null; } - @Override - public void setResult(ServerResult result) { - this.result = Preconditions.checkNotNull(result, "result"); - } - - public RegisteredServer getOriginalServer() { - return originalServer; + public Optional getServer() { + return Optional.ofNullable(server); } @Override public String toString() { - return "ServerPreConnectEvent{" + - "player=" + player + - ", originalServer=" + originalServer + - ", result=" + result + - '}'; + if (server != null) { + return "allowed: connect to " + server.getServerInfo().getName(); + } + return "denied"; } - /** - * Represents the result of the {@link ServerPreConnectEvent}. - */ - public static class ServerResult implements ResultedEvent.Result { - private static final ServerResult DENIED = new ServerResult(null); - - private final @Nullable RegisteredServer server; - - private ServerResult(@Nullable RegisteredServer server) { - this.server = server; - } - - @Override - public boolean isAllowed() { - return server != null; - } - - public Optional getServer() { - return Optional.ofNullable(server); - } - - @Override - public String toString() { - if (server != null) { - return "allowed: connect to " + server.getServerInfo().getName(); - } - return "denied"; - } - - public static ServerResult denied() { - return DENIED; - } - - public static ServerResult allowed(RegisteredServer server) { - Preconditions.checkNotNull(server, "server"); - return new ServerResult(server); - } + public static ServerResult denied() { + return DENIED; } + + public static ServerResult allowed(RegisteredServer server) { + Preconditions.checkNotNull(server, "server"); + return new ServerResult(server); + } + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyInitializeEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyInitializeEvent.java index ea55e9515..7a439fa8f 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyInitializeEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyInitializeEvent.java @@ -1,11 +1,13 @@ package com.velocitypowered.api.event.proxy; /** - * This event is fired by the proxy after plugins have been loaded but before the proxy starts accepting connections. + * This event is fired by the proxy after plugins have been loaded but before the proxy starts + * accepting connections. */ public final class ProxyInitializeEvent { - @Override - public String toString() { - return "ProxyInitializeEvent"; - } + + @Override + public String toString() { + return "ProxyInitializeEvent"; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java index aa8b605b3..0b21dd18d 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java @@ -8,31 +8,32 @@ import com.velocitypowered.api.proxy.server.ServerPing; * This event is fired when a server list ping request is sent by a remote client. */ public final class ProxyPingEvent { - private final InboundConnection connection; - private ServerPing ping; - public ProxyPingEvent(InboundConnection connection, ServerPing ping) { - this.connection = Preconditions.checkNotNull(connection, "connection"); - this.ping = Preconditions.checkNotNull(ping, "ping"); - } + private final InboundConnection connection; + private ServerPing ping; - public InboundConnection getConnection() { - return connection; - } + public ProxyPingEvent(InboundConnection connection, ServerPing ping) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + this.ping = Preconditions.checkNotNull(ping, "ping"); + } - public ServerPing getPing() { - return ping; - } + public InboundConnection getConnection() { + return connection; + } - public void setPing(ServerPing ping) { - this.ping = Preconditions.checkNotNull(ping, "ping"); - } + public ServerPing getPing() { + return ping; + } - @Override - public String toString() { - return "ProxyPingEvent{" + - "connection=" + connection + - ", ping=" + ping + - '}'; - } + public void setPing(ServerPing ping) { + this.ping = Preconditions.checkNotNull(ping, "ping"); + } + + @Override + public String toString() { + return "ProxyPingEvent{" + + "connection=" + connection + + ", ping=" + ping + + '}'; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyShutdownEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyShutdownEvent.java index 76bfac5e7..dc99fde0c 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyShutdownEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyShutdownEvent.java @@ -1,12 +1,13 @@ package com.velocitypowered.api.event.proxy; /** - * This event is fired by the proxy after the proxy has stopped accepting connections but before the proxy process - * exits. + * This event is fired by the proxy after the proxy has stopped accepting connections but before the + * proxy process exits. */ public final class ProxyShutdownEvent { - @Override - public String toString() { - return "ProxyShutdownEvent"; - } + + @Override + public String toString() { + return "ProxyShutdownEvent"; + } } diff --git a/api/src/main/java/com/velocitypowered/api/event/query/ProxyQueryEvent.java b/api/src/main/java/com/velocitypowered/api/event/query/ProxyQueryEvent.java index 7b64ffbf5..73344be63 100644 --- a/api/src/main/java/com/velocitypowered/api/event/query/ProxyQueryEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/query/ProxyQueryEvent.java @@ -2,82 +2,86 @@ package com.velocitypowered.api.event.query; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.server.QueryResponse; -import org.checkerframework.checker.nullness.qual.NonNull; - import java.net.InetAddress; +import org.checkerframework.checker.nullness.qual.NonNull; /** * This event is fired if proxy is getting queried over GS4 Query protocol */ public final class ProxyQueryEvent { - private final QueryType queryType; - private final InetAddress querierAddress; - private QueryResponse response; - public ProxyQueryEvent(QueryType queryType, InetAddress querierAddress, QueryResponse response) { - this.queryType = Preconditions.checkNotNull(queryType, "queryType"); - this.querierAddress = Preconditions.checkNotNull(querierAddress, "querierAddress"); - this.response = Preconditions.checkNotNull(response, "response"); - } + private final QueryType queryType; + private final InetAddress querierAddress; + private QueryResponse response; + + public ProxyQueryEvent(QueryType queryType, InetAddress querierAddress, QueryResponse response) { + this.queryType = Preconditions.checkNotNull(queryType, "queryType"); + this.querierAddress = Preconditions.checkNotNull(querierAddress, "querierAddress"); + this.response = Preconditions.checkNotNull(response, "response"); + } + + /** + * Get query type + * + * @return query type + */ + @NonNull + public QueryType getQueryType() { + return queryType; + } + + /** + * Get querier address + * + * @return querier address + */ + @NonNull + public InetAddress getQuerierAddress() { + return querierAddress; + } + + /** + * Get query response + * + * @return query response + */ + @NonNull + public QueryResponse getResponse() { + return response; + } + + /** + * Set query response + * + * @param response query response + */ + public void setResponse(@NonNull QueryResponse response) { + this.response = Preconditions.checkNotNull(response, "response"); + } + + @Override + public String toString() { + return "ProxyQueryEvent{" + + "queryType=" + queryType + + ", querierAddress=" + querierAddress + + ", response=" + response + + '}'; + } + + /** + * The type of query + */ + public enum QueryType { + /** + * Basic query asks only a subset of information, such as hostname, game type (hardcoded to + *
MINECRAFT
), map, current players, max players, proxy port and proxy hostname + */ + BASIC, /** - * Get query type - * @return query type + * Full query asks pretty much everything present on this event (only hardcoded values cannot be + * modified here). */ - @NonNull - public QueryType getQueryType() { - return queryType; - } - - /** - * Get querier address - * @return querier address - */ - @NonNull - public InetAddress getQuerierAddress() { - return querierAddress; - } - - /** - * Get query response - * @return query response - */ - @NonNull - public QueryResponse getResponse() { - return response; - } - - /** - * Set query response - * @param response query response - */ - public void setResponse(@NonNull QueryResponse response) { - this.response = Preconditions.checkNotNull(response, "response"); - } - - @Override - public String toString() { - return "ProxyQueryEvent{" + - "queryType=" + queryType + - ", querierAddress=" + querierAddress + - ", response=" + response + - '}'; - } - - /** - * The type of query - */ - public enum QueryType { - /** - * Basic query asks only a subset of information, such as hostname, game type (hardcoded to
MINECRAFT
), map, - * current players, max players, proxy port and proxy hostname - */ - BASIC, - - /** - * Full query asks pretty much everything present on this event (only hardcoded values cannot be modified here). - */ - FULL - ; - } + FULL; + } } diff --git a/api/src/main/java/com/velocitypowered/api/permission/PermissionFunction.java b/api/src/main/java/com/velocitypowered/api/permission/PermissionFunction.java index 21131fe11..98f2cc707 100644 --- a/api/src/main/java/com/velocitypowered/api/permission/PermissionFunction.java +++ b/api/src/main/java/com/velocitypowered/api/permission/PermissionFunction.java @@ -1,31 +1,31 @@ package com.velocitypowered.api.permission; /** - * Function that calculates the permission settings for a given - * {@link PermissionSubject}. + * Function that calculates the permission settings for a given {@link PermissionSubject}. */ @FunctionalInterface public interface PermissionFunction { - /** - * A permission function that always returns {@link Tristate#TRUE}. - */ - PermissionFunction ALWAYS_TRUE = p -> Tristate.TRUE; - /** - * A permission function that always returns {@link Tristate#FALSE}. - */ - PermissionFunction ALWAYS_FALSE = p -> Tristate.FALSE; + /** + * A permission function that always returns {@link Tristate#TRUE}. + */ + PermissionFunction ALWAYS_TRUE = p -> Tristate.TRUE; - /** - * A permission function that always returns {@link Tristate#UNDEFINED}. - */ - PermissionFunction ALWAYS_UNDEFINED = p -> Tristate.UNDEFINED; + /** + * A permission function that always returns {@link Tristate#FALSE}. + */ + PermissionFunction ALWAYS_FALSE = p -> Tristate.FALSE; - /** - * Gets the subjects setting for a particular permission. - * - * @param permission the permission - * @return the value the permission is set to - */ - Tristate getPermissionValue(String permission); + /** + * A permission function that always returns {@link Tristate#UNDEFINED}. + */ + PermissionFunction ALWAYS_UNDEFINED = p -> Tristate.UNDEFINED; + + /** + * Gets the subjects setting for a particular permission. + * + * @param permission the permission + * @return the value the permission is set to + */ + Tristate getPermissionValue(String permission); } diff --git a/api/src/main/java/com/velocitypowered/api/permission/PermissionProvider.java b/api/src/main/java/com/velocitypowered/api/permission/PermissionProvider.java index 70df2adc5..d6fdc507e 100644 --- a/api/src/main/java/com/velocitypowered/api/permission/PermissionProvider.java +++ b/api/src/main/java/com/velocitypowered/api/permission/PermissionProvider.java @@ -5,11 +5,12 @@ package com.velocitypowered.api.permission; */ @FunctionalInterface public interface PermissionProvider { - /** - * Creates a {@link PermissionFunction} for the subject. - * - * @param subject the subject - * @return the function - */ - PermissionFunction createFunction(PermissionSubject subject); + + /** + * Creates a {@link PermissionFunction} for the subject. + * + * @param subject the subject + * @return the function + */ + PermissionFunction createFunction(PermissionSubject subject); } diff --git a/api/src/main/java/com/velocitypowered/api/permission/PermissionSubject.java b/api/src/main/java/com/velocitypowered/api/permission/PermissionSubject.java index 2b6b50980..85a7a0958 100644 --- a/api/src/main/java/com/velocitypowered/api/permission/PermissionSubject.java +++ b/api/src/main/java/com/velocitypowered/api/permission/PermissionSubject.java @@ -4,21 +4,22 @@ package com.velocitypowered.api.permission; * Represents a object that has a set of queryable permissions. */ public interface PermissionSubject { - /** - * Determines whether or not the subject has a particular permission. - * - * @param permission the permission to check for - * @return whether or not the subject has the permission - */ - default boolean hasPermission(String permission) { - return getPermissionValue(permission).asBoolean(); - } - /** - * Gets the subjects setting for a particular permission. - * - * @param permission the permission - * @return the value the permission is set to - */ - Tristate getPermissionValue(String permission); + /** + * Determines whether or not the subject has a particular permission. + * + * @param permission the permission to check for + * @return whether or not the subject has the permission + */ + default boolean hasPermission(String permission) { + return getPermissionValue(permission).asBoolean(); + } + + /** + * Gets the subjects setting for a particular permission. + * + * @param permission the permission + * @return the value the permission is set to + */ + Tristate getPermissionValue(String permission); } diff --git a/api/src/main/java/com/velocitypowered/api/permission/Tristate.java b/api/src/main/java/com/velocitypowered/api/permission/Tristate.java index 876ae707f..854fb818c 100644 --- a/api/src/main/java/com/velocitypowered/api/permission/Tristate.java +++ b/api/src/main/java/com/velocitypowered/api/permission/Tristate.java @@ -8,69 +8,70 @@ import org.checkerframework.checker.nullness.qual.Nullable; *

Possible values:

*

*
    - *
  • {@link #TRUE} - a positive setting
  • - *
  • {@link #FALSE} - a negative (negated) setting
  • - *
  • {@link #UNDEFINED} - a non-existent setting
  • + *
  • {@link #TRUE} - a positive setting
  • + *
  • {@link #FALSE} - a negative (negated) setting
  • + *
  • {@link #UNDEFINED} - a non-existent setting
  • *
*/ public enum Tristate { - /** - * A value indicating a positive setting - */ - TRUE(true), + /** + * A value indicating a positive setting + */ + TRUE(true), - /** - * A value indicating a negative (negated) setting - */ - FALSE(false), + /** + * A value indicating a negative (negated) setting + */ + FALSE(false), - /** - * A value indicating a non-existent setting - */ - UNDEFINED(false); + /** + * A value indicating a non-existent setting + */ + UNDEFINED(false); - /** - * Returns a {@link Tristate} from a boolean - * - * @param val the boolean value - * @return {@link #TRUE} or {@link #FALSE}, if the value is true or false, respectively. - */ - public static Tristate fromBoolean(boolean val) { - return val ? TRUE : FALSE; + /** + * Returns a {@link Tristate} from a boolean + * + * @param val the boolean value + * @return {@link #TRUE} or {@link #FALSE}, if the value is true or + * false, respectively. + */ + public static Tristate fromBoolean(boolean val) { + return val ? TRUE : FALSE; + } + + /** + * Returns a {@link Tristate} from a nullable boolean. + * + *

Unlike {@link #fromBoolean(boolean)}, this method returns {@link #UNDEFINED} + * if the value is null.

+ * + * @param val the boolean value + * @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value is null, + * true or false, respectively. + */ + public static Tristate fromNullableBoolean(@Nullable Boolean val) { + if (val == null) { + return UNDEFINED; } + return val ? TRUE : FALSE; + } - /** - * Returns a {@link Tristate} from a nullable boolean. - * - *

Unlike {@link #fromBoolean(boolean)}, this method returns {@link #UNDEFINED} - * if the value is null.

- * - * @param val the boolean value - * @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value - * is null, true or false, respectively. - */ - public static Tristate fromNullableBoolean(@Nullable Boolean val) { - if (val == null) { - return UNDEFINED; - } - return val ? TRUE : FALSE; - } + private final boolean booleanValue; - private final boolean booleanValue; + Tristate(boolean booleanValue) { + this.booleanValue = booleanValue; + } - Tristate(boolean booleanValue) { - this.booleanValue = booleanValue; - } - - /** - * Returns the value of the Tristate as a boolean. - * - *

A value of {@link #UNDEFINED} converts to false.

- * - * @return a boolean representation of the Tristate. - */ - public boolean asBoolean() { - return this.booleanValue; - } + /** + * Returns the value of the Tristate as a boolean. + * + *

A value of {@link #UNDEFINED} converts to false.

+ * + * @return a boolean representation of the Tristate. + */ + public boolean asBoolean() { + return this.booleanValue; + } } diff --git a/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java b/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java index 031f4598e..618a1fa23 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java @@ -10,19 +10,19 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Dependency { - /** - * The plugin ID of the dependency. - * - * @return The dependency plugin ID - * @see Plugin#id() - */ - String id(); - /** - * If this dependency is optional for the plugin to work. By default - * this is {@code false}. - * - * @return true if the dependency is optional for the plugin to work - */ - boolean optional() default false; + /** + * The plugin ID of the dependency. + * + * @return The dependency plugin ID + * @see Plugin#id() + */ + String id(); + + /** + * If this dependency is optional for the plugin to work. By default this is {@code false}. + * + * @return true if the dependency is optional for the plugin to work + */ + boolean optional() default false; } diff --git a/api/src/main/java/com/velocitypowered/api/plugin/InvalidPluginException.java b/api/src/main/java/com/velocitypowered/api/plugin/InvalidPluginException.java index bd9bdad24..ac48c06fe 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/InvalidPluginException.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/InvalidPluginException.java @@ -1,19 +1,20 @@ package com.velocitypowered.api.plugin; public class InvalidPluginException extends Exception { - public InvalidPluginException() { - super(); - } - public InvalidPluginException(String message) { - super(message); - } + public InvalidPluginException() { + super(); + } - public InvalidPluginException(String message, Throwable cause) { - super(message, cause); - } + public InvalidPluginException(String message) { + super(message); + } - public InvalidPluginException(Throwable cause) { - super(cause); - } + public InvalidPluginException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidPluginException(Throwable cause) { + super(cause); + } } diff --git a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java index b3e603f13..d2437523d 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/Plugin.java @@ -11,56 +11,55 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Plugin { - /** - * The ID of the plugin. This ID should be unique as to - * not conflict with other plugins. - * - * The plugin ID must match the {@link PluginDescription#ID_PATTERN}. - * - * @return the ID for this plugin - */ - String id(); - /** - * The human readable name of the plugin as to be used in descriptions and - * similar things. - * - * @return The plugin name, or an empty string if unknown - */ - String name() default ""; + /** + * The ID of the plugin. This ID should be unique as to not conflict with other plugins. + * + * The plugin ID must match the {@link PluginDescription#ID_PATTERN}. + * + * @return the ID for this plugin + */ + String id(); - /** - * The version of the plugin. - * - * @return the version of the plugin, or an empty string if unknown - */ - String version() default ""; + /** + * The human readable name of the plugin as to be used in descriptions and similar things. + * + * @return The plugin name, or an empty string if unknown + */ + String name() default ""; - /** - * The description of the plugin, explaining what it can be used for. - * - * @return The plugin description, or an empty string if unknown - */ - String description() default ""; + /** + * The version of the plugin. + * + * @return the version of the plugin, or an empty string if unknown + */ + String version() default ""; - /** - * The URL or website of the plugin. - * - * @return The plugin url, or an empty string if unknown - */ - String url() default ""; + /** + * The description of the plugin, explaining what it can be used for. + * + * @return The plugin description, or an empty string if unknown + */ + String description() default ""; - /** - * The author of the plugin. - * - * @return the plugin's author, or empty if unknown - */ - String[] authors() default ""; + /** + * The URL or website of the plugin. + * + * @return The plugin url, or an empty string if unknown + */ + String url() default ""; - /** - * The dependencies required to load before this plugin. - * - * @return the plugin dependencies - */ - Dependency[] dependencies() default {}; + /** + * The author of the plugin. + * + * @return the plugin's author, or empty if unknown + */ + String[] authors() default ""; + + /** + * The dependencies required to load before this plugin. + * + * @return the plugin dependencies + */ + Dependency[] dependencies() default {}; } 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 b9c1cf2ad..1c0e8a422 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginContainer.java @@ -6,19 +6,20 @@ import java.util.Optional; * A wrapper around a plugin loaded by the proxy. */ public interface PluginContainer { - /** - * Returns the plugin's description. - * - * @return the plugin's description - */ - PluginDescription getDescription(); - /** - * Returns the created plugin if it is available. - * - * @return the instance if available - */ - default Optional getInstance() { - return Optional.empty(); - } + /** + * Returns the plugin's description. + * + * @return the plugin's description + */ + PluginDescription getDescription(); + + /** + * Returns the created plugin if it is available. + * + * @return the instance if available + */ + default Optional getInstance() { + return Optional.empty(); + } } diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java index d789f68c5..b5b925273 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java @@ -3,7 +3,6 @@ package com.velocitypowered.api.plugin; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.velocitypowered.api.plugin.meta.PluginDependency; - import java.nio.file.Path; import java.util.Collection; import java.util.List; @@ -14,93 +13,92 @@ import java.util.regex.Pattern; * Represents metadata for a specific version of a plugin. */ public interface PluginDescription { - /** - * The pattern plugin IDs must match. Plugin IDs may only contain - * alphanumeric characters, dashes or underscores, must start with - * an alphabetic character and cannot be longer than 64 characters. - */ - Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}"); - /** - * Gets the qualified ID of the {@link Plugin} within this container. - * - * @return the plugin ID - * @see Plugin#id() - */ - String getId(); + /** + * The pattern plugin IDs must match. Plugin IDs may only contain alphanumeric characters, dashes + * or underscores, must start with an alphabetic character and cannot be longer than 64 + * characters. + */ + Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}"); - /** - * Gets the name of the {@link Plugin} within this container. - * - * @return an {@link Optional} with the plugin name, may be empty - * @see Plugin#name() - */ - default Optional getName() { - return Optional.empty(); - } + /** + * Gets the qualified ID of the {@link Plugin} within this container. + * + * @return the plugin ID + * @see Plugin#id() + */ + String getId(); - /** - * Gets the version of the {@link Plugin} within this container. - * - * @return an {@link Optional} with the plugin version, may be empty - * @see Plugin#version() - */ - default Optional getVersion() { - return Optional.empty(); - } + /** + * Gets the name of the {@link Plugin} within this container. + * + * @return an {@link Optional} with the plugin name, may be empty + * @see Plugin#name() + */ + default Optional getName() { + return Optional.empty(); + } - /** - * Gets the description of the {@link Plugin} within this container. - * - * @return an {@link Optional} with the plugin description, may be empty - * @see Plugin#description() - */ - default Optional getDescription() { - return Optional.empty(); - } + /** + * Gets the version of the {@link Plugin} within this container. + * + * @return an {@link Optional} with the plugin version, may be empty + * @see Plugin#version() + */ + default Optional getVersion() { + return Optional.empty(); + } - /** - * Gets the url or website of the {@link Plugin} within this container. - * - * @return an {@link Optional} with the plugin url, may be empty - * @see Plugin#url() - */ - default Optional getUrl() { - return Optional.empty(); - } + /** + * Gets the description of the {@link Plugin} within this container. + * + * @return an {@link Optional} with the plugin description, may be empty + * @see Plugin#description() + */ + default Optional getDescription() { + return Optional.empty(); + } - /** - * Gets the authors of the {@link Plugin} within this container. - * - * @return the plugin authors, may be empty - * @see Plugin#authors() - */ - default List getAuthors() { - return ImmutableList.of(); - } + /** + * Gets the url or website of the {@link Plugin} within this container. + * + * @return an {@link Optional} with the plugin url, may be empty + * @see Plugin#url() + */ + default Optional getUrl() { + return Optional.empty(); + } - /** - * Gets a {@link Collection} of all dependencies of the {@link Plugin} within - * this container. - * - * @return the plugin dependencies, can be empty - * @see Plugin#dependencies() - */ - default Collection getDependencies() { - return ImmutableSet.of(); - } + /** + * Gets the authors of the {@link Plugin} within this container. + * + * @return the plugin authors, may be empty + * @see Plugin#authors() + */ + default List getAuthors() { + return ImmutableList.of(); + } - default Optional getDependency(String id) { - return Optional.empty(); - } + /** + * Gets a {@link Collection} of all dependencies of the {@link Plugin} within this container. + * + * @return the plugin dependencies, can be empty + * @see Plugin#dependencies() + */ + default Collection getDependencies() { + return ImmutableSet.of(); + } - /** - * Returns the source the plugin was loaded from. - * - * @return the source the plugin was loaded from or {@link Optional#empty()} - * if unknown - */ - default Optional getSource() { - return Optional.empty(); - } + default Optional getDependency(String id) { + return Optional.empty(); + } + + /** + * Returns the source the plugin was loaded from. + * + * @return the source the plugin was loaded from or {@link Optional#empty()} if unknown + */ + default Optional getSource() { + return Optional.empty(); + } } 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 744487d19..36ac27cfb 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginManager.java @@ -5,47 +5,49 @@ import java.util.Collection; import java.util.Optional; /** - * 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)}. + * 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 { - /** - * Gets the plugin container from an instance. - * - * @param instance the instance - * @return the container - */ - Optional fromInstance(Object instance); - /** - * Retrieves a {@link PluginContainer} based on its ID. - * - * @param id the plugin ID - * @return the plugin, if available - */ - Optional getPlugin(String id); + /** + * Gets the plugin container from an instance. + * + * @param instance the instance + * @return the container + */ + Optional fromInstance(Object instance); - /** - * Gets a {@link Collection} of all {@link PluginContainer}s. - * - * @return the plugins - */ - Collection getPlugins(); + /** + * Retrieves a {@link PluginContainer} based on its ID. + * + * @param id the plugin ID + * @return the plugin, if available + */ + Optional getPlugin(String id); - /** - * Checks if a plugin is loaded based on its ID. - * - * @param id the id of the plugin - * @return {@code true} if loaded - */ - boolean isLoaded(String id); + /** + * Gets a {@link Collection} of all {@link PluginContainer}s. + * + * @return the plugins + */ + Collection getPlugins(); - /** - * Adds the specified {@code path} to the plugin classpath. - * - * @param plugin the plugin - * @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(Object plugin, Path path); + /** + * Checks if a plugin is loaded based on its ID. + * + * @param id the id of the plugin + * @return {@code true} if loaded + */ + boolean isLoaded(String id); + + /** + * Adds the specified {@code path} to the plugin classpath. + * + * @param plugin the plugin + * @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(Object plugin, Path path); } diff --git a/api/src/main/java/com/velocitypowered/api/plugin/annotation/DataDirectory.java b/api/src/main/java/com/velocitypowered/api/plugin/annotation/DataDirectory.java index aa120d94e..c3575cf55 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/annotation/DataDirectory.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/annotation/DataDirectory.java @@ -1,18 +1,18 @@ package com.velocitypowered.api.plugin.annotation; import com.google.inject.BindingAnnotation; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * This annotation requests that Velocity inject a {@link java.nio.file.Path} instance with a plugin-specific data - * directory. + * This annotation requests that Velocity inject a {@link java.nio.file.Path} instance with a + * plugin-specific data directory. */ @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @BindingAnnotation public @interface DataDirectory { + } 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 1238af8fa..6eea7ec27 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,80 +1,83 @@ package com.velocitypowered.api.plugin.meta; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.Objects; -import java.util.Optional; - import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.emptyToNull; +import java.util.Objects; +import java.util.Optional; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * Represents a dependency on another plugin. */ public final class PluginDependency { - private final String id; - @Nullable - private final String version; - private final boolean optional; + private final String id; + @Nullable + private final String version; - public PluginDependency(String id, @Nullable String version, boolean optional) { - this.id = checkNotNull(id, "id"); - checkArgument(!id.isEmpty(), "id cannot be empty"); - this.version = emptyToNull(version); - this.optional = optional; + private final boolean optional; + + public PluginDependency(String id, @Nullable String version, boolean optional) { + this.id = checkNotNull(id, "id"); + checkArgument(!id.isEmpty(), "id cannot be empty"); + this.version = emptyToNull(version); + this.optional = optional; + } + + /** + * Returns the plugin ID of this {@link PluginDependency} + * + * @return the plugin ID + */ + public String getId() { + return id; + } + + /** + * Returns the version this {@link PluginDependency} should match. + * + * @return an {@link Optional} with the plugin version, may be empty + */ + public Optional getVersion() { + return Optional.ofNullable(version); + } + + /** + * Returns whether the dependency is optional for the plugin to work correctly. + * + * @return true if dependency is optional + */ + public boolean isOptional() { + return optional; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; } - - /** - * Returns the plugin ID of this {@link PluginDependency} - * - * @return the plugin ID - */ - public String getId() { - return id; + if (o == null || getClass() != o.getClass()) { + return false; } + PluginDependency that = (PluginDependency) o; + return optional == that.optional && + Objects.equals(id, that.id) && + Objects.equals(version, that.version); + } - /** - * Returns the version this {@link PluginDependency} should match. - * - * @return an {@link Optional} with the plugin version, may be empty - */ - public Optional getVersion() { - return Optional.ofNullable(version); - } + @Override + public int hashCode() { + return Objects.hash(id, version, optional); + } - /** - * Returns whether the dependency is optional for the plugin to work - * correctly. - * - * @return true if dependency is optional - */ - public boolean isOptional() { - return optional; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PluginDependency that = (PluginDependency) o; - return optional == that.optional && - Objects.equals(id, that.id) && - Objects.equals(version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(id, version, optional); - } - - @Override - public String toString() { - return "PluginDependency{" + - "id='" + id + '\'' + - ", version='" + version + '\'' + - ", optional=" + optional + - '}'; - } + @Override + public String toString() { + return "PluginDependency{" + + "id='" + id + '\'' + + ", version='" + version + '\'' + + ", optional=" + optional + + '}'; + } } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java b/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java index e9108c645..40dcc000f 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ConnectionRequestBuilder.java @@ -1,91 +1,100 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.proxy.server.RegisteredServer; -import net.kyori.text.Component; - import java.util.Optional; import java.util.concurrent.CompletableFuture; +import net.kyori.text.Component; /** - * Provides a fluent interface to compose and send a connection request to another server behind the proxy. A connection - * request is created using {@link Player#createConnectionRequest(RegisteredServer)}. + * Provides a fluent interface to compose and send a connection request to another server behind the + * proxy. A connection request is created using {@link Player#createConnectionRequest(RegisteredServer)}. */ public interface ConnectionRequestBuilder { - /** - * Returns the server that this connection request represents. - * @return the server this request will connect to - */ - RegisteredServer getServer(); + + /** + * Returns the server that this connection request represents. + * + * @return the server this request will connect to + */ + RegisteredServer getServer(); + + /** + * Initiates the connection to the remote server and emits a result on the {@link + * CompletableFuture} after the user has logged on. No messages will be communicated to the + * client: the user is responsible for all error handling. + * + * @return a {@link CompletableFuture} representing the status of this connection + */ + CompletableFuture connect(); + + /** + * Initiates the connection to the remote server and emits a result on the {@link + * CompletableFuture} after the user has logged on. Velocity's own built-in handling will be used + * to provide errors to the client. + * + * @return a {@link CompletableFuture} representing the status of this connection + */ + CompletableFuture connectWithIndication(); + + /** + * Initiates the connection to the remote server without waiting for a result. Velocity will use + * generic error handling code to notify the user. + */ + void fireAndForget(); + + /** + * Represents the result of a connection request. + */ + interface Result { /** - * Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user - * has logged on. No messages will be communicated to the client: the user is responsible for all error handling. - * @return a {@link CompletableFuture} representing the status of this connection + * Determines whether or not the connection request was successful. + * + * @return whether or not the request succeeded */ - CompletableFuture connect(); - - /** - * Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user - * has logged on. Velocity's own built-in handling will be used to provide errors to the client. - * @return a {@link CompletableFuture} representing the status of this connection - */ - CompletableFuture connectWithIndication(); - - /** - * Initiates the connection to the remote server without waiting for a result. Velocity will use generic error - * handling code to notify the user. - */ - void fireAndForget(); - - /** - * Represents the result of a connection request. - */ - interface Result { - /** - * Determines whether or not the connection request was successful. - * @return whether or not the request succeeded - */ - default boolean isSuccessful() { - return getStatus() == Status.SUCCESS; - } - - /** - * Returns the status associated with this result. - * @return the status for this result - */ - Status getStatus(); - - /** - * Returns an (optional) textual reason for the failure to connect to the server. - * @return the reason why the user could not connect to the server - */ - Optional getReason(); + default boolean isSuccessful() { + return getStatus() == Status.SUCCESS; } /** - * Represents the status of a connection request initiated by a {@link ConnectionRequestBuilder}. + * Returns the status associated with this result. + * + * @return the status for this result */ - enum Status { - /** - * The player was successfully connected to the server. - */ - SUCCESS, - /** - * The player is already connected to this server. - */ - ALREADY_CONNECTED, - /** - * The connection is already in progress. - */ - CONNECTION_IN_PROGRESS, - /** - * A plugin has cancelled this connection. - */ - CONNECTION_CANCELLED, - /** - * The server disconnected the user. A reason may be provided in the {@link Result} object. - */ - SERVER_DISCONNECTED - } + Status getStatus(); + + /** + * Returns an (optional) textual reason for the failure to connect to the server. + * + * @return the reason why the user could not connect to the server + */ + Optional getReason(); + } + + /** + * Represents the status of a connection request initiated by a {@link ConnectionRequestBuilder}. + */ + enum Status { + /** + * The player was successfully connected to the server. + */ + SUCCESS, + /** + * The player is already connected to this server. + */ + ALREADY_CONNECTED, + /** + * The connection is already in progress. + */ + CONNECTION_IN_PROGRESS, + /** + * A plugin has cancelled this connection. + */ + CONNECTION_CANCELLED, + /** + * The server disconnected the user. A reason may be provided in the {@link Result} object. + */ + SERVER_DISCONNECTED + } } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java index e3cdcce24..9e442ff07 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java @@ -7,27 +7,32 @@ import java.util.Optional; * Represents an incoming connection to the proxy. */ public interface InboundConnection { - /** - * Returns the player's IP address. - * @return the player's IP - */ - InetSocketAddress getRemoteAddress(); - /** - * Returns the hostname that the user entered into the client, if applicable. - * @return the hostname from the client - */ - Optional getVirtualHost(); + /** + * Returns the player's IP address. + * + * @return the player's IP + */ + InetSocketAddress getRemoteAddress(); - /** - * Determine whether or not the player remains online. - * @return whether or not the player active - */ - boolean isActive(); + /** + * Returns the hostname that the user entered into the client, if applicable. + * + * @return the hostname from the client + */ + Optional getVirtualHost(); - /** - * Returns the current protocol version this connection uses. - * @return the protocol version the connection uses - */ - int getProtocolVersion(); + /** + * Determine whether or not the player remains online. + * + * @return whether or not the player active + */ + boolean isActive(); + + /** + * Returns the current protocol version this connection uses. + * + * @return the protocol version the connection uses + */ + int getProtocolVersion(); } 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 d6ba45da4..ebabc6c4e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -4,136 +4,150 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.player.PlayerSettings; +import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; -import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.api.util.title.Title; import java.util.List; - -import net.kyori.text.Component; - import java.util.Optional; import java.util.UUID; +import net.kyori.text.Component; /** * Represents a player who is connected to the proxy. */ -public interface Player extends CommandSource, InboundConnection, ChannelMessageSource, ChannelMessageSink { - /** - * Returns the player's current username. - * @return the username - */ - String getUsername(); +public interface Player extends CommandSource, InboundConnection, ChannelMessageSource, + ChannelMessageSink { - /** - * Returns the player's UUID. - * @return the UUID - */ - UUID getUniqueId(); + /** + * Returns the player's current username. + * + * @return the username + */ + String getUsername(); - /** - * Returns the server that the player is currently connected to. - * @return an {@link Optional} the server that the player is connected to, which may be empty - */ - Optional getCurrentServer(); + /** + * Returns the player's UUID. + * + * @return the UUID + */ + UUID getUniqueId(); - /** - * Returns the player settings - * @return the settings - */ - PlayerSettings getPlayerSettings(); - - /** - * Returns the player's mod info if they have a modded client. - * @return an {@link Optional} the mod info. which may be empty - */ - Optional getModInfo(); + /** + * Returns the server that the player is currently connected to. + * + * @return an {@link Optional} the server that the player is connected to, which may be empty + */ + Optional getCurrentServer(); - /** - * Returns the current player's ping - * @return the player's ping or -1 if ping information is currently unknown - */ - long getPing(); - - /** - * Sends a chat message to the player's client. - * @param component the chat message to send - */ - default void sendMessage(Component component) { - sendMessage(component, MessagePosition.CHAT); - } + /** + * Returns the player settings + * + * @return the settings + */ + PlayerSettings getPlayerSettings(); - /** - * Sends a chat message to the player's client in the specified position. - * @param component the chat message to send - * @param position the position for the message - */ - void sendMessage(Component component, MessagePosition position); + /** + * Returns the player's mod info if they have a modded client. + * + * @return an {@link Optional} the mod info. which may be empty + */ + Optional getModInfo(); - /** - * 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(RegisteredServer server); + /** + * Returns the current player's ping + * + * @return the player's ping or -1 if ping information is currently unknown + */ + long getPing(); - /** - * Gets the player's profile properties. - * - *

The returned list may be unmodifiable.

- * - * @return the player's profile properties - */ - List getGameProfileProperties(); - - /** - * Sets the player's profile properties. - * - * @param properties the properties - */ - void setGameProfileProperties(List properties); - - /** - * Sets the tab list header and footer for the player. - * @param header the header component - * @param footer the footer component - * @deprecated Use {@link TabList#setHeaderAndFooter(Component, Component)}. - */ - @Deprecated - void setHeaderAndFooter(Component header, Component footer); + /** + * Sends a chat message to the player's client. + * + * @param component the chat message to send + */ + default void sendMessage(Component component) { + sendMessage(component, MessagePosition.CHAT); + } - /** - * Clears the tab list header and footer for the player. - * @deprecated Use {@link TabList#clearHeaderAndFooter()}. - */ - @Deprecated - void clearHeaderAndFooter(); - - /** - * Returns the player's tab list. - * @return this player's tab list - */ - TabList getTabList(); + /** + * Sends a chat message to the player's client in the specified position. + * + * @param component the chat message to send + * @param position the position for the message + */ + void sendMessage(Component component, MessagePosition position); - /** - * Disconnects the player with the specified reason. Once this method is called, further calls to other {@link Player} - * methods will become undefined. - * @param reason component with the reason - */ - void disconnect(Component reason); + /** + * 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(RegisteredServer server); - /** - * Sends the specified title to the client. - * @param title the title to send - */ - void sendTitle(Title title); + /** + * Gets the player's profile properties. + * + *

The returned list may be unmodifiable.

+ * + * @return the player's profile properties + */ + List getGameProfileProperties(); - /** - * Sends chat input onto the players current server as if they typed it - * into the client chat box. - * @param input the chat input to send - */ - void spoofChatInput(String input); + /** + * Sets the player's profile properties. + * + * @param properties the properties + */ + void setGameProfileProperties(List properties); + + /** + * Sets the tab list header and footer for the player. + * + * @param header the header component + * @param footer the footer component + * @deprecated Use {@link TabList#setHeaderAndFooter(Component, Component)}. + */ + @Deprecated + void setHeaderAndFooter(Component header, Component footer); + + /** + * Clears the tab list header and footer for the player. + * + * @deprecated Use {@link TabList#clearHeaderAndFooter()}. + */ + @Deprecated + void clearHeaderAndFooter(); + + /** + * Returns the player's tab list. + * + * @return this player's tab list + */ + TabList getTabList(); + + /** + * Disconnects the player with the specified reason. Once this method is called, further calls to + * other {@link Player} methods will become undefined. + * + * @param reason component with the reason + */ + void disconnect(Component reason); + + /** + * Sends the specified title to the client. + * + * @param title the title to send + */ + void sendTitle(Title title); + + /** + * Sends chat input onto the players current server as if they typed it into the client chat box. + * + * @param input the chat input to send + */ + void spoofChatInput(String input); } 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 698c7466b..a3d0111f8 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -10,132 +10,151 @@ 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; import java.util.Collection; import java.util.Optional; import java.util.UUID; +import net.kyori.text.Component; /** * Provides an interface to a Minecraft server proxy. */ public interface ProxyServer { - /** - * Retrieves the player currently connected to this proxy by their Minecraft username. The search is case-insensitive. - * @param username the username to search for - * @return an {@link Optional} with the player, which may be empty - */ - Optional getPlayer(String username); - /** - * Retrieves the player currently connected to this proxy by their Minecraft UUID. - * @param uuid the UUID - * @return an {@link Optional} with the player, which may be empty - */ - Optional getPlayer(UUID uuid); + /** + * Retrieves the player currently connected to this proxy by their Minecraft username. The search + * is case-insensitive. + * + * @param username the username to search for + * @return an {@link Optional} with the player, which may be empty + */ + Optional getPlayer(String username); - /** - * Broadcasts a message to all players currently online. - * @param component the message to send - */ - void broadcast(Component component); + /** + * Retrieves the player currently connected to this proxy by their Minecraft UUID. + * + * @param uuid the UUID + * @return an {@link Optional} with the player, which may be empty + */ + Optional getPlayer(UUID uuid); - /** - * Retrieves all players currently connected to this proxy. This call may or may not be a snapshot of all players - * online. - * @return the players online on this proxy - */ - Collection getAllPlayers(); + /** + * Broadcasts a message to all players currently online. + * + * @param component the message to send + */ + void broadcast(Component component); - /** - * Returns the number of players currently connected to this proxy. - * @return the players on this proxy - */ - int getPlayerCount(); + /** + * Retrieves all players currently connected to this proxy. This call may or may not be a snapshot + * of all players online. + * + * @return the players online on this proxy + */ + Collection getAllPlayers(); - /** - * Retrieves a registered {@link RegisteredServer} instance by its name. The search is case-insensitive. - * @param name the name of the server - * @return the registered server, which may be empty - */ - Optional getServer(String name); + /** + * Returns the number of players currently connected to this proxy. + * + * @return the players on this proxy + */ + int getPlayerCount(); - /** - * Retrieves all {@link RegisteredServer}s registered with this proxy. - * @return the servers registered with this proxy - */ - Collection getAllServers(); + /** + * Retrieves a registered {@link RegisteredServer} instance by its name. The search is + * case-insensitive. + * + * @param name the name of the server + * @return the registered server, which may be empty + */ + Optional getServer(String name); - /** - * Registers a server with this proxy. A server with this name should not already exist. - * @param server the server to register - * @return the newly registered server - */ - RegisteredServer registerServer(ServerInfo server); + /** + * Retrieves all {@link RegisteredServer}s registered with this proxy. + * + * @return the servers registered with this proxy + */ + Collection getAllServers(); - /** - * Unregisters this server from the proxy. - * @param server the server to unregister - */ - void unregisterServer(ServerInfo server); + /** + * Registers a server with this proxy. A server with this name should not already exist. + * + * @param server the server to register + * @return the newly registered server + */ + RegisteredServer registerServer(ServerInfo server); - /** - * Returns an instance of {@link CommandSource} that can be used to determine if the command is being invoked by - * the console or a console-like executor. Plugins that execute commands are strongly urged to implement their own - * {@link CommandSource} instead of using the console invoker. - * @return the console command invoker - */ - CommandSource getConsoleCommandSource(); + /** + * Unregisters this server from the proxy. + * + * @param server the server to unregister + */ + void unregisterServer(ServerInfo server); - /** - * Gets the {@link PluginManager} instance. - * - * @return the plugin manager instance - */ - PluginManager getPluginManager(); + /** + * Returns an instance of {@link CommandSource} that can be used to determine if the command is + * being invoked by the console or a console-like executor. Plugins that execute commands are + * strongly urged to implement their own {@link CommandSource} instead of using the console + * invoker. + * + * @return the console command invoker + */ + CommandSource getConsoleCommandSource(); - /** - * Gets the {@link EventManager} instance. - * - * @return the event manager instance - */ - EventManager getEventManager(); + /** + * Gets the {@link PluginManager} instance. + * + * @return the plugin manager instance + */ + PluginManager getPluginManager(); - /** - * Gets the {@link CommandManager} instance. - * @return the command manager - */ - CommandManager getCommandManager(); + /** + * Gets the {@link EventManager} instance. + * + * @return the event manager instance + */ + EventManager getEventManager(); - /** - * Gets the {@link Scheduler} instance. - * @return the scheduler instance - */ - Scheduler getScheduler(); + /** + * Gets the {@link CommandManager} instance. + * + * @return the command manager + */ + CommandManager getCommandManager(); - /** - * Gets the {@link ChannelRegistrar} instance. - * @return the channel registrar - */ - ChannelRegistrar getChannelRegistrar(); + /** + * Gets the {@link Scheduler} instance. + * + * @return the scheduler instance + */ + Scheduler getScheduler(); - /** - * Gets the address that this proxy is bound to. This does not necessarily indicate the external IP address of the - * proxy. - * @return the address the proxy is bound to - */ - InetSocketAddress getBoundAddress(); + /** + * Gets the {@link ChannelRegistrar} instance. + * + * @return the channel registrar + */ + ChannelRegistrar getChannelRegistrar(); - /** - * Gets the {@link ProxyConfig} instance. - * @return the proxy config - * */ - ProxyConfig getConfiguration(); + /** + * Gets the address that this proxy is bound to. This does not necessarily indicate the external + * IP address of the proxy. + * + * @return the address the proxy is bound to + */ + InetSocketAddress getBoundAddress(); - /** - * Returns the version of the proxy. - * @return the proxy version - */ - ProxyVersion getVersion(); + /** + * Gets the {@link ProxyConfig} instance. + * + * @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/ServerConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java index b800131c0..72c93fe5f 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ServerConnection.java @@ -9,21 +9,25 @@ import com.velocitypowered.api.proxy.server.ServerInfo; * Represents a connection to a backend server from the proxy for a client. */ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSink { - /** - * Returns the server that this connection is connected to. - * @return the server this connection is connected to - */ - RegisteredServer getServer(); - /** - * Returns the server info for this connection. - * @return the server info for this connection - */ - ServerInfo getServerInfo(); + /** + * Returns the server that this connection is connected to. + * + * @return the server this connection is connected to + */ + RegisteredServer getServer(); - /** - * Returns the player that this connection is associated with. - * @return the player for this connection - */ - Player getPlayer(); + /** + * Returns the server info for this connection. + * + * @return the server info for this connection + */ + ServerInfo getServerInfo(); + + /** + * Returns the player that this connection is associated with. + * + * @return the player for this connection + */ + Player getPlayer(); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java b/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java index 851e62903..988931314 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java @@ -1,116 +1,133 @@ package com.velocitypowered.api.proxy.config; import com.velocitypowered.api.util.Favicon; -import net.kyori.text.Component; - import java.util.List; import java.util.Map; import java.util.Optional; +import net.kyori.text.Component; /** * Provides an interface to a proxy configuration */ public interface ProxyConfig { - /** - * Whether GameSpy 4 queries are accepted by the proxy - * @return queries enabled - */ - boolean isQueryEnabled(); - /** - * Get the port GameSpy 4 queries are accepted on - * @return the query port - */ - int getQueryPort(); + /** + * Whether GameSpy 4 queries are accepted by the proxy + * + * @return queries enabled + */ + boolean isQueryEnabled(); - /** - * Get the map name reported to GameSpy 4 query services - * @return the map name - */ - String getQueryMap(); + /** + * Get the port GameSpy 4 queries are accepted on + * + * @return the query port + */ + int getQueryPort(); - /** - * Whether GameSpy 4 queries should show plugins installed on - * Velocity by default - * @return show plugins in query - */ - boolean shouldQueryShowPlugins(); + /** + * Get the map name reported to GameSpy 4 query services + * + * @return the map name + */ + String getQueryMap(); - /** - * Get the MOTD component shown in the tab list - * @return the motd component - */ - Component getMotdComponent(); + /** + * Whether GameSpy 4 queries should show plugins installed on Velocity by default + * + * @return show plugins in query + */ + boolean shouldQueryShowPlugins(); - /** - * Get the maximum players shown in the tab list - * @return max players - */ - int getShowMaxPlayers(); + /** + * Get the MOTD component shown in the tab list + * + * @return the motd component + */ + Component getMotdComponent(); - /** - * Get whether the proxy is online mode. This determines if players are authenticated with Mojang servers. - * @return online mode enabled - */ - boolean isOnlineMode(); + /** + * Get the maximum players shown in the tab list + * + * @return max players + */ + int getShowMaxPlayers(); - /** - * Get a Map of all servers registered on this proxy - * @return registered servers map - */ - Map getServers(); + /** + * Get whether the proxy is online mode. This determines if players are authenticated with Mojang + * servers. + * + * @return online mode enabled + */ + boolean isOnlineMode(); - /** - * Get the order of servers that players will be connected to - * @return connection order list - */ - List getAttemptConnectionOrder(); + /** + * Get a Map of all servers registered on this proxy + * + * @return registered servers map + */ + Map getServers(); - /** - * Get forced servers mapped to given virtual host - * @return list of server names - */ - Map> getForcedHosts(); + /** + * Get the order of servers that players will be connected to + * + * @return connection order list + */ + List getAttemptConnectionOrder(); - /** - * Get the minimum compression threshold for packets - * @return the compression threshold - */ - int getCompressionThreshold(); + /** + * Get forced servers mapped to given virtual host + * + * @return list of server names + */ + Map> getForcedHosts(); - /** - * Get the level of compression that packets will be compressed to - * @return the compression level - */ - int getCompressionLevel(); + /** + * Get the minimum compression threshold for packets + * + * @return the compression threshold + */ + int getCompressionThreshold(); - /** - * Get the limit for how long a player must wait to log back in - * @return the login rate limit (in milliseconds) - */ - int getLoginRatelimit(); + /** + * Get the level of compression that packets will be compressed to + * + * @return the compression level + */ + int getCompressionLevel(); - /** - * Get the proxy favicon shown in the tablist - * @return optional favicon - */ - Optional getFavicon(); + /** + * Get the limit for how long a player must wait to log back in + * + * @return the login rate limit (in milliseconds) + */ + int getLoginRatelimit(); - /** - * Get whether this proxy displays that it supports Forge/FML - * @return forge announce enabled - */ - boolean isAnnounceForge(); + /** + * Get the proxy favicon shown in the tablist + * + * @return optional favicon + */ + Optional getFavicon(); - /** - * Get how long this proxy will wait until performing a read timeout - * @return connection timeout (in milliseconds) - */ - int getConnectTimeout(); + /** + * Get whether this proxy displays that it supports Forge/FML + * + * @return forge announce enabled + */ + boolean isAnnounceForge(); - /** - * Get how long this proxy will wait until performing a read timeout - * @return read timeout (in milliseconds) - */ - int getReadTimeout(); + /** + * Get how long this proxy will wait until performing a read timeout + * + * @return connection timeout (in milliseconds) + */ + int getConnectTimeout(); + + /** + * Get how long this proxy will wait until performing a read timeout + * + * @return read timeout (in milliseconds) + */ + int getReadTimeout(); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java index 4acd5d72a..5aff053c4 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelIdentifier.java @@ -4,9 +4,11 @@ package com.velocitypowered.api.proxy.messages; * Represents a kind of channel identifier. */ public interface ChannelIdentifier { - /** - * Returns the textual representation of this identifier. - * @return the textual representation of the identifier - */ - String getId(); + + /** + * Returns the textual representation of this identifier. + * + * @return the textual representation of the identifier + */ + String getId(); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java index 37f02e539..aca183a4c 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSink.java @@ -4,11 +4,13 @@ package com.velocitypowered.api.proxy.messages; * Represents something that can send plugin messages. */ public interface ChannelMessageSink { - /** - * Sends a plugin message to this target. - * @param identifier the channel identifier to send the message on - * @param data the data to send - * @return whether or not the message could be sent - */ - boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data); + + /** + * Sends a plugin message to this target. + * + * @param identifier the channel identifier to send the message on + * @param data the data to send + * @return whether or not the message could be sent + */ + boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java index 8cb85ef1c..8985ad83e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelMessageSource.java @@ -4,4 +4,5 @@ package com.velocitypowered.api.proxy.messages; * A marker interface that indicates a source of plugin messages. */ public interface ChannelMessageSource { + } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java index 84db8799d..dc8c5cd17 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/ChannelRegistrar.java @@ -1,18 +1,22 @@ package com.velocitypowered.api.proxy.messages; /** - * Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on. + * Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to + * listen on. */ public interface ChannelRegistrar { - /** - * Registers the specified message identifiers to listen on for the - * @param identifiers the channel identifiers to register - */ - void register(ChannelIdentifier... identifiers); - /** - * Unregisters the handler for the specified channel. - * @param identifiers the identifiers to unregister - */ - void unregister(ChannelIdentifier... identifiers); + /** + * Registers the specified message identifiers to listen on for the + * + * @param identifiers the channel identifiers to register + */ + void register(ChannelIdentifier... identifiers); + + /** + * Unregisters the handler for the specified channel. + * + * @param identifiers the identifiers to unregister + */ + void unregister(ChannelIdentifier... identifiers); } 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 b71983e96..ed298e2d5 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,46 +2,51 @@ package com.velocitypowered.api.proxy.messages; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Objects; - /** - * Reperesents a legacy channel identifier (for Minecraft 1.12 and below). For modern 1.13 plugin messages, please see - * {@link MinecraftChannelIdentifier}. This class is immutable and safe for multi-threaded use. + * Reperesents a legacy channel identifier (for Minecraft 1.12 and below). For modern 1.13 plugin + * messages, please see {@link MinecraftChannelIdentifier}. This class is immutable and safe for + * multi-threaded use. */ public final class LegacyChannelIdentifier implements ChannelIdentifier { - private final String name; - public LegacyChannelIdentifier(String name) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "provided name is empty"); - this.name = name; - } + private final String name; - public String getName() { - return name; - } + public LegacyChannelIdentifier(String name) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "provided name is empty"); + this.name = name; + } - @Override - public String toString() { - return name + " (legacy)"; - } + public String getName() { + return name; + } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - LegacyChannelIdentifier that = (LegacyChannelIdentifier) o; - return Objects.equals(name, that.name); - } + @Override + public String toString() { + return name + " (legacy)"; + } - @Override - public int hashCode() { - return Objects.hash(name); + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; } + if (o == null || getClass() != o.getClass()) { + return false; + } + LegacyChannelIdentifier that = (LegacyChannelIdentifier) o; + return Objects.equals(name, that.name); + } - @Override - public String getId() { - return this.getName(); - } + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String getId() { + return this.getName(); + } } 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 c6899e0d0..471ac64db 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,78 +2,87 @@ 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; +import org.checkerframework.checker.nullness.qual.Nullable; /** - * Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use. + * Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for + * multi-threaded use. */ public final class MinecraftChannelIdentifier implements ChannelIdentifier { - private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+"); - private final String namespace; - private final String name; + private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+"); - private MinecraftChannelIdentifier(String namespace, String name) { - this.namespace = namespace; - this.name = name; + private final String namespace; + private final String name; + + private MinecraftChannelIdentifier(String namespace, String name) { + this.namespace = namespace; + this.name = name; + } + + /** + * Creates an identifier in the default namespace ({@code minecraft}). Plugins are strongly + * encouraged to provide their own namespace. + * + * @param name the name in the default namespace to use + * @return a new channel identifier + */ + public static MinecraftChannelIdentifier forDefaultNamespace(String name) { + return new MinecraftChannelIdentifier("minecraft", name); + } + + /** + * Creates an identifier in the specified namespace. + * + * @param namespace the namespace to use + * @param name the channel name inside the specified namespace + * @return a new channel identifier + */ + public static MinecraftChannelIdentifier create(String namespace, String name) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "namespace is null or empty"); + Preconditions.checkArgument(VALID_IDENTIFIER_REGEX.matcher(namespace).matches(), + "namespace is not valid"); + Preconditions + .checkArgument(VALID_IDENTIFIER_REGEX.matcher(name).matches(), "name is not valid"); + return new MinecraftChannelIdentifier(namespace, name); + } + + public String getNamespace() { + return namespace; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return namespace + ":" + name + " (modern)"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; } - - /** - * Creates an identifier in the default namespace ({@code minecraft}). Plugins are strongly encouraged to provide - * their own namespace. - * @param name the name in the default namespace to use - * @return a new channel identifier - */ - public static MinecraftChannelIdentifier forDefaultNamespace(String name) { - return new MinecraftChannelIdentifier("minecraft", name); + if (o == null || getClass() != o.getClass()) { + return false; } + MinecraftChannelIdentifier that = (MinecraftChannelIdentifier) o; + return Objects.equals(namespace, that.namespace) && + Objects.equals(name, that.name); + } - /** - * Creates an identifier in the specified namespace. - * @param namespace the namespace to use - * @param name the channel name inside the specified namespace - * @return a new channel identifier - */ - public static MinecraftChannelIdentifier create(String namespace, String name) { - Preconditions.checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "namespace is null or empty"); - Preconditions.checkArgument(VALID_IDENTIFIER_REGEX.matcher(namespace).matches(), "namespace is not valid"); - Preconditions.checkArgument(VALID_IDENTIFIER_REGEX.matcher(name).matches(), "name is not valid"); - return new MinecraftChannelIdentifier(namespace, name); - } + @Override + public int hashCode() { + return Objects.hash(namespace, name); + } - public String getNamespace() { - return namespace; - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return namespace + ":" + name + " (modern)"; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MinecraftChannelIdentifier that = (MinecraftChannelIdentifier) o; - return Objects.equals(namespace, that.namespace) && - Objects.equals(name, that.name); - } - - @Override - public int hashCode() { - return Objects.hash(namespace, name); - } - - @Override - public String getId() { - return namespace + ":" + name; - } + @Override + public String getId() { + return namespace + ":" + name; + } } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/messages/package-info.java b/api/src/main/java/com/velocitypowered/api/proxy/messages/package-info.java index d55279722..753595884 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/messages/package-info.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/messages/package-info.java @@ -1,4 +1,5 @@ /** - * Provides an interface to receive, handle, and send plugin messages on the proxy from clients and servers. + * Provides an interface to receive, handle, and send plugin messages on the proxy from clients and + * servers. */ package com.velocitypowered.api.proxy.messages; \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/PlayerSettings.java b/api/src/main/java/com/velocitypowered/api/proxy/player/PlayerSettings.java index 050df101e..98d52806f 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/PlayerSettings.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/PlayerSettings.java @@ -6,51 +6,58 @@ import java.util.Locale; * Represents the client settings for the player. */ public interface PlayerSettings { - /** - * Returns the locale of the Minecraft client. - * @return the client locale - */ - Locale getLocale(); - /** - * Returns the client's view distance. This does not guarantee the client will see this many chunks, since your - * servers are responsible for sending the chunks. - * @return the client view distance - */ - byte getViewDistance(); + /** + * Returns the locale of the Minecraft client. + * + * @return the client locale + */ + Locale getLocale(); - /** - * Returns the chat setting for the client. - * @return the chat setting - */ - ChatMode getChatMode(); + /** + * Returns the client's view distance. This does not guarantee the client will see this many + * chunks, since your servers are responsible for sending the chunks. + * + * @return the client view distance + */ + byte getViewDistance(); - /** - * Returns whether or not the client has chat colors disabled. - * @return whether or not the client has chat colors disabled - */ - boolean hasChatColors(); + /** + * Returns the chat setting for the client. + * + * @return the chat setting + */ + ChatMode getChatMode(); - /** - * Returns the parts of player skins the client will show. - * @return the skin parts for the client - */ - SkinParts getSkinParts(); + /** + * Returns whether or not the client has chat colors disabled. + * + * @return whether or not the client has chat colors disabled + */ + boolean hasChatColors(); - /** - * Returns the primary hand of the client. - * @return the primary hand of the client - */ - MainHand getMainHand(); + /** + * Returns the parts of player skins the client will show. + * + * @return the skin parts for the client + */ + SkinParts getSkinParts(); - enum ChatMode { - SHOWN, - COMMANDS_ONLY, - HIDDEN - } + /** + * Returns the primary hand of the client. + * + * @return the primary hand of the client + */ + MainHand getMainHand(); - enum MainHand { - LEFT, - RIGHT - } + enum ChatMode { + SHOWN, + COMMANDS_ONLY, + HIDDEN + } + + enum MainHand { + LEFT, + RIGHT + } } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/SkinParts.java b/api/src/main/java/com/velocitypowered/api/proxy/player/SkinParts.java index c9fd2bbac..0a5d0833e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/SkinParts.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/SkinParts.java @@ -1,37 +1,38 @@ package com.velocitypowered.api.proxy.player; public final class SkinParts { - private final byte bitmask; - public SkinParts(byte skinBitmask) { - this.bitmask = skinBitmask; - } + private final byte bitmask; - public boolean hasCape() { - return (bitmask & 1) == 1; - } + public SkinParts(byte skinBitmask) { + this.bitmask = skinBitmask; + } - public boolean hasJacket() { - return ((bitmask >> 1) & 1) == 1; - } + public boolean hasCape() { + return (bitmask & 1) == 1; + } - public boolean hasLeftSleeve() { - return ((bitmask >> 2) & 1) == 1; - } + public boolean hasJacket() { + return ((bitmask >> 1) & 1) == 1; + } - public boolean hasRightSleeve() { - return ((bitmask >> 3) & 1) == 1; - } + public boolean hasLeftSleeve() { + return ((bitmask >> 2) & 1) == 1; + } - public boolean hasLeftPants() { - return ((bitmask >> 4) & 1) == 1; - } + public boolean hasRightSleeve() { + return ((bitmask >> 3) & 1) == 1; + } - public boolean hasRightPants() { - return ((bitmask >> 5) & 1) == 1; - } + public boolean hasLeftPants() { + return ((bitmask >> 4) & 1) == 1; + } - public boolean hasHat() { - return ((bitmask >> 6) & 1) == 1; - } + public boolean hasRightPants() { + return ((bitmask >> 5) & 1) == 1; + } + + public boolean hasHat() { + return ((bitmask >> 6) & 1) == 1; + } } 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 a517ba2de..9215ae473 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 @@ -2,51 +2,56 @@ 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; import java.util.UUID; +import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents the tab list of a {@link Player}. */ public interface TabList { - /** - * Sets the tab list header and footer for the player. - * @param header the header component - * @param footer the footer component - */ - void setHeaderAndFooter(Component header, Component footer); - /** - * Clears the tab list header and footer for the player. - */ - void clearHeaderAndFooter(); - - /** - * Adds a {@link TabListEntry} to the {@link Player}'s tab list. - * @param entry to add to the tab list - */ - void addEntry(TabListEntry entry); - - /** - * Removes the {@link TabListEntry} from the tab list with the {@link GameProfile} - * identified with the specified {@link UUID}. - * @param uuid of the - * @return {@link Optional} containing the removed {@link TabListEntry} if present, - * otherwise {@link Optional#empty()} - */ - Optional removeEntry(UUID uuid); - - /** - * Returns an immutable {@link Collection} of the {@link TabListEntry}s in the tab list. - * @return immutable {@link Collection} of tab list entries - */ - Collection getEntries(); + /** + * Sets the tab list header and footer for the player. + * + * @param header the header component + * @param footer the footer component + */ + void setHeaderAndFooter(Component header, Component footer); - // Necessary because the TabListEntry implementation isn't in the api - @Deprecated - TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode); + /** + * Clears the tab list header and footer for the player. + */ + void clearHeaderAndFooter(); + + /** + * Adds a {@link TabListEntry} to the {@link Player}'s tab list. + * + * @param entry to add to the tab list + */ + void addEntry(TabListEntry entry); + + /** + * Removes the {@link TabListEntry} from the tab list with the {@link GameProfile} identified with + * the specified {@link UUID}. + * + * @param uuid of the + * @return {@link Optional} containing the removed {@link TabListEntry} if present, otherwise + * {@link Optional#empty()} + */ + Optional removeEntry(UUID uuid); + + /** + * Returns an immutable {@link Collection} of the {@link TabListEntry}s in the tab list. + * + * @return immutable {@link Collection} of tab list entries + */ + Collection getEntries(); + + // Necessary because the TabListEntry implementation isn't in the api + @Deprecated + 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 dfabb458e..3ab5e5c4d 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,177 +1,195 @@ package com.velocitypowered.api.proxy.player; import com.velocitypowered.api.util.GameProfile; +import java.util.Optional; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Optional; - /** * Represents a single entry in a {@link TabList}. */ public interface TabListEntry { - /** - * Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}. - * @return parent {@link TabList} - */ - TabList getTabList(); - - /** - * Returns the {@link GameProfile} of the entry, which uniquely identifies the entry - * with the containing {@link java.util.UUID}, as well as deciding what is shown - * as the player head in the tab list. - * @return {@link GameProfile} of the entry - */ - 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 - */ - Optional getDisplayName(); - - /** - * Sets the text {@link Component} to be displayed for {@code this} {@link TabListEntry}. - * If {@code null}, {@link GameProfile#getName()} will be shown. - * @param displayName to show in the {@link TabList} for {@code this} entry - * @return {@code this}, for chaining - */ - TabListEntry setDisplayName(@Nullable Component displayName); - - /** - * Returns the latency for {@code this} entry. - *

The icon shown in the tab list is calculated by the latency in the following way:

- *

    - *
  • A negative latency will display the no connection icon
  • - *
  • 0-150 will display 5 bars
  • - *
  • 150-300 will display 4 bars
  • - *
  • 300-600 will display 3 bars
  • - *
  • 600-1000 will display 2 bars
  • - *
  • A latency move than 1 second will display 1 bar
  • - *
  • - *
- * @return latency set for {@code this} entry - */ - int getLatency(); - - /** - * Sets the latency for {@code this} entry to the specified value - * @see #getLatency() - * @param latency to changed to - * @return {@code this}, for chaining - */ - TabListEntry setLatency(int latency); - - /** - * Gets the game mode {@code this} entry has been set to. - *

The number corresponds to the game mode in the following way:

- *
    - *
  1. Survival
  2. - *
  3. Creative
  4. - *
  5. Adventure
  6. - *
  7. Spectator
  8. - *
- * @return the game mode - */ - int getGameMode(); - - /** - * Sets the game mode for {@code this} entry to the specified value - * @see #getGameMode() - * @param gameMode to change to - * @return {@code this}, for chaining - */ - TabListEntry setGameMode(int gameMode); - - /** - * Returns a {@link Builder} to create a {@link TabListEntry}. - * @return {@link TabListEntry} builder - */ - static Builder builder() { - return new Builder(); - } - - /** - * Represents a builder which creates {@link TabListEntry}s. - * @see TabListEntry - */ - class Builder { - private @Nullable TabList tabList; - private @Nullable GameProfile profile; - private @Nullable Component displayName; - private int latency = 0; - private int gameMode = 0; - private Builder() {} - - /** - * Sets the parent {@link TabList} for this entry, - * the entry will only be able to be added to that specific {@link TabList}. - * @param tabList to set - * @return {@code this}, for chaining - */ - public Builder tabList(TabList tabList) { - this.tabList = tabList; - return this; - } - - /** - * Sets the {@link GameProfile} of the {@link TabListEntry}. - * @see TabListEntry#getProfile() - * @param profile to set - * @return {@code this}, for chaining - */ - public Builder profile(GameProfile profile) { - this.profile = profile; - return this; - } - - /** - * Sets the displayed name of the {@link TabListEntry} - * @see TabListEntry#getDisplayName() - * @param displayName to set - * @return {@code this}, for chaining - */ - public Builder displayName(@Nullable Component displayName) { - this.displayName = displayName; - return this; - } - - /** - * Sets the latency of the {@link TabListEntry} - * @see TabListEntry#getLatency() - * @param latency to set - * @return {@code this}, for chaining - */ - public Builder latency(int latency) { - this.latency = latency; - return this; - } - - /** - * Sets the game mode of the {@link TabListEntry} - * @see TabListEntry#getGameMode() - * @param gameMode to set - * @return {@code this}, for chaining - */ - public Builder gameMode(int gameMode) { - this.gameMode = gameMode; - return this; - } - - /** - * Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}. - * @return the constructed {@link TabListEntry} - */ - public TabListEntry build() { - 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); - } + /** + * Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}. + * + * @return parent {@link TabList} + */ + TabList getTabList(); + + /** + * Returns the {@link GameProfile} of the entry, which uniquely identifies the entry with the + * containing {@link java.util.UUID}, as well as deciding what is shown as the player head in the + * tab list. + * + * @return {@link GameProfile} of the entry + */ + 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 + */ + Optional getDisplayName(); + + /** + * Sets the text {@link Component} to be displayed for {@code this} {@link TabListEntry}. If + * {@code null}, {@link GameProfile#getName()} will be shown. + * + * @param displayName to show in the {@link TabList} for {@code this} entry + * @return {@code this}, for chaining + */ + TabListEntry setDisplayName(@Nullable Component displayName); + + /** + * Returns the latency for {@code this} entry. + *

The icon shown in the tab list is calculated by the latency in the following way:

+ *

    + *
  • A negative latency will display the no connection icon
  • + *
  • 0-150 will display 5 bars
  • + *
  • 150-300 will display 4 bars
  • + *
  • 300-600 will display 3 bars
  • + *
  • 600-1000 will display 2 bars
  • + *
  • A latency move than 1 second will display 1 bar
  • + *
  • + *
+ * + * @return latency set for {@code this} entry + */ + int getLatency(); + + /** + * Sets the latency for {@code this} entry to the specified value + * + * @param latency to changed to + * @return {@code this}, for chaining + * @see #getLatency() + */ + TabListEntry setLatency(int latency); + + /** + * Gets the game mode {@code this} entry has been set to. + *

The number corresponds to the game mode in the following way:

+ *
    + *
  1. Survival
  2. + *
  3. Creative
  4. + *
  5. Adventure
  6. + *
  7. Spectator
  8. + *
+ * + * @return the game mode + */ + int getGameMode(); + + /** + * Sets the game mode for {@code this} entry to the specified value + * + * @param gameMode to change to + * @return {@code this}, for chaining + * @see #getGameMode() + */ + TabListEntry setGameMode(int gameMode); + + /** + * Returns a {@link Builder} to create a {@link TabListEntry}. + * + * @return {@link TabListEntry} builder + */ + static Builder builder() { + return new Builder(); + } + + /** + * Represents a builder which creates {@link TabListEntry}s. + * + * @see TabListEntry + */ + class Builder { + + private @Nullable TabList tabList; + private @Nullable GameProfile profile; + private @Nullable Component displayName; + private int latency = 0; + private int gameMode = 0; + + private Builder() { } + + /** + * Sets the parent {@link TabList} for this entry, the entry will only be able to be added to + * that specific {@link TabList}. + * + * @param tabList to set + * @return {@code this}, for chaining + */ + public Builder tabList(TabList tabList) { + this.tabList = tabList; + return this; + } + + /** + * Sets the {@link GameProfile} of the {@link TabListEntry}. + * + * @param profile to set + * @return {@code this}, for chaining + * @see TabListEntry#getProfile() + */ + public Builder profile(GameProfile profile) { + this.profile = profile; + return this; + } + + /** + * Sets the displayed name of the {@link TabListEntry} + * + * @param displayName to set + * @return {@code this}, for chaining + * @see TabListEntry#getDisplayName() + */ + public Builder displayName(@Nullable Component displayName) { + this.displayName = displayName; + return this; + } + + /** + * Sets the latency of the {@link TabListEntry} + * + * @param latency to set + * @return {@code this}, for chaining + * @see TabListEntry#getLatency() + */ + public Builder latency(int latency) { + this.latency = latency; + return this; + } + + /** + * Sets the game mode of the {@link TabListEntry} + * + * @param gameMode to set + * @return {@code this}, for chaining + * @see TabListEntry#getGameMode() + */ + public Builder gameMode(int gameMode) { + this.gameMode = gameMode; + return this; + } + + /** + * Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}. + * + * @return the constructed {@link TabListEntry} + */ + public TabListEntry build() { + 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/QueryResponse.java b/api/src/main/java/com/velocitypowered/api/proxy/server/QueryResponse.java index 2da751e6f..1e934dfeb 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/QueryResponse.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/QueryResponse.java @@ -3,301 +3,323 @@ package com.velocitypowered.api.proxy.server; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.velocitypowered.api.proxy.config.ProxyConfig; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * GS4 query response. This class is immutable. */ public final class QueryResponse { - private final String hostname; - private final String gameVersion; - private final String map; - private final int currentPlayers; - private final int maxPlayers; - private final String proxyHost; - private final int proxyPort; - private final Collection players; - private final String proxyVersion; - private final Collection plugins; - private QueryResponse(String hostname, String gameVersion, String map, int currentPlayers, int maxPlayers, String proxyHost, int proxyPort, Collection players, String proxyVersion, Collection plugins) { - this.hostname = hostname; - this.gameVersion = gameVersion; - this.map = map; - this.currentPlayers = currentPlayers; - this.maxPlayers = maxPlayers; - this.proxyHost = proxyHost; - this.proxyPort = proxyPort; - this.players = players; - this.proxyVersion = proxyVersion; - this.plugins = plugins; + private final String hostname; + private final String gameVersion; + private final String map; + private final int currentPlayers; + private final int maxPlayers; + private final String proxyHost; + private final int proxyPort; + private final Collection players; + private final String proxyVersion; + private final Collection plugins; + + private QueryResponse(String hostname, String gameVersion, String map, int currentPlayers, + int maxPlayers, String proxyHost, int proxyPort, Collection players, + String proxyVersion, Collection plugins) { + this.hostname = hostname; + this.gameVersion = gameVersion; + this.map = map; + this.currentPlayers = currentPlayers; + this.maxPlayers = maxPlayers; + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.players = players; + this.proxyVersion = proxyVersion; + this.plugins = plugins; + } + + /** + * Get hostname which will be used to reply to the query. By default it is {@link + * ProxyConfig#getMotdComponent()} in plain text without colour codes. + * + * @return hostname + */ + public String getHostname() { + return hostname; + } + + /** + * Get game version which will be used to reply to the query. By default supported Minecraft + * versions range is sent. + * + * @return game version + */ + public String getGameVersion() { + return gameVersion; + } + + /** + * Get map name which will be used to reply to the query. By default {@link + * ProxyConfig#getQueryMap()} is sent. + * + * @return map name + */ + public String getMap() { + return map; + } + + /** + * Get current online player count which will be used to reply to the query. + * + * @return online player count + */ + public int getCurrentPlayers() { + return currentPlayers; + } + + /** + * Get max player count which will be used to reply to the query. + * + * @return max player count + */ + public int getMaxPlayers() { + return maxPlayers; + } + + /** + * Get proxy (public facing) hostname + * + * @return proxy hostname + */ + public String getProxyHost() { + return proxyHost; + } + + /** + * Get proxy (public facing) port + * + * @return proxy port + */ + public int getProxyPort() { + return proxyPort; + } + + /** + * Get collection of players which will be used to reply to the query. + * + * @return collection of players + */ + public Collection getPlayers() { + return players; + } + + /** + * Get server software (name and version) which will be used to reply to the query. + * + * @return server software + */ + public String getProxyVersion() { + return proxyVersion; + } + + /** + * Get list of plugins which will be used to reply to the query. + * + * @return collection of plugins + */ + public Collection getPlugins() { + return plugins; + } + + + /** + * Creates a new {@link Builder} instance from data represented by this response + * + * @return {@link QueryResponse} builder + */ + public Builder toBuilder() { + return QueryResponse.builder() + .hostname(getHostname()) + .gameVersion(getGameVersion()) + .map(getMap()) + .currentPlayers(getCurrentPlayers()) + .maxPlayers(getMaxPlayers()) + .proxyHost(getProxyHost()) + .proxyPort(getProxyPort()) + .players(getPlayers()) + .proxyVersion(getProxyVersion()) + .plugins(getPlugins()); + } + + /** + * Creates a new {@link Builder} instance + * + * @return {@link QueryResponse} builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link QueryResponse} objects. + */ + public static final class Builder { + + @MonotonicNonNull + private String hostname; + + @MonotonicNonNull + private String gameVersion; + + @MonotonicNonNull + private String map; + + @MonotonicNonNull + private String proxyHost; + + @MonotonicNonNull + private String proxyVersion; + + private int currentPlayers; + private int maxPlayers; + private int proxyPort; + + private List players = new ArrayList<>(); + private List plugins = new ArrayList<>(); + + private Builder() { + } + + public Builder hostname(String hostname) { + this.hostname = Preconditions.checkNotNull(hostname, "hostname"); + return this; + } + + public Builder gameVersion(String gameVersion) { + this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion"); + return this; + } + + public Builder map(String map) { + this.map = Preconditions.checkNotNull(map, "map"); + return this; + } + + public Builder currentPlayers(int currentPlayers) { + Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative"); + this.currentPlayers = currentPlayers; + return this; + } + + public Builder maxPlayers(int maxPlayers) { + Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative"); + this.maxPlayers = maxPlayers; + return this; + } + + public Builder proxyHost(String proxyHost) { + this.proxyHost = Preconditions.checkNotNull(proxyHost, "proxyHost"); + return this; + } + + public Builder proxyPort(int proxyPort) { + Preconditions + .checkArgument(proxyPort >= 1 && proxyPort <= 65535, "proxyPort must be between 1-65535"); + this.proxyPort = proxyPort; + return this; + } + + public Builder players(Collection players) { + this.players.addAll(Preconditions.checkNotNull(players, "players")); + return this; + } + + public Builder players(String... players) { + this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players"))); + return this; + } + + public Builder clearPlayers() { + this.players.clear(); + return this; + } + + public Builder proxyVersion(String proxyVersion) { + this.proxyVersion = Preconditions.checkNotNull(proxyVersion, "proxyVersion"); + return this; + } + + public Builder plugins(Collection plugins) { + this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins")); + return this; + } + + public Builder plugins(PluginInformation... plugins) { + this.plugins.addAll(Arrays.asList(Preconditions.checkNotNull(plugins, "plugins"))); + return this; + } + + public Builder clearPlugins() { + this.plugins.clear(); + return this; } /** - * Get hostname which will be used to reply to the query. By default it is {@link ProxyConfig#getMotdComponent()} in plain text without colour codes. - * @return hostname + * Builds new {@link QueryResponse} with supplied data + * + * @return response */ - public String getHostname() { - return hostname; + public QueryResponse build() { + return new QueryResponse( + Preconditions.checkNotNull(hostname, "hostname"), + Preconditions.checkNotNull(gameVersion, "gameVersion"), + Preconditions.checkNotNull(map, "map"), + currentPlayers, + maxPlayers, + Preconditions.checkNotNull(proxyHost, "proxyHost"), + proxyPort, + ImmutableList.copyOf(players), + Preconditions.checkNotNull(proxyVersion, "proxyVersion"), + ImmutableList.copyOf(plugins) + ); + } + } + + /** + * Plugin information + */ + public static class PluginInformation { + + private String name; + private String version; + + public PluginInformation(String name, String version) { + this.name = Preconditions.checkNotNull(name, "name"); + this.version = Preconditions.checkNotNull(version, "version"); } - /** - * Get game version which will be used to reply to the query. By default supported Minecraft versions range is sent. - * @return game version - */ - public String getGameVersion() { - return gameVersion; + public String getName() { + return name; } - /** - * Get map name which will be used to reply to the query. By default {@link ProxyConfig#getQueryMap()} is sent. - * @return map name - */ - public String getMap() { - return map; + public void setName(String name) { + this.name = name; } - /** - * Get current online player count which will be used to reply to the query. - * @return online player count - */ - public int getCurrentPlayers() { - return currentPlayers; + public void setVersion(@Nullable String version) { + this.version = version; } - /** - * Get max player count which will be used to reply to the query. - * @return max player count - */ - public int getMaxPlayers() { - return maxPlayers; + @Nullable + public String getVersion() { + return version; } - /** - * Get proxy (public facing) hostname - * @return proxy hostname - */ - public String getProxyHost() { - return proxyHost; - } - - /** - * Get proxy (public facing) port - * @return proxy port - */ - public int getProxyPort() { - return proxyPort; - } - - /** - * Get collection of players which will be used to reply to the query. - * @return collection of players - */ - public Collection getPlayers() { - return players; - } - - /** - * Get server software (name and version) which will be used to reply to the query. - * @return server software - */ - public String getProxyVersion() { - return proxyVersion; - } - - /** - * Get list of plugins which will be used to reply to the query. - * @return collection of plugins - */ - public Collection getPlugins() { - return plugins; - } - - - /** - * Creates a new {@link Builder} instance from data represented by this response - * @return {@link QueryResponse} builder - */ - public Builder toBuilder() { - return QueryResponse.builder() - .hostname(getHostname()) - .gameVersion(getGameVersion()) - .map(getMap()) - .currentPlayers(getCurrentPlayers()) - .maxPlayers(getMaxPlayers()) - .proxyHost(getProxyHost()) - .proxyPort(getProxyPort()) - .players(getPlayers()) - .proxyVersion(getProxyVersion()) - .plugins(getPlugins()); - } - - /** - * Creates a new {@link Builder} instance - * @return {@link QueryResponse} builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder for {@link QueryResponse} objects. - */ - public static final class Builder { - @MonotonicNonNull - private String hostname; - - @MonotonicNonNull - private String gameVersion; - - @MonotonicNonNull - private String map; - - @MonotonicNonNull - private String proxyHost; - - @MonotonicNonNull - private String proxyVersion; - - private int currentPlayers; - private int maxPlayers; - private int proxyPort; - - private List players = new ArrayList<>(); - private List plugins = new ArrayList<>(); - - private Builder() {} - - public Builder hostname(String hostname) { - this.hostname = Preconditions.checkNotNull(hostname, "hostname"); - return this; - } - - public Builder gameVersion(String gameVersion) { - this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion"); - return this; - } - - public Builder map(String map) { - this.map = Preconditions.checkNotNull(map, "map"); - return this; - } - - public Builder currentPlayers(int currentPlayers) { - Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative"); - this.currentPlayers = currentPlayers; - return this; - } - - public Builder maxPlayers(int maxPlayers) { - Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative"); - this.maxPlayers = maxPlayers; - return this; - } - - public Builder proxyHost(String proxyHost) { - this.proxyHost = Preconditions.checkNotNull(proxyHost, "proxyHost"); - return this; - } - - public Builder proxyPort(int proxyPort) { - Preconditions.checkArgument(proxyPort >= 1 && proxyPort <= 65535, "proxyPort must be between 1-65535"); - this.proxyPort = proxyPort; - return this; - } - - public Builder players(Collection players) { - this.players.addAll(Preconditions.checkNotNull(players, "players")); - return this; - } - - public Builder players(String... players) { - this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players"))); - return this; - } - - public Builder clearPlayers() { - this.players.clear(); - return this; - } - - public Builder proxyVersion(String proxyVersion) { - this.proxyVersion = Preconditions.checkNotNull(proxyVersion, "proxyVersion"); - return this; - } - - public Builder plugins(Collection plugins) { - this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins")); - return this; - } - - public Builder plugins(PluginInformation... plugins) { - this.plugins.addAll(Arrays.asList(Preconditions.checkNotNull(plugins, "plugins"))); - return this; - } - - public Builder clearPlugins() { - this.plugins.clear(); - return this; - } - - /** - * Builds new {@link QueryResponse} with supplied data - * @return response - */ - public QueryResponse build() { - return new QueryResponse( - Preconditions.checkNotNull(hostname, "hostname"), - Preconditions.checkNotNull(gameVersion, "gameVersion"), - Preconditions.checkNotNull(map, "map"), - currentPlayers, - maxPlayers, - Preconditions.checkNotNull(proxyHost, "proxyHost"), - proxyPort, - ImmutableList.copyOf(players), - Preconditions.checkNotNull(proxyVersion, "proxyVersion"), - ImmutableList.copyOf(plugins) - ); - } - } - - /** - * Plugin information - */ - public static class PluginInformation { - private String name; - private String version; - - public PluginInformation(String name, String version) { - this.name = Preconditions.checkNotNull(name, "name"); - this.version = Preconditions.checkNotNull(version, "version"); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public void setVersion(@Nullable String version) { - this.version = version; - } - - @Nullable - public String getVersion() { - return version; - } - - public static PluginInformation of(String name, @Nullable String version) { - return new PluginInformation(name, version); - } + public static PluginInformation of(String name, @Nullable String version) { + return new PluginInformation(name, version); } + } } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java b/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java index b7bf9acb5..1ed7066b3 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java @@ -2,7 +2,6 @@ package com.velocitypowered.api.proxy.server; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.messages.ChannelMessageSink; - import java.util.Collection; import java.util.concurrent.CompletableFuture; @@ -10,21 +9,25 @@ import java.util.concurrent.CompletableFuture; * Represents a server that has been registered with the proxy. */ public interface RegisteredServer extends ChannelMessageSink { - /** - * Returns the {@link ServerInfo} for this server. - * @return the server info - */ - ServerInfo getServerInfo(); - /** - * Returns a list of all the players currently connected to this server on this proxy. - * @return the players on this proxy - */ - Collection getPlayersConnected(); + /** + * Returns the {@link ServerInfo} for this server. + * + * @return the server info + */ + ServerInfo getServerInfo(); - /** - * Attempts to ping the remote server and return the server list ping result. - * @return the server ping result from the server - */ - CompletableFuture ping(); + /** + * Returns a list of all the players currently connected to this server on this proxy. + * + * @return the players on this proxy + */ + Collection getPlayersConnected(); + + /** + * Attempts to ping the remote server and return the server list ping result. + * + * @return the server ping result from the server + */ + CompletableFuture ping(); } 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 ea2d55ec3..16dafc20c 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,55 +1,61 @@ package com.velocitypowered.api.proxy.server; import com.google.common.base.Preconditions; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.net.InetSocketAddress; import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** - * ServerInfo represents a server that a player can connect to. This object is immutable and safe for concurrent access. + * 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 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(String name, InetSocketAddress address) { - this.name = Preconditions.checkNotNull(name, "name"); - this.address = Preconditions.checkNotNull(address, "address"); - } + private final String name; + private final InetSocketAddress address; - public final String getName() { - return name; - } + /** + * Creates a new ServerInfo object. + * + * @param name the name for the server + * @param address the address of the server to connect to + */ + public ServerInfo(String name, InetSocketAddress address) { + this.name = Preconditions.checkNotNull(name, "name"); + this.address = Preconditions.checkNotNull(address, "address"); + } - public final InetSocketAddress getAddress() { - return address; - } + public final String getName() { + return name; + } - @Override - public String toString() { - return "ServerInfo{" + - "name='" + name + '\'' + - ", address=" + address + - '}'; - } + public final InetSocketAddress getAddress() { + return address; + } - @Override - public final boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ServerInfo that = (ServerInfo) o; - return Objects.equals(name, that.name) && - Objects.equals(address, that.address); - } + @Override + public String toString() { + return "ServerInfo{" + + "name='" + name + '\'' + + ", address=" + address + + '}'; + } - @Override - public final int hashCode() { - return Objects.hash(name, address); + @Override + public final boolean equals(@Nullable Object o) { + if (this == o) { + return true; } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerInfo that = (ServerInfo) o; + return Objects.equals(name, that.name) && + Objects.equals(address, that.address); + } + + @Override + public final int hashCode() { + return Objects.hash(name, address); + } } 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 c47a520c9..31eb1dc12 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 @@ -4,308 +4,319 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.ModInfo; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.*; - /** * Represents a 1.7 and above server list ping response. This class is immutable. */ public final class ServerPing { - private final Version version; - private final @Nullable Players players; - private final Component description; - private final @Nullable Favicon favicon; - private final @Nullable ModInfo modinfo; - public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon) { - this(version, players, description, favicon, ModInfo.DEFAULT); + private final Version version; + private final @Nullable Players players; + private final Component description; + private final @Nullable Favicon favicon; + private final @Nullable ModInfo modinfo; + + public ServerPing(Version version, @Nullable Players players, Component description, + @Nullable Favicon favicon) { + this(version, players, description, favicon, ModInfo.DEFAULT); + } + + public ServerPing(Version version, @Nullable Players players, Component description, + @Nullable Favicon favicon, @Nullable ModInfo modinfo) { + this.version = Preconditions.checkNotNull(version, "version"); + this.players = players; + this.description = Preconditions.checkNotNull(description, "description"); + this.favicon = favicon; + this.modinfo = modinfo; + } + + public Version getVersion() { + return version; + } + + public Optional getPlayers() { + return Optional.ofNullable(players); + } + + public Component getDescription() { + return description; + } + + public Optional getFavicon() { + return Optional.ofNullable(favicon); + } + + public Optional getModinfo() { + return Optional.ofNullable(modinfo); + } + + @Override + public String toString() { + return "ServerPing{" + + "version=" + version + + ", players=" + players + + ", description=" + description + + ", favicon='" + favicon + '\'' + + '}'; + } + + public Builder asBuilder() { + Builder builder = new Builder(); + builder.version = version; + if (players != null) { + builder.onlinePlayers = players.online; + builder.maximumPlayers = players.max; + builder.samplePlayers.addAll(players.sample); + } else { + builder.nullOutPlayers = true; + } + builder.description = description; + builder.favicon = favicon; + builder.nullOutModinfo = modinfo == null; + if (modinfo != null) { + builder.modType = modinfo.getType(); + builder.mods.addAll(modinfo.getMods()); + } + return builder; + } + + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link ServerPing} objects. + */ + public static final class Builder { + + private Version version = new Version(0, "Unknown"); + private int onlinePlayers; + private int maximumPlayers; + private final List samplePlayers = new ArrayList<>(); + private String modType = "FML"; + private final List mods = new ArrayList<>(); + private @Nullable Component description; + private @Nullable Favicon favicon; + private boolean nullOutPlayers; + private boolean nullOutModinfo; + + private Builder() { + } - public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, @Nullable ModInfo modinfo) { - this.version = Preconditions.checkNotNull(version, "version"); - this.players = players; - this.description = Preconditions.checkNotNull(description, "description"); - this.favicon = favicon; - this.modinfo = modinfo; + public Builder version(Version version) { + this.version = Preconditions.checkNotNull(version, "version"); + return this; + } + + public Builder onlinePlayers(int onlinePlayers) { + this.onlinePlayers = onlinePlayers; + return this; + } + + public Builder maximumPlayers(int maximumPlayers) { + this.maximumPlayers = maximumPlayers; + return this; + } + + public Builder samplePlayers(SamplePlayer... players) { + this.samplePlayers.addAll(Arrays.asList(players)); + return this; + } + + public Builder modType(String modType) { + this.modType = Preconditions.checkNotNull(modType, "modType"); + return this; + } + + public Builder mods(ModInfo.Mod... mods) { + this.mods.addAll(Arrays.asList(mods)); + return this; + } + + public Builder clearMods() { + this.mods.clear(); + return this; + } + + public Builder clearSamplePlayers() { + this.samplePlayers.clear(); + return this; + } + + public Builder notModCompatible() { + this.nullOutModinfo = true; + return this; + } + + public Builder nullPlayers() { + this.nullOutPlayers = true; + return this; + } + + public Builder description(Component description) { + this.description = Preconditions.checkNotNull(description, "description"); + return this; + } + + public Builder favicon(Favicon favicon) { + this.favicon = Preconditions.checkNotNull(favicon, "favicon"); + return this; + } + + 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)); } public Version getVersion() { - return version; + return version; } - public Optional getPlayers() { - return Optional.ofNullable(players); + public int getOnlinePlayers() { + return onlinePlayers; } - public Component getDescription() { - return description; + public int getMaximumPlayers() { + return maximumPlayers; + } + + public List getSamplePlayers() { + return samplePlayers; + } + + public Optional getDescription() { + return Optional.ofNullable(description); } public Optional getFavicon() { - return Optional.ofNullable(favicon); + return Optional.ofNullable(favicon); } - public Optional getModinfo() { - return Optional.ofNullable(modinfo); + public String getModType() { + return modType; + } + + public List getMods() { + return mods; } @Override public String toString() { - return "ServerPing{" + - "version=" + version + - ", players=" + players + - ", description=" + description + - ", favicon='" + favicon + '\'' + - '}'; + return "Builder{" + + "version=" + version + + ", onlinePlayers=" + onlinePlayers + + ", maximumPlayers=" + maximumPlayers + + ", samplePlayers=" + samplePlayers + + ", modType=" + modType + + ", mods=" + mods + + ", description=" + description + + ", favicon=" + favicon + + ", nullOutPlayers=" + nullOutPlayers + + ", nullOutModinfo=" + nullOutModinfo + + '}'; + } + } + + public static final class Version { + + private final int protocol; + private final String name; + + public Version(int protocol, String name) { + this.protocol = protocol; + this.name = name; } - public Builder asBuilder() { - Builder builder = new Builder(); - builder.version = version; - if (players != null) { - builder.onlinePlayers = players.online; - builder.maximumPlayers = players.max; - builder.samplePlayers.addAll(players.sample); - } else { - builder.nullOutPlayers = true; - } - builder.description = description; - builder.favicon = favicon; - builder.nullOutModinfo = modinfo == null; - if (modinfo != null) { - builder.modType = modinfo.getType(); - builder.mods.addAll(modinfo.getMods()); - } - return builder; + public int getProtocol() { + return protocol; } - public static Builder builder() { - return new Builder(); + public String getName() { + return name; } - /** - * A builder for {@link ServerPing} objects. - */ - public static final class Builder { - private Version version = new Version(0, "Unknown"); - private int onlinePlayers; - private int maximumPlayers; - private final List samplePlayers = new ArrayList<>(); - private String modType = "FML"; - private final List mods = new ArrayList<>(); - private @Nullable Component description; - private @Nullable Favicon favicon; - private boolean nullOutPlayers; - private boolean nullOutModinfo; + @Override + public String toString() { + return "Version{" + + "protocol=" + protocol + + ", name='" + name + '\'' + + '}'; + } + } - private Builder() { + public static final class Players { - } + private final int online; + private final int max; + private final List sample; - public Builder version(Version version) { - this.version = Preconditions.checkNotNull(version, "version"); - return this; - } - - public Builder onlinePlayers(int onlinePlayers) { - this.onlinePlayers = onlinePlayers; - return this; - } - - public Builder maximumPlayers(int maximumPlayers) { - this.maximumPlayers = maximumPlayers; - return this; - } - - public Builder samplePlayers(SamplePlayer... players) { - this.samplePlayers.addAll(Arrays.asList(players)); - return this; - } - - public Builder modType(String modType) { - this.modType = Preconditions.checkNotNull(modType, "modType"); - return this; - } - - public Builder mods(ModInfo.Mod... mods) { - this.mods.addAll(Arrays.asList(mods)); - return this; - } - - public Builder clearMods() { - this.mods.clear(); - return this; - } - - public Builder clearSamplePlayers() { - this.samplePlayers.clear(); - return this; - } - - public Builder notModCompatible() { - this.nullOutModinfo = true; - return this; - } - - public Builder nullPlayers() { - this.nullOutPlayers = true; - return this; - } - - public Builder description(Component description) { - this.description = Preconditions.checkNotNull(description, "description"); - return this; - } - - public Builder favicon(Favicon favicon) { - this.favicon = Preconditions.checkNotNull(favicon, "favicon"); - return this; - } - - 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)); - } - - public Version getVersion() { - return version; - } - - public int getOnlinePlayers() { - return onlinePlayers; - } - - public int getMaximumPlayers() { - return maximumPlayers; - } - - public List getSamplePlayers() { - return samplePlayers; - } - - public Optional getDescription() { - return Optional.ofNullable(description); - } - - public Optional getFavicon() { - return Optional.ofNullable(favicon); - } - - public String getModType() { - return modType; - } - - public List getMods() { - return mods; - } - - @Override - public String toString() { - return "Builder{" + - "version=" + version + - ", onlinePlayers=" + onlinePlayers + - ", maximumPlayers=" + maximumPlayers + - ", samplePlayers=" + samplePlayers + - ", modType=" + modType + - ", mods=" + mods + - ", description=" + description + - ", favicon=" + favicon + - ", nullOutPlayers=" + nullOutPlayers + - ", nullOutModinfo=" + nullOutModinfo + - '}'; - } + public Players(int online, int max, List sample) { + this.online = online; + this.max = max; + this.sample = ImmutableList.copyOf(sample); } - public static final class Version { - private final int protocol; - private final String name; - - public Version(int protocol, String name) { - this.protocol = protocol; - this.name = name; - } - - public int getProtocol() { - return protocol; - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return "Version{" + - "protocol=" + protocol + - ", name='" + name + '\'' + - '}'; - } + public int getOnline() { + return online; } - public static final class Players { - private final int online; - private final int max; - private final List sample; - - public Players(int online, int max, List sample) { - this.online = online; - this.max = max; - this.sample = ImmutableList.copyOf(sample); - } - - public int getOnline() { - return online; - } - - public int getMax() { - return max; - } - - public List getSample() { - return sample; - } - - @Override - public String toString() { - return "Players{" + - "online=" + online + - ", max=" + max + - ", sample=" + sample + - '}'; - } + public int getMax() { + return max; } - public static final class SamplePlayer { - private final String name; - private final UUID id; - - public SamplePlayer(String name, UUID id) { - this.name = name; - this.id = id; - } - - public String getName() { - return name; - } - - public UUID getId() { - return id; - } - - @Override - public String toString() { - return "SamplePlayer{" + - "name='" + name + '\'' + - ", id=" + id + - '}'; - } + public List getSample() { + return sample; } + + @Override + public String toString() { + return "Players{" + + "online=" + online + + ", max=" + max + + ", sample=" + sample + + '}'; + } + } + + public static final class SamplePlayer { + + private final String name; + private final UUID id; + + public SamplePlayer(String name, UUID id) { + this.name = name; + this.id = id; + } + + public String getName() { + return name; + } + + public UUID getId() { + return id; + } + + @Override + public String toString() { + return "SamplePlayer{" + + "name='" + name + '\'' + + ", id=" + id + + '}'; + } + } } diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java b/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java index 2ee41380f..15cdbd149 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/ScheduledTask.java @@ -4,21 +4,24 @@ package com.velocitypowered.api.scheduler; * Represents a task that is scheduled to run on the proxy. */ public interface ScheduledTask { - /** - * Returns the plugin that scheduled this task. - * @return the plugin that scheduled this task - */ - Object plugin(); - /** - * Returns the current status of this task. - * @return the current status of this task - */ - TaskStatus status(); + /** + * Returns the plugin that scheduled this task. + * + * @return the plugin that scheduled this task + */ + Object plugin(); - /** - * Cancels this task. If the task is already running, the thread in which it is running will be interrupted. - * If the task is not currently running, Velocity will terminate it safely. - */ - void cancel(); + /** + * Returns the current status of this task. + * + * @return the current status of this task + */ + TaskStatus status(); + + /** + * Cancels this task. If the task is already running, the thread in which it is running will be + * interrupted. If the task is not currently running, Velocity will terminate it safely. + */ + void cancel(); } 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 5a8848519..6f850b98c 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java @@ -1,57 +1,65 @@ package com.velocitypowered.api.scheduler; -import org.checkerframework.common.value.qual.IntRange; - import java.util.concurrent.TimeUnit; +import org.checkerframework.common.value.qual.IntRange; /** * Represents a scheduler to execute tasks on the proxy. */ public interface Scheduler { - /** - * Initializes a new {@link TaskBuilder} for creating a task on the proxy. - * @param plugin the plugin to request the task for - * @param runnable the task to run when scheduled - * @return the task builder - */ - TaskBuilder buildTask(Object plugin, Runnable runnable); + + /** + * Initializes a new {@link TaskBuilder} for creating a task on the proxy. + * + * @param plugin the plugin to request the task for + * @param runnable the task to run when scheduled + * @return the task builder + */ + TaskBuilder buildTask(Object plugin, Runnable runnable); + + /** + * Represents a fluent interface to schedule tasks on the proxy. + */ + interface TaskBuilder { /** - * Represents a fluent interface to schedule tasks on the proxy. + * Specifies that the task should delay its execution by the specified amount of time. + * + * @param time the time to delay by + * @param unit the unit of time for {@code time} + * @return this builder, for chaining */ - interface TaskBuilder { - /** - * Specifies that the task should delay its execution by the specified amount of time. - * @param time the time to delay by - * @param unit the unit of time for {@code time} - * @return this builder, for chaining - */ - TaskBuilder delay(@IntRange(from = 0) 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. - * @param time the time to delay by - * @param unit the unit of time for {@code time} - * @return this builder, for chaining - */ - TaskBuilder repeat(@IntRange(from = 0) long time, TimeUnit unit); + /** + * Specifies that the task should continue running after waiting for the specified amount, until + * it is cancelled. + * + * @param time the time to delay by + * @param unit the unit of time for {@code time} + * @return this builder, for chaining + */ + TaskBuilder repeat(@IntRange(from = 0) long time, TimeUnit unit); - /** - * Clears the delay on this task. - * @return this builder, for chaining - */ - TaskBuilder clearDelay(); + /** + * Clears the delay on this task. + * + * @return this builder, for chaining + */ + TaskBuilder clearDelay(); - /** - * Clears the repeat interval on this task. - * @return this builder, for chaining - */ - TaskBuilder clearRepeat(); + /** + * Clears the repeat interval on this task. + * + * @return this builder, for chaining + */ + TaskBuilder clearRepeat(); - /** - * Schedules this task for execution. - * @return the scheduled task - */ - ScheduledTask schedule(); - } + /** + * Schedules this task for execution. + * + * @return the scheduled task + */ + ScheduledTask schedule(); + } } diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java b/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java index e86f56e12..2ec051fec 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/TaskStatus.java @@ -1,16 +1,16 @@ package com.velocitypowered.api.scheduler; public enum TaskStatus { - /** - * The task is scheduled and is currently running. - */ - SCHEDULED, - /** - * The task was cancelled with {@link ScheduledTask#cancel()}. - */ - CANCELLED, - /** - * The task has run to completion. This is applicable only for tasks without a repeat. - */ - FINISHED + /** + * The task is scheduled and is currently running. + */ + SCHEDULED, + /** + * The task was cancelled with {@link ScheduledTask#cancel()}. + */ + CANCELLED, + /** + * The task has run to completion. This is applicable only for tasks without a repeat. + */ + FINISHED } 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 7ce0a4a99..c6aa38c9c 100644 --- a/api/src/main/java/com/velocitypowered/api/util/Favicon.java +++ b/api/src/main/java/com/velocitypowered/api/util/Favicon.java @@ -1,9 +1,6 @@ package com.velocitypowered.api.util; import com.google.common.base.Preconditions; -import org.checkerframework.checker.nullness.qual.Nullable; - -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -12,78 +9,92 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Base64; import java.util.Objects; +import javax.imageio.ImageIO; +import org.checkerframework.checker.nullness.qual.Nullable; /** - * Represents a Minecraft server favicon. A Minecraft server favicon is a 64x64 image that can be displayed to a remote - * client that sends a Server List Ping packet, and is automatically displayed in the Minecraft client. + * Represents a Minecraft server favicon. A Minecraft server favicon is a 64x64 image that can be + * displayed to a remote client that sends a Server List Ping packet, and is automatically displayed + * in the Minecraft client. */ public final class Favicon { - private final String base64Url; - /** - * Directly create a favicon using its Base64 URL directly. You are generally better served by the create() series - * of functions. - * @param base64Url the url for use with this favicon - */ - public Favicon(String base64Url) { - this.base64Url = Preconditions.checkNotNull(base64Url, "base64Url"); - } + private final String base64Url; - /** - * Returns the Base64-encoded URI for this image. - * @return a URL representing this favicon - */ - public String getBase64Url() { - return base64Url; - } + /** + * Directly create a favicon using its Base64 URL directly. You are generally better served by the + * create() series of functions. + * + * @param base64Url the url for use with this favicon + */ + public Favicon(String base64Url) { + this.base64Url = Preconditions.checkNotNull(base64Url, "base64Url"); + } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Favicon favicon = (Favicon) o; - return Objects.equals(base64Url, favicon.base64Url); - } + /** + * Returns the Base64-encoded URI for this image. + * + * @return a URL representing this favicon + */ + public String getBase64Url() { + return base64Url; + } - @Override - public int hashCode() { - return Objects.hash(base64Url); + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; } + if (o == null || getClass() != o.getClass()) { + return false; + } + Favicon favicon = (Favicon) o; + return Objects.equals(base64Url, favicon.base64Url); + } - @Override - public String toString() { - return "Favicon{" + - "base64Url='" + base64Url + '\'' + - '}'; - } + @Override + public int hashCode() { + return Objects.hash(base64Url); + } - /** - * Creates a new {@code Favicon} from the specified {@code image}. - * @param image the image to use for the favicon - * @return the created {@link Favicon} instance - */ - 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()); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - ImageIO.write(image, "PNG", os); - } catch (IOException e) { - throw new AssertionError(e); - } - return new Favicon("data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray())); - } + @Override + public String toString() { + return "Favicon{" + + "base64Url='" + base64Url + '\'' + + '}'; + } - /** - * Creates a new {@code Favicon} by reading the image from the specified {@code path}. - * @param path the path to the image to create a favicon for - * @return the created {@link Favicon} instance - * @throws IOException if the file could not be read from the path - */ - public static Favicon create(Path path) throws IOException { - try (InputStream stream = Files.newInputStream(path)) { - return create(ImageIO.read(stream)); - } + /** + * Creates a new {@code Favicon} from the specified {@code image}. + * + * @param image the image to use for the favicon + * @return the created {@link Favicon} instance + */ + 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()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + ImageIO.write(image, "PNG", os); + } catch (IOException e) { + throw new AssertionError(e); } + return new Favicon( + "data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray())); + } + + /** + * Creates a new {@code Favicon} by reading the image from the specified {@code path}. + * + * @param path the path to the image to create a favicon for + * @return the created {@link Favicon} instance + * @throws IOException if the file could not be read from the path + */ + 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 c9f290ff0..4646ededa 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 java.util.List; import java.util.UUID; @@ -10,82 +9,85 @@ import java.util.UUID; * Represents a Mojang game profile. This class is immutable. */ public final class GameProfile { - private final String id; + + private final String id; + private final String name; + private final 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); + } + + public String getId() { + return id; + } + + public UUID idAsUuid() { + return UuidUtils.fromUndashed(id); + } + + public String getName() { + return name; + } + + public List getProperties() { + return properties; + } + + /** + * Creates a game profile suitable for use in offline-mode. + * + * @param username the username to use + * @return the new offline-mode game profile + */ + public static GameProfile forOfflinePlayer(String username) { + Preconditions.checkNotNull(username, "username"); + String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username)); + return new GameProfile(id, username, ImmutableList.of()); + } + + @Override + public String toString() { + return "GameProfile{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", properties=" + properties + + '}'; + } + + public static final class Property { + private final String name; - private final List properties; + private final String value; + private final String signature; - 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); - } - - public String getId() { - return id; - } - - public UUID idAsUuid() { - return UuidUtils.fromUndashed(id); + 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"); } public String getName() { - return name; + return name; } - public List getProperties() { - return properties; + public String getValue() { + return value; } - /** - * Creates a game profile suitable for use in offline-mode. - * @param username the username to use - * @return the new offline-mode game profile - */ - public static GameProfile forOfflinePlayer(String username) { - Preconditions.checkNotNull(username, "username"); - String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username)); - return new GameProfile(id, username, ImmutableList.of()); + public String getSignature() { + return signature; } @Override public String toString() { - return "GameProfile{" + - "id='" + id + '\'' + - ", name='" + name + '\'' + - ", properties=" + properties + - '}'; - } - - public static final class Property { - private final String name; - private final String value; - private final 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"); - } - - public String getName() { - return name; - } - - public String getValue() { - return value; - } - - public String getSignature() { - return signature; - } - - @Override - public String toString() { - return "Property{" + - "name='" + name + '\'' + - ", value='" + value + '\'' + - ", signature='" + signature + '\'' + - '}'; - } + return "Property{" + + "name='" + name + '\'' + + ", value='" + value + '\'' + + ", signature='" + signature + '\'' + + '}'; } + } } diff --git a/api/src/main/java/com/velocitypowered/api/util/MessagePosition.java b/api/src/main/java/com/velocitypowered/api/util/MessagePosition.java index b27d7d4b1..dce7428e0 100644 --- a/api/src/main/java/com/velocitypowered/api/util/MessagePosition.java +++ b/api/src/main/java/com/velocitypowered/api/util/MessagePosition.java @@ -4,17 +4,18 @@ package com.velocitypowered.api.util; * Represents where a chat message is going to be sent. */ public enum MessagePosition { - /** - * The chat message will appear in the client's HUD. These messages can be filtered out by the client. - */ - CHAT, - /** - * The chat message will appear in the client's HUD and can't be dismissed. - */ - SYSTEM, - /** - * The chat message will appear above the player's main HUD. This text format doesn't support many component features, - * such as hover events. - */ - ACTION_BAR + /** + * The chat message will appear in the client's HUD. These messages can be filtered out by the + * client. + */ + CHAT, + /** + * The chat message will appear in the client's HUD and can't be dismissed. + */ + SYSTEM, + /** + * The chat message will appear above the player's main HUD. This text format doesn't support many + * component features, such as hover events. + */ + ACTION_BAR } diff --git a/api/src/main/java/com/velocitypowered/api/util/ModInfo.java b/api/src/main/java/com/velocitypowered/api/util/ModInfo.java index bdf7c9423..2c4aee8dd 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ModInfo.java +++ b/api/src/main/java/com/velocitypowered/api/util/ModInfo.java @@ -2,59 +2,60 @@ package com.velocitypowered.api.util; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; - import java.util.List; public final class ModInfo { - public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of()); - - private final String type; - private final List modList; - - public ModInfo(String type, List modList) { - this.type = Preconditions.checkNotNull(type, "type"); - this.modList = ImmutableList.copyOf(modList); + + public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of()); + + private final String type; + private final List modList; + + public ModInfo(String type, List modList) { + this.type = Preconditions.checkNotNull(type, "type"); + this.modList = ImmutableList.copyOf(modList); + } + + public String getType() { + return type; + } + + public List getMods() { + return modList; + } + + @Override + public String toString() { + return "ModInfo{" + + "type='" + type + '\'' + + ", modList=" + modList + + '}'; + } + + public static final class Mod { + + private final String id; + private final String version; + + public Mod(String id, String version) { + this.id = Preconditions.checkNotNull(id, "id"); + this.version = Preconditions.checkNotNull(version, "version"); } - - public String getType() { - return type; + + public String getId() { + return id; } - - public List getMods() { - return modList; + + public String getVersion() { + return version; } - + @Override public String toString() { - return "ModInfo{" + - "type='" + type + '\'' + - ", modList=" + modList + - '}'; - } - - public static final class Mod { - private final String id; - private final String version; - - public Mod(String id, String version) { - this.id = Preconditions.checkNotNull(id, "id"); - this.version = Preconditions.checkNotNull(version, "version"); - } - - public String getId() { - return id; - } - - public String getVersion() { - return version; - } - - @Override - public String toString() { - return "Mod{" + - "id='" + id + '\'' + - ", version='" + version + '\'' + - '}'; - } + return "Mod{" + + "id='" + id + '\'' + + ", version='" + version + '\'' + + '}'; } + } } \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java b/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java index 248b83512..b55ed0a68 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java +++ b/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java @@ -1,57 +1,61 @@ package com.velocitypowered.api.util; import com.google.common.base.Preconditions; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; /** * 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"); - } + private final String name; + private final String vendor; + private final String version; - public String getName() { - return name; - } + 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 getVendor() { - return vendor; - } + public String getName() { + return name; + } - public String getVersion() { - return version; - } + public String getVendor() { + return vendor; + } - @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); - } + public String getVersion() { + return version; + } - @Override - public int hashCode() { - return Objects.hash(name, vendor, 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 String toString() { - return "ProxyVersion{" + - "name='" + name + '\'' + - ", vendor='" + vendor + '\'' + - ", version='" + 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 920384612..a0f0e3f85 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 java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.UUID; @@ -11,41 +10,45 @@ import java.util.UUID; * Provides a small, useful selection of utilities for working with Minecraft UUIDs. */ public final class UuidUtils { - private UuidUtils() { - throw new AssertionError(); - } - /** - * Converts from an undashed Mojang-style UUID into a Java {@link UUID} object. - * @param string the string to convert - * @return the UUID object - */ - public static UUID fromUndashed(final String string) { - Objects.requireNonNull(string, "string"); - Preconditions.checkArgument(string.length() == 32, "Length is incorrect"); - return new UUID( - Long.parseUnsignedLong(string.substring(0, 16), 16), - Long.parseUnsignedLong(string.substring(16), 16) - ); - } + private UuidUtils() { + throw new AssertionError(); + } - /** - * Converts from a Java {@link UUID} object into an undashed Mojang-style UUID. - * @param uuid the UUID to convert - * @return the undashed 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'); - } + /** + * Converts from an undashed Mojang-style UUID into a Java {@link UUID} object. + * + * @param string the string to convert + * @return the UUID object + */ + public static UUID fromUndashed(final String string) { + Objects.requireNonNull(string, "string"); + Preconditions.checkArgument(string.length() == 32, "Length is incorrect"); + return new UUID( + Long.parseUnsignedLong(string.substring(0, 16), 16), + Long.parseUnsignedLong(string.substring(16), 16) + ); + } - /** - * Generates a UUID for use for offline mode. - * @param username the username to use - * @return the offline mode UUID - */ - public static UUID generateOfflinePlayerUuid(String username) { - return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); - } + /** + * Converts from a Java {@link UUID} object into an undashed Mojang-style UUID. + * + * @param uuid the UUID to convert + * @return the undashed 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'); + } + + /** + * Generates a UUID for use for offline mode. + * + * @param username the username to use + * @return the offline mode UUID + */ + 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 0f1fca552..3fd650e62 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 @@ -1,236 +1,251 @@ package com.velocitypowered.api.util.title; import com.google.common.base.Preconditions; -import net.kyori.text.Component; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Objects; import java.util.Optional; +import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents a "full" title, including all components. This class is immutable. */ public final class TextTitle implements Title { - private final @Nullable Component title; - private final @Nullable Component subtitle; - private final int stay; - private final int fadeIn; - private final int fadeOut; - private final boolean resetBeforeSend; - private TextTitle(Builder builder) { - this.title = builder.title; - this.subtitle = builder.subtitle; - this.stay = builder.stay; - this.fadeIn = builder.fadeIn; - this.fadeOut = builder.fadeOut; - this.resetBeforeSend = builder.resetBeforeSend; + private final @Nullable Component title; + private final @Nullable Component subtitle; + private final int stay; + private final int fadeIn; + private final int fadeOut; + private final boolean resetBeforeSend; + + private TextTitle(Builder builder) { + this.title = builder.title; + this.subtitle = builder.subtitle; + this.stay = builder.stay; + this.fadeIn = builder.fadeIn; + this.fadeOut = builder.fadeOut; + this.resetBeforeSend = builder.resetBeforeSend; + } + + /** + * Returns the main title this title has, if any. + * + * @return the main title of this title + */ + public Optional getTitle() { + return Optional.ofNullable(title); + } + + /** + * Returns the subtitle this title has, if any. + * + * @return the subtitle + */ + public Optional getSubtitle() { + return Optional.ofNullable(subtitle); + } + + /** + * Returns the number of ticks this title will stay up. + * + * @return how long the title will stay, in ticks + */ + public int getStay() { + return stay; + } + + /** + * Returns the number of ticks over which this title will fade in. + * + * @return how long the title will fade in, in ticks + */ + public int getFadeIn() { + return fadeIn; + } + + /** + * Returns the number of ticks over which this title will fade out. + * + * @return how long the title will fade out, in ticks + */ + public int getFadeOut() { + return fadeOut; + } + + /** + * Returns whether or not a reset packet will be sent before this title is sent. By default, + * unless explicitly disabled, this is enabled by default. + * + * @return whether or not a reset packet will be sent before this title is sent + */ + public boolean isResetBeforeSend() { + return resetBeforeSend; + } + + /** + * Determines whether or not this title has times set on it. If none are set, it will update the + * previous title set on the client. + * + * @return whether or not this title has times set on it + */ + public boolean areTimesSet() { + return stay != 0 || fadeIn != 0 || fadeOut != 0; + } + + /** + * Creates a new builder from the contents of this title so that it may be changed. + * + * @return a builder instance with the contents of this title + */ + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TextTitle textTitle = (TextTitle) o; + return stay == textTitle.stay && + fadeIn == textTitle.fadeIn && + fadeOut == textTitle.fadeOut && + resetBeforeSend == textTitle.resetBeforeSend && + Objects.equals(title, textTitle.title) && + Objects.equals(subtitle, textTitle.subtitle); + } + + @Override + public String toString() { + return "TextTitle{" + + "title=" + title + + ", subtitle=" + subtitle + + ", stay=" + stay + + ", fadeIn=" + fadeIn + + ", fadeOut=" + fadeOut + + ", resetBeforeSend=" + resetBeforeSend + + '}'; + } + + @Override + public int hashCode() { + return Objects.hash(title, subtitle, stay, fadeIn, fadeOut, resetBeforeSend); + } + + /** + * Creates a new builder for constructing titles. + * + * @return a builder for constructing titles + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private @Nullable Component title; + private @Nullable Component subtitle; + private int stay; + private int fadeIn; + private int fadeOut; + private boolean resetBeforeSend = true; + + private Builder() { + } + + private Builder(TextTitle copy) { + this.title = copy.title; + this.subtitle = copy.subtitle; + this.stay = copy.stay; + this.fadeIn = copy.fadeIn; + this.fadeOut = copy.fadeOut; + this.resetBeforeSend = copy.resetBeforeSend; + } + + public Builder title(Component title) { + this.title = Preconditions.checkNotNull(title, "title"); + return this; + } + + public Builder clearTitle() { + this.title = null; + return this; + } + + public Builder subtitle(Component subtitle) { + this.subtitle = Preconditions.checkNotNull(subtitle, "subtitle"); + return this; + } + + public Builder clearSubtitle() { + this.subtitle = null; + return this; + } + + public Builder stay(int ticks) { + Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks); + this.stay = ticks; + return this; + } + + public Builder fadeIn(int ticks) { + Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks); + this.fadeIn = ticks; + return this; + } + + public Builder fadeOut(int ticks) { + Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks); + this.fadeOut = ticks; + return this; + } + + public Builder resetBeforeSend(boolean b) { + this.resetBeforeSend = b; + return this; } - /** - * Returns the main title this title has, if any. - * @return the main title of this title - */ public Optional getTitle() { - return Optional.ofNullable(title); + return Optional.ofNullable(title); } - /** - * Returns the subtitle this title has, if any. - * @return the subtitle - */ public Optional getSubtitle() { - return Optional.ofNullable(subtitle); + return Optional.ofNullable(subtitle); } - /** - * Returns the number of ticks this title will stay up. - * @return how long the title will stay, in ticks - */ public int getStay() { - return stay; + return stay; } - /** - * Returns the number of ticks over which this title will fade in. - * @return how long the title will fade in, in ticks - */ public int getFadeIn() { - return fadeIn; + return fadeIn; } - /** - * Returns the number of ticks over which this title will fade out. - * @return how long the title will fade out, in ticks - */ public int getFadeOut() { - return fadeOut; + return fadeOut; } - /** - * Returns whether or not a reset packet will be sent before this title is sent. By default, unless explicitly - * disabled, this is enabled by default. - * @return whether or not a reset packet will be sent before this title is sent - */ public boolean isResetBeforeSend() { - return resetBeforeSend; + return resetBeforeSend; } - /** - * Determines whether or not this title has times set on it. If none are set, it will update the previous title - * set on the client. - * @return whether or not this title has times set on it - */ - public boolean areTimesSet() { - return stay != 0 || fadeIn != 0 || fadeOut != 0; - } - - /** - * Creates a new builder from the contents of this title so that it may be changed. - * @return a builder instance with the contents of this title - */ - public Builder toBuilder() { - return new Builder(this); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TextTitle textTitle = (TextTitle) o; - return stay == textTitle.stay && - fadeIn == textTitle.fadeIn && - fadeOut == textTitle.fadeOut && - resetBeforeSend == textTitle.resetBeforeSend && - Objects.equals(title, textTitle.title) && - Objects.equals(subtitle, textTitle.subtitle); + public TextTitle build() { + return new TextTitle(this); } @Override public String toString() { - return "TextTitle{" + - "title=" + title + - ", subtitle=" + subtitle + - ", stay=" + stay + - ", fadeIn=" + fadeIn + - ", fadeOut=" + fadeOut + - ", resetBeforeSend=" + resetBeforeSend + - '}'; - } - - @Override - public int hashCode() { - return Objects.hash(title, subtitle, stay, fadeIn, fadeOut, resetBeforeSend); - } - - /** - * Creates a new builder for constructing titles. - * @return a builder for constructing titles - */ - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private @Nullable Component title; - private @Nullable Component subtitle; - private int stay; - private int fadeIn; - private int fadeOut; - private boolean resetBeforeSend = true; - - private Builder() {} - - private Builder(TextTitle copy) { - this.title = copy.title; - this.subtitle = copy.subtitle; - this.stay = copy.stay; - this.fadeIn = copy.fadeIn; - this.fadeOut = copy.fadeOut; - this.resetBeforeSend = copy.resetBeforeSend; - } - - public Builder title(Component title) { - this.title = Preconditions.checkNotNull(title, "title"); - return this; - } - - public Builder clearTitle() { - this.title = null; - return this; - } - - public Builder subtitle(Component subtitle) { - this.subtitle = Preconditions.checkNotNull(subtitle, "subtitle"); - return this; - } - - public Builder clearSubtitle() { - this.subtitle = null; - return this; - } - - public Builder stay(int ticks) { - Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks); - this.stay = ticks; - return this; - } - - public Builder fadeIn(int ticks) { - Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks); - this.fadeIn = ticks; - return this; - } - - public Builder fadeOut(int ticks) { - Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks); - this.fadeOut = ticks; - return this; - } - - public Builder resetBeforeSend(boolean b) { - this.resetBeforeSend = b; - return this; - } - - public Optional getTitle() { - return Optional.ofNullable(title); - } - - public Optional getSubtitle() { - return Optional.ofNullable(subtitle); - } - - public int getStay() { - return stay; - } - - public int getFadeIn() { - return fadeIn; - } - - public int getFadeOut() { - return fadeOut; - } - - public boolean isResetBeforeSend() { - return resetBeforeSend; - } - - public TextTitle build() { - return new TextTitle(this); - } - - @Override - public String toString() { - return "Builder{" + - "title=" + title + - ", subtitle=" + subtitle + - ", stay=" + stay + - ", fadeIn=" + fadeIn + - ", fadeOut=" + fadeOut + - ", resetBeforeSend=" + resetBeforeSend + - '}'; - } + return "Builder{" + + "title=" + title + + ", subtitle=" + subtitle + + ", stay=" + stay + + ", fadeIn=" + fadeIn + + ", fadeOut=" + fadeOut + + ", resetBeforeSend=" + resetBeforeSend + + '}'; } + } } diff --git a/api/src/main/java/com/velocitypowered/api/util/title/Title.java b/api/src/main/java/com/velocitypowered/api/util/title/Title.java index 6849e38a4..bf70ba029 100644 --- a/api/src/main/java/com/velocitypowered/api/util/title/Title.java +++ b/api/src/main/java/com/velocitypowered/api/util/title/Title.java @@ -4,4 +4,5 @@ package com.velocitypowered.api.util.title; * Represents a title that can be sent to a Minecraft client. */ public interface Title { + } diff --git a/api/src/main/java/com/velocitypowered/api/util/title/Titles.java b/api/src/main/java/com/velocitypowered/api/util/title/Titles.java index 9474b575f..31b1959bc 100644 --- a/api/src/main/java/com/velocitypowered/api/util/title/Titles.java +++ b/api/src/main/java/com/velocitypowered/api/util/title/Titles.java @@ -4,47 +4,51 @@ package com.velocitypowered.api.util.title; * Provides special-purpose titles. */ public final class Titles { - private Titles() { - throw new AssertionError(); + + private Titles() { + throw new AssertionError(); + } + + private static final Title RESET = new Title() { + @Override + public String toString() { + return "reset title"; } + }; - private static final Title RESET = new Title() { - @Override - public String toString() { - return "reset title"; - } - }; - - private static final Title HIDE = new Title() { - @Override - public String toString() { - return "hide title"; - } - }; - - /** - * Returns a title that, when sent to the client, will cause all title data to be reset and any existing title to be - * hidden. - * @return the reset title - */ - public static Title reset() { - return RESET; + private static final Title HIDE = new Title() { + @Override + public String toString() { + return "hide title"; } + }; - /** - * Returns a title that, when sent to the client, will cause any existing title to be hidden. The title may be - * restored by a {@link TextTitle} with no title or subtitle (only a time). - * @return the hide title - */ - public static Title hide() { - return HIDE; - } + /** + * Returns a title that, when sent to the client, will cause all title data to be reset and any + * existing title to be hidden. + * + * @return the reset title + */ + public static Title reset() { + return RESET; + } - /** - * Returns a builder for {@link TextTitle}s. - * @return a builder for text titles - */ - public static TextTitle.Builder text() { - return TextTitle.builder(); - } + /** + * Returns a title that, when sent to the client, will cause any existing title to be hidden. The + * title may be restored by a {@link TextTitle} with no title or subtitle (only a time). + * + * @return the hide title + */ + public static Title hide() { + return HIDE; + } + + /** + * Returns a builder for {@link TextTitle}s. + * + * @return a builder for text titles + */ + public static TextTitle.Builder text() { + return TextTitle.builder(); + } } diff --git a/api/src/test/java/com/velocitypowered/api/util/UuidUtilsTest.java b/api/src/test/java/com/velocitypowered/api/util/UuidUtilsTest.java index 0638b41ff..b6b37620f 100644 --- a/api/src/test/java/com/velocitypowered/api/util/UuidUtilsTest.java +++ b/api/src/test/java/com/velocitypowered/api/util/UuidUtilsTest.java @@ -1,67 +1,81 @@ package com.velocitypowered.api.util; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; class UuidUtilsTest { - private static final UUID EXPECTED_DASHED_UUID = UUID.fromString("6b501978-d3be-4f33-bcf6-6e7808f37a0d"); - private static final String ACTUAL_UNDASHED_UUID = EXPECTED_DASHED_UUID.toString().replace("-", ""); - private static final UUID ISSUE_109_ZERO_UUID = new UUID(0, 0); - private static final String ISSUE_109_ZERO_UUID_UNDASHED = "00000000000000000000000000000000"; + private static final UUID EXPECTED_DASHED_UUID = UUID + .fromString("6b501978-d3be-4f33-bcf6-6e7808f37a0d"); + private static final String ACTUAL_UNDASHED_UUID = EXPECTED_DASHED_UUID.toString() + .replace("-", ""); - private static final UUID ISSUE_109_ONE_LSB_UUID = new UUID(0, 1); - private static final String ISSUE_109_ONE_LSB_UUID_UNDASHED = "00000000000000000000000000000001"; + private static final UUID ISSUE_109_ZERO_UUID = new UUID(0, 0); + private static final String ISSUE_109_ZERO_UUID_UNDASHED = "00000000000000000000000000000000"; - private static final UUID ISSUE_109_ONE_MLSB_UUID = new UUID(1, 1); - private static final String ISSUE_109_ONE_MLSB_UUID_UNDASHED = "00000000000000010000000000000001"; + private static final UUID ISSUE_109_ONE_LSB_UUID = new UUID(0, 1); + private static final String ISSUE_109_ONE_LSB_UUID_UNDASHED = "00000000000000000000000000000001"; - private static final UUID ISSUE_109_LEADING_ZERO_UUID = UUID.fromString("0d470a25-0416-48a1-b7a6-2a27aa5eb251"); - private static final String ISSUE_109_LEADING_ZERO_UNDASHED = "0d470a25041648a1b7a62a27aa5eb251"; + private static final UUID ISSUE_109_ONE_MLSB_UUID = new UUID(1, 1); + private static final String ISSUE_109_ONE_MLSB_UUID_UNDASHED = "00000000000000010000000000000001"; - private static final UUID TEST_OFFLINE_PLAYER_UUID = UUID.fromString("708f6260-183d-3912-bbde-5e279a5e739a"); - private static final String TEST_OFFLINE_PLAYER = "tuxed"; + private static final UUID ISSUE_109_LEADING_ZERO_UUID = UUID + .fromString("0d470a25-0416-48a1-b7a6-2a27aa5eb251"); + private static final String ISSUE_109_LEADING_ZERO_UNDASHED = "0d470a25041648a1b7a62a27aa5eb251"; - @Test - void generateOfflinePlayerUuid() { - assertEquals(TEST_OFFLINE_PLAYER_UUID, UuidUtils.generateOfflinePlayerUuid(TEST_OFFLINE_PLAYER), "UUIDs do not match"); - } + private static final UUID TEST_OFFLINE_PLAYER_UUID = UUID + .fromString("708f6260-183d-3912-bbde-5e279a5e739a"); + private static final String TEST_OFFLINE_PLAYER = "tuxed"; - @Test - void fromUndashed() { - assertEquals(EXPECTED_DASHED_UUID, UuidUtils.fromUndashed(ACTUAL_UNDASHED_UUID), "UUIDs do not match"); - } + @Test + void generateOfflinePlayerUuid() { + assertEquals(TEST_OFFLINE_PLAYER_UUID, UuidUtils.generateOfflinePlayerUuid(TEST_OFFLINE_PLAYER), + "UUIDs do not match"); + } - @Test - void toUndashed() { - assertEquals(ACTUAL_UNDASHED_UUID, UuidUtils.toUndashed(EXPECTED_DASHED_UUID), "UUIDs do not match"); - } + @Test + void fromUndashed() { + assertEquals(EXPECTED_DASHED_UUID, UuidUtils.fromUndashed(ACTUAL_UNDASHED_UUID), + "UUIDs do not match"); + } - @Test - void zeroUuidIssue109() { - assertEquals(ISSUE_109_ZERO_UUID, UuidUtils.fromUndashed(ISSUE_109_ZERO_UUID_UNDASHED), "UUIDs do not match"); - assertEquals(ISSUE_109_ZERO_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ZERO_UUID), "UUIDs do not match"); - } + @Test + void toUndashed() { + assertEquals(ACTUAL_UNDASHED_UUID, UuidUtils.toUndashed(EXPECTED_DASHED_UUID), + "UUIDs do not match"); + } - @Test - void leadingZeroUuidIssue109() { - assertEquals(ISSUE_109_LEADING_ZERO_UUID, UuidUtils.fromUndashed(ISSUE_109_LEADING_ZERO_UNDASHED), "UUIDs do not match"); - assertEquals(ISSUE_109_LEADING_ZERO_UNDASHED, UuidUtils.toUndashed(ISSUE_109_LEADING_ZERO_UUID), "UUIDs do not match"); - } + @Test + void zeroUuidIssue109() { + assertEquals(ISSUE_109_ZERO_UUID, UuidUtils.fromUndashed(ISSUE_109_ZERO_UUID_UNDASHED), + "UUIDs do not match"); + assertEquals(ISSUE_109_ZERO_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ZERO_UUID), + "UUIDs do not match"); + } - @Test - void oneUuidLsbIssue109() { - assertEquals(ISSUE_109_ONE_LSB_UUID, UuidUtils.fromUndashed(ISSUE_109_ONE_LSB_UUID_UNDASHED), "UUIDs do not match"); - assertEquals(ISSUE_109_ONE_LSB_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ONE_LSB_UUID), "UUIDs do not match"); - } + @Test + void leadingZeroUuidIssue109() { + assertEquals(ISSUE_109_LEADING_ZERO_UUID, + UuidUtils.fromUndashed(ISSUE_109_LEADING_ZERO_UNDASHED), "UUIDs do not match"); + assertEquals(ISSUE_109_LEADING_ZERO_UNDASHED, UuidUtils.toUndashed(ISSUE_109_LEADING_ZERO_UUID), + "UUIDs do not match"); + } - @Test - void oneUuidMsbAndLsbIssue109() { - assertEquals(ISSUE_109_ONE_MLSB_UUID, UuidUtils.fromUndashed(ISSUE_109_ONE_MLSB_UUID_UNDASHED), "UUIDs do not match"); - assertEquals(ISSUE_109_ONE_MLSB_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ONE_MLSB_UUID), "UUIDs do not match"); - } + @Test + void oneUuidLsbIssue109() { + assertEquals(ISSUE_109_ONE_LSB_UUID, UuidUtils.fromUndashed(ISSUE_109_ONE_LSB_UUID_UNDASHED), + "UUIDs do not match"); + assertEquals(ISSUE_109_ONE_LSB_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ONE_LSB_UUID), + "UUIDs do not match"); + } + + @Test + void oneUuidMsbAndLsbIssue109() { + assertEquals(ISSUE_109_ONE_MLSB_UUID, UuidUtils.fromUndashed(ISSUE_109_ONE_MLSB_UUID_UNDASHED), + "UUIDs do not match"); + assertEquals(ISSUE_109_ONE_MLSB_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ONE_MLSB_UUID), + "UUIDs do not match"); + } } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 000000000..4509ee489 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/checkerframework.gradle b/gradle/checkerframework.gradle index dd225b272..a2643d10f 100644 --- a/gradle/checkerframework.gradle +++ b/gradle/checkerframework.gradle @@ -25,7 +25,7 @@ dependencies { 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()) { + } 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" diff --git a/gradle/checkstyle.gradle b/gradle/checkstyle.gradle new file mode 100644 index 000000000..7ff1c3b2d --- /dev/null +++ b/gradle/checkstyle.gradle @@ -0,0 +1,4 @@ +checkstyle { + toolVersion '8.14' + configFile new File(project.rootDir, ['config', 'checkstyle', 'checkstyle.xml'].join(File.separator)) +} \ No newline at end of file diff --git a/native/build.gradle b/native/build.gradle index 364ef5543..ced801284 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -1,8 +1,10 @@ plugins { id 'java' + id 'checkstyle' } apply from: '../gradle/checkerframework.gradle' +apply from: '../gradle/checkstyle.gradle' dependencies { compile "com.google.guava:guava:${guavaVersion}" diff --git a/native/src/main/java/com/velocitypowered/natives/Disposable.java b/native/src/main/java/com/velocitypowered/natives/Disposable.java index bb2cf0350..1aad8597e 100644 --- a/native/src/main/java/com/velocitypowered/natives/Disposable.java +++ b/native/src/main/java/com/velocitypowered/natives/Disposable.java @@ -1,13 +1,16 @@ package com.velocitypowered.natives; /** - * This marker interface indicates that this object should be explicitly disposed before the object can no longer be used. - * Not disposing these objects will likely leak native resources and eventually lead to resource exhaustion. + * This marker interface indicates that this object should be explicitly disposed before the object + * can no longer be used. Not disposing these objects will likely leak native resources and + * eventually lead to resource exhaustion. */ public interface Disposable { - /** - * Disposes this object. After this call returns, any use of this object becomes invalid. Multiple calls to - * this function should be safe: there should be no side-effects once an object is disposed. - */ - void dispose(); + + /** + * Disposes this object. After this call returns, any use of this object becomes invalid. Multiple + * calls to this function should be safe: there should be no side-effects once an object is + * disposed. + */ + void dispose(); } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java index 2fdd91bab..015b4adca 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java @@ -2,62 +2,62 @@ package com.velocitypowered.natives.compression; import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; - import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; public class JavaVelocityCompressor implements VelocityCompressor { - public static final VelocityCompressorFactory FACTORY = JavaVelocityCompressor::new; - private final Deflater deflater; - private final Inflater inflater; - private final byte[] buf; - private boolean disposed = false; + public static final VelocityCompressorFactory FACTORY = JavaVelocityCompressor::new; - private JavaVelocityCompressor(int level) { - this.deflater = new Deflater(level); - this.inflater = new Inflater(); - this.buf = new byte[ZLIB_BUFFER_SIZE]; + private final Deflater deflater; + private final Inflater inflater; + private final byte[] buf; + private boolean disposed = false; + + private JavaVelocityCompressor(int level) { + this.deflater = new Deflater(level); + this.inflater = new Inflater(); + this.buf = new byte[ZLIB_BUFFER_SIZE]; + } + + @Override + public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + ensureNotDisposed(); + + byte[] inData = new byte[source.readableBytes()]; + source.readBytes(inData); + inflater.setInput(inData); + while (!inflater.finished()) { + int read = inflater.inflate(buf); + destination.writeBytes(buf, 0, read); } + inflater.reset(); + } - @Override - public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException { - ensureNotDisposed(); + @Override + public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + ensureNotDisposed(); - byte[] inData = new byte[source.readableBytes()]; - source.readBytes(inData); - inflater.setInput(inData); - while (!inflater.finished()) { - int read = inflater.inflate(buf); - destination.writeBytes(buf, 0, read); - } - inflater.reset(); + byte[] inData = new byte[source.readableBytes()]; + source.readBytes(inData); + deflater.setInput(inData); + deflater.finish(); + while (!deflater.finished()) { + int bytes = deflater.deflate(buf); + destination.writeBytes(buf, 0, bytes); } + deflater.reset(); + } - @Override - public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { - ensureNotDisposed(); + @Override + public void dispose() { + disposed = true; + deflater.end(); + inflater.end(); + } - byte[] inData = new byte[source.readableBytes()]; - source.readBytes(inData); - deflater.setInput(inData); - deflater.finish(); - while (!deflater.finished()) { - int bytes = deflater.deflate(buf); - destination.writeBytes(buf, 0, bytes); - } - deflater.reset(); - } - - @Override - public void dispose() { - disposed = true; - deflater.end(); - inflater.end(); - } - - private void ensureNotDisposed() { - Preconditions.checkState(!disposed, "Object already disposed"); - } + private void ensureNotDisposed() { + Preconditions.checkState(!disposed, "Object already disposed"); + } } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java index 90b96ec04..40961693a 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeVelocityCompressor.java @@ -2,75 +2,78 @@ package com.velocitypowered.natives.compression; import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; - import java.util.zip.DataFormatException; public class NativeVelocityCompressor implements VelocityCompressor { - public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new; - private final NativeZlibInflate inflate = new NativeZlibInflate(); - private final long inflateCtx; - private final NativeZlibDeflate deflate = new NativeZlibDeflate(); - private final long deflateCtx; - private boolean disposed = false; + public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new; - private NativeVelocityCompressor(int level) { - this.inflateCtx = inflate.init(); - this.deflateCtx = deflate.init(level); + private final NativeZlibInflate inflate = new NativeZlibInflate(); + private final long inflateCtx; + private final NativeZlibDeflate deflate = new NativeZlibDeflate(); + private final long deflateCtx; + private boolean disposed = false; + + private NativeVelocityCompressor(int level) { + this.inflateCtx = inflate.init(); + this.deflateCtx = deflate.init(level); + } + + @Override + public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + ensureNotDisposed(); + source.memoryAddress(); + destination.memoryAddress(); + + while (!inflate.finished && source.isReadable()) { + if (!destination.isWritable()) { + destination.ensureWritable(ZLIB_BUFFER_SIZE); + } + int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(), + source.readableBytes(), + destination.memoryAddress() + destination.writerIndex(), destination.writableBytes()); + source.readerIndex(source.readerIndex() + inflate.consumed); + destination.writerIndex(destination.writerIndex() + produced); } - @Override - public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException { - ensureNotDisposed(); - source.memoryAddress(); - destination.memoryAddress(); + inflate.reset(inflateCtx); + inflate.consumed = 0; + inflate.finished = false; + } - while (!inflate.finished && source.isReadable()) { - if (!destination.isWritable()) { - destination.ensureWritable(ZLIB_BUFFER_SIZE); - } - int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(), source.readableBytes(), - destination.memoryAddress() + destination.writerIndex(), destination.writableBytes()); - source.readerIndex(source.readerIndex() + inflate.consumed); - destination.writerIndex(destination.writerIndex() + produced); - } + @Override + public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + ensureNotDisposed(); + source.memoryAddress(); + destination.memoryAddress(); - inflate.reset(inflateCtx); - inflate.consumed = 0; - inflate.finished = false; + while (!deflate.finished) { + if (!destination.isWritable()) { + destination.ensureWritable(ZLIB_BUFFER_SIZE); + } + int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(), + source.readableBytes(), + destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(), + !source.isReadable()); + source.readerIndex(source.readerIndex() + deflate.consumed); + destination.writerIndex(destination.writerIndex() + produced); } - @Override - public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { - ensureNotDisposed(); - source.memoryAddress(); - destination.memoryAddress(); + deflate.reset(deflateCtx); + deflate.consumed = 0; + deflate.finished = false; + } - while (!deflate.finished) { - if (!destination.isWritable()) { - destination.ensureWritable(ZLIB_BUFFER_SIZE); - } - int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(), source.readableBytes(), - destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(), !source.isReadable()); - source.readerIndex(source.readerIndex() + deflate.consumed); - destination.writerIndex(destination.writerIndex() + produced); - } + private void ensureNotDisposed() { + Preconditions.checkState(!disposed, "Object already disposed"); + } - deflate.reset(deflateCtx); - deflate.consumed = 0; - deflate.finished = false; - } - - private void ensureNotDisposed() { - Preconditions.checkState(!disposed, "Object already disposed"); - } - - @Override - public void dispose() { - if (!disposed) { - inflate.free(inflateCtx); - deflate.free(deflateCtx); - } - disposed = true; + @Override + public void dispose() { + if (!disposed) { + inflate.free(inflateCtx); + deflate.free(deflateCtx); } + disposed = true; + } } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java index 609f34cb4..ca5cd468a 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java @@ -4,21 +4,23 @@ package com.velocitypowered.natives.compression; * Represents a native interface for zlib's deflate functions. */ class NativeZlibDeflate { - boolean finished; - int consumed; - native long init(int level); + boolean finished; + int consumed; - native long free(long ctx); + native long init(int level); - native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength, - boolean flush); + native long free(long ctx); - native void reset(long ctx); + native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, + int destinationLength, + boolean flush); - static { - initIDs(); - } + native void reset(long ctx); - private static native void initIDs(); + static { + initIDs(); + } + + private static native void initIDs(); } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java index d9d7a9fb9..7eabd9a05 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java @@ -4,20 +4,22 @@ package com.velocitypowered.natives.compression; * Represents a native interface for zlib's inflate functions. */ class NativeZlibInflate { - boolean finished; - int consumed; - native long init(); + boolean finished; + int consumed; - native long free(long ctx); + native long init(); - native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength); + native long free(long ctx); - native void reset(long ctx); + native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, + int destinationLength); - static { - initIDs(); - } + native void reset(long ctx); - private static native void initIDs(); + static { + initIDs(); + } + + private static native void initIDs(); } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java index de42e629e..fd0bd9f02 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java @@ -2,19 +2,19 @@ package com.velocitypowered.natives.compression; import com.velocitypowered.natives.Disposable; import io.netty.buffer.ByteBuf; - import java.util.zip.DataFormatException; /** * Provides an interface to inflate and deflate {@link ByteBuf}s using zlib. */ public interface VelocityCompressor extends Disposable { - /** - * The default preferred output buffer size for zlib. - */ - int ZLIB_BUFFER_SIZE = 8192; - void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException; + /** + * The default preferred output buffer size for zlib. + */ + int ZLIB_BUFFER_SIZE = 8192; - void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; + void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException; + + void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressorFactory.java b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressorFactory.java index 493bdee24..dda60f76c 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressorFactory.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressorFactory.java @@ -1,5 +1,6 @@ package com.velocitypowered.natives.compression; public interface VelocityCompressorFactory { - VelocityCompressor create(int level); + + VelocityCompressor create(int level); } diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java b/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java index e74480793..d446c92a4 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java @@ -2,53 +2,54 @@ package com.velocitypowered.natives.encryption; import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; - +import java.security.GeneralSecurityException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; -import java.security.GeneralSecurityException; public class JavaVelocityCipher implements VelocityCipher { - public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() { - @Override - public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException { - return new JavaVelocityCipher(true, key); - } - @Override - public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException { - return new JavaVelocityCipher(false, key); - } - }; - - private final Cipher cipher; - private boolean disposed = false; - - private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException { - this.cipher = Cipher.getInstance("AES/CFB8/NoPadding"); - this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(key.getEncoded())); + public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() { + @Override + public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException { + return new JavaVelocityCipher(true, key); } @Override - public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException { - ensureNotDisposed(); - - byte[] sourceAsBytes = new byte[source.readableBytes()]; - source.readBytes(sourceAsBytes); - - int outputSize = cipher.getOutputSize(sourceAsBytes.length); - byte[] destinationBytes = new byte[outputSize]; - cipher.update(sourceAsBytes, 0, sourceAsBytes.length, destinationBytes); - destination.writeBytes(destinationBytes); + public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException { + return new JavaVelocityCipher(false, key); } + }; - @Override - public void dispose() { - disposed = true; - } + private final Cipher cipher; + private boolean disposed = false; - private void ensureNotDisposed() { - Preconditions.checkState(!disposed, "Object already disposed"); - } + private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException { + this.cipher = Cipher.getInstance("AES/CFB8/NoPadding"); + this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, + new IvParameterSpec(key.getEncoded())); + } + + @Override + public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException { + ensureNotDisposed(); + + byte[] sourceAsBytes = new byte[source.readableBytes()]; + source.readBytes(sourceAsBytes); + + int outputSize = cipher.getOutputSize(sourceAsBytes.length); + byte[] destinationBytes = new byte[outputSize]; + cipher.update(sourceAsBytes, 0, sourceAsBytes.length, destinationBytes); + destination.writeBytes(destinationBytes); + } + + @Override + public void dispose() { + disposed = true; + } + + private void ensureNotDisposed() { + Preconditions.checkState(!disposed, "Object already disposed"); + } } diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/MbedtlsAesImpl.java b/native/src/main/java/com/velocitypowered/natives/encryption/MbedtlsAesImpl.java index 9af2b2ed4..9e5aa403d 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/MbedtlsAesImpl.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/MbedtlsAesImpl.java @@ -1,9 +1,11 @@ package com.velocitypowered.natives.encryption; public class MbedtlsAesImpl { - native long init(byte[] key); - native void process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, boolean encrypt); + native long init(byte[] key); - native void free(long ptr); + native void process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, + boolean encrypt); + + native void free(long ptr); } diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/NativeVelocityCipher.java b/native/src/main/java/com/velocitypowered/natives/encryption/NativeVelocityCipher.java index de4d24321..51f5be745 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/NativeVelocityCipher.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/NativeVelocityCipher.java @@ -1,55 +1,55 @@ package com.velocitypowered.natives.encryption; import io.netty.buffer.ByteBuf; - +import java.security.GeneralSecurityException; import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; -import java.security.GeneralSecurityException; public class NativeVelocityCipher implements VelocityCipher { - public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() { - @Override - public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException { - return new NativeVelocityCipher(true, key); - } - @Override - public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException { - return new NativeVelocityCipher(false, key); - } - }; - private static final MbedtlsAesImpl impl = new MbedtlsAesImpl(); - - private final long ctx; - private final boolean encrypt; - private boolean disposed = false; - - private NativeVelocityCipher(boolean encrypt, SecretKey key) { - this.encrypt = encrypt; - this.ctx = impl.init(key.getEncoded()); + public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() { + @Override + public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException { + return new NativeVelocityCipher(true, key); } @Override - public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException { - source.memoryAddress(); - destination.memoryAddress(); - - // The exact amount we read in is also the amount we write out. - int len = source.readableBytes(); - destination.ensureWritable(len); - - impl.process(ctx, source.memoryAddress() + source.readerIndex(), len, - destination.memoryAddress() + destination.writerIndex(), encrypt); - - source.skipBytes(len); - destination.writerIndex(destination.writerIndex() + len); + public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException { + return new NativeVelocityCipher(false, key); } + }; + private static final MbedtlsAesImpl impl = new MbedtlsAesImpl(); - @Override - public void dispose() { - if (!disposed) { - impl.free(ctx); - } - disposed = true; + private final long ctx; + private final boolean encrypt; + private boolean disposed = false; + + private NativeVelocityCipher(boolean encrypt, SecretKey key) { + this.encrypt = encrypt; + this.ctx = impl.init(key.getEncoded()); + } + + @Override + public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException { + source.memoryAddress(); + destination.memoryAddress(); + + // The exact amount we read in is also the amount we write out. + int len = source.readableBytes(); + destination.ensureWritable(len); + + impl.process(ctx, source.memoryAddress() + source.readerIndex(), len, + destination.memoryAddress() + destination.writerIndex(), encrypt); + + source.skipBytes(len); + destination.writerIndex(destination.writerIndex() + len); + } + + @Override + public void dispose() { + if (!disposed) { + impl.free(ctx); } + disposed = true; + } } diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/VelocityCipher.java b/native/src/main/java/com/velocitypowered/natives/encryption/VelocityCipher.java index d7ba55427..31dbeaa97 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/VelocityCipher.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/VelocityCipher.java @@ -2,9 +2,9 @@ package com.velocitypowered.natives.encryption; import com.velocitypowered.natives.Disposable; import io.netty.buffer.ByteBuf; - import javax.crypto.ShortBufferException; public interface VelocityCipher extends Disposable { - void process(ByteBuf source, ByteBuf destination) throws ShortBufferException; + + void process(ByteBuf source, ByteBuf destination) throws ShortBufferException; } diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/VelocityCipherFactory.java b/native/src/main/java/com/velocitypowered/natives/encryption/VelocityCipherFactory.java index ea3d6b536..8ce83a1b1 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/VelocityCipherFactory.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/VelocityCipherFactory.java @@ -1,10 +1,11 @@ package com.velocitypowered.natives.encryption; -import javax.crypto.SecretKey; import java.security.GeneralSecurityException; +import javax.crypto.SecretKey; public interface VelocityCipherFactory { - VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException; - VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException; + VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException; + + VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException; } 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 00bda3454..11dc09d5f 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java +++ b/native/src/main/java/com/velocitypowered/natives/util/NativeCodeLoader.java @@ -1,81 +1,85 @@ package com.velocitypowered.natives.util; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.List; import java.util.function.BooleanSupplier; import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.Nullable; public final class NativeCodeLoader implements Supplier { - private final Variant selected; - NativeCodeLoader(List> variants) { - this.selected = getVariant(variants); + private final Variant selected; + + NativeCodeLoader(List> variants) { + this.selected = getVariant(variants); + } + + @Override + public T get() { + return selected.object; + } + + private static Variant getVariant(List> variants) { + for (Variant variant : variants) { + T got = variant.get(); + if (got == null) { + continue; + } + return variant; + } + throw new IllegalArgumentException("Can't find any suitable variants"); + } + + public String getLoadedVariant() { + return selected.name; + } + + static class Variant { + + private Status status; + private final Runnable setup; + private final String name; + private final T object; + + Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) { + this.status = + possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE; + this.setup = setup; + this.name = name; + this.object = object; } - @Override - public T get() { - return selected.object; - } + public @Nullable T get() { + if (status == Status.NOT_AVAILABLE || status == Status.SETUP_FAILURE) { + return null; + } - private static Variant getVariant(List> variants) { - for (Variant variant : variants) { - T got = variant.get(); - if (got == null) { - continue; - } - return variant; + // Make sure setup happens only once + if (status == Status.POSSIBLY_AVAILABLE) { + try { + setup.run(); + status = Status.SETUP; + } catch (Exception e) { + status = Status.SETUP_FAILURE; + return null; } - throw new IllegalArgumentException("Can't find any suitable variants"); + } + + return object; } + } - public String getLoadedVariant() { - return selected.name; - } + private enum Status { + NOT_AVAILABLE, + POSSIBLY_AVAILABLE, + SETUP, + SETUP_FAILURE + } - static class Variant { - private Status status; - private final Runnable setup; - private final String name; - private final T object; - - Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) { - this.status = possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE; - this.setup = setup; - this.name = name; - this.object = object; - } - - public @Nullable T get() { - if (status == Status.NOT_AVAILABLE || status == Status.SETUP_FAILURE) { - return null; - } - - // Make sure setup happens only once - if (status == Status.POSSIBLY_AVAILABLE) { - try { - setup.run(); - status = Status.SETUP; - } catch (Exception e) { - status = Status.SETUP_FAILURE; - return null; - } - } - - return object; - } - } - - private enum Status { - NOT_AVAILABLE, - POSSIBLY_AVAILABLE, - SETUP, - SETUP_FAILURE - } - - static final BooleanSupplier MACOS = () -> System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") && - System.getProperty("os.arch").equals("x86_64"); - static final BooleanSupplier LINUX = () -> System.getProperties().getProperty("os.name", "").equalsIgnoreCase("Linux") && - System.getProperty("os.arch").equals("amd64"); - static final BooleanSupplier ALWAYS = () -> true; + static final BooleanSupplier MACOS = () -> + System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") && + System.getProperty("os.arch").equals("x86_64"); + static final BooleanSupplier LINUX = () -> + System.getProperties().getProperty("os.name", "").equalsIgnoreCase("Linux") && + System.getProperty("os.arch").equals("amd64"); + static final BooleanSupplier ALWAYS = () -> true; } 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 e50a70ca2..2a395ad46 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/Natives.java +++ b/native/src/main/java/com/velocitypowered/natives/util/Natives.java @@ -6,7 +6,6 @@ import com.velocitypowered.natives.compression.NativeVelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressorFactory; import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.VelocityCipherFactory; - import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -14,55 +13,58 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; public class Natives { - private Natives() { - throw new AssertionError(); - } - private static Runnable copyAndLoadNative(String path) { - return () -> { - try { - Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.'))); - InputStream nativeLib = Natives.class.getResourceAsStream(path); - if (nativeLib == null) { - throw new IllegalStateException("Native library " + path + " not found."); - } + private Natives() { + throw new AssertionError(); + } - Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - Files.deleteIfExists(tempFile); - } catch (IOException ignored) { - // Well, it doesn't matter... - } - })); - System.load(tempFile.toAbsolutePath().toString()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }; - } + private static Runnable copyAndLoadNative(String path) { + return () -> { + try { + Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.'))); + InputStream nativeLib = Natives.class.getResourceAsStream(path); + if (nativeLib == null) { + throw new IllegalStateException("Native library " + path + " not found."); + } - public static final NativeCodeLoader compressor = new NativeCodeLoader<>( - ImmutableList.of( - new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS, - copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)", - NativeVelocityCompressor.FACTORY), - new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX, - copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)", - NativeVelocityCompressor.FACTORY), - new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java", JavaVelocityCompressor.FACTORY) - ) - ); + Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + Files.deleteIfExists(tempFile); + } catch (IOException ignored) { + // Well, it doesn't matter... + } + })); + System.load(tempFile.toAbsolutePath().toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + } - public static final NativeCodeLoader cipher = new NativeCodeLoader<>( - ImmutableList.of( + public static final NativeCodeLoader compressor = new NativeCodeLoader<>( + ImmutableList.of( + new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS, + copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)", + NativeVelocityCompressor.FACTORY), + new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX, + copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)", + NativeVelocityCompressor.FACTORY), + new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> { + }, "Java", JavaVelocityCompressor.FACTORY) + ) + ); + + public static final NativeCodeLoader cipher = new NativeCodeLoader<>( + ImmutableList.of( /*new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS, copyAndLoadNative("/macosx/velocity-cipher.dylib"), "mbed TLS (macOS)", NativeVelocityCipher.FACTORY), new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX, copyAndLoadNative("/linux_x64/velocity-cipher.so"), "mbed TLS (Linux amd64)", NativeVelocityCipher.FACTORY),*/ - new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java", JavaVelocityCipher.FACTORY) - ) - ); + new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> { + }, "Java", JavaVelocityCipher.FACTORY) + ) + ); } diff --git a/native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java b/native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java index 363c80442..64c1df251 100644 --- a/native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java +++ b/native/src/test/java/com/velocitypowered/natives/compression/VelocityCompressorTest.java @@ -1,65 +1,66 @@ package com.velocitypowered.natives.compression; -import com.velocitypowered.natives.util.Natives; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnOs; - -import java.util.Random; -import java.util.zip.DataFormatException; -import java.util.zip.Deflater; - import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.MAC; +import com.velocitypowered.natives.util.Natives; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import java.util.Random; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; + class VelocityCompressorTest { - @BeforeAll - static void checkNatives() { - Natives.compressor.getLoadedVariant(); + + @BeforeAll + static void checkNatives() { + Natives.compressor.getLoadedVariant(); + } + + @Test + @EnabledOnOs({MAC, LINUX}) + void nativeIntegrityCheck() throws DataFormatException { + VelocityCompressor compressor = Natives.compressor.get().create(Deflater.DEFAULT_COMPRESSION); + if (compressor instanceof JavaVelocityCompressor) { + compressor.dispose(); + fail("Loaded regular compressor"); } + check(compressor); + } - @Test - @EnabledOnOs({ MAC, LINUX }) - void nativeIntegrityCheck() throws DataFormatException { - VelocityCompressor compressor = Natives.compressor.get().create(Deflater.DEFAULT_COMPRESSION); - if (compressor instanceof JavaVelocityCompressor) { - compressor.dispose(); - fail("Loaded regular compressor"); - } - check(compressor); - } - - @Test - void javaIntegrityCheck() throws DataFormatException { - VelocityCompressor compressor = JavaVelocityCompressor.FACTORY.create(Deflater.DEFAULT_COMPRESSION); - check(compressor); - } - - private void check(VelocityCompressor compressor) throws DataFormatException { - ByteBuf source = Unpooled.directBuffer(); - ByteBuf dest = Unpooled.directBuffer(); - ByteBuf decompressed = Unpooled.directBuffer(); - - Random random = new Random(1); - byte[] randomBytes = new byte[1 << 16]; - random.nextBytes(randomBytes); - source.writeBytes(randomBytes); - - try { - compressor.deflate(source, dest); - compressor.inflate(dest, decompressed); - source.readerIndex(0); - assertTrue(ByteBufUtil.equals(source, decompressed)); - } finally { - source.release(); - dest.release(); - decompressed.release(); - compressor.dispose(); - } + @Test + void javaIntegrityCheck() throws DataFormatException { + VelocityCompressor compressor = JavaVelocityCompressor.FACTORY + .create(Deflater.DEFAULT_COMPRESSION); + check(compressor); + } + + private void check(VelocityCompressor compressor) throws DataFormatException { + ByteBuf source = Unpooled.directBuffer(); + ByteBuf dest = Unpooled.directBuffer(); + ByteBuf decompressed = Unpooled.directBuffer(); + + Random random = new Random(1); + byte[] randomBytes = new byte[1 << 16]; + random.nextBytes(randomBytes); + source.writeBytes(randomBytes); + + try { + compressor.deflate(source, dest); + compressor.inflate(dest, decompressed); + source.readerIndex(0); + assertTrue(ByteBufUtil.equals(source, decompressed)); + } finally { + source.release(); + dest.release(); + decompressed.release(); + compressor.dispose(); } + } } \ No newline at end of file diff --git a/native/src/test/java/com/velocitypowered/natives/encryption/VelocityCipherTest.java b/native/src/test/java/com/velocitypowered/natives/encryption/VelocityCipherTest.java index 11d40f093..968f675b5 100644 --- a/native/src/test/java/com/velocitypowered/natives/encryption/VelocityCipherTest.java +++ b/native/src/test/java/com/velocitypowered/natives/encryption/VelocityCipherTest.java @@ -1,71 +1,71 @@ package com.velocitypowered.natives.encryption; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import com.velocitypowered.natives.util.Natives; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; +import java.security.GeneralSecurityException; +import java.util.Random; +import javax.crypto.spec.SecretKeySpec; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import javax.crypto.spec.SecretKeySpec; -import java.security.GeneralSecurityException; -import java.util.Random; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - class VelocityCipherTest { - private static final int ENCRYPT_DATA_SIZE = 1 << 16; - @BeforeAll - static void checkNatives() { - Natives.cipher.getLoadedVariant(); + private static final int ENCRYPT_DATA_SIZE = 1 << 16; + + @BeforeAll + static void checkNatives() { + Natives.cipher.getLoadedVariant(); + } + + @Test + @Disabled + void nativeIntegrityCheck() throws GeneralSecurityException { + VelocityCipherFactory factory = Natives.cipher.get(); + if (factory == JavaVelocityCipher.FACTORY) { + fail("Loaded regular compressor"); } + check(factory); + } - @Test - @Disabled - void nativeIntegrityCheck() throws GeneralSecurityException { - VelocityCipherFactory factory = Natives.cipher.get(); - if (factory == JavaVelocityCipher.FACTORY) { - fail("Loaded regular compressor"); - } - check(factory); - } - - @Test - void javaIntegrityCheck() throws GeneralSecurityException { - check(JavaVelocityCipher.FACTORY); - } - - private void check(VelocityCipherFactory factory) throws GeneralSecurityException { - // Generate a random 16-byte key. - Random random = new Random(1); - byte[] key = new byte[16]; - random.nextBytes(key); - - VelocityCipher decrypt = factory.forDecryption(new SecretKeySpec(key, "AES")); - VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(key, "AES")); - - ByteBuf source = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); - ByteBuf dest = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); - ByteBuf decryptionBuf = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); - - byte[] randomBytes = new byte[ENCRYPT_DATA_SIZE]; - random.nextBytes(randomBytes); - source.writeBytes(randomBytes); - - try { - encrypt.process(source, dest); - decrypt.process(dest, decryptionBuf); - source.readerIndex(0); - assertTrue(ByteBufUtil.equals(source, decryptionBuf)); - } finally { - source.release(); - dest.release(); - decryptionBuf.release(); - decrypt.dispose(); - encrypt.dispose(); - } + @Test + void javaIntegrityCheck() throws GeneralSecurityException { + check(JavaVelocityCipher.FACTORY); + } + + private void check(VelocityCipherFactory factory) throws GeneralSecurityException { + // Generate a random 16-byte key. + Random random = new Random(1); + byte[] key = new byte[16]; + random.nextBytes(key); + + VelocityCipher decrypt = factory.forDecryption(new SecretKeySpec(key, "AES")); + VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(key, "AES")); + + ByteBuf source = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); + ByteBuf dest = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); + ByteBuf decryptionBuf = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); + + byte[] randomBytes = new byte[ENCRYPT_DATA_SIZE]; + random.nextBytes(randomBytes); + source.writeBytes(randomBytes); + + try { + encrypt.process(source, dest); + decrypt.process(dest, decryptionBuf); + source.readerIndex(0); + assertTrue(ByteBufUtil.equals(source, decryptionBuf)); + } finally { + source.release(); + dest.release(); + decryptionBuf.release(); + decrypt.dispose(); + encrypt.dispose(); } + } } diff --git a/proxy/build.gradle b/proxy/build.gradle index 0cfcf1de8..1901a221b 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -2,17 +2,11 @@ plugins { id 'java' id 'com.github.johnrengelman.shadow' version '2.0.4' id 'de.sebastianboegl.shadow.transformer.log4j' version '2.1.1' + id 'checkstyle' } apply from: '../gradle/checkerframework.gradle' - -compileJava { - options.compilerArgs += ['-proc:none'] -} - -compileTestJava { - options.compilerArgs += ['-proc:none'] -} +apply from: '../gradle/checkstyle.gradle' jar { manifest { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index 8e09f0665..e91df7d32 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -1,29 +1,29 @@ package com.velocitypowered.proxy; +import java.text.DecimalFormat; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.text.DecimalFormat; - public class Velocity { - private static final Logger logger = LogManager.getLogger(Velocity.class); - 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. - System.setProperty("java.awt.headless", "true"); - } + private static final Logger logger = LogManager.getLogger(Velocity.class); - public static void main(String... args) { - long startTime = System.currentTimeMillis(); + 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. + System.setProperty("java.awt.headless", "true"); + } - VelocityServer server = new VelocityServer(); - server.start(); + public static void main(String... args) { + long startTime = System.currentTimeMillis(); - Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread")); + VelocityServer server = new VelocityServer(); + server.start(); - double bootTime = (System.currentTimeMillis() - startTime) / 1000d; - logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime)); - server.getConsoleCommandSource().start(); - } + Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread")); + + double bootTime = (System.currentTimeMillis() - startTime) / 1000d; + logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime)); + server.getConsoleCommandSource().start(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index e2826b21b..10c54c201 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -24,7 +24,6 @@ import com.velocitypowered.proxy.config.AnnotatedConfig; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.console.VelocityConsole; -import com.velocitypowered.proxy.util.VelocityChannelRegistrar; import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.network.http.NettyHttpClient; import com.velocitypowered.proxy.plugin.VelocityEventManager; @@ -36,356 +35,367 @@ import com.velocitypowered.proxy.server.ServerMap; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.Ratelimiter; +import com.velocitypowered.proxy.util.VelocityChannelRegistrar; import io.netty.bootstrap.Bootstrap; -import net.kyori.text.Component; -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; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyPair; -import java.util.*; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import net.kyori.text.Component; +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.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class VelocityServer implements ProxyServer { - private static final Logger logger = LogManager.getLogger(VelocityServer.class); - public static final Gson GSON = new GsonBuilder() - .registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer()) - .registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer()) - .create(); + private static final Logger logger = LogManager.getLogger(VelocityServer.class); + public static final Gson GSON = new GsonBuilder() + .registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer()) + .registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer()) + .create(); - 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 @MonotonicNonNull VelocityPluginManager pluginManager; + 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 @MonotonicNonNull VelocityPluginManager pluginManager; - private final Map connectionsByUuid = new ConcurrentHashMap<>(); - private final Map connectionsByName = new ConcurrentHashMap<>(); - private @MonotonicNonNull VelocityConsole console; - private @MonotonicNonNull Ratelimiter ipAttemptLimiter; - private @MonotonicNonNull VelocityEventManager eventManager; - private @MonotonicNonNull VelocityScheduler scheduler; - private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar(); + private final Map connectionsByUuid = new ConcurrentHashMap<>(); + private final Map connectionsByName = new ConcurrentHashMap<>(); + 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 KeyPair getServerKeyPair() { + if (serverKeyPair == null) { + throw new AssertionError(); + } + return serverKeyPair; + } + + public VelocityConfiguration getConfiguration() { + 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; + String implVersion; + String 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"; } - public VelocityConfiguration getConfiguration() { - VelocityConfiguration cfg = this.configuration; - if (cfg == null) { - throw new IllegalStateException("Configuration not initialized!"); - } - return cfg; + return new ProxyVersion(implName, implVendor, implVersion); + } + + @Override + public VelocityCommandManager getCommandManager() { + 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); + + if (!configuration.validate()) { + logger.error( + "Your configuration is invalid. Velocity will refuse to start up until the errors are resolved."); + LogManager.shutdown(); + System.exit(1); + } + + AnnotatedConfig + .saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values + + } catch (Exception e) { + logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e); + LogManager.shutdown(); + System.exit(1); } - @Override - public ProxyVersion getVersion() { - Package pkg = VelocityServer.class.getPackage(); - String implName; - String implVersion; - String 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"; + for (Map.Entry entry : configuration.getServers().entrySet()) { + servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue()))); + } + + ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit()); + httpClient = new NettyHttpClient(this); + loadPlugins(); + + // 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(); + + this.cm.bind(configuration.getBind()); + + if (configuration.isQueryEnabled()) { + this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort()); + } + } + + @RequiresNonNull({"pluginManager", "eventManager"}) + private void loadPlugins() { + logger.info("Loading plugins..."); + + try { + Path pluginPath = Paths.get("plugins"); + + if (!pluginPath.toFile().exists()) { + Files.createDirectory(pluginPath); + } else { + if (!pluginPath.toFile().isDirectory()) { + logger.warn("Plugin location {} is not a directory, continuing without loading plugins", + pluginPath); + return; } - return new ProxyVersion(implName, implVendor, implVersion); + pluginManager.loadPlugins(pluginPath); + } + } catch (Exception e) { + logger.error("Couldn't load plugins", e); } - @Override - public VelocityCommandManager getCommandManager() { - return commandManager; + // Register the plugin main classes so that we may proceed with firing the proxy initialize event + for (PluginContainer plugin : pluginManager.getPlugins()) { + Optional instance = plugin.getInstance(); + if (instance.isPresent()) { + eventManager.register(instance.get(), instance.get()); + } } - @EnsuresNonNull({"serverKeyPair", "servers", "pluginManager", "eventManager", "scheduler", "console", "cm", "configuration"}) - public void start() { - logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion()); + logger.info("Loaded {} plugins", pluginManager.getPlugins().size()); + } - 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); + public Bootstrap initializeGenericBootstrap() { + if (cm == null) { + throw new IllegalStateException("Server did not initialize properly."); + } + return this.cm.createWorker(); + } - cm.logChannelInformation(); + public boolean isShutdown() { + return shutdown; + } - // 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); - - if (!configuration.validate()) { - logger.error("Your configuration is invalid. Velocity will refuse to start up until the errors are resolved."); - LogManager.shutdown(); - System.exit(1); - } - - AnnotatedConfig.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values - - } catch (Exception e) { - logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e); - LogManager.shutdown(); - System.exit(1); - } - - for (Map.Entry entry : configuration.getServers().entrySet()) { - servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue()))); - } - - ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit()); - httpClient = new NettyHttpClient(this); - loadPlugins(); - - // 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(); - - this.cm.bind(configuration.getBind()); - - if (configuration.isQueryEnabled()) { - this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort()); - } + public void shutdown() { + if (eventManager == null || pluginManager == null || cm == null || scheduler == null) { + throw new AssertionError(); } - @RequiresNonNull({"pluginManager", "eventManager"}) - private void loadPlugins() { - logger.info("Loading plugins..."); + if (!shutdownInProgress.compareAndSet(false, true)) { + return; + } + logger.info("Shutting down the proxy..."); - try { - Path pluginPath = Paths.get("plugins"); - - if (!pluginPath.toFile().exists()) { - Files.createDirectory(pluginPath); - } else { - if (!pluginPath.toFile().isDirectory()) { - logger.warn("Plugin location {} is not a directory, continuing without loading plugins", pluginPath); - return; - } - - pluginManager.loadPlugins(pluginPath); - } - } catch (Exception e) { - logger.error("Couldn't load plugins", e); - } - - // Register the plugin main classes so that we may proceed with firing the proxy initialize event - 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()); + for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) { + player.close(TextComponent.of("Proxy shutting down.")); } - public Bootstrap initializeGenericBootstrap() { - if (cm == null) { - throw new IllegalStateException("Server did not initialize properly."); - } - return this.cm.createWorker(); + this.cm.shutdown(); + + eventManager.fire(new ProxyShutdownEvent()); + try { + if (!eventManager.shutdown() || !scheduler.shutdown()) { + logger.error("Your plugins took over 10 seconds to shut down."); + } + } catch (InterruptedException e) { + // Not much we can do about this... + Thread.currentThread().interrupt(); } - public boolean isShutdown() { - return shutdown; + shutdown = true; + } + + public NettyHttpClient getHttpClient() { + if (httpClient == null) { + throw new IllegalStateException("HTTP client not initialized"); } + return httpClient; + } - public void shutdown() { - if (eventManager == null || pluginManager == null || cm == null || scheduler == null) { - throw new AssertionError(); - } - - if (!shutdownInProgress.compareAndSet(false, true)) { - return; - } - logger.info("Shutting down the proxy..."); - - for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) { - player.close(TextComponent.of("Proxy shutting down.")); - } - - this.cm.shutdown(); - - eventManager.fire(new ProxyShutdownEvent()); - try { - if (!eventManager.shutdown() || !scheduler.shutdown()) { - logger.error("Your plugins took over 10 seconds to shut down."); - } - } catch (InterruptedException e) { - // Not much we can do about this... - Thread.currentThread().interrupt(); - } - - shutdown = true; + public Ratelimiter getIpAttemptLimiter() { + if (ipAttemptLimiter == null) { + throw new IllegalStateException("Ratelimiter not initialized"); } + return ipAttemptLimiter; + } - public NettyHttpClient getHttpClient() { - if (httpClient == null) { - throw new IllegalStateException("HTTP client not initialized"); - } - return httpClient; + public boolean registerConnection(ConnectedPlayer connection) { + String lowerName = connection.getUsername().toLowerCase(Locale.US); + if (connectionsByName.putIfAbsent(lowerName, connection) != null) { + return false; } + if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) { + connectionsByName.remove(lowerName, connection); + return false; + } + return true; + } - public Ratelimiter getIpAttemptLimiter() { - if (ipAttemptLimiter == null) { - throw new IllegalStateException("Ratelimiter not initialized"); - } - return ipAttemptLimiter; - } + public void unregisterConnection(ConnectedPlayer connection) { + connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection); + connectionsByUuid.remove(connection.getUniqueId(), connection); + } - public boolean registerConnection(ConnectedPlayer connection) { - String lowerName = connection.getUsername().toLowerCase(Locale.US); - if (connectionsByName.putIfAbsent(lowerName, connection) != null) { - return false; - } - if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) { - connectionsByName.remove(lowerName, connection); - return false; - } - return true; - } + @Override + public Optional getPlayer(String username) { + Preconditions.checkNotNull(username, "username"); + return Optional.ofNullable(connectionsByName.get(username.toLowerCase(Locale.US))); + } - public void unregisterConnection(ConnectedPlayer connection) { - connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection); - connectionsByUuid.remove(connection.getUniqueId(), connection); - } + @Override + public Optional getPlayer(UUID uuid) { + Preconditions.checkNotNull(uuid, "uuid"); + return Optional.ofNullable(connectionsByUuid.get(uuid)); + } - @Override - public Optional getPlayer(String username) { - Preconditions.checkNotNull(username, "username"); - return Optional.ofNullable(connectionsByName.get(username.toLowerCase(Locale.US))); + @Override + public void broadcast(Component component) { + Preconditions.checkNotNull(component, "component"); + Chat chat = Chat.createClientbound(component); + for (ConnectedPlayer player : connectionsByUuid.values()) { + player.getConnection().write(chat); } + } - @Override - public Optional getPlayer(UUID uuid) { - Preconditions.checkNotNull(uuid, "uuid"); - return Optional.ofNullable(connectionsByUuid.get(uuid)); - } + @Override + public Collection getAllPlayers() { + return ImmutableList.copyOf(connectionsByUuid.values()); + } - @Override - public void broadcast(Component component) { - Preconditions.checkNotNull(component, "component"); - Chat chat = Chat.createClientbound(component); - for (ConnectedPlayer player : connectionsByUuid.values()) { - player.getConnection().write(chat); - } - } + @Override + public int getPlayerCount() { + return connectionsByUuid.size(); + } - @Override - public Collection getAllPlayers() { - return ImmutableList.copyOf(connectionsByUuid.values()); + @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 int getPlayerCount() { - return connectionsByUuid.size(); + @Override + public Collection getAllServers() { + if (servers == null) { + throw new IllegalStateException("Server did not initialize properly."); } + return servers.getAllServers(); + } - @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 RegisteredServer registerServer(ServerInfo server) { + if (servers == null) { + throw new IllegalStateException("Server did not initialize properly."); } + return servers.register(server); + } - @Override - public Collection getAllServers() { - if (servers == null) { - throw new IllegalStateException("Server did not initialize properly."); - } - return servers.getAllServers(); + @Override + public void unregisterServer(ServerInfo server) { + if (servers == null) { + throw new IllegalStateException("Server did not initialize properly."); } + servers.unregister(server); + } - @Override - public RegisteredServer registerServer(ServerInfo server) { - if (servers == null) { - throw new IllegalStateException("Server did not initialize properly."); - } - return servers.register(server); + @Override + public VelocityConsole getConsoleCommandSource() { + if (console == null) { + throw new IllegalStateException("Server did not initialize properly."); } + return console; + } - @Override - public void unregisterServer(ServerInfo server) { - if (servers == null) { - throw new IllegalStateException("Server did not initialize properly."); - } - servers.unregister(server); + @Override + public PluginManager getPluginManager() { + if (pluginManager == null) { + throw new IllegalStateException("Server did not initialize properly."); } + return pluginManager; + } - @Override - public VelocityConsole getConsoleCommandSource() { - if (console == null) { - throw new IllegalStateException("Server did not initialize properly."); - } - return console; + @Override + public EventManager getEventManager() { + if (eventManager == null) { + throw new IllegalStateException("Server did not initialize properly."); } + return eventManager; + } - @Override - public PluginManager getPluginManager() { - if (pluginManager == null) { - throw new IllegalStateException("Server did not initialize properly."); - } - return pluginManager; + @Override + public VelocityScheduler getScheduler() { + if (scheduler == null) { + throw new IllegalStateException("Server did not initialize properly."); } + return scheduler; + } - @Override - public EventManager getEventManager() { - if (eventManager == null) { - throw new IllegalStateException("Server did not initialize properly."); - } - return eventManager; - } + @Override + public VelocityChannelRegistrar getChannelRegistrar() { + return channelRegistrar; + } - @Override - public VelocityScheduler getScheduler() { - if (scheduler == null) { - throw new IllegalStateException("Server did not initialize properly."); - } - return scheduler; - } - - @Override - public VelocityChannelRegistrar getChannelRegistrar() { - return channelRegistrar; - } - - @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(); + @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 345c62e68..650911f38 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -9,90 +9,96 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; import net.kyori.text.format.TextColor; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - public class ServerCommand implements Command { - private final ProxyServer server; - public ServerCommand(ProxyServer server) { - this.server = server; + private final ProxyServer server; + + public ServerCommand(ProxyServer server) { + this.server = server; + } + + @Override + 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; } - @Override - 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; - } + Player player = (Player) source; + if (args.length == 1) { + // Trying to connect to a server. + String serverName = args[0]; + Optional toConnect = server.getServer(serverName); + if (!toConnect.isPresent()) { + player.sendMessage( + TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED)); + return; + } - Player player = (Player) source; - if (args.length == 1) { - // Trying to connect to a server. - String serverName = args[0]; - Optional toConnect = server.getServer(serverName); - if (!toConnect.isPresent()) { - player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED)); - return; - } + player.createConnectionRequest(toConnect.get()).fireAndForget(); + } else { + String currentServer = player.getCurrentServer().map(ServerConnection::getServerInfo) + .map(ServerInfo::getName) + .orElse(""); + player.sendMessage(TextComponent + .of("You are currently connected to " + currentServer + ".", TextColor.YELLOW)); - player.createConnectionRequest(toConnect.get()).fireAndForget(); + // Assemble the list of servers as components + TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ") + .color(TextColor.YELLOW); + List infos = ImmutableList.copyOf(server.getAllServers()); + for (int i = 0; i < infos.size(); i++) { + RegisteredServer rs = infos.get(i); + TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName()); + String playersText = rs.getPlayersConnected().size() + " player(s) online"; + if (rs.getServerInfo().getName().equals(currentServer)) { + infoComponent = infoComponent.color(TextColor.GREEN) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + TextComponent.of("Currently connected to this server\n" + playersText))); } else { - String currentServer = player.getCurrentServer().map(ServerConnection::getServerInfo).map(ServerInfo::getName) - .orElse(""); - player.sendMessage(TextComponent.of("You are currently connected to " + currentServer + ".", TextColor.YELLOW)); - - // Assemble the list of servers as components - TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW); - List infos = ImmutableList.copyOf(server.getAllServers()); - for (int i = 0; i < infos.size(); i++) { - RegisteredServer rs = infos.get(i); - TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName()); - String playersText = rs.getPlayersConnected().size() + " player(s) online"; - if (rs.getServerInfo().getName().equals(currentServer)) { - infoComponent = infoComponent.color(TextColor.GREEN) - .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, - TextComponent.of("Currently connected to this server\n" + playersText))); - } else { - infoComponent = infoComponent.color(TextColor.GRAY) - .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName())) - .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText))); - } - serverListBuilder.append(infoComponent); - if (i != infos.size() - 1) { - serverListBuilder.append(TextComponent.of(", ", TextColor.GRAY)); - } - } - - player.sendMessage(serverListBuilder.build()); + infoComponent = infoComponent.color(TextColor.GRAY) + .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, + "/server " + rs.getServerInfo().getName())) + .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + TextComponent.of("Click to connect to this server\n" + playersText))); } - } - - @Override - public List suggest(CommandSource source, String @NonNull [] currentArgs) { - if (currentArgs.length == 0) { - return server.getAllServers().stream() - .map(rs -> rs.getServerInfo().getName()) - .collect(Collectors.toList()); - } else if (currentArgs.length == 1) { - return server.getAllServers().stream() - .map(rs -> rs.getServerInfo().getName()) - .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) - .collect(Collectors.toList()); - } else { - return ImmutableList.of(); + serverListBuilder.append(infoComponent); + if (i != infos.size() - 1) { + serverListBuilder.append(TextComponent.of(", ", TextColor.GRAY)); } - } + } - @Override - public boolean hasPermission(CommandSource source, String @NonNull [] args) { - return source.getPermissionValue("velocity.command.server") != Tristate.FALSE; + player.sendMessage(serverListBuilder.build()); } + } + + @Override + public List suggest(CommandSource source, String @NonNull [] currentArgs) { + if (currentArgs.length == 0) { + return server.getAllServers().stream() + .map(rs -> rs.getServerInfo().getName()) + .collect(Collectors.toList()); + } else if (currentArgs.length == 1) { + return server.getAllServers().stream() + .map(rs -> rs.getServerInfo().getName()) + .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) + .collect(Collectors.toList()); + } else { + return ImmutableList.of(); + } + } + + @Override + 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 3b4168dbb..4d396fac8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ShutdownCommand.java @@ -8,23 +8,25 @@ import net.kyori.text.format.TextColor; import org.checkerframework.checker.nullness.qual.NonNull; public class ShutdownCommand implements Command { - private final VelocityServer server; - public ShutdownCommand(VelocityServer server) { - this.server = server; - } + private final VelocityServer server; - @Override - 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; - } - server.shutdown(); - } + public ShutdownCommand(VelocityServer server) { + this.server = server; + } - @Override - public boolean hasPermission(CommandSource source, String @NonNull [] args) { - return source == server.getConsoleCommandSource(); + @Override + 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; } + server.shutdown(); + } + + @Override + 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 acbe9f0cc..577a9a8ce 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -7,125 +7,130 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.util.ProxyVersion; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.format.TextColor; import net.kyori.text.format.TextDecoration; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Collectors; - public class VelocityCommand implements Command { - private final Map subcommands; - public VelocityCommand(ProxyServer server) { - this.subcommands = ImmutableMap.builder() - .put("version", new Info(server)) - .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()) + ">"; + source.sendMessage(TextComponent.of(commandText, TextColor.RED)); + } + + @Override + public void execute(CommandSource source, String @NonNull [] args) { + if (args.length == 0) { + usage(source); + return; } - private void usage(CommandSource source) { - String commandText = "/velocity <" + String.join("|", subcommands.keySet()) + ">"; - source.sendMessage(TextComponent.of(commandText, TextColor.RED)); + Command command = subcommands.get(args[0].toLowerCase(Locale.US)); + if (command == null) { + usage(source); + return; + } + @SuppressWarnings("nullness") + String[] actualArgs = Arrays.copyOfRange(args, 1, args.length); + command.execute(source, actualArgs); + } + + @Override + public List suggest(CommandSource source, String @NonNull [] currentArgs) { + if (currentArgs.length == 0) { + return ImmutableList.copyOf(subcommands.keySet()); + } + + if (currentArgs.length == 1) { + return subcommands.keySet().stream() + .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) + .collect(Collectors.toList()); + } + + Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US)); + if (command == null) { + return ImmutableList.of(); + } + @SuppressWarnings("nullness") + String[] actualArgs = Arrays.copyOfRange(currentArgs, 1, currentArgs.length); + return command.suggest(source, actualArgs); + } + + @Override + public boolean hasPermission(CommandSource source, String @NonNull [] args) { + if (args.length == 0) { + return true; + } + Command command = subcommands.get(args[0].toLowerCase(Locale.US)); + if (command == null) { + return true; + } + @SuppressWarnings("nullness") + String[] actualArgs = Arrays.copyOfRange(args, 1, args.length); + return command.hasPermission(source, actualArgs); + } + + private static class Info implements Command { + + private final ProxyServer server; + + private Info(ProxyServer server) { + this.server = server; } @Override public void execute(CommandSource source, String @NonNull [] args) { - if (args.length == 0) { - usage(source); - return; - } + ProxyVersion version = server.getVersion(); - Command command = subcommands.get(args[0].toLowerCase(Locale.US)); - if (command == null) { - usage(source); - return; - } - @SuppressWarnings("nullness") - String[] actualArgs = Arrays.copyOfRange(args, 1, args.length); - command.execute(source, actualArgs); - } + TextComponent velocity = TextComponent.builder(version.getName() + " ") + .decoration(TextDecoration.BOLD, true) + .color(TextColor.DARK_AQUA) + .append(TextComponent.of(version.getVersion()).decoration(TextDecoration.BOLD, false)) + .build(); + 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); - @Override - public List suggest(CommandSource source, String @NonNull [] currentArgs) { - if (currentArgs.length == 0) { - return ImmutableList.copyOf(subcommands.keySet()); - } - - if (currentArgs.length == 1) { - return subcommands.keySet().stream() - .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) - .collect(Collectors.toList()); - } - - Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US)); - if (command == null) { - return ImmutableList.of(); - } - @SuppressWarnings("nullness") - String[] actualArgs = Arrays.copyOfRange(currentArgs, 1, currentArgs.length); - return command.suggest(source, actualArgs); + if (version.getName().equals("Velocity")) { + TextComponent velocityWebsite = TextComponent.builder() + .content("Visit the ") + .append(TextComponent.builder("Velocity website") + .color(TextColor.GREEN) + .clickEvent( + new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.velocitypowered.com")) + .build()) + .append(TextComponent.of(" or the ").resetStyle()) + .append(TextComponent.builder("Velocity GitHub") + .color(TextColor.GREEN) + .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, + "https://github.com/VelocityPowered/Velocity")) + .build()) + .build(); + source.sendMessage(velocityWebsite); + } } @Override public boolean hasPermission(CommandSource source, String @NonNull [] args) { - if (args.length == 0) { - return true; - } - Command command = subcommands.get(args[0].toLowerCase(Locale.US)); - if (command == null) { - return true; - } - @SuppressWarnings("nullness") - String[] actualArgs = Arrays.copyOfRange(args, 1, args.length); - return command.hasPermission(source, actualArgs); - } - - private static class Info implements Command { - private final ProxyServer server; - - private Info(ProxyServer server) { - this.server = server; - } - - @Override - 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(version.getVersion()).decoration(TextDecoration.BOLD, false)) - .build(); - 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 (version.getName().equals("Velocity")) { - TextComponent velocityWebsite = TextComponent.builder() - .content("Visit the ") - .append(TextComponent.builder("Velocity website") - .color(TextColor.GREEN) - .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.velocitypowered.com")) - .build()) - .append(TextComponent.of(" or the ").resetStyle()) - .append(TextComponent.builder("Velocity GitHub") - .color(TextColor.GREEN) - .clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/VelocityPowered/Velocity")) - .build()) - .build(); - source.sendMessage(velocityWebsite); - } - } - - @Override - public boolean hasPermission(CommandSource source, String @NonNull [] args) { - return source.getPermissionValue("velocity.command.info") != Tristate.FALSE; - } + 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 c48410bdd..20fe7097a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -5,100 +5,106 @@ 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.*; - public class VelocityCommandManager implements CommandManager { - private final Map commands = new HashMap<>(); - @Override - 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++) { - final String alias = aliases[i]; - Preconditions.checkNotNull(aliases, "alias at index %s", i); - this.commands.put(alias.toLowerCase(Locale.ENGLISH), command); - } + private final Map commands = new HashMap<>(); + + @Override + 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++) { + final String alias = aliases[i]; + Preconditions.checkNotNull(aliases, "alias at index %s", i); + this.commands.put(alias.toLowerCase(Locale.ENGLISH), command); + } + } + + @Override + public void unregister(@NonNull final String alias) { + Preconditions.checkNotNull(alias, "name"); + this.commands.remove(alias.toLowerCase(Locale.ENGLISH)); + } + + @Override + public boolean execute(@NonNull CommandSource source, @NonNull String cmdLine) { + Preconditions.checkNotNull(source, "invoker"); + Preconditions.checkNotNull(cmdLine, "cmdLine"); + + String[] split = cmdLine.split(" ", -1); + if (split.length == 0) { + return false; } - @Override - public void unregister(@NonNull final String alias) { - Preconditions.checkNotNull(alias, "name"); - this.commands.remove(alias.toLowerCase(Locale.ENGLISH)); + 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) { + return false; } - @Override - public boolean execute(@NonNull CommandSource source, @NonNull String cmdLine) { - Preconditions.checkNotNull(source, "invoker"); - Preconditions.checkNotNull(cmdLine, "cmdLine"); + try { + if (!command.hasPermission(source, actualArgs)) { + return false; + } - String[] split = cmdLine.split(" ", -1); - if (split.length == 0) { - return false; - } + command.execute(source, actualArgs); + return true; + } catch (Exception e) { + throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e); + } + } - 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) { - return false; - } + public boolean hasCommand(String command) { + return commands.containsKey(command); + } - try { - if (!command.hasPermission(source, actualArgs)) { - return false; - } + public List offerSuggestions(CommandSource source, String cmdLine) { + Preconditions.checkNotNull(source, "source"); + Preconditions.checkNotNull(cmdLine, "cmdLine"); - command.execute(source, actualArgs); - return true; - } catch (Exception e) { - throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e); - } + String[] split = cmdLine.split(" ", -1); + if (split.length == 0) { + return ImmutableList.of(); } - public boolean hasCommand(String command) { - return commands.containsKey(command); + String alias = split[0]; + if (split.length == 1) { + List availableCommands = new ArrayList<>(); + for (Map.Entry entry : commands.entrySet()) { + if (entry.getKey().regionMatches(true, 0, alias, 0, alias.length()) && + entry.getValue().hasPermission(source, new String[0])) { + availableCommands.add("/" + entry.getKey()); + } + } + return availableCommands; } - 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 ImmutableList.of(); - } - - String alias = split[0]; - if (split.length == 1) { - List availableCommands = new ArrayList<>(); - for (Map.Entry entry : commands.entrySet()) { - if (entry.getKey().regionMatches(true, 0, alias, 0, alias.length()) && - entry.getValue().hasPermission(source, new String[0])) { - availableCommands.add("/" + entry.getKey()); - } - } - return availableCommands; - } - - @SuppressWarnings("nullness") - String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); - Command command = commands.get(alias.toLowerCase(Locale.ENGLISH)); - if (command == null) { - return ImmutableList.of(); - } - - try { - if (!command.hasPermission(source, actualArgs)) { - return ImmutableList.of(); - } - - return command.suggest(source, actualArgs); - } catch (Exception e) { - throw new RuntimeException("Unable to invoke suggestions for command " + alias + " for " + source, e); - } + @SuppressWarnings("nullness") + String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); + Command command = commands.get(alias.toLowerCase(Locale.ENGLISH)); + if (command == null) { + return ImmutableList.of(); } + + try { + if (!command.hasPermission(source, actualArgs)) { + return ImmutableList.of(); + } + + 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 1f05ca1aa..ccedcdfba 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java @@ -1,8 +1,5 @@ package com.velocitypowered.proxy.config; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -19,206 +16,221 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Simple annotation and fields based TOML configuration serializer */ public abstract class AnnotatedConfig { - private static final Logger logger = LogManager.getLogger(AnnotatedConfig.class); - public static Logger getLogger() { - return logger; - } + private static final Logger logger = LogManager.getLogger(AnnotatedConfig.class); - /** - * Indicates that a field is a table - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface Table { - String value(); - } + public static Logger getLogger() { + return logger; + } - /** - * Creates a comment - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface Comment { - String[] value(); - } + /** + * Indicates that a field is a table + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface Table { - /** - * How field will be named in config - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface ConfigKey { - String value(); - } + String value(); + } - /** - * Indicates that a field is a map and we need to save all map data to - * config - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface IsMap {} + /** + * Creates a comment + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface Comment { - /** - * Indicates that a field is a string converted to byte[] - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface StringAsBytes {} + String[] value(); + } - /** - * Indicates that a field should be skipped - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD}) - public @interface Ignore {} + /** + * How field will be named in config + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface ConfigKey { - /** - * Dumps this configuration to list of strings using {@link #dumpConfig(Object)} - * @return configuration dump - */ - public List dumpConfig() { - return dumpConfig(this); - } + String value(); + } - /** - * Creates TOML configuration from supplied
dumpable
object. - * - * @param dumpable object which is going to be dumped - * @throws RuntimeException if reading field value(s) fail - * @return string list of configuration file lines - */ - private static List dumpConfig(Object dumpable) { - List lines = new ArrayList<>(); - try { - for (Field field : dumpable.getClass().getDeclaredFields()) { - // Skip fields with @Ignore annotation - if (field.getAnnotation(Ignore.class) != null) { - continue; - } + /** + * Indicates that a field is a map and we need to save all map data to config + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface IsMap { - // Make field accessible - field.setAccessible(true); + } - // Add comments - Comment comment = field.getAnnotation(Comment.class); - if (comment != null) { - for (String line : comment.value()) { - lines.add("# " + line); - } - } + /** + * Indicates that a field is a string converted to byte[] + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface StringAsBytes { - // Get a key name for config - ConfigKey key = field.getAnnotation(ConfigKey.class); - 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); - if (table != null) { - lines.add(table.value()); // Write [name] - lines.addAll(dumpConfig(field.get(dumpable))); // Dump fields of table - continue; - } + /** + * Indicates that a field should be skipped + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + public @interface Ignore { - if (field.getAnnotation(IsMap.class) != null) { // Check if field is a map - @SuppressWarnings("unchecked") - Map map = (Map) field.get(dumpable); - for (Entry entry : map.entrySet()) { - lines.add(safeKey(entry.getKey()) + " = " + serialize(entry.getValue())); // Save map data - } - lines.add(""); // Add empty line - continue; - } + } - Object value = field.get(dumpable); + /** + * Dumps this configuration to list of strings using {@link #dumpConfig(Object)} + * + * @return configuration dump + */ + public List dumpConfig() { + return dumpConfig(this); + } - // Check if field is a byte[] representation of a string - if (field.getAnnotation(StringAsBytes.class) != null) { - value = new String((byte[]) value, StandardCharsets.UTF_8); - } - - // Save field to config - lines.add(name + " = " + serialize(value)); - lines.add(""); // Add empty line - } - } catch (IllegalAccessException | IllegalArgumentException | SecurityException e) { - throw new RuntimeException("Could not dump configuration", e); + /** + * Creates TOML configuration from supplied
dumpable
object. + * + * @param dumpable object which is going to be dumped + * @return string list of configuration file lines + * @throws RuntimeException if reading field value(s) fail + */ + private static List dumpConfig(Object dumpable) { + List lines = new ArrayList<>(); + try { + for (Field field : dumpable.getClass().getDeclaredFields()) { + // Skip fields with @Ignore annotation + if (field.getAnnotation(Ignore.class) != null) { + continue; } - return lines; + // Make field accessible + field.setAccessible(true); + + // Add comments + Comment comment = field.getAnnotation(Comment.class); + if (comment != null) { + for (String line : comment.value()) { + lines.add("# " + line); + } + } + + // Get a key name for config + ConfigKey key = field.getAnnotation(ConfigKey.class); + 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); + if (table != null) { + lines.add(table.value()); // Write [name] + lines.addAll(dumpConfig(field.get(dumpable))); // Dump fields of table + continue; + } + + if (field.getAnnotation(IsMap.class) != null) { // Check if field is a map + @SuppressWarnings("unchecked") + Map map = (Map) field.get(dumpable); + for (Entry entry : map.entrySet()) { + lines.add( + safeKey(entry.getKey()) + " = " + serialize(entry.getValue())); // Save map data + } + lines.add(""); // Add empty line + continue; + } + + Object value = field.get(dumpable); + + // Check if field is a byte[] representation of a string + if (field.getAnnotation(StringAsBytes.class) != null) { + value = new String((byte[]) value, StandardCharsets.UTF_8); + } + + // Save field to config + lines.add(name + " = " + serialize(value)); + lines.add(""); // Add empty line + } + } catch (IllegalAccessException | IllegalArgumentException | SecurityException e) { + throw new RuntimeException("Could not dump configuration", e); } - /** - * Serializes
value
so it could be parsed by TOML specification - * - * @param value object to serialize - * @return Serialized object - */ - private static String serialize(Object value) { - if (value instanceof List) { - List listValue = (List) value; - if (listValue.isEmpty()) { - return "[]"; - } + return lines; + } - StringBuilder m = new StringBuilder(); - m.append("["); + /** + * Serializes
value
so it could be parsed by TOML specification + * + * @param value object to serialize + * @return Serialized object + */ + private static String serialize(Object value) { + if (value instanceof List) { + List listValue = (List) value; + if (listValue.isEmpty()) { + return "[]"; + } - for (Object obj : listValue) { - m.append(System.lineSeparator()).append(" ").append(serialize(obj)).append(","); - } + StringBuilder m = new StringBuilder(); + m.append("["); - m.deleteCharAt(m.length() - 1).append(System.lineSeparator()).append("]"); - return m.toString(); - } + for (Object obj : listValue) { + m.append(System.lineSeparator()).append(" ").append(serialize(obj)).append(","); + } - if (value instanceof Enum) { - value = value.toString(); - } - - if (value instanceof String) { - String stringValue = (String) value; - if (stringValue.isEmpty()) { - return "\"\""; - } - return "\"" + stringValue.replace("\n", "\\n") + "\""; - } - - return value != null ? value.toString() : "null"; + m.deleteCharAt(m.length() - 1).append(System.lineSeparator()).append("]"); + return m.toString(); } - private static String safeKey(String key) { - if(key.contains(".") && !(key.indexOf('"') == 0 && key.lastIndexOf('"') == (key.length() - 1))) { - return '"' + key + '"'; - } - return key; + if (value instanceof Enum) { + value = value.toString(); } - /** - * Writes list of strings to file - * - * @param lines list of strings to write - * @param to Path of file where lines should be written - * @throws IOException if error occurred during writing - * @throws IllegalArgumentException if
lines
is empty list - */ - public static void saveConfig(List lines, Path to) throws IOException { - if (lines.isEmpty()) { - throw new IllegalArgumentException("lines cannot be empty"); - } - - Path temp = to.getParent().resolve(to.getFileName().toString() + "__tmp"); - Files.write(temp, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE); - try { - Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - } catch (AtomicMoveNotSupportedException e) { - Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING); - } + if (value instanceof String) { + String stringValue = (String) value; + if (stringValue.isEmpty()) { + return "\"\""; + } + return "\"" + stringValue.replace("\n", "\\n") + "\""; } + + return value != null ? value.toString() : "null"; + } + + private static String safeKey(String key) { + if (key.contains(".") && !(key.indexOf('"') == 0 && key.lastIndexOf('"') == (key.length() + - 1))) { + return '"' + key + '"'; + } + return key; + } + + /** + * Writes list of strings to file + * + * @param lines list of strings to write + * @param to Path of file where lines should be written + * @throws IOException if error occurred during writing + * @throws IllegalArgumentException if
lines
is empty list + */ + public static void saveConfig(List lines, Path to) throws IOException { + if (lines.isEmpty()) { + throw new IllegalArgumentException("lines cannot be empty"); + } + + Path temp = to.getParent().resolve(to.getFileName().toString() + "__tmp"); + Files.write(temp, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE); + try { + Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java b/proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java index 078c239c5..e0c2c3558 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.config; public enum PlayerInfoForwarding { - NONE, - LEGACY, - MODERN + NONE, + LEGACY, + MODERN } 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 f77c3ddd8..b7ac585b2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -1,18 +1,12 @@ package com.velocitypowered.proxy.config; import com.google.common.base.MoreObjects; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.moandjiezana.toml.Toml; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.util.Favicon; 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; import java.net.InetSocketAddress; @@ -20,636 +14,665 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +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; public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfig { - @Comment("Config version. Do not change this") - @ConfigKey("config-version") - private final String configVersion = "1.0"; + @Comment("Config version. Do not change this") + @ConfigKey("config-version") + private final String configVersion = "1.0"; - @Comment("What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.") - private String bind = "0.0.0.0:25577"; + @Comment("What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.") + private String bind = "0.0.0.0:25577"; - @Comment("What should be the MOTD? Legacy color codes and JSON are accepted.") - private String motd = "&3A Velocity Server"; + @Comment("What should be the MOTD? Legacy color codes and JSON are accepted.") + private String motd = "&3A Velocity Server"; - @Comment({ - "What should we display for the maximum number of players? (Velocity does not support a cap", - "on the number of players online.)" - }) - @ConfigKey("show-max-players") - private int showMaxPlayers = 500; + @Comment({ + "What should we display for the maximum number of players? (Velocity does not support a cap", + "on the number of players online.)" + }) + @ConfigKey("show-max-players") + private int showMaxPlayers = 500; - @Comment("Should we authenticate players with Mojang? By default, this is on.") - @ConfigKey("online-mode") - private boolean onlineMode = true; + @Comment("Should we authenticate players with Mojang? By default, this is on.") + @ConfigKey("online-mode") + private boolean onlineMode = true; - @Comment({ - "Should we forward IP addresses and other data to backend servers?", - "Available options:", - "- \"none\": No forwarding will be done. All players will appear to be connecting from the proxy", - " and will have offline-mode UUIDs.", - "- \"legacy\": Forward player IPs and UUIDs in BungeeCord-compatible fashion. Use this if you run", - " servers using Minecraft 1.12 or lower.", - "- \"modern\": Forward player IPs and UUIDs as part of the login process using Velocity's native", - " forwarding. Only applicable for Minecraft 1.13 or higher." - }) - @ConfigKey("player-info-forwarding-mode") - private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; + @Comment({ + "Should we forward IP addresses and other data to backend servers?", + "Available options:", + "- \"none\": No forwarding will be done. All players will appear to be connecting from the proxy", + " and will have offline-mode UUIDs.", + "- \"legacy\": Forward player IPs and UUIDs in BungeeCord-compatible fashion. Use this if you run", + " servers using Minecraft 1.12 or lower.", + "- \"modern\": Forward player IPs and UUIDs as part of the login process using Velocity's native", + " forwarding. Only applicable for Minecraft 1.13 or higher." + }) + @ConfigKey("player-info-forwarding-mode") + private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; - @StringAsBytes - @Comment("If you are using modern IP forwarding, configure an unique secret here.") - @ConfigKey("forwarding-secret") - private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); + @StringAsBytes + @Comment("If you are using modern IP forwarding, configure an unique secret here.") + @ConfigKey("forwarding-secret") + private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); - @Comment("Announce whether or not your server supports Forge/FML. If you run a modded server, we suggest turning this on.") - @ConfigKey("announce-forge") - private boolean announceForge = false; + @Comment("Announce whether or not your server supports Forge/FML. If you run a modded server, we suggest turning this on.") + @ConfigKey("announce-forge") + private boolean announceForge = false; - @Table("[servers]") - private final Servers servers; + @Table("[servers]") + private final Servers servers; - @Table("[forced-hosts]") - private final ForcedHosts forcedHosts; + @Table("[forced-hosts]") + private final ForcedHosts forcedHosts; - @Table("[advanced]") - private final Advanced advanced; + @Table("[advanced]") + private final Advanced advanced; - @Table("[query]") - private final Query query; + @Table("[query]") + private final Query query; - @Ignore - private @MonotonicNonNull Component motdAsComponent; + @Ignore + private @MonotonicNonNull Component motdAsComponent; - @Ignore - private @Nullable Favicon favicon; + @Ignore + private @Nullable Favicon favicon; - public VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query) { - this.servers = servers; - this.forcedHosts = forcedHosts; - this.advanced = advanced; - this.query = query; + public VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, + Query query) { + this.servers = servers; + this.forcedHosts = forcedHosts; + this.advanced = advanced; + this.query = query; + } + + private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, + boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, + Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query) { + this.bind = bind; + this.motd = motd; + this.showMaxPlayers = showMaxPlayers; + this.onlineMode = onlineMode; + this.announceForge = announceForge; + this.playerInfoForwardingMode = playerInfoForwardingMode; + this.forwardingSecret = forwardingSecret; + this.servers = servers; + this.forcedHosts = forcedHosts; + this.advanced = advanced; + this.query = query; + } + + public boolean validate() { + boolean valid = true; + Logger logger = AnnotatedConfig.getLogger(); + + if (bind.isEmpty()) { + logger.error("'bind' option is empty."); + valid = false; + } else { + try { + AddressUtil.parseAddress(bind); + } catch (IllegalArgumentException e) { + logger.error("'bind' option does not specify a valid IP address.", e); + valid = false; + } } - private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, - boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, - Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query) { - this.bind = bind; - this.motd = motd; - this.showMaxPlayers = showMaxPlayers; - this.onlineMode = onlineMode; - this.announceForge = announceForge; - this.playerInfoForwardingMode = playerInfoForwardingMode; - this.forwardingSecret = forwardingSecret; - this.servers = servers; - this.forcedHosts = forcedHosts; - this.advanced = advanced; - this.query = query; + if (!onlineMode) { + logger.info("Proxy is running in offline mode!"); } - public boolean validate() { - boolean valid = true; - Logger logger = AnnotatedConfig.getLogger(); - - if (bind.isEmpty()) { - logger.error("'bind' option is empty."); - valid = false; - } else { - try { - AddressUtil.parseAddress(bind); - } catch (IllegalArgumentException e) { - logger.error("'bind' option does not specify a valid IP address.", e); - valid = false; - } + switch (playerInfoForwardingMode) { + case NONE: + logger.warn( + "Player info forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs."); + break; + case MODERN: + if (forwardingSecret == null || forwardingSecret.length == 0) { + logger.error("You don't have a forwarding secret set. This is required for security."); + valid = false; } + break; + } - if (!onlineMode) { - logger.info("Proxy is running in offline mode!"); - } - - switch (playerInfoForwardingMode) { - case NONE: - logger.warn("Player info forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs."); - break; - case MODERN: - if (forwardingSecret == null || forwardingSecret.length == 0) { - logger.error("You don't have a forwarding secret set. This is required for security."); - valid = false; - } - break; - } - - if (servers.getServers().isEmpty()) { - logger.error("You have no servers configured. :("); - valid = false; - } else { - if (servers.getAttemptConnectionOrder().isEmpty()) { - logger.error("No fallback servers are configured!"); - valid = false; - } - - for (Map.Entry entry : servers.getServers().entrySet()) { - try { - AddressUtil.parseAddress(entry.getValue()); - } catch (IllegalArgumentException e) { - logger.error("Server {} does not have a valid IP address.", entry.getKey(), e); - valid = false; - } - } - - for (String s : servers.getAttemptConnectionOrder()) { - if (!servers.getServers().containsKey(s)) { - logger.error("Fallback server " + s + " is not registered in your configuration!"); - valid = false; - } - } - - for (Map.Entry> entry : forcedHosts.getForcedHosts().entrySet()) { - if (entry.getValue().isEmpty()) { - logger.error("Forced host '{}' does not contain any servers", entry.getKey()); - valid = false; - continue; - } - - for (String server : entry.getValue()) { - if (!servers.getServers().containsKey(server)) { - logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey()); - valid = false; - } - } - } - } + if (servers.getServers().isEmpty()) { + logger.error("You have no servers configured. :("); + valid = false; + } else { + if (servers.getAttemptConnectionOrder().isEmpty()) { + logger.error("No fallback servers are configured!"); + valid = false; + } + for (Map.Entry entry : servers.getServers().entrySet()) { try { - getMotdComponent(); - } catch (Exception e) { - logger.error("Can't parse your MOTD", e); - valid = false; + AddressUtil.parseAddress(entry.getValue()); + } catch (IllegalArgumentException e) { + logger.error("Server {} does not have a valid IP address.", entry.getKey(), e); + valid = false; + } + } + + for (String s : servers.getAttemptConnectionOrder()) { + if (!servers.getServers().containsKey(s)) { + logger.error("Fallback server " + s + " is not registered in your configuration!"); + valid = false; + } + } + + for (Map.Entry> entry : forcedHosts.getForcedHosts().entrySet()) { + if (entry.getValue().isEmpty()) { + logger.error("Forced host '{}' does not contain any servers", entry.getKey()); + valid = false; + continue; } - if (advanced.compressionLevel < -1 || advanced.compressionLevel > 9) { - logger.error("Invalid compression level {}", advanced.compressionLevel); + for (String server : entry.getValue()) { + if (!servers.getServers().containsKey(server)) { + logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey()); valid = false; - } else if (advanced.compressionLevel == 0) { - logger.warn("ALL packets going through the proxy will be uncompressed. This will increase bandwidth usage."); + } } - - if (advanced.compressionThreshold < -1) { - logger.error("Invalid compression threshold {}", advanced.compressionLevel); - valid = false; - } else if (advanced.compressionThreshold == 0) { - logger.warn("ALL packets going through the proxy will be compressed. This will compromise throughput and increase CPU usage!"); - } - - if (advanced.loginRatelimit < 0) { - logger.error("Invalid login ratelimit {}ms", advanced.loginRatelimit); - valid = false; - } - - loadFavicon(); - - return valid; + } } - private void loadFavicon() { - Path faviconPath = Paths.get("server-icon.png"); - if (Files.exists(faviconPath)) { - try { - this.favicon = Favicon.create(faviconPath); - } catch (Exception e) { - getLogger().info("Unable to load your server-icon.png, continuing without it.", e); + try { + getMotdComponent(); + } catch (Exception e) { + logger.error("Can't parse your MOTD", e); + valid = false; + } + + if (advanced.compressionLevel < -1 || advanced.compressionLevel > 9) { + logger.error("Invalid compression level {}", advanced.compressionLevel); + valid = false; + } else if (advanced.compressionLevel == 0) { + logger.warn( + "ALL packets going through the proxy will be uncompressed. This will increase bandwidth usage."); + } + + if (advanced.compressionThreshold < -1) { + logger.error("Invalid compression threshold {}", advanced.compressionLevel); + valid = false; + } else if (advanced.compressionThreshold == 0) { + logger.warn( + "ALL packets going through the proxy will be compressed. This will compromise throughput and increase CPU usage!"); + } + + if (advanced.loginRatelimit < 0) { + logger.error("Invalid login ratelimit {}ms", advanced.loginRatelimit); + valid = false; + } + + loadFavicon(); + + return valid; + } + + private void loadFavicon() { + Path faviconPath = Paths.get("server-icon.png"); + if (Files.exists(faviconPath)) { + try { + this.favicon = Favicon.create(faviconPath); + } catch (Exception e) { + getLogger().info("Unable to load your server-icon.png, continuing without it.", e); + } + } + } + + public InetSocketAddress getBind() { + return AddressUtil.parseAddress(bind); + } + + public boolean isQueryEnabled() { + return query.isQueryEnabled(); + } + + public int getQueryPort() { + return query.getQueryPort(); + } + + public String getQueryMap() { + return query.getQueryMap(); + } + + @Override + public boolean shouldQueryShowPlugins() { + return query.shouldQueryShowPlugins(); + } + + public String getMotd() { + return motd; + } + + public Component getMotdComponent() { + if (motdAsComponent == null) { + if (motd.startsWith("{")) { + motdAsComponent = ComponentSerializers.JSON.deserialize(motd); + } else { + motdAsComponent = ComponentSerializers.LEGACY.deserialize(motd, '&'); + } + } + return motdAsComponent; + } + + public int getShowMaxPlayers() { + return showMaxPlayers; + } + + public boolean isOnlineMode() { + return onlineMode; + } + + public PlayerInfoForwarding getPlayerInfoForwardingMode() { + return playerInfoForwardingMode; + } + + public byte[] getForwardingSecret() { + return forwardingSecret; + } + + public Map getServers() { + return servers.getServers(); + } + + public List getAttemptConnectionOrder() { + return servers.getAttemptConnectionOrder(); + } + + public Map> getForcedHosts() { + return forcedHosts.getForcedHosts(); + } + + public int getCompressionThreshold() { + return advanced.getCompressionThreshold(); + } + + public int getCompressionLevel() { + return advanced.getCompressionLevel(); + } + + public int getLoginRatelimit() { + return advanced.getLoginRatelimit(); + } + + public Optional getFavicon() { + return Optional.ofNullable(favicon); + } + + public boolean isAnnounceForge() { + return announceForge; + } + + public int getConnectTimeout() { + return advanced.getConnectionTimeout(); + } + + public int getReadTimeout() { + return advanced.getReadTimeout(); + } + + public boolean isProxyProtocol() { + return advanced.isProxyProtocol(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("configVersion", configVersion) + .add("bind", bind) + .add("motd", motd) + .add("showMaxPlayers", showMaxPlayers) + .add("onlineMode", onlineMode) + .add("playerInfoForwardingMode", playerInfoForwardingMode) + .add("forwardingSecret", forwardingSecret) + .add("announceForge", announceForge) + .add("servers", servers) + .add("forcedHosts", forcedHosts) + .add("advanced", advanced) + .add("query", query) + .add("favicon", favicon) + .toString(); + } + + public static VelocityConfiguration read(Path path) throws IOException { + Toml toml; + if (!path.toFile().exists()) { + getLogger().info("No velocity.toml found, creating one for you..."); + return new VelocityConfiguration(new Servers(), new ForcedHosts(), new Advanced(), + new Query()); + } else { + try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + toml = new Toml().read(reader); + } + } + + Servers servers = new Servers(toml.getTable("servers")); + ForcedHosts forcedHosts = new ForcedHosts(toml.getTable("forced-hosts")); + Advanced advanced = new Advanced(toml.getTable("advanced")); + Query query = new Query(toml.getTable("query")); + byte[] forwardingSecret = toml.getString("forwarding-secret", "5up3r53cr3t") + .getBytes(StandardCharsets.UTF_8); + + VelocityConfiguration configuration = new VelocityConfiguration( + toml.getString("bind", "0.0.0.0:25577"), + toml.getString("motd", "&3A Velocity Server"), + toml.getLong("show-max-players", 500L).intValue(), + toml.getBoolean("online-mode", true), + toml.getBoolean("announce-forge", false), + PlayerInfoForwarding + .valueOf(toml.getString("player-info-forwarding-mode", "MODERN").toUpperCase()), + forwardingSecret, + servers, + forcedHosts, + advanced, + query + ); + upgradeConfig(configuration, toml); + return configuration; + } + + private static void upgradeConfig(VelocityConfiguration configuration, Toml toml) { + switch (toml.getString("config-version", configuration.configVersion)) { + case "1.0": + //TODO: Upgrade a 1.0 config to a new version. Maybe add a recursive support in future. + break; + default: + break; + } + } + + private static String generateRandomString(int lenght) { + String chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890"; + StringBuilder builder = new StringBuilder(); + Random rnd = new Random(); + for (int i = 0; i < lenght; i++) { + builder.append(chars.charAt(rnd.nextInt(chars.length()))); + } + return builder.toString(); + } + + private static class Servers { + + @IsMap + @Comment("Configure your servers here.") + private Map servers = ImmutableMap.of( + "lobby", "127.0.0.1:30066", + "factions", "127.0.0.1:30067", + "minigames", "127.0.0.1:30068" + ); + + @Comment("In what order we should try servers when a player logs in or is kicked from a server.") + @ConfigKey("try") + private List attemptConnectionOrder = Arrays.asList("lobby"); + + private Servers() { + } + + private Servers(Toml toml) { + if (toml != null) { + Map servers = new HashMap<>(); + for (Map.Entry entry : toml.entrySet()) { + if (entry.getValue() instanceof String) { + servers.put(entry.getKey(), (String) entry.getValue()); + } else { + if (!entry.getKey().equalsIgnoreCase("try")) { + throw new IllegalArgumentException( + "Server entry " + entry.getKey() + " is not a string!"); } + } } + this.servers = ImmutableMap.copyOf(servers); + this.attemptConnectionOrder = toml.getList("try", attemptConnectionOrder); + } } - public InetSocketAddress getBind() { - return AddressUtil.parseAddress(bind); + private Servers(Map servers, List attemptConnectionOrder) { + this.servers = servers; + this.attemptConnectionOrder = attemptConnectionOrder; } - public boolean isQueryEnabled() { - return query.isQueryEnabled(); + private Map getServers() { + return servers; } - public int getQueryPort() { - return query.getQueryPort(); - } - - public String getQueryMap() { - return query.getQueryMap(); - } - - @Override - public boolean shouldQueryShowPlugins() { - return query.shouldQueryShowPlugins(); - } - - public String getMotd() { - return motd; - } - - public Component getMotdComponent() { - if (motdAsComponent == null) { - if (motd.startsWith("{")) { - motdAsComponent = ComponentSerializers.JSON.deserialize(motd); - } else { - motdAsComponent = ComponentSerializers.LEGACY.deserialize(motd, '&'); - } - } - return motdAsComponent; - } - - public int getShowMaxPlayers() { - return showMaxPlayers; - } - - public boolean isOnlineMode() { - return onlineMode; - } - - public PlayerInfoForwarding getPlayerInfoForwardingMode() { - return playerInfoForwardingMode; - } - - public byte[] getForwardingSecret() { - return forwardingSecret; - } - - public Map getServers() { - return servers.getServers(); + public void setServers(Map servers) { + this.servers = servers; } public List getAttemptConnectionOrder() { - return servers.getAttemptConnectionOrder(); + return attemptConnectionOrder; } - public Map> getForcedHosts() { - return forcedHosts.getForcedHosts(); - } - - public int getCompressionThreshold() { - return advanced.getCompressionThreshold(); - } - - public int getCompressionLevel() { - return advanced.getCompressionLevel(); - } - - public int getLoginRatelimit() { - return advanced.getLoginRatelimit(); - } - - public Optional getFavicon() { - return Optional.ofNullable(favicon); - } - - public boolean isAnnounceForge() { - return announceForge; - } - - public int getConnectTimeout() { - return advanced.getConnectionTimeout(); - } - - public int getReadTimeout() { - return advanced.getReadTimeout(); - } - - public boolean isProxyProtocol() { - return advanced.isProxyProtocol(); + public void setAttemptConnectionOrder(List attemptConnectionOrder) { + this.attemptConnectionOrder = attemptConnectionOrder; } @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("configVersion", configVersion) - .add("bind", bind) - .add("motd", motd) - .add("showMaxPlayers", showMaxPlayers) - .add("onlineMode", onlineMode) - .add("playerInfoForwardingMode", playerInfoForwardingMode) - .add("forwardingSecret", forwardingSecret) - .add("announceForge", announceForge) - .add("servers", servers) - .add("forcedHosts", forcedHosts) - .add("advanced", advanced) - .add("query", query) - .add("favicon", favicon) - .toString(); + return "Servers{" + + "servers=" + servers + + ", attemptConnectionOrder=" + attemptConnectionOrder + + '}'; + } + } + + private static class ForcedHosts { + + @IsMap + @Comment("Configure your forced hosts here.") + private Map> forcedHosts = ImmutableMap.of( + "lobby.example.com", ImmutableList.of("lobby"), + "factions.example.com", ImmutableList.of("factions"), + "minigames.example.com", ImmutableList.of("minigames") + ); + + private ForcedHosts() { } - public static VelocityConfiguration read(Path path) throws IOException { - Toml toml; - if (!path.toFile().exists()) { - getLogger().info("No velocity.toml found, creating one for you..."); - return new VelocityConfiguration(new Servers(), new ForcedHosts(), new Advanced(), new Query()); - } else { - try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { - toml = new Toml().read(reader); - } + private ForcedHosts(Toml toml) { + if (toml != null) { + Map> forcedHosts = new HashMap<>(); + for (Map.Entry entry : toml.entrySet()) { + if (entry.getValue() instanceof String) { + forcedHosts + .put(stripQuotes(entry.getKey()), ImmutableList.of((String) entry.getValue())); + } else if (entry.getValue() instanceof List) { + forcedHosts.put(stripQuotes(entry.getKey()), + ImmutableList.copyOf((List) entry.getValue())); + } else { + throw new IllegalStateException( + "Invalid value of type " + entry.getValue().getClass() + " in forced hosts!"); + } } - - Servers servers = new Servers(toml.getTable("servers")); - ForcedHosts forcedHosts = new ForcedHosts(toml.getTable("forced-hosts")); - Advanced advanced = new Advanced(toml.getTable("advanced")); - Query query = new Query(toml.getTable("query")); - byte[] forwardingSecret = toml.getString("forwarding-secret", "5up3r53cr3t") - .getBytes(StandardCharsets.UTF_8); - - VelocityConfiguration configuration = new VelocityConfiguration( - toml.getString("bind", "0.0.0.0:25577"), - toml.getString("motd", "&3A Velocity Server"), - toml.getLong("show-max-players", 500L).intValue(), - toml.getBoolean("online-mode", true), - toml.getBoolean("announce-forge", false), - PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding-mode", "MODERN").toUpperCase()), - forwardingSecret, - servers, - forcedHosts, - advanced, - query - ); - upgradeConfig(configuration, toml); - return configuration; + this.forcedHosts = ImmutableMap.copyOf(forcedHosts); + } } - private static void upgradeConfig(VelocityConfiguration configuration, Toml toml) { - switch (toml.getString("config-version", configuration.configVersion)) { - case "1.0": - //TODO: Upgrade a 1.0 config to a new version. Maybe add a recursive support in future. - break; - default: - break; - } + private ForcedHosts(Map> forcedHosts) { + this.forcedHosts = forcedHosts; } - private static String generateRandomString(int lenght) { - String chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890"; - StringBuilder builder = new StringBuilder(); - Random rnd = new Random(); - for (int i = 0; i < lenght; i++) { - builder.append(chars.charAt(rnd.nextInt(chars.length()))); - } - return builder.toString(); + private Map> getForcedHosts() { + return forcedHosts; } - private static class Servers { - @IsMap - @Comment("Configure your servers here.") - private Map servers = ImmutableMap.of( - "lobby", "127.0.0.1:30066", - "factions", "127.0.0.1:30067", - "minigames", "127.0.0.1:30068" - ); - - @Comment("In what order we should try servers when a player logs in or is kicked from a server.") - @ConfigKey("try") - private List attemptConnectionOrder = Arrays.asList("lobby"); - - private Servers() {} - - private Servers(Toml toml) { - if (toml != null) { - Map servers = new HashMap<>(); - for (Map.Entry entry : toml.entrySet()) { - if (entry.getValue() instanceof String) { - servers.put(entry.getKey(), (String) entry.getValue()); - } else { - if (!entry.getKey().equalsIgnoreCase("try")) { - throw new IllegalArgumentException("Server entry " + entry.getKey() + " is not a string!"); - } - } - } - this.servers = ImmutableMap.copyOf(servers); - this.attemptConnectionOrder = toml.getList("try", attemptConnectionOrder); - } - } - - private Servers(Map servers, List attemptConnectionOrder) { - this.servers = servers; - this.attemptConnectionOrder = attemptConnectionOrder; - } - - private Map getServers() { - return servers; - } - - public void setServers(Map servers) { - this.servers = servers; - } - - public List getAttemptConnectionOrder() { - return attemptConnectionOrder; - } - - public void setAttemptConnectionOrder(List attemptConnectionOrder) { - this.attemptConnectionOrder = attemptConnectionOrder; - } - - @Override - public String toString() { - return "Servers{" - + "servers=" + servers - + ", attemptConnectionOrder=" + attemptConnectionOrder - + '}'; - } + private void setForcedHosts(Map> forcedHosts) { + this.forcedHosts = forcedHosts; } - private static class ForcedHosts { - @IsMap - @Comment("Configure your forced hosts here.") - private Map> forcedHosts = ImmutableMap.of( - "lobby.example.com", ImmutableList.of("lobby"), - "factions.example.com", ImmutableList.of("factions"), - "minigames.example.com", ImmutableList.of("minigames") - ); - - private ForcedHosts() {} - - private ForcedHosts(Toml toml) { - if (toml != null) { - Map> forcedHosts = new HashMap<>(); - for (Map.Entry entry : toml.entrySet()) { - if (entry.getValue() instanceof String) { - forcedHosts.put(stripQuotes(entry.getKey()), ImmutableList.of((String) entry.getValue())); - } else if (entry.getValue() instanceof List) { - forcedHosts.put(stripQuotes(entry.getKey()), ImmutableList.copyOf((List) entry.getValue())); - } else { - throw new IllegalStateException("Invalid value of type " + entry.getValue().getClass() + " in forced hosts!"); - } - } - this.forcedHosts = ImmutableMap.copyOf(forcedHosts); - } - } - - private ForcedHosts(Map> forcedHosts) { - this.forcedHosts = forcedHosts; - } - - private Map> getForcedHosts() { - return forcedHosts; - } - - private void setForcedHosts(Map> forcedHosts) { - this.forcedHosts = forcedHosts; - } - - private static String stripQuotes(String key) { - int lastIndex; - if (key.indexOf('"') == 0 && (lastIndex = key.lastIndexOf('"')) == (key.length() - 1)) { - return key.substring(1, lastIndex); - } - return key; - } - - @Override - public String toString() { - return "ForcedHosts{" + - "forcedHosts=" + forcedHosts + - '}'; - } + private static String stripQuotes(String key) { + int lastIndex; + if (key.indexOf('"') == 0 && (lastIndex = key.lastIndexOf('"')) == (key.length() - 1)) { + return key.substring(1, lastIndex); + } + return key; } - private static class Advanced { - @Comment({ - "How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", - "setting it to -1 will disable compression entirely." - }) - @ConfigKey("compression-threshold") - private int compressionThreshold = 1024; + @Override + public String toString() { + return "ForcedHosts{" + + "forcedHosts=" + forcedHosts + + '}'; + } + } - @Comment("How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6.") - @ConfigKey("compression-level") - private int compressionLevel = -1; + private static class Advanced { - @Comment({ - "How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", - "Disable by setting to 0" - }) - @ConfigKey("login-ratelimit") - private int loginRatelimit = 3000; + @Comment({ + "How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", + "setting it to -1 will disable compression entirely." + }) + @ConfigKey("compression-threshold") + private int compressionThreshold = 1024; - @Comment({"Specify a custom timeout for connection timeouts here. The default is five seconds."}) - @ConfigKey("connection-timeout") - private int connectionTimeout = 5000; + @Comment("How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6.") + @ConfigKey("compression-level") + private int compressionLevel = -1; - @Comment({"Specify a read timeout for connections here. The default is 30 seconds."}) - @ConfigKey("read-timeout") - private int readTimeout = 30000; + @Comment({ + "How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", + "Disable by setting to 0" + }) + @ConfigKey("login-ratelimit") + private int loginRatelimit = 3000; - @Comment("Enables compatibility with HAProxy.") - @ConfigKey("proxy-protocol") - private boolean proxyProtocol = false; + @Comment({ + "Specify a custom timeout for connection timeouts here. The default is five seconds."}) + @ConfigKey("connection-timeout") + private int connectionTimeout = 5000; - private Advanced() {} + @Comment({"Specify a read timeout for connections here. The default is 30 seconds."}) + @ConfigKey("read-timeout") + private int readTimeout = 30000; - private Advanced(Toml toml) { - if (toml != null) { - this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); - this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); - this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); - this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue(); - this.readTimeout = toml.getLong("read-timeout", 30000L).intValue(); - this.proxyProtocol = toml.getBoolean("proxy-protocol", false); - } - } + @Comment("Enables compatibility with HAProxy.") + @ConfigKey("proxy-protocol") + private boolean proxyProtocol = false; - public int getCompressionThreshold() { - return compressionThreshold; - } - - public int getCompressionLevel() { - return compressionLevel; - } - - public int getLoginRatelimit() { - return loginRatelimit; - } - - public int getConnectionTimeout() { - return connectionTimeout; - } - - public int getReadTimeout() { - return readTimeout; - } - - public boolean isProxyProtocol() { - return proxyProtocol; - } - - @Override - public String toString() { - return "Advanced{" + - "compressionThreshold=" + compressionThreshold + - ", compressionLevel=" + compressionLevel + - ", loginRatelimit=" + loginRatelimit + - ", connectionTimeout=" + connectionTimeout + - ", readTimeout=" + readTimeout + - ", proxyProtocol=" + proxyProtocol + - '}'; - } + private Advanced() { } - private static class Query { - @Comment("Whether to enable responding to GameSpy 4 query responses or not") - @ConfigKey("enabled") - private boolean queryEnabled = false; - - @Comment("If query responding is enabled, on what port should query response listener listen on?") - @ConfigKey("port") - private int queryPort = 25577; - - @Comment("This is the map name that is reported to the query services.") - @ConfigKey("map") - private String queryMap = "Velocity"; - - @Comment("Whether plugins should be shown in query response by default or not") - @ConfigKey("show-plugins") - private boolean showPlugins = false; - - private Query() {} - - private Query(boolean queryEnabled, int queryPort, String queryMap, boolean showPlugins) { - this.queryEnabled = queryEnabled; - this.queryPort = queryPort; - this.queryMap = queryMap; - this.showPlugins = showPlugins; - } - - private Query(Toml toml) { - if (toml != null) { - this.queryEnabled = toml.getBoolean("enabled", false); - this.queryPort = toml.getLong("port", 25577L).intValue(); - this.queryMap = toml.getString("map", "Velocity"); - this.showPlugins = toml.getBoolean("show-plugins", false); - } - } - - public boolean isQueryEnabled() { - return queryEnabled; - } - - public int getQueryPort() { - return queryPort; - } - - public String getQueryMap() { - return queryMap; - } - - public boolean shouldQueryShowPlugins() { - return showPlugins; - } - - @Override - public String toString() { - return "Query{" + - "queryEnabled=" + queryEnabled + - ", queryPort=" + queryPort + - ", queryMap='" + queryMap + '\'' + - ", showPlugins=" + showPlugins + - '}'; - } + private Advanced(Toml toml) { + if (toml != null) { + this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); + this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); + this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); + this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue(); + this.readTimeout = toml.getLong("read-timeout", 30000L).intValue(); + this.proxyProtocol = toml.getBoolean("proxy-protocol", false); + } } + + public int getCompressionThreshold() { + return compressionThreshold; + } + + public int getCompressionLevel() { + return compressionLevel; + } + + public int getLoginRatelimit() { + return loginRatelimit; + } + + public int getConnectionTimeout() { + return connectionTimeout; + } + + public int getReadTimeout() { + return readTimeout; + } + + public boolean isProxyProtocol() { + return proxyProtocol; + } + + @Override + public String toString() { + return "Advanced{" + + "compressionThreshold=" + compressionThreshold + + ", compressionLevel=" + compressionLevel + + ", loginRatelimit=" + loginRatelimit + + ", connectionTimeout=" + connectionTimeout + + ", readTimeout=" + readTimeout + + ", proxyProtocol=" + proxyProtocol + + '}'; + } + } + + private static class Query { + + @Comment("Whether to enable responding to GameSpy 4 query responses or not") + @ConfigKey("enabled") + private boolean queryEnabled = false; + + @Comment("If query responding is enabled, on what port should query response listener listen on?") + @ConfigKey("port") + private int queryPort = 25577; + + @Comment("This is the map name that is reported to the query services.") + @ConfigKey("map") + private String queryMap = "Velocity"; + + @Comment("Whether plugins should be shown in query response by default or not") + @ConfigKey("show-plugins") + private boolean showPlugins = false; + + private Query() { + } + + private Query(boolean queryEnabled, int queryPort, String queryMap, boolean showPlugins) { + this.queryEnabled = queryEnabled; + this.queryPort = queryPort; + this.queryMap = queryMap; + this.showPlugins = showPlugins; + } + + private Query(Toml toml) { + if (toml != null) { + this.queryEnabled = toml.getBoolean("enabled", false); + this.queryPort = toml.getLong("port", 25577L).intValue(); + this.queryMap = toml.getString("map", "Velocity"); + this.showPlugins = toml.getBoolean("show-plugins", false); + } + } + + public boolean isQueryEnabled() { + return queryEnabled; + } + + public int getQueryPort() { + return queryPort; + } + + public String getQueryMap() { + return queryMap; + } + + public boolean shouldQueryShowPlugins() { + return showPlugins; + } + + @Override + public String toString() { + return "Query{" + + "queryEnabled=" + queryEnabled + + ", queryPort=" + queryPort + + ", queryMap='" + queryMap + '\'' + + ", showPlugins=" + showPlugins + + '}'; + } + } } 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 0fd490649..d474b903e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -1,5 +1,14 @@ package com.velocitypowered.proxy.connection; +import static com.velocitypowered.proxy.network.Connections.CIPHER_DECODER; +import static com.velocitypowered.proxy.network.Connections.CIPHER_ENCODER; +import static com.velocitypowered.proxy.network.Connections.COMPRESSION_DECODER; +import static com.velocitypowered.proxy.network.Connections.COMPRESSION_ENCODER; +import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; +import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER; + import com.google.common.base.Preconditions; import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.encryption.VelocityCipher; @@ -9,278 +18,288 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.proxy.protocol.netty.*; +import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftCipherEncoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftCompressDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftCompressEncoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; +import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import io.netty.buffer.ByteBuf; -import io.netty.channel.*; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.EventLoop; import io.netty.handler.codec.haproxy.HAProxyMessage; import io.netty.util.ReferenceCountUtil; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.GeneralSecurityException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; 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; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.security.GeneralSecurityException; - -import static com.velocitypowered.proxy.network.Connections.*; - /** - * A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft - * protocol mechanics. + * A utility class to make working with the pipeline a little less painful and transparently handles + * certain Minecraft protocol mechanics. */ public class MinecraftConnection extends ChannelInboundHandlerAdapter { - private static final Logger logger = LogManager.getLogger(MinecraftConnection.class); - private final Channel channel; - private SocketAddress remoteAddress; - private StateRegistry state; - private @Nullable MinecraftSessionHandler sessionHandler; - private int protocolVersion; - private int nextProtocolVersion; - private @Nullable MinecraftConnectionAssociation association; - private boolean isLegacyForge; - private final VelocityServer server; - private boolean canSendLegacyFMLResetPacket = false; + private static final Logger logger = LogManager.getLogger(MinecraftConnection.class); - public MinecraftConnection(Channel channel, VelocityServer server) { - this.channel = channel; - this.remoteAddress = channel.remoteAddress(); - this.server = server; - this.state = StateRegistry.HANDSHAKE; + private final Channel channel; + private SocketAddress remoteAddress; + private StateRegistry state; + private @Nullable MinecraftSessionHandler sessionHandler; + private int protocolVersion; + private int nextProtocolVersion; + private @Nullable MinecraftConnectionAssociation association; + private boolean isLegacyForge; + private final VelocityServer server; + private boolean canSendLegacyFMLResetPacket = false; + + public MinecraftConnection(Channel channel, VelocityServer server) { + this.channel = channel; + this.remoteAddress = channel.remoteAddress(); + this.server = server; + this.state = StateRegistry.HANDSHAKE; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + if (sessionHandler != null) { + sessionHandler.connected(); } - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - if (sessionHandler != null) { - sessionHandler.connected(); + if (association != null) { + logger.info("{} has connected", association); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + if (sessionHandler != null) { + sessionHandler.disconnected(); + } + + if (association != null) { + logger.info("{} has disconnected", association); + } + } + + @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; + } + + MinecraftPacket pkt = (MinecraftPacket) msg; + if (!pkt.handle(sessionHandler)) { + sessionHandler.handleGeneric((MinecraftPacket) msg); + } + } else if (msg instanceof HAProxyMessage) { + if (sessionHandler.beforeHandle()) { + return; + } + + HAProxyMessage proxyMessage = (HAProxyMessage) msg; + this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(), + proxyMessage.sourcePort()); + } else if (msg instanceof ByteBuf) { + try { + if (sessionHandler.beforeHandle()) { + return; } + sessionHandler.handleUnknown((ByteBuf) msg); + } finally { + ReferenceCountUtil.release(msg); + } + } + } - if (association != null) { - logger.info("{} has connected", association); - } + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (ctx.channel().isActive()) { + if (sessionHandler != null) { + sessionHandler.exception(cause); + } + + if (association != null) { + logger.error("{}: exception encountered", association, cause); + } + + ctx.close(); + } + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + if (sessionHandler != null) { + sessionHandler.writabilityChanged(); + } + } + + public EventLoop eventLoop() { + return channel.eventLoop(); + } + + public void write(Object msg) { + if (channel.isActive()) { + channel.writeAndFlush(msg, channel.voidPromise()); + } + } + + public void delayedWrite(Object msg) { + if (channel.isActive()) { + channel.write(msg, channel.voidPromise()); + } + } + + public void flush() { + if (channel.isActive()) { + channel.flush(); + } + } + + public void closeWith(Object msg) { + if (channel.isActive()) { + channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); + } + } + + public void close() { + if (channel.isActive()) { + channel.close(); + } + } + + public Channel getChannel() { + return channel; + } + + public boolean isClosed() { + return !channel.isActive(); + } + + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + public StateRegistry getState() { + return state; + } + + public void setState(StateRegistry state) { + this.state = state; + this.channel.pipeline().get(MinecraftEncoder.class).setState(state); + this.channel.pipeline().get(MinecraftDecoder.class).setState(state); + } + + public int getProtocolVersion() { + return protocolVersion; + } + + public void setProtocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + this.nextProtocolVersion = protocolVersion; + if (protocolVersion != ProtocolConstants.LEGACY) { + this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion); + this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion); + } else { + // Legacy handshake handling + this.channel.pipeline().remove(MINECRAFT_ENCODER); + this.channel.pipeline().remove(MINECRAFT_DECODER); + } + } + + @Nullable + public MinecraftSessionHandler getSessionHandler() { + return sessionHandler; + } + + public void setSessionHandler(MinecraftSessionHandler sessionHandler) { + if (this.sessionHandler != null) { + this.sessionHandler.deactivated(); + } + this.sessionHandler = sessionHandler; + sessionHandler.activated(); + } + + private void ensureOpen() { + Preconditions.checkState(!isClosed(), "Connection is closed."); + } + + public void setCompressionThreshold(int threshold) { + ensureOpen(); + + if (threshold == -1) { + channel.pipeline().remove(COMPRESSION_DECODER); + channel.pipeline().remove(COMPRESSION_ENCODER); + return; } - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - if (sessionHandler != null) { - sessionHandler.disconnected(); - } + int level = server.getConfiguration().getCompressionLevel(); + VelocityCompressor compressor = Natives.compressor.get().create(level); + MinecraftCompressEncoder encoder = new MinecraftCompressEncoder(threshold, compressor); + MinecraftCompressDecoder decoder = new MinecraftCompressDecoder(threshold, compressor); - if (association != null) { - logger.info("{} has disconnected", association); - } - } + channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder); + channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder); + } - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (sessionHandler == null) { - // No session handler available, do nothing - ReferenceCountUtil.release(msg); - return; - } + public void enableEncryption(byte[] secret) throws GeneralSecurityException { + ensureOpen(); - if (msg instanceof MinecraftPacket) { - if (sessionHandler.beforeHandle()) { - return; - } + SecretKey key = new SecretKeySpec(secret, "AES"); - MinecraftPacket pkt = (MinecraftPacket) msg; - if (!pkt.handle(sessionHandler)) { - sessionHandler.handleGeneric((MinecraftPacket) msg); - } - } else if (msg instanceof HAProxyMessage) { - if (sessionHandler.beforeHandle()) { - return; - } + VelocityCipherFactory factory = Natives.cipher.get(); + VelocityCipher decryptionCipher = factory.forDecryption(key); + VelocityCipher encryptionCipher = factory.forEncryption(key); + channel.pipeline() + .addBefore(FRAME_DECODER, CIPHER_DECODER, new MinecraftCipherDecoder(decryptionCipher)); + channel.pipeline() + .addBefore(FRAME_ENCODER, CIPHER_ENCODER, new MinecraftCipherEncoder(encryptionCipher)); + } - HAProxyMessage proxyMessage = (HAProxyMessage) msg; - this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(), proxyMessage.sourcePort()); - } else if (msg instanceof ByteBuf) { - try { - if (sessionHandler.beforeHandle()) { - return; - } - sessionHandler.handleUnknown((ByteBuf) msg); - } finally { - ReferenceCountUtil.release(msg); - } - } - } + @Nullable + public MinecraftConnectionAssociation getAssociation() { + return association; + } - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - if (ctx.channel().isActive()) { - if (sessionHandler != null) { - sessionHandler.exception(cause); - } + public void setAssociation(MinecraftConnectionAssociation association) { + this.association = association; + } - if (association != null) { - logger.error("{}: exception encountered", association, cause); - } + public boolean isLegacyForge() { + return isLegacyForge; + } - ctx.close(); - } - } + public void setLegacyForge(boolean isForge) { + this.isLegacyForge = isForge; + } - @Override - public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { - if (sessionHandler != null) { - sessionHandler.writabilityChanged(); - } - } + public boolean canSendLegacyFMLResetPacket() { + return canSendLegacyFMLResetPacket; + } - public EventLoop eventLoop() { - return channel.eventLoop(); - } + public void setCanSendLegacyFMLResetPacket(boolean canSendLegacyFMLResetPacket) { + this.canSendLegacyFMLResetPacket = isLegacyForge && canSendLegacyFMLResetPacket; + } - public void write(Object msg) { - if (channel.isActive()) { - channel.writeAndFlush(msg, channel.voidPromise()); - } - } + public int getNextProtocolVersion() { + return this.nextProtocolVersion; + } - public void delayedWrite(Object msg) { - if (channel.isActive()) { - channel.write(msg, channel.voidPromise()); - } - } - - public void flush() { - if (channel.isActive()) { - channel.flush(); - } - } - - public void closeWith(Object msg) { - if (channel.isActive()) { - channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); - } - } - - public void close() { - if (channel.isActive()) { - channel.close(); - } - } - - public Channel getChannel() { - return channel; - } - - public boolean isClosed() { - return !channel.isActive(); - } - - public SocketAddress getRemoteAddress() { - return remoteAddress; - } - - public StateRegistry getState() { - return state; - } - - public void setState(StateRegistry state) { - this.state = state; - this.channel.pipeline().get(MinecraftEncoder.class).setState(state); - this.channel.pipeline().get(MinecraftDecoder.class).setState(state); - } - - public int getProtocolVersion() { - return protocolVersion; - } - - public void setProtocolVersion(int protocolVersion) { - this.protocolVersion = protocolVersion; - this.nextProtocolVersion = protocolVersion; - if (protocolVersion != ProtocolConstants.LEGACY) { - this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion); - this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion); - } else { - // Legacy handshake handling - this.channel.pipeline().remove(MINECRAFT_ENCODER); - this.channel.pipeline().remove(MINECRAFT_DECODER); - } - } - - @Nullable - public MinecraftSessionHandler getSessionHandler() { - return sessionHandler; - } - - public void setSessionHandler(MinecraftSessionHandler sessionHandler) { - if (this.sessionHandler != null) { - this.sessionHandler.deactivated(); - } - this.sessionHandler = sessionHandler; - sessionHandler.activated(); - } - - private void ensureOpen() { - Preconditions.checkState(!isClosed(), "Connection is closed."); - } - - public void setCompressionThreshold(int threshold) { - ensureOpen(); - - if (threshold == -1) { - channel.pipeline().remove(COMPRESSION_DECODER); - channel.pipeline().remove(COMPRESSION_ENCODER); - return; - } - - int level = server.getConfiguration().getCompressionLevel(); - VelocityCompressor compressor = Natives.compressor.get().create(level); - MinecraftCompressEncoder encoder = new MinecraftCompressEncoder(threshold, compressor); - MinecraftCompressDecoder decoder = new MinecraftCompressDecoder(threshold, compressor); - - channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder); - channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder); - } - - public void enableEncryption(byte[] secret) throws GeneralSecurityException { - ensureOpen(); - - SecretKey key = new SecretKeySpec(secret, "AES"); - - VelocityCipherFactory factory = Natives.cipher.get(); - VelocityCipher decryptionCipher = factory.forDecryption(key); - VelocityCipher encryptionCipher = factory.forEncryption(key); - channel.pipeline().addBefore(FRAME_DECODER, CIPHER_DECODER, new MinecraftCipherDecoder(decryptionCipher)); - channel.pipeline().addBefore(FRAME_ENCODER, CIPHER_ENCODER, new MinecraftCipherEncoder(encryptionCipher)); - } - - @Nullable - public MinecraftConnectionAssociation getAssociation() { - return association; - } - - public void setAssociation(MinecraftConnectionAssociation association) { - this.association = association; - } - - public boolean isLegacyForge() { - return isLegacyForge; - } - - public void setLegacyForge(boolean isForge) { - this.isLegacyForge = isForge; - } - - public boolean canSendLegacyFMLResetPacket() { - return canSendLegacyFMLResetPacket; - } - - public void setCanSendLegacyFMLResetPacket(boolean canSendLegacyFMLResetPacket) { - this.canSendLegacyFMLResetPacket = isLegacyForge && canSendLegacyFMLResetPacket; - } - - public int getNextProtocolVersion() { - return this.nextProtocolVersion; - } - - public void setNextProtocolVersion(int nextProtocolVersion) { - this.nextProtocolVersion = nextProtocolVersion; - } + public void setNextProtocolVersion(int nextProtocolVersion) { + this.nextProtocolVersion = nextProtocolVersion; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnectionAssociation.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnectionAssociation.java index b557e0b4f..d28e3be4e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnectionAssociation.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnectionAssociation.java @@ -1,4 +1,5 @@ package com.velocitypowered.proxy.connection; public interface MinecraftConnectionAssociation { + } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index c756b31cf..e9b88e144 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -1,70 +1,173 @@ package com.velocitypowered.proxy.connection; import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.protocol.packet.BossBar; +import com.velocitypowered.proxy.protocol.packet.Chat; +import com.velocitypowered.proxy.protocol.packet.ClientSettings; +import com.velocitypowered.proxy.protocol.packet.Disconnect; +import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; +import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; +import com.velocitypowered.proxy.protocol.packet.Handshake; +import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; +import com.velocitypowered.proxy.protocol.packet.JoinGame; +import com.velocitypowered.proxy.protocol.packet.KeepAlive; +import com.velocitypowered.proxy.protocol.packet.LegacyHandshake; +import com.velocitypowered.proxy.protocol.packet.LegacyPing; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import com.velocitypowered.proxy.protocol.packet.PlayerListItem; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.packet.Respawn; +import com.velocitypowered.proxy.protocol.packet.ServerLogin; +import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; +import com.velocitypowered.proxy.protocol.packet.SetCompression; +import com.velocitypowered.proxy.protocol.packet.StatusPing; +import com.velocitypowered.proxy.protocol.packet.StatusRequest; +import com.velocitypowered.proxy.protocol.packet.StatusResponse; +import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; +import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; +import com.velocitypowered.proxy.protocol.packet.TitlePacket; import io.netty.buffer.ByteBuf; public interface MinecraftSessionHandler { - default boolean beforeHandle() { - return false; - } - default void handleGeneric(MinecraftPacket packet) { + default boolean beforeHandle() { + return false; + } - } + default void handleGeneric(MinecraftPacket packet) { - default void handleUnknown(ByteBuf buf) { + } - } + default void handleUnknown(ByteBuf buf) { - default void connected() { + } - } + default void connected() { - default void disconnected() { + } - } + default void disconnected() { - default void activated() { + } - } + default void activated() { - default void deactivated() { + } - } + default void deactivated() { - default void exception(Throwable throwable) { + } - } + default void exception(Throwable throwable) { - default void writabilityChanged() { + } - } + default void writabilityChanged() { - default boolean handle(BossBar packet) { return false; } - default boolean handle(Chat packet) { return false; } - default boolean handle(ClientSettings packet) { return false; } - default boolean handle(Disconnect packet) { return false; } - default boolean handle(EncryptionRequest packet) { return false; } - default boolean handle(EncryptionResponse packet) { return false; } - default boolean handle(Handshake packet) { return false; } - default boolean handle(HeaderAndFooter packet) { return false; } - default boolean handle(JoinGame packet) { return false; } - default boolean handle(KeepAlive packet) { return false; } - default boolean handle(LegacyHandshake packet) { return false; } - default boolean handle(LegacyPing packet) { return false; } - default boolean handle(LoginPluginMessage packet) { return false; } - default boolean handle(LoginPluginResponse packet) { return false; } - default boolean handle(PluginMessage packet) { return false; } - default boolean handle(Respawn packet) { return false; } - default boolean handle(ServerLogin packet) { return false; } - default boolean handle(ServerLoginSuccess packet) { return false; } - default boolean handle(SetCompression packet) { return false; } - default boolean handle(StatusPing packet) { return false; } - default boolean handle(StatusRequest packet) { return false; } - default boolean handle(StatusResponse packet) { return false; } - default boolean handle(TabCompleteRequest packet) { return false; } - default boolean handle(TabCompleteResponse packet) { return false; } - default boolean handle(TitlePacket packet) { return false; } - default boolean handle(PlayerListItem packet) { return false; } + } + + default boolean handle(BossBar packet) { + return false; + } + + default boolean handle(Chat packet) { + return false; + } + + default boolean handle(ClientSettings packet) { + return false; + } + + default boolean handle(Disconnect packet) { + return false; + } + + default boolean handle(EncryptionRequest packet) { + return false; + } + + default boolean handle(EncryptionResponse packet) { + return false; + } + + default boolean handle(Handshake packet) { + return false; + } + + default boolean handle(HeaderAndFooter packet) { + return false; + } + + default boolean handle(JoinGame packet) { + return false; + } + + default boolean handle(KeepAlive packet) { + return false; + } + + default boolean handle(LegacyHandshake packet) { + return false; + } + + default boolean handle(LegacyPing packet) { + return false; + } + + default boolean handle(LoginPluginMessage packet) { + return false; + } + + default boolean handle(LoginPluginResponse packet) { + return false; + } + + default boolean handle(PluginMessage packet) { + return false; + } + + default boolean handle(Respawn packet) { + return false; + } + + default boolean handle(ServerLogin packet) { + return false; + } + + default boolean handle(ServerLoginSuccess packet) { + return false; + } + + default boolean handle(SetCompression packet) { + return false; + } + + default boolean handle(StatusPing packet) { + return false; + } + + default boolean handle(StatusRequest packet) { + return false; + } + + default boolean handle(StatusResponse packet) { + return false; + } + + default boolean handle(TabCompleteRequest packet) { + return false; + } + + default boolean handle(TabCompleteResponse packet) { + return false; + } + + default boolean handle(TitlePacket packet) { + return false; + } + + default boolean handle(PlayerListItem packet) { + return false; + } } 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 15f9fc559..665cd5c60 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java @@ -1,12 +1,13 @@ package com.velocitypowered.proxy.connection; public class VelocityConstants { - private VelocityConstants() { - throw new AssertionError(); - } - public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info"; - public static final int FORWARDING_VERSION = 1; + private VelocityConstants() { + throw new AssertionError(); + } - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + 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 6f6318ed6..ccd5c798b 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 @@ -10,167 +10,179 @@ import com.velocitypowered.proxy.connection.forge.ForgeConstants; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; -import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.protocol.packet.BossBar; +import com.velocitypowered.proxy.protocol.packet.Disconnect; +import com.velocitypowered.proxy.protocol.packet.JoinGame; +import com.velocitypowered.proxy.protocol.packet.KeepAlive; +import com.velocitypowered.proxy.protocol.packet.PlayerListItem; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; public class BackendPlaySessionHandler implements MinecraftSessionHandler { - private final VelocityServer server; - private final VelocityServerConnection serverConn; - private final ClientPlaySessionHandler playerSessionHandler; - BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) { - this.server = server; - this.serverConn = serverConn; + private final VelocityServer server; + private final VelocityServerConnection serverConn; + private final ClientPlaySessionHandler playerSessionHandler; - 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; + BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) { + this.server = server; + this.serverConn = serverConn; + + 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 + public void activated() { + serverConn.getServer().addPlayer(serverConn.getPlayer()); + } + + @Override + public boolean beforeHandle() { + if (!serverConn.getPlayer().isActive()) { + // Obsolete connection + serverConn.disconnect(); + return true; + } + return false; + } + + @Override + public boolean handle(KeepAlive packet) { + serverConn.setLastPingId(packet.getRandomId()); + return false; // forwards on + } + + @Override + public boolean handle(Disconnect packet) { + serverConn.disconnect(); + serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet); + return true; + } + + @Override + public boolean handle(JoinGame packet) { + playerSessionHandler.handleBackendJoinGame(packet); + return true; + } + + @Override + public boolean handle(BossBar packet) { + if (packet.getAction() == BossBar.ADD) { + playerSessionHandler.getServerBossBars().add(packet.getUuid()); + } else if (packet.getAction() == BossBar.REMOVE) { + playerSessionHandler.getServerBossBars().remove(packet.getUuid()); + } + return false; // forward + } + + @Override + public boolean handle(PluginMessage packet) { + MinecraftConnection smc = serverConn.getConnection(); + if (smc == null) { + return true; } - @Override - public void activated() { - serverConn.getServer().addPlayer(serverConn.getPlayer()); + if (!canForwardPluginMessage(packet)) { + return true; } - @Override - public boolean beforeHandle() { - if (!serverConn.getPlayer().isActive()) { - // Obsolete connection - serverConn.disconnect(); - return true; - } - return false; + if (PluginMessageUtil.isMCBrand(packet)) { + serverConn.getPlayer().getConnection().write(PluginMessageUtil.rewriteMCBrand(packet)); + return true; } - @Override - public boolean handle(KeepAlive packet) { - serverConn.setLastPingId(packet.getRandomId()); - return false; // forwards on + if (!serverConn.hasCompletedJoin() && packet.getChannel() + .equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) { + if (!serverConn.isLegacyForge()) { + serverConn.setLegacyForge(true); + + // We must always reset the handshake before a modded connection is established if + // we haven't done so already. + serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket(); + } + + // Always forward these messages during login. + return false; } - @Override - public boolean handle(Disconnect packet) { - serverConn.disconnect(); - serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet); - return true; + ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel()); + if (id == null) { + return false; } - @Override - public boolean handle(JoinGame packet) { - playerSessionHandler.handleBackendJoinGame(packet); - return true; + PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, + packet.getData()); + server.getEventManager().fire(event) + .thenAcceptAsync(pme -> { + if (pme.getResult().isAllowed()) { + smc.write(packet); + } + }, smc.eventLoop()); + return true; + } + + @Override + public boolean handle(TabCompleteResponse packet) { + playerSessionHandler.handleTabCompleteResponse(packet); + return true; + } + + @Override + public boolean handle(PlayerListItem packet) { + serverConn.getPlayer().getTabList().processBackendPacket(packet); + return false; //Forward packet to player + } + + @Override + public void handleGeneric(MinecraftPacket packet) { + serverConn.getPlayer().getConnection().write(packet); + } + + @Override + public void handleUnknown(ByteBuf buf) { + serverConn.getPlayer().getConnection().write(buf.retain()); + } + + @Override + public void exception(Throwable throwable) { + serverConn.getPlayer().handleConnectionException(serverConn.getServer(), throwable); + } + + public VelocityServer getServer() { + return server; + } + + @Override + public void disconnected() { + serverConn.getServer().removePlayer(serverConn.getPlayer()); + if (!serverConn.isGracefulDisconnect()) { + serverConn.getPlayer().handleConnectionException(serverConn.getServer(), Disconnect.create( + ConnectionMessages.UNEXPECTED_DISCONNECT)); } + } - @Override - public boolean handle(BossBar packet) { - if (packet.getAction() == BossBar.ADD) { - playerSessionHandler.getServerBossBars().add(packet.getUuid()); - } else if (packet.getAction() == BossBar.REMOVE) { - playerSessionHandler.getServerBossBars().remove(packet.getUuid()); - } - return false; // forward + private boolean canForwardPluginMessage(PluginMessage message) { + MinecraftConnection mc = serverConn.getConnection(); + if (mc == null) { + return false; } - - @Override - public boolean handle(PluginMessage packet) { - MinecraftConnection smc = serverConn.getConnection(); - if (smc == null) { - return true; - } - - if (!canForwardPluginMessage(packet)) { - return true; - } - - if (PluginMessageUtil.isMCBrand(packet)) { - serverConn.getPlayer().getConnection().write(PluginMessageUtil.rewriteMCBrand(packet)); - return true; - } - - if (!serverConn.hasCompletedJoin() && packet.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) { - if (!serverConn.isLegacyForge()) { - serverConn.setLegacyForge(true); - - // We must always reset the handshake before a modded connection is established if - // we haven't done so already. - serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket(); - } - - // Always forward these messages during login. - return false; - } - - ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel()); - if (id == null) { - return false; - } - - PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, packet.getData()); - server.getEventManager().fire(event) - .thenAcceptAsync(pme -> { - if (pme.getResult().isAllowed()) { - smc.write(packet); - } - }, smc.eventLoop()); - return true; - } - - @Override - public boolean handle(TabCompleteResponse packet) { - playerSessionHandler.handleTabCompleteResponse(packet); - return true; - } - - @Override - public boolean handle(PlayerListItem packet) { - serverConn.getPlayer().getTabList().processBackendPacket(packet); - return false; //Forward packet to player - } - - @Override - public void handleGeneric(MinecraftPacket packet) { - serverConn.getPlayer().getConnection().write(packet); - } - - @Override - public void handleUnknown(ByteBuf buf) { - serverConn.getPlayer().getConnection().write(buf.retain()); - } - - @Override - public void exception(Throwable throwable) { - serverConn.getPlayer().handleConnectionException(serverConn.getServer(), throwable); - } - - public VelocityServer getServer() { - return server; - } - - @Override - public void disconnected() { - serverConn.getServer().removePlayer(serverConn.getPlayer()); - if (!serverConn.isGracefulDisconnect()) { - serverConn.getPlayer().handleConnectionException(serverConn.getServer(), Disconnect.create( - ConnectionMessages.UNEXPECTED_DISCONNECT)); - } - } - - private boolean canForwardPluginMessage(PluginMessage message) { - MinecraftConnection mc = serverConn.getConnection(); - if (mc == null) { - return false; - } - boolean isMCOrFMLMessage; - 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 || playerSessionHandler.getClientPluginMsgChannels().contains(message.getChannel()) || - server.getChannelRegistrar().registered(message.getChannel()); + boolean isMCOrFMLMessage; + 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 || 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 f3234673b..d9d63d13e 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 @@ -13,156 +13,168 @@ import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.protocol.packet.Disconnect; +import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; +import com.velocitypowered.proxy.protocol.packet.SetCompression; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import net.kyori.text.TextComponent; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.concurrent.CompletableFuture; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import net.kyori.text.TextComponent; public class LoginSessionHandler implements MinecraftSessionHandler { - private final VelocityServer server; - private final VelocityServerConnection serverConn; - private final CompletableFuture resultFuture; - private boolean informationForwarded; - LoginSessionHandler(VelocityServer server, VelocityServerConnection serverConn, - CompletableFuture resultFuture) { - this.server = server; - this.serverConn = serverConn; - this.resultFuture = resultFuture; + private final VelocityServer server; + private final VelocityServerConnection serverConn; + private final CompletableFuture resultFuture; + private boolean informationForwarded; + + LoginSessionHandler(VelocityServer server, VelocityServerConnection serverConn, + CompletableFuture resultFuture) { + this.server = server; + this.serverConn = serverConn; + 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!"); + } + + @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)) { + LoginPluginResponse response = new LoginPluginResponse(); + response.setSuccess(true); + response.setId(packet.getId()); + response.setData(createForwardingData(configuration.getForwardingSecret(), + serverConn.getPlayer().getRemoteAddress().getHostString(), + serverConn.getPlayer().getProfile())); + mc.write(response); + informationForwarded = true; + } else { + // Don't understand + LoginPluginResponse response = new LoginPluginResponse(); + response.setSuccess(false); + response.setId(packet.getId()); + response.setData(Unpooled.EMPTY_BUFFER); + mc.write(response); + } + return true; + } + + @Override + public boolean handle(Disconnect packet) { + resultFuture.complete(ConnectionRequestResults.forDisconnect(packet)); + serverConn.disconnect(); + return true; + } + + @Override + public boolean handle(SetCompression packet) { + ensureMinecraftConnection().setCompressionThreshold(packet.getThreshold()); + return true; + } + + @Override + public boolean handle(ServerLoginSuccess packet) { + if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN + && !informationForwarded) { + resultFuture.complete(ConnectionRequestResults.forDisconnect( + TextComponent + .of("Your server did not send a forwarding request to the proxy. Is it set up correctly?"))); + serverConn.disconnect(); + return true; } - private MinecraftConnection ensureMinecraftConnection() { - MinecraftConnection mc = serverConn.getConnection(); - if (mc == null) { - throw new IllegalStateException("Not connected to backend server!"); - } - return mc; + // The player has been logged on to the backend server. + MinecraftConnection smc = ensureMinecraftConnection(); + smc.setState(StateRegistry.PLAY); + VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer(); + if (existingConnection == null) { + // Strap on the play session handler + serverConn.getPlayer().getConnection() + .setSessionHandler(new ClientPlaySessionHandler(server, serverConn.getPlayer())); + } else { + // The previous server connection should become obsolete. + // Before we remove it, if the server we are departing is modded, we must always reset the client state. + if (existingConnection.isLegacyForge()) { + serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket(); + } + existingConnection.disconnect(); } - @Override - public boolean handle(EncryptionRequest packet) { - throw new IllegalStateException("Backend server is online-mode!"); - } - - @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)) { - LoginPluginResponse response = new LoginPluginResponse(); - response.setSuccess(true); - response.setId(packet.getId()); - response.setData(createForwardingData(configuration.getForwardingSecret(), - serverConn.getPlayer().getRemoteAddress().getHostString(), - serverConn.getPlayer().getProfile())); - mc.write(response); - informationForwarded = true; - } else { - // Don't understand - LoginPluginResponse response = new LoginPluginResponse(); - response.setSuccess(false); - response.setId(packet.getId()); - response.setData(Unpooled.EMPTY_BUFFER); - mc.write(response); - } - return true; - } - - @Override - public boolean handle(Disconnect packet) { - resultFuture.complete(ConnectionRequestResults.forDisconnect(packet)); - serverConn.disconnect(); - return true; - } - - @Override - public boolean handle(SetCompression packet) { - ensureMinecraftConnection().setCompressionThreshold(packet.getThreshold()); - return true; - } - - @Override - public boolean handle(ServerLoginSuccess packet) { - if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && !informationForwarded) { - resultFuture.complete(ConnectionRequestResults.forDisconnect( - TextComponent.of("Your server did not send a forwarding request to the proxy. Is it set up correctly?"))); - serverConn.disconnect(); - return true; - } - - // The player has been logged on to the backend server. - MinecraftConnection smc = ensureMinecraftConnection(); - smc.setState(StateRegistry.PLAY); - VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer(); - if (existingConnection == null) { - // Strap on the play session handler - serverConn.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, serverConn.getPlayer())); - } else { - // The previous server connection should become obsolete. - // Before we remove it, if the server we are departing is modded, we must always reset the client state. - if (existingConnection.isLegacyForge()) { - serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket(); - } - existingConnection.disconnect(); - } - - smc.getChannel().config().setAutoRead(false); - server.getEventManager().fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer())) - .whenCompleteAsync((x, error) -> { - resultFuture.complete(ConnectionRequestResults.SUCCESSFUL); - smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn)); - serverConn.getPlayer().setConnectedServer(serverConn); - smc.getChannel().config().setAutoRead(true); - }, smc.eventLoop()); - return true; - } - - @Override - public void exception(Throwable throwable) { - resultFuture.completeExceptionally(throwable); - } - - @Override - public void disconnected() { - resultFuture.completeExceptionally(new IOException("Unexpectedly disconnected from remote server")); - } - - private static ByteBuf createForwardingData(byte[] hmacSecret, String address, GameProfile profile) { - ByteBuf dataToForward = Unpooled.buffer(); - ByteBuf finalData = Unpooled.buffer(); - try { - ProtocolUtils.writeVarInt(dataToForward, VelocityConstants.FORWARDING_VERSION); - ProtocolUtils.writeString(dataToForward, address); - ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid()); - ProtocolUtils.writeString(dataToForward, profile.getName()); - ProtocolUtils.writeProperties(dataToForward, profile.getProperties()); - - SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256"); - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(key); - mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes()); - byte[] sig = mac.doFinal(); - finalData.writeBytes(sig); - finalData.writeBytes(dataToForward); - return finalData; - } catch (InvalidKeyException e) { - finalData.release(); - throw new RuntimeException("Unable to authenticate data", e); - } catch (NoSuchAlgorithmException e) { - // Should never happen - finalData.release(); - throw new AssertionError(e); - } finally { - dataToForward.release(); - } + smc.getChannel().config().setAutoRead(false); + server.getEventManager() + .fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer())) + .whenCompleteAsync((x, error) -> { + resultFuture.complete(ConnectionRequestResults.SUCCESSFUL); + smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn)); + serverConn.getPlayer().setConnectedServer(serverConn); + smc.getChannel().config().setAutoRead(true); + }, smc.eventLoop()); + return true; + } + + @Override + public void exception(Throwable throwable) { + resultFuture.completeExceptionally(throwable); + } + + @Override + public void disconnected() { + resultFuture + .completeExceptionally(new IOException("Unexpectedly disconnected from remote server")); + } + + private static ByteBuf createForwardingData(byte[] hmacSecret, String address, + GameProfile profile) { + ByteBuf dataToForward = Unpooled.buffer(); + ByteBuf finalData = Unpooled.buffer(); + try { + ProtocolUtils.writeVarInt(dataToForward, VelocityConstants.FORWARDING_VERSION); + ProtocolUtils.writeString(dataToForward, address); + ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid()); + ProtocolUtils.writeString(dataToForward, profile.getName()); + ProtocolUtils.writeProperties(dataToForward, profile.getProperties()); + + SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(key); + mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes()); + byte[] sig = mac.doFinal(); + finalData.writeBytes(sig); + finalData.writeBytes(dataToForward); + return finalData; + } catch (InvalidKeyException e) { + finalData.release(); + throw new RuntimeException("Unable to authenticate data", e); + } catch (NoSuchAlgorithmException e) { + // Should never happen + finalData.release(); + throw new AssertionError(e); + } finally { + dataToForward.release(); } + } } 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 7abf631b3..1232fef6a 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,5 +1,13 @@ package com.velocitypowered.proxy.connection.backend; +import static com.velocitypowered.proxy.VelocityServer.GSON; +import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; +import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER; +import static com.velocitypowered.proxy.network.Connections.HANDLER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER; +import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT; + import com.google.common.base.Preconditions; import com.google.common.base.VerifyException; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; @@ -25,193 +33,196 @@ 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.*; +import org.checkerframework.checker.nullness.qual.Nullable; public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection { - private final VelocityRegisteredServer registeredServer; - private final ConnectedPlayer proxyPlayer; - private final VelocityServer server; - private @Nullable MinecraftConnection connection; - private boolean legacyForge = false; - private boolean hasCompletedJoin = false; - private boolean gracefulDisconnect = false; - private long lastPingId; - private long lastPingSent; - public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) { - this.registeredServer = registeredServer; - this.proxyPlayer = proxyPlayer; - this.server = server; + private final VelocityRegisteredServer registeredServer; + private final ConnectedPlayer proxyPlayer; + private final VelocityServer server; + private @Nullable MinecraftConnection connection; + private boolean legacyForge = false; + private boolean hasCompletedJoin = false; + private boolean gracefulDisconnect = false; + private long lastPingId; + private long lastPingSent; + + public VelocityServerConnection(VelocityRegisteredServer registeredServer, + ConnectedPlayer proxyPlayer, VelocityServer server) { + this.registeredServer = registeredServer; + this.proxyPlayer = proxyPlayer; + this.server = server; + } + + public CompletableFuture connect() { + CompletableFuture result = new CompletableFuture<>(); + server.initializeGenericBootstrap() + .handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline() + .addLast(READ_TIMEOUT, + new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), + TimeUnit.SECONDS)) + .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) + .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) + .addLast(MINECRAFT_DECODER, + new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND)) + .addLast(MINECRAFT_ENCODER, + new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND)); + + MinecraftConnection mc = new MinecraftConnection(ch, server); + mc.setState(StateRegistry.HANDSHAKE); + mc.setAssociation(VelocityServerConnection.this); + ch.pipeline().addLast(HANDLER, mc); + } + }) + .connect(registeredServer.getServerInfo().getAddress()) + .addListener((ChannelFutureListener) future -> { + 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(); + } else { + result.completeExceptionally(future.cause()); + } + }); + return result; + } + + private String createBungeeForwardingAddress() { + // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, + // separated by \0 (the null byte). In order, you send the original host, the player's IP, their + // UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang). + return registeredServer.getServerInfo().getAddress().getHostString() + "\0" + + proxyPlayer.getRemoteAddress().getHostString() + "\0" + + proxyPlayer.getProfile().getId() + "\0" + + GSON.toJson(proxyPlayer.getProfile().getProperties()); + } + + private void startHandshake() { + MinecraftConnection mc = connection; + if (mc == null) { + throw new IllegalStateException("No connection established!"); } - public CompletableFuture connect() { - CompletableFuture result = new CompletableFuture<>(); - server.initializeGenericBootstrap() - .handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline() - .addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS)) - .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) - .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) - .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND)) - .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND)); + PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode(); - MinecraftConnection mc = new MinecraftConnection(ch, server); - mc.setState(StateRegistry.HANDSHAKE); - mc.setAssociation(VelocityServerConnection.this); - ch.pipeline().addLast(HANDLER, mc); - } - }) - .connect(registeredServer.getServerInfo().getAddress()) - .addListener((ChannelFutureListener) future -> { - if (future.isSuccess()) { - connection = future.channel().pipeline().get(MinecraftConnection.class); + // Initiate a handshake. + Handshake handshake = new Handshake(); + handshake.setNextStatus(StateRegistry.LOGIN_ID); + handshake.setProtocolVersion(proxyPlayer.getConnection().getNextProtocolVersion()); + if (forwardingMode == PlayerInfoForwarding.LEGACY) { + handshake.setServerAddress(createBungeeForwardingAddress()); + } else if (proxyPlayer.getConnection().isLegacyForge()) { + handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0"); + } else { + handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString()); + } + handshake.setPort(registeredServer.getServerInfo().getAddress().getPort()); + mc.write(handshake); - // 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"); - } + int protocolVersion = proxyPlayer.getConnection().getNextProtocolVersion(); + mc.setProtocolVersion(protocolVersion); + mc.setState(StateRegistry.LOGIN); + mc.write(new ServerLogin(proxyPlayer.getUsername())); + } - // Kick off the connection process - connection.setSessionHandler(new LoginSessionHandler(server, VelocityServerConnection.this, result)); - startHandshake(); - } else { - result.completeExceptionally(future.cause()); - } - }); - return result; + @Nullable + public MinecraftConnection getConnection() { + return connection; + } + + @Override + public VelocityRegisteredServer getServer() { + return registeredServer; + } + + @Override + public ServerInfo getServerInfo() { + return registeredServer.getServerInfo(); + } + + @Override + public ConnectedPlayer getPlayer() { + return proxyPlayer; + } + + public void disconnect() { + if (connection != null) { + connection.close(); + connection = null; + gracefulDisconnect = true; + } + } + + @Override + public String toString() { + return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer + .getServerInfo().getName(); + } + + @Override + 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!"); } - private String createBungeeForwardingAddress() { - // BungeeCord IP forwarding is simply a special injection after the "address" in the handshake, - // separated by \0 (the null byte). In order, you send the original host, the player's IP, their - // UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang). - return registeredServer.getServerInfo().getAddress().getHostString() + "\0" + - proxyPlayer.getRemoteAddress().getHostString() + "\0" + - proxyPlayer.getProfile().getId() + "\0" + - GSON.toJson(proxyPlayer.getProfile().getProperties()); - } + PluginMessage message = new PluginMessage(); + message.setChannel(identifier.getId()); + message.setData(data); + mc.write(message); + return true; + } - private void startHandshake() { - MinecraftConnection mc = connection; - if (mc == null) { - throw new IllegalStateException("No connection established!"); - } + public boolean isLegacyForge() { + return legacyForge; + } - PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode(); + public void setLegacyForge(boolean modded) { + legacyForge = modded; + } - // Initiate a handshake. - Handshake handshake = new Handshake(); - handshake.setNextStatus(StateRegistry.LOGIN_ID); - handshake.setProtocolVersion(proxyPlayer.getConnection().getNextProtocolVersion()); - if (forwardingMode == PlayerInfoForwarding.LEGACY) { - handshake.setServerAddress(createBungeeForwardingAddress()); - } else if (proxyPlayer.getConnection().isLegacyForge()) { - handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0"); - } else { - handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString()); - } - handshake.setPort(registeredServer.getServerInfo().getAddress().getPort()); - mc.write(handshake); + public boolean hasCompletedJoin() { + return hasCompletedJoin; + } - int protocolVersion = proxyPlayer.getConnection().getNextProtocolVersion(); - mc.setProtocolVersion(protocolVersion); - mc.setState(StateRegistry.LOGIN); - mc.write(new ServerLogin(proxyPlayer.getUsername())); - } + public void setHasCompletedJoin(boolean hasCompletedJoin) { + this.hasCompletedJoin = hasCompletedJoin; + } - @Nullable - public MinecraftConnection getConnection() { - return connection; - } + public boolean isGracefulDisconnect() { + return gracefulDisconnect; + } - @Override - public VelocityRegisteredServer getServer() { - return registeredServer; - } + public long getLastPingId() { + return lastPingId; + } - @Override - public ServerInfo getServerInfo() { - return registeredServer.getServerInfo(); - } + public long getLastPingSent() { + return lastPingSent; + } - @Override - public ConnectedPlayer getPlayer() { - return proxyPlayer; - } + public void setLastPingId(long lastPingId) { + this.lastPingId = lastPingId; + this.lastPingSent = System.currentTimeMillis(); + } - public void disconnect() { - if (connection != null) { - connection.close(); - connection = null; - gracefulDisconnect = true; - } - } - - @Override - public String toString() { - return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName(); - } - - @Override - 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); - mc.write(message); - return true; - } - - public boolean isLegacyForge() { - return legacyForge; - } - - public void setLegacyForge(boolean modded) { - legacyForge = modded; - } - - public boolean hasCompletedJoin() { - return hasCompletedJoin; - } - - public void setHasCompletedJoin(boolean hasCompletedJoin) { - this.hasCompletedJoin = hasCompletedJoin; - } - - public boolean isGracefulDisconnect() { - return gracefulDisconnect; - } - - public long getLastPingId() { - return lastPingId; - } - - public long getLastPingSent() { - return lastPingSent; - } - - public void setLastPingId(long lastPingId) { - this.lastPingId = lastPingId; - this.lastPingSent = System.currentTimeMillis(); - } - - public void resetLastPingId() { - this.lastPingId = -1; - } + public void resetLastPingId() { + this.lastPingId = -1; + } } 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 5dc5e261b..db9a1df12 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 @@ -12,357 +12,388 @@ import com.velocitypowered.proxy.connection.forge.ForgeConstants; import com.velocitypowered.proxy.connection.forge.ForgeUtil; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; -import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.protocol.packet.BossBar; +import com.velocitypowered.proxy.protocol.packet.Chat; +import com.velocitypowered.proxy.protocol.packet.ClientSettings; +import com.velocitypowered.proxy.protocol.packet.JoinGame; +import com.velocitypowered.proxy.protocol.packet.KeepAlive; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.packet.Respawn; +import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; +import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; +import com.velocitypowered.proxy.protocol.packet.TitlePacket; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.util.ThrowableUtils; import io.netty.buffer.ByteBuf; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; 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.*; - /** - * Handles communication with the connected Minecraft client. This is effectively the primary nerve center that - * joins backend servers with players. + * Handles communication with the connected Minecraft client. This is effectively the primary nerve + * center that joins backend servers with players. */ public class ClientPlaySessionHandler implements MinecraftSessionHandler { - private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class); - private static final int MAX_PLUGIN_CHANNELS = 1024; - private final ConnectedPlayer player; - private boolean spawned = false; - private final List serverBossBars = new ArrayList<>(); - private final Set clientPluginMsgChannels = new HashSet<>(); - private final Queue loginPluginMessages = new ArrayDeque<>(); - private final VelocityServer server; - private @Nullable TabCompleteRequest outstandingTabComplete; + private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class); + private static final int MAX_PLUGIN_CHANNELS = 1024; - public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) { - this.player = player; - this.server = server; + private final ConnectedPlayer player; + private boolean spawned = false; + private final List serverBossBars = new ArrayList<>(); + private final Set clientPluginMsgChannels = new HashSet<>(); + private final Queue loginPluginMessages = new ArrayDeque<>(); + private final VelocityServer server; + private @Nullable TabCompleteRequest outstandingTabComplete; + + public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) { + this.player = player; + this.server = server; + } + + @Override + public void activated() { + PluginMessage register = PluginMessageUtil.constructChannelsPacket(player.getProtocolVersion(), + server.getChannelRegistrar().getModernChannelIds()); + player.getConnection().write(register); + } + + @Override + public boolean handle(KeepAlive packet) { + VelocityServerConnection serverConnection = player.getConnectedServer(); + if (serverConnection != null && packet.getRandomId() == serverConnection.getLastPingId()) { + MinecraftConnection smc = serverConnection.getConnection(); + if (smc != null) { + player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent()); + smc.write(packet); + serverConnection.resetLastPingId(); + } } + return true; + } - @Override - public void activated() { - PluginMessage register = PluginMessageUtil.constructChannelsPacket(player.getProtocolVersion(), server.getChannelRegistrar().getModernChannelIds()); - player.getConnection().write(register); - } + @Override + public boolean handle(ClientSettings packet) { + player.setPlayerSettings(packet); + return false; // will forward onto the handleGeneric below, which will write the packet to the remote server + } - @Override - public boolean handle(KeepAlive packet) { - VelocityServerConnection serverConnection = player.getConnectedServer(); - if (serverConnection != null && packet.getRandomId() == serverConnection.getLastPingId()) { - MinecraftConnection smc = serverConnection.getConnection(); - if (smc != null) { - player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent()); + @Override + public boolean handle(Chat packet) { + String msg = packet.getMessage(); + if (msg.startsWith("/")) { + try { + if (!server.getCommandManager().execute(player, msg.substring(1))) { + return false; + } + } catch (Exception e) { + logger + .info("Exception occurred while running command for {}", player.getProfile().getName(), + e); + player.sendMessage( + TextComponent.of("An error occurred while running this command.", TextColor.RED)); + return true; + } + } else { + VelocityServerConnection serverConnection = player.getConnectedServer(); + 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 -> { + 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.resetLastPingId(); + } } + }, smc.eventLoop()); + } + return true; + } + + @Override + public boolean handle(TabCompleteRequest packet) { + // Record the request so that the outstanding request can be augmented later. + if (!packet.isAssumeCommand() && packet.getCommand().startsWith("/")) { + int spacePos = packet.getCommand().indexOf(' '); + if (spacePos > 0) { + String cmd = packet.getCommand().substring(1, spacePos); + if (server.getCommandManager().hasCommand(cmd)) { + List suggestions = server.getCommandManager() + .offerSuggestions(player, packet.getCommand().substring(1)); + if (!suggestions.isEmpty()) { + TabCompleteResponse resp = new TabCompleteResponse(); + resp.getOffers().addAll(suggestions); + player.getConnection().write(resp); + return true; + } } - return true; + } } + outstandingTabComplete = packet; + return false; + } - @Override - public boolean handle(ClientSettings packet) { - player.setPlayerSettings(packet); - return false; // will forward onto the handleGeneric below, which will write the packet to the remote server - } + @Override + public boolean handle(PluginMessage packet) { + 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); + } + } - @Override - public boolean handle(Chat packet) { - String msg = packet.getMessage(); - if (msg.startsWith("/")) { - try { - if (!server.getCommandManager().execute(player, msg.substring(1))) { - return false; - } - } catch (Exception e) { - logger.info("Exception occurred while running command for {}", player.getProfile().getName(), e); - player.sendMessage(TextComponent.of("An error occurred while running this command.", TextColor.RED)); - return true; + if (!actuallyRegistered.isEmpty()) { + PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(backendConn + .getProtocolVersion(), actuallyRegistered); + backendConn.write(newRegisterPacket); + } + } else if (PluginMessageUtil.isMCUnregister(packet)) { + List channels = PluginMessageUtil.getChannels(packet); + clientPluginMsgChannels.removeAll(channels); + backendConn.write(packet); + } else if (PluginMessageUtil.isMCBrand(packet)) { + backendConn.write(PluginMessageUtil.rewriteMCBrand(packet)); + } 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 { - VelocityServerConnection serverConnection = player.getConnectedServer(); - 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 -> { - 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); - } - } - }, smc.eventLoop()); + // 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; - } - - @Override - public boolean handle(TabCompleteRequest packet) { - // Record the request so that the outstanding request can be augmented later. - if (!packet.isAssumeCommand() && packet.getCommand().startsWith("/")) { - int spacePos = packet.getCommand().indexOf(' '); - if (spacePos > 0) { - String cmd = packet.getCommand().substring(1, spacePos); - if (server.getCommandManager().hasCommand(cmd)) { - List suggestions = server.getCommandManager().offerSuggestions(player, packet.getCommand().substring(1)); - if (!suggestions.isEmpty()) { - TabCompleteResponse resp = new TabCompleteResponse(); - resp.getOffers().addAll(suggestions); - player.getConnection().write(resp); - return true; - } - } - } - } - outstandingTabComplete = packet; - return false; - } - - @Override - public boolean handle(PluginMessage packet) { - 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 (!actuallyRegistered.isEmpty()) { - PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(backendConn - .getProtocolVersion(), actuallyRegistered); - backendConn.write(newRegisterPacket); - } - } else if (PluginMessageUtil.isMCUnregister(packet)) { - List channels = PluginMessageUtil.getChannels(packet); - clientPluginMsgChannels.removeAll(channels); - backendConn.write(packet); - } else if (PluginMessageUtil.isMCBrand(packet)) { - backendConn.write(PluginMessageUtil.rewriteMCBrand(packet)); - } 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); - } - } else { - 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; - } - - @Override - public void handleGeneric(MinecraftPacket packet) { - VelocityServerConnection serverConnection = player.getConnectedServer(); - if (serverConnection == null) { - // No server connection yet, probably transitioning. - return; - } - - MinecraftConnection smc = serverConnection.getConnection(); - if (smc != null && serverConnection.hasCompletedJoin()) { - smc.write(packet); - } - } - - @Override - public void handleUnknown(ByteBuf buf) { - VelocityServerConnection serverConnection = player.getConnectedServer(); - if (serverConnection == null) { - // No server connection yet, probably transitioning. - return; - } - - MinecraftConnection smc = serverConnection.getConnection(); - if (smc != null && serverConnection.hasCompletedJoin()) { - smc.write(buf.retain()); - } - } - - @Override - public void disconnected() { - player.teardown(); - } - - @Override - public void exception(Throwable throwable) { - player.close(TextComponent.builder() - .content("An exception occurred in your connection: ") - .color(TextColor.RED) - .append(TextComponent.of(ThrowableUtils.briefDescription(throwable), TextColor.WHITE)) - .build()); - } - - @Override - public void writabilityChanged() { - VelocityServerConnection serverConn = player.getConnectedServer(); - if (serverConn != null) { - boolean writable = player.getConnection().getChannel().isWritable(); - MinecraftConnection smc = serverConn.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; - player.getConnection().delayedWrite(joinGame); - - // We have something special to do for legacy Forge servers - during first connection the FML handshake - // will transition to complete regardless. Thus, we need to ensure that a reset packet is ALWAYS sent on - // first switch. - // - // As we know that calling this branch only happens on first join, we set that if we are a Forge - // client that we must reset on the next switch. - // - // The call will handle if the player is not a Forge player appropriately. - player.getConnection().setCanSendLegacyFMLResetPacket(true); + } else { + ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel()); + if (id == null) { + backendConn.write(packet); } else { - // Clear tab list to avoid duplicate entries - player.getTabList().clearAll(); - - // In order to handle switching to another server, you will need to send three packets: - // - // - The join game packet from the backend server - // - A respawn packet with a different dimension - // - Another respawn with the correct dimension - // - // The two respawns with different dimensions are required, otherwise the client gets confused. - // - // Most notably, by having the client accept the join game packet, we can work around the need to perform - // entity ID rewrites, eliminating potential issues from rewriting packets and improving compatibility with - // mods. - player.getConnection().delayedWrite(joinGame); - int tempDim = joinGame.getDimension() == 0 ? -1 : 0; - player.getConnection().delayedWrite(new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); - player.getConnection().delayedWrite(new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); - } - - // Remove old boss bars. These don't get cleared when sending JoinGame so we need to track these. - for (UUID serverBossBar : serverBossBars) { - BossBar deletePacket = new BossBar(); - deletePacket.setUuid(serverBossBar); - deletePacket.setAction(BossBar.REMOVE); - player.getConnection().delayedWrite(deletePacket); - } - serverBossBars.clear(); - - // Tell the server about this client's plugin message channels. - int serverVersion = serverMc.getProtocolVersion(); - Collection toRegister = new HashSet<>(clientPluginMsgChannels); - if (serverVersion >= ProtocolConstants.MINECRAFT_1_13) { - toRegister.addAll(server.getChannelRegistrar().getModernChannelIds()); - } else { - toRegister.addAll(server.getChannelRegistrar().getIdsForLegacyConnections()); - } - if (!toRegister.isEmpty()) { - 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) { - serverMc.delayedWrite(pm); - } - - // Clear any title from the previous server. - player.getConnection().delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion())); - - // Flush everything - player.getConnection().flush(); - 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. - // - // The special case is if we log onto a Vanilla server as our first server, FML will treat this - // as complete and **will** need a reset packet sending at some point. We will handle this - // during initial player connection if the player is detected to be forge. - // - // This is why we use an if statement rather than the result of VelocityServerConnection#isLegacyForge() - // because we don't want to set it false if this is a first connection to a Vanilla server. - // - // See LoginSessionHandler#handle for where the counterpart to this method is - player.getConnection().setCanSendLegacyFMLResetPacket(true); + PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, + packet.getData()); + server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet), + backendConn.eventLoop()); } + } } - public List getServerBossBars() { - return serverBossBars; + return true; + } + + @Override + public void handleGeneric(MinecraftPacket packet) { + VelocityServerConnection serverConnection = player.getConnectedServer(); + if (serverConnection == null) { + // No server connection yet, probably transitioning. + return; } - public Set getClientPluginMsgChannels() { - return clientPluginMsgChannels; + MinecraftConnection smc = serverConnection.getConnection(); + if (smc != null && serverConnection.hasCompletedJoin()) { + smc.write(packet); + } + } + + @Override + public void handleUnknown(ByteBuf buf) { + VelocityServerConnection serverConnection = player.getConnectedServer(); + if (serverConnection == null) { + // No server connection yet, probably transitioning. + return; } - public void handleTabCompleteResponse(TabCompleteResponse response) { - if (outstandingTabComplete != null) { - if (!outstandingTabComplete.isAssumeCommand()) { - String command = outstandingTabComplete.getCommand().substring(1); - try { - 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); - } - outstandingTabComplete = null; - } - player.getConnection().write(response); + MinecraftConnection smc = serverConnection.getConnection(); + if (smc != null && serverConnection.hasCompletedJoin()) { + smc.write(buf.retain()); + } + } + + @Override + public void disconnected() { + player.teardown(); + } + + @Override + public void exception(Throwable throwable) { + player.close(TextComponent.builder() + .content("An exception occurred in your connection: ") + .color(TextColor.RED) + .append(TextComponent.of(ThrowableUtils.briefDescription(throwable), TextColor.WHITE)) + .build()); + } + + @Override + public void writabilityChanged() { + VelocityServerConnection serverConn = player.getConnectedServer(); + if (serverConn != null) { + boolean writable = player.getConnection().getChannel().isWritable(); + MinecraftConnection smc = serverConn.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; + player.getConnection().delayedWrite(joinGame); + + // We have something special to do for legacy Forge servers - during first connection the FML handshake + // will transition to complete regardless. Thus, we need to ensure that a reset packet is ALWAYS sent on + // first switch. + // + // As we know that calling this branch only happens on first join, we set that if we are a Forge + // client that we must reset on the next switch. + // + // The call will handle if the player is not a Forge player appropriately. + player.getConnection().setCanSendLegacyFMLResetPacket(true); + } else { + // Clear tab list to avoid duplicate entries + player.getTabList().clearAll(); + + // In order to handle switching to another server, you will need to send three packets: + // + // - The join game packet from the backend server + // - A respawn packet with a different dimension + // - Another respawn with the correct dimension + // + // The two respawns with different dimensions are required, otherwise the client gets confused. + // + // Most notably, by having the client accept the join game packet, we can work around the need to perform + // entity ID rewrites, eliminating potential issues from rewriting packets and improving compatibility with + // mods. + player.getConnection().delayedWrite(joinGame); + int tempDim = joinGame.getDimension() == 0 ? -1 : 0; + player.getConnection().delayedWrite( + new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), + joinGame.getLevelType())); + player.getConnection().delayedWrite( + new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), + joinGame.getLevelType())); + } + + // Remove old boss bars. These don't get cleared when sending JoinGame so we need to track these. + for (UUID serverBossBar : serverBossBars) { + BossBar deletePacket = new BossBar(); + deletePacket.setUuid(serverBossBar); + deletePacket.setAction(BossBar.REMOVE); + player.getConnection().delayedWrite(deletePacket); + } + serverBossBars.clear(); + + // Tell the server about this client's plugin message channels. + int serverVersion = serverMc.getProtocolVersion(); + Collection toRegister = new HashSet<>(clientPluginMsgChannels); + if (serverVersion >= ProtocolConstants.MINECRAFT_1_13) { + toRegister.addAll(server.getChannelRegistrar().getModernChannelIds()); + } else { + toRegister.addAll(server.getChannelRegistrar().getIdsForLegacyConnections()); + } + if (!toRegister.isEmpty()) { + 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) { + serverMc.delayedWrite(pm); + } + + // Clear any title from the previous server. + player.getConnection() + .delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion())); + + // Flush everything + player.getConnection().flush(); + 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. + // + // The special case is if we log onto a Vanilla server as our first server, FML will treat this + // as complete and **will** need a reset packet sending at some point. We will handle this + // during initial player connection if the player is detected to be forge. + // + // This is why we use an if statement rather than the result of VelocityServerConnection#isLegacyForge() + // because we don't want to set it false if this is a first connection to a Vanilla server. + // + // See LoginSessionHandler#handle for where the counterpart to this method is + player.getConnection().setCanSendLegacyFMLResetPacket(true); + } + } + + public List getServerBossBars() { + return serverBossBars; + } + + public Set getClientPluginMsgChannels() { + return clientPluginMsgChannels; + } + + public void handleTabCompleteResponse(TabCompleteResponse response) { + if (outstandingTabComplete != null) { + if (!outstandingTabComplete.isAssumeCommand()) { + String command = outstandingTabComplete.getCommand().substring(1); + try { + 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); } + outstandingTabComplete = null; + } + player.getConnection().write(response); } + } } 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 5881b7692..a0d349db8 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,58 +3,59 @@ 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 java.util.Locale; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Locale; - public class ClientSettingsWrapper implements PlayerSettings { - static final PlayerSettings DEFAULT = new ClientSettingsWrapper(new ClientSettings("en_US", (byte) 10, 0, true, (short)127, 1)); - - private final ClientSettings settings; - private final SkinParts parts; - private @Nullable Locale locale; - ClientSettingsWrapper(ClientSettings settings) { - this.settings = settings; - this.parts = new SkinParts((byte) settings.getSkinParts()); - } + static final PlayerSettings DEFAULT = new ClientSettingsWrapper( + new ClientSettings("en_US", (byte) 10, 0, true, (short) 127, 1)); - @Override - public Locale getLocale() { - if (locale == null) { - locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-")); - } - return locale; - } + private final ClientSettings settings; + private final SkinParts parts; + private @Nullable Locale locale; - @Override - public byte getViewDistance() { - return settings.getViewDistance(); - } + ClientSettingsWrapper(ClientSettings settings) { + this.settings = settings; + this.parts = new SkinParts((byte) settings.getSkinParts()); + } - @Override - public ChatMode getChatMode() { - int chat = settings.getChatVisibility(); - if (chat < 0 || chat > 2) { - return ChatMode.SHOWN; - } - return ChatMode.values()[chat]; + @Override + public Locale getLocale() { + if (locale == null) { + locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-")); } + return locale; + } - @Override - public boolean hasChatColors() { - return settings.isChatColors(); - } + @Override + public byte getViewDistance() { + return settings.getViewDistance(); + } - @Override - public SkinParts getSkinParts() { - return parts; + @Override + public ChatMode getChatMode() { + int chat = settings.getChatVisibility(); + if (chat < 0 || chat > 2) { + return ChatMode.SHOWN; } + return ChatMode.values()[chat]; + } + + @Override + public boolean hasChatColors() { + return settings.isChatColors(); + } + + @Override + public SkinParts getSkinParts() { + return parts; + } + + @Override + public MainHand getMainHand() { + return settings.getMainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT; + } + - @Override - public MainHand getMainHand() { - return settings.getMainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT; - } - - } 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 6e934d1ed..e29133861 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 @@ -30,10 +30,21 @@ import com.velocitypowered.proxy.connection.forge.ForgeConstants; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.protocol.ProtocolConstants; -import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.protocol.packet.Chat; +import com.velocitypowered.proxy.protocol.packet.ClientSettings; +import com.velocitypowered.proxy.protocol.packet.Disconnect; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.packet.TitlePacket; import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.tablist.VelocityTabList; import com.velocitypowered.proxy.util.ThrowableUtils; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; @@ -45,500 +56,516 @@ import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.net.InetSocketAddress; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { - private static final PlainComponentSerializer PASS_THRU_TRANSLATE = new PlainComponentSerializer(c -> "", TranslatableComponent::key); - static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; - private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class); + private static final PlainComponentSerializer PASS_THRU_TRANSLATE = new PlainComponentSerializer( + c -> "", TranslatableComponent::key); + static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; - private final MinecraftConnection connection; - private final @Nullable InetSocketAddress virtualHost; - private GameProfile profile; - private PermissionFunction permissionFunction; - private int tryIndex = 0; - private long ping = -1; - private @Nullable VelocityServerConnection connectedServer; - private @Nullable VelocityServerConnection connectionInFlight; - private @Nullable PlayerSettings settings; - private @Nullable ModInfo modInfo; - private final VelocityTabList tabList; - private final VelocityServer server; + private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class); - @MonotonicNonNull - private List serversToTry = null; + private final MinecraftConnection connection; + private final @Nullable InetSocketAddress virtualHost; + private GameProfile profile; + private PermissionFunction permissionFunction; + private int tryIndex = 0; + private long ping = -1; + private @Nullable VelocityServerConnection connectedServer; + private @Nullable VelocityServerConnection connectionInFlight; + private @Nullable PlayerSettings settings; + private @Nullable ModInfo modInfo; + private final VelocityTabList tabList; + private final VelocityServer server; - 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 = PermissionFunction.ALWAYS_UNDEFINED; + @MonotonicNonNull + private List serversToTry = null; + + 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 = PermissionFunction.ALWAYS_UNDEFINED; + } + + @Override + public String getUsername() { + return profile.getName(); + } + + @Override + public UUID getUniqueId() { + return profile.idAsUuid(); + } + + @Override + public Optional getCurrentServer() { + return Optional.ofNullable(connectedServer); + } + + public GameProfile getProfile() { + return profile; + } + + public MinecraftConnection getConnection() { + return connection; + } + + @Override + public long getPing() { + return this.ping; + } + + void setPing(long ping) { + this.ping = ping; + } + + public PlayerSettings getPlayerSettings() { + return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings; + } + + void setPlayerSettings(ClientSettings settings) { + ClientSettingsWrapper cs = new ClientSettingsWrapper(settings); + this.settings = cs; + server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, cs)); + } + + public Optional getModInfo() { + return Optional.ofNullable(modInfo); + } + + void setModInfo(ModInfo modInfo) { + this.modInfo = modInfo; + server.getEventManager().fireAndForget(new PlayerModInfoEvent(this, modInfo)); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return (InetSocketAddress) connection.getRemoteAddress(); + } + + @Override + public Optional getVirtualHost() { + return Optional.ofNullable(virtualHost); + } + + void setPermissionFunction(PermissionFunction permissionFunction) { + this.permissionFunction = permissionFunction; + } + + @Override + public boolean isActive() { + return connection.getChannel().isActive(); + } + + @Override + public int getProtocolVersion() { + return connection.getProtocolVersion(); + } + + @Override + public void sendMessage(Component component, MessagePosition position) { + Preconditions.checkNotNull(component, "component"); + Preconditions.checkNotNull(position, "position"); + + byte pos = (byte) position.ordinal(); + String json; + if (position == MessagePosition.ACTION_BAR) { + if (getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_11) { + // We can use the title packet instead. + TitlePacket pkt = new TitlePacket(); + pkt.setAction(TitlePacket.SET_ACTION_BAR); + pkt.setComponent(ComponentSerializers.JSON.serialize(component)); + connection.write(pkt); + return; + } else { + // Due to issues with action bar packets, we'll need to convert the text message into a legacy message + // and then inject the legacy text into a component... yuck! + JsonObject object = new JsonObject(); + object.addProperty("text", ComponentSerializers.LEGACY.serialize(component)); + json = VelocityServer.GSON.toJson(object); + } + } else { + json = ComponentSerializers.JSON.serialize(component); } - @Override - public String getUsername() { - return profile.getName(); + Chat chat = new Chat(); + chat.setType(pos); + chat.setMessage(json); + connection.write(chat); + } + + @Override + public ConnectionRequestBuilder createConnectionRequest(RegisteredServer server) { + return new ConnectionRequestBuilderImpl(server); + } + + @Override + public List getGameProfileProperties() { + return this.profile.getProperties(); + } + + @Override + public void setGameProfileProperties(List properties) { + Preconditions.checkNotNull(properties); + this.profile = new GameProfile(profile.getId(), profile.getName(), properties); + } + + @Override + public void setHeaderAndFooter(Component header, Component footer) { + tabList.setHeaderAndFooter(header, footer); + } + + @Override + public void clearHeaderAndFooter() { + tabList.clearHeaderAndFooter(); + } + + @Override + public VelocityTabList getTabList() { + return tabList; + } + + @Override + public void disconnect(Component reason) { + connection.closeWith(Disconnect.create(reason)); + } + + @Override + public void sendTitle(Title title) { + Preconditions.checkNotNull(title, "title"); + + if (title.equals(Titles.reset())) { + connection.write(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion())); + } else if (title.equals(Titles.hide())) { + connection.write(TitlePacket.hideForProtocolVersion(connection.getProtocolVersion())); + } else if (title instanceof TextTitle) { + TextTitle tt = (TextTitle) title; + + if (tt.isResetBeforeSend()) { + connection + .delayedWrite(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion())); + } + + Optional titleText = tt.getTitle(); + if (titleText.isPresent()) { + TitlePacket titlePkt = new TitlePacket(); + titlePkt.setAction(TitlePacket.SET_TITLE); + titlePkt.setComponent(ComponentSerializers.JSON.serialize(titleText.get())); + connection.delayedWrite(titlePkt); + } + + Optional subtitleText = tt.getSubtitle(); + if (subtitleText.isPresent()) { + TitlePacket titlePkt = new TitlePacket(); + titlePkt.setAction(TitlePacket.SET_SUBTITLE); + titlePkt.setComponent(ComponentSerializers.JSON.serialize(subtitleText.get())); + connection.delayedWrite(titlePkt); + } + + if (tt.areTimesSet()) { + TitlePacket timesPkt = TitlePacket.timesForProtocolVersion(connection.getProtocolVersion()); + timesPkt.setFadeIn(tt.getFadeIn()); + timesPkt.setStay(tt.getStay()); + timesPkt.setFadeOut(tt.getFadeOut()); + connection.delayedWrite(timesPkt); + } + connection.flush(); + } else { + throw new IllegalArgumentException("Unknown title class " + title.getClass().getName()); } - @Override - public UUID getUniqueId() { - return profile.idAsUuid(); + } + + @Nullable + public VelocityServerConnection getConnectedServer() { + return connectedServer; + } + + public void handleConnectionException(RegisteredServer server, Throwable throwable) { + if (throwable == null) { + throw new NullPointerException("throwable"); } - @Override - public Optional getCurrentServer() { - return Optional.ofNullable(connectedServer); + Throwable wrapped = throwable; + if (throwable instanceof CompletionException) { + Throwable cause = throwable.getCause(); + if (cause != null) { + wrapped = cause; + } } - - public GameProfile getProfile() { - return profile; + String error = ThrowableUtils.briefDescription(wrapped); + String userMessage; + if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { + userMessage = "Exception in server " + server.getServerInfo().getName(); + } else { + logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), + wrapped); + userMessage = "Can't connect to server " + server.getServerInfo().getName(); } + handleConnectionException(server, null, TextComponent.builder() + .content(userMessage + ": ") + .color(TextColor.RED) + .append(TextComponent.of(error, TextColor.WHITE)) + .build()); + } - public MinecraftConnection getConnection() { - return connection; + public void handleConnectionException(RegisteredServer server, Disconnect disconnect) { + Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason()); + String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); + if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { + logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), + plainTextReason); + handleConnectionException(server, disconnectReason, TextComponent.builder() + .content("Kicked from " + server.getServerInfo().getName() + ": ") + .color(TextColor.RED) + .append(disconnectReason) + .build()); + } else { + logger.error("{}: disconnected while connecting to {}: {}", this, + server.getServerInfo().getName(), plainTextReason); + handleConnectionException(server, disconnectReason, TextComponent.builder() + .content("Can't connect to server " + server.getServerInfo().getName() + ": ") + .color(TextColor.RED) + .append(disconnectReason) + .build()); } + } - @Override - public long getPing() { - return this.ping; - } - - void setPing(long ping) { - this.ping = ping; - } - - public PlayerSettings getPlayerSettings() { - return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings; - } - - void setPlayerSettings(ClientSettings settings) { - ClientSettingsWrapper cs = new ClientSettingsWrapper(settings); - this.settings = cs; - server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, cs)); - } - - public Optional getModInfo() { - return Optional.ofNullable(modInfo); - } - - void setModInfo(ModInfo modInfo) { - this.modInfo = modInfo; - server.getEventManager().fireAndForget(new PlayerModInfoEvent(this, modInfo)); - } - - @Override - public InetSocketAddress getRemoteAddress() { - return (InetSocketAddress) connection.getRemoteAddress(); - } - - @Override - public Optional getVirtualHost() { - return Optional.ofNullable(virtualHost); - } - - void setPermissionFunction(PermissionFunction permissionFunction) { - this.permissionFunction = permissionFunction; - } - - @Override - public boolean isActive() { - return connection.getChannel().isActive(); - } - - @Override - public int getProtocolVersion() { - return connection.getProtocolVersion(); - } - - @Override - public void sendMessage(Component component, MessagePosition position) { - Preconditions.checkNotNull(component, "component"); - Preconditions.checkNotNull(position, "position"); - - byte pos = (byte) position.ordinal(); - String json; - if (position == MessagePosition.ACTION_BAR) { - if (getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_11) { - // We can use the title packet instead. - TitlePacket pkt = new TitlePacket(); - pkt.setAction(TitlePacket.SET_ACTION_BAR); - pkt.setComponent(ComponentSerializers.JSON.serialize(component)); - connection.write(pkt); - return; - } else { - // Due to issues with action bar packets, we'll need to convert the text message into a legacy message - // and then inject the legacy text into a component... yuck! - JsonObject object = new JsonObject(); - object.addProperty("text", ComponentSerializers.LEGACY.serialize(component)); - json = VelocityServer.GSON.toJson(object); - } - } else { - json = ComponentSerializers.JSON.serialize(component); - } - - Chat chat = new Chat(); - chat.setType(pos); - chat.setMessage(json); - connection.write(chat); - } - - @Override - public ConnectionRequestBuilder createConnectionRequest(RegisteredServer server) { - return new ConnectionRequestBuilderImpl(server); - } - - @Override - public List getGameProfileProperties() { - return this.profile.getProperties(); - } - - @Override - public void setGameProfileProperties(List properties) { - Preconditions.checkNotNull(properties); - this.profile = new GameProfile(profile.getId(), profile.getName(), properties); - } - - @Override - public void setHeaderAndFooter(Component header, Component footer) { - tabList.setHeaderAndFooter(header, footer); - } - - @Override - public void clearHeaderAndFooter() { - tabList.clearHeaderAndFooter(); - } - - @Override - public VelocityTabList getTabList() { - return tabList; - } - - @Override - public void disconnect(Component reason) { - connection.closeWith(Disconnect.create(reason)); - } - - @Override - public void sendTitle(Title title) { - Preconditions.checkNotNull(title, "title"); - - if (title.equals(Titles.reset())) { - connection.write(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion())); - } else if (title.equals(Titles.hide())) { - connection.write(TitlePacket.hideForProtocolVersion(connection.getProtocolVersion())); - } else if (title instanceof TextTitle) { - TextTitle tt = (TextTitle) title; - - if (tt.isResetBeforeSend()) { - connection.delayedWrite(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion())); - } - - Optional titleText = tt.getTitle(); - if (titleText.isPresent()) { - TitlePacket titlePkt = new TitlePacket(); - titlePkt.setAction(TitlePacket.SET_TITLE); - titlePkt.setComponent(ComponentSerializers.JSON.serialize(titleText.get())); - connection.delayedWrite(titlePkt); - } - - Optional subtitleText = tt.getSubtitle(); - if (subtitleText.isPresent()) { - TitlePacket titlePkt = new TitlePacket(); - titlePkt.setAction(TitlePacket.SET_SUBTITLE); - titlePkt.setComponent(ComponentSerializers.JSON.serialize(subtitleText.get())); - connection.delayedWrite(titlePkt); - } - - if (tt.areTimesSet()) { - TitlePacket timesPkt = TitlePacket.timesForProtocolVersion(connection.getProtocolVersion()); - timesPkt.setFadeIn(tt.getFadeIn()); - timesPkt.setStay(tt.getStay()); - timesPkt.setFadeOut(tt.getFadeOut()); - connection.delayedWrite(timesPkt); - } - connection.flush(); - } else { - throw new IllegalArgumentException("Unknown title class " + title.getClass().getName()); - } - - } - - @Nullable - public VelocityServerConnection getConnectedServer() { - return connectedServer; - } - - public void handleConnectionException(RegisteredServer server, Throwable throwable) { - 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())) { - userMessage = "Exception in server " + server.getServerInfo().getName(); - } else { - logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), wrapped); - userMessage = "Can't connect to server " + server.getServerInfo().getName(); - } - handleConnectionException(server, null, TextComponent.builder() - .content(userMessage + ": ") - .color(TextColor.RED) - .append(TextComponent.of(error, TextColor.WHITE)) - .build()); - } - - public void handleConnectionException(RegisteredServer server, Disconnect disconnect) { - Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason()); - String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); - if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) { - logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), plainTextReason); - handleConnectionException(server, disconnectReason, TextComponent.builder() - .content("Kicked from " + server.getServerInfo().getName() + ": ") - .color(TextColor.RED) - .append(disconnectReason) - .build()); - } else { - logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason); - handleConnectionException(server, disconnectReason, TextComponent.builder() - .content("Can't connect to server " + server.getServerInfo().getName() + ": ") - .color(TextColor.RED) - .append(disconnectReason) - .build()); - } - } - - private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) { - boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo()); - connectionInFlight = null; - if (connectedServer == null) { - // The player isn't yet connected to a server. - Optional nextServer = getNextServerToTry(); - if (nextServer.isPresent()) { - createConnectionRequest(nextServer.get()).fireAndForget(); - } else { + private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, + Component friendlyReason) { + boolean alreadyConnected = + connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo()); + connectionInFlight = null; + if (connectedServer == null) { + // The player isn't yet connected to a server. + Optional nextServer = getNextServerToTry(); + if (nextServer.isPresent()) { + createConnectionRequest(nextServer.get()).fireAndForget(); + } else { + connection.closeWith(Disconnect.create(friendlyReason)); + } + } else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) { + // Already connected to the server being disconnected from. + if (kickReason != null) { + server.getEventManager().fire( + new KickedFromServerEvent(this, rs, kickReason, !alreadyConnected, friendlyReason)) + .thenAcceptAsync(event -> { + if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) { + KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event + .getResult(); + connection.closeWith(Disconnect.create(res.getReason())); + } else if (event.getResult() instanceof KickedFromServerEvent.RedirectPlayer) { + KickedFromServerEvent.RedirectPlayer res = (KickedFromServerEvent.RedirectPlayer) event + .getResult(); + createConnectionRequest(res.getServer()).fireAndForget(); + } else { + // In case someone gets creative, assume we want to disconnect the player. connection.closeWith(Disconnect.create(friendlyReason)); + } + }, connection.eventLoop()); + } else { + connection.closeWith(Disconnect.create(friendlyReason)); + } + } else { + connection.write(Chat.createClientbound(friendlyReason)); + } + } + + Optional getNextServerToTry() { + if (serversToTry == null) { + String virtualHostStr = getVirtualHost().map(InetSocketAddress::getHostString).orElse(""); + serversToTry = server.getConfiguration().getForcedHosts() + .getOrDefault(virtualHostStr, Collections.emptyList()); + } + + if (serversToTry.isEmpty()) { + serversToTry = server.getConfiguration().getAttemptConnectionOrder(); + } + + if (tryIndex >= serversToTry.size()) { + return Optional.empty(); + } + + String toTryName = serversToTry.get(tryIndex); + tryIndex++; + return server.getServer(toTryName); + } + + private Optional checkServer(RegisteredServer server) { + Preconditions + .checkState(server instanceof VelocityRegisteredServer, "Not a valid Velocity server."); + if (connectionInFlight != null) { + return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS); + } + if (connectedServer != null && connectedServer.getServer().equals(server)) { + return Optional.of(ConnectionRequestBuilder.Status.ALREADY_CONNECTED); + } + return Optional.empty(); + } + + private CompletableFuture connect( + ConnectionRequestBuilderImpl request) { + Optional initialCheck = checkServer(request.getServer()); + if (initialCheck.isPresent()) { + return CompletableFuture + .completedFuture(ConnectionRequestResults.plainResult(initialCheck.get())); + } + + // Otherwise, initiate the connection. + ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer()); + return server.getEventManager().fire(event) + .thenCompose(newEvent -> { + Optional connectTo = newEvent.getResult().getServer(); + if (!connectTo.isPresent()) { + return CompletableFuture.completedFuture( + ConnectionRequestResults + .plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED) + ); + } + + RegisteredServer rs = connectTo.get(); + Optional lastCheck = checkServer(rs); + if (lastCheck.isPresent()) { + return CompletableFuture + .completedFuture(ConnectionRequestResults.plainResult(lastCheck.get())); + } + return new VelocityServerConnection((VelocityRegisteredServer) rs, this, server) + .connect(); + }); + } + + public void setConnectedServer(VelocityServerConnection serverConnection) { + VelocityServerConnection oldConnection = this.connectedServer; + if (oldConnection != null && !serverConnection.getServerInfo() + .equals(oldConnection.getServerInfo())) { + this.tryIndex = 0; + } + if (serverConnection == connectionInFlight) { + connectionInFlight = null; + } + this.connectedServer = serverConnection; + } + + public void sendLegacyForgeHandshakeResetPacket() { + if (connection.canSendLegacyFMLResetPacket()) { + connection.write(ForgeConstants.resetPacket()); + connection.setCanSendLegacyFMLResetPacket(false); + } + } + + public void close(TextComponent reason) { + 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(); + } + if (connectedServer != null) { + connectedServer.disconnect(); + } + server.unregisterConnection(this); + server.getEventManager().fireAndForget(new DisconnectEvent(this)); + } + + @Override + public String toString() { + return "[connected player] " + profile.getName() + " (" + getRemoteAddress() + ")"; + } + + @Override + public Tristate getPermissionValue(String permission) { + return permissionFunction.getPermissionValue(permission); + } + + @Override + public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + Preconditions.checkNotNull(identifier, "identifier"); + Preconditions.checkNotNull(data, "data"); + PluginMessage message = new PluginMessage(); + message.setChannel(identifier.getId()); + message.setData(data); + connection.write(message); + return true; + } + + @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"); + ensureBackendConnection().write(Chat.createServerbound(input)); + } + + private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { + + private final RegisteredServer server; + + ConnectionRequestBuilderImpl(RegisteredServer server) { + this.server = Preconditions.checkNotNull(server, "info"); + } + + @Override + public RegisteredServer getServer() { + return server; + } + + @Override + public CompletableFuture connect() { + return ConnectedPlayer.this.connect(this); + } + + @Override + public CompletableFuture connectWithIndication() { + return connect() + .whenCompleteAsync((status, throwable) -> { + if (throwable != null) { + handleConnectionException(server, throwable); + return; } - } else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) { - // Already connected to the server being disconnected from. - if (kickReason != null) { - server.getEventManager().fire(new KickedFromServerEvent(this, rs, kickReason, !alreadyConnected, friendlyReason)) - .thenAcceptAsync(event -> { - if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) { - KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult(); - connection.closeWith(Disconnect.create(res.getReason())); - } else if (event.getResult() instanceof KickedFromServerEvent.RedirectPlayer) { - KickedFromServerEvent.RedirectPlayer res = (KickedFromServerEvent.RedirectPlayer) event.getResult(); - createConnectionRequest(res.getServer()).fireAndForget(); - } else { - // In case someone gets creative, assume we want to disconnect the player. - connection.closeWith(Disconnect.create(friendlyReason)); - } - }, connection.eventLoop()); - } else { - connection.closeWith(Disconnect.create(friendlyReason)); + + switch (status.getStatus()) { + case ALREADY_CONNECTED: + sendMessage(ConnectionMessages.ALREADY_CONNECTED); + break; + case CONNECTION_IN_PROGRESS: + sendMessage(ConnectionMessages.IN_PROGRESS); + break; + case CONNECTION_CANCELLED: + // Ignored; the plugin probably already handled this. + break; + case SERVER_DISCONNECTED: + handleConnectionException(server, Disconnect.create(status.getReason() + .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR))); + break; + default: + // The only remaining value is successful (no need to do anything!) + break; } - } else { - connection.write(Chat.createClientbound(friendlyReason)); - } - } - - Optional getNextServerToTry() { - if (serversToTry == null) { - String virtualHostStr = getVirtualHost().map(InetSocketAddress::getHostString).orElse(""); - serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(virtualHostStr, Collections.emptyList()); - } - - if (serversToTry.isEmpty()) { - serversToTry = server.getConfiguration().getAttemptConnectionOrder(); - } - - if (tryIndex >= serversToTry.size()) { - return Optional.empty(); - } - - String toTryName = serversToTry.get(tryIndex); - tryIndex++; - return server.getServer(toTryName); - } - - private Optional checkServer(RegisteredServer server) { - Preconditions.checkState(server instanceof VelocityRegisteredServer, "Not a valid Velocity server."); - if (connectionInFlight != null) { - return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS); - } - if (connectedServer != null && connectedServer.getServer().equals(server)) { - return Optional.of(ConnectionRequestBuilder.Status.ALREADY_CONNECTED); - } - return Optional.empty(); - } - - private CompletableFuture connect(ConnectionRequestBuilderImpl request) { - Optional initialCheck = checkServer(request.getServer()); - if (initialCheck.isPresent()) { - return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(initialCheck.get())); - } - - // Otherwise, initiate the connection. - ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer()); - return server.getEventManager().fire(event) - .thenCompose(newEvent -> { - Optional connectTo = newEvent.getResult().getServer(); - if (!connectTo.isPresent()) { - return CompletableFuture.completedFuture( - ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED) - ); - } - - RegisteredServer rs = connectTo.get(); - Optional lastCheck = checkServer(rs); - if (lastCheck.isPresent()) { - return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(lastCheck.get())); - } - return new VelocityServerConnection((VelocityRegisteredServer) rs, this, server).connect(); - }); - } - - public void setConnectedServer(VelocityServerConnection serverConnection) { - VelocityServerConnection oldConnection = this.connectedServer; - if (oldConnection != null && !serverConnection.getServerInfo().equals(oldConnection.getServerInfo())) { - this.tryIndex = 0; - } - if (serverConnection == connectionInFlight) { - connectionInFlight = null; - } - this.connectedServer = serverConnection; - } - - public void sendLegacyForgeHandshakeResetPacket() { - if (connection.canSendLegacyFMLResetPacket()) { - connection.write(ForgeConstants.resetPacket()); - connection.setCanSendLegacyFMLResetPacket(false); - } - } - - public void close(TextComponent reason) { - 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(); - } - if (connectedServer != null) { - connectedServer.disconnect(); - } - server.unregisterConnection(this); - server.getEventManager().fireAndForget(new DisconnectEvent(this)); + }, connection.eventLoop()) + .thenApply(Result::isSuccessful); } @Override - public String toString() { - return "[connected player] " + profile.getName() + " (" + getRemoteAddress() + ")"; - } - - @Override - public Tristate getPermissionValue(String permission) { - return permissionFunction.getPermissionValue(permission); - } - - @Override - public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { - Preconditions.checkNotNull(identifier, "identifier"); - Preconditions.checkNotNull(data, "data"); - PluginMessage message = new PluginMessage(); - message.setChannel(identifier.getId()); - message.setData(data); - connection.write(message); - return true; - } - - @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"); - ensureBackendConnection().write(Chat.createServerbound(input)); - } - - private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { - private final RegisteredServer server; - - ConnectionRequestBuilderImpl(RegisteredServer server) { - this.server = Preconditions.checkNotNull(server, "info"); - } - - @Override - public RegisteredServer getServer() { - return server; - } - - @Override - public CompletableFuture connect() { - return ConnectedPlayer.this.connect(this); - } - - @Override - public CompletableFuture connectWithIndication() { - return connect() - .whenCompleteAsync((status, throwable) -> { - if (throwable != null) { - handleConnectionException(server, throwable); - return; - } - - switch (status.getStatus()) { - case ALREADY_CONNECTED: - sendMessage(ConnectionMessages.ALREADY_CONNECTED); - break; - case CONNECTION_IN_PROGRESS: - sendMessage(ConnectionMessages.IN_PROGRESS); - break; - case CONNECTION_CANCELLED: - // Ignored; the plugin probably already handled this. - break; - case SERVER_DISCONNECTED: - handleConnectionException(server, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR))); - break; - default: - // The only remaining value is successful (no need to do anything!) - break; - } - }, connection.eventLoop()) - .thenApply(Result::isSuccessful); - } - - @Override - public void fireAndForget() { - connectWithIndication(); - } + public void fireAndForget() { + connectWithIndication(); } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java index aa3efa01d..da3a13259 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java @@ -14,144 +14,159 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.protocol.packet.Disconnect; +import com.velocitypowered.proxy.protocol.packet.Handshake; +import com.velocitypowered.proxy.protocol.packet.LegacyDisconnect; +import com.velocitypowered.proxy.protocol.packet.LegacyHandshake; +import com.velocitypowered.proxy.protocol.packet.LegacyPing; +import com.velocitypowered.proxy.protocol.packet.LegacyPingResponse; import io.netty.buffer.ByteBuf; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Optional; import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; import net.kyori.text.format.TextColor; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.util.Optional; - public class HandshakeSessionHandler implements MinecraftSessionHandler { + + private final MinecraftConnection connection; + private final VelocityServer server; + + public HandshakeSessionHandler(MinecraftConnection connection, VelocityServer server) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + this.server = Preconditions.checkNotNull(server, "server"); + } + + @Override + public boolean handle(LegacyPing packet) { + connection.setProtocolVersion(ProtocolConstants.LEGACY); + VelocityConfiguration configuration = server.getConfiguration(); + ServerPing ping = new ServerPing( + new ServerPing.Version(ProtocolConstants.MAXIMUM_GENERIC_VERSION, + "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), + new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), + ImmutableList.of()), + configuration.getMotdComponent(), + null, + null + ); + ProxyPingEvent event = new ProxyPingEvent(new LegacyInboundConnection(connection), ping); + server.getEventManager().fire(event) + .thenRunAsync(() -> { + // The disconnect packet is the same as the server response one. + LegacyPingResponse response = LegacyPingResponse.from(event.getPing()); + connection.closeWith(LegacyDisconnect.fromPingResponse(response)); + }, connection.eventLoop()); + return true; + } + + @Override + public boolean handle(LegacyHandshake packet) { + connection.closeWith(LegacyDisconnect + .from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED))); + return true; + } + + @Override + public boolean handle(Handshake handshake) { + InitialInboundConnection ic = new InitialInboundConnection(connection, + cleanVhost(handshake.getServerAddress()), handshake); + switch (handshake.getNextStatus()) { + case StateRegistry.STATUS_ID: + connection.setState(StateRegistry.STATUS); + connection.setProtocolVersion(handshake.getProtocolVersion()); + connection.setSessionHandler(new StatusSessionHandler(server, connection, ic)); + return true; + case StateRegistry.LOGIN_ID: + connection.setState(StateRegistry.LOGIN); + connection.setProtocolVersion(handshake.getProtocolVersion()); + + if (!ProtocolConstants.isSupported(handshake.getProtocolVersion())) { + connection.closeWith(Disconnect + .create(TranslatableComponent.of("multiplayer.disconnect.outdated_client"))); + return true; + } + + InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress(); + if (!server.getIpAttemptLimiter().attempt(address)) { + connection.closeWith( + Disconnect.create(TextComponent.of("You are logging in too fast, try again later."))); + return true; + } + + // Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13) and store that in the connection + boolean isForge = handshake.getServerAddress().endsWith("\0FML\0"); + connection.setLegacyForge(isForge); + + // Make sure legacy forwarding is not in use on this connection. Make sure that we do _not_ reject Forge + if (handshake.getServerAddress().contains("\0") && !isForge) { + connection.closeWith(Disconnect + .create(TextComponent.of("Running Velocity behind Velocity is unsupported."))); + return true; + } + + // If the proxy is configured for modern forwarding, we must deny connections from 1.12.2 and lower, + // otherwise IP information will never get forwarded. + if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN + && handshake.getProtocolVersion() < + ProtocolConstants.MINECRAFT_1_13) { + connection.closeWith(Disconnect + .create(TextComponent.of("This server is only compatible with 1.13 and above."))); + return true; + } + + server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic)); + connection.setSessionHandler(new LoginSessionHandler(server, connection, ic)); + return true; + default: + throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus()); + } + } + + private String cleanVhost(String hostname) { + int zeroIdx = hostname.indexOf('\0'); + return zeroIdx == -1 ? hostname : hostname.substring(0, zeroIdx); + } + + @Override + public void handleGeneric(MinecraftPacket packet) { + // Unknown packet received. Better to close the connection. + connection.close(); + } + + @Override + public void handleUnknown(ByteBuf buf) { + // Unknown packet received. Better to close the connection. + connection.close(); + } + + private static class LegacyInboundConnection implements InboundConnection { + private final MinecraftConnection connection; - private final VelocityServer server; - public HandshakeSessionHandler(MinecraftConnection connection, VelocityServer server) { - this.connection = Preconditions.checkNotNull(connection, "connection"); - this.server = Preconditions.checkNotNull(server, "server"); + private LegacyInboundConnection(MinecraftConnection connection) { + this.connection = connection; } @Override - public boolean handle(LegacyPing packet) { - connection.setProtocolVersion(ProtocolConstants.LEGACY); - VelocityConfiguration configuration = server.getConfiguration(); - ServerPing ping = new ServerPing( - new ServerPing.Version(ProtocolConstants.MAXIMUM_GENERIC_VERSION, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), - new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()), - configuration.getMotdComponent(), - null, - null - ); - ProxyPingEvent event = new ProxyPingEvent(new LegacyInboundConnection(connection), ping); - server.getEventManager().fire(event) - .thenRunAsync(() -> { - // The disconnect packet is the same as the server response one. - LegacyPingResponse response = LegacyPingResponse.from(event.getPing()); - connection.closeWith(LegacyDisconnect.fromPingResponse(response)); - }, connection.eventLoop()); - return true; + public InetSocketAddress getRemoteAddress() { + return (InetSocketAddress) connection.getRemoteAddress(); } @Override - public boolean handle(LegacyHandshake packet) { - connection.closeWith(LegacyDisconnect.from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED))); - return true; + public Optional getVirtualHost() { + return Optional.empty(); } @Override - public boolean handle(Handshake handshake) { - InitialInboundConnection ic = new InitialInboundConnection(connection, cleanVhost(handshake.getServerAddress()), handshake); - switch (handshake.getNextStatus()) { - case StateRegistry.STATUS_ID: - connection.setState(StateRegistry.STATUS); - connection.setProtocolVersion(handshake.getProtocolVersion()); - connection.setSessionHandler(new StatusSessionHandler(server, connection, ic)); - return true; - case StateRegistry.LOGIN_ID: - connection.setState(StateRegistry.LOGIN); - connection.setProtocolVersion(handshake.getProtocolVersion()); - - if (!ProtocolConstants.isSupported(handshake.getProtocolVersion())) { - connection.closeWith(Disconnect.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client"))); - return true; - } - - InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress(); - if (!server.getIpAttemptLimiter().attempt(address)) { - connection.closeWith(Disconnect.create(TextComponent.of("You are logging in too fast, try again later."))); - return true; - } - - // Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13) and store that in the connection - boolean isForge = handshake.getServerAddress().endsWith("\0FML\0"); - connection.setLegacyForge(isForge); - - // Make sure legacy forwarding is not in use on this connection. Make sure that we do _not_ reject Forge - if (handshake.getServerAddress().contains("\0") && !isForge) { - connection.closeWith(Disconnect.create(TextComponent.of("Running Velocity behind Velocity is unsupported."))); - return true; - } - - // If the proxy is configured for modern forwarding, we must deny connections from 1.12.2 and lower, - // otherwise IP information will never get forwarded. - if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && handshake.getProtocolVersion() < - ProtocolConstants.MINECRAFT_1_13) { - connection.closeWith(Disconnect.create(TextComponent.of("This server is only compatible with 1.13 and above."))); - return true; - } - - server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic)); - connection.setSessionHandler(new LoginSessionHandler(server, connection, ic)); - return true; - default: - throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus()); - } - } - - private String cleanVhost(String hostname) { - int zeroIdx = hostname.indexOf('\0'); - return zeroIdx == -1 ? hostname : hostname.substring(0, zeroIdx); + public boolean isActive() { + return !connection.isClosed(); } @Override - public void handleGeneric(MinecraftPacket packet) { - // Unknown packet received. Better to close the connection. - connection.close(); - } - - @Override - public void handleUnknown(ByteBuf buf) { - // Unknown packet received. Better to close the connection. - connection.close(); - } - - private static class LegacyInboundConnection implements InboundConnection { - private final MinecraftConnection connection; - - private LegacyInboundConnection(MinecraftConnection connection) { - this.connection = connection; - } - - @Override - public InetSocketAddress getRemoteAddress() { - return (InetSocketAddress) connection.getRemoteAddress(); - } - - @Override - public Optional getVirtualHost() { - return Optional.empty(); - } - - @Override - public boolean isActive() { - return !connection.isClosed(); - } - - @Override - public int getProtocolVersion() { - return 0; - } + public int getProtocolVersion() { + return 0; } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java index 6b9a7750f..58068d697 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialConnectSessionHandler.java @@ -4,20 +4,21 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; public class InitialConnectSessionHandler implements MinecraftSessionHandler { - private final ConnectedPlayer player; - InitialConnectSessionHandler(ConnectedPlayer player) { - this.player = player; - } + private final ConnectedPlayer player; - @Override - public void handleGeneric(MinecraftPacket packet) { - // No-op: will never handle packets - } + InitialConnectSessionHandler(ConnectedPlayer player) { + this.player = player; + } - @Override - public void disconnected() { - // the user cancelled the login process - player.teardown(); - } + @Override + public void handleGeneric(MinecraftPacket packet) { + // No-op: will never handle packets + } + + @Override + public void disconnected() { + // the user cancelled the login process + player.teardown(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java index 411a3faa9..e36db5b21 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialInboundConnection.java @@ -3,38 +3,39 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.protocol.packet.Handshake; - import java.net.InetSocketAddress; import java.util.Optional; class InitialInboundConnection implements InboundConnection { - private final MinecraftConnection connection; - private final String cleanedAddress; - private final Handshake handshake; - InitialInboundConnection(MinecraftConnection connection, String cleanedAddress, Handshake handshake) { - this.connection = connection; - this.cleanedAddress = cleanedAddress; - this.handshake = handshake; - } + private final MinecraftConnection connection; + private final String cleanedAddress; + private final Handshake handshake; - @Override - public InetSocketAddress getRemoteAddress() { - return (InetSocketAddress) connection.getRemoteAddress(); - } + InitialInboundConnection(MinecraftConnection connection, String cleanedAddress, + Handshake handshake) { + this.connection = connection; + this.cleanedAddress = cleanedAddress; + this.handshake = handshake; + } - @Override - public Optional getVirtualHost() { - return Optional.of(InetSocketAddress.createUnresolved(cleanedAddress, handshake.getPort())); - } + @Override + public InetSocketAddress getRemoteAddress() { + return (InetSocketAddress) connection.getRemoteAddress(); + } - @Override - public boolean isActive() { - return connection.getChannel().isActive(); - } + @Override + public Optional getVirtualHost() { + return Optional.of(InetSocketAddress.createUnresolved(cleanedAddress, handshake.getPort())); + } - @Override - public int getProtocolVersion() { - return connection.getProtocolVersion(); - } + @Override + public boolean isActive() { + return connection.getChannel().isActive(); + } + + @Override + public int getProtocolVersion() { + return connection.getProtocolVersion(); + } } 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 6fa327c8a..fa5afd1c6 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 @@ -1,5 +1,7 @@ package com.velocitypowered.proxy.connection.client; +import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; + import com.google.common.base.Preconditions; import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.PostLoginEvent; @@ -17,18 +19,18 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.protocol.packet.Disconnect; +import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; +import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import com.velocitypowered.proxy.protocol.packet.ServerLogin; +import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; +import com.velocitypowered.proxy.protocol.packet.SetCompression; import com.velocitypowered.proxy.util.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; import java.net.URL; @@ -39,246 +41,266 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; - -import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; +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; public class LoginSessionHandler implements MinecraftSessionHandler { - private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); - private static final String MOJANG_SERVER_AUTH_URL = - "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; + private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); + private static final String MOJANG_SERVER_AUTH_URL = + "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s"; - private final VelocityServer server; - private final MinecraftConnection inbound; - private final InboundConnection apiInbound; - private @MonotonicNonNull ServerLogin login; - private byte[] verify = EMPTY_BYTE_ARRAY; - private int playerInfoId; - private @MonotonicNonNull ConnectedPlayer connectedPlayer; + private final VelocityServer server; + private final MinecraftConnection inbound; + private final InboundConnection apiInbound; + private @MonotonicNonNull ServerLogin login; + private byte[] verify = EMPTY_BYTE_ARRAY; + private int playerInfoId; + private @MonotonicNonNull ConnectedPlayer connectedPlayer; - public LoginSessionHandler(VelocityServer server, MinecraftConnection inbound, InboundConnection apiInbound) { - this.server = Preconditions.checkNotNull(server, "server"); - this.inbound = Preconditions.checkNotNull(inbound, "inbound"); - this.apiInbound = Preconditions.checkNotNull(apiInbound, "apiInbound"); + public LoginSessionHandler(VelocityServer server, MinecraftConnection inbound, + InboundConnection apiInbound) { + this.server = Preconditions.checkNotNull(server, "server"); + this.inbound = Preconditions.checkNotNull(inbound, "inbound"); + this.apiInbound = Preconditions.checkNotNull(apiInbound, "apiInbound"); + } + + @Override + public boolean handle(ServerLogin packet) { + this.login = packet; + if (inbound.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) { + playerInfoId = ThreadLocalRandom.current().nextInt(); + inbound.write( + new LoginPluginMessage(playerInfoId, VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL, + Unpooled.EMPTY_BUFFER)); + } else { + beginPreLogin(); + } + return true; + } + + @Override + public boolean handle(LoginPluginResponse packet) { + if (packet.getId() == playerInfoId) { + if (packet.isSuccess()) { + // Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening. + inbound.closeWith(Disconnect.create( + TextComponent.of("Running Velocity behind Velocity isn't supported.", TextColor.RED) + )); + } else { + // Proceed with the regular login process. + beginPreLogin(); + } + } + return true; + } + + @Override + public boolean handle(EncryptionResponse packet) { + ServerLogin login = this.login; + if (login == null) { + throw new IllegalStateException("No ServerLogin packet received yet."); } - @Override - public boolean handle(ServerLogin packet) { - this.login = packet; - if (inbound.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) { - playerInfoId = ThreadLocalRandom.current().nextInt(); - inbound.write(new LoginPluginMessage(playerInfoId, VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL, - Unpooled.EMPTY_BUFFER)); - } else { - beginPreLogin(); - } - return true; + if (verify.length == 0) { + throw new IllegalStateException("No EncryptionRequest packet sent yet."); } - @Override - public boolean handle(LoginPluginResponse packet) { - if (packet.getId() == playerInfoId) { - if (packet.isSuccess()) { - // Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening. - inbound.closeWith(Disconnect.create( - TextComponent.of("Running Velocity behind Velocity isn't supported.", TextColor.RED) - )); + try { + KeyPair serverKeyPair = server.getServerKeyPair(); + byte[] decryptedVerifyToken = EncryptionUtils + .decryptRsa(serverKeyPair, packet.getVerifyToken()); + if (!Arrays.equals(verify, decryptedVerifyToken)) { + throw new IllegalStateException("Unable to successfully decrypt the verification token."); + } + + byte[] decryptedSharedSecret = EncryptionUtils + .decryptRsa(serverKeyPair, packet.getSharedSecret()); + String serverId = EncryptionUtils + .generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); + + String playerIp = ((InetSocketAddress) inbound.getRemoteAddress()).getHostString(); + server.getHttpClient() + .get(new URL( + String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp))) + .thenAcceptAsync(profileResponse -> { + if (inbound.isClosed()) { + // The player disconnected after we authenticated them. + return; + } + + // Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption is + // enabled. + try { + inbound.enableEncryption(decryptedSharedSecret); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + + if (profileResponse.getCode() == 200) { + // All went well, initialize the session. + initializePlayer( + VelocityServer.GSON.fromJson(profileResponse.getBody(), GameProfile.class), true); + } else if (profileResponse.getCode() == 204) { + // Apparently an offline-mode user logged onto this online-mode proxy. The client has enabled + // encryption, so we need to do that as well. + logger.warn("An offline-mode client ({} from {}) tried to connect!", + login.getUsername(), playerIp); + inbound.closeWith(Disconnect.create(TextComponent + .of("This server only accepts connections from online-mode clients."))); } else { - // Proceed with the regular login process. - beginPreLogin(); + // Something else went wrong + logger.error( + "Got an unexpected error code {} whilst contacting Mojang to log in {} ({})", + profileResponse.getCode(), login.getUsername(), playerIp); + inbound.close(); } - } - return true; - } - - @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()); - if (!Arrays.equals(verify, decryptedVerifyToken)) { - throw new IllegalStateException("Unable to successfully decrypt the verification token."); - } - - byte[] decryptedSharedSecret = EncryptionUtils.decryptRsa(serverKeyPair, packet.getSharedSecret()); - String serverId = EncryptionUtils.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); - - String playerIp = ((InetSocketAddress) inbound.getRemoteAddress()).getHostString(); - server.getHttpClient() - .get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp))) - .thenAcceptAsync(profileResponse -> { - if (inbound.isClosed()) { - // The player disconnected after we authenticated them. - return; - } - - // Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption is - // enabled. - try { - inbound.enableEncryption(decryptedSharedSecret); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - - if (profileResponse.getCode() == 200) { - // All went well, initialize the session. - initializePlayer(VelocityServer.GSON.fromJson(profileResponse.getBody(), GameProfile.class), true); - } else if (profileResponse.getCode() == 204) { - // Apparently an offline-mode user logged onto this online-mode proxy. The client has enabled - // encryption, so we need to do that as well. - logger.warn("An offline-mode client ({} from {}) tried to connect!", login.getUsername(), playerIp); - inbound.closeWith(Disconnect.create(TextComponent.of("This server only accepts connections from online-mode clients."))); - } else { - // Something else went wrong - logger.error("Got an unexpected error code {} whilst contacting Mojang to log in {} ({})", - profileResponse.getCode(), login.getUsername(), playerIp); - inbound.close(); - } - }, inbound.eventLoop()) - .exceptionally(exception -> { - logger.error("Unable to enable encryption", exception); - inbound.close(); - return null; - }); - } catch (GeneralSecurityException e) { - logger.error("Unable to enable encryption", e); + }, inbound.eventLoop()) + .exceptionally(exception -> { + logger.error("Unable to enable encryption", exception); inbound.close(); - } catch (MalformedURLException e) { - throw new AssertionError(e); - } - return true; + return null; + }); + } catch (GeneralSecurityException e) { + logger.error("Unable to enable encryption", e); + inbound.close(); + } catch (MalformedURLException e) { + throw new AssertionError(e); } + return true; + } - 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(() -> { - if (inbound.isClosed()) { - // The player was disconnected - return; - } - PreLoginComponentResult result = event.getResult(); - Optional disconnectReason = result.getReason(); - if (disconnectReason.isPresent()) { - // The component is guaranteed to be provided if the connection was denied. - inbound.closeWith(Disconnect.create(disconnectReason.get())); - return; - } - - if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) { - // Request encryption. - EncryptionRequest request = generateRequest(); - this.verify = Arrays.copyOf(request.getVerifyToken(), 4); - inbound.write(request); - } else { - initializePlayer(GameProfile.forOfflinePlayer(login.getUsername()), false); - } - }, inbound.eventLoop()); + private void beginPreLogin() { + ServerLogin login = this.login; + if (login == null) { + throw new IllegalStateException("No ServerLogin packet received yet."); } - - private EncryptionRequest generateRequest() { - byte[] verify = new byte[4]; - ThreadLocalRandom.current().nextBytes(verify); - - EncryptionRequest request = new EncryptionRequest(); - request.setPublicKey(server.getServerKeyPair().getPublic().getEncoded()); - request.setVerifyToken(verify); - return request; - } - - private void initializePlayer(GameProfile profile, boolean onlineMode) { - if (inbound.isLegacyForge() && server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { - // We want to add the FML token to the properties - List properties = new ArrayList<>(profile.getProperties()); - properties.add(new GameProfile.Property("forgeClient", "true", "")); - profile = new GameProfile(profile.getId(), profile.getName(), properties); - } - GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(apiInbound, profile, onlineMode); - - server.getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> { - // Initiate a regular connection and move over to it. - ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(), inbound, - apiInbound.getVirtualHost().orElse(null)); - this.connectedPlayer = player; - - return server.getEventManager().fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS)) - .thenCompose(event -> { - // wait for permissions to load, then set the players permission function - player.setPermissionFunction(event.createFunction(player)); - // then call & wait for the login event - return server.getEventManager().fire(new LoginEvent(player)); - }) - // then complete the connection - .thenAcceptAsync(event -> { - if (inbound.isClosed()) { - // The player was disconnected - return; - } - - Optional reason = event.getResult().getReason(); - if (reason.isPresent()) { - player.disconnect(reason.get()); - } else { - handleProxyLogin(player); - } - }, inbound.eventLoop()); - }); - - } - - private void handleProxyLogin(ConnectedPlayer player) { - Optional toTry = player.getNextServerToTry(); - if (!toTry.isPresent()) { - player.close(TextComponent.of("No available servers", TextColor.RED)); + PreLoginEvent event = new PreLoginEvent(apiInbound, login.getUsername()); + server.getEventManager().fire(event) + .thenRunAsync(() -> { + if (inbound.isClosed()) { + // The player was disconnected return; - } - - if (!server.registerConnection(player)) { - inbound.closeWith(Disconnect.create(TextComponent.of("You are already on this proxy!", TextColor.RED))); + } + PreLoginComponentResult result = event.getResult(); + Optional disconnectReason = result.getReason(); + if (disconnectReason.isPresent()) { + // The component is guaranteed to be provided if the connection was denied. + inbound.closeWith(Disconnect.create(disconnectReason.get())); return; - } + } - int threshold = server.getConfiguration().getCompressionThreshold(); - if (threshold >= 0) { - inbound.write(new SetCompression(threshold)); - inbound.setCompressionThreshold(threshold); - } + if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result + .isOnlineModeAllowed())) { + // Request encryption. + EncryptionRequest request = generateRequest(); + this.verify = Arrays.copyOf(request.getVerifyToken(), 4); + inbound.write(request); + } else { + initializePlayer(GameProfile.forOfflinePlayer(login.getUsername()), false); + } + }, inbound.eventLoop()); + } - ServerLoginSuccess success = new ServerLoginSuccess(); - success.setUsername(player.getUsername()); - success.setUuid(player.getUniqueId()); - inbound.write(success); + private EncryptionRequest generateRequest() { + byte[] verify = new byte[4]; + ThreadLocalRandom.current().nextBytes(verify); - inbound.setAssociation(player); - inbound.setState(StateRegistry.PLAY); + EncryptionRequest request = new EncryptionRequest(); + request.setPublicKey(server.getServerKeyPair().getPublic().getEncoded()); + request.setVerifyToken(verify); + return request; + } - logger.info("{} has connected", player); - inbound.setSessionHandler(new InitialConnectSessionHandler(player)); - server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget()); + private void initializePlayer(GameProfile profile, boolean onlineMode) { + if (inbound.isLegacyForge() + && server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { + // We want to add the FML token to the properties + List properties = new ArrayList<>(profile.getProperties()); + properties.add(new GameProfile.Property("forgeClient", "true", "")); + profile = new GameProfile(profile.getId(), profile.getName(), properties); + } + GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(apiInbound, profile, + onlineMode); + + server.getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> { + // Initiate a regular connection and move over to it. + ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(), inbound, + apiInbound.getVirtualHost().orElse(null)); + this.connectedPlayer = player; + + return server.getEventManager() + .fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS)) + .thenCompose(event -> { + // wait for permissions to load, then set the players permission function + player.setPermissionFunction(event.createFunction(player)); + // then call & wait for the login event + return server.getEventManager().fire(new LoginEvent(player)); + }) + // then complete the connection + .thenAcceptAsync(event -> { + if (inbound.isClosed()) { + // The player was disconnected + return; + } + + Optional reason = event.getResult().getReason(); + if (reason.isPresent()) { + player.disconnect(reason.get()); + } else { + handleProxyLogin(player); + } + }, inbound.eventLoop()); + }); + + } + + private void handleProxyLogin(ConnectedPlayer player) { + Optional toTry = player.getNextServerToTry(); + if (!toTry.isPresent()) { + player.close(TextComponent.of("No available servers", TextColor.RED)); + return; } - @Override - public void handleUnknown(ByteBuf buf) { - throw new IllegalStateException("Unknown data " + ByteBufUtil.hexDump(buf)); + if (!server.registerConnection(player)) { + inbound.closeWith( + Disconnect.create(TextComponent.of("You are already on this proxy!", TextColor.RED))); + return; } - @Override - public void disconnected() { - if (connectedPlayer != null) { - connectedPlayer.teardown(); - } + int threshold = server.getConfiguration().getCompressionThreshold(); + if (threshold >= 0) { + inbound.write(new SetCompression(threshold)); + inbound.setCompressionThreshold(threshold); } + + ServerLoginSuccess success = new ServerLoginSuccess(); + success.setUsername(player.getUsername()); + success.setUuid(player.getUniqueId()); + inbound.write(success); + + inbound.setAssociation(player); + inbound.setState(StateRegistry.PLAY); + + logger.info("{} has connected", player); + inbound.setSessionHandler(new InitialConnectSessionHandler(player)); + server.getEventManager().fire(new PostLoginEvent(player)) + .thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget()); + } + + @Override + public void handleUnknown(ByteBuf buf) { + throw new IllegalStateException("Unknown data " + ByteBufUtil.hexDump(buf)); + } + + @Override + public void disconnected() { + if (connectedPlayer != null) { + connectedPlayer.teardown(); + } + } } 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 1bbf81914..d726d0124 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 @@ -16,46 +16,52 @@ import com.velocitypowered.proxy.protocol.packet.StatusResponse; import io.netty.buffer.ByteBuf; public class StatusSessionHandler implements MinecraftSessionHandler { - private final VelocityServer server; - private final MinecraftConnection connection; - private final InboundConnection inboundWrapper; - StatusSessionHandler(VelocityServer server, MinecraftConnection connection, InboundConnection inboundWrapper) { - this.server = server; - this.connection = connection; - this.inboundWrapper = inboundWrapper; - } + private final VelocityServer server; + private final MinecraftConnection connection; + private final InboundConnection inboundWrapper; - @Override - public boolean handle(StatusPing packet) { - connection.closeWith(packet); - return true; - } + StatusSessionHandler(VelocityServer server, MinecraftConnection connection, + InboundConnection inboundWrapper) { + this.server = server; + this.connection = connection; + this.inboundWrapper = inboundWrapper; + } - @Override - public boolean handle(StatusRequest packet) { - VelocityConfiguration configuration = server.getConfiguration(); + @Override + public boolean handle(StatusPing packet) { + connection.closeWith(packet); + return true; + } - int shownVersion = ProtocolConstants.isSupported(connection.getProtocolVersion()) ? connection.getProtocolVersion() : - ProtocolConstants.MAXIMUM_GENERIC_VERSION; - ServerPing initialPing = new ServerPing( - new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), - new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()), - configuration.getMotdComponent(), - configuration.getFavicon().orElse(null), - configuration.isAnnounceForge() ? ModInfo.DEFAULT : null - ); + @Override + public boolean handle(StatusRequest packet) { + VelocityConfiguration configuration = server.getConfiguration(); - ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing); - server.getEventManager().fire(event) - .thenRunAsync(() -> connection.write(new StatusResponse(VelocityServer.GSON.toJson(event.getPing()))), - connection.eventLoop()); - return true; - } + int shownVersion = ProtocolConstants.isSupported(connection.getProtocolVersion()) ? connection + .getProtocolVersion() : + ProtocolConstants.MAXIMUM_GENERIC_VERSION; + ServerPing initialPing = new ServerPing( + new ServerPing.Version(shownVersion, + "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING), + new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), + ImmutableList.of()), + configuration.getMotdComponent(), + configuration.getFavicon().orElse(null), + configuration.isAnnounceForge() ? ModInfo.DEFAULT : null + ); - @Override - public void handleUnknown(ByteBuf buf) { - // what even is going on? - connection.close(); - } + ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing); + server.getEventManager().fire(event) + .thenRunAsync( + () -> connection.write(new StatusResponse(VelocityServer.GSON.toJson(event.getPing()))), + connection.eventLoop()); + return true; + } + + @Override + public void handleUnknown(ByteBuf buf) { + // what even is going on? + connection.close(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeConstants.java index d4be8e585..322741a31 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/ForgeConstants.java @@ -3,19 +3,20 @@ package com.velocitypowered.proxy.connection.forge; import com.velocitypowered.proxy.protocol.packet.PluginMessage; public class ForgeConstants { - public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS"; - public static final String FORGE_LEGACY_CHANNEL = "FML"; - public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP"; - private static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 }; - private ForgeConstants() { - throw new AssertionError(); - } + public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS"; + public static final String FORGE_LEGACY_CHANNEL = "FML"; + public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP"; + private static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[]{-2, 0}; - public static PluginMessage resetPacket() { - PluginMessage msg = new PluginMessage(); - msg.setChannel(FORGE_LEGACY_HANDSHAKE_CHANNEL); - msg.setData(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone()); - return msg; - } + private ForgeConstants() { + throw new AssertionError(); + } + + public static PluginMessage resetPacket() { + PluginMessage msg = new PluginMessage(); + msg.setChannel(FORGE_LEGACY_HANDSHAKE_CHANNEL); + msg.setData(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone()); + return msg; + } } 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 34ba3cea4..cf1774e76 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 @@ -7,39 +7,40 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; - import java.util.List; public class ForgeUtil { - private ForgeUtil() { - throw new AssertionError(); - } - 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"); + private ForgeUtil() { + throw new AssertionError(); + } - ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getData()); - try { - byte discriminator = byteBuf.readByte(); + 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"); - if (discriminator == 2) { - ImmutableList.Builder mods = ImmutableList.builder(); - int modCount = ProtocolUtils.readVarInt(byteBuf); + ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getData()); + try { + byte discriminator = byteBuf.readByte(); - for (int index = 0; index < modCount; index++) { - String id = ProtocolUtils.readString(byteBuf); - String version = ProtocolUtils.readString(byteBuf); - mods.add(new ModInfo.Mod(id, version)); - } + if (discriminator == 2) { + ImmutableList.Builder mods = ImmutableList.builder(); + int modCount = ProtocolUtils.readVarInt(byteBuf); - return mods.build(); - } - - return ImmutableList.of(); - } finally { - byteBuf.release(); + for (int index = 0; index < modCount; index++) { + String id = ProtocolUtils.readString(byteBuf); + String version = ProtocolUtils.readString(byteBuf); + mods.add(new ModInfo.Mod(id, version)); } + + return mods.build(); + } + + return ImmutableList.of(); + } finally { + byteBuf.release(); } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionMessages.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionMessages.java index 727750d62..c35dd9f71 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionMessages.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionMessages.java @@ -4,12 +4,17 @@ import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; public class ConnectionMessages { - public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED); - public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED); - public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error"); - public static final TextComponent UNEXPECTED_DISCONNECT = TextComponent.of("Unexpectedly disconnected from server - crash?"); - private ConnectionMessages() { - throw new AssertionError(); - } + public static final TextComponent ALREADY_CONNECTED = TextComponent + .of("You are already connected to this server!", TextColor.RED); + public static final TextComponent IN_PROGRESS = TextComponent + .of("You are already connecting to a server!", TextColor.RED); + public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent + .of("Internal server connection error"); + public static final TextComponent UNEXPECTED_DISCONNECT = TextComponent + .of("Unexpectedly disconnected from server - crash?"); + + private ConnectionMessages() { + throw new AssertionError(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java index 5d36b2ed0..59af8bee1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ConnectionRequestResults.java @@ -2,48 +2,50 @@ package com.velocitypowered.proxy.connection.util; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.proxy.protocol.packet.Disconnect; +import java.util.Optional; import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializers; -import java.util.Optional; - public class ConnectionRequestResults { - public static final ConnectionRequestBuilder.Result SUCCESSFUL = plainResult(ConnectionRequestBuilder.Status.SUCCESS); - private ConnectionRequestResults() { - throw new AssertionError(); - } + public static final ConnectionRequestBuilder.Result SUCCESSFUL = plainResult( + ConnectionRequestBuilder.Status.SUCCESS); - public static ConnectionRequestBuilder.Result plainResult(ConnectionRequestBuilder.Status status) { - return new ConnectionRequestBuilder.Result() { - @Override - public ConnectionRequestBuilder.Status getStatus() { - return status; - } + private ConnectionRequestResults() { + throw new AssertionError(); + } - @Override - public Optional getReason() { - return Optional.empty(); - } - }; - } + public static ConnectionRequestBuilder.Result plainResult( + ConnectionRequestBuilder.Status status) { + return new ConnectionRequestBuilder.Result() { + @Override + public ConnectionRequestBuilder.Status getStatus() { + return status; + } - public static ConnectionRequestBuilder.Result forDisconnect(Disconnect disconnect) { - Component deserialized = ComponentSerializers.JSON.deserialize(disconnect.getReason()); - return forDisconnect(deserialized); - } + @Override + public Optional getReason() { + return Optional.empty(); + } + }; + } - public static ConnectionRequestBuilder.Result forDisconnect(Component component) { - return new ConnectionRequestBuilder.Result() { - @Override - public ConnectionRequestBuilder.Status getStatus() { - return ConnectionRequestBuilder.Status.SERVER_DISCONNECTED; - } + public static ConnectionRequestBuilder.Result forDisconnect(Disconnect disconnect) { + Component deserialized = ComponentSerializers.JSON.deserialize(disconnect.getReason()); + return forDisconnect(deserialized); + } - @Override - public Optional getReason() { - return Optional.of(component); - } - }; - } + public static ConnectionRequestBuilder.Result forDisconnect(Component component) { + return new ConnectionRequestBuilder.Result() { + @Override + public ConnectionRequestBuilder.Status getStatus() { + return ConnectionRequestBuilder.Status.SERVER_DISCONNECTED; + } + + @Override + public Optional getReason() { + return Optional.of(component); + } + }; + } } 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 be213db4d..5fa1050c9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -5,6 +5,7 @@ import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.proxy.VelocityServer; +import java.util.List; import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; @@ -17,75 +18,77 @@ import org.jline.reader.Candidate; import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; -import java.util.List; - public final class VelocityConsole extends SimpleTerminalConsole implements CommandSource { - private static final Logger logger = LogManager.getLogger(VelocityConsole.class); - private final VelocityServer server; - private PermissionFunction permissionFunction = PermissionFunction.ALWAYS_TRUE; + private static final Logger logger = LogManager.getLogger(VelocityConsole.class); - public VelocityConsole(VelocityServer server) { - this.server = server; - } + private final VelocityServer server; + private PermissionFunction permissionFunction = PermissionFunction.ALWAYS_TRUE; - @Override - public void sendMessage(Component component) { - logger.info(ComponentSerializers.LEGACY.serialize(component)); - } + public VelocityConsole(VelocityServer server) { + this.server = server; + } - @Override - public @NonNull Tristate getPermissionValue(@NonNull String permission) { - return this.permissionFunction.getPermissionValue(permission); - } + @Override + public void sendMessage(Component component) { + logger.info(ComponentSerializers.LEGACY.serialize(component)); + } - public void setupPermissions() { - PermissionsSetupEvent event = new PermissionsSetupEvent(this, s -> PermissionFunction.ALWAYS_TRUE); - this.server.getEventManager().fire(event).join(); // this is called on startup, we can safely #join - this.permissionFunction = event.createFunction(this); - } + @Override + public @NonNull Tristate getPermissionValue(@NonNull String permission) { + return this.permissionFunction.getPermissionValue(permission); + } - @Override - protected LineReader buildReader(LineReaderBuilder builder) { - return super.buildReader(builder - .appName("Velocity") - .completer((reader, parsedLine, list) -> { - try { - boolean isCommand = parsedLine.line().indexOf(' ') == -1; - 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); - } - }) - ); - } + public void setupPermissions() { + PermissionsSetupEvent event = new PermissionsSetupEvent(this, + s -> PermissionFunction.ALWAYS_TRUE); + this.server.getEventManager().fire(event) + .join(); // this is called on startup, we can safely #join + this.permissionFunction = event.createFunction(this); + } - @Override - protected boolean isRunning() { - return !this.server.isShutdown(); - } - - @Override - protected void runCommand(String command) { - try { - if (!this.server.getCommandManager().execute(this, command)) { - sendMessage(TextComponent.of("Command not found.", TextColor.RED)); + @Override + protected LineReader buildReader(LineReaderBuilder builder) { + return super.buildReader(builder + .appName("Velocity") + .completer((reader, parsedLine, list) -> { + try { + boolean isCommand = parsedLine.line().indexOf(' ') == -1; + 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 running this command.", e); - } - } + } catch (Exception e) { + logger.error("An error occurred while trying to perform tab completion.", e); + } + }) + ); + } - @Override - protected void shutdown() { - this.server.shutdown(); + @Override + protected boolean isRunning() { + return !this.server.isShutdown(); + } + + @Override + protected void runCommand(String command) { + try { + if (!this.server.getCommandManager().execute(this, command)) { + sendMessage(TextComponent.of("Command not found.", TextColor.RED)); + } + } catch (Exception e) { + logger.error("An error occurred while running this command.", e); } + } + + @Override + protected void shutdown() { + this.server.shutdown(); + } } 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 524887f4a..544c6f2f5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -10,95 +10,99 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.WriteBufferWaterMark; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - import java.net.InetSocketAddress; import java.util.HashSet; import java.util.Set; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public final class ConnectionManager { - private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 16, 1 << 18); - private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); - private final Set endpoints = new HashSet<>(); - private final TransportType transportType; - private final EventLoopGroup bossGroup; - private final EventLoopGroup workerGroup; - private final VelocityServer server; - public final ServerChannelInitializerHolder serverChannelInitializer; - 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)); - } + private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 16, + 1 << 18); + private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); + private final Set endpoints = new HashSet<>(); + private final TransportType transportType; + private final EventLoopGroup bossGroup; + private final EventLoopGroup workerGroup; + private final VelocityServer server; + public final ServerChannelInitializerHolder serverChannelInitializer; - public void logChannelInformation() { - LOGGER.info("Connections will use {} channels, {} compression, {} ciphers", this.transportType, Natives.compressor.getLoadedVariant(), Natives.cipher.getLoadedVariant()); - } + 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)); + } - public void bind(final InetSocketAddress address) { - final ServerBootstrap bootstrap = new ServerBootstrap() - .channel(this.transportType.serverSocketChannelClass) - .group(this.bossGroup, this.workerGroup) - .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK) - .childHandler(this.serverChannelInitializer.get()) - .childOption(ChannelOption.TCP_NODELAY, true) - .childOption(ChannelOption.IP_TOS, 0x18) - .localAddress(address); - bootstrap.bind() - .addListener((ChannelFutureListener) future -> { - final Channel channel = future.channel(); - if (future.isSuccess()) { - this.endpoints.add(channel); - LOGGER.info("Listening on {}", channel.localAddress()); - } else { - LOGGER.error("Can't bind to {}", address, future.cause()); - } - }); - } + public void logChannelInformation() { + LOGGER.info("Connections will use {} channels, {} compression, {} ciphers", this.transportType, + Natives.compressor.getLoadedVariant(), Natives.cipher.getLoadedVariant()); + } - public void queryBind(final String hostname, final int port) { - final Bootstrap bootstrap = new Bootstrap() - .channel(this.transportType.datagramChannelClass) - .group(this.workerGroup) - .handler(new GS4QueryHandler(this.server)) - .localAddress(hostname, port); - bootstrap.bind() - .addListener((ChannelFutureListener) future -> { - final Channel channel = future.channel(); - if (future.isSuccess()) { - this.endpoints.add(channel); - LOGGER.info("Listening for GS4 query on {}", channel.localAddress()); - } else { - LOGGER.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause()); - } - }); - } + public void bind(final InetSocketAddress address) { + final ServerBootstrap bootstrap = new ServerBootstrap() + .channel(this.transportType.serverSocketChannelClass) + .group(this.bossGroup, this.workerGroup) + .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK) + .childHandler(this.serverChannelInitializer.get()) + .childOption(ChannelOption.TCP_NODELAY, true) + .childOption(ChannelOption.IP_TOS, 0x18) + .localAddress(address); + bootstrap.bind() + .addListener((ChannelFutureListener) future -> { + final Channel channel = future.channel(); + if (future.isSuccess()) { + this.endpoints.add(channel); + LOGGER.info("Listening on {}", channel.localAddress()); + } else { + LOGGER.error("Can't bind to {}", address, future.cause()); + } + }); + } - public Bootstrap createWorker() { - return new Bootstrap() - .channel(this.transportType.socketChannelClass) - .group(this.workerGroup) - .option(ChannelOption.TCP_NODELAY, true) - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.server.getConfiguration().getConnectTimeout()); - } + public void queryBind(final String hostname, final int port) { + final Bootstrap bootstrap = new Bootstrap() + .channel(this.transportType.datagramChannelClass) + .group(this.workerGroup) + .handler(new GS4QueryHandler(this.server)) + .localAddress(hostname, port); + bootstrap.bind() + .addListener((ChannelFutureListener) future -> { + final Channel channel = future.channel(); + if (future.isSuccess()) { + this.endpoints.add(channel); + LOGGER.info("Listening for GS4 query on {}", channel.localAddress()); + } else { + LOGGER.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause()); + } + }); + } - public void shutdown() { - for (final Channel endpoint : this.endpoints) { - try { - LOGGER.info("Closing endpoint {}", endpoint.localAddress()); - endpoint.close().sync(); - } catch (final InterruptedException e) { - LOGGER.info("Interrupted whilst closing endpoint", e); - Thread.currentThread().interrupt(); - } - } - } + public Bootstrap createWorker() { + return new Bootstrap() + .channel(this.transportType.socketChannelClass) + .group(this.workerGroup) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, + this.server.getConfiguration().getConnectTimeout()); + } - public ServerChannelInitializerHolder getServerChannelInitializer() { - return this.serverChannelInitializer; + public void shutdown() { + for (final Channel endpoint : this.endpoints) { + try { + LOGGER.info("Closing endpoint {}", endpoint.localAddress()); + endpoint.close().sync(); + } catch (final InterruptedException e) { + LOGGER.info("Interrupted whilst closing endpoint", e); + Thread.currentThread().interrupt(); + } } + } + + public ServerChannelInitializerHolder getServerChannelInitializer() { + return this.serverChannelInitializer; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java index 57a8aa53d..8f7a6c278 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java @@ -1,20 +1,21 @@ package com.velocitypowered.proxy.network; public class Connections { - public static final String CIPHER_DECODER = "cipher-decoder"; - public static final String CIPHER_ENCODER = "cipher-encoder"; - public static final String COMPRESSION_DECODER = "compression-decoder"; - public static final String COMPRESSION_ENCODER = "compression-encoder"; - public static final String FRAME_DECODER = "frame-decoder"; - public static final String FRAME_ENCODER = "frame-encoder"; - public static final String HANDLER = "handler"; - public static final String LEGACY_PING_DECODER = "legacy-ping-decoder"; - public static final String LEGACY_PING_ENCODER = "legacy-ping-encoder"; - public static final String MINECRAFT_DECODER = "minecraft-decoder"; - public static final String MINECRAFT_ENCODER = "minecraft-encoder"; - public static final String READ_TIMEOUT = "read-timeout"; - private Connections() { - throw new AssertionError(); - } + public static final String CIPHER_DECODER = "cipher-decoder"; + public static final String CIPHER_ENCODER = "cipher-encoder"; + public static final String COMPRESSION_DECODER = "compression-decoder"; + public static final String COMPRESSION_ENCODER = "compression-encoder"; + public static final String FRAME_DECODER = "frame-decoder"; + public static final String FRAME_ENCODER = "frame-encoder"; + public static final String HANDLER = "handler"; + public static final String LEGACY_PING_DECODER = "legacy-ping-decoder"; + public static final String LEGACY_PING_ENCODER = "legacy-ping-encoder"; + public static final String MINECRAFT_DECODER = "minecraft-decoder"; + public static final String MINECRAFT_ENCODER = "minecraft-encoder"; + public static final String READ_TIMEOUT = "read-timeout"; + + private Connections() { + throw new AssertionError(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java index 46572fc5d..6187b9426 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializer.java @@ -1,5 +1,13 @@ package com.velocitypowered.proxy.network; +import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; +import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER; +import static com.velocitypowered.proxy.network.Connections.LEGACY_PING_DECODER; +import static com.velocitypowered.proxy.network.Connections.LEGACY_PING_ENCODER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER; +import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT; + import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; @@ -15,43 +23,37 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; import io.netty.handler.timeout.ReadTimeoutHandler; - import java.util.concurrent.TimeUnit; -import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; -import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER; -import static com.velocitypowered.proxy.network.Connections.LEGACY_PING_DECODER; -import static com.velocitypowered.proxy.network.Connections.LEGACY_PING_ENCODER; -import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER; -import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER; -import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT; - @SuppressWarnings("WeakerAccess") public class ServerChannelInitializer extends ChannelInitializer { - private final VelocityServer server; - public ServerChannelInitializer(final VelocityServer server) { - this.server = server; - } - - @Override - protected void initChannel(final Channel ch) { - ch.pipeline() - .addLast(READ_TIMEOUT, new ReadTimeoutHandler(this.server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS)) - .addLast(LEGACY_PING_DECODER, new LegacyPingDecoder()) - .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) - .addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE) - .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) - .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.SERVERBOUND)) - .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.CLIENTBOUND)); - - final MinecraftConnection connection = new MinecraftConnection(ch, this.server); - connection.setState(StateRegistry.HANDSHAKE); - connection.setSessionHandler(new HandshakeSessionHandler(connection, this.server)); - ch.pipeline().addLast(Connections.HANDLER, connection); - - if (ServerChannelInitializer.this.server.getConfiguration().isProxyProtocol()) { - ch.pipeline().addFirst(new HAProxyMessageDecoder()); - } + private final VelocityServer server; + + public ServerChannelInitializer(final VelocityServer server) { + this.server = server; + } + + @Override + protected void initChannel(final Channel ch) { + ch.pipeline() + .addLast(READ_TIMEOUT, + new ReadTimeoutHandler(this.server.getConfiguration().getReadTimeout(), + TimeUnit.SECONDS)) + .addLast(LEGACY_PING_DECODER, new LegacyPingDecoder()) + .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) + .addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE) + .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) + .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.SERVERBOUND)) + .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.CLIENTBOUND)); + + final MinecraftConnection connection = new MinecraftConnection(ch, this.server); + connection.setState(StateRegistry.HANDSHAKE); + connection.setSessionHandler(new HandshakeSessionHandler(connection, this.server)); + ch.pipeline().addLast(Connections.HANDLER, connection); + + if (ServerChannelInitializer.this.server.getConfiguration().isProxyProtocol()) { + ch.pipeline().addFirst(new HAProxyMessageDecoder()); } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializerHolder.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializerHolder.java index 2370397bd..96c154eba 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializerHolder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ServerChannelInitializerHolder.java @@ -2,27 +2,28 @@ package com.velocitypowered.proxy.network; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; +import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.function.Supplier; - public class ServerChannelInitializerHolder implements Supplier> { - private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); - private ChannelInitializer initializer; - public ServerChannelInitializerHolder(final ChannelInitializer initializer) { - this.initializer = initializer; - } + private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); + private ChannelInitializer initializer; - @Override - public ChannelInitializer get() { - return this.initializer; - } + public ServerChannelInitializerHolder(final ChannelInitializer initializer) { + this.initializer = initializer; + } - @Deprecated - public void set(final ChannelInitializer initializer) { - LOGGER.warn("The server channel initializer has been replaced by {}", Thread.currentThread().getStackTrace()[2]); - this.initializer = initializer; - } + @Override + public ChannelInitializer get() { + return this.initializer; + } + + @Deprecated + public void set(final ChannelInitializer initializer) { + LOGGER.warn("The server channel initializer has been replaced by {}", + Thread.currentThread().getStackTrace()[2]); + this.initializer = initializer; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java b/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java index 5b300ffb8..beb42d92f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java @@ -19,68 +19,76 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; - import java.util.concurrent.ThreadFactory; import java.util.function.BiFunction; enum TransportType { - NIO("NIO", NioServerSocketChannel.class, NioSocketChannel.class, NioDatagramChannel.class, (name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))), - EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class, EpollDatagramChannel.class, (name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))), - KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class, KQueueDatagramChannel.class, (name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type))); + NIO("NIO", NioServerSocketChannel.class, NioSocketChannel.class, NioDatagramChannel.class, + (name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))), + EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class, + EpollDatagramChannel.class, + (name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))), + KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class, + KQueueDatagramChannel.class, + (name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type))); - final String name; - final Class serverSocketChannelClass; - final Class socketChannelClass; - final Class datagramChannelClass; - final BiFunction eventLoopGroupFactory; + final String name; + final Class serverSocketChannelClass; + final Class socketChannelClass; + final Class datagramChannelClass; + final BiFunction eventLoopGroupFactory; - TransportType(final String name, final Class serverSocketChannelClass, final Class socketChannelClass, final Class datagramChannelClass, final BiFunction eventLoopGroupFactory) { - this.name = name; - this.serverSocketChannelClass = serverSocketChannelClass; - this.socketChannelClass = socketChannelClass; - this.datagramChannelClass = datagramChannelClass; - this.eventLoopGroupFactory = eventLoopGroupFactory; + TransportType(final String name, + final Class serverSocketChannelClass, + final Class socketChannelClass, + final Class datagramChannelClass, + final BiFunction eventLoopGroupFactory) { + this.name = name; + this.serverSocketChannelClass = serverSocketChannelClass; + this.socketChannelClass = socketChannelClass; + this.datagramChannelClass = datagramChannelClass; + this.eventLoopGroupFactory = eventLoopGroupFactory; + } + + @Override + public String toString() { + return this.name; + } + + public EventLoopGroup createEventLoopGroup(final Type type) { + return this.eventLoopGroupFactory.apply(this.name, type); + } + + private static ThreadFactory createThreadFactory(final String name, final Type type) { + return new ThreadFactoryBuilder() + .setNameFormat("Netty " + name + ' ' + type.toString() + " #%d") + .setDaemon(true) + .build(); + } + + public static TransportType bestType() { + if (Epoll.isAvailable()) { + return EPOLL; + } else if (KQueue.isAvailable()) { + return KQUEUE; + } else { + return NIO; + } + } + + public enum Type { + BOSS("Boss"), + WORKER("Worker"); + + private final String name; + + Type(final String name) { + this.name = name; } @Override public String toString() { - return this.name; - } - - public EventLoopGroup createEventLoopGroup(final Type type) { - return this.eventLoopGroupFactory.apply(this.name, type); - } - - private static ThreadFactory createThreadFactory(final String name, final Type type) { - return new ThreadFactoryBuilder() - .setNameFormat("Netty " + name + ' ' + type.toString() + " #%d") - .setDaemon(true) - .build(); - } - - public static TransportType bestType() { - if (Epoll.isAvailable()) { - return EPOLL; - } else if (KQueue.isAvailable()) { - return KQUEUE; - } else { - return NIO; - } - } - - public enum Type { - BOSS("Boss"), - WORKER("Worker"); - - private final String name; - - Type(final String name) { - this.name = name; - } - - @Override - public String toString() { - return this.name; - } + return this.name; } + } } 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 128911d34..66fd27fd8 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 @@ -4,82 +4,91 @@ import com.google.common.base.VerifyException; import com.velocitypowered.proxy.VelocityServer; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; -import io.netty.channel.pool.*; -import io.netty.handler.codec.http.*; +import io.netty.channel.pool.AbstractChannelPoolMap; +import io.netty.channel.pool.ChannelPoolHandler; +import io.netty.channel.pool.ChannelPoolMap; +import io.netty.channel.pool.FixedChannelPool; +import io.netty.channel.pool.SimpleChannelPool; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; - -import javax.net.ssl.SSLEngine; import java.net.InetSocketAddress; import java.net.URL; import java.util.concurrent.CompletableFuture; +import javax.net.ssl.SSLEngine; public class NettyHttpClient { - private final ChannelPoolMap poolMap; - public NettyHttpClient(VelocityServer server) { - Bootstrap bootstrap = server.initializeGenericBootstrap(); - this.poolMap = new AbstractChannelPoolMap() { - @Override - protected SimpleChannelPool newPool(InetSocketAddress key) { - return new FixedChannelPool(bootstrap.remoteAddress(key), new ChannelPoolHandler() { - @Override - public void channelReleased(Channel channel) throws Exception { - channel.pipeline().remove("collector"); - } + private final ChannelPoolMap poolMap; - @Override - public void channelAcquired(Channel channel) throws Exception { - // We don't do anything special when acquiring channels. The channel handler cleans up after - // each connection is used. - } + public NettyHttpClient(VelocityServer server) { + Bootstrap bootstrap = server.initializeGenericBootstrap(); + this.poolMap = new AbstractChannelPoolMap() { + @Override + protected SimpleChannelPool newPool(InetSocketAddress key) { + return new FixedChannelPool(bootstrap.remoteAddress(key), new ChannelPoolHandler() { + @Override + public void channelReleased(Channel channel) throws Exception { + channel.pipeline().remove("collector"); + } - @Override - public void channelCreated(Channel channel) throws Exception { - if (key.getPort() == 443) { - SslContext context = SslContextBuilder.forClient().build(); - SSLEngine engine = context.newEngine(channel.alloc()); - channel.pipeline().addLast("ssl", new SslHandler(engine)); - } - channel.pipeline().addLast("http", new HttpClientCodec()); - } - }, 8); + @Override + public void channelAcquired(Channel channel) throws Exception { + // We don't do anything special when acquiring channels. The channel handler cleans up after + // each connection is used. + } + + @Override + public void channelCreated(Channel channel) throws Exception { + if (key.getPort() == 443) { + SslContext context = SslContextBuilder.forClient().build(); + SSLEngine engine = context.newEngine(channel.alloc()); + channel.pipeline().addLast("ssl", new SslHandler(engine)); } - }; + channel.pipeline().addLast("http", new HttpClientCodec()); + } + }, 8); + } + }; + } + + public CompletableFuture get(URL url) { + String host = url.getHost(); + int port = url.getPort(); + boolean ssl = url.getProtocol().equals("https"); + if (port == -1) { + port = ssl ? 443 : 80; } - public CompletableFuture get(URL url) { - String host = url.getHost(); - int port = url.getPort(); - boolean ssl = url.getProtocol().equals("https"); - if (port == -1) { - port = ssl ? 443 : 80; - } + CompletableFuture reply = new CompletableFuture<>(); + InetSocketAddress address = new InetSocketAddress(host, port); + poolMap.get(address) + .acquire() + .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)); - CompletableFuture reply = new CompletableFuture<>(); - InetSocketAddress address = new InetSocketAddress(host, port); - poolMap.get(address) - .acquire() - .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()); + request.headers().add(HttpHeaderNames.HOST, url.getHost()); + request.headers().add(HttpHeaderNames.USER_AGENT, "Velocity"); + channel.writeAndFlush(request); - DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url.getPath() + "?" + url.getQuery()); - request.headers().add(HttpHeaderNames.HOST, url.getHost()); - request.headers().add(HttpHeaderNames.USER_AGENT, "Velocity"); - channel.writeAndFlush(request); - - // Make sure to release this connection - reply.whenComplete((resp, err) -> poolMap.get(address).release(channel)); - } else { - reply.completeExceptionally(future.cause()); - } - }); - return reply; - } + // Make sure to release this connection + reply.whenComplete((resp, err) -> poolMap.get(address).release(channel)); + } else { + reply.completeExceptionally(future.cause()); + } + }); + return reply; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/http/SimpleHttpResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/network/http/SimpleHttpResponse.java index 90f21141b..c5947def6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/http/SimpleHttpResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/http/SimpleHttpResponse.java @@ -1,27 +1,28 @@ package com.velocitypowered.proxy.network.http; public class SimpleHttpResponse { - private final int code; - private final String body; - SimpleHttpResponse(int code, String body) { - this.code = code; - this.body = body; - } + private final int code; + private final String body; - public int getCode() { - return code; - } + SimpleHttpResponse(int code, String body) { + this.code = code; + this.body = body; + } - public String getBody() { - return body; - } + public int getCode() { + return code; + } - @Override - public String toString() { - return "SimpleHttpResponse{" + - "code=" + code + - ", body='" + body + '\'' + - '}'; - } + public String getBody() { + return body; + } + + @Override + public String toString() { + return "SimpleHttpResponse{" + + "code=" + code + + ", body='" + body + '\'' + + '}'; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/http/SimpleHttpResponseCollector.java b/proxy/src/main/java/com/velocitypowered/proxy/network/http/SimpleHttpResponseCollector.java index 1e54c2706..54d74514f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/http/SimpleHttpResponseCollector.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/http/SimpleHttpResponseCollector.java @@ -2,50 +2,54 @@ package com.velocitypowered.proxy.network.http; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpUtil; +import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.ReferenceCountUtil; - import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter { - private final StringBuilder buffer = new StringBuilder(1024); - private final CompletableFuture reply; - private int httpCode; - private boolean canKeepAlive; - SimpleHttpResponseCollector(CompletableFuture reply) { - this.reply = reply; - } + private final StringBuilder buffer = new StringBuilder(1024); + private final CompletableFuture reply; + private int httpCode; + private boolean canKeepAlive; - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - try { - if (msg instanceof HttpResponse) { - HttpResponse response = (HttpResponse) msg; - HttpResponseStatus status = response.status(); - this.httpCode = status.code(); - this.canKeepAlive = HttpUtil.isKeepAlive(response); - } + SimpleHttpResponseCollector(CompletableFuture reply) { + this.reply = reply; + } - if (msg instanceof HttpContent) { - buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8)); + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + try { + if (msg instanceof HttpResponse) { + HttpResponse response = (HttpResponse) msg; + HttpResponseStatus status = response.status(); + this.httpCode = status.code(); + this.canKeepAlive = HttpUtil.isKeepAlive(response); + } - if (msg instanceof LastHttpContent) { - if (!canKeepAlive) { - ctx.close(); - } - reply.complete(new SimpleHttpResponse(httpCode, buffer.toString())); - } - } - } finally { - ReferenceCountUtil.release(msg); + if (msg instanceof HttpContent) { + buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8)); + + if (msg instanceof LastHttpContent) { + if (!canKeepAlive) { + ctx.close(); + } + reply.complete(new SimpleHttpResponse(httpCode, buffer.toString())); } + } + } finally { + ReferenceCountUtil.release(msg); } + } - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - ctx.close(); - reply.completeExceptionally(cause); - } + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + ctx.close(); + reply.completeExceptionally(cause); + } } 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 5805c5c74..686056cd7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java @@ -8,52 +8,54 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; public class PluginClassLoader extends URLClassLoader { - private static final Set loaders = new CopyOnWriteArraySet<>(); - static { - ClassLoader.registerAsParallelCapable(); + private static final Set loaders = new CopyOnWriteArraySet<>(); + + static { + ClassLoader.registerAsParallelCapable(); + } + + public PluginClassLoader(URL[] urls) { + super(urls); + } + + public void addToClassloaders() { + loaders.add(this); + } + + void addPath(Path path) { + try { + addURL(path.toUri().toURL()); + } catch (MalformedURLException e) { + throw new AssertionError(e); + } + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return loadClass0(name, resolve, true); + } + + private Class loadClass0(String name, boolean resolve, boolean checkOther) + throws ClassNotFoundException { + try { + return super.loadClass(name, resolve); + } catch (ClassNotFoundException ignored) { + // Ignored: we'll try others } - public PluginClassLoader(URL[] urls) { - super(urls); - } - - public void addToClassloaders() { - loaders.add(this); - } - - void addPath(Path path) { - try { - addURL(path.toUri().toURL()); - } catch (MalformedURLException e) { - throw new AssertionError(e); + if (checkOther) { + for (PluginClassLoader loader : loaders) { + if (loader != this) { + try { + return loader.loadClass0(name, resolve, false); + } catch (ClassNotFoundException ignored) { + // We're trying others, safe to ignore + } } + } } - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - return loadClass0(name, resolve, true); - } - - private Class loadClass0(String name, boolean resolve, boolean checkOther) throws ClassNotFoundException { - try { - return super.loadClass(name, resolve); - } catch (ClassNotFoundException ignored) { - // Ignored: we'll try others - } - - if (checkOther) { - for (PluginClassLoader loader : loaders) { - if (loader != this) { - try { - return loader.loadClass0(name, resolve, false); - } catch (ClassNotFoundException ignored) { - // We're trying others, safe to ignore - } - } - } - } - - throw new ClassNotFoundException(name); - } + throw new ClassNotFoundException(name); + } } 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 8a9e677ac..c1e9a5dcb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityEventManager.java @@ -9,6 +9,16 @@ import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.plugin.PluginManager; +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; +import java.util.concurrent.TimeUnit; import net.kyori.event.EventSubscriber; import net.kyori.event.PostResult; import net.kyori.event.SimpleEventBus; @@ -21,180 +31,183 @@ 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; -import java.util.concurrent.TimeUnit; - public class VelocityEventManager implements EventManager { - private static final Logger logger = LogManager.getLogger(VelocityEventManager.class); - private final ListMultimap registeredListenersByPlugin = Multimaps - .synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new)); - private final ListMultimap> registeredHandlersByPlugin = Multimaps - .synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new)); - private final SimpleEventBus bus; - private final MethodSubscriptionAdapter methodAdapter; - private final ExecutorService service; - private final PluginManager pluginManager; + private static final Logger logger = LogManager.getLogger(VelocityEventManager.class); - 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()); + private final ListMultimap registeredListenersByPlugin = Multimaps + .synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new)); + private final ListMultimap> registeredHandlersByPlugin = Multimaps + .synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new)); + 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()); + } + + private void ensurePlugin(Object plugin) { + Preconditions.checkNotNull(plugin, "plugin"); + Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), + "Specified plugin is not loaded"); + } + + @Override + public void register(Object plugin, Object listener) { + ensurePlugin(plugin); + Preconditions.checkNotNull(listener, "listener"); + if (plugin == listener && registeredListenersByPlugin.containsEntry(plugin, plugin)) { + throw new IllegalArgumentException( + "Trying to register the plugin main instance. Velocity already takes care of this for you."); + } + registeredListenersByPlugin.put(plugin, listener); + methodAdapter.register(listener); + } + + @Override + @SuppressWarnings("type.argument.type.incompatible") + public void register(Object plugin, Class eventClass, PostOrder postOrder, + EventHandler handler) { + ensurePlugin(plugin); + Preconditions.checkNotNull(eventClass, "eventClass"); + Preconditions.checkNotNull(postOrder, "postOrder"); + Preconditions.checkNotNull(handler, "listener"); + bus.register(eventClass, new KyoriToVelocityHandler<>(handler, postOrder)); + } + + @Override + 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); } - private void ensurePlugin(Object plugin) { - Preconditions.checkNotNull(plugin, "plugin"); - Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "Specified plugin is not loaded"); + Runnable runEvent = () -> { + PostResult result = bus.post(event); + if (!result.exceptions().isEmpty()) { + logger.error("Some errors occurred whilst posting event {}.", event); + int i = 0; + for (Throwable exception : result.exceptions().values()) { + logger.error("#{}: \n", ++i, exception); + } + } + }; + + CompletableFuture eventFuture = new CompletableFuture<>(); + service.execute(() -> { + runEvent.run(); + eventFuture.complete(event); + }); + return eventFuture; + } + + @Override + public void unregisterListeners(Object plugin) { + ensurePlugin(plugin); + Collection listeners = registeredListenersByPlugin.removeAll(plugin); + listeners.forEach(methodAdapter::unregister); + Collection> handlers = registeredHandlersByPlugin.removeAll(plugin); + handlers + .forEach(handler -> bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST))); + } + + @Override + public void unregisterListener(Object plugin, Object listener) { + ensurePlugin(plugin); + Preconditions.checkNotNull(listener, "listener"); + registeredListenersByPlugin.remove(plugin, listener); + methodAdapter.unregister(listener); + } + + @Override + public void unregister(Object plugin, EventHandler handler) { + ensurePlugin(plugin); + Preconditions.checkNotNull(handler, "listener"); + registeredHandlersByPlugin.remove(plugin, handler); + bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST)); + } + + public boolean shutdown() throws InterruptedException { + service.shutdown(); + return service.awaitTermination(10, TimeUnit.SECONDS); + } + + private static class VelocityMethodScanner implements MethodScanner { + + @Override + public boolean shouldRegister(@NonNull Object listener, @NonNull Method method) { + return method.isAnnotationPresent(Subscribe.class); } @Override - public void register(Object plugin, Object listener) { - ensurePlugin(plugin); - Preconditions.checkNotNull(listener, "listener"); - if (plugin == listener && registeredListenersByPlugin.containsEntry(plugin, plugin)) { - throw new IllegalArgumentException("Trying to register the plugin main instance. Velocity already takes care of this for you."); - } - registeredListenersByPlugin.put(plugin, listener); - methodAdapter.register(listener); + public int postOrder(@NonNull Object listener, @NonNull Method method) { + 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 - @SuppressWarnings("type.argument.type.incompatible") - public void register(Object plugin, Class eventClass, PostOrder postOrder, EventHandler handler) { - ensurePlugin(plugin); - Preconditions.checkNotNull(eventClass, "eventClass"); - Preconditions.checkNotNull(postOrder, "postOrder"); - Preconditions.checkNotNull(handler, "listener"); - bus.register(eventClass, new KyoriToVelocityHandler<>(handler, postOrder)); + public boolean consumeCancelledEvents(@NonNull Object listener, @NonNull Method method) { + return true; + } + } + + private static class KyoriToVelocityHandler implements EventSubscriber { + + private final EventHandler handler; + private final int postOrder; + + private KyoriToVelocityHandler(EventHandler handler, PostOrder postOrder) { + this.handler = handler; + this.postOrder = postOrder.ordinal(); } @Override - 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); - } - - Runnable runEvent = () -> { - PostResult result = bus.post(event); - if (!result.exceptions().isEmpty()) { - logger.error("Some errors occurred whilst posting event {}.", event); - int i = 0; - for (Throwable exception : result.exceptions().values()) { - logger.error("#{}: \n", ++i, exception); - } - } - }; - - CompletableFuture eventFuture = new CompletableFuture<>(); - service.execute(() -> { - runEvent.run(); - eventFuture.complete(event); - }); - return eventFuture; + public void invoke(@NonNull E event) { + handler.execute(event); } @Override - public void unregisterListeners(Object plugin) { - ensurePlugin(plugin); - Collection listeners = registeredListenersByPlugin.removeAll(plugin); - listeners.forEach(methodAdapter::unregister); - Collection> handlers = registeredHandlersByPlugin.removeAll(plugin); - handlers.forEach(handler -> bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST))); + public int postOrder() { + return postOrder; + } + + public EventHandler getHandler() { + return handler; } @Override - public void unregisterListener(Object plugin, Object listener) { - ensurePlugin(plugin); - Preconditions.checkNotNull(listener, "listener"); - registeredListenersByPlugin.remove(plugin, listener); - methodAdapter.unregister(listener); + 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 void unregister(Object plugin, EventHandler handler) { - ensurePlugin(plugin); - Preconditions.checkNotNull(handler, "listener"); - registeredHandlersByPlugin.remove(plugin, handler); - bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST)); - } - - public boolean shutdown() throws InterruptedException { - service.shutdown(); - return service.awaitTermination(10, TimeUnit.SECONDS); - } - - private static class VelocityMethodScanner implements MethodScanner { - @Override - public boolean shouldRegister(@NonNull Object listener, @NonNull Method method) { - return method.isAnnotationPresent(Subscribe.class); - } - - @Override - public int postOrder(@NonNull Object listener, @NonNull Method method) { - 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 - public boolean consumeCancelledEvents(@NonNull Object listener, @NonNull Method method) { - return true; - } - } - - private static class KyoriToVelocityHandler implements EventSubscriber { - private final EventHandler handler; - private final int postOrder; - - private KyoriToVelocityHandler(EventHandler handler, PostOrder postOrder) { - this.handler = handler; - this.postOrder = postOrder.ordinal(); - } - - @Override - public void invoke(@NonNull E event) { - handler.execute(event); - } - - @Override - public int postOrder() { - return postOrder; - } - - 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); - } + 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 880025a6e..83e9b69bd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -1,5 +1,8 @@ package com.velocitypowered.proxy.plugin; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginManager; @@ -7,124 +10,131 @@ import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.plugin.loader.JavaPluginLoader; import com.velocitypowered.proxy.plugin.util.PluginDependencyUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class VelocityPluginManager implements PluginManager { - private static final Logger logger = LogManager.getLogger(VelocityPluginManager.class); - private final Map plugins = new HashMap<>(); - private final Map pluginInstances = new IdentityHashMap<>(); - private final VelocityServer server; + private static final Logger logger = LogManager.getLogger(VelocityPluginManager.class); - public VelocityPluginManager(VelocityServer server) { - this.server = checkNotNull(server, "server"); + private final Map plugins = new HashMap<>(); + private final Map pluginInstances = new IdentityHashMap<>(); + private final VelocityServer server; + + public VelocityPluginManager(VelocityServer server) { + this.server = checkNotNull(server, "server"); + } + + private void registerPlugin(PluginContainer plugin) { + plugins.put(plugin.getDescription().getId(), plugin); + Optional instance = plugin.getInstance(); + if (instance.isPresent()) { + pluginInstances.put(instance.get(), plugin); } + } - private void registerPlugin(PluginContainer plugin) { - plugins.put(plugin.getDescription().getId(), plugin); - Optional instance = plugin.getInstance(); - if (instance.isPresent()) { - pluginInstances.put(instance.get(), plugin); + public void loadPlugins(Path directory) throws IOException { + checkNotNull(directory, "directory"); + checkArgument(directory.toFile().isDirectory(), "provided path isn't a directory"); + + List found = new ArrayList<>(); + JavaPluginLoader loader = new JavaPluginLoader(server, directory); + + try (DirectoryStream stream = Files + .newDirectoryStream(directory, p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) { + for (Path path : stream) { + try { + found.add(loader.loadPlugin(path)); + } catch (Exception e) { + logger.error("Unable to load plugin {}", path, e); } + } } - public void loadPlugins(Path directory) throws IOException { - checkNotNull(directory, "directory"); - checkArgument(directory.toFile().isDirectory(), "provided path isn't a directory"); + if (found.isEmpty()) { + // No plugins found + return; + } - List found = new ArrayList<>(); - JavaPluginLoader loader = new JavaPluginLoader(server, directory); + List sortedPlugins = PluginDependencyUtils.sortCandidates(found); - try (DirectoryStream stream = Files.newDirectoryStream(directory, p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) { - for (Path path : stream) { - try { - found.add(loader.loadPlugin(path)); - } catch (Exception e) { - logger.error("Unable to load plugin {}", path, e); - } - } + // Now load the plugins + pluginLoad: + for (PluginDescription plugin : sortedPlugins) { + // Verify dependencies + for (PluginDependency dependency : plugin.getDependencies()) { + if (!dependency.isOptional() && !isLoaded(dependency.getId())) { + logger.error("Can't load plugin {} due to missing dependency {}", plugin.getId(), + dependency.getId()); + continue pluginLoad; } + } - if (found.isEmpty()) { - // No plugins found - return; - } + // Actually create the plugin + PluginContainer pluginObject; - List sortedPlugins = PluginDependencyUtils.sortCandidates(found); + try { + pluginObject = loader.createPlugin(plugin); + } catch (Exception e) { + logger.error("Can't create plugin {}", plugin.getId(), e); + continue; + } - // Now load the plugins - pluginLoad: - for (PluginDescription plugin : sortedPlugins) { - // Verify dependencies - for (PluginDependency dependency : plugin.getDependencies()) { - if (!dependency.isOptional() && !isLoaded(dependency.getId())) { - logger.error("Can't load plugin {} due to missing dependency {}", plugin.getId(), dependency.getId()); - continue pluginLoad; - } - } + registerPlugin(pluginObject); + } + } - // Actually create the plugin - PluginContainer pluginObject; + @Override + public Optional fromInstance(Object instance) { + checkNotNull(instance, "instance"); - try { - pluginObject = loader.createPlugin(plugin); - } catch (Exception e) { - logger.error("Can't create plugin {}", plugin.getId(), e); - continue; - } - - registerPlugin(pluginObject); - } + if (instance instanceof PluginContainer) { + return Optional.of((PluginContainer) instance); } - @Override - public Optional fromInstance(Object instance) { - checkNotNull(instance, "instance"); + return Optional.ofNullable(pluginInstances.get(instance)); + } - if (instance instanceof PluginContainer) { - return Optional.of((PluginContainer) instance); - } + @Override + public Optional getPlugin(String id) { + checkNotNull(id, "id"); + return Optional.ofNullable(plugins.get(id)); + } - return Optional.ofNullable(pluginInstances.get(instance)); - } - - @Override - public Optional getPlugin(String id) { - checkNotNull(id, "id"); - return Optional.ofNullable(plugins.get(id)); - } - - @Override - public Collection getPlugins() { - return Collections.unmodifiableCollection(plugins.values()); - } - - @Override - public boolean isLoaded(String id) { - return plugins.containsKey(id); - } - - @Override - public void addToClasspath(Object plugin, Path path) { - checkNotNull(plugin, "instance"); - checkNotNull(path, "path"); - checkArgument(pluginInstances.containsKey(plugin), "plugin is not loaded"); - - ClassLoader pluginClassloader = plugin.getClass().getClassLoader(); - if (pluginClassloader instanceof PluginClassLoader) { - ((PluginClassLoader) pluginClassloader).addPath(path); - } else { - throw new UnsupportedOperationException("Operation is not supported on non-Java Velocity plugins."); - } + @Override + public Collection getPlugins() { + return Collections.unmodifiableCollection(plugins.values()); + } + + @Override + public boolean isLoaded(String id) { + return plugins.containsKey(id); + } + + @Override + public void addToClasspath(Object plugin, Path path) { + checkNotNull(plugin, "instance"); + checkNotNull(path, "path"); + checkArgument(pluginInstances.containsKey(plugin), "plugin is not loaded"); + + ClassLoader pluginClassloader = plugin.getClass().getClassLoader(); + if (pluginClassloader instanceof PluginClassLoader) { + ((PluginClassLoader) pluginClassloader).addPath(path); + } else { + throw new UnsupportedOperationException( + "Operation is not supported on non-Java Velocity plugins."); } + } } 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 fda0836d3..ac75badc3 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,7 +12,6 @@ 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 java.io.BufferedInputStream; import java.io.InputStreamReader; import java.io.Reader; @@ -26,100 +25,108 @@ import java.util.jar.JarEntry; import java.util.jar.JarInputStream; public class JavaPluginLoader implements PluginLoader { - private final ProxyServer server; - private final Path baseDirectory; - public JavaPluginLoader(ProxyServer server, Path baseDirectory) { - this.server = server; - this.baseDirectory = baseDirectory; + private final ProxyServer server; + private final Path baseDirectory; + + public JavaPluginLoader(ProxyServer server, Path baseDirectory) { + this.server = server; + this.baseDirectory = baseDirectory; + } + + @Override + public PluginDescription loadPlugin(Path source) throws Exception { + Optional serialized = getSerializedPluginInfo(source); + + if (!serialized.isPresent()) { + throw new InvalidPluginException("Did not find a valid velocity-info.json."); } - @Override - public PluginDescription loadPlugin(Path source) throws Exception { - Optional serialized = getSerializedPluginInfo(source); - - if (!serialized.isPresent()) { - throw new InvalidPluginException("Did not find a valid velocity-info.json."); - } - - SerializedPluginDescription pd = serialized.get(); - if (!PluginDescription.ID_PATTERN.matcher(pd.getId()).matches()) { - throw new InvalidPluginException("Plugin ID '" + pd.getId() + "' must match pattern " + - PluginDescription.ID_PATTERN.pattern()); - } - - PluginClassLoader loader = new PluginClassLoader( - new URL[] {source.toUri().toURL() } - ); - loader.addToClassloaders(); - - Class mainClass = loader.loadClass(pd.getMain()); - return createDescription(pd, source, mainClass); + SerializedPluginDescription pd = serialized.get(); + if (!PluginDescription.ID_PATTERN.matcher(pd.getId()).matches()) { + throw new InvalidPluginException("Plugin ID '" + pd.getId() + "' must match pattern " + + PluginDescription.ID_PATTERN.pattern()); } - @Override - public PluginContainer createPlugin(PluginDescription description) throws Exception { - if (!(description instanceof JavaVelocityPluginDescription)) { - throw new IllegalArgumentException("Description provided isn't of the Java plugin loader"); - } + PluginClassLoader loader = new PluginClassLoader( + new URL[]{source.toUri().toURL()} + ); + loader.addToClassloaders(); - JavaVelocityPluginDescription javaDescription = (JavaVelocityPluginDescription) description; - Optional source = javaDescription.getSource(); + Class mainClass = loader.loadClass(pd.getMain()); + return createDescription(pd, source, mainClass); + } - if (!source.isPresent()) { - throw new IllegalArgumentException("No path in plugin description"); - } - - 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); + @Override + public PluginContainer createPlugin(PluginDescription description) throws Exception { + if (!(description instanceof JavaVelocityPluginDescription)) { + throw new IllegalArgumentException("Description provided isn't of the Java plugin loader"); } - private Optional getSerializedPluginInfo(Path source) throws Exception { - try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(source)))) { - JarEntry entry; - while ((entry = in.getNextJarEntry()) != null) { - if (entry.getName().equals("velocity-plugin.json")) { - try (Reader pluginInfoReader = new InputStreamReader(in)) { - return Optional.of(VelocityServer.GSON.fromJson(pluginInfoReader, SerializedPluginDescription.class)); - } - } - } + JavaVelocityPluginDescription javaDescription = (JavaVelocityPluginDescription) description; + Optional source = javaDescription.getSource(); - return Optional.empty(); + if (!source.isPresent()) { + throw new IllegalArgumentException("No path in plugin description"); + } + + 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); + } + + private Optional getSerializedPluginInfo(Path source) + throws Exception { + try (JarInputStream in = new JarInputStream( + new BufferedInputStream(Files.newInputStream(source)))) { + JarEntry entry; + while ((entry = in.getNextJarEntry()) != null) { + if (entry.getName().equals("velocity-plugin.json")) { + try (Reader pluginInfoReader = new InputStreamReader(in)) { + return Optional.of(VelocityServer.GSON + .fromJson(pluginInfoReader, SerializedPluginDescription.class)); + } } + } + + return Optional.empty(); + } + } + + private VelocityPluginDescription createDescription(SerializedPluginDescription description, + Path source, Class mainClass) { + Set dependencies = new HashSet<>(); + + for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) { + dependencies.add(toDependencyMeta(dependency)); } - private VelocityPluginDescription createDescription(SerializedPluginDescription description, Path source, Class mainClass) { - Set dependencies = new HashSet<>(); + return new JavaVelocityPluginDescription( + description.getId(), + description.getName(), + description.getVersion(), + description.getDescription(), + description.getUrl(), + description.getAuthors(), + dependencies, + source, + mainClass + ); + } - for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) { - dependencies.add(toDependencyMeta(dependency)); - } - - return new JavaVelocityPluginDescription( - description.getId(), - description.getName(), - description.getVersion(), - description.getDescription(), - description.getUrl(), - description.getAuthors(), - dependencies, - source, - mainClass - ); - } - - private static PluginDependency toDependencyMeta(SerializedPluginDescription.Dependency dependency) { - return new PluginDependency( - dependency.getId(), - null, // TODO Implement version matching in dependency annotation - dependency.isOptional() - ); - } + private static PluginDependency toDependencyMeta( + SerializedPluginDescription.Dependency dependency) { + return new PluginDependency( + dependency.getId(), + null, // TODO Implement version matching in dependency annotation + dependency.isOptional() + ); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java index b16e58d0a..743a17067 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java @@ -2,14 +2,14 @@ package com.velocitypowered.proxy.plugin.loader; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; - import java.nio.file.Path; /** * This interface is used for loading plugins. */ public interface PluginLoader { - PluginDescription loadPlugin(Path source) throws Exception; - PluginContainer createPlugin(PluginDescription plugin) throws Exception; + PluginDescription loadPlugin(Path source) throws Exception; + + PluginContainer createPlugin(PluginDescription plugin) throws Exception; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java index d7cea4c4a..c7975c55b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginContainer.java @@ -2,25 +2,25 @@ package com.velocitypowered.proxy.plugin.loader; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; - import java.util.Optional; public class VelocityPluginContainer implements PluginContainer { - private final PluginDescription description; - private final Object instance; - public VelocityPluginContainer(PluginDescription description, Object instance) { - this.description = description; - this.instance = instance; - } + private final PluginDescription description; + private final Object instance; - @Override - public PluginDescription getDescription() { - return this.description; - } + public VelocityPluginContainer(PluginDescription description, Object instance) { + this.description = description; + this.instance = instance; + } - @Override - public Optional getInstance() { - return Optional.ofNullable(instance); - } + @Override + public PluginDescription getDescription() { + return this.description; + } + + @Override + public Optional getInstance() { + return Optional.ofNullable(instance); + } } 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 d20b515ad..7d0d0d7a8 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 @@ -1,98 +1,99 @@ package com.velocitypowered.proxy.plugin.loader; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.meta.PluginDependency; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; - -import static com.google.common.base.Preconditions.checkNotNull; +import org.checkerframework.checker.nullness.qual.Nullable; public class VelocityPluginDescription implements PluginDescription { - private final String id; - private final @Nullable String name; - private final @Nullable String version; - private final @Nullable String description; - private final @Nullable String url; - private final List authors; - private final Map dependencies; - private final Path source; - public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version, @Nullable String description, @Nullable String url, - @Nullable List authors, Collection dependencies, Path source) { - this.id = checkNotNull(id, "id"); - this.name = Strings.emptyToNull(name); - this.version = Strings.emptyToNull(version); - this.description = Strings.emptyToNull(description); - this.url = Strings.emptyToNull(url); - this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors); - this.dependencies = Maps.uniqueIndex(dependencies, d -> d == null ? null : d.getId()); - this.source = source; - } + private final String id; + private final @Nullable String name; + private final @Nullable String version; + private final @Nullable String description; + private final @Nullable String url; + private final List authors; + private final Map dependencies; + private final Path source; - @Override - public String getId() { - return id; - } + public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version, + @Nullable String description, @Nullable String url, + @Nullable List authors, Collection dependencies, Path source) { + this.id = checkNotNull(id, "id"); + this.name = Strings.emptyToNull(name); + this.version = Strings.emptyToNull(version); + this.description = Strings.emptyToNull(description); + this.url = Strings.emptyToNull(url); + this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors); + this.dependencies = Maps.uniqueIndex(dependencies, d -> d == null ? null : d.getId()); + this.source = source; + } - @Override - public Optional getName() { - return Optional.ofNullable(name); - } + @Override + public String getId() { + return id; + } - @Override - public Optional getVersion() { - return Optional.ofNullable(version); - } + @Override + public Optional getName() { + return Optional.ofNullable(name); + } - @Override - public Optional getDescription() { - return Optional.ofNullable(description); - } + @Override + public Optional getVersion() { + return Optional.ofNullable(version); + } - @Override - public Optional getUrl() { - return Optional.ofNullable(url); - } + @Override + public Optional getDescription() { + return Optional.ofNullable(description); + } - @Override - public List getAuthors() { - return authors; - } + @Override + public Optional getUrl() { + return Optional.ofNullable(url); + } - @Override - public Collection getDependencies() { - return dependencies.values(); - } + @Override + public List getAuthors() { + return authors; + } - @Override - public Optional getDependency(String id) { - return Optional.ofNullable(dependencies.get(id)); - } + @Override + public Collection getDependencies() { + return dependencies.values(); + } - @Override - public Optional getSource() { - return Optional.ofNullable(source); - } + @Override + public Optional getDependency(String id) { + return Optional.ofNullable(dependencies.get(id)); + } - @Override - public String toString() { - return "VelocityPluginDescription{" + - "id='" + id + '\'' + - ", name='" + name + '\'' + - ", version='" + version + '\'' + - ", description='" + description + '\'' + - ", url='" + url + '\'' + - ", authors=" + authors + - ", dependencies=" + dependencies + - ", source=" + source + - '}'; - } + @Override + public Optional getSource() { + return Optional.ofNullable(source); + } + + @Override + public String toString() { + return "VelocityPluginDescription{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", version='" + version + '\'' + + ", description='" + description + '\'' + + ", url='" + url + '\'' + + ", authors=" + authors + + ", dependencies=" + dependencies + + ", source=" + source + + '}'; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java index a73aaec78..2948ee2d8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java @@ -1,25 +1,27 @@ package com.velocitypowered.proxy.plugin.loader.java; -import com.velocitypowered.api.plugin.meta.PluginDependency; -import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; - import static com.google.common.base.Preconditions.checkNotNull; +import com.velocitypowered.api.plugin.meta.PluginDependency; +import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; + public class JavaVelocityPluginDescription extends VelocityPluginDescription { - private final Class mainClass; - public JavaVelocityPluginDescription(String id, @Nullable String name, @Nullable String version, @Nullable String description, @Nullable String url, - @Nullable List authors, Collection dependencies, Path source, Class mainClass) { - super(id, name, version, description, url, authors, dependencies, source); - this.mainClass = checkNotNull(mainClass); - } + private final Class mainClass; - public Class getMainClass() { - return mainClass; - } + public JavaVelocityPluginDescription(String id, @Nullable String name, @Nullable String version, + @Nullable String description, @Nullable String url, + @Nullable List authors, Collection dependencies, Path source, + Class mainClass) { + super(id, name, version, description, url, authors, dependencies, source); + this.mainClass = checkNotNull(mainClass); + } + + public Class getMainClass() { + return mainClass; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java index 66e116eeb..d35c9382a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/VelocityPluginModule.java @@ -8,30 +8,32 @@ import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; +import java.nio.file.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.file.Path; - public class VelocityPluginModule implements Module { - private final ProxyServer server; - private final JavaVelocityPluginDescription description; - private final Path basePluginPath; - public VelocityPluginModule(ProxyServer server, JavaVelocityPluginDescription description, Path basePluginPath) { - this.server = server; - this.description = description; - this.basePluginPath = basePluginPath; - } + private final ProxyServer server; + private final JavaVelocityPluginDescription description; + private final Path basePluginPath; - @Override - public void configure(Binder binder) { - binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId())); - binder.bind(ProxyServer.class).toInstance(server); - binder.bind(Path.class).annotatedWith(DataDirectory.class).toInstance(basePluginPath.resolve(description.getId())); - binder.bind(PluginDescription.class).toInstance(description); - binder.bind(PluginManager.class).toInstance(server.getPluginManager()); - binder.bind(EventManager.class).toInstance(server.getEventManager()); - binder.bind(CommandManager.class).toInstance(server.getCommandManager()); - } + public VelocityPluginModule(ProxyServer server, JavaVelocityPluginDescription description, + Path basePluginPath) { + this.server = server; + this.description = description; + this.basePluginPath = basePluginPath; + } + + @Override + public void configure(Binder binder) { + binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId())); + binder.bind(ProxyServer.class).toInstance(server); + binder.bind(Path.class).annotatedWith(DataDirectory.class) + .toInstance(basePluginPath.resolve(description.getId())); + binder.bind(PluginDescription.class).toInstance(description); + binder.bind(PluginManager.class).toInstance(server.getPluginManager()); + binder.bind(EventManager.class).toInstance(server.getEventManager()); + binder.bind(CommandManager.class).toInstance(server.getCommandManager()); + } } 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 c222ad643..8be6c7609 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,96 +8,107 @@ 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 java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; import org.checkerframework.checker.nullness.qual.NonNull; -import java.util.*; - public class PluginDependencyUtils { - private PluginDependencyUtils() { - throw new AssertionError(); + + private PluginDependencyUtils() { + throw new AssertionError(); + } + + 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, d -> d == null ? null : d.getId()); + + // Add edges + for (PluginDescription description : candidates) { + graph.addNode(description); + + for (PluginDependency dependency : description.getDependencies()) { + PluginDescription in = candidateMap.get(dependency.getId()); + + if (in != null) { + graph.putEdge(description, in); + } + } } - 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, d -> d == null ? null : d.getId()); + // Find nodes that have no edges + Queue noEdges = getNoDependencyCandidates(graph); - // Add edges - for (PluginDescription description : candidates) { - graph.addNode(description); + // Actually run Kahn's algorithm + List sorted = new ArrayList<>(); + while (!noEdges.isEmpty()) { + PluginDescription candidate = noEdges.remove(); + sorted.add(candidate); - for (PluginDependency dependency : description.getDependencies()) { - PluginDescription in = candidateMap.get(dependency.getId()); + for (PluginDescription node : ImmutableSet.copyOf(graph.adjacentNodes(candidate))) { + graph.removeEdge(node, candidate); - if (in != null) { - graph.putEdge(description, in); - } - } + if (graph.adjacentNodes(node).isEmpty()) { + noEdges.add(node); } - - // Find nodes that have no edges - Queue noEdges = getNoDependencyCandidates(graph); - - // Actually run Kahn's algorithm - List sorted = new ArrayList<>(); - while (!noEdges.isEmpty()) { - PluginDescription candidate = noEdges.remove(); - sorted.add(candidate); - - for (PluginDescription node : ImmutableSet.copyOf(graph.adjacentNodes(candidate))) { - graph.removeEdge(node, candidate); - - if (graph.adjacentNodes(node).isEmpty()) { - noEdges.add(node); - } - } - } - - if (!graph.edges().isEmpty()) { - throw new IllegalStateException("Plugin circular dependency found: " + createLoopInformation(graph)); - } - - return sorted; + } } - public static Queue getNoDependencyCandidates(Graph graph) { - Queue found = new ArrayDeque<>(); - - for (PluginDescription node : graph.nodes()) { - if (graph.outDegree(node) == 0) { - found.add(node); - } - } - - return found; + if (!graph.edges().isEmpty()) { + throw new IllegalStateException( + "Plugin circular dependency found: " + createLoopInformation(graph)); } - public static String createLoopInformation(Graph graph) { - StringBuilder repr = new StringBuilder("{"); - for (EndpointPair edge : graph.edges()) { - repr.append(edge.target().getId()).append(": ["); - repr.append(dependencyLoopInfo(graph, edge.target(), new HashSet<>())).append("], "); - } - repr.setLength(repr.length() - 2); - repr.append("}"); - return repr.toString(); + return sorted; + } + + public static Queue getNoDependencyCandidates(Graph graph) { + Queue found = new ArrayDeque<>(); + + for (PluginDescription node : graph.nodes()) { + if (graph.outDegree(node) == 0) { + found.add(node); + } } - private static String dependencyLoopInfo(Graph graph, PluginDescription dependency, Set seen) { - StringBuilder repr = new StringBuilder(); - for (PluginDescription pd : graph.adjacentNodes(dependency)) { - if (seen.add(pd)) { - repr.append(pd.getId()).append(": [").append(dependencyLoopInfo(graph, dependency, seen)).append("], "); - } else { - repr.append(pd.getId()).append(", "); - } - } + return found; + } - if (repr.length() != 0) { - repr.setLength(repr.length() - 2); - return repr.toString(); - } else { - return ""; - } + public static String createLoopInformation(Graph graph) { + StringBuilder repr = new StringBuilder("{"); + for (EndpointPair edge : graph.edges()) { + repr.append(edge.target().getId()).append(": ["); + repr.append(dependencyLoopInfo(graph, edge.target(), new HashSet<>())).append("], "); } + repr.setLength(repr.length() - 2); + repr.append("}"); + return repr.toString(); + } + + private static String dependencyLoopInfo(Graph graph, + PluginDescription dependency, Set seen) { + StringBuilder repr = new StringBuilder(); + for (PluginDescription pd : graph.adjacentNodes(dependency)) { + if (seen.add(pd)) { + repr.append(pd.getId()).append(": [").append(dependencyLoopInfo(graph, dependency, seen)) + .append("], "); + } else { + repr.append(pd.getId()).append(", "); + } + } + + if (repr.length() != 0) { + repr.setLength(repr.length() - 2); + return repr.toString(); + } else { + return ""; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java index 1118b7625..66b37cdd1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java @@ -4,9 +4,10 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import io.netty.buffer.ByteBuf; public interface MinecraftPacket { - void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion); - void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion); + void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion); - boolean handle(MinecraftSessionHandler handler); + void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion); + + boolean handle(MinecraftSessionHandler handler); } 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 acba39c10..c2ad94b46 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java @@ -2,56 +2,59 @@ package com.velocitypowered.proxy.protocol; import com.google.common.primitives.ImmutableIntArray; -public enum ProtocolConstants { ; - public static final int LEGACY = -1; +public enum ProtocolConstants { + ; + public static final int LEGACY = -1; - public static final int MINECRAFT_1_8 = 47; - public static final int MINECRAFT_1_9 = 107; - public static final int MINECRAFT_1_9_1 = 108; - public static final int MINECRAFT_1_9_2 = 109; - public static final int MINECRAFT_1_9_4 = 110; - public static final int MINECRAFT_1_10 = 210; - public static final int MINECRAFT_1_11 = 315; - public static final int MINECRAFT_1_11_1 = 316; - public static final int MINECRAFT_1_12 = 335; - public static final int MINECRAFT_1_12_1 = 338; - public static final int MINECRAFT_1_12_2 = 340; - public static final int MINECRAFT_1_13 = 393; - public static final int MINECRAFT_1_13_1 = 401; - public static final int MINECRAFT_1_13_2 = 404; + public static final int MINECRAFT_1_8 = 47; + public static final int MINECRAFT_1_9 = 107; + public static final int MINECRAFT_1_9_1 = 108; + public static final int MINECRAFT_1_9_2 = 109; + public static final int MINECRAFT_1_9_4 = 110; + public static final int MINECRAFT_1_10 = 210; + public static final int MINECRAFT_1_11 = 315; + public static final int MINECRAFT_1_11_1 = 316; + public static final int MINECRAFT_1_12 = 335; + public static final int MINECRAFT_1_12_1 = 338; + public static final int MINECRAFT_1_12_2 = 340; + public static final int MINECRAFT_1_13 = 393; + public static final int MINECRAFT_1_13_1 = 401; + public static final int MINECRAFT_1_13_2 = 404; - public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_8; - public static final int MAXIMUM_GENERIC_VERSION = MINECRAFT_1_13_2; + public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_8; + public static final int MAXIMUM_GENERIC_VERSION = MINECRAFT_1_13_2; - public static final String SUPPORTED_GENERIC_VERSION_STRING = "1.8-1.13.2"; + public static final String SUPPORTED_GENERIC_VERSION_STRING = "1.8-1.13.2"; - public static final ImmutableIntArray SUPPORTED_VERSIONS = ImmutableIntArray.of( - MINECRAFT_1_8, - MINECRAFT_1_9, - MINECRAFT_1_9_1, - MINECRAFT_1_9_2, - MINECRAFT_1_9_4, - MINECRAFT_1_10, - MINECRAFT_1_11, - MINECRAFT_1_11_1, - MINECRAFT_1_12, - MINECRAFT_1_12_1, - MINECRAFT_1_12_2, - MINECRAFT_1_13, - MINECRAFT_1_13_1, - MINECRAFT_1_13_2 - ); + public static final ImmutableIntArray SUPPORTED_VERSIONS = ImmutableIntArray.of( + MINECRAFT_1_8, + MINECRAFT_1_9, + MINECRAFT_1_9_1, + MINECRAFT_1_9_2, + MINECRAFT_1_9_4, + MINECRAFT_1_10, + MINECRAFT_1_11, + MINECRAFT_1_11_1, + MINECRAFT_1_12, + MINECRAFT_1_12_1, + MINECRAFT_1_12_2, + MINECRAFT_1_13, + MINECRAFT_1_13_1, + MINECRAFT_1_13_2 + ); - public static boolean isSupported(int version) { - return SUPPORTED_VERSIONS.contains(version); - } - - public enum Direction { - SERVERBOUND, - CLIENTBOUND; - - public StateRegistry.PacketRegistry.ProtocolVersion getProtocol(StateRegistry state, int protocolVersion) { - return (this == SERVERBOUND ? state.SERVERBOUND : state.CLIENTBOUND).getVersion(protocolVersion); - } + public static boolean isSupported(int version) { + return SUPPORTED_VERSIONS.contains(version); + } + + public enum Direction { + SERVERBOUND, + 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/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index 35614c817..ded089bd5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -4,113 +4,119 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.util.GameProfile; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; - import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; -public enum ProtocolUtils { ; - private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB +public enum ProtocolUtils { + ; + private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB - public static int readVarInt(ByteBuf buf) { - int i = 0; - int j = 0; - while (true) { - int k = buf.readByte(); - i |= (k & 0x7F) << j++ * 7; - if (j > 5) throw new RuntimeException("VarInt too big"); - if ((k & 0x80) != 128) break; - } - return i; + public static int readVarInt(ByteBuf buf) { + int i = 0; + int j = 0; + while (true) { + int k = buf.readByte(); + i |= (k & 0x7F) << j++ * 7; + if (j > 5) { + throw new RuntimeException("VarInt too big"); + } + if ((k & 0x80) != 128) { + break; + } } + return i; + } - public static void writeVarInt(ByteBuf buf, int value) { - while (true) { - if ((value & 0xFFFFFF80) == 0) { - buf.writeByte(value); - return; - } + public static void writeVarInt(ByteBuf buf, int value) { + while (true) { + if ((value & 0xFFFFFF80) == 0) { + buf.writeByte(value); + return; + } - buf.writeByte(value & 0x7F | 0x80); - value >>>= 7; - } + buf.writeByte(value & 0x7F | 0x80); + value >>>= 7; } + } - public static String readString(ByteBuf buf) { - return readString(buf, DEFAULT_MAX_STRING_SIZE); - } + public static String readString(ByteBuf buf) { + return readString(buf, DEFAULT_MAX_STRING_SIZE); + } - public static String readString(ByteBuf buf, int cap) { - int length = readVarInt(buf); - Preconditions.checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap); - String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); - buf.skipBytes(length); - return str; - } + public static String readString(ByteBuf buf, int cap) { + int length = readVarInt(buf); + Preconditions + .checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap); + String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); + buf.skipBytes(length); + return str; + } - public static void writeString(ByteBuf buf, String str) { - int size = ByteBufUtil.utf8Bytes(str); - writeVarInt(buf, size); - ByteBufUtil.writeUtf8(buf, str); - } + public static void writeString(ByteBuf buf, String str) { + int size = ByteBufUtil.utf8Bytes(str); + writeVarInt(buf, size); + ByteBufUtil.writeUtf8(buf, str); + } - public static byte[] readByteArray(ByteBuf buf) { - return readByteArray(buf, DEFAULT_MAX_STRING_SIZE); - } + public static byte[] readByteArray(ByteBuf buf) { + return readByteArray(buf, DEFAULT_MAX_STRING_SIZE); + } - public static byte[] readByteArray(ByteBuf buf, int cap) { - int length = readVarInt(buf); - Preconditions.checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap); - byte[] array = new byte[length]; - buf.readBytes(array); - return array; - } + public static byte[] readByteArray(ByteBuf buf, int cap) { + int length = readVarInt(buf); + Preconditions + .checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap); + byte[] array = new byte[length]; + buf.readBytes(array); + return array; + } - public static void writeByteArray(ByteBuf buf, byte[] array) { - writeVarInt(buf, array.length); - buf.writeBytes(array); - } + public static void writeByteArray(ByteBuf buf, byte[] array) { + writeVarInt(buf, array.length); + buf.writeBytes(array); + } - public static UUID readUuid(ByteBuf buf) { - long msb = buf.readLong(); - long lsb = buf.readLong(); - return new UUID(msb, lsb); - } + public static UUID readUuid(ByteBuf buf) { + long msb = buf.readLong(); + long lsb = buf.readLong(); + return new UUID(msb, lsb); + } - public static void writeUuid(ByteBuf buf, UUID uuid) { - buf.writeLong(uuid.getMostSignificantBits()); - buf.writeLong(uuid.getLeastSignificantBits()); - } + public static void writeUuid(ByteBuf buf, UUID uuid) { + buf.writeLong(uuid.getMostSignificantBits()); + buf.writeLong(uuid.getLeastSignificantBits()); + } - public static void writeProperties(ByteBuf buf, List properties) { - writeVarInt(buf, properties.size()); - for (GameProfile.Property property : properties) { - writeString(buf, property.getName()); - writeString(buf, property.getValue()); - String signature = property.getSignature(); - if (signature != null) { - buf.writeBoolean(true); - writeString(buf, signature); - } else { - buf.writeBoolean(false); - } - } + public static void writeProperties(ByteBuf buf, List properties) { + writeVarInt(buf, properties.size()); + for (GameProfile.Property property : properties) { + writeString(buf, property.getName()); + writeString(buf, property.getValue()); + String signature = property.getSignature(); + if (signature != null) { + buf.writeBoolean(true); + writeString(buf, signature); + } else { + buf.writeBoolean(false); + } } + } - public static List readProperties(ByteBuf buf) { - List properties = new ArrayList<>(); - int size = readVarInt(buf); - for (int i = 0; i < size; i++) { - String name = readString(buf); - String value = readString(buf); - String signature = ""; - boolean hasSignature = buf.readBoolean(); - if (hasSignature) { - signature = readString(buf); - } - properties.add(new GameProfile.Property(name, value, signature)); - } - return properties; + public static List readProperties(ByteBuf buf) { + List properties = new ArrayList<>(); + int size = readVarInt(buf); + for (int i = 0; i < size; i++) { + String name = readString(buf); + String value = readString(buf); + String signature = ""; + boolean hasSignature = buf.readBoolean(); + if (hasSignature) { + signature = readString(buf); + } + properties.add(new GameProfile.Property(name, value, signature)); } + return properties; + } } 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 61b332ff9..844f1eb90 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -1,305 +1,362 @@ package com.velocitypowered.proxy.protocol; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.Direction; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_10; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_11; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_11_1; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13_1; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13_2; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_8; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9_1; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9_2; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9_4; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINIMUM_GENERIC_VERSION; + import com.google.common.primitives.ImmutableIntArray; -import com.velocitypowered.proxy.protocol.packet.*; +import com.velocitypowered.proxy.protocol.packet.BossBar; +import com.velocitypowered.proxy.protocol.packet.Chat; +import com.velocitypowered.proxy.protocol.packet.ClientSettings; +import com.velocitypowered.proxy.protocol.packet.Disconnect; +import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; +import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; +import com.velocitypowered.proxy.protocol.packet.Handshake; +import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; +import com.velocitypowered.proxy.protocol.packet.JoinGame; +import com.velocitypowered.proxy.protocol.packet.KeepAlive; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import com.velocitypowered.proxy.protocol.packet.PlayerListItem; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.packet.Respawn; +import com.velocitypowered.proxy.protocol.packet.ServerLogin; +import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; +import com.velocitypowered.proxy.protocol.packet.SetCompression; +import com.velocitypowered.proxy.protocol.packet.StatusPing; +import com.velocitypowered.proxy.protocol.packet.StatusRequest; +import com.velocitypowered.proxy.protocol.packet.StatusResponse; +import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; +import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; +import com.velocitypowered.proxy.protocol.packet.TitlePacket; 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; - -import static com.velocitypowered.proxy.protocol.ProtocolConstants.*; +import org.checkerframework.checker.nullness.qual.Nullable; public enum StateRegistry { - - HANDSHAKE { - { - SERVERBOUND.register(Handshake.class, Handshake::new, - genericMappings(0x00)); - } - }, - STATUS { - { - SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE, - genericMappings(0x00)); - SERVERBOUND.register(StatusPing.class, StatusPing::new, - genericMappings(0x01)); - CLIENTBOUND.register(StatusResponse.class, StatusResponse::new, - genericMappings(0x00)); - CLIENTBOUND.register(StatusPing.class, StatusPing::new, - genericMappings(0x01)); - } - }, - PLAY { - { - SERVERBOUND.fallback = false; - CLIENTBOUND.fallback = false; + HANDSHAKE { + { + SERVERBOUND.register(Handshake.class, Handshake::new, + genericMappings(0x00)); + } + }, + STATUS { + { + SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE, + genericMappings(0x00)); + SERVERBOUND.register(StatusPing.class, StatusPing::new, + genericMappings(0x01)); - SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new, - map(0x14, MINECRAFT_1_8, false), - map(0x01, MINECRAFT_1_9, false), - map(0x02, MINECRAFT_1_12, false), - map(0x01, MINECRAFT_1_12_1, false)); - SERVERBOUND.register(Chat.class, Chat::new, - map(0x01, MINECRAFT_1_8, false), - map(0x02, MINECRAFT_1_9, false), - map(0x03, MINECRAFT_1_12, false), - map(0x02, MINECRAFT_1_12_1, false), - map(0x02, MINECRAFT_1_13, false)); - SERVERBOUND.register(ClientSettings.class, ClientSettings::new, - map(0x15, MINECRAFT_1_8, false), - map(0x04, MINECRAFT_1_9, false), - map(0x05, MINECRAFT_1_12, false), - map(0x04, MINECRAFT_1_12_1, false), - map(0x04, MINECRAFT_1_13, false)); - SERVERBOUND.register(PluginMessage.class, PluginMessage::new, - map(0x17, MINECRAFT_1_8, false), - map(0x09, MINECRAFT_1_9, false), - map(0x0A, MINECRAFT_1_12, false), - map(0x09, MINECRAFT_1_12_1, false), - map(0x0A, MINECRAFT_1_13, false)); - SERVERBOUND.register(KeepAlive.class, KeepAlive::new, - map(0x00, MINECRAFT_1_8, false), - map(0x0B, MINECRAFT_1_9, false), - map(0x0C, MINECRAFT_1_12, false), - map(0x0B, MINECRAFT_1_12_1, false), - map(0x0E, MINECRAFT_1_13, false)); + CLIENTBOUND.register(StatusResponse.class, StatusResponse::new, + genericMappings(0x00)); + CLIENTBOUND.register(StatusPing.class, StatusPing::new, + genericMappings(0x01)); + } + }, + PLAY { + { + SERVERBOUND.fallback = false; + CLIENTBOUND.fallback = false; - CLIENTBOUND.register(BossBar.class, BossBar::new, - map(0x0C, MINECRAFT_1_9, false), - map(0x0C, MINECRAFT_1_12, false), - map(0x0C, MINECRAFT_1_13, false)); - CLIENTBOUND.register(Chat.class, Chat::new, - map(0x02, MINECRAFT_1_8, true), - map(0x0F, MINECRAFT_1_9, true), - map(0x0F, MINECRAFT_1_12, true), - map(0x0E, MINECRAFT_1_13, true)); - CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new, - map(0x3A, MINECRAFT_1_8, false), - map(0x0E, MINECRAFT_1_9, false), - map(0x0E, MINECRAFT_1_12, false)); - CLIENTBOUND.register(PluginMessage.class, PluginMessage::new, - map(0x3F, MINECRAFT_1_8, false), - map(0x18, MINECRAFT_1_9, false), - map(0x18, MINECRAFT_1_12, false), - map(0x19, MINECRAFT_1_13, false)); - CLIENTBOUND.register(Disconnect.class, Disconnect::new, - map(0x40, MINECRAFT_1_8, false), - map(0x1A, MINECRAFT_1_9, false), - map(0x1A, MINECRAFT_1_12, false), - map(0x1B, MINECRAFT_1_13, false)); - CLIENTBOUND.register(KeepAlive.class, KeepAlive::new, - map(0x00, MINECRAFT_1_8, false), - map(0x1F, MINECRAFT_1_9, false), - map(0x1F, MINECRAFT_1_12, false), - map(0x21, MINECRAFT_1_13, false)); - CLIENTBOUND.register(JoinGame.class, JoinGame::new, - map(0x01, MINECRAFT_1_8, false), - map(0x23, MINECRAFT_1_9, false), - map(0x23, MINECRAFT_1_12, false), - map(0x25, MINECRAFT_1_13, false)); - CLIENTBOUND.register(Respawn.class, Respawn::new, - map(0x07, MINECRAFT_1_8, true), - map(0x33, MINECRAFT_1_9, true), - map(0x34, MINECRAFT_1_12, true), - map(0x35, MINECRAFT_1_12_2, true), - map(0x38, MINECRAFT_1_13, true)); - CLIENTBOUND.register(HeaderAndFooter.class, HeaderAndFooter::new, - map(0x47, MINECRAFT_1_8, true), - map(0x48, MINECRAFT_1_9, true), - map(0x47, MINECRAFT_1_9_4, true), - map(0x49, MINECRAFT_1_12, true), - map(0x4A, MINECRAFT_1_12_1, true), - map(0x4E, MINECRAFT_1_13, true)); - CLIENTBOUND.register(TitlePacket.class, TitlePacket::new, - map(0x45, MINECRAFT_1_8, true), - map(0x45, MINECRAFT_1_9, true), - map(0x47, MINECRAFT_1_12, true), - map(0x48, MINECRAFT_1_12_1, true), - map(0x4B, MINECRAFT_1_13, true)); - CLIENTBOUND.register(PlayerListItem.class, PlayerListItem::new, - map(0x38, MINECRAFT_1_8, false), - map(0x2D, MINECRAFT_1_9, false), - map(0x2D, MINECRAFT_1_12, false), - map(0x2E, MINECRAFT_1_12_1, false), - map(0x30, MINECRAFT_1_13, false)); - } - }, - LOGIN { - { - SERVERBOUND.register(ServerLogin.class, ServerLogin::new, - genericMappings(0x00)); - SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new, - genericMappings(0x01)); - SERVERBOUND.register(LoginPluginResponse.class, LoginPluginResponse::new, - map(0x02, MINECRAFT_1_13, false)); + SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new, + map(0x14, MINECRAFT_1_8, false), + map(0x01, MINECRAFT_1_9, false), + map(0x02, MINECRAFT_1_12, false), + map(0x01, MINECRAFT_1_12_1, false)); + SERVERBOUND.register(Chat.class, Chat::new, + map(0x01, MINECRAFT_1_8, false), + map(0x02, MINECRAFT_1_9, false), + map(0x03, MINECRAFT_1_12, false), + map(0x02, MINECRAFT_1_12_1, false), + map(0x02, MINECRAFT_1_13, false)); + SERVERBOUND.register(ClientSettings.class, ClientSettings::new, + map(0x15, MINECRAFT_1_8, false), + map(0x04, MINECRAFT_1_9, false), + map(0x05, MINECRAFT_1_12, false), + map(0x04, MINECRAFT_1_12_1, false), + map(0x04, MINECRAFT_1_13, false)); + SERVERBOUND.register(PluginMessage.class, PluginMessage::new, + map(0x17, MINECRAFT_1_8, false), + map(0x09, MINECRAFT_1_9, false), + map(0x0A, MINECRAFT_1_12, false), + map(0x09, MINECRAFT_1_12_1, false), + map(0x0A, MINECRAFT_1_13, false)); + SERVERBOUND.register(KeepAlive.class, KeepAlive::new, + map(0x00, MINECRAFT_1_8, false), + map(0x0B, MINECRAFT_1_9, false), + map(0x0C, MINECRAFT_1_12, false), + map(0x0B, MINECRAFT_1_12_1, false), + map(0x0E, MINECRAFT_1_13, false)); - CLIENTBOUND.register(Disconnect.class, Disconnect::new, - genericMappings(0x00)); - CLIENTBOUND.register(EncryptionRequest.class, EncryptionRequest::new, - genericMappings(0x01)); - CLIENTBOUND.register(ServerLoginSuccess.class, ServerLoginSuccess::new, - genericMappings(0x02)); - CLIENTBOUND.register(SetCompression.class, SetCompression::new, - genericMappings(0x03)); - CLIENTBOUND.register(LoginPluginMessage.class, LoginPluginMessage::new, - map(0x04, MINECRAFT_1_13, false)); + CLIENTBOUND.register(BossBar.class, BossBar::new, + map(0x0C, MINECRAFT_1_9, false), + map(0x0C, MINECRAFT_1_12, false), + map(0x0C, MINECRAFT_1_13, false)); + CLIENTBOUND.register(Chat.class, Chat::new, + map(0x02, MINECRAFT_1_8, true), + map(0x0F, MINECRAFT_1_9, true), + map(0x0F, MINECRAFT_1_12, true), + map(0x0E, MINECRAFT_1_13, true)); + CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new, + map(0x3A, MINECRAFT_1_8, false), + map(0x0E, MINECRAFT_1_9, false), + map(0x0E, MINECRAFT_1_12, false)); + CLIENTBOUND.register(PluginMessage.class, PluginMessage::new, + map(0x3F, MINECRAFT_1_8, false), + map(0x18, MINECRAFT_1_9, false), + map(0x18, MINECRAFT_1_12, false), + map(0x19, MINECRAFT_1_13, false)); + CLIENTBOUND.register(Disconnect.class, Disconnect::new, + map(0x40, MINECRAFT_1_8, false), + map(0x1A, MINECRAFT_1_9, false), + map(0x1A, MINECRAFT_1_12, false), + map(0x1B, MINECRAFT_1_13, false)); + CLIENTBOUND.register(KeepAlive.class, KeepAlive::new, + map(0x00, MINECRAFT_1_8, false), + map(0x1F, MINECRAFT_1_9, false), + map(0x1F, MINECRAFT_1_12, false), + map(0x21, MINECRAFT_1_13, false)); + CLIENTBOUND.register(JoinGame.class, JoinGame::new, + map(0x01, MINECRAFT_1_8, false), + map(0x23, MINECRAFT_1_9, false), + map(0x23, MINECRAFT_1_12, false), + map(0x25, MINECRAFT_1_13, false)); + CLIENTBOUND.register(Respawn.class, Respawn::new, + map(0x07, MINECRAFT_1_8, true), + map(0x33, MINECRAFT_1_9, true), + map(0x34, MINECRAFT_1_12, true), + map(0x35, MINECRAFT_1_12_2, true), + map(0x38, MINECRAFT_1_13, true)); + CLIENTBOUND.register(HeaderAndFooter.class, HeaderAndFooter::new, + map(0x47, MINECRAFT_1_8, true), + map(0x48, MINECRAFT_1_9, true), + map(0x47, MINECRAFT_1_9_4, true), + map(0x49, MINECRAFT_1_12, true), + map(0x4A, MINECRAFT_1_12_1, true), + map(0x4E, MINECRAFT_1_13, true)); + CLIENTBOUND.register(TitlePacket.class, TitlePacket::new, + map(0x45, MINECRAFT_1_8, true), + map(0x45, MINECRAFT_1_9, true), + map(0x47, MINECRAFT_1_12, true), + map(0x48, MINECRAFT_1_12_1, true), + map(0x4B, MINECRAFT_1_13, true)); + CLIENTBOUND.register(PlayerListItem.class, PlayerListItem::new, + map(0x38, MINECRAFT_1_8, false), + map(0x2D, MINECRAFT_1_9, false), + map(0x2D, MINECRAFT_1_12, false), + map(0x2E, MINECRAFT_1_12_1, false), + map(0x30, MINECRAFT_1_13, false)); + } + }, + LOGIN { + { + SERVERBOUND.register(ServerLogin.class, ServerLogin::new, + genericMappings(0x00)); + SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new, + genericMappings(0x01)); + SERVERBOUND.register(LoginPluginResponse.class, LoginPluginResponse::new, + map(0x02, MINECRAFT_1_13, false)); + + CLIENTBOUND.register(Disconnect.class, Disconnect::new, + genericMappings(0x00)); + CLIENTBOUND.register(EncryptionRequest.class, EncryptionRequest::new, + genericMappings(0x01)); + CLIENTBOUND.register(ServerLoginSuccess.class, ServerLoginSuccess::new, + genericMappings(0x02)); + CLIENTBOUND.register(SetCompression.class, SetCompression::new, + genericMappings(0x03)); + CLIENTBOUND.register(LoginPluginMessage.class, LoginPluginMessage::new, + map(0x04, MINECRAFT_1_13, false)); + } + }; + + public static final int STATUS_ID = 1; + public static final int LOGIN_ID = 2; + 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<>(); + + static { + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9, + ImmutableIntArray.of(MINECRAFT_1_9_1, MINECRAFT_1_9_2, MINECRAFT_1_9_4)); + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9_4, + ImmutableIntArray.of(MINECRAFT_1_10, MINECRAFT_1_11, MINECRAFT_1_11_1)); + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12, ImmutableIntArray.of(MINECRAFT_1_12_1)); + LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12_1, ImmutableIntArray.of(MINECRAFT_1_12_2)); + LINKED_PROTOCOL_VERSIONS + .put(MINECRAFT_1_13, ImmutableIntArray.of(MINECRAFT_1_13_1, MINECRAFT_1_13_2)); + } + + private final ProtocolConstants.Direction direction; + private final IntObjectMap versions = new IntObjectHashMap<>(16); + private boolean fallback = true; + + public PacketRegistry(Direction direction) { + this.direction = direction; + 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 (fallback) { + return getVersion(MINIMUM_GENERIC_VERSION); } + throw new IllegalArgumentException("Could not find data for protocol version " + version); + } + return result; + } + + public

void register(Class

clazz, Supplier

packetSupplier, + PacketMapping... mappings) { + if (mappings.length == 0) { + throw new IllegalArgumentException("At least one mapping must be provided."); + } + + for (final PacketMapping mapping : mappings) { + ProtocolVersion version = this.versions.get(mapping.protocolVersion); + if (version == null) { + throw new IllegalArgumentException("Unknown protocol version " + mapping.protocolVersion); + } + if (!mapping.encodeOnly) { + version.packetIdToSupplier.put(mapping.id, packetSupplier); + } + version.packetClassToId.put(clazz, mapping.id); + + ImmutableIntArray linked = LINKED_PROTOCOL_VERSIONS.get(mapping.protocolVersion); + if (linked != null) { + links: + for (int i = 0; i < linked.length(); i++) { + int linkedVersion = linked.get(i); + // Make sure that later mappings override this one. + for (PacketMapping m : mappings) { + if (linkedVersion == m.protocolVersion) { + continue links; + } + } + register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.encodeOnly)); + } + } + } + } + + public class ProtocolVersion { + + public final int version; + final IntObjectMap> packetIdToSupplier = new IntObjectHashMap<>( + 16, 0.5f); + final Object2IntMap> packetClassToId = new Object2IntOpenHashMap<>( + 16, 0.5f); + + ProtocolVersion(final int version) { + this.version = version; + this.packetClassToId.defaultReturnValue(Integer.MIN_VALUE); + } + + public @Nullable MinecraftPacket createPacket(final int id) { + final Supplier supplier = this.packetIdToSupplier.get(id); + if (supplier == null) { + return null; + } + return supplier.get(); + } + + public int getPacketId(final MinecraftPacket packet) { + final int id = this.packetClassToId.getInt(packet.getClass()); + if (id == Integer.MIN_VALUE) { + throw new IllegalArgumentException(String.format( + "Unable to find id for packet of type %s in %s protocol %s", + packet.getClass().getName(), PacketRegistry.this.direction, this.version + )); + } + return id; + } + } + } + + public static class PacketMapping { + + private final int id; + private final int protocolVersion; + private final boolean encodeOnly; + + public PacketMapping(int id, int protocolVersion, boolean packetDecoding) { + this.id = id; + this.protocolVersion = protocolVersion; + this.encodeOnly = packetDecoding; + } + + @Override + public String toString() { + return "PacketMapping{" + + "id=" + id + + ", protocolVersion=" + protocolVersion + + ", encodeOnly=" + encodeOnly + + '}'; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PacketMapping that = (PacketMapping) o; + return id == that.id && + protocolVersion == that.protocolVersion && + encodeOnly == that.encodeOnly; + } + + @Override + public int hashCode() { + return Objects.hash(id, protocolVersion, encodeOnly); + } + } + + /** + * Creates a PacketMapping using the provided arguments + * + * @param id Packet Id + * @param version Protocol version + * @param encodeOnly When true packet decoding will be disabled + * @return PacketMapping with the provided arguments + */ + private static PacketMapping map(int id, int version, boolean encodeOnly) { + return new PacketMapping(id, version, encodeOnly); + } + + private static PacketMapping[] genericMappings(int id) { + return new PacketMapping[]{ + map(id, MINECRAFT_1_8, false), + map(id, MINECRAFT_1_9, false), + map(id, MINECRAFT_1_12, false), + map(id, MINECRAFT_1_13, false) }; - - public static final int STATUS_ID = 1; - public static final int LOGIN_ID = 2; - 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<>(); - - static { - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9, ImmutableIntArray.of(MINECRAFT_1_9_1, MINECRAFT_1_9_2, MINECRAFT_1_9_4)); - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9_4, ImmutableIntArray.of(MINECRAFT_1_10, MINECRAFT_1_11, MINECRAFT_1_11_1)); - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12, ImmutableIntArray.of(MINECRAFT_1_12_1)); - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12_1, ImmutableIntArray.of(MINECRAFT_1_12_2)); - LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_13, ImmutableIntArray.of(MINECRAFT_1_13_1, MINECRAFT_1_13_2)); - } - - private final ProtocolConstants.Direction direction; - private final IntObjectMap versions = new IntObjectHashMap<>(16); - private boolean fallback = true; - - public PacketRegistry(Direction direction) { - this.direction = direction; - 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 (fallback) { - return getVersion(MINIMUM_GENERIC_VERSION); - } - throw new IllegalArgumentException("Could not find data for protocol version " + version); - } - return result; - } - - public

void register(Class

clazz, Supplier

packetSupplier, PacketMapping... mappings) { - if (mappings.length == 0) { - throw new IllegalArgumentException("At least one mapping must be provided."); - } - - for (final PacketMapping mapping : mappings) { - ProtocolVersion version = this.versions.get(mapping.protocolVersion); - if (version == null) { - throw new IllegalArgumentException("Unknown protocol version " + mapping.protocolVersion); - } - if (!mapping.encodeOnly) { - version.packetIdToSupplier.put(mapping.id, packetSupplier); - } - version.packetClassToId.put(clazz, mapping.id); - - ImmutableIntArray linked = LINKED_PROTOCOL_VERSIONS.get(mapping.protocolVersion); - if (linked != null) { - links: for (int i = 0; i < linked.length(); i++) { - int linkedVersion = linked.get(i); - // Make sure that later mappings override this one. - for (PacketMapping m : mappings) { - if (linkedVersion == m.protocolVersion) continue links; - } - register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.encodeOnly)); - } - } - } - } - - public class ProtocolVersion { - public final int version; - final IntObjectMap> packetIdToSupplier = new IntObjectHashMap<>(16, 0.5f); - final Object2IntMap> packetClassToId = new Object2IntOpenHashMap<>(16, 0.5f); - - ProtocolVersion(final int version) { - this.version = version; - this.packetClassToId.defaultReturnValue(Integer.MIN_VALUE); - } - - public @Nullable MinecraftPacket createPacket(final int id) { - final Supplier supplier = this.packetIdToSupplier.get(id); - if (supplier == null) { - return null; - } - return supplier.get(); - } - - public int getPacketId(final MinecraftPacket packet) { - final int id = this.packetClassToId.getInt(packet.getClass()); - if (id == Integer.MIN_VALUE) { - throw new IllegalArgumentException(String.format( - "Unable to find id for packet of type %s in %s protocol %s", - packet.getClass().getName(), PacketRegistry.this.direction, this.version - )); - } - return id; - } - } - } - - public static class PacketMapping { - private final int id; - private final int protocolVersion; - private final boolean encodeOnly; - - public PacketMapping(int id, int protocolVersion, boolean packetDecoding) { - this.id = id; - this.protocolVersion = protocolVersion; - this.encodeOnly = packetDecoding; - } - - @Override - public String toString() { - return "PacketMapping{" + - "id=" + id + - ", protocolVersion=" + protocolVersion + - ", encodeOnly=" + encodeOnly + - '}'; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PacketMapping that = (PacketMapping) o; - return id == that.id && - protocolVersion == that.protocolVersion && - encodeOnly == that.encodeOnly; - } - - @Override - public int hashCode() { - return Objects.hash(id, protocolVersion, encodeOnly); - } - } - - /** - * Creates a PacketMapping using the provided arguments - * @param id Packet Id - * @param version Protocol version - * @param encodeOnly When true packet decoding will be disabled - * @return PacketMapping with the provided arguments - */ - private static PacketMapping map(int id, int version, boolean encodeOnly) { - return new PacketMapping(id, version, encodeOnly); - } - - private static PacketMapping[] genericMappings(int id) { - return new PacketMapping[]{ - map(id, MINECRAFT_1_8, false), - map(id, MINECRAFT_1_9, false), - map(id, MINECRAFT_1_12, false), - map(id, MINECRAFT_1_13, false) - }; - } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java index 29e998110..9bcf330de 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java @@ -1,5 +1,8 @@ package com.velocitypowered.proxy.protocol.netty; +import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.BASIC; +import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.FULL; + import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableSet; @@ -13,11 +16,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.DatagramPacket; -import net.kyori.text.serializer.ComponentSerializers; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; - import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -28,239 +26,254 @@ import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; - -import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.BASIC; -import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.FULL; +import net.kyori.text.serializer.ComponentSerializers; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public class GS4QueryHandler extends SimpleChannelInboundHandler { - private static final Logger logger = LogManager.getLogger(GS4QueryHandler.class); - private static final short QUERY_MAGIC_FIRST = 0xFE; - private static final short QUERY_MAGIC_SECOND = 0xFD; - private static final byte QUERY_TYPE_HANDSHAKE = 0x09; - private static final byte QUERY_TYPE_STAT = 0x00; - private static final byte[] QUERY_RESPONSE_FULL_PADDING = new byte[] { 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00, (byte) 0x80, 0x00 }; - private static final byte[] QUERY_RESPONSE_FULL_PADDING2 = new byte[] { 0x01, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x5F, 0x00, 0x00 }; + private static final Logger logger = LogManager.getLogger(GS4QueryHandler.class); - // Contents to add into basic stat response. See ResponseWriter class below - private static final Set QUERY_BASIC_RESPONSE_CONTENTS = ImmutableSet.of( - "hostname", - "gametype", - "map", - "numplayers", - "maxplayers", - "hostport", - "hostip" - ); + private static final short QUERY_MAGIC_FIRST = 0xFE; + private static final short QUERY_MAGIC_SECOND = 0xFD; + private static final byte QUERY_TYPE_HANDSHAKE = 0x09; + private static final byte QUERY_TYPE_STAT = 0x00; + private static final byte[] QUERY_RESPONSE_FULL_PADDING = new byte[]{0x73, 0x70, 0x6C, 0x69, 0x74, + 0x6E, 0x75, 0x6D, 0x00, (byte) 0x80, 0x00}; + private static final byte[] QUERY_RESPONSE_FULL_PADDING2 = new byte[]{0x01, 0x70, 0x6C, 0x61, + 0x79, 0x65, 0x72, 0x5F, 0x00, 0x00}; - private final Cache sessions = CacheBuilder.newBuilder() - .expireAfterWrite(30, TimeUnit.SECONDS) - .build(); + // Contents to add into basic stat response. See ResponseWriter class below + private static final Set QUERY_BASIC_RESPONSE_CONTENTS = ImmutableSet.of( + "hostname", + "gametype", + "map", + "numplayers", + "maxplayers", + "hostport", + "hostip" + ); - @MonotonicNonNull - private volatile List pluginInformationList = null; + private final Cache sessions = CacheBuilder.newBuilder() + .expireAfterWrite(30, TimeUnit.SECONDS) + .build(); - private final VelocityServer server; + @MonotonicNonNull + private volatile List pluginInformationList = null; - public GS4QueryHandler(VelocityServer server) { - this.server = server; + private final VelocityServer server; + + public GS4QueryHandler(VelocityServer server) { + this.server = server; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { + ByteBuf queryMessage = msg.content(); + InetAddress senderAddress = msg.sender().getAddress(); + + // Allocate buffer for response + ByteBuf queryResponse = ctx.alloc().buffer(); + DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender()); + + try { + // Verify query packet magic + if (queryMessage.readUnsignedByte() != QUERY_MAGIC_FIRST + || queryMessage.readUnsignedByte() != QUERY_MAGIC_SECOND) { + throw new IllegalStateException("Invalid query packet magic"); + } + + // Read packet header + short type = queryMessage.readUnsignedByte(); + int sessionId = queryMessage.readInt(); + + switch (type) { + case QUERY_TYPE_HANDSHAKE: { + // Generate new challenge token and put it into the sessions cache + int challengeToken = ThreadLocalRandom.current().nextInt(); + sessions.put(senderAddress, challengeToken); + + // Respond with challenge token + queryResponse.writeByte(QUERY_TYPE_HANDSHAKE); + queryResponse.writeInt(sessionId); + writeString(queryResponse, Integer.toString(challengeToken)); + ctx.writeAndFlush(responsePacket); + break; + } + + case QUERY_TYPE_STAT: { + // Check if query was done with session previously generated using a handshake packet + int challengeToken = queryMessage.readInt(); + Integer session = sessions.getIfPresent(senderAddress); + if (session == null || session != challengeToken) { + throw new IllegalStateException("Invalid challenge token"); + } + + // Check which query response client expects + if (queryMessage.readableBytes() != 0 && queryMessage.readableBytes() != 4) { + throw new IllegalStateException("Invalid query packet"); + } + + // Build query response + QueryResponse response = QueryResponse.builder() + .hostname(ComponentSerializers.PLAIN + .serialize(server.getConfiguration().getMotdComponent())) + .gameVersion(ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING) + .map(server.getConfiguration().getQueryMap()) + .currentPlayers(server.getPlayerCount()) + .maxPlayers(server.getConfiguration().getShowMaxPlayers()) + .proxyPort(server.getConfiguration().getBind().getPort()) + .proxyHost(server.getConfiguration().getBind().getHostString()) + .players(server.getAllPlayers().stream().map(Player::getUsername) + .collect(Collectors.toList())) + .proxyVersion("Velocity") + .plugins( + server.getConfiguration().shouldQueryShowPlugins() ? getRealPluginInformation() + : Collections.emptyList()) + .build(); + + boolean isBasic = queryMessage.readableBytes() == 0; + + // Call event and write response + server.getEventManager() + .fire(new ProxyQueryEvent(isBasic ? BASIC : FULL, senderAddress, response)) + .whenCompleteAsync((event, exc) -> { + // Packet header + queryResponse.writeByte(QUERY_TYPE_STAT); + queryResponse.writeInt(sessionId); + + // Start writing the response + ResponseWriter responseWriter = new ResponseWriter(queryResponse, isBasic); + responseWriter.write("hostname", event.getResponse().getHostname()); + responseWriter.write("gametype", "SMP"); + + responseWriter.write("game_id", "MINECRAFT"); + responseWriter.write("version", event.getResponse().getGameVersion()); + responseWriter.writePlugins(event.getResponse().getProxyVersion(), + event.getResponse().getPlugins()); + + responseWriter.write("map", event.getResponse().getMap()); + responseWriter.write("numplayers", event.getResponse().getCurrentPlayers()); + responseWriter.write("maxplayers", event.getResponse().getMaxPlayers()); + responseWriter.write("hostport", event.getResponse().getProxyPort()); + responseWriter.write("hostip", event.getResponse().getProxyHost()); + + if (!responseWriter.isBasic) { + responseWriter.writePlayers(event.getResponse().getPlayers()); + } + + // Send the response + ctx.writeAndFlush(responsePacket); + }, ctx.channel().eventLoop()); + + break; + } + default: + throw new IllegalStateException("Invalid query type: " + type); + } + } catch (Exception e) { + logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e); + // NB: Only need to explicitly release upon exception, writing the response out will decrement the reference + // count. + responsePacket.release(); + } + } + + private static void writeString(ByteBuf buf, String string) { + buf.writeCharSequence(string, StandardCharsets.ISO_8859_1); + buf.writeByte(0x00); + } + + private List getRealPluginInformation() { + // Effective Java, Third Edition; Item 83: Use lazy initialization judiciously + List res = pluginInformationList; + if (res == null) { + synchronized (this) { + if (pluginInformationList == null) { + pluginInformationList = res = server.getPluginManager().getPlugins().stream() + .map(PluginContainer::getDescription) + .map(desc -> QueryResponse.PluginInformation + .of(desc.getName().orElse(desc.getId()), desc.getVersion().orElse(null))) + .collect(Collectors.toList()); + } + } + } + return res; + } + + private static class ResponseWriter { + + private final ByteBuf buf; + private final boolean isBasic; + + ResponseWriter(ByteBuf buf, boolean isBasic) { + this.buf = buf; + this.isBasic = isBasic; + + if (!isBasic) { + buf.writeBytes(QUERY_RESPONSE_FULL_PADDING); + } } - @Override - protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { - ByteBuf queryMessage = msg.content(); - InetAddress senderAddress = msg.sender().getAddress(); - - // Allocate buffer for response - ByteBuf queryResponse = ctx.alloc().buffer(); - DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender()); - - try { - // Verify query packet magic - if (queryMessage.readUnsignedByte() != QUERY_MAGIC_FIRST || queryMessage.readUnsignedByte() != QUERY_MAGIC_SECOND) { - throw new IllegalStateException("Invalid query packet magic"); - } - - // Read packet header - short type = queryMessage.readUnsignedByte(); - int sessionId = queryMessage.readInt(); - - switch (type) { - case QUERY_TYPE_HANDSHAKE: { - // Generate new challenge token and put it into the sessions cache - int challengeToken = ThreadLocalRandom.current().nextInt(); - sessions.put(senderAddress, challengeToken); - - // Respond with challenge token - queryResponse.writeByte(QUERY_TYPE_HANDSHAKE); - queryResponse.writeInt(sessionId); - writeString(queryResponse, Integer.toString(challengeToken)); - ctx.writeAndFlush(responsePacket); - break; - } - - case QUERY_TYPE_STAT: { - // Check if query was done with session previously generated using a handshake packet - int challengeToken = queryMessage.readInt(); - Integer session = sessions.getIfPresent(senderAddress); - if (session == null || session != challengeToken) { - throw new IllegalStateException("Invalid challenge token"); - } - - // Check which query response client expects - if (queryMessage.readableBytes() != 0 && queryMessage.readableBytes() != 4) { - throw new IllegalStateException("Invalid query packet"); - } - - // Build query response - QueryResponse response = QueryResponse.builder() - .hostname(ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent())) - .gameVersion(ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING) - .map(server.getConfiguration().getQueryMap()) - .currentPlayers(server.getPlayerCount()) - .maxPlayers(server.getConfiguration().getShowMaxPlayers()) - .proxyPort(server.getConfiguration().getBind().getPort()) - .proxyHost(server.getConfiguration().getBind().getHostString()) - .players(server.getAllPlayers().stream().map(Player::getUsername).collect(Collectors.toList())) - .proxyVersion("Velocity") - .plugins(server.getConfiguration().shouldQueryShowPlugins() ? getRealPluginInformation() : Collections.emptyList()) - .build(); - - boolean isBasic = queryMessage.readableBytes() == 0; - - // Call event and write response - server.getEventManager().fire(new ProxyQueryEvent(isBasic ? BASIC : FULL, senderAddress, response)).whenCompleteAsync((event, exc) -> { - // Packet header - queryResponse.writeByte(QUERY_TYPE_STAT); - queryResponse.writeInt(sessionId); - - // Start writing the response - ResponseWriter responseWriter = new ResponseWriter(queryResponse, isBasic); - responseWriter.write("hostname", event.getResponse().getHostname()); - responseWriter.write("gametype", "SMP"); - - responseWriter.write("game_id", "MINECRAFT"); - responseWriter.write("version", event.getResponse().getGameVersion()); - responseWriter.writePlugins(event.getResponse().getProxyVersion(), event.getResponse().getPlugins()); - - responseWriter.write("map", event.getResponse().getMap()); - responseWriter.write("numplayers", event.getResponse().getCurrentPlayers()); - responseWriter.write("maxplayers", event.getResponse().getMaxPlayers()); - responseWriter.write("hostport", event.getResponse().getProxyPort()); - responseWriter.write("hostip", event.getResponse().getProxyHost()); - - if (!responseWriter.isBasic) { - responseWriter.writePlayers(event.getResponse().getPlayers()); - } - - // Send the response - ctx.writeAndFlush(responsePacket); - }, ctx.channel().eventLoop()); - - break; - } - default: - throw new IllegalStateException("Invalid query type: " + type); - } - } catch (Exception e) { - logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e); - // NB: Only need to explicitly release upon exception, writing the response out will decrement the reference - // count. - responsePacket.release(); + // Writes k/v to stat packet body if this writer is initialized + // for full stat response. Otherwise this follows + // GS4QueryHandler#QUERY_BASIC_RESPONSE_CONTENTS to decide what + // to write into packet body + void write(String key, Object value) { + if (isBasic) { + // Basic contains only specific set of data + if (!QUERY_BASIC_RESPONSE_CONTENTS.contains(key)) { + return; } + + // Special case hostport + if (key.equals("hostport")) { + buf.writeShortLE((Integer) value); + } else { + writeString(buf, value.toString()); + } + } else { + writeString(buf, key); + writeString(buf, value.toString()); + } } - private static void writeString(ByteBuf buf, String string) { - buf.writeCharSequence(string, StandardCharsets.ISO_8859_1); - buf.writeByte(0x00); + // Ends packet k/v body writing and writes stat player list to + // the packet if this writer is initialized for full stat response + void writePlayers(Collection players) { + if (isBasic) { + return; + } + + // Ends the full stat key-value body with \0 + buf.writeByte(0x00); + + buf.writeBytes(QUERY_RESPONSE_FULL_PADDING2); + players.forEach(player -> writeString(buf, player)); + buf.writeByte(0x00); } - private List getRealPluginInformation() { - // Effective Java, Third Edition; Item 83: Use lazy initialization judiciously - List res = pluginInformationList; - if (res == null) { - synchronized (this) { - if (pluginInformationList == null) { - pluginInformationList = res = server.getPluginManager().getPlugins().stream() - .map(PluginContainer::getDescription) - .map(desc -> QueryResponse.PluginInformation.of(desc.getName().orElse(desc.getId()), desc.getVersion().orElse(null))) - .collect(Collectors.toList()); - } - } - } - return res; - } - - private static class ResponseWriter { - private final ByteBuf buf; - private final boolean isBasic; - - ResponseWriter(ByteBuf buf, boolean isBasic) { - this.buf = buf; - this.isBasic = isBasic; - - if (!isBasic) { - buf.writeBytes(QUERY_RESPONSE_FULL_PADDING); - } - } - - // Writes k/v to stat packet body if this writer is initialized - // for full stat response. Otherwise this follows - // GS4QueryHandler#QUERY_BASIC_RESPONSE_CONTENTS to decide what - // to write into packet body - void write(String key, Object value) { - if (isBasic) { - // Basic contains only specific set of data - if (!QUERY_BASIC_RESPONSE_CONTENTS.contains(key)) { - return; - } - - // Special case hostport - if (key.equals("hostport")) { - buf.writeShortLE((Integer) value); - } else { - writeString(buf, value.toString()); - } - } else { - writeString(buf, key); - writeString(buf, value.toString()); - } - } - - // Ends packet k/v body writing and writes stat player list to - // the packet if this writer is initialized for full stat response - void writePlayers(Collection players) { - if (isBasic) { - return; - } - - // Ends the full stat key-value body with \0 - buf.writeByte(0x00); - - buf.writeBytes(QUERY_RESPONSE_FULL_PADDING2); - players.forEach(player -> writeString(buf, player)); - buf.writeByte(0x00); - } - - void writePlugins(String serverVersion, Collection plugins) { - if (isBasic) - return; - - StringBuilder pluginsString = new StringBuilder(); - pluginsString.append(serverVersion).append(':').append(' '); - Iterator iterator = plugins.iterator(); - while (iterator.hasNext()) { - QueryResponse.PluginInformation info = iterator.next(); - pluginsString.append(info.getName()); - if (info.getVersion() != null) { - pluginsString.append(' ').append(info.getVersion()); - } - if (iterator.hasNext()) { - pluginsString.append(';').append(' '); - } - } - - writeString(buf, pluginsString.toString()); + void writePlugins(String serverVersion, Collection plugins) { + if (isBasic) { + return; + } + + StringBuilder pluginsString = new StringBuilder(); + pluginsString.append(serverVersion).append(':').append(' '); + Iterator iterator = plugins.iterator(); + while (iterator.hasNext()) { + QueryResponse.PluginInformation info = iterator.next(); + pluginsString.append(info.getName()); + if (info.getVersion() != null) { + pluginsString.append(' ').append(info.getVersion()); } + if (iterator.hasNext()) { + pluginsString.append(';').append(' '); + } + } + + writeString(buf, pluginsString.toString()); } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java index dfe396c7e..56deff298 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java @@ -5,26 +5,26 @@ import com.velocitypowered.proxy.protocol.packet.LegacyPing; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; - import java.util.List; public class LegacyPingDecoder extends ByteToMessageDecoder { - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - if (in.readableBytes() < 2) { - return; - } - short first = in.getUnsignedByte(in.readerIndex()); - short second = in.getUnsignedByte(in.readerIndex() + 1); - if (first == 0xfe && second == 0x01) { - in.skipBytes(in.readableBytes()); - out.add(new LegacyPing()); - } else if (first == 0x02) { - in.skipBytes(in.readableBytes()); - out.add(new LegacyHandshake()); - } else { - ctx.pipeline().remove(this); - } + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + if (in.readableBytes() < 2) { + return; } + + short first = in.getUnsignedByte(in.readerIndex()); + short second = in.getUnsignedByte(in.readerIndex() + 1); + if (first == 0xfe && second == 0x01) { + in.skipBytes(in.readableBytes()); + out.add(new LegacyPing()); + } else if (first == 0x02) { + in.skipBytes(in.readableBytes()); + out.add(new LegacyHandshake()); + } else { + ctx.pipeline().remove(this); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingEncoder.java index 3b31305e1..436eedaa5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingEncoder.java @@ -5,23 +5,25 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; - import java.nio.charset.StandardCharsets; @ChannelHandler.Sharable public class LegacyPingEncoder extends MessageToByteEncoder { - public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder(); - private LegacyPingEncoder() {} + public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder(); - @Override - protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out) throws Exception { - out.writeByte(0xff); - writeLegacyString(out, msg.getReason()); - } + private LegacyPingEncoder() { + } - private static void writeLegacyString(ByteBuf out, String string) { - out.writeShort(string.length()); - out.writeBytes(string.getBytes(StandardCharsets.UTF_16BE)); - } + @Override + protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out) + throws Exception { + out.writeByte(0xff); + writeLegacyString(out, msg.getReason()); + } + + private static void writeLegacyString(ByteBuf out, String string) { + out.writeShort(string.length()); + out.writeBytes(string.getBytes(StandardCharsets.UTF_16BE)); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCipherDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCipherDecoder.java index 5a7cb4f28..0d630b77d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCipherDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCipherDecoder.java @@ -5,30 +5,30 @@ import com.velocitypowered.natives.encryption.VelocityCipher; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; - import java.util.List; public class MinecraftCipherDecoder extends ByteToMessageDecoder { - private final VelocityCipher cipher; - public MinecraftCipherDecoder(VelocityCipher cipher) { - this.cipher = Preconditions.checkNotNull(cipher, "cipher"); - } + private final VelocityCipher cipher; - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - ByteBuf decrypted = ctx.alloc().buffer(in.readableBytes()); - try { - cipher.process(in, decrypted); - out.add(decrypted); - } catch (Exception e) { - decrypted.release(); - throw e; - } - } + public MinecraftCipherDecoder(VelocityCipher cipher) { + this.cipher = Preconditions.checkNotNull(cipher, "cipher"); + } - @Override - protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { - cipher.dispose(); + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + ByteBuf decrypted = ctx.alloc().buffer(in.readableBytes()); + try { + cipher.process(in, decrypted); + out.add(decrypted); + } catch (Exception e) { + decrypted.release(); + throw e; } + } + + @Override + protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { + cipher.dispose(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCipherEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCipherEncoder.java index e147aef0f..3df7e8287 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCipherEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCipherEncoder.java @@ -7,24 +7,26 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class MinecraftCipherEncoder extends MessageToByteEncoder { - private final VelocityCipher cipher; - public MinecraftCipherEncoder(VelocityCipher cipher) { - this.cipher = Preconditions.checkNotNull(cipher, "cipher"); - } + private final VelocityCipher cipher; - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { - cipher.process(msg, out); - } + public MinecraftCipherEncoder(VelocityCipher cipher) { + this.cipher = Preconditions.checkNotNull(cipher, "cipher"); + } - @Override - protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { - return ctx.alloc().directBuffer(msg.readableBytes()); - } + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { + cipher.process(msg, out); + } - @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { - cipher.dispose(); - } + @Override + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) + throws Exception { + return ctx.alloc().directBuffer(msg.readableBytes()); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + cipher.dispose(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java index 95b218056..9d6200718 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java @@ -6,46 +6,49 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; - import java.util.List; public class MinecraftCompressDecoder extends MessageToMessageDecoder { - private static final int MAXIMUM_INITIAL_BUFFER_SIZE = 65536; // 64KiB - private final int threshold; - private final VelocityCompressor compressor; + private static final int MAXIMUM_INITIAL_BUFFER_SIZE = 65536; // 64KiB - public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) { - this.threshold = threshold; - this.compressor = compressor; + private final int threshold; + private final VelocityCompressor compressor; + + public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) { + this.threshold = threshold; + this.compressor = compressor; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { + int uncompressedSize = ProtocolUtils.readVarInt(msg); + if (uncompressedSize == 0) { + // Strip the now-useless uncompressed size, this message is already uncompressed. + out.add(msg.retainedSlice()); + msg.skipBytes(msg.readableBytes()); + return; } - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { - int uncompressedSize = ProtocolUtils.readVarInt(msg); - if (uncompressedSize == 0) { - // Strip the now-useless uncompressed size, this message is already uncompressed. - out.add(msg.retainedSlice()); - msg.skipBytes(msg.readableBytes()); - return; - } - - Preconditions.checkState(uncompressedSize >= threshold, "Uncompressed size %s doesn't make sense with threshold %s", uncompressedSize, threshold); - // Try to use the uncompressed size, but place a cap if it might be too big (possibly malicious). - ByteBuf uncompressed = ctx.alloc().buffer(Math.min(uncompressedSize, MAXIMUM_INITIAL_BUFFER_SIZE)); - try { - compressor.inflate(msg, uncompressed); - Preconditions.checkState(uncompressedSize == uncompressed.readableBytes(), "Mismatched compression sizes"); - out.add(uncompressed); - } catch (Exception e) { - // If something went wrong, rethrow the exception, but ensure we free our temporary buffer first. - uncompressed.release(); - throw e; - } + Preconditions.checkState(uncompressedSize >= threshold, + "Uncompressed size %s doesn't make sense with threshold %s", uncompressedSize, threshold); + // Try to use the uncompressed size, but place a cap if it might be too big (possibly malicious). + ByteBuf uncompressed = ctx.alloc() + .buffer(Math.min(uncompressedSize, MAXIMUM_INITIAL_BUFFER_SIZE)); + try { + compressor.inflate(msg, uncompressed); + Preconditions.checkState(uncompressedSize == uncompressed.readableBytes(), + "Mismatched compression sizes"); + out.add(uncompressed); + } catch (Exception e) { + // If something went wrong, rethrow the exception, but ensure we free our temporary buffer first. + uncompressed.release(); + throw e; } + } - @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { - compressor.dispose(); - } + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + compressor.dispose(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java index 48ded65a5..60d7c5d19 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java @@ -7,38 +7,40 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class MinecraftCompressEncoder extends MessageToByteEncoder { - private final int threshold; - private final VelocityCompressor compressor; - public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) { - this.threshold = threshold; - this.compressor = compressor; - } + private final int threshold; + private final VelocityCompressor compressor; - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { - int uncompressed = msg.readableBytes(); - if (uncompressed <= threshold) { - // Under the threshold, there is nothing to do. - ProtocolUtils.writeVarInt(out, 0); - out.writeBytes(msg); - } else { - ProtocolUtils.writeVarInt(out, uncompressed); - compressor.deflate(msg, out); - } - } + public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) { + this.threshold = threshold; + this.compressor = compressor; + } - @Override - protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { - if (msg.readableBytes() <= threshold) { - return ctx.alloc().directBuffer(msg.readableBytes() + 1); - } - // A reasonable assumption about compression savings - return ctx.alloc().directBuffer(msg.readableBytes() / 3); + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { + int uncompressed = msg.readableBytes(); + if (uncompressed <= threshold) { + // Under the threshold, there is nothing to do. + ProtocolUtils.writeVarInt(out, 0); + out.writeBytes(msg); + } else { + ProtocolUtils.writeVarInt(out, uncompressed); + compressor.deflate(msg, out); } + } - @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { - compressor.dispose(); + @Override + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) + throws Exception { + if (msg.readableBytes() <= threshold) { + return ctx.alloc().directBuffer(msg.readableBytes() + 1); } + // A reasonable assumption about compression savings + return ctx.alloc().directBuffer(msg.readableBytes() / 3); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + compressor.dispose(); + } } 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 c28adca2b..22a266a9b 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 @@ -9,54 +9,59 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.MessageToMessageDecoder; - import java.util.List; public class MinecraftDecoder extends MessageToMessageDecoder { - private final ProtocolConstants.Direction direction; - private StateRegistry state; - private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion; - public MinecraftDecoder(ProtocolConstants.Direction direction) { - this.direction = Preconditions.checkNotNull(direction, "direction"); - this.protocolVersion = direction.getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION); - this.state = StateRegistry.HANDSHAKE; + private final ProtocolConstants.Direction direction; + private StateRegistry state; + private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion; + + public MinecraftDecoder(ProtocolConstants.Direction direction) { + this.direction = Preconditions.checkNotNull(direction, "direction"); + this.protocolVersion = direction + .getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION); + this.state = StateRegistry.HANDSHAKE; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { + if (!msg.isReadable()) { + return; } - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { - if (!msg.isReadable()) { - return; - } + ByteBuf slice = msg.slice(); - ByteBuf slice = msg.slice(); - - int packetId = ProtocolUtils.readVarInt(msg); - MinecraftPacket packet = this.protocolVersion.createPacket(packetId); - if (packet == null) { - msg.skipBytes(msg.readableBytes()); - out.add(slice.retain()); - } else { - try { - packet.decode(msg, direction, protocolVersion.version); - } catch (Exception e) { - throw new CorruptedFrameException("Error decoding " + packet.getClass() + " Direction " + direction - + " Protocol " + protocolVersion.version + " State " + state + " ID " + Integer.toHexString(packetId), e); - } - if (msg.isReadable()) { - throw new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " Direction " + direction - + " Protocol " + protocolVersion.version + " State " + state + " ID " + Integer.toHexString(packetId)); - } - out.add(packet); - } + int packetId = ProtocolUtils.readVarInt(msg); + MinecraftPacket packet = this.protocolVersion.createPacket(packetId); + if (packet == null) { + msg.skipBytes(msg.readableBytes()); + out.add(slice.retain()); + } else { + try { + packet.decode(msg, direction, protocolVersion.version); + } catch (Exception e) { + throw new CorruptedFrameException( + "Error decoding " + packet.getClass() + " Direction " + direction + + " Protocol " + protocolVersion.version + " State " + state + " ID " + Integer + .toHexString(packetId), e); + } + if (msg.isReadable()) { + throw new CorruptedFrameException( + "Did not read full packet for " + packet.getClass() + " Direction " + direction + + " Protocol " + protocolVersion.version + " State " + state + " ID " + Integer + .toHexString(packetId)); + } + out.add(packet); } + } - public void setProtocolVersion(int protocolVersion) { - this.protocolVersion = direction.getProtocol(state, protocolVersion); - } + public void setProtocolVersion(int protocolVersion) { + this.protocolVersion = direction.getProtocol(state, protocolVersion); + } - public void setState(StateRegistry state) { - this.state = state; - this.setProtocolVersion(protocolVersion.version); - } + public void setState(StateRegistry state) { + this.state = state; + this.setProtocolVersion(protocolVersion.version); + } } 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 bba53d49e..e941f2fab 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,29 +10,31 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class MinecraftEncoder extends MessageToByteEncoder { - private final ProtocolConstants.Direction direction; - private StateRegistry state; - private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion; - public MinecraftEncoder(ProtocolConstants.Direction direction) { - this.direction = Preconditions.checkNotNull(direction, "direction"); - this.protocolVersion = direction.getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION); - this.state = StateRegistry.HANDSHAKE; - } + private final ProtocolConstants.Direction direction; + private StateRegistry state; + private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion; - @Override - protected void encode(ChannelHandlerContext ctx, MinecraftPacket msg, ByteBuf out) { - int packetId = this.protocolVersion.getPacketId(msg); - ProtocolUtils.writeVarInt(out, packetId); - msg.encode(out, direction, protocolVersion.version); - } + public MinecraftEncoder(ProtocolConstants.Direction direction) { + this.direction = Preconditions.checkNotNull(direction, "direction"); + this.protocolVersion = direction + .getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION); + this.state = StateRegistry.HANDSHAKE; + } - public void setProtocolVersion(final int protocolVersion) { - this.protocolVersion = direction.getProtocol(state, protocolVersion); - } + @Override + protected void encode(ChannelHandlerContext ctx, MinecraftPacket msg, ByteBuf out) { + int packetId = this.protocolVersion.getPacketId(msg); + ProtocolUtils.writeVarInt(out, packetId); + msg.encode(out, direction, protocolVersion.version); + } - public void setState(StateRegistry state) { - this.state = state; - this.setProtocolVersion(protocolVersion.version); - } + public void setProtocolVersion(final int protocolVersion) { + this.protocolVersion = direction.getProtocol(state, protocolVersion); + } + + public void setState(StateRegistry state) { + this.state = state; + this.setProtocolVersion(protocolVersion.version); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java index 136204fa2..6bc48a3b9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java @@ -6,42 +6,42 @@ import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.CorruptedFrameException; - import java.util.List; public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - if (!in.isReadable()) { - return; - } - in.markReaderIndex(); - - byte[] lenBuf = new byte[3]; - for (int i = 0; i < lenBuf.length; i++) { - if (!in.isReadable()) { - in.resetReaderIndex(); - return; - } - - lenBuf[i] = in.readByte(); - if (lenBuf[i] > 0) { - int packetLength = ProtocolUtils.readVarInt(Unpooled.wrappedBuffer(lenBuf)); - if (packetLength == 0) { - return; - } - - if (in.readableBytes() < packetLength) { - in.resetReaderIndex(); - return; - } - - out.add(in.readRetainedSlice(packetLength)); - return; - } - } - - throw new CorruptedFrameException("VarInt too big"); + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + if (!in.isReadable()) { + return; } + + in.markReaderIndex(); + + byte[] lenBuf = new byte[3]; + for (int i = 0; i < lenBuf.length; i++) { + if (!in.isReadable()) { + in.resetReaderIndex(); + return; + } + + lenBuf[i] = in.readByte(); + if (lenBuf[i] > 0) { + int packetLength = ProtocolUtils.readVarInt(Unpooled.wrappedBuffer(lenBuf)); + if (packetLength == 0) { + return; + } + + if (in.readableBytes() < packetLength) { + in.resetReaderIndex(); + return; + } + + out.add(in.readRetainedSlice(packetLength)); + return; + } + } + + throw new CorruptedFrameException("VarInt too big"); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java index 3116c6a69..44d1b8847 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintLengthEncoder.java @@ -5,20 +5,22 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; - import java.util.List; @ChannelHandler.Sharable public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder { - public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder(); - private MinecraftVarintLengthEncoder() { } + public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder(); - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List list) throws Exception { - ByteBuf lengthBuf = ctx.alloc().buffer(5); // the maximum size of a varint - ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes()); - list.add(lengthBuf); - list.add(buf.retain()); - } + private MinecraftVarintLengthEncoder() { + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List list) + throws Exception { + ByteBuf lengthBuf = ctx.alloc().buffer(5); // the maximum size of a varint + ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes()); + list.add(lengthBuf); + list.add(buf.retain()); + } } 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 8c2dd2ae7..ae7dfbac1 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,172 +5,172 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.UUID; - public class BossBar implements MinecraftPacket { - public static final int ADD = 0; - public static final int REMOVE = 1; - public static final int UPDATE_PERCENT = 2; - public static final int UPDATE_NAME = 3; - public static final int UPDATE_STYLE = 4; - public static final int UPDATE_PROPERTIES = 5; - private @Nullable UUID uuid; - private int action; - 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"); + public static final int ADD = 0; + public static final int REMOVE = 1; + public static final int UPDATE_PERCENT = 2; + public static final int UPDATE_NAME = 3; + public static final int UPDATE_STYLE = 4; + public static final int UPDATE_PROPERTIES = 5; + private @Nullable UUID uuid; + private int action; + 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; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public int getAction() { + return action; + } + + public void setAction(int action) { + this.action = action; + } + + public @Nullable String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public float getPercent() { + return percent; + } + + public void setPercent(float percent) { + this.percent = percent; + } + + public int getColor() { + return color; + } + + public void setColor(int color) { + this.color = color; + } + + public int getOverlay() { + return overlay; + } + + public void setOverlay(int overlay) { + this.overlay = overlay; + } + + public short getFlags() { + return flags; + } + + public void setFlags(short flags) { + this.flags = flags; + } + + @Override + public String toString() { + return "BossBar{" + + "uuid=" + uuid + + ", action=" + action + + ", name='" + name + '\'' + + ", percent=" + percent + + ", color=" + color + + ", overlay=" + overlay + + ", flags=" + flags + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.uuid = ProtocolUtils.readUuid(buf); + this.action = ProtocolUtils.readVarInt(buf); + switch (action) { + case ADD: + this.name = ProtocolUtils.readString(buf); + this.percent = buf.readFloat(); + this.color = ProtocolUtils.readVarInt(buf); + this.overlay = ProtocolUtils.readVarInt(buf); + this.flags = buf.readUnsignedByte(); + break; + case REMOVE: + break; + case UPDATE_PERCENT: + this.percent = buf.readFloat(); + break; + case UPDATE_NAME: + this.name = ProtocolUtils.readString(buf); + break; + case UPDATE_STYLE: + this.color = ProtocolUtils.readVarInt(buf); + this.overlay = ProtocolUtils.readVarInt(buf); + break; + case UPDATE_PROPERTIES: + this.flags = buf.readUnsignedByte(); + break; + default: + throw new UnsupportedOperationException("Unknown action " + action); + } + } + + @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!"); } - return uuid; - } - - public void setUuid(UUID uuid) { - this.uuid = uuid; - } - - public int getAction() { - return action; - } - - public void setAction(int action) { - this.action = action; - } - - public @Nullable String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public float getPercent() { - return percent; - } - - public void setPercent(float percent) { - this.percent = percent; - } - - public int getColor() { - return color; - } - - public void setColor(int color) { - this.color = color; - } - - public int getOverlay() { - return overlay; - } - - public void setOverlay(int overlay) { - this.overlay = overlay; - } - - public short getFlags() { - return flags; - } - - public void setFlags(short flags) { - this.flags = flags; - } - - @Override - public String toString() { - return "BossBar{" + - "uuid=" + uuid + - ", action=" + action + - ", name='" + name + '\'' + - ", percent=" + percent + - ", color=" + color + - ", overlay=" + overlay + - ", flags=" + flags + - '}'; - } - - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.uuid = ProtocolUtils.readUuid(buf); - this.action = ProtocolUtils.readVarInt(buf); - switch (action) { - case ADD: - this.name = ProtocolUtils.readString(buf); - this.percent = buf.readFloat(); - this.color = ProtocolUtils.readVarInt(buf); - this.overlay = ProtocolUtils.readVarInt(buf); - this.flags = buf.readUnsignedByte(); - break; - case REMOVE: - break; - case UPDATE_PERCENT: - this.percent = buf.readFloat(); - break; - case UPDATE_NAME: - this.name = ProtocolUtils.readString(buf); - break; - case UPDATE_STYLE: - this.color = ProtocolUtils.readVarInt(buf); - this.overlay = ProtocolUtils.readVarInt(buf); - break; - case UPDATE_PROPERTIES: - this.flags = buf.readUnsignedByte(); - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); + ProtocolUtils.writeString(buf, name); + buf.writeFloat(percent); + ProtocolUtils.writeVarInt(buf, color); + ProtocolUtils.writeVarInt(buf, overlay); + buf.writeByte(flags); + break; + case REMOVE: + break; + case UPDATE_PERCENT: + buf.writeFloat(percent); + break; + case UPDATE_NAME: + if (name == null) { + throw new IllegalStateException("No name specified!"); } + ProtocolUtils.writeString(buf, name); + break; + case UPDATE_STYLE: + ProtocolUtils.writeVarInt(buf, color); + ProtocolUtils.writeVarInt(buf, overlay); + break; + case UPDATE_PROPERTIES: + buf.writeByte(flags); + break; + default: + throw new UnsupportedOperationException("Unknown action " + action); } + } - @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); - ProtocolUtils.writeVarInt(buf, overlay); - buf.writeByte(flags); - break; - case REMOVE: - break; - case UPDATE_PERCENT: - buf.writeFloat(percent); - break; - case UPDATE_NAME: - if (name == null) { - throw new IllegalStateException("No name specified!"); - } - ProtocolUtils.writeString(buf, name); - break; - case UPDATE_STYLE: - ProtocolUtils.writeVarInt(buf, color); - ProtocolUtils.writeVarInt(buf, overlay); - break; - case UPDATE_PROPERTIES: - buf.writeByte(flags); - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } - - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 2944d4d7d..1c53be24f 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 @@ -11,81 +11,82 @@ import net.kyori.text.serializer.ComponentSerializers; import org.checkerframework.checker.nullness.qual.Nullable; public class Chat implements MinecraftPacket { - public static final byte CHAT_TYPE = (byte) 0; - public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256; - private @Nullable String message; - private byte type; + public static final byte CHAT_TYPE = (byte) 0; + public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256; - public Chat() { + private @Nullable String message; + private byte type; + + public Chat() { + } + + public Chat(String message, byte type) { + this.message = message; + this.type = type; + } + + public String getMessage() { + if (message == null) { + throw new IllegalStateException("Message is not specified"); } + return message; + } - public Chat(String message, byte type) { - this.message = message; - this.type = type; - } + public void setMessage(String message) { + this.message = message; + } - public String getMessage() { - if (message == null) { - throw new IllegalStateException("Message is not specified"); - } - return message; - } + public byte getType() { + return type; + } - public void setMessage(String message) { - this.message = message; - } + public void setType(byte type) { + this.type = type; + } - public byte getType() { - return type; - } + @Override + public String toString() { + return "Chat{" + + "message='" + message + '\'' + + ", type=" + type + + '}'; + } - public void setType(byte type) { - this.type = type; + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + message = ProtocolUtils.readString(buf); + if (direction == ProtocolConstants.Direction.CLIENTBOUND) { + type = buf.readByte(); } + } - @Override - public String toString() { - return "Chat{" + - "message='" + message + '\'' + - ", type=" + type + - '}'; + @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); + } + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - message = ProtocolUtils.readString(buf); - if (direction == ProtocolConstants.Direction.CLIENTBOUND) { - type = buf.readByte(); - } - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } - @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); - } - } + public static Chat createClientbound(Component component) { + return createClientbound(component, CHAT_TYPE); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + public static Chat createClientbound(Component component, byte type) { + Preconditions.checkNotNull(component, "component"); + return new Chat(ComponentSerializers.JSON.serialize(component), type); + } - public static Chat createClientbound(Component component) { - return createClientbound(component, CHAT_TYPE); - } - - public static Chat createClientbound(Component component, byte type) { - Preconditions.checkNotNull(component, "component"); - return new Chat(ComponentSerializers.JSON.serialize(component), type); - } - - public static Chat createServerbound(String message) { - return new Chat(message, CHAT_TYPE); - } + public static Chat createServerbound(String message) { + return new Chat(message, CHAT_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 d28c9a581..966dd1169 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 @@ -9,119 +9,120 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class ClientSettings implements MinecraftPacket { - private @Nullable String locale; - private byte viewDistance; - private int chatVisibility; - private boolean chatColors; - private short skinParts; - private int mainHand; + private @Nullable String locale; + private byte viewDistance; + private int chatVisibility; + private boolean chatColors; + private short skinParts; + private int mainHand; - public ClientSettings() { + public ClientSettings() { + } + + public ClientSettings(String locale, byte viewDistance, int chatVisibility, boolean chatColors, + short skinParts, int mainHand) { + this.locale = locale; + this.viewDistance = viewDistance; + this.chatVisibility = chatVisibility; + this.chatColors = chatColors; + this.skinParts = skinParts; + this.mainHand = mainHand; + } + + public String getLocale() { + if (locale == null) { + throw new IllegalStateException("No locale specified"); } + return locale; + } - public ClientSettings(String locale, byte viewDistance, int chatVisibility, boolean chatColors, short skinParts, int mainHand) { - this.locale = locale; - this.viewDistance = viewDistance; - this.chatVisibility = chatVisibility; - this.chatColors = chatColors; - this.skinParts = skinParts; - this.mainHand = mainHand; + public void setLocale(String locale) { + this.locale = locale; + } + + public byte getViewDistance() { + return viewDistance; + } + + public void setViewDistance(byte viewDistance) { + this.viewDistance = viewDistance; + } + + public int getChatVisibility() { + return chatVisibility; + } + + public void setChatVisibility(int chatVisibility) { + this.chatVisibility = chatVisibility; + } + + public boolean isChatColors() { + return chatColors; + } + + public void setChatColors(boolean chatColors) { + this.chatColors = chatColors; + } + + public short getSkinParts() { + return skinParts; + } + + public void setSkinParts(short skinParts) { + this.skinParts = skinParts; + } + + public int getMainHand() { + return mainHand; + } + + public void setMainHand(int mainHand) { + this.mainHand = mainHand; + } + + @Override + public String toString() { + return "ClientSettings{" + + "locale='" + locale + '\'' + + ", viewDistance=" + viewDistance + + ", chatVisibility=" + chatVisibility + + ", chatColors=" + chatColors + + ", skinParts=" + skinParts + + ", mainHand=" + mainHand + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.locale = ProtocolUtils.readString(buf, 16); + this.viewDistance = buf.readByte(); + this.chatVisibility = ProtocolUtils.readVarInt(buf); + this.chatColors = buf.readBoolean(); + this.skinParts = buf.readUnsignedByte(); + + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) { + this.mainHand = ProtocolUtils.readVarInt(buf); } + } - public String getLocale() { - if (locale == null) { - throw new IllegalStateException("No locale specified"); - } - return locale; + @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); + buf.writeBoolean(chatColors); + buf.writeByte(skinParts); - public void setLocale(String locale) { - this.locale = locale; + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) { + ProtocolUtils.writeVarInt(buf, mainHand); } + } - public byte getViewDistance() { - return viewDistance; - } - - public void setViewDistance(byte viewDistance) { - this.viewDistance = viewDistance; - } - - public int getChatVisibility() { - return chatVisibility; - } - - public void setChatVisibility(int chatVisibility) { - this.chatVisibility = chatVisibility; - } - - public boolean isChatColors() { - return chatColors; - } - - public void setChatColors(boolean chatColors) { - this.chatColors = chatColors; - } - - public short getSkinParts() { - return skinParts; - } - - public void setSkinParts(short skinParts) { - this.skinParts = skinParts; - } - - public int getMainHand() { - return mainHand; - } - - public void setMainHand(int mainHand) { - this.mainHand = mainHand; - } - - @Override - public String toString() { - return "ClientSettings{" - + "locale='" + locale + '\'' - + ", viewDistance=" + viewDistance - + ", chatVisibility=" + chatVisibility - + ", chatColors=" + chatColors - + ", skinParts=" + skinParts - + ", mainHand=" + mainHand - + '}'; - } - - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.locale = ProtocolUtils.readString(buf, 16); - this.viewDistance = buf.readByte(); - this.chatVisibility = ProtocolUtils.readVarInt(buf); - this.chatColors = buf.readBoolean(); - this.skinParts = buf.readUnsignedByte(); - - if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) { - this.mainHand = ProtocolUtils.readVarInt(buf); - } - } - - @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); - buf.writeBoolean(chatColors); - buf.writeByte(skinParts); - - if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) { - ProtocolUtils.writeVarInt(buf, mainHand); - } - } - - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 9828445f1..3f433f61f 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 @@ -11,53 +11,54 @@ import net.kyori.text.serializer.ComponentSerializers; import org.checkerframework.checker.nullness.qual.Nullable; public class Disconnect implements MinecraftPacket { - private @Nullable String reason; - public Disconnect() { - } + private @Nullable String reason; - public Disconnect(String reason) { - this.reason = Preconditions.checkNotNull(reason, "reason"); - } + public Disconnect() { + } - public String getReason() { - if (reason == null) { - throw new IllegalStateException("No reason specified"); - } - return reason; - } + public Disconnect(String reason) { + this.reason = Preconditions.checkNotNull(reason, "reason"); + } - public void setReason(@Nullable String reason) { - this.reason = reason; + public String getReason() { + if (reason == null) { + throw new IllegalStateException("No reason specified"); } + return reason; + } - @Override - public String toString() { - return "Disconnect{" + - "reason='" + reason + '\'' + - '}'; - } + public void setReason(@Nullable String reason) { + this.reason = reason; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - reason = ProtocolUtils.readString(buf); - } + @Override + public String toString() { + return "Disconnect{" + + "reason='" + reason + '\'' + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - if (reason == null) { - throw new IllegalStateException("No reason specified."); - } - ProtocolUtils.writeString(buf, reason); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + reason = ProtocolUtils.readString(buf); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + if (reason == null) { + throw new IllegalStateException("No reason specified."); } + ProtocolUtils.writeString(buf, reason); + } - public static Disconnect create(Component component) { - Preconditions.checkNotNull(component, "component"); - return new Disconnect(ComponentSerializers.JSON.serialize(component)); - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + public static Disconnect create(Component component) { + Preconditions.checkNotNull(component, "component"); + return new Disconnect(ComponentSerializers.JSON.serialize(component)); + } } 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 f44a39707..a06eb4d2d 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 @@ -1,60 +1,60 @@ package com.velocitypowered.proxy.protocol.packet; +import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; + 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 java.util.Arrays; -import static com.velocitypowered.proxy.connection.VelocityConstants.*; - public class EncryptionRequest implements MinecraftPacket { - private String serverId = ""; - private byte[] publicKey = EMPTY_BYTE_ARRAY; - private byte[] verifyToken = EMPTY_BYTE_ARRAY; - public byte[] getPublicKey() { - return publicKey; - } + private String serverId = ""; + private byte[] publicKey = EMPTY_BYTE_ARRAY; + private byte[] verifyToken = EMPTY_BYTE_ARRAY; - public void setPublicKey(byte[] publicKey) { - this.publicKey = publicKey; - } + public byte[] getPublicKey() { + return publicKey; + } - public byte[] getVerifyToken() { - return verifyToken; - } + public void setPublicKey(byte[] publicKey) { + this.publicKey = publicKey; + } - public void setVerifyToken(byte[] verifyToken) { - this.verifyToken = verifyToken; - } + public byte[] getVerifyToken() { + return verifyToken; + } - @Override - public String toString() { - return "EncryptionRequest{" + - "publicKey=" + Arrays.toString(publicKey) + - ", verifyToken=" + Arrays.toString(verifyToken) + - '}'; - } + public void setVerifyToken(byte[] verifyToken) { + this.verifyToken = verifyToken; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.serverId = ProtocolUtils.readString(buf, 20); - publicKey = ProtocolUtils.readByteArray(buf, 256); - verifyToken = ProtocolUtils.readByteArray(buf, 16); - } + @Override + public String toString() { + return "EncryptionRequest{" + + "publicKey=" + Arrays.toString(publicKey) + + ", verifyToken=" + Arrays.toString(verifyToken) + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeString(buf, this.serverId); - ProtocolUtils.writeByteArray(buf, publicKey); - ProtocolUtils.writeByteArray(buf, verifyToken); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.serverId = ProtocolUtils.readString(buf, 20); + publicKey = ProtocolUtils.readByteArray(buf, 256); + verifyToken = ProtocolUtils.readByteArray(buf, 16); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeString(buf, this.serverId); + ProtocolUtils.writeByteArray(buf, publicKey); + ProtocolUtils.writeByteArray(buf, verifyToken); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 ae62ac128..49ffd2d43 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 @@ -1,57 +1,57 @@ package com.velocitypowered.proxy.protocol.packet; +import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; + 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 java.util.Arrays; -import static com.velocitypowered.proxy.connection.VelocityConstants.*; - public class EncryptionResponse implements MinecraftPacket { - private byte[] sharedSecret = EMPTY_BYTE_ARRAY; - private byte[] verifyToken = EMPTY_BYTE_ARRAY; - public byte[] getSharedSecret() { - return sharedSecret; - } + private byte[] sharedSecret = EMPTY_BYTE_ARRAY; + private byte[] verifyToken = EMPTY_BYTE_ARRAY; - public void setSharedSecret(byte[] sharedSecret) { - this.sharedSecret = sharedSecret; - } + public byte[] getSharedSecret() { + return sharedSecret; + } - public byte[] getVerifyToken() { - return verifyToken; - } + public void setSharedSecret(byte[] sharedSecret) { + this.sharedSecret = sharedSecret; + } - public void setVerifyToken(byte[] verifyToken) { - this.verifyToken = verifyToken; - } + public byte[] getVerifyToken() { + return verifyToken; + } - @Override - public String toString() { - return "EncryptionResponse{" + - "sharedSecret=" + Arrays.toString(sharedSecret) + - ", verifyToken=" + Arrays.toString(verifyToken) + - '}'; - } + public void setVerifyToken(byte[] verifyToken) { + this.verifyToken = verifyToken; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.sharedSecret = ProtocolUtils.readByteArray(buf, 256); - this.verifyToken = ProtocolUtils.readByteArray(buf, 128); - } + @Override + public String toString() { + return "EncryptionResponse{" + + "sharedSecret=" + Arrays.toString(sharedSecret) + + ", verifyToken=" + Arrays.toString(verifyToken) + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeByteArray(buf, sharedSecret); - ProtocolUtils.writeByteArray(buf, verifyToken); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.sharedSecret = ProtocolUtils.readByteArray(buf, 256); + this.verifyToken = ProtocolUtils.readByteArray(buf, 128); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeByteArray(buf, sharedSecret); + ProtocolUtils.writeByteArray(buf, verifyToken); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 3029854e6..db4fbcd6f 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 @@ -7,71 +7,72 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; public class Handshake implements MinecraftPacket { - private int protocolVersion; - private String serverAddress = ""; - private int port; - private int nextStatus; - public int getProtocolVersion() { - return protocolVersion; - } + private int protocolVersion; + private String serverAddress = ""; + private int port; + private int nextStatus; - public void setProtocolVersion(int protocolVersion) { - this.protocolVersion = protocolVersion; - } + public int getProtocolVersion() { + return protocolVersion; + } - public String getServerAddress() { - return serverAddress; - } + public void setProtocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + } - public void setServerAddress(String serverAddress) { - this.serverAddress = serverAddress; - } + public String getServerAddress() { + return serverAddress; + } - public int getPort() { - return port; - } + public void setServerAddress(String serverAddress) { + this.serverAddress = serverAddress; + } - public void setPort(int port) { - this.port = port; - } + public int getPort() { + return port; + } - public int getNextStatus() { - return nextStatus; - } + public void setPort(int port) { + this.port = port; + } - public void setNextStatus(int nextStatus) { - this.nextStatus = nextStatus; - } + public int getNextStatus() { + return nextStatus; + } - @Override - public String toString() { - return "Handshake{" + - "protocolVersion=" + protocolVersion + - ", serverAddress='" + serverAddress + '\'' + - ", port=" + port + - ", nextStatus=" + nextStatus + - '}'; - } + public void setNextStatus(int nextStatus) { + this.nextStatus = nextStatus; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.protocolVersion = ProtocolUtils.readVarInt(buf); - this.serverAddress = ProtocolUtils.readString(buf); - this.port = buf.readUnsignedShort(); - this.nextStatus = ProtocolUtils.readVarInt(buf); - } + @Override + public String toString() { + return "Handshake{" + + "protocolVersion=" + protocolVersion + + ", serverAddress='" + serverAddress + '\'' + + ", port=" + port + + ", nextStatus=" + nextStatus + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeVarInt(buf, this.protocolVersion); - ProtocolUtils.writeString(buf, this.serverAddress); - buf.writeShort(this.port); - ProtocolUtils.writeVarInt(buf, this.nextStatus); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.protocolVersion = ProtocolUtils.readVarInt(buf); + this.serverAddress = ProtocolUtils.readString(buf); + this.port = buf.readUnsignedShort(); + this.nextStatus = ProtocolUtils.readVarInt(buf); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeVarInt(buf, this.protocolVersion); + ProtocolUtils.writeString(buf, this.serverAddress); + buf.writeShort(this.port); + ProtocolUtils.writeVarInt(buf, this.nextStatus); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 f33fe12a3..bde4bff8a 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,7 @@ package com.velocitypowered.proxy.protocol.packet; +import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString; + import com.google.common.base.Preconditions; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; @@ -9,54 +11,53 @@ import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializer; import net.kyori.text.serializer.ComponentSerializers; -import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString; - public class HeaderAndFooter implements MinecraftPacket { - 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); - } + private static final String EMPTY_COMPONENT = "{\"translate\":\"\"}"; + private static final HeaderAndFooter RESET = new HeaderAndFooter(); - public HeaderAndFooter(String header, String footer) { - this.header = Preconditions.checkNotNull(header, "header"); - this.footer = Preconditions.checkNotNull(footer, "footer"); - } + private String header; + private String footer; - public String getHeader() { - return header; - } + public HeaderAndFooter() { + this(EMPTY_COMPONENT, EMPTY_COMPONENT); + } - public String getFooter() { - return footer; - } + public HeaderAndFooter(String header, String footer) { + this.header = Preconditions.checkNotNull(header, "header"); + this.footer = Preconditions.checkNotNull(footer, "footer"); + } - @Override - public void decode(ByteBuf buf, Direction direction, int protocolVersion) { - throw new UnsupportedOperationException("Decode is not implemented"); - } + public String getHeader() { + return header; + } - @Override - public void encode(ByteBuf buf, Direction direction, int protocolVersion) { - writeString(buf, header); - writeString(buf, footer); - } + public String getFooter() { + return footer; + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void decode(ByteBuf buf, Direction direction, int protocolVersion) { + throw new UnsupportedOperationException("Decode is not implemented"); + } - public static HeaderAndFooter create(Component header, Component footer) { - ComponentSerializer json = ComponentSerializers.JSON; - return new HeaderAndFooter(json.serialize(header), json.serialize(footer)); - } - - public static HeaderAndFooter reset() { - return RESET; - } + @Override + public void encode(ByteBuf buf, Direction direction, int protocolVersion) { + writeString(buf, header); + writeString(buf, footer); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + public static HeaderAndFooter create(Component header, Component footer) { + ComponentSerializer json = ComponentSerializers.JSON; + return new HeaderAndFooter(json.serialize(header), json.serialize(footer)); + } + + public static HeaderAndFooter reset() { + return RESET; + } } 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 fab67338d..7cbbd1165 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 @@ -8,121 +8,122 @@ import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; public class JoinGame implements MinecraftPacket { - private int entityId; - private short gamemode; - private int dimension; - private short difficulty; - private short maxPlayers; - private @Nullable String levelType; - private boolean reducedDebugInfo; - public int getEntityId() { - return entityId; - } + private int entityId; + private short gamemode; + private int dimension; + private short difficulty; + private short maxPlayers; + private @Nullable String levelType; + private boolean reducedDebugInfo; - public void setEntityId(int entityId) { - this.entityId = entityId; - } + public int getEntityId() { + return entityId; + } - public short getGamemode() { - return gamemode; - } + public void setEntityId(int entityId) { + this.entityId = entityId; + } - public void setGamemode(short gamemode) { - this.gamemode = gamemode; - } + public short getGamemode() { + return gamemode; + } - public int getDimension() { - return dimension; - } + public void setGamemode(short gamemode) { + this.gamemode = gamemode; + } - public void setDimension(int dimension) { - this.dimension = dimension; - } + public int getDimension() { + return dimension; + } - public short getDifficulty() { - return difficulty; - } + public void setDimension(int dimension) { + this.dimension = dimension; + } - public void setDifficulty(short difficulty) { - this.difficulty = difficulty; - } + public short getDifficulty() { + return difficulty; + } - public short getMaxPlayers() { - return maxPlayers; - } + public void setDifficulty(short difficulty) { + this.difficulty = difficulty; + } - public void setMaxPlayers(short maxPlayers) { - this.maxPlayers = maxPlayers; - } + public short getMaxPlayers() { + return maxPlayers; + } - public String getLevelType() { - if (levelType == null) { - throw new IllegalStateException("No level type specified."); - } - return levelType; - } + public void setMaxPlayers(short maxPlayers) { + this.maxPlayers = maxPlayers; + } - public void setLevelType(String levelType) { - this.levelType = levelType; + public String getLevelType() { + if (levelType == null) { + throw new IllegalStateException("No level type specified."); } + return levelType; + } - public boolean isReducedDebugInfo() { - return reducedDebugInfo; - } + public void setLevelType(String levelType) { + this.levelType = levelType; + } - public void setReducedDebugInfo(boolean reducedDebugInfo) { - this.reducedDebugInfo = reducedDebugInfo; - } + public boolean isReducedDebugInfo() { + return reducedDebugInfo; + } - @Override - public String toString() { - return "JoinGame{" + - "entityId=" + entityId + - ", gamemode=" + gamemode + - ", dimension=" + dimension + - ", difficulty=" + difficulty + - ", maxPlayers=" + maxPlayers + - ", levelType='" + levelType + '\'' + - ", reducedDebugInfo=" + reducedDebugInfo + - '}'; - } + public void setReducedDebugInfo(boolean reducedDebugInfo) { + this.reducedDebugInfo = reducedDebugInfo; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.entityId = buf.readInt(); - this.gamemode = buf.readUnsignedByte(); - if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) { - this.dimension = buf.readInt(); - } else { - this.dimension = buf.readByte(); - } - this.difficulty = buf.readUnsignedByte(); - this.maxPlayers = buf.readUnsignedByte(); - this.levelType = ProtocolUtils.readString(buf, 16); - this.reducedDebugInfo = buf.readBoolean(); - } + @Override + public String toString() { + return "JoinGame{" + + "entityId=" + entityId + + ", gamemode=" + gamemode + + ", dimension=" + dimension + + ", difficulty=" + difficulty + + ", maxPlayers=" + maxPlayers + + ", levelType='" + levelType + '\'' + + ", reducedDebugInfo=" + reducedDebugInfo + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - buf.writeInt(entityId); - buf.writeByte(gamemode); - if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) { - buf.writeInt(dimension); - } else { - buf.writeByte(dimension); - } - buf.writeByte(difficulty); - buf.writeByte(maxPlayers); - if (levelType == null) { - throw new IllegalStateException("No level type specified."); - } - ProtocolUtils.writeString(buf, levelType); - buf.writeBoolean(reducedDebugInfo); + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.entityId = buf.readInt(); + this.gamemode = buf.readUnsignedByte(); + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) { + this.dimension = buf.readInt(); + } else { + this.dimension = buf.readByte(); } + this.difficulty = buf.readUnsignedByte(); + this.maxPlayers = buf.readUnsignedByte(); + this.levelType = ProtocolUtils.readString(buf, 16); + this.reducedDebugInfo = buf.readBoolean(); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + buf.writeInt(entityId); + buf.writeByte(gamemode); + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) { + buf.writeInt(dimension); + } else { + buf.writeByte(dimension); } + buf.writeByte(difficulty); + buf.writeByte(maxPlayers); + if (levelType == null) { + throw new IllegalStateException("No level type specified."); + } + ProtocolUtils.writeString(buf, levelType); + buf.writeBoolean(reducedDebugInfo); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java index 0de51ab9c..2d2cecab9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java @@ -1,51 +1,52 @@ package com.velocitypowered.proxy.protocol.packet; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2; + 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 static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2; - public class KeepAlive implements MinecraftPacket { - private long randomId; - public long getRandomId() { - return randomId; - } + private long randomId; - public void setRandomId(long randomId) { - this.randomId = randomId; - } + public long getRandomId() { + return randomId; + } - @Override - public String toString() { - return "KeepAlive{" + - "randomId=" + randomId + - '}'; - } + public void setRandomId(long randomId) { + this.randomId = randomId; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - if (protocolVersion >= MINECRAFT_1_12_2) { - randomId = buf.readLong(); - } else { - randomId = ProtocolUtils.readVarInt(buf); - } - } + @Override + public String toString() { + return "KeepAlive{" + + "randomId=" + randomId + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - if (protocolVersion >= MINECRAFT_1_12_2) { - buf.writeLong(randomId); - } else { - ProtocolUtils.writeVarInt(buf, (int) randomId); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + if (protocolVersion >= MINECRAFT_1_12_2) { + randomId = buf.readLong(); + } else { + randomId = ProtocolUtils.readVarInt(buf); } + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + if (protocolVersion >= MINECRAFT_1_12_2) { + buf.writeLong(randomId); + } else { + ProtocolUtils.writeVarInt(buf, (int) randomId); } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyDisconnect.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyDisconnect.java index 7a4ef0f0f..3f6271a69 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyDisconnect.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyDisconnect.java @@ -4,29 +4,30 @@ import net.kyori.text.TextComponent; import net.kyori.text.serializer.ComponentSerializers; public class LegacyDisconnect { - private final String reason; - public LegacyDisconnect(String reason) { - this.reason = reason; - } + private final String reason; - public static LegacyDisconnect fromPingResponse(LegacyPingResponse response) { - String kickMessage = String.join("\0", - "§1", - Integer.toString(response.getProtocolVersion()), - response.getServerVersion(), - response.getMotd(), - Integer.toString(response.getPlayersOnline()), - Integer.toString(response.getPlayersMax()) - ); - return new LegacyDisconnect(kickMessage); - } + public LegacyDisconnect(String reason) { + this.reason = reason; + } - public static LegacyDisconnect from(TextComponent component) { - return new LegacyDisconnect(ComponentSerializers.LEGACY.serialize(component)); - } + public static LegacyDisconnect fromPingResponse(LegacyPingResponse response) { + String kickMessage = String.join("\0", + "§1", + Integer.toString(response.getProtocolVersion()), + response.getServerVersion(), + response.getMotd(), + Integer.toString(response.getPlayersOnline()), + Integer.toString(response.getPlayersMax()) + ); + return new LegacyDisconnect(kickMessage); + } - public String getReason() { - return reason; - } + public static LegacyDisconnect from(TextComponent component) { + return new LegacyDisconnect(ComponentSerializers.LEGACY.serialize(component)); + } + + public String getReason() { + return reason; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshake.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshake.java index 33249abf5..91a11744f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshake.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshake.java @@ -6,18 +6,19 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants; import io.netty.buffer.ByteBuf; public class LegacyHandshake implements MinecraftPacket { - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - throw new UnsupportedOperationException(); - } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - throw new UnsupportedOperationException(); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPing.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPing.java index 51f46a4b4..3d07399fd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPing.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPing.java @@ -6,18 +6,19 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants; import io.netty.buffer.ByteBuf; public class LegacyPing implements MinecraftPacket { - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - throw new UnsupportedOperationException(); - } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - throw new UnsupportedOperationException(); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 354abec73..917a10717 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 @@ -5,57 +5,60 @@ import com.velocitypowered.api.proxy.server.ServerPing; import net.kyori.text.serializer.ComponentSerializers; public class LegacyPingResponse { - private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0, ImmutableList.of()); - 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; - this.serverVersion = serverVersion; - this.motd = motd; - this.playersOnline = playersOnline; - this.playersMax = playersMax; - } + private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0, + ImmutableList.of()); + private final int protocolVersion; + private final String serverVersion; + private final String motd; + private final int playersOnline; + private final int playersMax; - public int getProtocolVersion() { - return protocolVersion; - } + public LegacyPingResponse(int protocolVersion, String serverVersion, String motd, + int playersOnline, int playersMax) { + this.protocolVersion = protocolVersion; + this.serverVersion = serverVersion; + this.motd = motd; + this.playersOnline = playersOnline; + this.playersMax = playersMax; + } - public String getServerVersion() { - return serverVersion; - } + public int getProtocolVersion() { + return protocolVersion; + } - public String getMotd() { - return motd; - } + public String getServerVersion() { + return serverVersion; + } - public int getPlayersOnline() { - return playersOnline; - } + public String getMotd() { + return motd; + } - public int getPlayersMax() { - return playersMax; - } + public int getPlayersOnline() { + return playersOnline; + } - @Override - public String toString() { - return "LegacyPingResponse{" + - "protocolVersion=" + protocolVersion + - ", serverVersion='" + serverVersion + '\'' + - ", motd='" + motd + '\'' + - ", playersOnline=" + playersOnline + - ", playersMax=" + playersMax + - '}'; - } + public int getPlayersMax() { + return playersMax; + } - public static LegacyPingResponse from(ServerPing ping) { - return new LegacyPingResponse(ping.getVersion().getProtocol(), - ping.getVersion().getName(), - ComponentSerializers.LEGACY.serialize(ping.getDescription()), - ping.getPlayers().orElse(FAKE_PLAYERS).getOnline(), - ping.getPlayers().orElse(FAKE_PLAYERS).getMax()); - } + @Override + public String toString() { + return "LegacyPingResponse{" + + "protocolVersion=" + protocolVersion + + ", serverVersion='" + serverVersion + '\'' + + ", motd='" + motd + '\'' + + ", playersOnline=" + playersOnline + + ", playersMax=" + playersMax + + '}'; + } + + public static LegacyPingResponse from(ServerPing ping) { + return new LegacyPingResponse(ping.getVersion().getProtocol(), + ping.getVersion().getName(), + ComponentSerializers.LEGACY.serialize(ping.getDescription()), + ping.getPlayers().orElse(FAKE_PLAYERS).getOnline(), + ping.getPlayers().orElse(FAKE_PLAYERS).getMax()); + } } 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 bbef77d0c..f7ea9cd97 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 @@ -9,67 +9,68 @@ import io.netty.buffer.Unpooled; import org.checkerframework.checker.nullness.qual.Nullable; public class LoginPluginMessage implements MinecraftPacket { - private int id; - private @Nullable String channel; - private ByteBuf data = Unpooled.EMPTY_BUFFER; - public LoginPluginMessage() { + private int id; + 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 String getChannel() { + if (channel == null) { + throw new IllegalStateException("Channel is not specified!"); } + return channel; + } - public LoginPluginMessage(int id, @Nullable String channel, ByteBuf data) { - this.id = id; - this.channel = channel; - this.data = data; - } + public ByteBuf getData() { + return data; + } - public int getId() { - return id; - } + @Override + public String toString() { + return "LoginPluginMessage{" + + "id=" + id + + ", channel='" + channel + '\'' + + ", data=" + data + + '}'; + } - public String getChannel() { - if (channel == null) { - throw new IllegalStateException("Channel is not specified!"); - } - return channel; + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.id = ProtocolUtils.readVarInt(buf); + this.channel = ProtocolUtils.readString(buf); + if (buf.isReadable()) { + this.data = buf.readSlice(buf.readableBytes()); + } else { + this.data = Unpooled.EMPTY_BUFFER; } + } - public ByteBuf getData() { - return data; + @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); + } - @Override - public String toString() { - return "LoginPluginMessage{" + - "id=" + id + - ", channel='" + channel + '\'' + - ", data=" + data + - '}'; - } - - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.id = ProtocolUtils.readVarInt(buf); - this.channel = ProtocolUtils.readString(buf); - if (buf.isReadable()) { - this.data = buf.readSlice(buf.readableBytes()); - } else { - this.data = Unpooled.EMPTY_BUFFER; - } - } - - @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); - } - - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 c0c637331..bcbb9a639 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 @@ -8,63 +8,64 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; public class LoginPluginResponse implements MinecraftPacket { - private int id; - private boolean success; - private ByteBuf data = Unpooled.EMPTY_BUFFER; - public int getId() { - return id; - } + private int id; + private boolean success; + private ByteBuf data = Unpooled.EMPTY_BUFFER; - public void setId(int id) { - this.id = id; - } + public int getId() { + return id; + } - public boolean isSuccess() { - return success; - } + public void setId(int id) { + this.id = id; + } - public void setSuccess(boolean success) { - this.success = success; - } + public boolean isSuccess() { + return success; + } - public ByteBuf getData() { - return data; - } + public void setSuccess(boolean success) { + this.success = success; + } - public void setData(ByteBuf data) { - this.data = data; - } + public ByteBuf getData() { + return data; + } - @Override - public String toString() { - return "LoginPluginResponse{" + - "id=" + id + - ", success=" + success + - ", data=" + data + - '}'; - } + public void setData(ByteBuf data) { + this.data = data; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.id = ProtocolUtils.readVarInt(buf); - this.success = buf.readBoolean(); - if (buf.isReadable()) { - this.data = buf.readSlice(buf.readableBytes()); - } else { - this.data = Unpooled.EMPTY_BUFFER; - } - } + @Override + public String toString() { + return "LoginPluginResponse{" + + "id=" + id + + ", success=" + success + + ", data=" + data + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeVarInt(buf, id); - buf.writeBoolean(success); - buf.writeBytes(data); + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.id = ProtocolUtils.readVarInt(buf); + this.success = buf.readBoolean(); + if (buf.isReadable()) { + this.data = buf.readSlice(buf.readableBytes()); + } else { + this.data = Unpooled.EMPTY_BUFFER; } + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeVarInt(buf, id); + buf.writeBoolean(success); + buf.writeBytes(data); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 0fa2e4c3c..c2a5afeb9 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 @@ -8,194 +8,196 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; 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; -import java.util.UUID; - public class PlayerListItem implements MinecraftPacket { - public static final int ADD_PLAYER = 0; - public static final int UPDATE_GAMEMODE = 1; - public static final int UPDATE_LATENCY = 2; - public static final int UPDATE_DISPLAY_NAME = 3; - public static final int REMOVE_PLAYER = 4; - private int action; - private final List items = new ArrayList<>(); - public PlayerListItem(int action, List items) { - this.action = action; - this.items.addAll(items); + public static final int ADD_PLAYER = 0; + public static final int UPDATE_GAMEMODE = 1; + public static final int UPDATE_LATENCY = 2; + public static final int UPDATE_DISPLAY_NAME = 3; + public static final int REMOVE_PLAYER = 4; + private int action; + private final List items = new ArrayList<>(); + + public PlayerListItem(int action, List items) { + this.action = action; + this.items.addAll(items); + } + + public PlayerListItem() { + } + + public int getAction() { + return action; + } + + public List getItems() { + return items; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + action = ProtocolUtils.readVarInt(buf); + int length = ProtocolUtils.readVarInt(buf); + + for (int i = 0; i < length; i++) { + Item item = new Item(ProtocolUtils.readUuid(buf)); + items.add(item); + switch (action) { + case ADD_PLAYER: { + item.setName(ProtocolUtils.readString(buf)); + item.setProperties(ProtocolUtils.readProperties(buf)); + item.setGameMode(ProtocolUtils.readVarInt(buf)); + item.setLatency(ProtocolUtils.readVarInt(buf)); + item.setDisplayName(readOptionalComponent(buf)); + break; + } + case UPDATE_GAMEMODE: + item.setGameMode(ProtocolUtils.readVarInt(buf)); + break; + case UPDATE_LATENCY: + item.setLatency(ProtocolUtils.readVarInt(buf)); + break; + case UPDATE_DISPLAY_NAME: + item.setDisplayName(readOptionalComponent(buf)); + break; + case REMOVE_PLAYER: + //Do nothing, all that is needed is the uuid + break; + default: + throw new UnsupportedOperationException("Unknown action " + action); + } + } + } + + @Nullable + private static Component readOptionalComponent(ByteBuf buf) { + if (buf.readBoolean()) { + return ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf)); + } + return null; + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeVarInt(buf, action); + ProtocolUtils.writeVarInt(buf, items.size()); + for (Item item : items) { + ProtocolUtils.writeUuid(buf, item.getUuid()); + switch (action) { + case ADD_PLAYER: + ProtocolUtils.writeString(buf, item.getName()); + ProtocolUtils.writeProperties(buf, item.getProperties()); + ProtocolUtils.writeVarInt(buf, item.getGameMode()); + ProtocolUtils.writeVarInt(buf, item.getLatency()); + + writeDisplayName(buf, item.getDisplayName()); + break; + case UPDATE_GAMEMODE: + ProtocolUtils.writeVarInt(buf, item.getGameMode()); + break; + case UPDATE_LATENCY: + ProtocolUtils.writeVarInt(buf, item.getLatency()); + break; + case UPDATE_DISPLAY_NAME: + writeDisplayName(buf, item.getDisplayName()); + break; + case REMOVE_PLAYER: + //Do nothing, all that is needed is the uuid + break; + default: + throw new UnsupportedOperationException("Unknown action " + action); + } + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + private void writeDisplayName(ByteBuf buf, @Nullable Component displayName) { + buf.writeBoolean(displayName != null); + if (displayName != null) { + ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(displayName)); + } + } + + public static class Item { + + private final UUID uuid; + private String name = ""; + private List properties = ImmutableList.of(); + private int gameMode; + private int latency; + private @Nullable Component displayName; + + public Item(UUID uuid) { + this.uuid = uuid; } - public PlayerListItem() {} - - public int getAction() { - return action; + public static Item from(TabListEntry entry) { + return new Item(entry.getProfile().idAsUuid()) + .setName(entry.getProfile().getName()) + .setProperties(entry.getProfile().getProperties()) + .setLatency(entry.getLatency()) + .setGameMode(entry.getGameMode()) + .setDisplayName(entry.getDisplayName().orElse(null)); } - public List getItems() { - return items; + public UUID getUuid() { + return uuid; } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - action = ProtocolUtils.readVarInt(buf); - int length = ProtocolUtils.readVarInt(buf); - - for (int i = 0; i < length; i++) { - Item item = new Item(ProtocolUtils.readUuid(buf)); - items.add(item); - switch (action) { - case ADD_PLAYER: { - item.setName(ProtocolUtils.readString(buf)); - item.setProperties(ProtocolUtils.readProperties(buf)); - item.setGameMode(ProtocolUtils.readVarInt(buf)); - item.setLatency(ProtocolUtils.readVarInt(buf)); - item.setDisplayName(readOptionalComponent(buf)); - break; - } - case UPDATE_GAMEMODE: - item.setGameMode(ProtocolUtils.readVarInt(buf)); - break; - case UPDATE_LATENCY: - item.setLatency(ProtocolUtils.readVarInt(buf)); - break; - case UPDATE_DISPLAY_NAME: - item.setDisplayName(readOptionalComponent(buf)); - break; - case REMOVE_PLAYER: - //Do nothing, all that is needed is the uuid - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } + public String getName() { + return name; } - @Nullable - private static Component readOptionalComponent(ByteBuf buf) { - if (buf.readBoolean()) { - return ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf)); - } - return null; + public Item setName(String name) { + this.name = name; + return this; } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeVarInt(buf, action); - ProtocolUtils.writeVarInt(buf, items.size()); - for (Item item : items) { - ProtocolUtils.writeUuid(buf, item.getUuid()); - switch (action) { - case ADD_PLAYER: - ProtocolUtils.writeString(buf, item.getName()); - ProtocolUtils.writeProperties(buf, item.getProperties()); - ProtocolUtils.writeVarInt(buf, item.getGameMode()); - ProtocolUtils.writeVarInt(buf, item.getLatency()); - - writeDisplayName(buf, item.getDisplayName()); - break; - case UPDATE_GAMEMODE: - ProtocolUtils.writeVarInt(buf, item.getGameMode()); - break; - case UPDATE_LATENCY: - ProtocolUtils.writeVarInt(buf, item.getLatency()); - break; - case UPDATE_DISPLAY_NAME: - writeDisplayName(buf, item.getDisplayName()); - break; - case REMOVE_PLAYER: - //Do nothing, all that is needed is the uuid - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } + public List getProperties() { + return properties; } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); + public Item setProperties(List properties) { + this.properties = properties; + return this; } - private void writeDisplayName(ByteBuf buf, @Nullable Component displayName) { - buf.writeBoolean(displayName != null); - if (displayName != null) { - ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(displayName)); - } + public int getGameMode() { + return gameMode; } - public static class Item { - private final UUID uuid; - private String name = ""; - private List properties = ImmutableList.of(); - private int gameMode; - private int latency; - private @Nullable Component displayName; - - public Item(UUID uuid) { - this.uuid = uuid; - } - - public static Item from(TabListEntry entry) { - return new Item(entry.getProfile().idAsUuid()) - .setName(entry.getProfile().getName()) - .setProperties(entry.getProfile().getProperties()) - .setLatency(entry.getLatency()) - .setGameMode(entry.getGameMode()) - .setDisplayName(entry.getDisplayName().orElse(null)); - } - - public UUID getUuid() { - return uuid; - } - - public String getName() { - return name; - } - - public Item setName(String name) { - this.name = name; - return this; - } - - public List getProperties() { - return properties; - } - - public Item setProperties(List properties) { - this.properties = properties; - return this; - } - - public int getGameMode() { - return gameMode; - } - - public Item setGameMode(int gamemode) { - this.gameMode = gamemode; - return this; - } - - public int getLatency() { - return latency; - } - - public Item setLatency(int latency) { - this.latency = latency; - return this; - } - - public @Nullable Component getDisplayName() { - return displayName; - } - - public Item setDisplayName(@Nullable Component displayName) { - this.displayName = displayName; - return this; - } + public Item setGameMode(int gamemode) { + this.gameMode = gamemode; + return this; } + + public int getLatency() { + return latency; + } + + public Item setLatency(int latency) { + this.latency = latency; + return this; + } + + public @Nullable Component getDisplayName() { + return 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 3afd67e25..ed594cffb 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 @@ -1,5 +1,7 @@ package com.velocitypowered.proxy.protocol.packet; +import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; + import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -8,57 +10,56 @@ 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 @Nullable String channel; - private byte[] data = EMPTY_BYTE_ARRAY; - public String getChannel() { - if (channel == null) { - throw new IllegalStateException("Channel is not specified."); - } - return channel; - } + private @Nullable String channel; + private byte[] data = EMPTY_BYTE_ARRAY; - public void setChannel(String channel) { - this.channel = channel; + public String getChannel() { + if (channel == null) { + throw new IllegalStateException("Channel is not specified."); } + return channel; + } - public byte[] getData() { - return data; - } + public void setChannel(String channel) { + this.channel = channel; + } - public void setData(byte[] data) { - this.data = data; - } + public byte[] getData() { + return data; + } - @Override - public String toString() { - return "PluginMessage{" + - "channel='" + channel + '\'' + - ", data=" + ByteBufUtil.hexDump(data) + - '}'; - } + public void setData(byte[] data) { + this.data = data; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.channel = ProtocolUtils.readString(buf); - this.data = new byte[buf.readableBytes()]; - buf.readBytes(data); - } + @Override + public String toString() { + return "PluginMessage{" + + "channel='" + channel + '\'' + + ", data=" + ByteBufUtil.hexDump(data) + + '}'; + } - @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); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.channel = ProtocolUtils.readString(buf); + this.data = new byte[buf.readableBytes()]; + buf.readBytes(data); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); + @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); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 fbe530815..89383f7bf 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 @@ -7,81 +7,82 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; public class Respawn implements MinecraftPacket { - private int dimension; - private short difficulty; - private short gamemode; - private String levelType = ""; - public Respawn() { - } + private int dimension; + private short difficulty; + private short gamemode; + private String levelType = ""; - public Respawn(int dimension, short difficulty, short gamemode, String levelType) { - this.dimension = dimension; - this.difficulty = difficulty; - this.gamemode = gamemode; - this.levelType = levelType; - } + public Respawn() { + } - public int getDimension() { - return dimension; - } + public Respawn(int dimension, short difficulty, short gamemode, String levelType) { + this.dimension = dimension; + this.difficulty = difficulty; + this.gamemode = gamemode; + this.levelType = levelType; + } - public void setDimension(int dimension) { - this.dimension = dimension; - } + public int getDimension() { + return dimension; + } - public short getDifficulty() { - return difficulty; - } + public void setDimension(int dimension) { + this.dimension = dimension; + } - public void setDifficulty(short difficulty) { - this.difficulty = difficulty; - } + public short getDifficulty() { + return difficulty; + } - public short getGamemode() { - return gamemode; - } + public void setDifficulty(short difficulty) { + this.difficulty = difficulty; + } - public void setGamemode(short gamemode) { - this.gamemode = gamemode; - } + public short getGamemode() { + return gamemode; + } - public String getLevelType() { - return levelType; - } + public void setGamemode(short gamemode) { + this.gamemode = gamemode; + } - public void setLevelType(String levelType) { - this.levelType = levelType; - } + public String getLevelType() { + return levelType; + } - @Override - public String toString() { - return "Respawn{" + - "dimension=" + dimension + - ", difficulty=" + difficulty + - ", gamemode=" + gamemode + - ", levelType='" + levelType + '\'' + - '}'; - } + public void setLevelType(String levelType) { + this.levelType = levelType; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.dimension = buf.readInt(); - this.difficulty = buf.readUnsignedByte(); - this.gamemode = buf.readUnsignedByte(); - this.levelType = ProtocolUtils.readString(buf, 16); - } + @Override + public String toString() { + return "Respawn{" + + "dimension=" + dimension + + ", difficulty=" + difficulty + + ", gamemode=" + gamemode + + ", levelType='" + levelType + '\'' + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - buf.writeInt(dimension); - buf.writeByte(difficulty); - buf.writeByte(gamemode); - ProtocolUtils.writeString(buf, levelType); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.dimension = buf.readInt(); + this.difficulty = buf.readUnsignedByte(); + this.gamemode = buf.readUnsignedByte(); + this.levelType = ProtocolUtils.readString(buf, 16); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + buf.writeInt(dimension); + buf.writeByte(difficulty); + buf.writeByte(gamemode); + ProtocolUtils.writeString(buf, levelType); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 0868c65cc..7bb3a4944 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 @@ -9,43 +9,45 @@ import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; public class ServerLogin implements MinecraftPacket { - private @Nullable String username; - public ServerLogin() {} + private @Nullable String username; - public ServerLogin(String username) { - this.username = Preconditions.checkNotNull(username, "username"); + public ServerLogin() { + } + + public ServerLogin(String username) { + this.username = Preconditions.checkNotNull(username, "username"); + } + + public String getUsername() { + if (username == null) { + throw new IllegalStateException("No username found!"); } + return username; + } - public String getUsername() { - if (username == null) { - throw new IllegalStateException("No username found!"); - } - return username; - } + @Override + public String toString() { + return "ServerLogin{" + + "username='" + username + '\'' + + '}'; + } - @Override - public String toString() { - return "ServerLogin{" + - "username='" + username + '\'' + - '}'; - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + username = ProtocolUtils.readString(buf, 16); + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - username = ProtocolUtils.readString(buf, 16); + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + if (username == null) { + throw new IllegalStateException("No username found!"); } + ProtocolUtils.writeString(buf, username); + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - if (username == null) { - throw new IllegalStateException("No username found!"); - } - ProtocolUtils.writeString(buf, username); - } - - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 d553c6566..068b0d428 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,64 +5,64 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.UUID; - public class ServerLoginSuccess implements MinecraftPacket { - private @Nullable UUID uuid; - private @Nullable String username; - public UUID getUuid() { - if (uuid == null) { - throw new IllegalStateException("No UUID specified!"); - } - return uuid; - } + private @Nullable UUID uuid; + private @Nullable String username; - public void setUuid(UUID uuid) { - this.uuid = uuid; + public UUID getUuid() { + if (uuid == null) { + throw new IllegalStateException("No UUID specified!"); } + return uuid; + } - public String getUsername() { - if (username == null) { - throw new IllegalStateException("No username specified!"); - } - return username; - } + public void setUuid(UUID uuid) { + this.uuid = uuid; + } - public void setUsername(String username) { - this.username = username; + public String getUsername() { + if (username == null) { + throw new IllegalStateException("No username specified!"); } + return username; + } - @Override - public String toString() { - return "ServerLoginSuccess{" + - "uuid=" + uuid + - ", username='" + username + '\'' + - '}'; - } + public void setUsername(String username) { + this.username = username; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); - username = ProtocolUtils.readString(buf, 16); - } + @Override + public String toString() { + return "ServerLoginSuccess{" + + "uuid=" + uuid + + ", username='" + username + '\'' + + '}'; + } - @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); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); + username = ProtocolUtils.readString(buf, 16); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); + @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); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/SetCompression.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/SetCompression.java index f1c3bbf4a..7d33cae36 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/SetCompression.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/SetCompression.java @@ -7,41 +7,43 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; public class SetCompression implements MinecraftPacket { - private int threshold; - public SetCompression() {} + private int threshold; - public SetCompression(int threshold) { - this.threshold = threshold; - } + public SetCompression() { + } - public int getThreshold() { - return threshold; - } + public SetCompression(int threshold) { + this.threshold = threshold; + } - public void setThreshold(int threshold) { - this.threshold = threshold; - } + public int getThreshold() { + return threshold; + } - @Override - public String toString() { - return "SetCompression{" + - "threshold=" + threshold + - '}'; - } + public void setThreshold(int threshold) { + this.threshold = threshold; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.threshold = ProtocolUtils.readVarInt(buf); - } + @Override + public String toString() { + return "SetCompression{" + + "threshold=" + threshold + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeVarInt(buf, threshold); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.threshold = ProtocolUtils.readVarInt(buf); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeVarInt(buf, threshold); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPing.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPing.java index 28bbcbe7d..48c9475ce 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPing.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPing.java @@ -6,20 +6,21 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants; import io.netty.buffer.ByteBuf; public class StatusPing implements MinecraftPacket { - private long randomId; - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - randomId = buf.readLong(); - } + private long randomId; - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - buf.writeLong(randomId); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + randomId = buf.readLong(); + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + buf.writeLong(randomId); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java index cc2ad9e36..aa642d9b3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequest.java @@ -6,29 +6,30 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants; import io.netty.buffer.ByteBuf; public class StatusRequest implements MinecraftPacket { - public static final StatusRequest INSTANCE = new StatusRequest(); - private StatusRequest() { + public static final StatusRequest INSTANCE = new StatusRequest(); - } + private StatusRequest() { - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - // There is no additional data to decode. - } + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - // There is no data to decode. - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + // There is no additional data to decode. + } - @Override - public String toString() { - return "StatusRequest"; - } + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + // There is no data to decode. + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public String toString() { + return "StatusRequest"; + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 e508e49c9..bd264eee4 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 @@ -8,43 +8,45 @@ import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; public class StatusResponse implements MinecraftPacket { - private @Nullable String status; - public StatusResponse() {} + private @Nullable String status; - public StatusResponse(String status) { - this.status = status; + public StatusResponse() { + } + + public StatusResponse(String status) { + this.status = status; + } + + public String getStatus() { + if (status == null) { + throw new IllegalStateException("Status is not specified"); } + return status; + } - public String getStatus() { - if (status == null) { - throw new IllegalStateException("Status is not specified"); - } - return status; - } + @Override + public String toString() { + return "StatusResponse{" + + "status='" + status + '\'' + + '}'; + } - @Override - public String toString() { - return "StatusResponse{" + - "status='" + status + '\'' + - '}'; - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + status = ProtocolUtils.readString(buf, Short.MAX_VALUE); + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - status = ProtocolUtils.readString(buf, Short.MAX_VALUE); + @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); + } - @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); - } - - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 f933d4dca..f1229d2a0 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 @@ -1,5 +1,7 @@ package com.velocitypowered.proxy.protocol.packet; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9; + import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -7,88 +9,87 @@ 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 @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; - } + private @Nullable String command; + private boolean assumeCommand; + private boolean hasPosition; + private long position; - public void setCommand(String command) { - this.command = command; + public String getCommand() { + if (command == null) { + throw new IllegalStateException("Command is not specified"); } + return command; + } - public boolean isAssumeCommand() { - return assumeCommand; - } + public void setCommand(String command) { + this.command = command; + } - public void setAssumeCommand(boolean assumeCommand) { - this.assumeCommand = assumeCommand; - } + public boolean isAssumeCommand() { + return assumeCommand; + } - public boolean isHasPosition() { - return hasPosition; - } + public void setAssumeCommand(boolean assumeCommand) { + this.assumeCommand = assumeCommand; + } - public void setHasPosition(boolean hasPosition) { - this.hasPosition = hasPosition; - } + public boolean isHasPosition() { + return hasPosition; + } - public long getPosition() { - return position; - } + public void setHasPosition(boolean hasPosition) { + this.hasPosition = hasPosition; + } - public void setPosition(long position) { - this.position = position; - } + public long getPosition() { + return position; + } - @Override - public String toString() { - return "TabCompleteRequest{" + - "command='" + command + '\'' + - ", assumeCommand=" + assumeCommand + - ", hasPosition=" + hasPosition + - ", position=" + position + - '}'; - } + public void setPosition(long position) { + this.position = position; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - this.command = ProtocolUtils.readString(buf); - if (protocolVersion >= MINECRAFT_1_9) { - this.assumeCommand = buf.readBoolean(); - } - this.hasPosition = buf.readBoolean(); - if (hasPosition) { - this.position = buf.readLong(); - } - } + @Override + public String toString() { + return "TabCompleteRequest{" + + "command='" + command + '\'' + + ", assumeCommand=" + assumeCommand + + ", hasPosition=" + hasPosition + + ", position=" + position + + '}'; + } - @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); - } - buf.writeBoolean(hasPosition); - if (hasPosition) { - buf.writeLong(position); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.command = ProtocolUtils.readString(buf); + if (protocolVersion >= MINECRAFT_1_9) { + this.assumeCommand = buf.readBoolean(); } + this.hasPosition = buf.readBoolean(); + if (hasPosition) { + this.position = buf.readLong(); + } + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); + @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); + } + buf.writeBoolean(hasPosition); + if (hasPosition) { + buf.writeLong(position); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java index d58fae70d..723cbdd4b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java @@ -5,42 +5,42 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; - import java.util.ArrayList; import java.util.List; public class TabCompleteResponse implements MinecraftPacket { - private final List offers = new ArrayList<>(); - public List getOffers() { - return offers; - } + private final List offers = new ArrayList<>(); - @Override - public String toString() { - return "TabCompleteResponse{" + - "offers=" + offers + - '}'; - } + public List getOffers() { + return offers; + } - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - int offersAvailable = ProtocolUtils.readVarInt(buf); - for (int i = 0; i < offersAvailable; i++) { - offers.add(ProtocolUtils.readString(buf)); - } - } + @Override + public String toString() { + return "TabCompleteResponse{" + + "offers=" + offers + + '}'; + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeVarInt(buf, offers.size()); - for (String offer : offers) { - ProtocolUtils.writeString(buf, offer); - } + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + int offersAvailable = ProtocolUtils.readVarInt(buf); + for (int i = 0; i < offersAvailable; i++) { + offers.add(ProtocolUtils.readString(buf)); } + } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeVarInt(buf, offers.size()); + for (String offer : offers) { + ProtocolUtils.writeString(buf, offer); } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } 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 af9fd9a79..d2b31c7ff 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 @@ -8,146 +8,150 @@ import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; public class TitlePacket implements MinecraftPacket { - public static final int SET_TITLE = 0; - public static final int SET_SUBTITLE = 1; - public static final int SET_ACTION_BAR = 2; - public static final int SET_TIMES = 3; - public static final int SET_TIMES_OLD = 2; - public static final int HIDE = 4; - public static final int HIDE_OLD = 3; - public static final int RESET = 5; - public static final int RESET_OLD = 4; - private int action; - private @Nullable String component; - private int fadeIn; - private int stay; - private int fadeOut; + public static final int SET_TITLE = 0; + public static final int SET_SUBTITLE = 1; + public static final int SET_ACTION_BAR = 2; + public static final int SET_TIMES = 3; + public static final int SET_TIMES_OLD = 2; + public static final int HIDE = 4; + public static final int HIDE_OLD = 3; + public static final int RESET = 5; + public static final int RESET_OLD = 4; - @Override - public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - throw new UnsupportedOperationException(); // encode only + private int action; + private @Nullable String component; + private int fadeIn; + private int stay; + private int fadeOut; + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + throw new UnsupportedOperationException(); // encode only + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeVarInt(buf, action); + if (protocolVersion >= ProtocolConstants.MINECRAFT_1_11) { + // 1.11+ shifted the action enum by 1 to handle the action bar + switch (action) { + 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: + buf.writeInt(fadeIn); + buf.writeInt(stay); + buf.writeInt(fadeOut); + break; + case HIDE: + case RESET: + break; + default: + throw new UnsupportedOperationException("Unknown action " + action); + } + } else { + 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: + buf.writeInt(fadeIn); + buf.writeInt(stay); + buf.writeInt(fadeOut); + break; + case HIDE_OLD: + case RESET_OLD: + break; + default: + throw new UnsupportedOperationException("Unknown action " + action); + } } + } - @Override - public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeVarInt(buf, action); - if (protocolVersion >= ProtocolConstants.MINECRAFT_1_11) { - // 1.11+ shifted the action enum by 1 to handle the action bar - switch (action) { - 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: - buf.writeInt(fadeIn); - buf.writeInt(stay); - buf.writeInt(fadeOut); - break; - case HIDE: - case RESET: - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } else { - 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: - buf.writeInt(fadeIn); - buf.writeInt(stay); - buf.writeInt(fadeOut); - break; - case HIDE_OLD: - case RESET_OLD: - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } - } + public int getAction() { + return action; + } - public int getAction() { - return action; - } + public void setAction(int action) { + this.action = action; + } - public void setAction(int action) { - this.action = action; - } + public @Nullable String getComponent() { + return component; + } - public @Nullable String getComponent() { - return component; - } + public void setComponent(@Nullable String component) { + this.component = component; + } - public void setComponent(@Nullable String component) { - this.component = component; - } + public int getFadeIn() { + return fadeIn; + } - public int getFadeIn() { - return fadeIn; - } + public void setFadeIn(int fadeIn) { + this.fadeIn = fadeIn; + } - public void setFadeIn(int fadeIn) { - this.fadeIn = fadeIn; - } + public int getStay() { + return stay; + } - public int getStay() { - return stay; - } + public void setStay(int stay) { + this.stay = stay; + } - public void setStay(int stay) { - this.stay = stay; - } + public int getFadeOut() { + return fadeOut; + } - public int getFadeOut() { - return fadeOut; - } + public void setFadeOut(int fadeOut) { + this.fadeOut = fadeOut; + } - public void setFadeOut(int fadeOut) { - this.fadeOut = fadeOut; - } + public static TitlePacket hideForProtocolVersion(int protocolVersion) { + TitlePacket packet = new TitlePacket(); + packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.HIDE + : TitlePacket.HIDE_OLD); + return packet; + } - public static TitlePacket hideForProtocolVersion(int protocolVersion) { - TitlePacket packet = new TitlePacket(); - packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.HIDE : TitlePacket.HIDE_OLD); - return packet; - } + public static TitlePacket resetForProtocolVersion(int protocolVersion) { + TitlePacket packet = new TitlePacket(); + packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.RESET + : TitlePacket.RESET_OLD); + return packet; + } - public static TitlePacket resetForProtocolVersion(int protocolVersion) { - TitlePacket packet = new TitlePacket(); - packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.RESET : TitlePacket.RESET_OLD); - return packet; - } + public static TitlePacket timesForProtocolVersion(int protocolVersion) { + TitlePacket packet = new TitlePacket(); + packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.SET_TIMES + : TitlePacket.SET_TIMES_OLD); + return packet; + } - public static TitlePacket timesForProtocolVersion(int protocolVersion) { - TitlePacket packet = new TitlePacket(); - packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.SET_TIMES : TitlePacket.SET_TIMES_OLD); - return packet; - } + @Override + public String toString() { + return "TitlePacket{" + + "action=" + action + + ", component='" + component + '\'' + + ", fadeIn=" + fadeIn + + ", stay=" + stay + + ", fadeOut=" + fadeOut + + '}'; + } - @Override - public String toString() { - return "TitlePacket{" + - "action=" + action + - ", component='" + component + '\'' + - ", fadeIn=" + fadeIn + - ", stay=" + stay + - ", fadeOut=" + fadeOut + - '}'; - } - - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java index 21cdd6784..6e4cb72fa 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java @@ -1,18 +1,23 @@ package com.velocitypowered.proxy.protocol.util; -import com.google.gson.*; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import com.velocitypowered.api.util.Favicon; - import java.lang.reflect.Type; public class FaviconSerializer implements JsonSerializer, JsonDeserializer { - @Override - public Favicon deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { - return new Favicon(json.getAsString()); - } - @Override - public JsonElement serialize(Favicon src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src.getBase64Url()); - } + @Override + public Favicon deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + return new Favicon(json.getAsString()); + } + + @Override + public JsonElement serialize(Favicon src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.getBase64Url()); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java index c2d537f12..396d3f3a6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java @@ -7,74 +7,80 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; - import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.List; public class PluginMessageUtil { - public static final String BRAND_CHANNEL_LEGACY = "MC|Brand"; - public static final String BRAND_CHANNEL = "minecraft:brand"; - public static final String REGISTER_CHANNEL_LEGACY = "REGISTER"; - public static final String REGISTER_CHANNEL = "minecraft:register"; - public static final String UNREGISTER_CHANNEL_LEGACY = "UNREGISTER"; - public static final String UNREGISTER_CHANNEL = "minecraft:unregister"; - private PluginMessageUtil() { - throw new AssertionError(); + public static final String BRAND_CHANNEL_LEGACY = "MC|Brand"; + public static final String BRAND_CHANNEL = "minecraft:brand"; + public static final String REGISTER_CHANNEL_LEGACY = "REGISTER"; + public static final String REGISTER_CHANNEL = "minecraft:register"; + public static final String UNREGISTER_CHANNEL_LEGACY = "UNREGISTER"; + public static final String UNREGISTER_CHANNEL = "minecraft:unregister"; + + private PluginMessageUtil() { + throw new AssertionError(); + } + + public static boolean isMCBrand(PluginMessage message) { + Preconditions.checkNotNull(message, "message"); + return message.getChannel().equals(BRAND_CHANNEL_LEGACY) || message.getChannel() + .equals(BRAND_CHANNEL); + } + + public static boolean isMCRegister(PluginMessage message) { + Preconditions.checkNotNull(message, "message"); + return message.getChannel().equals(REGISTER_CHANNEL_LEGACY) || message.getChannel() + .equals(REGISTER_CHANNEL); + } + + public static boolean isMCUnregister(PluginMessage message) { + Preconditions.checkNotNull(message, "message"); + return message.getChannel().equals(UNREGISTER_CHANNEL_LEGACY) || message.getChannel() + .equals(UNREGISTER_CHANNEL); + } + + public static List getChannels(PluginMessage message) { + Preconditions.checkNotNull(message, "message"); + Preconditions + .checkArgument(isMCRegister(message) || isMCUnregister(message), "Unknown channel type %s", + message.getChannel()); + String channels = new String(message.getData(), StandardCharsets.UTF_8); + return ImmutableList.copyOf(channels.split("\0")); + } + + public static PluginMessage constructChannelsPacket(int protocolVersion, + Collection channels) { + Preconditions.checkNotNull(channels, "channels"); + String channelName = protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ? REGISTER_CHANNEL + : REGISTER_CHANNEL_LEGACY; + PluginMessage message = new PluginMessage(); + message.setChannel(channelName); + message.setData(String.join("\0", channels).getBytes(StandardCharsets.UTF_8)); + return message; + } + + public static PluginMessage rewriteMCBrand(PluginMessage message) { + Preconditions.checkNotNull(message, "message"); + Preconditions.checkArgument(isMCBrand(message), "message is not a MC Brand plugin message"); + + byte[] rewrittenData; + ByteBuf rewrittenBuf = Unpooled.buffer(); + try { + String currentBrand = ProtocolUtils.readString(Unpooled.wrappedBuffer(message.getData())); + ProtocolUtils.writeString(rewrittenBuf, currentBrand + " (Velocity)"); + rewrittenData = new byte[rewrittenBuf.readableBytes()]; + rewrittenBuf.readBytes(rewrittenData); + } finally { + rewrittenBuf.release(); } - public static boolean isMCBrand(PluginMessage message) { - Preconditions.checkNotNull(message, "message"); - return message.getChannel().equals(BRAND_CHANNEL_LEGACY) || message.getChannel().equals(BRAND_CHANNEL); - } - - public static boolean isMCRegister(PluginMessage message) { - Preconditions.checkNotNull(message, "message"); - return message.getChannel().equals(REGISTER_CHANNEL_LEGACY) || message.getChannel().equals(REGISTER_CHANNEL); - } - - public static boolean isMCUnregister(PluginMessage message) { - Preconditions.checkNotNull(message, "message"); - return message.getChannel().equals(UNREGISTER_CHANNEL_LEGACY) || message.getChannel().equals(UNREGISTER_CHANNEL); - } - - public static List getChannels(PluginMessage message) { - Preconditions.checkNotNull(message, "message"); - Preconditions.checkArgument(isMCRegister(message) || isMCUnregister(message),"Unknown channel type %s", - message.getChannel()); - String channels = new String(message.getData(), StandardCharsets.UTF_8); - return ImmutableList.copyOf(channels.split("\0")); - } - - public static PluginMessage constructChannelsPacket(int protocolVersion, Collection channels) { - Preconditions.checkNotNull(channels, "channels"); - String channelName = protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ? REGISTER_CHANNEL : REGISTER_CHANNEL_LEGACY; - PluginMessage message = new PluginMessage(); - message.setChannel(channelName); - message.setData(String.join("\0", channels).getBytes(StandardCharsets.UTF_8)); - return message; - } - - public static PluginMessage rewriteMCBrand(PluginMessage message) { - Preconditions.checkNotNull(message, "message"); - Preconditions.checkArgument(isMCBrand(message), "message is not a MC Brand plugin message"); - - byte[] rewrittenData; - ByteBuf rewrittenBuf = Unpooled.buffer(); - try { - String currentBrand = ProtocolUtils.readString(Unpooled.wrappedBuffer(message.getData())); - ProtocolUtils.writeString(rewrittenBuf, currentBrand + " (Velocity)"); - rewrittenData = new byte[rewrittenBuf.readableBytes()]; - rewrittenBuf.readBytes(rewrittenData); - } finally { - rewrittenBuf.release(); - } - - PluginMessage newMsg = new PluginMessage(); - newMsg.setChannel(message.getChannel()); - newMsg.setData(rewrittenData); - return newMsg; - } + PluginMessage newMsg = new PluginMessage(); + newMsg.setChannel(message.getChannel()); + newMsg.setData(rewrittenData); + return newMsg; + } } 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 03792bbae..83254cdf6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -9,174 +9,184 @@ import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.scheduler.ScheduledTask; import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.scheduler.TaskStatus; +import java.util.Collection; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; 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; -import java.util.IdentityHashMap; -import java.util.concurrent.*; - public class VelocityScheduler implements Scheduler { - private final PluginManager pluginManager; - private final ExecutorService taskService; - private final ScheduledExecutorService timerExecutionService; - private final Multimap tasksByPlugin = Multimaps.synchronizedMultimap( - Multimaps.newSetMultimap(new IdentityHashMap<>(), HashSet::new)); - public VelocityScheduler(PluginManager pluginManager) { - this.pluginManager = pluginManager; - this.taskService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true) - .setNameFormat("Velocity Task Scheduler - #%d").build()); - this.timerExecutionService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setDaemon(true) - .setNameFormat("Velocity Task Scheduler Timer").build()); + private final PluginManager pluginManager; + private final ExecutorService taskService; + private final ScheduledExecutorService timerExecutionService; + private final Multimap tasksByPlugin = Multimaps.synchronizedMultimap( + Multimaps.newSetMultimap(new IdentityHashMap<>(), HashSet::new)); + + public VelocityScheduler(PluginManager pluginManager) { + this.pluginManager = pluginManager; + this.taskService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true) + .setNameFormat("Velocity Task Scheduler - #%d").build()); + this.timerExecutionService = Executors + .newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setDaemon(true) + .setNameFormat("Velocity Task Scheduler Timer").build()); + } + + @Override + public TaskBuilder buildTask(Object plugin, Runnable runnable) { + Preconditions.checkNotNull(plugin, "plugin"); + Preconditions.checkNotNull(runnable, "runnable"); + Preconditions + .checkArgument(pluginManager.fromInstance(plugin).isPresent(), "plugin is not registered"); + return new TaskBuilderImpl(plugin, runnable); + } + + public boolean shutdown() throws InterruptedException { + Collection terminating; + synchronized (tasksByPlugin) { + terminating = ImmutableList.copyOf(tasksByPlugin.values()); + } + for (ScheduledTask task : terminating) { + task.cancel(); + } + timerExecutionService.shutdown(); + taskService.shutdown(); + return taskService.awaitTermination(10, TimeUnit.SECONDS); + } + + private class TaskBuilderImpl implements TaskBuilder { + + private final Object plugin; + private final Runnable runnable; + private long delay; // ms + private long repeat; // ms + + private TaskBuilderImpl(Object plugin, Runnable runnable) { + this.plugin = plugin; + this.runnable = runnable; } @Override - public TaskBuilder buildTask(Object plugin, Runnable runnable) { - Preconditions.checkNotNull(plugin, "plugin"); - Preconditions.checkNotNull(runnable, "runnable"); - Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "plugin is not registered"); - return new TaskBuilderImpl(plugin, runnable); + public TaskBuilder delay(long time, TimeUnit unit) { + this.delay = unit.toMillis(time); + return this; } - public boolean shutdown() throws InterruptedException { - Collection terminating; - synchronized (tasksByPlugin) { - terminating = ImmutableList.copyOf(tasksByPlugin.values()); - } - for (ScheduledTask task : terminating) { - task.cancel(); - } - timerExecutionService.shutdown(); - taskService.shutdown(); - return taskService.awaitTermination(10, TimeUnit.SECONDS); + @Override + public TaskBuilder repeat(long time, TimeUnit unit) { + this.repeat = unit.toMillis(time); + return this; } - private class TaskBuilderImpl implements TaskBuilder { - private final Object plugin; - private final Runnable runnable; - private long delay; // ms - private long repeat; // ms - - private TaskBuilderImpl(Object plugin, Runnable runnable) { - this.plugin = plugin; - this.runnable = runnable; - } - - @Override - public TaskBuilder delay(long time, TimeUnit unit) { - this.delay = unit.toMillis(time); - return this; - } - - @Override - public TaskBuilder repeat(long time, TimeUnit unit) { - this.repeat = unit.toMillis(time); - return this; - } - - @Override - public TaskBuilder clearDelay() { - this.delay = 0; - return this; - } - - @Override - public TaskBuilder clearRepeat() { - this.repeat = 0; - return this; - } - - @Override - public ScheduledTask schedule() { - VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat); - tasksByPlugin.put(plugin, task); - task.schedule(); - return task; - } + @Override + public TaskBuilder clearDelay() { + this.delay = 0; + return this; } - private class VelocityTask implements Runnable, ScheduledTask { - private final Object plugin; - private final Runnable runnable; - 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 { - this.future = timerExecutionService.scheduleAtFixedRate(this, delay, repeat, TimeUnit.MILLISECONDS); - } - } - - @Override - public Object plugin() { - return plugin; - } - - @Override - public TaskStatus status() { - if (future == null) { - return TaskStatus.SCHEDULED; - } - - if (future.isCancelled()) { - return TaskStatus.CANCELLED; - } - - if (future.isDone()) { - return TaskStatus.FINISHED; - } - - return TaskStatus.SCHEDULED; - } - - @Override - public void cancel() { - if (future != null) { - future.cancel(false); - - Thread cur = currentTaskThread; - if (cur != null) { - cur.interrupt(); - } - - onFinish(); - } - } - - @Override - public void run() { - taskService.execute(() -> { - currentTaskThread = Thread.currentThread(); - try { - runnable.run(); - } catch (Exception e) { - Log.logger.error("Exception in task {} by plugin {}", runnable, plugin); - } finally { - currentTaskThread = null; - } - }); - } - - private void onFinish() { - tasksByPlugin.remove(plugin, this); - } + @Override + public TaskBuilder clearRepeat() { + this.repeat = 0; + return this; } - private static class Log { - private static final Logger logger = LogManager.getLogger(VelocityTask.class); + @Override + public ScheduledTask schedule() { + VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat); + tasksByPlugin.put(plugin, task); + task.schedule(); + return task; } + } + + private class VelocityTask implements Runnable, ScheduledTask { + + private final Object plugin; + private final Runnable runnable; + 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 { + this.future = timerExecutionService + .scheduleAtFixedRate(this, delay, repeat, TimeUnit.MILLISECONDS); + } + } + + @Override + public Object plugin() { + return plugin; + } + + @Override + public TaskStatus status() { + if (future == null) { + return TaskStatus.SCHEDULED; + } + + if (future.isCancelled()) { + return TaskStatus.CANCELLED; + } + + if (future.isDone()) { + return TaskStatus.FINISHED; + } + + return TaskStatus.SCHEDULED; + } + + @Override + public void cancel() { + if (future != null) { + future.cancel(false); + + Thread cur = currentTaskThread; + if (cur != null) { + cur.interrupt(); + } + + onFinish(); + } + } + + @Override + public void run() { + taskService.execute(() -> { + currentTaskThread = Thread.currentThread(); + try { + runnable.run(); + } catch (Exception e) { + Log.logger.error("Exception in task {} by plugin {}", runnable, plugin); + } finally { + currentTaskThread = null; + } + }); + } + + private void onFinish() { + tasksByPlugin.remove(plugin, this); + } + } + + private static class Log { + + private static final Logger logger = LogManager.getLogger(VelocityTask.class); + } } 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 64a8b23fc..19cd95ae7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/ServerMap.java @@ -5,55 +5,59 @@ 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; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import org.checkerframework.checker.nullness.qual.Nullable; public class ServerMap { - private final @Nullable VelocityServer server; - private final Map servers = new ConcurrentHashMap<>(); - public ServerMap(@Nullable VelocityServer server) { - this.server = server; + private final @Nullable VelocityServer server; + private final Map servers = new ConcurrentHashMap<>(); + + public ServerMap(@Nullable VelocityServer server) { + this.server = server; + } + + public Optional getServer(String name) { + Preconditions.checkNotNull(name, "server"); + String lowerName = name.toLowerCase(Locale.US); + return Optional.ofNullable(servers.get(lowerName)); + } + + public Collection getAllServers() { + return ImmutableList.copyOf(servers.values()); + } + + public RegisteredServer register(ServerInfo serverInfo) { + Preconditions.checkNotNull(serverInfo, "serverInfo"); + String lowerName = serverInfo.getName().toLowerCase(Locale.US); + VelocityRegisteredServer rs = new VelocityRegisteredServer(server, serverInfo); + + RegisteredServer existing = servers.putIfAbsent(lowerName, rs); + if (existing != null && !existing.getServerInfo().equals(serverInfo)) { + throw new IllegalArgumentException( + "Server with name " + serverInfo.getName() + " already registered"); + } else if (existing == null) { + return rs; + } else { + return existing; } + } - public Optional getServer(String name) { - Preconditions.checkNotNull(name, "server"); - String lowerName = name.toLowerCase(Locale.US); - return Optional.ofNullable(servers.get(lowerName)); - } - - public Collection getAllServers() { - return ImmutableList.copyOf(servers.values()); - } - - public RegisteredServer register(ServerInfo serverInfo) { - Preconditions.checkNotNull(serverInfo, "serverInfo"); - String lowerName = serverInfo.getName().toLowerCase(Locale.US); - VelocityRegisteredServer rs = new VelocityRegisteredServer(server, serverInfo); - - RegisteredServer existing = servers.putIfAbsent(lowerName, rs); - if (existing != null && !existing.getServerInfo().equals(serverInfo)) { - throw new IllegalArgumentException("Server with name " + serverInfo.getName() + " already registered"); - } else if (existing == null) { - return rs; - } else { - return existing; - } - } - - public void unregister(ServerInfo serverInfo) { - Preconditions.checkNotNull(serverInfo, "serverInfo"); - String lowerName = serverInfo.getName().toLowerCase(Locale.US); - RegisteredServer rs = servers.get(lowerName); - 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()); + public void unregister(ServerInfo serverInfo) { + Preconditions.checkNotNull(serverInfo, "serverInfo"); + String lowerName = serverInfo.getName().toLowerCase(Locale.US); + RegisteredServer rs = servers.get(lowerName); + 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 448cd43d4..9c057fc8b 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,12 @@ package com.velocitypowered.proxy.server; +import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; +import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER; +import static com.velocitypowered.proxy.network.Connections.HANDLER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER; +import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER; +import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT; + import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.velocitypowered.api.proxy.Player; @@ -23,95 +30,98 @@ 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; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; - -import static com.velocitypowered.proxy.network.Connections.*; +import org.checkerframework.checker.nullness.qual.Nullable; public class VelocityRegisteredServer implements RegisteredServer { - private final @Nullable VelocityServer server; - private final ServerInfo serverInfo; - private final Set players = ConcurrentHashMap.newKeySet(); - public VelocityRegisteredServer(@Nullable VelocityServer server, ServerInfo serverInfo) { - this.server = server; - this.serverInfo = Preconditions.checkNotNull(serverInfo, "serverInfo"); + private final @Nullable VelocityServer server; + private final ServerInfo serverInfo; + private final Set players = ConcurrentHashMap.newKeySet(); + + public VelocityRegisteredServer(@Nullable VelocityServer server, ServerInfo serverInfo) { + this.server = server; + this.serverInfo = Preconditions.checkNotNull(serverInfo, "serverInfo"); + } + + @Override + public ServerInfo getServerInfo() { + return serverInfo; + } + + @Override + public Collection getPlayersConnected() { + return ImmutableList.copyOf(players); + } + + @Override + public CompletableFuture ping() { + if (server == null) { + throw new IllegalStateException("No Velocity proxy instance available"); } + CompletableFuture pingFuture = new CompletableFuture<>(); + server.initializeGenericBootstrap() + .handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline() + .addLast(READ_TIMEOUT, + new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), + TimeUnit.SECONDS)) + .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) + .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) + .addLast(MINECRAFT_DECODER, + new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND)) + .addLast(MINECRAFT_ENCODER, + new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND)); - @Override - public ServerInfo getServerInfo() { - return serverInfo; - } - - @Override - public Collection getPlayersConnected() { - return ImmutableList.copyOf(players); - } - - @Override - public CompletableFuture ping() { - if (server == null) { - throw new IllegalStateException("No Velocity proxy instance available"); - } - CompletableFuture pingFuture = new CompletableFuture<>(); - server.initializeGenericBootstrap() - .handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline() - .addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS)) - .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) - .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) - .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND)) - .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND)); - - MinecraftConnection connection = new MinecraftConnection(ch, server); - connection.setState(StateRegistry.HANDSHAKE); - ch.pipeline().addLast(HANDLER, connection); - } - }) - .connect(serverInfo.getAddress()) - .addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (future.isSuccess()) { - MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); - conn.setSessionHandler(new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn)); - } else { - pingFuture.completeExceptionally(future.cause()); - } - } - }); - return pingFuture; - } - - public void addPlayer(ConnectedPlayer player) { - players.add(player); - } - - public void removePlayer(ConnectedPlayer player) { - players.remove(player); - } - - @Override - public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { - for (ConnectedPlayer player : players) { - ServerConnection connection = player.getConnectedServer(); - if (connection != null && connection.getServerInfo().equals(serverInfo)) { - return connection.sendPluginMessage(identifier, data); + MinecraftConnection connection = new MinecraftConnection(ch, server); + connection.setState(StateRegistry.HANDSHAKE); + ch.pipeline().addLast(HANDLER, connection); + } + }) + .connect(serverInfo.getAddress()) + .addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); + conn.setSessionHandler( + new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn)); + } else { + pingFuture.completeExceptionally(future.cause()); } - } + } + }); + return pingFuture; + } - return false; + public void addPlayer(ConnectedPlayer player) { + players.add(player); + } + + public void removePlayer(ConnectedPlayer player) { + players.remove(player); + } + + @Override + public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) { + for (ConnectedPlayer player : players) { + ServerConnection connection = player.getConnectedServer(); + if (connection != null && connection.getServerInfo().equals(serverInfo)) { + return connection.sendPluginMessage(identifier, data); + } } - @Override - public String toString() { - return "registered server: " + serverInfo; - } + return false; + } + + @Override + public String toString() { + return "registered server: " + serverInfo; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/ping/PingSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/server/ping/PingSessionHandler.java index 415e51ce2..d4fad2605 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/ping/PingSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/ping/PingSessionHandler.java @@ -10,56 +10,57 @@ import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.StatusRequest; import com.velocitypowered.proxy.protocol.packet.StatusResponse; - import java.io.IOException; import java.util.concurrent.CompletableFuture; public class PingSessionHandler implements MinecraftSessionHandler { - private final CompletableFuture result; - private final RegisteredServer server; - private final MinecraftConnection connection; - private boolean completed = false; - public PingSessionHandler(CompletableFuture result, RegisteredServer server, MinecraftConnection connection) { - this.result = result; - this.server = server; - this.connection = connection; + private final CompletableFuture result; + private final RegisteredServer server; + private final MinecraftConnection connection; + private boolean completed = false; + + public PingSessionHandler(CompletableFuture result, RegisteredServer server, + MinecraftConnection connection) { + this.result = result; + this.server = server; + this.connection = connection; + } + + @Override + public void activated() { + Handshake handshake = new Handshake(); + handshake.setNextStatus(StateRegistry.STATUS_ID); + handshake.setServerAddress(server.getServerInfo().getAddress().getHostString()); + handshake.setPort(server.getServerInfo().getAddress().getPort()); + handshake.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION); + connection.write(handshake); + + connection.setState(StateRegistry.STATUS); + connection.write(StatusRequest.INSTANCE); + } + + @Override + public boolean handle(StatusResponse packet) { + // All good! + completed = true; + connection.close(); + + ServerPing ping = VelocityServer.GSON.fromJson(packet.getStatus(), ServerPing.class); + result.complete(ping); + return true; + } + + @Override + public void disconnected() { + if (!completed) { + result.completeExceptionally(new IOException("Unexpectedly disconnected from remote server")); } + } - @Override - public void activated() { - Handshake handshake = new Handshake(); - handshake.setNextStatus(StateRegistry.STATUS_ID); - handshake.setServerAddress(server.getServerInfo().getAddress().getHostString()); - handshake.setPort(server.getServerInfo().getAddress().getPort()); - handshake.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION); - connection.write(handshake); - - connection.setState(StateRegistry.STATUS); - connection.write(StatusRequest.INSTANCE); - } - - @Override - public boolean handle(StatusResponse packet) { - // All good! - completed = true; - connection.close(); - - ServerPing ping = VelocityServer.GSON.fromJson(packet.getStatus(), ServerPing.class); - result.complete(ping); - return true; - } - - @Override - public void disconnected() { - if (!completed) { - result.completeExceptionally(new IOException("Unexpectedly disconnected from remote server")); - } - } - - @Override - public void exception(Throwable throwable) { - completed = true; - result.completeExceptionally(throwable); - } + @Override + public void exception(Throwable throwable) { + completed = true; + result.completeExceptionally(throwable); + } } 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 59586d787..920518dc9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -8,131 +8,142 @@ import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; import com.velocitypowered.proxy.protocol.packet.PlayerListItem; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - public class VelocityTabList implements TabList { - private final MinecraftConnection connection; - private final Map entries = new ConcurrentHashMap<>(); - public VelocityTabList(MinecraftConnection connection) { - this.connection = connection; + private final MinecraftConnection connection; + private final Map entries = new ConcurrentHashMap<>(); + + public VelocityTabList(MinecraftConnection connection) { + this.connection = connection; + } + + @Override + public void setHeaderAndFooter(Component header, Component footer) { + Preconditions.checkNotNull(header, "header"); + Preconditions.checkNotNull(footer, "footer"); + connection.write(HeaderAndFooter.create(header, footer)); + } + + @Override + public void clearHeaderAndFooter() { + connection.write(HeaderAndFooter.reset()); + } + + @Override + public void addEntry(TabListEntry entry) { + Preconditions.checkNotNull(entry, "entry"); + Preconditions.checkArgument(entry.getTabList().equals(this), + "The provided entry was not created by this tab list"); + Preconditions.checkArgument(!entries.containsKey(entry.getProfile().idAsUuid()), + "this TabList already contains an entry with the same uuid"); + + PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry); + connection.write( + new PlayerListItem(PlayerListItem.ADD_PLAYER, Collections.singletonList(packetItem))); + entries.put(entry.getProfile().idAsUuid(), entry); + } + + @Override + public Optional removeEntry(UUID uuid) { + TabListEntry entry = entries.remove(uuid); + if (entry != null) { + PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry); + connection.write( + new PlayerListItem(PlayerListItem.REMOVE_PLAYER, Collections.singletonList(packetItem))); } - @Override - public void setHeaderAndFooter(Component header, Component footer) { - Preconditions.checkNotNull(header, "header"); - Preconditions.checkNotNull(footer, "footer"); - connection.write(HeaderAndFooter.create(header, footer)); + return Optional.ofNullable(entry); + } + + public void clearAll() { // Note: this method is called upon server switch + List items = new ArrayList<>(); + for (TabListEntry value : entries.values()) { + items.add(PlayerListItem.Item.from(value)); } + entries.clear(); + connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, items)); + } - @Override - public void clearHeaderAndFooter() { - connection.write(HeaderAndFooter.reset()); - } + @Override + public Collection getEntries() { + return Collections.unmodifiableCollection(this.entries.values()); + } - @Override - public void addEntry(TabListEntry entry) { - Preconditions.checkNotNull(entry, "entry"); - Preconditions.checkArgument(entry.getTabList().equals(this), "The provided entry was not created by this tab list"); - Preconditions.checkArgument(!entries.containsKey(entry.getProfile().idAsUuid()), "this TabList already contains an entry with the same uuid"); + @Override + public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, + int gameMode) { + return new VelocityTabListEntry(this, profile, displayName, latency, gameMode); + } - PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry); - connection.write(new PlayerListItem(PlayerListItem.ADD_PLAYER, Collections.singletonList(packetItem))); - entries.put(entry.getProfile().idAsUuid(), entry); - } + public void processBackendPacket(PlayerListItem packet) { + //Packets are already forwarded on, so no need to do that here + for (PlayerListItem.Item item : packet.getItems()) { + UUID uuid = item.getUuid(); + if (packet.getAction() != PlayerListItem.ADD_PLAYER && !entries.containsKey(uuid)) { + //Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here + continue; + } - @Override - public Optional removeEntry(UUID uuid) { - TabListEntry entry = entries.remove(uuid); - if (entry != null) { - PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry); - connection.write(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, Collections.singletonList(packetItem))); + switch (packet.getAction()) { + 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), name, properties)) + .displayName(item.getDisplayName()) + .latency(item.getLatency()) + .gameMode(item.getGameMode()) + .build()); + break; } - - return Optional.ofNullable(entry); - } - - public void clearAll() { // Note: this method is called upon server switch - List items = new ArrayList<>(); - for (TabListEntry value : entries.values()) { - items.add(PlayerListItem.Item.from(value)); + case PlayerListItem.REMOVE_PLAYER: + entries.remove(uuid); + break; + case PlayerListItem.UPDATE_DISPLAY_NAME: { + TabListEntry entry = entries.get(uuid); + if (entry != null) { + entry.setDisplayName(item.getDisplayName()); + } + break; } - entries.clear(); - connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, items)); - } - - @Override - public Collection getEntries() { - return Collections.unmodifiableCollection(this.entries.values()); - } - - @Override - public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode) { - return new VelocityTabListEntry(this, profile, displayName, latency, gameMode); - } - - public void processBackendPacket(PlayerListItem packet) { - //Packets are already forwarded on, so no need to do that here - for (PlayerListItem.Item item : packet.getItems()) { - UUID uuid = item.getUuid(); - if (packet.getAction() != PlayerListItem.ADD_PLAYER && !entries.containsKey(uuid)) { - //Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here - continue; - } - - switch (packet.getAction()) { - 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), 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: { - TabListEntry entry = entries.get(uuid); - if (entry != null) { - entry.setDisplayName(item.getDisplayName()); - } - break; - } - case PlayerListItem.UPDATE_LATENCY: { - TabListEntry entry = entries.get(uuid); - if (entry != null) { - entry.setLatency(item.getLatency()); - } - break; - } - case PlayerListItem.UPDATE_GAMEMODE: { - TabListEntry entry = entries.get(uuid); - if (entry != null) { - entry.setLatency(item.getGameMode()); - } - break; - } - } + case PlayerListItem.UPDATE_LATENCY: { + TabListEntry entry = entries.get(uuid); + if (entry != null) { + entry.setLatency(item.getLatency()); + } + break; } - } - - void updateEntry(int action, TabListEntry entry) { - if (entries.containsKey(entry.getProfile().idAsUuid())) { - PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry); - connection.write(new PlayerListItem(action, Collections.singletonList(packetItem))); + case PlayerListItem.UPDATE_GAMEMODE: { + TabListEntry entry = entries.get(uuid); + if (entry != null) { + entry.setLatency(item.getGameMode()); + } + break; } + } } + } + + void updateEntry(int action, TabListEntry entry) { + if (entries.containsKey(entry.getProfile().idAsUuid())) { + PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry); + connection.write(new PlayerListItem(action, Collections.singletonList(packetItem))); + } + } } 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 bdf116954..1f2d774ec 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java @@ -4,69 +4,70 @@ import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.packet.PlayerListItem; +import java.util.Optional; import net.kyori.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Optional; - public class VelocityTabListEntry implements TabListEntry { - private final VelocityTabList tabList; - private final GameProfile profile; - private @Nullable Component displayName; - private int latency; - private int gameMode; - VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, @Nullable Component displayName, int latency, int gameMode) { - this.tabList = tabList; - this.profile = profile; - this.displayName = displayName; - this.latency = latency; - this.gameMode = gameMode; - } + private final VelocityTabList tabList; + private final GameProfile profile; + private @Nullable Component displayName; + private int latency; + private int gameMode; - @Override - public TabList getTabList() { - return tabList; - } + VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, + @Nullable Component displayName, int latency, int gameMode) { + this.tabList = tabList; + this.profile = profile; + this.displayName = displayName; + this.latency = latency; + this.gameMode = gameMode; + } - @Override - public GameProfile getProfile() { - return profile; - } + @Override + public TabList getTabList() { + return tabList; + } - @Override - public Optional getDisplayName() { - return Optional.ofNullable(displayName); - } + @Override + public GameProfile getProfile() { + return profile; + } - @Override - public TabListEntry setDisplayName(@Nullable Component displayName) { - this.displayName = displayName; - tabList.updateEntry(PlayerListItem.UPDATE_DISPLAY_NAME, this); - return this; - } + @Override + public Optional getDisplayName() { + return Optional.ofNullable(displayName); + } - @Override - public int getLatency() { - return latency; - } + @Override + public TabListEntry setDisplayName(@Nullable Component displayName) { + this.displayName = displayName; + tabList.updateEntry(PlayerListItem.UPDATE_DISPLAY_NAME, this); + return this; + } - @Override - public TabListEntry setLatency(int latency) { - this.latency = latency; - tabList.updateEntry(PlayerListItem.UPDATE_LATENCY, this); - return this; - } + @Override + public int getLatency() { + return latency; + } - @Override - public int getGameMode() { - return gameMode; - } + @Override + public TabListEntry setLatency(int latency) { + this.latency = latency; + tabList.updateEntry(PlayerListItem.UPDATE_LATENCY, this); + return this; + } - @Override - public TabListEntry setGameMode(int gameMode) { - this.gameMode = gameMode; - tabList.updateEntry(PlayerListItem.UPDATE_GAMEMODE, this); - return this; - } + @Override + public int getGameMode() { + return gameMode; + } + + @Override + public TabListEntry setGameMode(int gameMode) { + this.gameMode = gameMode; + tabList.updateEntry(PlayerListItem.UPDATE_GAMEMODE, this); + return this; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java index fdf890db1..9c3682a6b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java @@ -1,16 +1,15 @@ package com.velocitypowered.proxy.util; import com.google.common.base.Preconditions; - import java.net.InetSocketAddress; import java.net.URI; public enum AddressUtil { - ; + ; - public static InetSocketAddress parseAddress(String ip) { - Preconditions.checkNotNull(ip, "ip"); - URI uri = URI.create("tcp://" + ip); - return new InetSocketAddress(uri.getHost(), uri.getPort()); - } + public static InetSocketAddress parseAddress(String ip) { + Preconditions.checkNotNull(ip, "ip"); + URI uri = URI.create("tcp://" + ip); + return new InetSocketAddress(uri.getHost(), uri.getPort()); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java index 226a24130..bf458aed4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java @@ -1,40 +1,45 @@ package com.velocitypowered.proxy.util; -import javax.crypto.Cipher; import java.math.BigInteger; -import java.security.*; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import javax.crypto.Cipher; public enum EncryptionUtils { - ; + ; - public static KeyPair createRsaKeyPair(final int keysize) { - try { - final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); - generator.initialize(keysize); - return generator.generateKeyPair(); - } catch (final NoSuchAlgorithmException e) { - throw new RuntimeException("Unable to generate RSA keypair", e); - } + public static KeyPair createRsaKeyPair(final int keysize) { + try { + final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(keysize); + return generator.generateKeyPair(); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException("Unable to generate RSA keypair", e); } + } - public static String twosComplementHexdigest(byte[] digest) { - return new BigInteger(digest).toString(16); - } + public static String twosComplementHexdigest(byte[] digest) { + return new BigInteger(digest).toString(16); + } - public static byte[] decryptRsa(KeyPair keyPair, byte[] bytes) throws GeneralSecurityException { - Cipher cipher = Cipher.getInstance("RSA"); - cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); - return cipher.doFinal(bytes); - } + public static byte[] decryptRsa(KeyPair keyPair, byte[] bytes) throws GeneralSecurityException { + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); + return cipher.doFinal(bytes); + } - public static String generateServerId(byte[] sharedSecret, PublicKey key) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.update(sharedSecret); - digest.update(key.getEncoded()); - return twosComplementHexdigest(digest.digest()); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } + public static String generateServerId(byte[] sharedSecret, PublicKey key) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(sharedSecret); + digest.update(key.getEncoded()); + return twosComplementHexdigest(digest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); } + } } 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 1fff632e1..670949cec 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/Ratelimiter.java @@ -4,44 +4,46 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ticker; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; - import java.net.InetAddress; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class Ratelimiter { - private final Cache expiringCache; - private final long timeoutNanos; - public Ratelimiter(long timeoutMs) { - this(timeoutMs, Ticker.systemTicker()); - } + private final Cache expiringCache; + private final long timeoutNanos; - @VisibleForTesting - Ratelimiter(long timeoutMs, Ticker ticker) { - if (timeoutMs == 0) { - this.timeoutNanos = timeoutMs; - this.expiringCache = CacheBuilder.newBuilder().maximumSize(0).build(); - } else { - this.timeoutNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs); - this.expiringCache = CacheBuilder.newBuilder() - .ticker(ticker) - .concurrencyLevel(Runtime.getRuntime().availableProcessors()) - .expireAfterWrite(timeoutMs, TimeUnit.MILLISECONDS) - .build(); - } - } + public Ratelimiter(long timeoutMs) { + this(timeoutMs, Ticker.systemTicker()); + } - public boolean attempt(InetAddress address) { - if (timeoutNanos == 0) return true; - long expectedNewValue = System.nanoTime() + timeoutNanos; - long last; - try { - last = expiringCache.get(address, () -> expectedNewValue); - } catch (ExecutionException e) { - // It should be impossible for this to fail. - throw new AssertionError(e); - } - return expectedNewValue == last; + @VisibleForTesting + Ratelimiter(long timeoutMs, Ticker ticker) { + if (timeoutMs == 0) { + this.timeoutNanos = timeoutMs; + this.expiringCache = CacheBuilder.newBuilder().maximumSize(0).build(); + } else { + this.timeoutNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs); + this.expiringCache = CacheBuilder.newBuilder() + .ticker(ticker) + .concurrencyLevel(Runtime.getRuntime().availableProcessors()) + .expireAfterWrite(timeoutMs, TimeUnit.MILLISECONDS) + .build(); } + } + + public boolean attempt(InetAddress address) { + if (timeoutNanos == 0) { + return true; + } + long expectedNewValue = System.nanoTime() + timeoutNanos; + long last; + try { + last = expiringCache.get(address, () -> expectedNewValue); + } catch (ExecutionException e) { + // It should be impossible for this to fail. + throw new AssertionError(e); + } + return expectedNewValue == last; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/ThrowableUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/ThrowableUtils.java index 7bd578d7e..bbc9ea9b9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/ThrowableUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/ThrowableUtils.java @@ -1,9 +1,9 @@ package com.velocitypowered.proxy.util; public enum ThrowableUtils { - ; + ; - public static String briefDescription(Throwable throwable) { - return throwable.getClass().getSimpleName() + ": " + throwable.getMessage(); - } + public static String briefDescription(Throwable throwable) { + return throwable.getClass().getSimpleName() + ": " + throwable.getMessage(); + } } 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 78b93cefe..c48dd0a56 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityChannelRegistrar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityChannelRegistrar.java @@ -6,59 +6,61 @@ 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; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.checkerframework.checker.nullness.qual.Nullable; public class VelocityChannelRegistrar implements ChannelRegistrar { - private final Map identifierMap = new ConcurrentHashMap<>(); - @Override - public void register(ChannelIdentifier... identifiers) { - for (ChannelIdentifier identifier : identifiers) { - Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier, - "identifier is unknown"); - } + private final Map identifierMap = new ConcurrentHashMap<>(); - for (ChannelIdentifier identifier : identifiers) { - identifierMap.put(identifier.getId(), identifier); - } + @Override + public void register(ChannelIdentifier... identifiers) { + for (ChannelIdentifier identifier : identifiers) { + Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier + || identifier instanceof MinecraftChannelIdentifier, + "identifier is unknown"); } - @Override - public void unregister(ChannelIdentifier... identifiers) { - for (ChannelIdentifier identifier : identifiers) { - Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier, - "identifier is unknown"); - } + for (ChannelIdentifier identifier : identifiers) { + identifierMap.put(identifier.getId(), identifier); + } + } - for (ChannelIdentifier identifier : identifiers) { - identifierMap.remove(identifier.getId()); - } + @Override + public void unregister(ChannelIdentifier... identifiers) { + for (ChannelIdentifier identifier : identifiers) { + Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier + || identifier instanceof MinecraftChannelIdentifier, + "identifier is unknown"); } - public Collection getIdsForLegacyConnections() { - return ImmutableList.copyOf(identifierMap.keySet()); + for (ChannelIdentifier identifier : identifiers) { + identifierMap.remove(identifier.getId()); } + } - public Collection getModernChannelIds() { - Collection ids = new ArrayList<>(); - for (ChannelIdentifier value : identifierMap.values()) { - if (value instanceof MinecraftChannelIdentifier) { - ids.add(value.getId()); - } - } - return ids; - } + public Collection getIdsForLegacyConnections() { + return ImmutableList.copyOf(identifierMap.keySet()); + } - public boolean registered(String id) { - return identifierMap.containsKey(id); + public Collection getModernChannelIds() { + Collection ids = new ArrayList<>(); + for (ChannelIdentifier value : identifierMap.values()) { + if (value instanceof MinecraftChannelIdentifier) { + ids.add(value.getId()); + } } + return ids; + } - public @Nullable ChannelIdentifier getFromId(String id) { - return identifierMap.get(id); - } + public boolean registered(String id) { + return identifierMap.containsKey(id); + } + + public @Nullable ChannelIdentifier getFromId(String id) { + return identifierMap.get(id); + } } diff --git a/proxy/src/main/resources/log4j2.xml b/proxy/src/main/resources/log4j2.xml index 0dc941c11..85bf1574b 100644 --- a/proxy/src/main/resources/log4j2.xml +++ b/proxy/src/main/resources/log4j2.xml @@ -1,29 +1,31 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - + + + + + + \ No newline at end of file diff --git a/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java index 9a3533b16..304bf4908 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java @@ -1,84 +1,84 @@ package com.velocitypowered.proxy.plugin.util; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + import com.google.common.collect.ImmutableList; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription; -import org.junit.jupiter.api.Test; - import java.util.ArrayList; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; class PluginDependencyUtilsTest { - private static final PluginDescription NO_DEPENDENCY_1_EXAMPLE = new VelocityPluginDescription( - "example", "tuxed", "0.1", null, null, ImmutableList.of("example2"), - ImmutableList.of(), null - ); - private static final PluginDescription NO_DEPENDENCY_2_EXAMPLE = new VelocityPluginDescription( - "example2", "tuxed", "0.1", null, null, ImmutableList.of(), ImmutableList.of(), null - ); - private static final PluginDescription NEVER_DEPENDED = new VelocityPluginDescription( - "and-again", "tuxed", "0.1", null, null, ImmutableList.of(), ImmutableList.of(), null - ); - private static final PluginDescription SOFT_DEPENDENCY_EXISTS = new VelocityPluginDescription( - "soft", "tuxed", "0.1", null, null, ImmutableList.of(), - ImmutableList.of(new PluginDependency("example", "", true)), null - ); - private static final PluginDescription SOFT_DEPENDENCY_DOES_NOT_EXIST = new VelocityPluginDescription( - "fluffy", "tuxed", "0.1", null, null, ImmutableList.of(), - ImmutableList.of(new PluginDependency("i-dont-exist", "", false)), null - ); - private static final PluginDescription MULTI_DEPENDENCY = new VelocityPluginDescription( - "multi-depend", "tuxed", "0.1", null, null, ImmutableList.of(), - ImmutableList.of( - new PluginDependency("example", "", false), - new PluginDependency("example2", "", false) - ), null - ); - private static final PluginDescription CIRCULAR_DEPENDENCY_1 = new VelocityPluginDescription( - "circle", "tuxed", "0.1", null, null, ImmutableList.of(), - ImmutableList.of( - new PluginDependency("oval", "", false) - ), null - ); - private static final PluginDescription CIRCULAR_DEPENDENCY_2 = new VelocityPluginDescription( - "oval", "tuxed", "0.1", null, null, ImmutableList.of(), - ImmutableList.of( - new PluginDependency("circle", "", false) - ), null - ); + private static final PluginDescription NO_DEPENDENCY_1_EXAMPLE = new VelocityPluginDescription( + "example", "tuxed", "0.1", null, null, ImmutableList.of("example2"), + ImmutableList.of(), null + ); + private static final PluginDescription NO_DEPENDENCY_2_EXAMPLE = new VelocityPluginDescription( + "example2", "tuxed", "0.1", null, null, ImmutableList.of(), ImmutableList.of(), null + ); + private static final PluginDescription NEVER_DEPENDED = new VelocityPluginDescription( + "and-again", "tuxed", "0.1", null, null, ImmutableList.of(), ImmutableList.of(), null + ); + private static final PluginDescription SOFT_DEPENDENCY_EXISTS = new VelocityPluginDescription( + "soft", "tuxed", "0.1", null, null, ImmutableList.of(), + ImmutableList.of(new PluginDependency("example", "", true)), null + ); + private static final PluginDescription SOFT_DEPENDENCY_DOES_NOT_EXIST = new VelocityPluginDescription( + "fluffy", "tuxed", "0.1", null, null, ImmutableList.of(), + ImmutableList.of(new PluginDependency("i-dont-exist", "", false)), null + ); + private static final PluginDescription MULTI_DEPENDENCY = new VelocityPluginDescription( + "multi-depend", "tuxed", "0.1", null, null, ImmutableList.of(), + ImmutableList.of( + new PluginDependency("example", "", false), + new PluginDependency("example2", "", false) + ), null + ); - // Note: Kahn's algorithm is non-unique in its return result, although the topological sort will have the correct - // order. - private static final List EXPECTED = ImmutableList.of( - NO_DEPENDENCY_1_EXAMPLE, - NO_DEPENDENCY_2_EXAMPLE, - NEVER_DEPENDED, - SOFT_DEPENDENCY_DOES_NOT_EXIST, - SOFT_DEPENDENCY_EXISTS, - MULTI_DEPENDENCY - ); + private static final PluginDescription CIRCULAR_DEPENDENCY_1 = new VelocityPluginDescription( + "circle", "tuxed", "0.1", null, null, ImmutableList.of(), + ImmutableList.of( + new PluginDependency("oval", "", false) + ), null + ); + private static final PluginDescription CIRCULAR_DEPENDENCY_2 = new VelocityPluginDescription( + "oval", "tuxed", "0.1", null, null, ImmutableList.of(), + ImmutableList.of( + new PluginDependency("circle", "", false) + ), null + ); - @Test - void sortCandidates() throws Exception { - List descriptionList = new ArrayList<>(); - descriptionList.add(NO_DEPENDENCY_1_EXAMPLE); - descriptionList.add(NO_DEPENDENCY_2_EXAMPLE); - descriptionList.add(NEVER_DEPENDED); - descriptionList.add(SOFT_DEPENDENCY_DOES_NOT_EXIST); - descriptionList.add(SOFT_DEPENDENCY_EXISTS); - descriptionList.add(MULTI_DEPENDENCY); + // Note: Kahn's algorithm is non-unique in its return result, although the topological sort will have the correct + // order. + private static final List EXPECTED = ImmutableList.of( + NO_DEPENDENCY_1_EXAMPLE, + NO_DEPENDENCY_2_EXAMPLE, + NEVER_DEPENDED, + SOFT_DEPENDENCY_DOES_NOT_EXIST, + SOFT_DEPENDENCY_EXISTS, + MULTI_DEPENDENCY + ); - assertEquals(EXPECTED, PluginDependencyUtils.sortCandidates(descriptionList)); - } + @Test + void sortCandidates() throws Exception { + List descriptionList = new ArrayList<>(); + descriptionList.add(NO_DEPENDENCY_1_EXAMPLE); + descriptionList.add(NO_DEPENDENCY_2_EXAMPLE); + descriptionList.add(NEVER_DEPENDED); + descriptionList.add(SOFT_DEPENDENCY_DOES_NOT_EXIST); + descriptionList.add(SOFT_DEPENDENCY_EXISTS); + descriptionList.add(MULTI_DEPENDENCY); - @Test - void sortCandidatesCircularDependency() throws Exception { - List descs = ImmutableList.of(CIRCULAR_DEPENDENCY_1, CIRCULAR_DEPENDENCY_2); - assertThrows(IllegalStateException.class, () -> PluginDependencyUtils.sortCandidates(descs)); - } + assertEquals(EXPECTED, PluginDependencyUtils.sortCandidates(descriptionList)); + } + + @Test + void sortCandidatesCircularDependency() throws Exception { + List descs = ImmutableList.of(CIRCULAR_DEPENDENCY_1, CIRCULAR_DEPENDENCY_2); + assertThrows(IllegalStateException.class, () -> PluginDependencyUtils.sortCandidates(descs)); + } } 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 2a3c7730d..52c02e73a 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java @@ -1,51 +1,68 @@ package com.velocitypowered.proxy.protocol; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1; +import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + import com.velocitypowered.proxy.protocol.packet.Handshake; import org.junit.jupiter.api.Test; -import static com.velocitypowered.proxy.protocol.ProtocolConstants.*; -import static org.junit.jupiter.api.Assertions.*; - class PacketRegistryTest { - private StateRegistry.PacketRegistry setupRegistry() { - 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; - } - @Test - void packetRegistryWorks() { - StateRegistry.PacketRegistry registry = setupRegistry(); - MinecraftPacket packet = registry.getVersion(MINECRAFT_1_12).createPacket(0); - assertNotNull(packet, "Packet was not found in registry"); - assertEquals(Handshake.class, packet.getClass(), "Registry returned wrong class"); + private StateRegistry.PacketRegistry setupRegistry() { + 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; + } - assertEquals(0, registry.getVersion(MINECRAFT_1_12).getPacketId(packet), "Registry did not return the correct packet ID"); - } + @Test + void packetRegistryWorks() { + StateRegistry.PacketRegistry registry = setupRegistry(); + MinecraftPacket packet = registry.getVersion(MINECRAFT_1_12).createPacket(0); + assertNotNull(packet, "Packet was not found in registry"); + assertEquals(Handshake.class, packet.getClass(), "Registry returned wrong class"); - @Test - void packetRegistryLinkingWorks() { - StateRegistry.PacketRegistry registry = setupRegistry(); - MinecraftPacket packet = registry.getVersion(MINECRAFT_1_12_1).createPacket(0); - assertNotNull(packet, "Packet was not found in registry"); - assertEquals(Handshake.class, packet.getClass(), "Registry returned wrong class"); - assertEquals(0, registry.getVersion(MINECRAFT_1_12_1).getPacketId(packet), "Registry did not return the correct packet ID"); - } + assertEquals(0, registry.getVersion(MINECRAFT_1_12).getPacketId(packet), + "Registry did not return the correct packet ID"); + } - @Test - void failOnNoMappings() { - 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 packetRegistryLinkingWorks() { + StateRegistry.PacketRegistry registry = setupRegistry(); + MinecraftPacket packet = registry.getVersion(MINECRAFT_1_12_1).createPacket(0); + assertNotNull(packet, "Packet was not found in registry"); + assertEquals(Handshake.class, packet.getClass(), "Registry returned wrong class"); + assertEquals(0, registry.getVersion(MINECRAFT_1_12_1).getPacketId(packet), + "Registry did not return the correct packet ID"); + } - @Test - void registrySuppliesCorrectPacketsByProtocol() { - 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()); - assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12_1).createPacket(0x01).getClass()); - assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12_2).createPacket(0x01).getClass()); - } + @Test + void failOnNoMappings() { + 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); + 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()); + assertEquals(Handshake.class, + registry.getVersion(MINECRAFT_1_12_1).createPacket(0x01).getClass()); + assertEquals(Handshake.class, + registry.getVersion(MINECRAFT_1_12_2).createPacket(0x01).getClass()); + } } \ No newline at end of file diff --git a/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java b/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java index b1001b2c0..2afc90830 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java @@ -1,51 +1,51 @@ package com.velocitypowered.proxy.scheduler; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.velocitypowered.api.scheduler.ScheduledTask; import com.velocitypowered.api.scheduler.TaskStatus; import com.velocitypowered.proxy.testutil.FakePluginManager; -import org.junit.jupiter.api.Test; - import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; class VelocitySchedulerTest { - // TODO: The timings here will be inaccurate on slow systems. + // TODO: The timings here will be inaccurate on slow systems. - @Test - void buildTask() throws Exception { - VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager()); - CountDownLatch latch = new CountDownLatch(1); - ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, latch::countDown).schedule(); - latch.await(); - assertEquals(TaskStatus.FINISHED, task.status()); - } + @Test + void buildTask() throws Exception { + VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager()); + CountDownLatch latch = new CountDownLatch(1); + ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, latch::countDown) + .schedule(); + latch.await(); + assertEquals(TaskStatus.FINISHED, task.status()); + } - @Test - void cancelWorks() throws Exception { - VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager()); - AtomicInteger i = new AtomicInteger(3); - ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, i::decrementAndGet) - .delay(100, TimeUnit.SECONDS) - .schedule(); - task.cancel(); - Thread.sleep(200); - assertEquals(3, i.get()); - assertEquals(TaskStatus.CANCELLED, task.status()); - } + @Test + void cancelWorks() throws Exception { + VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager()); + AtomicInteger i = new AtomicInteger(3); + ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, i::decrementAndGet) + .delay(100, TimeUnit.SECONDS) + .schedule(); + task.cancel(); + Thread.sleep(200); + assertEquals(3, i.get()); + assertEquals(TaskStatus.CANCELLED, task.status()); + } - @Test - void repeatTaskWorks() throws Exception { - VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager()); - CountDownLatch latch = new CountDownLatch(3); - ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, latch::countDown) - .delay(100, TimeUnit.MILLISECONDS) - .repeat(100, TimeUnit.MILLISECONDS) - .schedule(); - latch.await(); - task.cancel(); - } + @Test + void repeatTaskWorks() throws Exception { + VelocityScheduler scheduler = new VelocityScheduler(new FakePluginManager()); + CountDownLatch latch = new CountDownLatch(3); + ScheduledTask task = scheduler.buildTask(FakePluginManager.PLUGIN_A, latch::countDown) + .delay(100, TimeUnit.MILLISECONDS) + .repeat(100, TimeUnit.MILLISECONDS) + .schedule(); + latch.await(); + task.cancel(); + } } \ No newline at end of file diff --git a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java index 86fb450ea..2948d6256 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java @@ -4,74 +4,75 @@ import com.google.common.collect.ImmutableList; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginManager; -import org.checkerframework.checker.nullness.qual.NonNull; - import java.nio.file.Path; import java.util.Collection; import java.util.Optional; +import org.checkerframework.checker.nullness.qual.NonNull; public class FakePluginManager implements PluginManager { - public static final Object PLUGIN_A = new Object(); - public static final Object PLUGIN_B = new Object(); - public static final PluginContainer PC_A = new FakePluginContainer("a", PLUGIN_A); - public static final PluginContainer PC_B = new FakePluginContainer("b", PLUGIN_B); + public static final Object PLUGIN_A = new Object(); + public static final Object PLUGIN_B = new Object(); - @Override - public @NonNull Optional fromInstance(@NonNull Object instance) { - if (instance == PLUGIN_A) { - return Optional.of(PC_A); - } else if (instance == PLUGIN_B) { - return Optional.of(PC_B); - } else { - return Optional.empty(); - } + public static final PluginContainer PC_A = new FakePluginContainer("a", PLUGIN_A); + public static final PluginContainer PC_B = new FakePluginContainer("b", PLUGIN_B); + + @Override + public @NonNull Optional fromInstance(@NonNull Object instance) { + if (instance == PLUGIN_A) { + return Optional.of(PC_A); + } else if (instance == PLUGIN_B) { + return Optional.of(PC_B); + } else { + return Optional.empty(); + } + } + + @Override + public @NonNull Optional getPlugin(@NonNull String id) { + switch (id) { + case "a": + return Optional.of(PC_A); + case "b": + return Optional.of(PC_B); + default: + return Optional.empty(); + } + } + + @Override + public @NonNull Collection getPlugins() { + return ImmutableList.of(PC_A, PC_B); + } + + @Override + public boolean isLoaded(@NonNull String id) { + return id.equals("a") || id.equals("b"); + } + + @Override + public void addToClasspath(@NonNull Object plugin, @NonNull Path path) { + throw new UnsupportedOperationException(); + } + + private static class FakePluginContainer implements PluginContainer { + + private final String id; + private final Object instance; + + private FakePluginContainer(String id, Object instance) { + this.id = id; + this.instance = instance; } @Override - public @NonNull Optional getPlugin(@NonNull String id) { - switch (id) { - case "a": - return Optional.of(PC_A); - case "b": - return Optional.of(PC_B); - default: - return Optional.empty(); - } + public @NonNull PluginDescription getDescription() { + return () -> id; } @Override - public @NonNull Collection getPlugins() { - return ImmutableList.of(PC_A, PC_B); - } - - @Override - public boolean isLoaded(@NonNull String id) { - return id.equals("a") || id.equals("b"); - } - - @Override - public void addToClasspath(@NonNull Object plugin, @NonNull Path path) { - throw new UnsupportedOperationException(); - } - - private static class FakePluginContainer implements PluginContainer { - private final String id; - private final Object instance; - - private FakePluginContainer(String id, Object instance) { - this.id = id; - this.instance = instance; - } - - @Override - public @NonNull PluginDescription getDescription() { - return () -> id; - } - - @Override - public Optional getInstance() { - return Optional.of(instance); - } + public Optional getInstance() { + return Optional.of(instance); } + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java index ae3e03e2f..f6229a018 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java @@ -1,26 +1,26 @@ package com.velocitypowered.proxy.util; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; - import static org.junit.jupiter.api.Assertions.assertEquals; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import org.junit.jupiter.api.Test; + class EncryptionUtilsTest { - @Test - void twosComplementHexdigest() throws Exception { - String notchHash = mojangLoginSha1("Notch"); - assertEquals("4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48", notchHash); - String jebHash = mojangLoginSha1("jeb_"); - assertEquals("-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1", jebHash); - } + @Test + void twosComplementHexdigest() throws Exception { + String notchHash = mojangLoginSha1("Notch"); + assertEquals("4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48", notchHash); - private String mojangLoginSha1(String str) throws Exception { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.update(str.getBytes(StandardCharsets.UTF_8)); - byte[] digested = digest.digest(); - return EncryptionUtils.twosComplementHexdigest(digested); - } + String jebHash = mojangLoginSha1("jeb_"); + assertEquals("-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1", jebHash); + } + + private String mojangLoginSha1(String str) throws Exception { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(str.getBytes(StandardCharsets.UTF_8)); + byte[] digested = digest.digest(); + return EncryptionUtils.twosComplementHexdigest(digested); + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java index ba97fc493..a4c0858e3 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/RatelimiterTest.java @@ -1,39 +1,38 @@ package com.velocitypowered.proxy.util; -import com.google.common.base.Ticker; -import org.junit.jupiter.api.Test; - -import java.net.InetAddress; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.common.base.Ticker; +import java.net.InetAddress; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.jupiter.api.Test; + class RatelimiterTest { - @Test - void attemptZero() { - Ratelimiter noRatelimiter = new Ratelimiter(0); - assertTrue(noRatelimiter.attempt(InetAddress.getLoopbackAddress())); - assertTrue(noRatelimiter.attempt(InetAddress.getLoopbackAddress())); - } + @Test + void attemptZero() { + Ratelimiter noRatelimiter = new Ratelimiter(0); + assertTrue(noRatelimiter.attempt(InetAddress.getLoopbackAddress())); + assertTrue(noRatelimiter.attempt(InetAddress.getLoopbackAddress())); + } - @Test - void attemptOne() { - long base = System.nanoTime(); - AtomicLong extra = new AtomicLong(); - Ticker testTicker = new Ticker() { - @Override - public long read() { - return base + extra.get(); - } - }; - Ratelimiter ratelimiter = new Ratelimiter(1000, testTicker); - assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress())); - assertFalse(ratelimiter.attempt(InetAddress.getLoopbackAddress())); - extra.addAndGet(TimeUnit.SECONDS.toNanos(2)); - assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress())); - } + @Test + void attemptOne() { + long base = System.nanoTime(); + AtomicLong extra = new AtomicLong(); + Ticker testTicker = new Ticker() { + @Override + public long read() { + return base + extra.get(); + } + }; + Ratelimiter ratelimiter = new Ratelimiter(1000, testTicker); + assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress())); + assertFalse(ratelimiter.attempt(InetAddress.getLoopbackAddress())); + extra.addAndGet(TimeUnit.SECONDS.toNanos(2)); + assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress())); + } } diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java index e2a1ab440..7c67b48bf 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/ServerMapTest.java @@ -1,46 +1,47 @@ package com.velocitypowered.proxy.util; -import com.velocitypowered.api.proxy.server.RegisteredServer; -import com.velocitypowered.api.proxy.server.ServerInfo; -import com.velocitypowered.proxy.server.ServerMap; -import org.junit.jupiter.api.Test; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.util.Optional; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.proxy.server.ServerMap; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Optional; +import org.junit.jupiter.api.Test; + class ServerMapTest { - private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565); - @Test - void respectsCaseInsensitivity() { - ServerMap map = new ServerMap(null); - ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); - RegisteredServer connection = map.register(info); + private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress( + InetAddress.getLoopbackAddress(), 25565); - assertEquals(Optional.of(connection), map.getServer("TestServer")); - assertEquals(Optional.of(connection), map.getServer("testserver")); - assertEquals(Optional.of(connection), map.getServer("TESTSERVER")); - } + @Test + void respectsCaseInsensitivity() { + ServerMap map = new ServerMap(null); + ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); + RegisteredServer connection = map.register(info); - @Test - void rejectsRepeatedRegisterAttempts() { - ServerMap map = new ServerMap(null); - ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); - map.register(info); + assertEquals(Optional.of(connection), map.getServer("TestServer")); + assertEquals(Optional.of(connection), map.getServer("testserver")); + assertEquals(Optional.of(connection), map.getServer("TESTSERVER")); + } - ServerInfo willReject = new ServerInfo("TESTSERVER", TEST_ADDRESS); - assertThrows(IllegalArgumentException.class, () -> map.register(willReject)); - } + @Test + void rejectsRepeatedRegisterAttempts() { + ServerMap map = new ServerMap(null); + ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); + map.register(info); - @Test - void allowsSameServerLaxRegistrationCheck() { - ServerMap map = new ServerMap(null); - ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); - RegisteredServer connection = map.register(info); - assertEquals(connection, map.register(info)); - } + ServerInfo willReject = new ServerInfo("TESTSERVER", TEST_ADDRESS); + assertThrows(IllegalArgumentException.class, () -> map.register(willReject)); + } + + @Test + void allowsSameServerLaxRegistrationCheck() { + ServerMap map = new ServerMap(null); + ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS); + RegisteredServer connection = map.register(info); + assertEquals(connection, map.register(info)); + } } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 044dc9fb2..525baa264 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ rootProject.name = 'velocity' -include ( +include( 'api', 'proxy', 'native'