Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: undefined behaviour when uuid on proxy and server don't match #388

Merged
merged 1 commit into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,54 @@
import com.rexcantor64.triton.language.item.LanguageText;
import com.rexcantor64.triton.player.LanguagePlayer;
import com.rexcantor64.triton.storage.LocalStorage;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;
import lombok.var;

import java.util.ArrayList;
import java.util.List;

/**
* Actions:
* 0 -> send storage and config
* 1 -> send player language
* 2 -> send command to be ran as console
* 3 -> tell server to re-fetch translations from database
* 4 -> forward Triton command as player
*/
public class BridgeSerializer {

/**
* Actions from proxy to server
*/
@RequiredArgsConstructor
@Getter
public enum ActionP2S {
/** Send storage and config **/
SEND_STORAGE_AND_CONFIG(0),
/** Send a player's language **/
SEND_PLAYER_LANGUAGE(1),
/** Send command to be run as console **/
SEND_COMMAND_AS_CONSOLE(2),
/** Signal server to re-fetch translations from database **/
SIGNAL_REFRESH_FROM_DB(3),
/** Forward a Triton command to be run as the player themselves **/
FORWARD_TRITON_COMMAND(4),
;

private final int key;
}

/**
* Actions from server to proxy
*/
@RequiredArgsConstructor
@Getter
public enum ActionS2P {
/** Update player's language **/
UPDATE_PLAYER_LANGUAGE(0),
/** Add or remove a sign (location) from a sign group **/
UPDATE_SIGN_GROUP_MEMBERSHIP(1),
;

private final int key;
}

public static byte[] getLanguageDataOutput() {
val languageOut = ByteStreams.newDataOutput();
// Action 0 (send config)
languageOut.writeUTF(Triton.get().getLanguageManager().getMainLanguage().getName());
val languageList = Triton.get().getLanguageManager().getAllLanguages();
languageOut.writeShort(languageList.size());
Expand All @@ -53,8 +81,7 @@ public static List<byte[]> buildTranslationData(String serverName, @NonNull byte
// If not using local storage, each server should fetch the translations for themselves
if (!(Triton.get().getStorage() instanceof LocalStorage)) {
val refreshSignalOut = ByteStreams.newDataOutput();
// Action 3 (tell server to re-fetch from database storage)
refreshSignalOut.writeByte(3);
refreshSignalOut.writeByte(ActionP2S.SIGNAL_REFRESH_FROM_DB.getKey());
outList.add(refreshSignalOut.toByteArray());
return outList;
}
Expand All @@ -67,7 +94,7 @@ public static List<byte[]> buildTranslationData(String serverName, @NonNull byte
for (val item : collection.getItems()) {
if (languageItemsOut.toByteArray().length > 29000) {
val out = ByteStreams.newDataOutput();
out.writeByte(0);
out.writeByte(ActionP2S.SEND_STORAGE_AND_CONFIG.getKey());
if (outList.size() == 0) {
out.writeBoolean(true);
out.write(languageOut);
Expand Down Expand Up @@ -144,7 +171,7 @@ public static List<byte[]> buildTranslationData(String serverName, @NonNull byte
size++;
}
val out = ByteStreams.newDataOutput();
out.writeByte(0);
out.writeByte(ActionP2S.SEND_STORAGE_AND_CONFIG.getKey());
val firstSend = outList.size() == 0;
out.writeBoolean(firstSend);
if (firstSend)
Expand All @@ -162,8 +189,7 @@ public static List<byte[]> buildTranslationData(String serverName, @NonNull byte

public static byte[] buildPlayerLanguageData(LanguagePlayer lp) {
val out = ByteStreams.newDataOutput();
// Action 1
out.writeByte(1);
out.writeByte(ActionP2S.SEND_PLAYER_LANGUAGE.getKey());
val uuid = lp.getUUID();
out.writeLong(uuid.getMostSignificantBits());
out.writeLong(uuid.getLeastSignificantBits());
Expand All @@ -173,16 +199,14 @@ public static byte[] buildPlayerLanguageData(LanguagePlayer lp) {

public static byte[] buildExecutableCommandData(String command) {
val out = ByteStreams.newDataOutput();
// Action 2
out.writeByte(2);
out.writeByte(ActionP2S.SEND_COMMAND_AS_CONSOLE.getKey());
out.writeUTF(command);
return out.toByteArray();
}

public static byte[] buildForwardCommandData(CommandEvent commandEvent) {
val out = ByteStreams.newDataOutput();
// Action 4
out.writeByte(4);
out.writeByte(ActionP2S.FORWARD_TRITON_COMMAND.getKey());

val uuid = commandEvent.getSender().getUUID();
out.writeLong(uuid.getMostSignificantBits());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void onPluginMessage(PluginMessageEvent e) {
val action = in.readByte();

// Player changes language
if (action == 0) {
if (action == BridgeSerializer.ActionS2P.UPDATE_PLAYER_LANGUAGE.getKey()) {
val uuid = UUID.fromString(in.readUTF());
val language = in.readUTF();

Expand All @@ -50,7 +50,7 @@ public void onPluginMessage(PluginMessageEvent e) {
}

// Add or remove a location from a sign group using /triton sign
if (action == 1) {
if (action == BridgeSerializer.ActionS2P.UPDATE_SIGN_GROUP_MEMBERSHIP.getKey()) {
val server = ((Server) e.getSender()).getInfo();
SignLocation location = new SignLocation(server.getName(), in.readUTF(), in.readInt(), in.readInt(), in
.readInt());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import com.rexcantor64.triton.player.SpigotLanguagePlayer;
import com.rexcantor64.triton.storage.LocalStorage;
import lombok.val;
import lombok.var;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
Expand Down Expand Up @@ -47,7 +46,7 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla
val in = new DataInputStream(new ByteArrayInputStream(bytes));
try {
val action = in.readByte();
if (action == 0) {
if (action == BridgeSerializer.ActionP2S.SEND_STORAGE_AND_CONFIG.getKey()) {
if (!(Triton.get().getStorage() instanceof LocalStorage)) {
Triton.get().getLogger()
.logWarning("You're using BungeeCord with a local storage option, but this server is " +
Expand Down Expand Up @@ -163,15 +162,18 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla
Bukkit.getScheduler().runTaskLater(Triton.asSpigot().getLoader(), () -> Triton.get()
.refreshPlayers(), 10L);
}
} else if (action == 1) {
} else if (action == BridgeSerializer.ActionP2S.SEND_PLAYER_LANGUAGE.getKey()) {
val uuid = new UUID(in.readLong(), in.readLong());

val lang = Triton.get().getLanguageManager().getLanguageByName(in.readUTF(), true);
val languagePlayer = (SpigotLanguagePlayer) Triton.asSpigot().getPlayerManager().get(player.getUniqueId());
languagePlayer.setProxyUniqueId(uuid);
Bukkit.getScheduler().runTaskLater(Triton.asSpigot().getLoader(),
() -> ((SpigotLanguagePlayer) Triton.get().getPlayerManager().get(uuid)).setLang(lang, false)
, 10L);
} else if (action == 2) {
() -> languagePlayer.setLang(lang, false),
10L);
} else if (action == BridgeSerializer.ActionP2S.SEND_COMMAND_AS_CONSOLE.getKey()) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), in.readUTF());
} else if (action == 3) {
} else if (action == BridgeSerializer.ActionP2S.SIGNAL_REFRESH_FROM_DB.getKey()) {
val storage = Triton.get().getStorage();
if (storage instanceof LocalStorage) {
Triton.get().getLogger()
Expand All @@ -190,19 +192,19 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla
Bukkit.getScheduler().runTaskLater(Triton.asSpigot().getLoader(), () -> Triton.get()
.refreshPlayers(), 10L);
});
} else if (action == 4) {
val uuid = new UUID(in.readLong(), in.readLong());
val p = Bukkit.getPlayer(uuid);
} else if (action == BridgeSerializer.ActionP2S.FORWARD_TRITON_COMMAND.getKey()) {
@Deprecated
val uuid = new UUID(in.readLong(), in.readLong()); // TODO remove in v4

val subCommand = in.readBoolean() ? in.readUTF() : null;
val args = new String[in.readShort()];
for (int i = 0; i < args.length; ++i)
args[i] = in.readUTF();

Triton.get().getLogger().logTrace("Received forwarded command '%1' with args %2 for player %3",
subCommand, Arrays.toString(args), uuid);
subCommand, Arrays.toString(args), player.getUniqueId());

val commandEvent = new CommandEvent(new SpigotSender(p), subCommand, args, "triton",
val commandEvent = new CommandEvent(new SpigotSender(player), subCommand, args, "triton",
CommandEvent.Environment.SPIGOT);
Triton.asSpigot().getCommandHandler().handleCommand(commandEvent);
}
Expand All @@ -213,9 +215,8 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla

public void updatePlayerLanguage(SpigotLanguagePlayer lp) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
// Action (0): updatePlayerLanguage
out.writeByte(0);
out.writeUTF(lp.getUUID().toString());
out.writeByte(BridgeSerializer.ActionS2P.UPDATE_PLAYER_LANGUAGE.getKey());
out.writeUTF(lp.getProxyUniqueId().toString());
out.writeUTF(lp.getLang().getName());
lp.toBukkit().ifPresent(player ->
player.sendPluginMessage(Triton.asSpigot().getLoader(), "triton:main", out.toByteArray())
Expand All @@ -224,8 +225,7 @@ public void updatePlayerLanguage(SpigotLanguagePlayer lp) {

public void updateSign(String world, int x, int y, int z, String key, Player p) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
// Action (1): sign management
out.writeByte(1);
out.writeByte(BridgeSerializer.ActionS2P.UPDATE_SIGN_GROUP_MEMBERSHIP.getKey());
out.writeUTF(world);
out.writeInt(x);
out.writeInt(y);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,9 @@ public void onPluginMessage(PluginMessageEvent e) {
}

public void sendPlayerLanguage(@NonNull VelocityLanguagePlayer lp) {
lp.getParent().getCurrentServer().ifPresent(server -> sendPlayerLanguage(lp, server.getServer()));
}

public void sendPlayerLanguage(@NonNull VelocityLanguagePlayer lp, @NonNull RegisteredServer server) {
Triton.get().getLogger().logTrace("Sending player %1 language to server %2", lp, server.getServerInfo().getName());
Triton.get().getLogger().logTrace("Sending player %1 language to server", lp);
val out = BridgeSerializer.buildPlayerLanguageData(lp);
sendPluginMessage(server, out);
sendPluginMessage(lp.getParent(), out);
}

public void sendExecutableCommand(String command, @NonNull RegisteredServer server) {
Expand All @@ -100,7 +96,6 @@ public void sendConfigToEveryone() {
Triton.get().getLogger()
.logError(e, "Failed to send config and language items to other servers! Not everything might work " +
"as expected");
e.printStackTrace();
}
}

Expand All @@ -122,8 +117,7 @@ public void forwardCommand(CommandEvent commandEvent) {
val out = BridgeSerializer.buildForwardCommandData(commandEvent);

Triton.asVelocity().getLoader().getServer().getPlayer(commandEvent.getSender().getUUID())
.flatMap(Player::getCurrentServer)
.ifPresent(serverConnection -> sendPluginMessage(serverConnection.getServer(), out));
.ifPresent(player -> sendPluginMessage(player, out));
}

private void sendPluginMessage(@NonNull RegisteredServer info, byte[] data) {
Expand All @@ -132,6 +126,14 @@ private void sendPluginMessage(@NonNull RegisteredServer info, byte[] data) {
}
}

private void sendPluginMessage(@NonNull Player player, byte[] data) {
player.getCurrentServer().ifPresent(serverConnection -> {
if (!serverConnection.sendPluginMessage(Triton.asVelocity().getBridgeChannelIdentifier(), data)) {
Triton.get().getLogger().logError("Failed to send plugin message to player %1 (%2)", player.getUsername(), player.getUniqueId());
}
});
}

public void executeQueue(@NonNull RegisteredServer server) {
byte[] data;
val queue = this.queue.get(server);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,17 @@
public class VelocityListener {

@Subscribe
public void onServerConnect(ServerConnectedEvent e) {
val lp = (VelocityLanguagePlayer) Triton.get().getPlayerManager().get(e.getPlayer().getUniqueId());

Triton.asVelocity().getBridgeManager().sendPlayerLanguage(lp, e.getServer());
public void afterServerConnect(ServerPostConnectEvent e) {
e.getPlayer().getCurrentServer().ifPresent(serverConnection -> {
Triton.asVelocity().getBridgeManager().executeQueue(serverConnection.getServer());

if (Triton.get().getConf().isRunLanguageCommandsOnLogin())
lp.executeCommands(e.getServer());
}
val lp = (VelocityLanguagePlayer) Triton.get().getPlayerManager().get(e.getPlayer().getUniqueId());
Triton.asVelocity().getBridgeManager().sendPlayerLanguage(lp);

@Subscribe
public void afterServerConnect(ServerPostConnectEvent e) {
if (e.getPlayer().getCurrentServer().isPresent())
Triton.asVelocity().getBridgeManager().executeQueue(e.getPlayer().getCurrentServer().get().getServer());
if (Triton.get().getConf().isRunLanguageCommandsOnLogin()) {
lp.executeCommands(serverConnection.getServer());
}
});
}

@Subscribe(order = PostOrder.FIRST)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.rexcantor64.triton.api.language.Language;

import java.util.UUID;

public interface LanguagePlayer extends com.rexcantor64.triton.api.players.LanguagePlayer {

boolean isWaitingForClientLocale();
Expand All @@ -12,4 +14,11 @@ public interface LanguagePlayer extends com.rexcantor64.triton.api.players.Langu
default Language getLanguage() {
return this.getLang();
}

/**
* @return the UUID that is to be used for storage-related operations
*/
default UUID getStorageUniqueId() {
return this.getUUID();
}
}
Loading
Loading