3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-12-26 00:00:55 +01:00
Dieser Commit ist enthalten in:
Leymooo 2018-09-20 15:54:22 +03:00
Commit 29b82ae301
80 geänderte Dateien mit 1001 neuen und 728 gelöschten Zeilen

1
.gitignore vendored
Datei anzeigen

@ -135,3 +135,4 @@ logs/
server-icon.png
/bin/
run/
plugins/

Datei anzeigen

@ -19,7 +19,8 @@ import javax.tools.StandardLocation;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.Objects;
import java.util.Set;
@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
public class PluginAnnotationProcessor extends AbstractProcessor {

Datei anzeigen

@ -0,0 +1,105 @@
package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
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}).
*/
public class PluginMessageEvent implements ResultedEvent<PluginMessageEvent.ForwardResult> {
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(@NonNull 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 class ForwardResult implements ResultedEvent.Result {
private static final ForwardResult ALLOWED = new ForwardResult(true);
private static final ForwardResult DENIED = new ForwardResult(false);
private final boolean allowed;
private ForwardResult(boolean b) {
this.allowed = b;
}
@Override
public boolean isAllowed() {
return allowed;
}
@Override
public String toString() {
return allowed ? "forward to sink" : "handled message at proxy";
}
public static ForwardResult forward() {
return ALLOWED;
}
public static ForwardResult handled() {
return DENIED;
}
}
}

Datei anzeigen

@ -0,0 +1,28 @@
package com.velocitypowered.api.event.connection;
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
*/
public class PostLoginEvent {
private final Player player;
public PostLoginEvent(Player player) {
this.player = Preconditions.checkNotNull(player, "player");
}
public Player getPlayer() {
return player;
}
@Override
public String toString() {
return "PostLoginEvent{"
+ "player=" + player
+ '}';
}
}

Datei anzeigen

@ -3,12 +3,13 @@ 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 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;
/**
* 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).
@ -52,44 +53,59 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
}
/**
* Represents an "allowed/allowed with online mode/denied" result with a reason allowed for denial.
* Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason allowed for denial.
*/
public static class PreLoginComponentResult extends ResultedEvent.ComponentResult {
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult((Component) null);
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(true);
public static class PreLoginComponentResult implements ResultedEvent.Result {
private final boolean onlineMode;
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);
/**
* Allows online mode to be enabled for the player connection, if Velocity is running in offline mode.
* @param allowedOnlineMode if true, online mode will be used for the connection
*/
private PreLoginComponentResult(boolean allowedOnlineMode) {
super(true, null);
this.onlineMode = allowedOnlineMode;
private final Result result;
private final Optional<Component> reason;
private PreLoginComponentResult(Result result, @Nullable Component reason) {
this.result = result;
this.reason = Optional.ofNullable(reason);
}
private PreLoginComponentResult(@Nullable Component reason) {
super(reason == null, reason);
// Don't care about this
this.onlineMode = false;
@Override
public boolean isAllowed() {
return result != Result.DISALLOWED;
}
public Optional<Component> getReason() {
return reason;
}
public boolean isOnlineModeAllowed() {
return this.onlineMode;
return result == Result.FORCE_ONLINE;
}
public boolean isForceOfflineMode() {
return result == Result.FORCE_OFFLINE;
}
@Override
public String toString() {
if (isForceOfflineMode()) {
return "allowed with force offline mode";
}
if (isOnlineModeAllowed()) {
return "allowed with online mode";
}
return super.toString();
if (isAllowed()) {
return "allowed";
}
if (reason.isPresent()) {
return "denied: " + ComponentSerializers.PLAIN.serialize(reason.get());
}
return "denied";
}
/**
* Returns a result indicating the connection will be allowed through the proxy.
* Returns a result indicating the connection will be allowed through
* the proxy.
* @return the allowed result
*/
public static PreLoginComponentResult allowed() {
@ -97,23 +113,41 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
}
/**
* 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.
* 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(@NonNull Component reason) {
public static PreLoginComponentResult denied(Component reason) {
Preconditions.checkNotNull(reason, "reason");
return new PreLoginComponentResult(reason);
return new PreLoginComponentResult(Result.DISALLOWED, reason);
}
private enum Result {
ALLOWED,
FORCE_ONLINE,
FORCE_OFFLINE,
DISALLOWED
}
}
}

Datei anzeigen

@ -1,10 +1,9 @@
package com.velocitypowered.api.event.player;
import com.velocitypowered.api.proxy.InboundConnection;
import org.checkerframework.checker.nullness.qual.Nullable;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.InboundConnection;
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

Datei anzeigen

@ -3,7 +3,7 @@ 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 com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -13,12 +13,12 @@ import org.checkerframework.checker.nullness.qual.NonNull;
*/
public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEvent.ServerKickResult> {
private final Player player;
private final ServerInfo server;
private final RegisteredServer server;
private final Component originalReason;
private final boolean duringLogin;
private ServerKickResult result;
public KickedFromServerEvent(Player player, ServerInfo server, Component originalReason, boolean duringLogin, Component fancyReason) {
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");
@ -40,7 +40,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return player;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}
@ -91,9 +91,9 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* when this result is used.
*/
public static class RedirectPlayer implements ServerKickResult {
private final ServerInfo server;
private final RegisteredServer server;
private RedirectPlayer(ServerInfo server) {
private RedirectPlayer(RegisteredServer server) {
this.server = Preconditions.checkNotNull(server, "server");
}
@ -102,7 +102,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return false;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}
@ -111,7 +111,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* @param server the server to send the player to
* @return the redirect result
*/
public static RedirectPlayer create(ServerInfo server) {
public static RedirectPlayer create(RegisteredServer server) {
return new RedirectPlayer(server);
}
}

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.player.PlayerSettings;
public class PlayerSettingsChangedEvent {
private final Player player;

Datei anzeigen

@ -2,7 +2,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo;
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
@ -10,9 +10,9 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
*/
public class ServerConnectedEvent {
private final Player player;
private final ServerInfo server;
private final RegisteredServer server;
public ServerConnectedEvent(Player player, ServerInfo server) {
public ServerConnectedEvent(Player player, RegisteredServer server) {
this.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server");
}
@ -21,7 +21,7 @@ public class ServerConnectedEvent {
return player;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}

Datei anzeigen

@ -3,7 +3,7 @@ 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 com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -14,11 +14,13 @@ import java.util.Optional;
*/
public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEvent.ServerResult> {
private final Player player;
private final RegisteredServer originalServer;
private ServerResult result;
public ServerPreConnectEvent(Player player, ServerResult result) {
public ServerPreConnectEvent(Player player, RegisteredServer originalServer) {
this.player = Preconditions.checkNotNull(player, "player");
this.result = Preconditions.checkNotNull(result, "result");
this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer");
this.result = ServerResult.allowed(originalServer);
}
public Player getPlayer() {
@ -35,10 +37,15 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
this.result = Preconditions.checkNotNull(result, "result");
}
public RegisteredServer getOriginalServer() {
return originalServer;
}
@Override
public String toString() {
return "ServerPreConnectEvent{" +
"player=" + player +
", originalServer=" + originalServer +
", result=" + result +
'}';
}
@ -50,11 +57,11 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
private static final ServerResult DENIED = new ServerResult(false, null);
private final boolean allowed;
private final ServerInfo info;
private final RegisteredServer server;
private ServerResult(boolean allowed, @Nullable ServerInfo info) {
private ServerResult(boolean allowed, @Nullable RegisteredServer server) {
this.allowed = allowed;
this.info = info;
this.server = server;
}
@Override
@ -62,8 +69,8 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
return allowed;
}
public Optional<ServerInfo> getInfo() {
return Optional.ofNullable(info);
public Optional<RegisteredServer> getServer() {
return Optional.ofNullable(server);
}
@Override
@ -71,14 +78,14 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
if (!allowed) {
return "denied";
}
return "allowed: connect to " + info.getName();
return "allowed: connect to " + server.getServerInfo().getName();
}
public static ServerResult denied() {
return DENIED;
}
public static ServerResult allowed(ServerInfo server) {
public static ServerResult allowed(RegisteredServer server) {
Preconditions.checkNotNull(server, "server");
return new ServerResult(true, server);
}

Datei anzeigen

@ -1,7 +1,6 @@
package com.velocitypowered.api.plugin.meta;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;

Datei anzeigen

@ -1,29 +1,28 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/**
* 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(ServerInfo)}.
* 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
*/
@NonNull ServerInfo getServer();
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
*/
@NonNull CompletableFuture<Result> connect();
CompletableFuture<Result> connect();
/**
* Initiates the connection to the remote server without waiting for a result. Velocity will use generic error

Datei anzeigen

@ -1,11 +1,11 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.MessagePosition;
import java.util.List;
import net.kyori.text.Component;
@ -65,10 +65,10 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
/**
* Creates a new connection request so that the player can connect to another server.
* @param info the server to connect to
* @param server the server to connect to
* @return a new connection request
*/
ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info);
ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server);
/**
* Gets a game profile properties of player

Datei anzeigen

@ -1,12 +1,13 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import com.velocitypowered.api.scheduler.Scheduler;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.scheduler.Scheduler;
import java.net.InetSocketAddress;
import java.util.Collection;
@ -45,23 +46,24 @@ public interface ProxyServer {
int getPlayerCount();
/**
* Retrieves a registered {@link ServerInfo} instance by its name. The search is case-insensitive.
* 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<ServerInfo> getServerInfo(String name);
Optional<RegisteredServer> getServer(String name);
/**
* Retrieves all {@link ServerInfo}s registered with this proxy.
* Retrieves all {@link RegisteredServer}s registered with this proxy.
* @return the servers registered with this proxy
*/
Collection<ServerInfo> getAllServers();
Collection<RegisteredServer> getAllServers();
/**
* 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
*/
void registerServer(ServerInfo server);
RegisteredServer registerServer(ServerInfo server);
/**
* Unregisters this server from the proxy.

Datei anzeigen

@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
/**
@ -12,6 +13,12 @@ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSi
* 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();
/**

Datei anzeigen

@ -8,6 +8,7 @@ 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
*/
void sendPluginMessage(ChannelIdentifier identifier, byte[] data);
boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data);
}

Datei anzeigen

@ -1,16 +1,14 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents an interface to register and unregister {@link MessageHandler} instances for handling plugin messages from
* the client or the server.
* Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on.
*/
public interface ChannelRegistrar {
/**
* Registers the specified message handler to listen for plugin messages on the specified channels.
* @param handler the handler to register
* Registers the specified message identifiers to listen on for the
* @param identifiers the channel identifiers to register
*/
void register(MessageHandler handler, ChannelIdentifier... identifiers);
void register(ChannelIdentifier... identifiers);
/**
* Unregisters the handler for the specified channel.

Datei anzeigen

@ -1,15 +0,0 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents from "which side" of the proxy the plugin message came from.
*/
public enum ChannelSide {
/**
* The plugin message came from a server that a client was connected to.
*/
FROM_SERVER,
/**
* The plugin message came from the client.
*/
FROM_CLIENT
}

Datei anzeigen

@ -1,28 +0,0 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents a handler for handling plugin messages.
*/
public interface MessageHandler {
/**
* Handles an incoming plugin message.
* @param source the source of the plugin message
* @param side from where the plugin message originated
* @param identifier the channel on which the message was sent
* @param data the data inside the plugin message
* @return a {@link ForwardStatus} indicating whether or not to forward this plugin message on
*/
ForwardStatus handle(ChannelMessageSource source, ChannelSide side, ChannelIdentifier identifier, byte[] data);
enum ForwardStatus {
/**
* Forwards this plugin message on to the client or server, depending on the {@link ChannelSide} it originated
* from.
*/
FORWARD,
/**
* Discard the plugin message and do not forward it on.
*/
HANDLED
}
}

Datei anzeigen

@ -10,7 +10,7 @@ import java.util.regex.Pattern;
* 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\\-_]+", Pattern.CASE_INSENSITIVE);
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+");
private final String namespace;
private final String name;

Datei anzeigen

@ -0,0 +1,30 @@
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;
/**
* 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<Player> getPlayersConnected();
/**
* Attempts to ping the remote server and return the server list ping result.
* @return the server ping result from the server
*/
CompletableFuture<ServerPing> ping();
}

Datei anzeigen

@ -74,6 +74,7 @@ public class ServerPing {
builder.favicon = favicon;
builder.nullOutModinfo = modinfo == null;
if (modinfo != null) {
builder.modType = modinfo.type;
builder.mods.addAll(modinfo.modList);
}
return builder;
@ -91,6 +92,7 @@ public class ServerPing {
private int onlinePlayers;
private int maximumPlayers;
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType;
private final List<Mod> mods = new ArrayList<>();
private Component description;
private Favicon favicon;
@ -121,6 +123,11 @@ public class ServerPing {
return this;
}
public Builder modType(String modType) {
this.modType = Preconditions.checkNotNull(modType, "modType");
return this;
}
public Builder mods(Mod... mods) {
this.mods.addAll(Arrays.asList(mods));
return this;
@ -158,7 +165,7 @@ public class ServerPing {
public ServerPing build() {
return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon,
nullOutModinfo ? null : new Modinfo(mods));
nullOutModinfo ? null : new Modinfo(modType, mods));
}
public Version getVersion() {
@ -185,6 +192,10 @@ public class ServerPing {
return favicon;
}
public String getModType() {
return modType;
}
public List<Mod> getMods() {
return mods;
}
@ -196,6 +207,7 @@ public class ServerPing {
", onlinePlayers=" + onlinePlayers +
", maximumPlayers=" + maximumPlayers +
", samplePlayers=" + samplePlayers +
", modType=" + modType +
", mods=" + mods +
", description=" + description +
", favicon=" + favicon +
@ -291,14 +303,23 @@ public class ServerPing {
}
public static class Modinfo {
public static final Modinfo DEFAULT = new Modinfo(ImmutableList.of());
public static final Modinfo DEFAULT = new Modinfo("FML", ImmutableList.of());
private final String type = "FML";
private final String type;
private final List<Mod> modList;
public Modinfo(List<Mod> modList) {
public Modinfo(String type, List<Mod> modList) {
this.type = Preconditions.checkNotNull(type, "type");
this.modList = ImmutableList.copyOf(modList);
}
public String getType() {
return type;
}
public List<Mod> getMods() {
return modList;
}
}
public static class Mod {

Datei anzeigen

@ -24,7 +24,7 @@ public interface Scheduler {
* @param unit the unit of time for {@code time}
* @return this builder, for chaining
*/
TaskBuilder delay(int time, TimeUnit unit);
TaskBuilder delay(long time, TimeUnit unit);
/**
* Specifies that the task should continue running after waiting for the specified amount, until it is cancelled.
@ -32,7 +32,7 @@ public interface Scheduler {
* @param unit the unit of time for {@code time}
* @return this builder, for chaining
*/
TaskBuilder repeat(int time, TimeUnit unit);
TaskBuilder repeat(long time, TimeUnit unit);
/**
* Clears the delay on this task.

Datei anzeigen

@ -1,66 +0,0 @@
package com.velocitypowered.api.util;
import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.regex.Pattern;
/**
* LegacyChatColorUtils contains utilities for handling legacy Minecraft color codes. Generally, you should prefer
* JSON-based components, but for convenience Velocity provides a limited set of tools to handle Minecraft color codes.
*/
public class LegacyChatColorUtils {
private LegacyChatColorUtils() {
throw new AssertionError();
}
/**
* Represents the legacy Minecraft format character, the section symbol.
*/
public static final char FORMAT_CHAR = '\u00a7';
/**
* Translates a string with Minecraft color codes prefixed with a different character than the section symbol into
* a string that uses the section symbol.
* @param originalChar the char the color codes are prefixed by
* @param text the text to translate
* @return the translated text
*/
public static String translate(char originalChar, @NonNull String text) {
Preconditions.checkNotNull(text, "text");
char[] textChars = text.toCharArray();
int foundSectionIdx = -1;
for (int i = 0; i < textChars.length; i++) {
char textChar = textChars[i];
if (textChar == originalChar) {
foundSectionIdx = i;
continue;
}
if (foundSectionIdx >= 0) {
textChar = Character.toLowerCase(textChar);
if ((textChar >= 'a' && textChar <= 'f') || (textChar >= '0' && textChar <= '9') ||
(textChar >= 'l' && textChar <= 'o' || textChar == 'r')) {
textChars[foundSectionIdx] = FORMAT_CHAR;
}
foundSectionIdx = -1;
}
}
return new String(textChars);
}
/**
* A regex that matches all Minecraft color codes and removes them.
*/
private static final Pattern CHAT_COLOR_MATCHER = Pattern.compile("(?i)" + Character.toString(FORMAT_CHAR) + "[0-9A-FL-OR]");
/**
* Removes all Minecraft color codes from the string.
* @param text the text to remove color codes from
* @return a new String without Minecraft color codes
*/
public static String removeFormatting(@NonNull String text) {
Preconditions.checkNotNull(text, "text");
return CHAT_COLOR_MATCHER.matcher(text).replaceAll("");
}
}

Datei anzeigen

@ -1,62 +0,0 @@
package com.velocitypowered.api.util;
import com.velocitypowered.api.util.LegacyChatColorUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class LegacyChatColorUtilsTest {
private static final String NON_FORMATTED = "Velocity";
private static final String FORMATTED = "\u00a7cVelocity";
private static final String FORMATTED_MULTIPLE = "\u00a7c\u00a7lVelocity";
private static final String FORMATTED_MULTIPLE_VARIED = "\u00a7c\u00a7lVelo\u00a7a\u00a7mcity";
private static final String INVALID = "\u00a7gVelocity";
private static final String RAW_SECTION = "\u00a7";
@Test
void removeFormattingNonFormatted() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(NON_FORMATTED));
}
@Test
void removeFormattingFormatted() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED));
}
@Test
void removeFormattingFormattedMultiple() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE));
}
@Test
void removeFormattingFormattedMultipleVaried() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE_VARIED));
}
@Test
void removeFormattingInvalidFormat() {
assertEquals(INVALID, LegacyChatColorUtils.removeFormatting(INVALID));
}
@Test
void removeFormattingRawSection() {
assertEquals(RAW_SECTION, LegacyChatColorUtils.removeFormatting(RAW_SECTION));
}
@Test
void translate() {
assertEquals(FORMATTED, LegacyChatColorUtils.translate('&', "&cVelocity"));
}
@Test
void translateMultiple() {
assertEquals(FORMATTED_MULTIPLE, LegacyChatColorUtils.translate('&', "&c&lVelocity"));
assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('&', "&c&lVelo&a&mcity"));
}
@Test
void translateDifferentChar() {
assertEquals(FORMATTED, LegacyChatColorUtils.translate('$', "$cVelocity"));
assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('$', "$c$lVelo$a$mcity"));
}
}

Datei anzeigen

@ -8,7 +8,7 @@ import java.util.function.Supplier;
public class NativeCodeLoader<T> implements Supplier<T> {
private final List<Variant<T>> variants;
private Variant<T> selected;
private volatile Variant<T> selected;
public NativeCodeLoader(List<Variant<T>> variants) {
this.variants = ImmutableList.copyOf(variants);
@ -16,36 +16,41 @@ public class NativeCodeLoader<T> implements Supplier<T> {
@Override
public T get() {
if (selected == null) {
selected = select();
}
return selected.object;
return tryLoad().object;
}
private Variant<T> select() {
for (Variant<T> variant : variants) {
T got = variant.get();
if (got == null) {
continue;
}
return variant;
private Variant<T> tryLoad() {
if (selected != null) {
return selected;
}
synchronized (this) {
if (selected != null) {
return selected;
}
for (Variant<T> variant : variants) {
T got = variant.get();
if (got == null) {
continue;
}
selected = variant;
return selected;
}
throw new IllegalArgumentException("Can't find any suitable variants");
}
throw new IllegalArgumentException("Can't find any suitable variants");
}
public String getLoadedVariant() {
if (selected == null) {
selected = select();
}
return selected.name;
return tryLoad().name;
}
static class Variant<T> {
private boolean available;
private volatile boolean available;
private final Runnable setup;
private final String name;
private final T object;
private boolean hasBeenSetup = false;
private volatile boolean hasBeenSetup = false;
Variant(BooleanSupplier available, Runnable setup, String name, T object) {
this.available = available.getAsBoolean();
@ -54,27 +59,34 @@ public class NativeCodeLoader<T> implements Supplier<T> {
this.object = object;
}
private void setup() {
if (available && !hasBeenSetup) {
try {
setup.run();
hasBeenSetup = true;
} catch (Exception e) {
available = false;
public T get() {
if (!available) {
return null;
}
// Make sure setup happens only once
if (!hasBeenSetup) {
synchronized (this) {
// We change availability if need be below, may as well check it again here for sanity.
if (!available) {
return null;
}
// Okay, now try the setup if we haven't done so yet.
if (!hasBeenSetup) {
try {
setup.run();
hasBeenSetup = true;
return object;
} catch (Exception e) {
available = false;
return null;
}
}
}
}
}
public T get() {
if (!hasBeenSetup) {
setup();
}
if (available) {
return object;
}
return null;
return object;
}
}

Datei anzeigen

@ -3,10 +3,8 @@ package com.velocitypowered.natives.util;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
import com.velocitypowered.natives.compression.NativeVelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressorFactory;
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
import com.velocitypowered.natives.encryption.NativeVelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import java.io.IOException;

Datei anzeigen

@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import java.util.Random;
import java.util.function.Supplier;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;

Datei anzeigen

@ -8,29 +8,30 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.command.ServerCommand;
import com.velocitypowered.proxy.command.ShutdownCommand;
import com.velocitypowered.proxy.command.VelocityCommand;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.command.VelocityCommandManager;
import com.velocitypowered.proxy.config.AnnotatedConfig;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.messages.VelocityChannelRegistrar;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.plugin.VelocityEventManager;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.scheduler.VelocityScheduler;
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.ServerMap;
import io.netty.bootstrap.Bootstrap;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
@ -61,7 +62,7 @@ public class VelocityServer implements ProxyServer {
private VelocityConfiguration configuration;
private NettyHttpClient httpClient;
private KeyPair serverKeyPair;
private final ServerMap servers = new ServerMap();
private final ServerMap servers = new ServerMap(this);
private final VelocityCommandManager commandManager = new VelocityCommandManager();
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
private boolean shutdown = false;
@ -263,19 +264,19 @@ public class VelocityServer implements ProxyServer {
}
@Override
public Optional<ServerInfo> getServerInfo(String name) {
public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "name");
return servers.getServer(name);
}
@Override
public Collection<ServerInfo> getAllServers() {
public Collection<RegisteredServer> getAllServers() {
return servers.getAllServers();
}
@Override
public void registerServer(ServerInfo server) {
servers.register(server);
public RegisteredServer registerServer(ServerInfo server) {
return servers.register(server);
}
@Override

Datei anzeigen

@ -6,6 +6,7 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
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 net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
@ -34,7 +35,7 @@ public class ServerCommand implements Command {
if (args.length == 1) {
// Trying to connect to a server.
String serverName = args[0];
Optional<ServerInfo> toConnect = server.getServerInfo(serverName);
Optional<RegisteredServer> toConnect = server.getServer(serverName);
if (!toConnect.isPresent()) {
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
return;
@ -48,17 +49,19 @@ public class ServerCommand implements Command {
// Assemble the list of servers as components
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW);
List<ServerInfo> infos = ImmutableList.copyOf(server.getAllServers());
List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
for (int i = 0; i < infos.size(); i++) {
ServerInfo serverInfo = infos.get(i);
TextComponent infoComponent = TextComponent.of(serverInfo.getName());
if (serverInfo.getName().equals(currentServer)) {
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")));
.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 " + serverInfo.getName()))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server")));
.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) {
@ -74,11 +77,11 @@ public class ServerCommand implements Command {
public List<String> suggest(CommandSource source, String[] currentArgs) {
if (currentArgs.length == 0) {
return server.getAllServers().stream()
.map(ServerInfo::getName)
.map(rs -> rs.getServerInfo().getName())
.collect(Collectors.toList());
} else if (currentArgs.length == 1) {
return server.getAllServers().stream()
.map(ServerInfo::getName)
.map(rs -> rs.getServerInfo().getName())
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
.collect(Collectors.toList());
} else {

Datei anzeigen

@ -1,10 +1,9 @@
package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import java.util.*;
import java.util.stream.Collectors;

Datei anzeigen

@ -1,5 +1,8 @@
package com.velocitypowered.proxy.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
@ -8,18 +11,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Collection;
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;
/**
* Only for simple configs

Datei anzeigen

@ -5,10 +5,9 @@ import com.google.common.collect.ImmutableMap;
import com.moandjiezana.toml.Toml;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.api.util.LegacyChatColorUtils;
import io.netty.buffer.ByteBufUtil;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.Reader;
@ -17,12 +16,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.logging.log4j.Logger;
import java.util.*;
public class VelocityConfiguration extends AnnotatedConfig {
@ -273,6 +267,14 @@ public class VelocityConfiguration extends AnnotatedConfig {
return announceForge;
}
public int getConnectTimeout() {
return advanced.getConnectionTimeout();
}
public int getReadTimeout() {
return advanced.getReadTimeout();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@ -420,21 +422,23 @@ public class VelocityConfiguration extends AnnotatedConfig {
"Disable by setting to 0"})
@ConfigKey("login-ratelimit")
private int loginRatelimit = 3000;
@Comment({"Specify a custom timeout for connection timeouts here. The default is five seconds."})
@ConfigKey("connection-timeout")
private int connectionTimeout = 5000;
@Comment({"Specify a read timeout for connections here. The default is 30 seconds."})
@ConfigKey("read-timeout")
private int readTimeout = 30000;
private Advanced() {
}
private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) {
this.compressionThreshold = compressionThreshold;
this.compressionLevel = compressionLevel;
this.loginRatelimit = loginRatelimit;
}
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();
}
}
@ -442,33 +446,31 @@ public class VelocityConfiguration extends AnnotatedConfig {
return compressionThreshold;
}
public void setCompressionThreshold(int compressionThreshold) {
this.compressionThreshold = compressionThreshold;
}
public int getCompressionLevel() {
return compressionLevel;
}
public void setCompressionLevel(int compressionLevel) {
this.compressionLevel = compressionLevel;
}
public int getLoginRatelimit() {
return loginRatelimit;
}
public void setLoginRatelimit(int loginRatelimit) {
this.loginRatelimit = loginRatelimit;
public int getConnectionTimeout() {
return connectionTimeout;
}
public int getReadTimeout() {
return readTimeout;
}
@Override
public String toString() {
return "Advanced{"
+ "compressionThreshold=" + compressionThreshold
+ ", compressionLevel=" + compressionLevel
+ ", loginRatelimit=" + loginRatelimit
+ '}';
return "Advanced{" +
"compressionThreshold=" + compressionThreshold +
", compressionLevel=" + compressionLevel +
", loginRatelimit=" + loginRatelimit +
", connectionTimeout=" + connectionTimeout +
", readTimeout=" + readTimeout +
'}';
}
}
@ -500,18 +502,10 @@ public class VelocityConfiguration extends AnnotatedConfig {
return queryEnabled;
}
public void setQueryEnabled(boolean queryEnabled) {
this.queryEnabled = queryEnabled;
}
public int getQueryPort() {
return queryPort;
}
public void setQueryPort(int queryPort) {
this.queryPort = queryPort;
}
@Override
public String toString() {
return "Query{"

Datei anzeigen

@ -2,13 +2,13 @@ package com.velocitypowered.proxy.connection;
import com.google.common.base.Preconditions;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import com.velocitypowered.natives.util.Natives;
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.natives.encryption.VelocityCipher;
import com.velocitypowered.proxy.protocol.netty.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
@ -21,17 +21,9 @@ import org.apache.logging.log4j.Logger;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
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 static com.velocitypowered.proxy.network.Connections.*;
/**
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
@ -99,8 +91,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (association != null) {
logger.error("{}: exception encountered", association, cause);
} else {
logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause);
}
ctx.close();

Datei anzeigen

@ -8,6 +8,8 @@ public class VelocityConstants {
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
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";
public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 };
}

Datei anzeigen

@ -1,15 +1,16 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.messages.ChannelSide;
import com.velocitypowered.api.proxy.messages.MessageHandler;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
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.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf;
@ -24,7 +25,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void activated() {
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServerInfo()));
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServer()));
connection.getServer().addPlayer(connection.getPlayer());
}
@Override
@ -32,7 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (!connection.getPlayer().isActive()) {
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
// errors.
connection.getMinecraftConnection().close();
connection.disconnect();
return;
}
@ -44,7 +46,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
connection.getPlayer().getConnection().write(packet);
} else if (packet instanceof Disconnect) {
Disconnect original = (Disconnect) packet;
connection.getPlayer().handleConnectionException(connection.getServerInfo(), original);
connection.disconnect();
connection.getPlayer().handleConnectionException(connection.getServer(), original);
} else if (packet instanceof JoinGame) {
playerHandler.handleBackendJoinGame((JoinGame) packet);
} else if (packet instanceof BossBar) {
@ -83,10 +86,17 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return;
}
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(connection,
ChannelSide.FROM_SERVER, pm);
if (status == MessageHandler.ForwardStatus.FORWARD) {
ChannelIdentifier id = server.getChannelRegistrar().getFromId(pm.getChannel());
if (id == null) {
connection.getPlayer().getConnection().write(pm);
} else {
PluginMessageEvent event = new PluginMessageEvent(connection, connection.getPlayer(), id, pm.getData());
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
connection.getPlayer().getConnection().write(pm);
}
}, connection.getMinecraftConnection().getChannel().eventLoop());
}
} else if (connection.hasCompletedJoin()) {
// Just forward the packet on. We don't have anything to handle at this time.
@ -99,7 +109,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (!connection.getPlayer().isActive()) {
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
// errors.
connection.getMinecraftConnection().close();
connection.disconnect();
return;
}
@ -110,7 +120,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void exception(Throwable throwable) {
connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable);
connection.getPlayer().handleConnectionException(connection.getServer(), throwable);
}
public VelocityServer getServer() {
return server;
}
@Override
public void disconnected() {
connection.getServer().removePlayer(connection.getPlayer());
if (!connection.isGracefulDisconnect()) {
connection.getPlayer().handleConnectionException(connection.getServer(), Disconnect.create(
ConnectionMessages.UNEXPECTED_DISCONNECT));
}
}
private boolean canForwardPluginMessage(PluginMessage message) {

Datei anzeigen

@ -1,18 +1,18 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.kyori.text.TextComponent;
@ -82,12 +82,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
if (existingConnection == null) {
// Strap on the play session handler
connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer()));
// This is 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.
//
// The call will handle if the player is not a Forge player appropriately.
connection.getPlayer().getConnection().setCanSendLegacyFMLResetPacket(true);
} 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.

Datei anzeigen

@ -4,9 +4,14 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
@ -14,12 +19,11 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.packet.Handshake;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import io.netty.channel.*;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.AttributeKey;
@ -27,27 +31,22 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
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 static com.velocitypowered.proxy.network.Connections.SERVER_READ_TIMEOUT_SECONDS;
import static com.velocitypowered.proxy.network.Connections.*;
public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection {
static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER =
AttributeKey.newInstance("connection-notification-result");
private final ServerInfo serverInfo;
private final VelocityRegisteredServer registeredServer;
private final ConnectedPlayer proxyPlayer;
private final VelocityServer server;
private MinecraftConnection minecraftConnection;
private boolean legacyForge = false;
private boolean hasCompletedJoin = false;
private boolean gracefulDisconnect = false;
public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) {
this.serverInfo = target;
public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) {
this.registeredServer = registeredServer;
this.proxyPlayer = proxyPlayer;
this.server = server;
}
@ -55,12 +54,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>();
server.initializeGenericBootstrap()
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(SERVER_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
.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))
@ -73,7 +71,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
ch.pipeline().addLast(HANDLER, connection);
}
})
.connect(serverInfo.getAddress())
.connect(registeredServer.getServerInfo().getAddress())
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
@ -95,7 +93,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
// 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 serverInfo.getAddress().getHostString() + "\0" +
return registeredServer.getServerInfo().getAddress().getHostString() + "\0" +
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
proxyPlayer.getProfile().getId() + "\0" +
GSON.toJson(proxyPlayer.getProfile().getProperties());
@ -113,9 +111,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
} else if (proxyPlayer.getConnection().isLegacyForge()) {
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
} else {
handshake.setServerAddress(serverInfo.getAddress().getHostString());
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
}
handshake.setPort(serverInfo.getAddress().getPort());
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
minecraftConnection.write(handshake);
int protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
@ -127,12 +125,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
minecraftConnection.write(login);
}
public void writeIfJoined(PluginMessage message) {
if (hasCompletedJoin) {
minecraftConnection.write(message);
}
}
public MinecraftConnection getMinecraftConnection() {
return minecraftConnection;
}
@Override
public VelocityRegisteredServer getServer() {
return registeredServer;
}
@Override
public ServerInfo getServerInfo() {
return serverInfo;
return registeredServer.getServerInfo();
}
@Override
@ -141,23 +151,27 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
}
public void disconnect() {
minecraftConnection.close();
minecraftConnection = null;
if (minecraftConnection != null) {
minecraftConnection.close();
minecraftConnection = null;
gracefulDisconnect = true;
}
}
@Override
public String toString() {
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName();
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName();
}
@Override
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
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);
minecraftConnection.write(message);
return true;
}
public boolean isLegacyForge() {
@ -175,4 +189,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public void setHasCompletedJoin(boolean hasCompletedJoin) {
this.hasCompletedJoin = hasCompletedJoin;
}
public boolean isGracefulDisconnect() {
return gracefulDisconnect;
}
}

Datei anzeigen

@ -1,14 +1,15 @@
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.proxy.messages.ChannelSide;
import com.velocitypowered.api.proxy.messages.MessageHandler;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.ThrowableUtils;
import io.netty.buffer.ByteBuf;
@ -33,6 +34,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private boolean spawned = false;
private final List<UUID> serverBossBars = new ArrayList<>();
private final Set<String> clientPluginMsgChannels = new HashSet<>();
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
private final VelocityServer server;
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
@ -53,6 +55,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void handle(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
if (packet instanceof KeepAlive) {
KeepAlive keepAlive = (KeepAlive) packet;
if (keepAlive.getRandomId() != lastPingID) {
@ -62,7 +70,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
player.setPing(System.currentTimeMillis() - lastPingSent);
resetPingData();
player.getConnectedServer().getMinecraftConnection().write(packet);
serverConnection.getMinecraftConnection().write(packet);
return;
}
@ -110,7 +118,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
player.getConnection().write(response);
} else {
player.getConnectedServer().getMinecraftConnection().write(packet);
serverConnection.getMinecraftConnection().write(packet);
}
} catch (Exception e) {
logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e);
@ -125,15 +133,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
// If we don't want to handle this packet, just forward it on.
if (player.getConnectedServer().hasCompletedJoin()) {
player.getConnectedServer().getMinecraftConnection().write(packet);
if (serverConnection.hasCompletedJoin()) {
serverConnection.getMinecraftConnection().write(packet);
}
}
@Override
public void handleUnknown(ByteBuf buf) {
if (player.getConnectedServer().hasCompletedJoin()) {
player.getConnectedServer().getMinecraftConnection().write(buf.retain());
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
if (serverConnection.hasCompletedJoin()) {
serverConnection.getMinecraftConnection().write(buf.retain());
}
}
@ -162,11 +176,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
public void handleBackendJoinGame(JoinGame joinGame) {
resetPingData(); // reset ping data;
resetPingData(); // reset ping data
if (!spawned) {
// nothing special to do here
// 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 {
// Ah, this is the meat and potatoes of the whole venture!
//
@ -210,6 +234,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
channel, toRegister));
}
// If we had plugin messages queued during login/FML handshake, send them now.
PluginMessage pm;
while ((pm = loginPluginMessages.poll()) != null) {
player.getConnectedServer().getMinecraftConnection().delayedWrite(pm);
}
// Flush everything
player.getConnection().flush();
player.getConnectedServer().getMinecraftConnection().flush();
@ -234,8 +264,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return serverBossBars;
}
public void handleClientPluginMessage(PluginMessage packet) {
if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) {
private void handleClientPluginMessage(PluginMessage packet) {
if (PluginMessageUtil.isMCRegister(packet)) {
List<String> actuallyRegistered = new ArrayList<>();
List<String> channels = PluginMessageUtil.getChannels(packet);
for (String channel : channels) {
@ -252,31 +282,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered);
player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket);
}
return;
}
if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) {
} else if (PluginMessageUtil.isMCUnregister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet);
clientPluginMsgChannels.removeAll(channels);
}
if (PluginMessageUtil.isMCBrand(packet)) {
player.getConnectedServer().getMinecraftConnection().write(packet);
} else if (PluginMessageUtil.isMCBrand(packet)) {
player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
return;
}
if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
// Ensure that the messages are forwarded
player.getConnectedServer().getMinecraftConnection().write(packet);
return;
}
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(player,
ChannelSide.FROM_CLIENT, packet);
if (status == MessageHandler.ForwardStatus.FORWARD) {
// We're going to forward on the original packet.
player.getConnectedServer().getMinecraftConnection().write(packet);
} else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
// Always forward the FML handshake to the remote server.
player.getConnectedServer().getMinecraftConnection().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) {
player.getConnectedServer().getMinecraftConnection().write(packet);
} else {
PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(), id, packet.getData());
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
player.getConnectedServer().getMinecraftConnection().write(packet);
}
}, player.getConnectedServer().getMinecraftConnection().getChannel().eventLoop());
}
}
}

Datei anzeigen

@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.player.SkinParts;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import java.util.Locale;
public class ClientSettingsWrapper implements PlayerSettings {

Datei anzeigen

@ -7,28 +7,24 @@ import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.permission.PermissionProvider;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import com.velocitypowered.proxy.util.ThrowableUtils;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent;
@ -159,8 +155,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
@Override
public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) {
return new ConnectionRequestBuilderImpl(info);
public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) {
return new ConnectionRequestBuilderImpl(server);
}
@Override
@ -195,52 +191,57 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return connectedServer;
}
public void handleConnectionException(ServerInfo info, Throwable throwable) {
public void handleConnectionException(RegisteredServer server, Throwable throwable) {
String error = ThrowableUtils.briefDescription(throwable);
String userMessage;
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
userMessage = "Exception in server " + info.getName();
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
userMessage = "Exception in server " + server.getServerInfo().getName();
} else {
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable);
userMessage = "Exception connecting to server " + info.getName();
logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), throwable);
userMessage = "Exception connecting to server " + server.getServerInfo().getName();
}
handleConnectionException(info, null, TextComponent.builder()
handleConnectionException(server, null, TextComponent.builder()
.content(userMessage + ": ")
.color(TextColor.RED)
.append(TextComponent.of(error, TextColor.WHITE))
.build());
}
public void handleConnectionException(ServerInfo info, Disconnect disconnect) {
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(info)) {
logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason);
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, info.getName(), plainTextReason);
logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason);
handleConnectionException(server, disconnectReason, TextComponent.builder()
.content("Unable to connect to " + server.getServerInfo().getName() + ": ")
.color(TextColor.RED)
.append(disconnectReason)
.build());
}
handleConnectionException(info, disconnectReason, TextComponent.builder()
.content("Unable to connect to " + info.getName() + ": ")
.color(TextColor.RED)
.append(disconnectReason)
.build());
}
private void handleConnectionException(ServerInfo info, @Nullable Component kickReason, Component friendlyReason) {
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);;
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<ServerInfo> nextServer = getNextServerToTry();
Optional<RegisteredServer> nextServer = getNextServerToTry();
if (nextServer.isPresent()) {
createConnectionRequest(nextServer.get()).fireAndForget();
} else {
connection.closeWith(Disconnect.create(friendlyReason));
}
} else if (connectedServer.getServerInfo().equals(info)) {
} else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) {
// Already connected to the server being disconnected from.
if (kickReason != null) {
server.getEventManager().fire(new KickedFromServerEvent(this, info, kickReason, !alreadyConnected, friendlyReason))
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();
@ -261,7 +262,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
}
Optional<ServerInfo> getNextServerToTry() {
Optional<RegisteredServer> getNextServerToTry() {
List<String> serversToTry = server.getConfiguration().getAttemptConnectionOrder();
if (tryIndex >= serversToTry.size()) {
return Optional.empty();
@ -272,21 +273,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return server.getServers().getServer(toTryName);
}
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
private Optional<ConnectionRequestBuilder.Status> checkServer(RegisteredServer server) {
Preconditions.checkState(server instanceof VelocityRegisteredServer, "Not a valid Velocity server.");
if (connectionInFlight != null) {
return CompletableFuture.completedFuture(
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS)
);
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();
}
if (connectedServer != null && connectedServer.getServerInfo().equals(request.getServer())) {
return CompletableFuture.completedFuture(
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.ALREADY_CONNECTED)
);
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
Optional<ConnectionRequestBuilder.Status> initialCheck = checkServer(request.getServer());
if (initialCheck.isPresent()) {
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(initialCheck.get()));
}
// Otherwise, initiate the connection.
ServerPreConnectEvent event = new ServerPreConnectEvent(this, ServerPreConnectEvent.ServerResult.allowed(request.getServer()));
ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer());
return server.getEventManager().fire(event)
.thenCompose((newEvent) -> {
if (!newEvent.getResult().isAllowed()) {
@ -295,7 +300,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
);
}
return new VelocityServerConnection(newEvent.getResult().getInfo().get(), this, server).connect();
RegisteredServer rs = newEvent.getResult().getServer().get();
Optional<ConnectionRequestBuilder.Status> lastCheck = checkServer(rs);
if (lastCheck.isPresent()) {
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(lastCheck.get()));
}
return new VelocityServerConnection((VelocityRegisteredServer) rs, this, server).connect();
});
}
@ -341,25 +351,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
@Override
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
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;
}
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
private final ServerInfo info;
private final RegisteredServer server;
ConnectionRequestBuilderImpl(ServerInfo info) {
this.info = Preconditions.checkNotNull(info, "info");
ConnectionRequestBuilderImpl(RegisteredServer server) {
this.server = Preconditions.checkNotNull(server, "info");
}
@Override
public ServerInfo getServer() {
return info;
public RegisteredServer getServer() {
return server;
}
@Override
@ -372,7 +383,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connect()
.whenCompleteAsync((status, throwable) -> {
if (throwable != null) {
handleConnectionException(info, throwable);
handleConnectionException(server, throwable);
return;
}
@ -387,7 +398,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
// Ignored; the plugin probably already handled this.
break;
case SERVER_DISCONNECTED:
handleConnectionException(info, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
handleConnectionException(server, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
break;
}
}, connection.getChannel().eventLoop());

Datei anzeigen

@ -5,12 +5,12 @@ import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
private final ConnectedPlayer player;

Datei anzeigen

@ -2,22 +2,23 @@ package com.velocitypowered.proxy.connection.client;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
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.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.util.EncryptionUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
@ -39,6 +40,7 @@ import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
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";
@ -157,7 +159,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
return;
}
if (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) {
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
// Request encryption.
EncryptionRequest request = generateRequest();
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
@ -218,7 +220,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
}
private void handleProxyLogin(ConnectedPlayer player) {
Optional<ServerInfo> toTry = player.getNextServerToTry();
Optional<RegisteredServer> toTry = player.getNextServerToTry();
if (!toTry.isPresent()) {
player.close(TextComponent.of("No available servers", TextColor.RED));
return;
@ -244,7 +246,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
logger.info("{} has connected", player);
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
player.createConnectionRequest(toTry.get()).fireAndForget();
server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
}
@Override

Datei anzeigen

@ -4,16 +4,16 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
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.connection.MinecraftConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;

Datei anzeigen

@ -7,6 +7,7 @@ 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();

Datei anzeigen

@ -4,7 +4,9 @@ import com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.jline.reader.*;
import org.jline.reader.Candidate;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import java.util.List;
import java.util.Optional;

Datei anzeigen

@ -2,10 +2,10 @@ package com.velocitypowered.proxy.messages;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.messages.*;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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 java.util.Collection;
import java.util.Map;
@ -13,39 +13,20 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class VelocityChannelRegistrar implements ChannelRegistrar {
private static final Logger logger = LogManager.getLogger(VelocityChannelRegistrar.class);
private final Map<String, MessageHandler> handlers = new ConcurrentHashMap<>();
private final Map<String, ChannelIdentifier> identifierMap = new ConcurrentHashMap<>();
@Override
public void register(MessageHandler handler, ChannelIdentifier... identifiers) {
public void register(ChannelIdentifier... identifiers) {
for (ChannelIdentifier identifier : identifiers) {
Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier,
"identifier is unknown");
}
for (ChannelIdentifier identifier : identifiers) {
handlers.put(identifier.getId(), handler);
identifierMap.put(identifier.getId(), identifier);
}
}
public MessageHandler.ForwardStatus handlePluginMessage(ChannelMessageSource source, ChannelSide side, PluginMessage message) {
MessageHandler handler = handlers.get(message.getChannel());
ChannelIdentifier identifier = identifierMap.get(message.getChannel());
if (handler == null || identifier == null) {
return MessageHandler.ForwardStatus.FORWARD;
}
try {
return handler.handle(source, side, identifier, message.getData());
} catch (Exception e) {
logger.info("Unable to handle plugin message on channel {} for {}", message.getChannel(), source);
// In case of doubt, do not forward the message on.
return MessageHandler.ForwardStatus.HANDLED;
}
}
@Override
public void unregister(ChannelIdentifier... identifiers) {
for (ChannelIdentifier identifier : identifiers) {
@ -54,7 +35,6 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
}
for (ChannelIdentifier identifier : identifiers) {
handlers.remove(identifier.getId());
identifierMap.remove(identifier.getId());
}
}
@ -73,4 +53,8 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
public boolean registered(String id) {
return identifierMap.containsKey(id);
}
public ChannelIdentifier getFromId(String id) {
return identifierMap.get(id);
}
}

Datei anzeigen

@ -7,21 +7,11 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.netty.*;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.epoll.*;
import io.netty.channel.kqueue.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
@ -75,7 +65,7 @@ public final class ConnectionManager {
@Override
protected void initChannel(final Channel ch) {
ch.pipeline()
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(CLIENT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
@ -125,7 +115,9 @@ public final class ConnectionManager {
public Bootstrap createWorker() {
return new Bootstrap()
.channel(this.transportType.socketChannelClass)
.group(this.workerGroup);
.group(this.workerGroup)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, server.getConfiguration().getConnectTimeout());
}
public void shutdown() {

Datei anzeigen

@ -13,7 +13,4 @@ public interface Connections {
String MINECRAFT_DECODER = "minecraft-decoder";
String MINECRAFT_ENCODER = "minecraft-encoder";
String READ_TIMEOUT = "read-timeout";
int CLIENT_READ_TIMEOUT_SECONDS = 30; // client -> proxy
int SERVER_READ_TIMEOUT_SECONDS = 30; // proxy -> server
}

Datei anzeigen

@ -2,7 +2,7 @@ package com.velocitypowered.proxy.network.http;
import com.velocitypowered.proxy.VelocityServer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.Channel;
import io.netty.channel.pool.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext;

Datei anzeigen

@ -13,7 +13,10 @@ import com.velocitypowered.proxy.util.concurrency.RecordingThreadFactory;
import net.kyori.event.EventSubscriber;
import net.kyori.event.PostResult;
import net.kyori.event.SimpleEventBus;
import net.kyori.event.method.*;
import net.kyori.event.method.EventExecutor;
import net.kyori.event.method.MethodScanner;
import net.kyori.event.method.MethodSubscriptionAdapter;
import net.kyori.event.method.SimpleMethodSubscriptionAdapter;
import net.kyori.event.method.asm.ASMEventExecutorFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -21,8 +24,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
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);

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.plugin;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.proxy.VelocityServer;

Datei anzeigen

@ -2,7 +2,9 @@ package com.velocitypowered.proxy.plugin.loader;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.velocitypowered.api.plugin.*;
import com.velocitypowered.api.plugin.InvalidPluginException;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer;

Datei anzeigen

@ -8,7 +8,6 @@ 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 com.velocitypowered.proxy.VelocityServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Datei anzeigen

@ -8,7 +8,7 @@ import io.netty.util.collection.IntObjectMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.*;
import java.util.Objects;
import java.util.function.Supplier;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
@ -23,7 +23,7 @@ public enum StateRegistry {
},
STATUS {
{
SERVERBOUND.register(StatusRequest.class, StatusRequest::new,
SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
genericMappings(0x00));
SERVERBOUND.register(StatusPing.class, StatusPing::new,
genericMappings(0x01));

Datei anzeigen

@ -101,9 +101,6 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
queryResponse.writeByte(QUERY_TYPE_STAT);
queryResponse.writeInt(sessionId);
// Fetch information
Collection<Player> players = server.getAllPlayers();
// Start writing the response
ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0);
responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()));
@ -114,12 +111,14 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
responseWriter.write("plugins", "");
responseWriter.write("map", "Velocity");
responseWriter.write("numplayers", players.size());
responseWriter.write("numplayers", server.getPlayerCount());
responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers());
responseWriter.write("hostport", server.getConfiguration().getBind().getPort());
responseWriter.write("hostip", server.getConfiguration().getBind().getHostString());
responseWriter.writePlayers(players);
if (!responseWriter.isBasic) {
responseWriter.writePlayers(server.getAllPlayers());
}
break;
}
@ -132,6 +131,8 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
ctx.writeAndFlush(responsePacket);
} 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();
}
}

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

Datei anzeigen

@ -1,7 +1,10 @@
package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.*;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException;

Datei anzeigen

@ -4,7 +4,6 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List;

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -2,13 +2,13 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants.Direction;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
import io.netty.buffer.ByteBuf;
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 HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}");

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -1,10 +1,16 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
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() {
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
@ -14,4 +20,9 @@ public class StatusRequest implements MinecraftPacket {
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
}
@Override
public String toString() {
return "StatusRequest";
}
}

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.nio.charset.StandardCharsets;
@ -20,13 +19,20 @@ public enum PluginMessageUtil {
return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand");
}
public static boolean isMCRegister(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
return message.getChannel().equals("REGISTER") || message.getChannel().equals("minecraft:register");
}
public static boolean isMCUnregister(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
return message.getChannel().equals("UNREGISTER") || message.getChannel().equals("minecraft:unregister");
}
public static List<String> getChannels(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
Preconditions.checkArgument(message.getChannel().equals("REGISTER") ||
message.getChannel().equals("UNREGISTER") ||
message.getChannel().equals("minecraft:register") ||
message.getChannel().equals("minecraft:unregister"),
"Unknown channel type " + message.getChannel());
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"));
}

Datei anzeigen

@ -16,7 +16,6 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class VelocityScheduler implements Scheduler {
private final PluginManager pluginManager;
@ -66,13 +65,13 @@ public class VelocityScheduler implements Scheduler {
}
@Override
public TaskBuilder delay(int time, TimeUnit unit) {
public TaskBuilder delay(long time, TimeUnit unit) {
this.delay = unit.toMillis(time);
return this;
}
@Override
public TaskBuilder repeat(int time, TimeUnit unit) {
public TaskBuilder repeat(long time, TimeUnit unit) {
this.repeat = unit.toMillis(time);
return this;
}
@ -91,62 +90,9 @@ public class VelocityScheduler implements Scheduler {
@Override
public ScheduledTask schedule() {
if (delay == 0 && repeat == 0) {
// A special purpose, simplified implementation
VelocityImmediatelyScheduledTask task = new VelocityImmediatelyScheduledTask(plugin, runnable);
tasksByPlugin.put(plugin, task);
taskService.execute(task);
return task;
} else {
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
tasksByPlugin.put(plugin, task);
return task;
}
}
}
private class VelocityImmediatelyScheduledTask implements ScheduledTask, Runnable {
private final Object plugin;
private final Runnable runnable;
private final AtomicReference<TaskStatus> status;
private Thread taskThread;
private VelocityImmediatelyScheduledTask(Object plugin, Runnable runnable) {
this.plugin = plugin;
this.runnable = runnable;
this.status = new AtomicReference<>(TaskStatus.SCHEDULED);
}
@Override
public Object plugin() {
return plugin;
}
@Override
public TaskStatus status() {
return status.get();
}
@Override
public void cancel() {
if (status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.CANCELLED)) {
if (taskThread != null) {
taskThread.interrupt();
}
}
}
@Override
public void run() {
taskThread = Thread.currentThread();
try {
runnable.run();
} catch (Exception e) {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
}
status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.FINISHED);
taskThread = null;
tasksByPlugin.remove(plugin, this);
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
tasksByPlugin.put(plugin, task);
return task;
}
}
@ -154,6 +100,7 @@ public class VelocityScheduler implements Scheduler {
private final Object plugin;
private final Runnable runnable;
private ScheduledFuture<?> future;
private volatile Thread currentTaskThread;
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
this.plugin = plugin;
@ -191,6 +138,12 @@ public class VelocityScheduler implements Scheduler {
public void cancel() {
if (future != null) {
future.cancel(false);
Thread cur = currentTaskThread;
if (cur != null) {
cur.interrupt();
}
onFinish();
}
}
@ -198,6 +151,7 @@ public class VelocityScheduler implements Scheduler {
@Override
public void run() {
taskService.execute(() -> {
currentTaskThread = Thread.currentThread();
try {
runnable.run();
} catch (Exception e) {
@ -208,6 +162,7 @@ public class VelocityScheduler implements Scheduler {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
}
}
currentTaskThread = null;
});
}

Datei anzeigen

@ -0,0 +1,49 @@
package com.velocitypowered.proxy.server;
import com.google.common.base.Preconditions;
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 java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ServerMap {
private final VelocityServer server;
private final Map<String, RegisteredServer> servers = new ConcurrentHashMap<>();
public ServerMap(VelocityServer server) {
this.server = server;
}
public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "server");
String lowerName = name.toLowerCase(Locale.US);
return Optional.ofNullable(servers.get(lowerName));
}
public Collection<RegisteredServer> 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);
Preconditions.checkArgument(servers.putIfAbsent(lowerName, rs) == null, "Server with name %s already registered", serverInfo.getName());
return rs;
}
public void unregister(ServerInfo serverInfo) {
Preconditions.checkNotNull(serverInfo, "serverInfo");
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
RegisteredServer rs = servers.get(lowerName);
Preconditions.checkArgument(rs != null, "Server with name %s is not registered!", serverInfo.getName());
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());
}
}

Datei anzeigen

@ -0,0 +1,112 @@
package com.velocitypowered.proxy.server;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.server.ping.PingSessionHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.timeout.ReadTimeoutHandler;
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.*;
public class VelocityRegisteredServer implements RegisteredServer {
private final VelocityServer server;
private final ServerInfo serverInfo;
private final Set<ConnectedPlayer> players = ConcurrentHashMap.newKeySet();
public VelocityRegisteredServer(VelocityServer server, ServerInfo serverInfo) {
this.server = server;
this.serverInfo = serverInfo;
}
@Override
public ServerInfo getServerInfo() {
return serverInfo;
}
@Override
public Collection<Player> getPlayersConnected() {
return ImmutableList.copyOf(players);
}
@Override
public CompletableFuture<ServerPing> ping() {
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.initializeGenericBootstrap()
.handler(new ChannelInitializer<Channel>() {
@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) {
if (player.getConnectedServer() != null && player.getConnectedServer().getServerInfo().equals(serverInfo)) {
ServerConnection connection = player.getConnectedServer();
return connection.sendPluginMessage(identifier, data);
}
}
return false;
}
@Override
public String toString() {
return "registered server: " + serverInfo;
}
}

Datei anzeigen

@ -0,0 +1,68 @@
package com.velocitypowered.proxy.server.ping;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
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.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<ServerPing> result;
private final RegisteredServer server;
private final MinecraftConnection connection;
private boolean completed = false;
public PingSessionHandler(CompletableFuture<ServerPing> 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 void handle(MinecraftPacket packet) {
Preconditions.checkState(packet instanceof StatusResponse, "Did not get status response back from connection");
// All good!
completed = true;
connection.close();
ServerPing ping = VelocityServer.GSON.fromJson(((StatusResponse) packet).getStatus(), ServerPing.class);
result.complete(ping);
}
@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);
}
}

Datei anzeigen

@ -1,56 +0,0 @@
package com.velocitypowered.proxy.util;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.server.ServerInfo;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ServerMap {
private final Map<String, ServerInfo> servers = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public Optional<ServerInfo> getServer(String server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.toLowerCase(Locale.US);
lock.readLock().lock();
try {
return Optional.ofNullable(servers.get(lowerName));
} finally {
lock.readLock().unlock();
}
}
public Collection<ServerInfo> getAllServers() {
lock.readLock().lock();
try {
return ImmutableList.copyOf(servers.values());
} finally {
lock.readLock().unlock();
}
}
public void register(ServerInfo server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.getName().toLowerCase(Locale.US);
lock.writeLock().lock();
try {
Preconditions.checkArgument(servers.putIfAbsent(lowerName, server) == null, "Server with name %s already registered", server.getName());
} finally {
lock.writeLock().unlock();
}
}
public void unregister(ServerInfo server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.getName().toLowerCase(Locale.US);
lock.writeLock().lock();
try {
Preconditions.checkArgument(servers.remove(lowerName, server), "Server with this name is not registered!");
} finally {
lock.writeLock().unlock();
}
}
}

Datei anzeigen

@ -6,7 +6,6 @@ import com.google.common.collect.MapMaker;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
/**

Datei anzeigen

@ -3,9 +3,7 @@ package com.velocitypowered.proxy.protocol;
import com.velocitypowered.proxy.protocol.packet.Handshake;
import org.junit.jupiter.api.Test;
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.*;
import static org.junit.jupiter.api.Assertions.*;
class PacketRegistryTest {

Datei anzeigen

@ -9,7 +9,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
class VelocitySchedulerTest {
// TODO: The timings here will be inaccurate on slow systems. Need to find a testing-friendly replacement for Thread.sleep()

Datei anzeigen

@ -7,7 +7,8 @@ import java.net.InetAddress;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RatelimiterTest {

Datei anzeigen

@ -1,31 +1,34 @@
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.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ServerMapTest {
private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565);
@Test
void respectsCaseInsensitivity() {
ServerMap map = new ServerMap();
ServerMap map = new ServerMap(null);
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
map.register(info);
RegisteredServer connection = map.register(info);
assertEquals(Optional.of(info), map.getServer("TestServer"));
assertEquals(Optional.of(info), map.getServer("testserver"));
assertEquals(Optional.of(info), map.getServer("TESTSERVER"));
assertEquals(Optional.of(connection), map.getServer("TestServer"));
assertEquals(Optional.of(connection), map.getServer("testserver"));
assertEquals(Optional.of(connection), map.getServer("TESTSERVER"));
}
@Test
void rejectsRepeatedRegisterAttempts() {
ServerMap map = new ServerMap();
ServerMap map = new ServerMap(null);
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
map.register(info);