diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/AziPluginMessaging.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/AziPluginMessaging.java index d5d5a70..37b2cca 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/AziPluginMessaging.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/AziPluginMessaging.java @@ -1,10 +1,12 @@ package net.azisaba.azipluginmessaging.api; +import net.azisaba.azipluginmessaging.api.entity.Player; import net.azisaba.azipluginmessaging.api.entity.PlayerAdapter; import net.azisaba.azipluginmessaging.api.server.PacketSender; import org.jetbrains.annotations.NotNull; import java.util.Optional; +import java.util.UUID; public interface AziPluginMessaging { /** @@ -30,6 +32,9 @@ public interface AziPluginMessaging { @NotNull Server getServer(); + @NotNull + Optional getPlayer(@NotNull UUID uuid); + /** * Returns the player adapter for class. *

Generally, class should be one of these (depending on the environment): @@ -44,15 +49,6 @@ public interface AziPluginMessaging { PlayerAdapter getPlayerAdapter(@NotNull Class clazz); interface Proxy { - /** - * Returns the packet sender instance for provided server. - * @param serverName the server name - * @return the packet sender instance - */ - @NotNull - default Optional getPacketSenderForServer(@NotNull String serverName) { - throw new UnsupportedOperationException("Unsupported in current environment."); - } } interface Server { diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/Logger.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/Logger.java index 82792a7..740fdd4 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/Logger.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/Logger.java @@ -49,7 +49,7 @@ public interface Logger { private String format(@NotNull String msg) { int length = 0; while (msg.contains("{}")) { - msg = msg.replace("{}", "{" + length++ + "}"); + msg = msg.replaceFirst("\\{}", "{" + length++ + "}"); } return msg; } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/entity/Player.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/entity/Player.java index 7f1d204..06ecbb7 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/entity/Player.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/entity/Player.java @@ -22,6 +22,12 @@ public interface Player { @NotNull UUID getUniqueId(); + /** + * Sends a message to the player. This method might do nothing depending on the implementation. + * @param message the message + */ + void sendMessage(@NotNull String message); + /** * Returns the username if present, and fallbacks to unique id. * @return username or unique id diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/entity/SimplePlayer.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/entity/SimplePlayer.java index 5552c1f..4c0dbe2 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/entity/SimplePlayer.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/entity/SimplePlayer.java @@ -1,5 +1,6 @@ package net.azisaba.azipluginmessaging.api.entity; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,6 +32,16 @@ public SimplePlayer(@NotNull UUID uuid, @Nullable String username) { return username; } + /** + * no-op method. + * @param message the message + */ + @Override + public void sendMessage(@NotNull String message) { + // no-op + } + + @Contract("_ -> new") @NotNull public static SimplePlayer read(@NotNull DataInputStream in) throws IOException { UUID uuid = UUID.fromString(in.readUTF()); diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/PacketFlow.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/PacketFlow.java new file mode 100644 index 0000000..936f8e6 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/PacketFlow.java @@ -0,0 +1,6 @@ +package net.azisaba.azipluginmessaging.api.protocol; + +public enum PacketFlow { + TO_PROXY, + TO_SERVER, +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java index ff204c1..1ee4b52 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java @@ -1,18 +1,29 @@ package net.azisaba.azipluginmessaging.api.protocol; import net.azisaba.azipluginmessaging.api.Logger; -import net.azisaba.azipluginmessaging.api.protocol.handler.GiveGamingSaraHandler; import net.azisaba.azipluginmessaging.api.protocol.handler.MessageHandler; -import net.azisaba.azipluginmessaging.api.protocol.handler.SetRankHandler; -import net.azisaba.azipluginmessaging.api.protocol.handler.ToggleGamingSaraHandler; -import net.azisaba.azipluginmessaging.api.protocol.handler.ToggleSaraHideHandler; -import net.azisaba.azipluginmessaging.api.protocol.handler.ToggleSaraShowHandler; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyMessageHandler; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundEncryptionPacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundGiveGamingSaraPacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundGiveSaraPacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundSetRankPacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundToggleGamingSaraPacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundToggleSaraHidePacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundToggleSaraShowPacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ServerMessageHandler; +import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundActionResponsePacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundEncryptionPacket; import net.azisaba.azipluginmessaging.api.protocol.message.Message; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerMessage; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerWithServerMessage; -import net.azisaba.azipluginmessaging.api.protocol.message.SetRankMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundGiveSaraMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundSetRankMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.PublicKeyMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundActionResponseMessage; import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.api.server.ServerConnection; +import net.azisaba.azipluginmessaging.api.util.Constants; +import net.azisaba.azipluginmessaging.api.util.EncryptionUtil; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,26 +38,47 @@ import java.util.concurrent.ConcurrentHashMap; public final class Protocol, M extends Message> { - private static final Map> BY_ID = new ConcurrentHashMap<>(); + private static final Map> TO_PROXY_BY_ID = new ConcurrentHashMap<>(); + private static final Map> TO_SERVER_BY_ID = new ConcurrentHashMap<>(); public static final String LEGACY_CHANNEL_ID = "AziPluginMessaging"; public static final String CHANNEL_ID = "azipm:main"; - public static final Protocol SET_RANK = new Protocol<>(0x00, new SetRankHandler()); - public static final Protocol GIVE_GAMING_SARA = new Protocol<>(0x01, new GiveGamingSaraHandler()); - public static final Protocol TOGGLE_GAMING_SARA = new Protocol<>(0x02, new ToggleGamingSaraHandler()); - public static final Protocol TOGGLE_SARA_HIDE = new Protocol<>(0x03, new ToggleSaraHideHandler()); // Note that this is non-contextual - public static final Protocol TOGGLE_SARA_SHOW = new Protocol<>(0x04, new ToggleSaraShowHandler()); // Note that this is contextual + public static final Protocol P_ENCRYPTION = new Protocol<>(PacketFlow.TO_PROXY, 0x00, new ProxyboundEncryptionPacket()); + public static final Protocol P_SET_RANK = new Protocol<>(PacketFlow.TO_PROXY, 0x01, new ProxyboundSetRankPacket()); + public static final Protocol P_GIVE_GAMING_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x02, new ProxyboundGiveGamingSaraPacket()); + public static final Protocol P_GIVE_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x03, new ProxyboundGiveSaraPacket()); + public static final Protocol P_TOGGLE_GAMING_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x04, new ProxyboundToggleGamingSaraPacket()); + public static final Protocol P_TOGGLE_SARA_HIDE = new Protocol<>(PacketFlow.TO_PROXY, 0x05, new ProxyboundToggleSaraHidePacket()); // Note that this is non-contextual + public static final Protocol P_TOGGLE_SARA_SHOW = new Protocol<>(PacketFlow.TO_PROXY, 0x06, new ProxyboundToggleSaraShowPacket()); // Note that this is contextual + public static final Protocol S_ENCRYPTION = new Protocol<>(PacketFlow.TO_SERVER, 0x00, new ServerboundEncryptionPacket()); + public static final Protocol S_ACTION_RESPONSE = new Protocol<>(PacketFlow.TO_SERVER, 0x01, new ServerboundActionResponsePacket()); + + private final PacketFlow packetFlow; private final byte id; private final T handler; - private Protocol(int id, @NotNull T handler) { + private Protocol(@NotNull PacketFlow packetFlow, int id, @NotNull T handler) { + this.packetFlow = packetFlow; this.id = (byte) (id & 0xFF); this.handler = handler; - if (BY_ID.containsKey(this.id)) { - throw new AssertionError("Duplicate protocol id: " + this.id); + if (packetFlow == PacketFlow.TO_PROXY) { + if (!(handler instanceof ProxyMessageHandler)) { + throw new IllegalArgumentException("Handler must be instance of ProxyMessageHandler"); + } + if (TO_PROXY_BY_ID.containsKey(this.id)) { + throw new AssertionError("Duplicate protocol id: " + this.id); + } + TO_PROXY_BY_ID.put(this.id, this); + } else { + if (!(handler instanceof ServerMessageHandler)) { + throw new IllegalArgumentException("Handler must be instance of ServerMessageHandler"); + } + if (TO_SERVER_BY_ID.containsKey(this.id)) { + throw new AssertionError("Duplicate protocol id: " + this.id); + } + TO_SERVER_BY_ID.put(this.id, this); } - BY_ID.put(this.id, this); } /** @@ -68,17 +100,29 @@ public T getHandler() { /** * Attempt to send a packet. - * @param packetSender the packet sender to send the packet from. + * @param sender the packet sender to send the packet from. * @param msg the message to send * @return true if the message was sent successfully, false otherwise. */ - public boolean sendPacket(@NotNull PacketSender packetSender, @NotNull M msg) { + public boolean sendPacket(@NotNull PacketSender sender, @NotNull M msg) { try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) { out.writeByte(id); msg.write(out); byte[] bytes = bout.toByteArray(); - return packetSender.sendPacket(bytes); + if (sender.isEncrypted()) { + try { + bytes = EncryptionUtil.encrypt(bytes, sender.getRemotePublicKey()); + } catch (Exception e) { + throw new RuntimeException("Could not encrypt the packet (sender: " + sender + ")", e); + } + } + if (Constants.DEBUG) { + String hex = Integer.toString(id, 16); + if (hex.length() == 1) hex = '0' + hex; + Logger.getCurrentLogger().info("Sending packet {} (0x{}) to {} (encrypted: {})", id, hex, sender, sender.isEncrypted()); + } + return sender.sendPacket(bytes); } catch (IOException e) { Logger.getCurrentLogger().warn("Failed to send packet", e); return false; @@ -88,27 +132,41 @@ public boolean sendPacket(@NotNull PacketSender packetSender, @NotNull M msg) { /** * This method is called when a packet is received (proxy-side). * @param server the server connection - * @param data the data of the packet + * @param rawData the data of the packet */ - public static void handleProxySide(ServerConnection server, byte[] data) { + public static void handleProxySide(ServerConnection server, byte[] rawData) { + byte[] data; + if (server.isEncrypted()) { + try { + data = EncryptionUtil.decrypt(rawData, server.getKeyPair().getPrivate()); + } catch (Exception e) { + throw new RuntimeException("Could not decrypt the packet (server: " + server + ")", e); + } + } else { + data = rawData; + } try (ByteArrayInputStream bin = new ByteArrayInputStream(data); DataInputStream in = new DataInputStream(bin)) { byte id = (byte) (in.readByte() & 0xFF); - Protocol protocol = Protocol.getById(id); + Protocol protocol = Protocol.getById(PacketFlow.TO_PROXY, id); if (protocol == null) { Logger.getCurrentLogger().warn( "Received unknown protocol id from server connection {}: {}", server, id); return; } - String hex = Integer.toString(id, 16); - if (hex.length() == 1) hex = '0' + hex; - Logger.getCurrentLogger().info("Received packet {} (0x{}) from server connection {}", - id, hex, server); + if (Constants.DEBUG) { + String hex = Integer.toString(id, 16); + if (hex.length() == 1) hex = '0' + hex; + Logger.getCurrentLogger().info("Received packet {} (0x{}) from server connection {} (encrypted: {})", id, hex, server, server.isEncrypted()); + } + if (protocol.packetFlow != PacketFlow.TO_PROXY) { + throw new IllegalArgumentException("Packet " + protocol + " is not proxybound"); + } @SuppressWarnings("unchecked") - MessageHandler handler = (MessageHandler) protocol.getHandler(); + ProxyMessageHandler handler = (ProxyMessageHandler) protocol.getHandler(); Message message = handler.read(server, in); - handler.handle(message); + handler.handle(server, message); } catch (Exception | AssertionError e) { Logger.getCurrentLogger().warn( "Failed to handle plugin message from server connection {} (player: {})", @@ -116,14 +174,64 @@ public static void handleProxySide(ServerConnection server, byte[] data) { } } + /** + * This method is called when a packet is received (server-side). + * @param rawData the data of the packet + */ + public static void handleServerSide(@NotNull PacketSender sender, byte[] rawData) { + byte[] data; + if (sender.isEncrypted()) { + try { + data = EncryptionUtil.decrypt(rawData, sender.getKeyPair().getPrivate()); + } catch (Exception e) { + throw new RuntimeException("Could not decrypt the packet (sender: " + sender + ")", e); + } + } else { + data = rawData; + } + try (ByteArrayInputStream bin = new ByteArrayInputStream(data); + DataInputStream in = new DataInputStream(bin)) { + byte id = (byte) (in.readByte() & 0xFF); + Protocol protocol = Protocol.getById(PacketFlow.TO_SERVER, id); + if (protocol == null) { + Logger.getCurrentLogger().warn("Received unknown protocol id from {}: {}", sender, id); + return; + } + if (Constants.DEBUG) { + String hex = Integer.toString(id, 16); + if (hex.length() == 1) hex = '0' + hex; + Logger.getCurrentLogger().info("Received packet {} (0x{}) from {}", id, hex, sender); + } + if (protocol.packetFlow != PacketFlow.TO_SERVER) { + throw new IllegalArgumentException("Packet " + protocol + " is not serverbound"); + } + @SuppressWarnings("unchecked") + ServerMessageHandler handler = (ServerMessageHandler) protocol.getHandler(); + Message message = handler.read(in); + handler.handle(sender, message); + } catch (Exception | AssertionError e) { + Logger.getCurrentLogger().warn("Failed to handle plugin message from proxy", e); + } + } + + @NotNull + @Contract(pure = true) + public PacketFlow getPacketFlow() { + return packetFlow; + } + /** * Gets the protocol (packet) by its id. * @param id the id * @return the protocol, or null if not found */ @Nullable - public static Protocol getById(byte id) { - return BY_ID.get(id); + public static Protocol getById(@NotNull PacketFlow packetFlow, byte id) { + if (packetFlow == PacketFlow.TO_PROXY) { + return TO_PROXY_BY_ID.get(id); + } else { + return TO_SERVER_BY_ID.get(id); + } } /** @@ -132,8 +240,19 @@ public static void handleProxySide(ServerConnection server, byte[] data) { */ @Contract(pure = true) @NotNull - public static Collection> values() { - return BY_ID.values(); + public static Collection> values(@NotNull PacketFlow packetFlow) { + if (packetFlow == PacketFlow.TO_PROXY) { + return TO_PROXY_BY_ID.values(); + } else { + return TO_SERVER_BY_ID.values(); + } } -} + @Override + public String toString() { + return "Protocol{" + + "id=" + id + + ", handler=" + handler + + '}'; + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/MessageHandler.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/MessageHandler.java index 708f067..c41d58b 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/MessageHandler.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/MessageHandler.java @@ -1,31 +1,16 @@ package net.azisaba.azipluginmessaging.api.protocol.handler; import net.azisaba.azipluginmessaging.api.protocol.message.Message; -import net.azisaba.azipluginmessaging.api.server.ServerConnection; +import net.azisaba.azipluginmessaging.api.server.PacketSender; import org.jetbrains.annotations.NotNull; -import java.io.DataInputStream; -import java.io.IOException; - -/** - * Represents a message handler which is responsible for decoding and handling the message. - * @param the message type - */ public interface MessageHandler { - /** - * Decodes the message from the stream. - * @param server The server connection. - * @param in The stream. - * @return The decoded message. - * @throws IOException If an I/O error occurs. - */ - @NotNull - T read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException; - /** * Handles the (decoded) message. - * @param msg The message. + * + * @param sender The sender of the message. + * @param msg The message. * @throws Exception If an exception occurs. */ - void handle(@NotNull T msg) throws Exception; + void handle(@NotNull PacketSender sender, @NotNull T msg) throws Exception; } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyMessageHandler.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyMessageHandler.java new file mode 100644 index 0000000..6522caa --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyMessageHandler.java @@ -0,0 +1,25 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.protocol.message.Message; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import net.azisaba.azipluginmessaging.api.server.ServerConnection; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.IOException; + +/** + * Represents a message handler which is responsible for decoding and handling the message. + * @param the message type + */ +public interface ProxyMessageHandler extends MessageHandler { + /** + * Decodes the message from the stream. + * @param server The server connection. + * @param in The stream. + * @return The decoded message. + * @throws IOException If an I/O error occurs. + */ + @NotNull + T read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException; +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionPacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionPacket.java new file mode 100644 index 0000000..8a8e01c --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionPacket.java @@ -0,0 +1,46 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.Logger; +import net.azisaba.azipluginmessaging.api.protocol.Protocol; +import net.azisaba.azipluginmessaging.api.protocol.message.PublicKeyMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import net.azisaba.azipluginmessaging.api.server.ServerConnection; +import net.azisaba.azipluginmessaging.api.util.Constants; +import net.azisaba.azipluginmessaging.api.util.EncryptionUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; + +public class ProxyboundEncryptionPacket implements ProxyMessageHandler { + @Override + public @NotNull PublicKeyMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) { + try { + return PublicKeyMessage.read(in); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void handle(@NotNull PacketSender sender, @NotNull PublicKeyMessage msg) throws Exception { + // Set public key of the server for encryption (note that this does not set the encrypted flag) + sender.setRemotePublicKey(msg.getPublicKey()); + + if (sender instanceof ServerConnection) { + // generate keypair before sending to the server + ((ServerConnection) sender).setKeyPair(EncryptionUtil.generateKeyPair(2048)); + } + + // Send our public key to the server + if (!Protocol.S_ENCRYPTION.sendPacket(sender, new PublicKeyMessage(sender.getKeyPair().getPublic()))) { + Logger.getCurrentLogger().warn("Failed to send public key to the server " + sender); + } + + // Enable encryption + sender.setEncrypted(true); + + if (Constants.DEBUG) { + Logger.getCurrentLogger().info("Encryption enabled for " + sender); + } + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/GiveGamingSaraHandler.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundGiveGamingSaraPacket.java similarity index 92% rename from api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/GiveGamingSaraHandler.java rename to api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundGiveGamingSaraPacket.java index 38f03ad..f02ab29 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/GiveGamingSaraHandler.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundGiveGamingSaraPacket.java @@ -2,6 +2,7 @@ import net.azisaba.azipluginmessaging.api.Logger; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.api.server.ServerConnection; import net.azisaba.azipluginmessaging.api.util.LuckPermsUtil; import net.luckperms.api.LuckPerms; @@ -18,14 +19,14 @@ import java.time.Instant; import java.util.UUID; -public class GiveGamingSaraHandler implements MessageHandler { +public class ProxyboundGiveGamingSaraPacket implements ProxyMessageHandler { @Override public @NotNull PlayerMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { return PlayerMessage.read(in); } @Override - public void handle(@NotNull PlayerMessage msg) { + public void handle(@NotNull PacketSender sender, @NotNull PlayerMessage msg) { LuckPerms api = LuckPermsProvider.get(); User user = api.getUserManager().loadUser(msg.getPlayer().getUniqueId()).join(); if (user == null || user.getUsername() == null) { diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundGiveSaraPacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundGiveSaraPacket.java new file mode 100644 index 0000000..54f629c --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundGiveSaraPacket.java @@ -0,0 +1,65 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.Logger; +import net.azisaba.azipluginmessaging.api.entity.SimplePlayer; +import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundGiveSaraMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import net.azisaba.azipluginmessaging.api.server.ServerConnection; +import net.azisaba.azipluginmessaging.api.util.Constants; +import net.azisaba.azipluginmessaging.api.util.LuckPermsUtil; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.actionlog.Action; +import net.luckperms.api.model.data.DataType; +import net.luckperms.api.model.data.NodeMap; +import net.luckperms.api.model.user.User; +import net.luckperms.api.node.Node; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.IOException; +import java.time.Instant; +import java.util.UUID; + +public class ProxyboundGiveSaraPacket implements ProxyMessageHandler { + @Override + public @NotNull ProxyboundGiveSaraMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { + return new ProxyboundGiveSaraMessage(in.readInt(), SimplePlayer.read(in)); + } + + @Override + public void handle(@NotNull PacketSender sender, @NotNull ProxyboundGiveSaraMessage msg) { + LuckPerms api = LuckPermsProvider.get(); + User user = api.getUserManager().loadUser(msg.getPlayer().getUniqueId()).join(); + if (user == null || user.getUsername() == null) { + throw new IllegalArgumentException("User " + msg.getPlayer().getUniqueId() + " could not be found in the LuckPerms database."); + } + if (!Constants.SARA_GROUPS.contains(msg.getAmount())) { + throw new IllegalArgumentException("Invalid sara group: " + msg.getAmount()); + } + String username = user.getUsername(); + boolean modified = false; + NodeMap map = user.getData(DataType.NORMAL); + Node nodeSara = LuckPermsUtil.findNode(map, msg.getAmount() + "yen", null); + if (nodeSara == null) { + LuckPermsUtil.addGroup(map, msg.getAmount() + "yen", null, -1); + modified = true; + } + if (!modified) { + Logger.getCurrentLogger().warn("Received GiveSara request for {} but they already have the rank", username); + return; + } + api.getUserManager().saveUser(user); + api.getMessagingService().ifPresent(service -> service.pushUserUpdate(user)); + api.getActionLogger().submit( + Action.builder() + .targetType(Action.Target.Type.USER) + .timestamp(Instant.now()) + .source(new UUID(0L, 0L)) + .sourceName("AziPluginMessaging@" + api.getServerName()) + .target(msg.getPlayer().getUniqueId()) + .targetName(username) + .description("Added " + msg.getAmount() + "yen sara to " + username) + .build()); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/SetRankHandler.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundSetRankPacket.java similarity index 86% rename from api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/SetRankHandler.java rename to api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundSetRankPacket.java index bdaa8e5..ed14d7d 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/SetRankHandler.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundSetRankPacket.java @@ -1,7 +1,8 @@ package net.azisaba.azipluginmessaging.api.protocol.handler; import net.azisaba.azipluginmessaging.api.AziPluginMessagingConfig; -import net.azisaba.azipluginmessaging.api.protocol.message.SetRankMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundSetRankMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.api.server.ServerConnection; import net.azisaba.azipluginmessaging.api.util.LuckPermsUtil; import net.luckperms.api.LuckPerms; @@ -22,17 +23,17 @@ import java.util.Objects; import java.util.UUID; -public class SetRankHandler implements MessageHandler { +public class ProxyboundSetRankPacket implements ProxyMessageHandler { @Override - public @NotNull SetRankMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { + public @NotNull ProxyboundSetRankMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { String serverName = server.getServerInfo().getName(); String s = AziPluginMessagingConfig.rankableServers.get(serverName); Objects.requireNonNull(s, "server is null (rankableServers entry in config.yml is missing)"); - return SetRankMessage.read(s, in); + return ProxyboundSetRankMessage.read(s, in); } @Override - public void handle(@NotNull SetRankMessage msg) { + public void handle(@NotNull PacketSender sender, @NotNull ProxyboundSetRankMessage msg) { Objects.requireNonNull(msg.getServer(), "server cannot be null"); LuckPerms api = LuckPermsProvider.get(); User user = api.getUserManager().loadUser(msg.getPlayer().getUniqueId()).join(); diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleGamingSaraHandler.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleGamingSaraPacket.java similarity index 71% rename from api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleGamingSaraHandler.java rename to api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleGamingSaraPacket.java index 1efa205..adfee0b 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleGamingSaraHandler.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleGamingSaraPacket.java @@ -1,7 +1,10 @@ package net.azisaba.azipluginmessaging.api.protocol.handler; import net.azisaba.azipluginmessaging.api.exception.MissingPermissionException; +import net.azisaba.azipluginmessaging.api.protocol.Protocol; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundActionResponseMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.api.server.ServerConnection; import net.azisaba.azipluginmessaging.api.util.LuckPermsUtil; import net.luckperms.api.LuckPerms; @@ -18,14 +21,14 @@ import java.time.Instant; import java.util.UUID; -public class ToggleGamingSaraHandler implements MessageHandler { +public class ProxyboundToggleGamingSaraPacket implements ProxyMessageHandler { @Override public @NotNull PlayerMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { return PlayerMessage.read(in); } @Override - public void handle(@NotNull PlayerMessage msg) { + public void handle(@NotNull PacketSender sender, @NotNull PlayerMessage msg) { LuckPerms api = LuckPermsProvider.get(); User user = api.getUserManager().loadUser(msg.getPlayer().getUniqueId()).join(); if (user == null || user.getUsername() == null) { @@ -35,15 +38,19 @@ public void handle(@NotNull PlayerMessage msg) { NodeMap map = user.getData(DataType.NORMAL); Node nodeChangeGamingSara = LuckPermsUtil.findNode(map, "changegamingsara", null); if (nodeChangeGamingSara == null) { + Protocol.S_ACTION_RESPONSE.sendPacket(sender, new ServerboundActionResponseMessage(msg.getPlayer().getUniqueId(), "\u00a7c権限がありません。皿を持ってるのにこのメッセージが出る場合はPerfectBoat#0001に泣きつきましょう!")); throw new MissingPermissionException("User " + msg.getPlayer().getUniqueId() + " does not have the group 'changegamingsara'."); } - char p = '+'; + char p; Node nodeGamingSara = LuckPermsUtil.findNode(map, "gamingsara", null); if (nodeGamingSara != null) { map.remove(nodeGamingSara); + Protocol.S_ACTION_RESPONSE.sendPacket(sender, new ServerboundActionResponseMessage(msg.getPlayer().getUniqueId(), "\u00a7aゲーミング皿を非表示にしました。")); p = '-'; } else { LuckPermsUtil.addGroup(map, "gamingsara", null, -1); + Protocol.S_ACTION_RESPONSE.sendPacket(sender, new ServerboundActionResponseMessage(msg.getPlayer().getUniqueId(), "\u00a7aゲーミング皿を表示しました。")); + p = '+'; } api.getUserManager().saveUser(user); api.getMessagingService().ifPresent(service -> service.pushUserUpdate(user)); diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleSaraHideHandler.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleSaraHidePacket.java similarity index 75% rename from api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleSaraHideHandler.java rename to api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleSaraHidePacket.java index efa2fc8..9bc0b86 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleSaraHideHandler.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleSaraHidePacket.java @@ -1,7 +1,10 @@ package net.azisaba.azipluginmessaging.api.protocol.handler; import net.azisaba.azipluginmessaging.api.exception.MissingPermissionException; +import net.azisaba.azipluginmessaging.api.protocol.Protocol; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundActionResponseMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.api.server.ServerConnection; import net.azisaba.azipluginmessaging.api.util.Constants; import net.azisaba.azipluginmessaging.api.util.LuckPermsUtil; @@ -19,14 +22,14 @@ import java.time.Instant; import java.util.UUID; -public class ToggleSaraHideHandler implements MessageHandler { +public class ProxyboundToggleSaraHidePacket implements ProxyMessageHandler { @Override public @NotNull PlayerMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { return PlayerMessage.read(in); } @Override - public void handle(@NotNull PlayerMessage msg) { + public void handle(@NotNull PacketSender sender, @NotNull PlayerMessage msg) { LuckPerms api = LuckPermsProvider.get(); User user = api.getUserManager().loadUser(msg.getPlayer().getUniqueId()).join(); if (user == null || user.getUsername() == null) { @@ -40,17 +43,21 @@ public void handle(@NotNull PlayerMessage msg) { Node nodeHideSara = LuckPermsUtil.findNode(map, "hide" + saraGroup, null); String desc = null; if (nodeSara != null) { + // hide map.remove(nodeSara); if (nodeHideSara == null) { LuckPermsUtil.addGroup(map, "hide" + saraGroup, null, -1); } modified = true; desc = "Toggled " + saraGroup + "yen -> hide" + saraGroup + " for " + username; + Protocol.S_ACTION_RESPONSE.sendPacket(sender, new ServerboundActionResponseMessage(msg.getPlayer().getUniqueId(), "\u00a7a" + saraGroup + "円皿を非表示にしました。")); } else if (nodeHideSara != null) { + // show map.remove(nodeHideSara); LuckPermsUtil.addGroup(map, saraGroup + "yen", null, -1); modified = true; desc = "Toggled hide" + saraGroup + " -> " + saraGroup + "yen for " + username; + Protocol.S_ACTION_RESPONSE.sendPacket(sender, new ServerboundActionResponseMessage(msg.getPlayer().getUniqueId(), "\u00a7a" + saraGroup + "円皿を表示しました。")); } if (desc != null) { api.getActionLogger().submit( @@ -66,6 +73,7 @@ public void handle(@NotNull PlayerMessage msg) { } } if (!modified) { + Protocol.S_ACTION_RESPONSE.sendPacket(sender, new ServerboundActionResponseMessage(msg.getPlayer().getUniqueId(), "\u00a7c権限がありません。皿を持ってるのにこのメッセージが出る場合はPerfectBoat#0001に泣きつきましょう!")); throw new MissingPermissionException("User " + msg.getPlayer().getUniqueId() + " does not have any sara groups to toggle."); } api.getUserManager().saveUser(user); diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleSaraShowHandler.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleSaraShowPacket.java similarity index 77% rename from api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleSaraShowHandler.java rename to api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleSaraShowPacket.java index c771b5a..7e0e24b 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ToggleSaraShowHandler.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundToggleSaraShowPacket.java @@ -2,7 +2,10 @@ import net.azisaba.azipluginmessaging.api.AziPluginMessagingConfig; import net.azisaba.azipluginmessaging.api.exception.MissingPermissionException; +import net.azisaba.azipluginmessaging.api.protocol.Protocol; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerWithServerMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundActionResponseMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.api.server.ServerConnection; import net.azisaba.azipluginmessaging.api.util.Constants; import net.azisaba.azipluginmessaging.api.util.LuckPermsUtil; @@ -21,7 +24,7 @@ import java.util.Objects; import java.util.UUID; -public class ToggleSaraShowHandler implements MessageHandler { +public class ProxyboundToggleSaraShowPacket implements ProxyMessageHandler { @Override public @NotNull PlayerWithServerMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { String serverName = server.getServerInfo().getName(); @@ -31,7 +34,7 @@ public class ToggleSaraShowHandler implements MessageHandler the message type + */ +public interface ServerMessageHandler extends MessageHandler { + /** + * Decodes the message from the stream. + * @param in The stream. + * @return The decoded message. + * @throws IOException If an I/O error occurs. + */ + @NotNull + T read(@NotNull DataInputStream in) throws IOException; +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundActionResponsePacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundActionResponsePacket.java new file mode 100644 index 0000000..a7c5805 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundActionResponsePacket.java @@ -0,0 +1,25 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.AziPluginMessagingProvider; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundActionResponseMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.UUID; + +public class ServerboundActionResponsePacket implements ServerMessageHandler { + @NotNull + @Override + public ServerboundActionResponseMessage read(@NotNull DataInputStream in) throws IOException { + UUID uuid = UUID.fromString(in.readUTF()); + String message = in.readUTF(); + return new ServerboundActionResponseMessage(uuid, message); + } + + @Override + public void handle(@NotNull PacketSender sender, @NotNull ServerboundActionResponseMessage msg) throws Exception { + AziPluginMessagingProvider.get().getPlayer(msg.getUniqueId()).ifPresent(player -> player.sendMessage(msg.getMessage())); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionPacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionPacket.java new file mode 100644 index 0000000..5edcc1a --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionPacket.java @@ -0,0 +1,33 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.Logger; +import net.azisaba.azipluginmessaging.api.protocol.message.PublicKeyMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import net.azisaba.azipluginmessaging.api.util.Constants; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; + +public class ServerboundEncryptionPacket implements ServerMessageHandler { + @Override + public @NotNull PublicKeyMessage read(@NotNull DataInputStream in) { + try { + return PublicKeyMessage.read(in); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void handle(@NotNull PacketSender sender, @NotNull PublicKeyMessage msg) throws Exception { + // Set the public key from the proxy + sender.setRemotePublicKey(msg.getPublicKey()); + + // Enable encryption + sender.setEncrypted(true); + + if (Constants.DEBUG) { + Logger.getCurrentLogger().info("Encryption enabled for " + sender); + } + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundGiveSaraMessage.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundGiveSaraMessage.java new file mode 100644 index 0000000..846aa49 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundGiveSaraMessage.java @@ -0,0 +1,26 @@ +package net.azisaba.azipluginmessaging.api.protocol.message; + +import net.azisaba.azipluginmessaging.api.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class ProxyboundGiveSaraMessage extends PlayerMessage { + private final int amount; + + public ProxyboundGiveSaraMessage(int amount, @NotNull Player player) { + super(player); + this.amount = amount; + } + + public int getAmount() { + return amount; + } + + @Override + public void write(@NotNull DataOutputStream out) throws IOException { + out.writeInt(amount); + super.write(out); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/SetRankMessage.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundSetRankMessage.java similarity index 66% rename from api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/SetRankMessage.java rename to api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundSetRankMessage.java index c141963..e886338 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/SetRankMessage.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundSetRankMessage.java @@ -14,15 +14,15 @@ /** * A message that sets the rank of a player. */ -public class SetRankMessage extends PlayerWithServerMessage { +public class ProxyboundSetRankMessage extends PlayerWithServerMessage { protected final String rank; - public SetRankMessage(@NotNull String server, @NotNull String rank, @NotNull Player player) { + public ProxyboundSetRankMessage(@NotNull String server, @NotNull String rank, @NotNull Player player) { super(server, player); this.rank = Objects.requireNonNull(rank, "rank cannot be null"); } - public SetRankMessage(@NotNull String rank, @NotNull Player player) { + public ProxyboundSetRankMessage(@NotNull String rank, @NotNull Player player) { this("global", rank, player); } @@ -40,7 +40,7 @@ public void write(@NotNull DataOutputStream out) throws IOException { @Contract("null, _ -> fail; _, _ -> new") @NotNull - public static SetRankMessage read(@Nullable String server, @NotNull DataInputStream in) throws IOException { - return new SetRankMessage(Objects.requireNonNull(server, "server cannot be null"), in.readUTF(), SimplePlayer.read(in)); + public static ProxyboundSetRankMessage read(@Nullable String server, @NotNull DataInputStream in) throws IOException { + return new ProxyboundSetRankMessage(Objects.requireNonNull(server, "server cannot be null"), in.readUTF(), SimplePlayer.read(in)); } } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/PublicKeyMessage.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/PublicKeyMessage.java new file mode 100644 index 0000000..29df021 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/PublicKeyMessage.java @@ -0,0 +1,33 @@ +package net.azisaba.azipluginmessaging.api.protocol.message; + +import net.azisaba.azipluginmessaging.api.util.EncryptionUtil; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.security.PublicKey; + +public class PublicKeyMessage implements Message { + private final PublicKey publicKey; + + public PublicKeyMessage(@NotNull PublicKey publicKey) { + this.publicKey = publicKey; + } + + @NotNull + public PublicKey getPublicKey() { + return publicKey; + } + + @Override + public void write(@NotNull DataOutputStream out) throws IOException { + out.writeUTF(EncryptionUtil.encodePublicKey(publicKey)); + } + + @Contract("_ -> new") + public static @NotNull PublicKeyMessage read(@NotNull DataInputStream in) throws Exception { + return new PublicKeyMessage(EncryptionUtil.decodePublicKey(in.readUTF())); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ServerboundActionResponseMessage.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ServerboundActionResponseMessage.java new file mode 100644 index 0000000..8389769 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ServerboundActionResponseMessage.java @@ -0,0 +1,33 @@ +package net.azisaba.azipluginmessaging.api.protocol.message; + +import org.jetbrains.annotations.NotNull; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.UUID; + +public class ServerboundActionResponseMessage implements Message { + private final UUID uuid; + private final String message; + + public ServerboundActionResponseMessage(@NotNull UUID uuid, @NotNull String message) { + this.uuid = uuid; + this.message = message; + } + + @NotNull + public UUID getUniqueId() { + return uuid; + } + + @NotNull + public String getMessage() { + return message; + } + + @Override + public void write(@NotNull DataOutputStream out) throws IOException { + out.writeUTF(uuid.toString()); + out.writeUTF(message); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/server/PacketSender.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/server/PacketSender.java index e4e66e4..3a8402a 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/server/PacketSender.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/server/PacketSender.java @@ -2,6 +2,9 @@ import org.jetbrains.annotations.NotNull; +import java.security.KeyPair; +import java.security.PublicKey; + /** * Represents an object that can send packet to target. */ @@ -12,4 +15,16 @@ public interface PacketSender { * @return true if the packet was sent successfully; false otherwise */ boolean sendPacket(byte @NotNull [] data); + + void setEncrypted(boolean encrypted); + + boolean isEncrypted(); + + @NotNull + KeyPair getKeyPair(); + + @NotNull + PublicKey getRemotePublicKey(); + + void setRemotePublicKey(@NotNull PublicKey publicKey); } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/server/ServerConnection.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/server/ServerConnection.java index eb22016..c431267 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/server/ServerConnection.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/server/ServerConnection.java @@ -3,6 +3,8 @@ import net.azisaba.azipluginmessaging.api.entity.Player; import org.jetbrains.annotations.NotNull; +import java.security.KeyPair; + /** * Represents a connection between a server. */ @@ -20,4 +22,10 @@ public interface ServerConnection extends PacketSender { */ @NotNull ServerInfo getServerInfo(); + + /** + * Sets the key pair for encryption. + * @param keyPair the key pair + */ + void setKeyPair(@NotNull KeyPair keyPair); } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/util/Base64Util.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/util/Base64Util.java new file mode 100644 index 0000000..a432cd2 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/util/Base64Util.java @@ -0,0 +1,17 @@ +package net.azisaba.azipluginmessaging.api.util; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Base64; + +public class Base64Util { + @Contract("_ -> new") + public static @NotNull String encode(byte[] bytes) { + return Base64.getEncoder().encodeToString(bytes); + } + + public static byte[] decode(@NotNull String string) { + return Base64.getDecoder().decode(string); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/util/Constants.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/util/Constants.java index ebfccff..f494336 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/util/Constants.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/util/Constants.java @@ -5,4 +5,5 @@ public class Constants { public static final List SARA_GROUPS = Arrays.asList(10000, 5000, 2000, 1000, 500, 100); + public static final boolean DEBUG = true; } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/util/EncryptionUtil.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/util/EncryptionUtil.java new file mode 100644 index 0000000..d4afdcc --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/util/EncryptionUtil.java @@ -0,0 +1,68 @@ +package net.azisaba.azipluginmessaging.api.util; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import javax.crypto.Cipher; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.RSAKeyGenParameterSpec; + +public class EncryptionUtil { + public static final String ALGORITHM = "RSA"; + + /** + * Generates a new RSA key pair for encryption and decryption. + * + * @param bits the number of bits to use + * @return The generated key pair + * @throws Exception If an error occurs + */ + @NotNull + public static KeyPair generateKeyPair(int bits) throws Exception { + KeyPairGenerator keygen = KeyPairGenerator.getInstance(ALGORITHM); + RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(bits, RSAKeyGenParameterSpec.F4); + keygen.initialize(spec); + return keygen.generateKeyPair(); + } + + /** + * Encrypts a block of data. + * + * @param data The data to encrypt + * @param key The key to encrypt with + * @return The encrypted data + * @throws Exception If an error occurs + */ + public static byte[] encrypt(byte[] data, @NotNull PublicKey key) throws Exception { + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(data); + } + + /** + * Decrypts a block of data. + * + * @param data The data to decrypt + * @param key The key to decrypt with + * @return The decrypted data + * @throws Exception If an error occurs + */ + public static byte[] decrypt(byte[] data, @NotNull PrivateKey key) throws Exception { + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, key); + return cipher.doFinal(data); + } + + @Contract("_ -> new") + public static @NotNull String encodePublicKey(@NotNull PublicKey key) { + return Base64Util.encode(key.getEncoded()); + } + + @Contract("_ -> new") + public static @NotNull PublicKey decodePublicKey(@NotNull String key) throws Exception { + return KeyFactoryUtil.getPublicKey(Base64Util.decode(key)); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/util/KeyFactoryUtil.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/util/KeyFactoryUtil.java new file mode 100644 index 0000000..341c78f --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/util/KeyFactoryUtil.java @@ -0,0 +1,13 @@ +package net.azisaba.azipluginmessaging.api.util; + +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; + +public class KeyFactoryUtil { + public static PublicKey getPublicKey(byte[] encoded) throws Exception { + X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded); + KeyFactory kf = KeyFactory.getInstance(EncryptionUtil.ALGORITHM); + return kf.generatePublic(spec); + } +} diff --git a/build.gradle.kts b/build.gradle.kts index a28fa63..9679715 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } group = "net.azisaba.azipluginmessaging" -version = "1.1.0" +version = "2.0.0" repositories { mavenCentral() @@ -57,4 +57,10 @@ allprojects { } } } + + tasks { + compileJava { + options.encoding = "UTF-8" + } + } } diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/AziPluginMessagingSpigot.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/AziPluginMessagingSpigot.java index 5107dec..ddc088f 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/AziPluginMessagingSpigot.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/AziPluginMessagingSpigot.java @@ -9,7 +9,10 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import java.util.List; import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; public class AziPluginMessagingSpigot implements AziPluginMessaging { private final Logger logger; @@ -35,6 +38,13 @@ public AziPluginMessagingSpigot(@NotNull SpigotPlugin plugin) { return server; } + @Override + public @NotNull Optional getPlayer(@NotNull UUID uuid) { + Player player = Bukkit.getPlayer(uuid); + if (player == null) return Optional.empty(); + return Optional.of(new PlayerImpl(player)); + } + @SuppressWarnings("unchecked") @Override public PlayerAdapter getPlayerAdapter(@NotNull Class clazz) { @@ -45,14 +55,14 @@ public PlayerAdapter getPlayerAdapter(@NotNull Class clazz) { public static class ServerImpl implements Server { @Override public @NotNull PacketSender getPacketSender() { - return data -> { - Optional p = Bukkit.getOnlinePlayers().stream().findAny(); - if (!p.isPresent()) { - return false; - } - PlayerImpl.sendPacketFromPlayer(p.get(), data); - return true; - }; + // prefer encrypted players (that is, player who is connected to the proxy where AziPluginMessaging is installed) + List players = Bukkit.getOnlinePlayers() + .stream() + .map(PlayerImpl::new) + .collect(Collectors.toList()); + if (players.size() == 0) throw new IllegalArgumentException("No player is online."); + Optional encryptedPlayer = players.stream().filter(PlayerImpl::isEncrypted).findAny(); + return encryptedPlayer.orElseGet(() -> players.get(0)); } } } diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/PluginMessageReceiver.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/PluginMessageReceiver.java new file mode 100644 index 0000000..3aa1e3e --- /dev/null +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/PluginMessageReceiver.java @@ -0,0 +1,13 @@ +package net.azisaba.azipluginmessaging.spigot; + +import net.azisaba.azipluginmessaging.api.protocol.Protocol; +import net.azisaba.azipluginmessaging.spigot.entity.PlayerImpl; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; + +public class PluginMessageReceiver implements PluginMessageListener { + @Override + public void onPluginMessageReceived(String channel, Player player, byte[] message) { + Protocol.handleServerSide(new PlayerImpl(player), message); + } +} diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/SpigotPlugin.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/SpigotPlugin.java index 0a9a3c7..dafed3c 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/SpigotPlugin.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/SpigotPlugin.java @@ -4,15 +4,22 @@ import net.azisaba.azipluginmessaging.api.AziPluginMessagingProvider; import net.azisaba.azipluginmessaging.api.AziPluginMessagingProviderProvider; import net.azisaba.azipluginmessaging.api.protocol.Protocol; +import net.azisaba.azipluginmessaging.api.protocol.message.PublicKeyMessage; import net.azisaba.azipluginmessaging.api.server.PacketSender; +import net.azisaba.azipluginmessaging.api.util.EncryptionUtil; import net.azisaba.azipluginmessaging.spigot.command.AziPluginMessagingCommand; +import net.azisaba.azipluginmessaging.spigot.entity.PlayerImpl; import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import java.security.KeyPair; import java.util.Objects; -public class SpigotPlugin extends JavaPlugin implements PacketSender { +public class SpigotPlugin extends JavaPlugin implements Listener { public static SpigotPlugin plugin; @Override @@ -27,12 +34,46 @@ public void onEnable() { Objects.requireNonNull(Bukkit.getPluginCommand("azipluginmessaging")).setExecutor(new AziPluginMessagingCommand()); try { Bukkit.getMessenger().registerOutgoingPluginChannel(this, Protocol.LEGACY_CHANNEL_ID); - } catch (IllegalArgumentException ignored) {} + Bukkit.getMessenger().registerIncomingPluginChannel(this, Protocol.LEGACY_CHANNEL_ID, new PluginMessageReceiver()); + } catch (IllegalArgumentException e) { + getLogger().info("Could not register legacy channel"); + e.printStackTrace(); + } Bukkit.getMessenger().registerOutgoingPluginChannel(this, Protocol.CHANNEL_ID); + Bukkit.getMessenger().registerIncomingPluginChannel(this, Protocol.CHANNEL_ID, new PluginMessageReceiver()); + Bukkit.getPluginManager().registerEvents(this, this); + getLogger().info("Enabled " + getName()); } - @Override - public boolean sendPacket(byte @NotNull [] data) { - return AziPluginMessagingProvider.get().getServer().getPacketSender().sendPacket(data); + @NotNull + public static PacketSender getAnyPacketSender() { + return AziPluginMessagingProvider.get().getServer().getPacketSender(); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent e) { + // do inside the separate thread to avoid blocking main thread + new Thread(() -> { + KeyPair keyPair; + try { + // generate keypair + keyPair = EncryptionUtil.generateKeyPair(2048); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + PlayerImpl player = new PlayerImpl(e.getPlayer()); + + // set keypair + player.setKeyPair(keyPair); + + // set encrypted flag to false if it was previously set to true + player.setEncrypted(false); + + // set public key to null too + player.setRemotePublicKeyInternal(null); + + // send our public key + Protocol.P_ENCRYPTION.sendPacket(player, new PublicKeyMessage(keyPair.getPublic())); + }, "AziPluginMessaging-" + e.getPlayer().getName()).start(); } } diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/command/CommandManager.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/command/CommandManager.java index bbc70bf..e71a038 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/command/CommandManager.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/command/CommandManager.java @@ -2,6 +2,7 @@ import net.azisaba.azipluginmessaging.spigot.commands.DumpProtocolCommand; import net.azisaba.azipluginmessaging.spigot.commands.GiveGamingSaraCommand; +import net.azisaba.azipluginmessaging.spigot.commands.GiveSaraCommand; import net.azisaba.azipluginmessaging.spigot.commands.HelpCommand; import net.azisaba.azipluginmessaging.spigot.commands.SetRankCommand; import net.azisaba.azipluginmessaging.spigot.commands.ToggleGamingSaraCommand; @@ -17,6 +18,7 @@ public class CommandManager { new HelpCommand(), new SetRankCommand(), new GiveGamingSaraCommand(), + new GiveSaraCommand(), new ToggleGamingSaraCommand(), new ToggleSaraHideCommand(), new ToggleSaraShowCommand(), diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/DumpProtocolCommand.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/DumpProtocolCommand.java index d949a27..40827ba 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/DumpProtocolCommand.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/DumpProtocolCommand.java @@ -1,5 +1,6 @@ package net.azisaba.azipluginmessaging.spigot.commands; +import net.azisaba.azipluginmessaging.api.protocol.PacketFlow; import net.azisaba.azipluginmessaging.api.protocol.Protocol; import net.azisaba.azipluginmessaging.spigot.command.Command; import org.bukkit.ChatColor; @@ -9,12 +10,19 @@ public class DumpProtocolCommand implements Command { @Override public void execute(@NotNull CommandSender sender, @NotNull String[] args) { - for (Protocol protocol : Protocol.values()) { + for (Protocol protocol : Protocol.values(PacketFlow.TO_PROXY)) { String hex = Integer.toString(protocol.getId(), 16); if (hex.length() == 1) hex = '0' + hex; - sender.sendMessage("" + ChatColor.GREEN + protocol.getId() + + sender.sendMessage(ChatColor.LIGHT_PURPLE + "TO_PROXY " + ChatColor.GREEN + protocol.getId() + " (0x" + hex + ") " + - ChatColor.WHITE + protocol.getHandler().getClass().getName()); + ChatColor.WHITE + protocol.getHandler().getClass().getSimpleName()); + } + for (Protocol protocol : Protocol.values(PacketFlow.TO_SERVER)) { + String hex = Integer.toString(protocol.getId(), 16); + if (hex.length() == 1) hex = '0' + hex; + sender.sendMessage(ChatColor.LIGHT_PURPLE + "TO_SERVER " + ChatColor.GREEN + protocol.getId() + + " (0x" + hex + ") " + + ChatColor.WHITE + protocol.getHandler().getClass().getSimpleName()); } } diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/GiveGamingSaraCommand.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/GiveGamingSaraCommand.java index 4e2e2db..6534571 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/GiveGamingSaraCommand.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/GiveGamingSaraCommand.java @@ -18,11 +18,11 @@ public void execute(@NotNull CommandSender sender, @NotNull String[] args) { return; } Player target = PlayerUtil.getOfflinePlayer(args[0]); - boolean res = Protocol.GIVE_GAMING_SARA.sendPacket(SpigotPlugin.plugin, new PlayerMessage(target)); + boolean res = Protocol.P_GIVE_GAMING_SARA.sendPacket(SpigotPlugin.getAnyPacketSender(), new PlayerMessage(target)); if (res) { sender.sendMessage(ChatColor.GREEN + "Sent a request to give " + target.getUsername() + " the gaming sara"); } else { - sender.sendMessage(ChatColor.RED + "Failed to send the packet. Maybe check console for errors?"); + sender.sendMessage(ChatColor.RED + "Failed to send the packet (attempted to give " + target.getUsernameOrUniqueId() + " the gaming sara). Maybe check console for errors?"); } } diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/GiveSaraCommand.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/GiveSaraCommand.java new file mode 100644 index 0000000..0ccc446 --- /dev/null +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/GiveSaraCommand.java @@ -0,0 +1,44 @@ +package net.azisaba.azipluginmessaging.spigot.commands; + +import net.azisaba.azipluginmessaging.api.entity.Player; +import net.azisaba.azipluginmessaging.api.protocol.Protocol; +import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundGiveSaraMessage; +import net.azisaba.azipluginmessaging.spigot.SpigotPlugin; +import net.azisaba.azipluginmessaging.spigot.command.Command; +import net.azisaba.azipluginmessaging.spigot.util.PlayerUtil; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class GiveSaraCommand implements Command { + @Override + public void execute(@NotNull CommandSender sender, @NotNull String[] args) { + if (args.length <= 1) { + sender.sendMessage(ChatColor.RED + "Usage: " + getFullUsage()); + return; + } + int amount = Integer.parseInt(args[0]); + Player target = PlayerUtil.getOfflinePlayer(args[1]); + boolean res = Protocol.P_GIVE_SARA.sendPacket(SpigotPlugin.getAnyPacketSender(), new ProxyboundGiveSaraMessage(amount, target)); + if (res) { + sender.sendMessage(ChatColor.GREEN + "Sent a request to give " + target.getUsername() + " the " + amount + "yen sara"); + } else { + sender.sendMessage(ChatColor.RED + "Failed to send the packet (attempted to give " + target.getUsernameOrUniqueId() + " the " + amount + "yen sara). Maybe check console for errors?"); + } + } + + @Override + public @NotNull String getName() { + return "giveSara"; + } + + @Override + public @NotNull String getDescription() { + return "Give a player the sara rank"; + } + + @Override + public @NotNull String getUsage() { + return " "; + } +} diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/SetRankCommand.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/SetRankCommand.java index d570bbe..8c6b9f9 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/SetRankCommand.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/SetRankCommand.java @@ -1,9 +1,9 @@ package net.azisaba.azipluginmessaging.spigot.commands; -import net.azisaba.azipluginmessaging.api.AziPluginMessagingProvider; import net.azisaba.azipluginmessaging.api.entity.Player; import net.azisaba.azipluginmessaging.api.protocol.Protocol; -import net.azisaba.azipluginmessaging.api.protocol.message.SetRankMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundSetRankMessage; +import net.azisaba.azipluginmessaging.spigot.SpigotPlugin; import net.azisaba.azipluginmessaging.spigot.command.Command; import net.azisaba.azipluginmessaging.spigot.entity.PlayerImpl; import net.azisaba.azipluginmessaging.spigot.util.PlayerUtil; @@ -30,11 +30,11 @@ public void execute(@NotNull CommandSender sender, @NotNull String[] args) { target = new PlayerImpl((org.bukkit.entity.Player) sender); } } - boolean res = Protocol.SET_RANK.sendPacket(AziPluginMessagingProvider.get().getServer().getPacketSender(), new SetRankMessage(rank, target)); + boolean res = Protocol.P_SET_RANK.sendPacket(SpigotPlugin.getAnyPacketSender(), new ProxyboundSetRankMessage(rank, target)); if (res) { sender.sendMessage(ChatColor.GREEN + "Sent a request to change " + target.getUsername() + "'s rank to " + rank); } else { - sender.sendMessage(ChatColor.RED + "Failed to send the packet. Maybe check console for errors?"); + sender.sendMessage(ChatColor.RED + "Failed to send the packet (attempted to change " + target.getUsernameOrUniqueId() + "'s rank to " + rank + "). Maybe check console for errors?"); } } diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleGamingSaraCommand.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleGamingSaraCommand.java index b031dae..78460b2 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleGamingSaraCommand.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleGamingSaraCommand.java @@ -25,7 +25,7 @@ public void execute(@NotNull CommandSender sender, @NotNull String[] args) { target = new PlayerImpl((org.bukkit.entity.Player) sender); } } - boolean res = Protocol.TOGGLE_GAMING_SARA.sendPacket(SpigotPlugin.plugin, new PlayerMessage(target)); + boolean res = Protocol.P_TOGGLE_GAMING_SARA.sendPacket(SpigotPlugin.getAnyPacketSender(), new PlayerMessage(target)); if (res) { sender.sendMessage(ChatColor.GREEN + "Sent a request to toggle " + target.getUsername() + "'s gaming sara"); } else { diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleSaraHideCommand.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleSaraHideCommand.java index be3144a..71f6812 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleSaraHideCommand.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleSaraHideCommand.java @@ -18,7 +18,7 @@ public void execute(@NotNull CommandSender sender, @NotNull String[] args) { return; } Player target = PlayerUtil.getOfflinePlayer(args[0]); - boolean res = Protocol.TOGGLE_SARA_HIDE.sendPacket(SpigotPlugin.plugin, new PlayerMessage(target)); + boolean res = Protocol.P_TOGGLE_SARA_HIDE.sendPacket(SpigotPlugin.getAnyPacketSender(), new PlayerMessage(target)); if (res) { sender.sendMessage(ChatColor.GREEN + "Sent a request to toggle " + target.getUsername() + "'s sara hide."); } else { diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleSaraShowCommand.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleSaraShowCommand.java index a02f9c1..bf5409e 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleSaraShowCommand.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/commands/ToggleSaraShowCommand.java @@ -2,7 +2,6 @@ import net.azisaba.azipluginmessaging.api.entity.Player; import net.azisaba.azipluginmessaging.api.protocol.Protocol; -import net.azisaba.azipluginmessaging.api.protocol.message.PlayerMessage; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerWithServerMessage; import net.azisaba.azipluginmessaging.spigot.SpigotPlugin; import net.azisaba.azipluginmessaging.spigot.command.Command; @@ -19,7 +18,7 @@ public void execute(@NotNull CommandSender sender, @NotNull String[] args) { return; } Player target = PlayerUtil.getOfflinePlayer(args[0]); - boolean res = Protocol.TOGGLE_SARA_SHOW.sendPacket(SpigotPlugin.plugin, new PlayerWithServerMessage(target)); + boolean res = Protocol.P_TOGGLE_SARA_SHOW.sendPacket(SpigotPlugin.getAnyPacketSender(), new PlayerWithServerMessage(target)); if (res) { sender.sendMessage(ChatColor.GREEN + "Sent a request to toggle " + target.getUsername() + "'s sara show flag."); } else { diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/entity/PlayerImpl.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/entity/PlayerImpl.java index 3964bfc..7cbc02b 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/entity/PlayerImpl.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/entity/PlayerImpl.java @@ -8,11 +8,20 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.security.KeyPair; +import java.security.PublicKey; +import java.util.AbstractMap; +import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; public class PlayerImpl implements Player, PacketSender { + private static final Map> KEY_MAP = new ConcurrentHashMap<>(); private final org.bukkit.entity.Player handle; + private KeyPair keyPair; + private PublicKey remotePublicKey; + private boolean encrypted = false; @Contract(value = "null -> fail", pure = true) public PlayerImpl(@Nullable org.bukkit.entity.Player handle) { @@ -20,6 +29,14 @@ public PlayerImpl(@Nullable org.bukkit.entity.Player handle) { throw new IllegalArgumentException("player is null"); } this.handle = Objects.requireNonNull(handle); + Map.Entry key = KEY_MAP.get(handle.getUniqueId()); + if (key != null) { + this.keyPair = key.getKey(); + if (key.getValue() != null) { + this.remotePublicKey = key.getValue(); + this.encrypted = true; + } + } } @NotNull @@ -37,18 +54,74 @@ public org.bukkit.entity.Player getHandle() { return getHandle().getUniqueId(); } + @Override + public void sendMessage(@NotNull String message) { + getHandle().sendMessage(message); + } + @Override public boolean sendPacket(byte @NotNull [] data) { sendPacketFromPlayer(getHandle(), data); return true; } + @NotNull + @Override + public KeyPair getKeyPair() { + return Objects.requireNonNull(keyPair, "keyPair is null"); + } + + public void setKeyPair(@NotNull KeyPair keyPair) { + KEY_MAP.put(getUniqueId(), new AbstractMap.SimpleImmutableEntry<>(keyPair, remotePublicKey)); + this.keyPair = keyPair; + } + + @Override + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } + + @Override + public boolean isEncrypted() { + return this.encrypted; + } + + @NotNull + @Override + public PublicKey getRemotePublicKey() { + return Objects.requireNonNull(remotePublicKey, "remotePublicKey is null"); + } + + @Override + public void setRemotePublicKey(@NotNull PublicKey remotePublicKey) { + Objects.requireNonNull(remotePublicKey, "remotePublicKey is null"); + KEY_MAP.put(getUniqueId(), new AbstractMap.SimpleImmutableEntry<>(keyPair, remotePublicKey)); + this.remotePublicKey = remotePublicKey; + } + + public void setRemotePublicKeyInternal(@Nullable PublicKey remotePublicKey) { + KEY_MAP.put(getUniqueId(), new AbstractMap.SimpleImmutableEntry<>(keyPair, remotePublicKey)); + this.remotePublicKey = remotePublicKey; + } + + @Override + public String toString() { + return "PlayerImpl{" + + "handle=" + handle + + ", encrypted=" + encrypted + + '}'; + } + public static void sendPacketFromPlayer(@NotNull org.bukkit.entity.Player player, byte @NotNull [] data) { + boolean success = false; try { player.sendPluginMessage(SpigotPlugin.plugin, Protocol.LEGACY_CHANNEL_ID, data); + success = true; } catch (IllegalArgumentException ignore) {} try { player.sendPluginMessage(SpigotPlugin.plugin, Protocol.CHANNEL_ID, data); - } catch (IllegalArgumentException ignore) {} + } catch (IllegalArgumentException e) { + if (!success) throw new RuntimeException("Both Protocol.LEGACY_CHANNEL_ID and Protocol.CHANNEL_ID failed to send packet", e); + } } } diff --git a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/AziPluginMessagingVelocity.java b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/AziPluginMessagingVelocity.java index 2d717fa..844ed85 100644 --- a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/AziPluginMessagingVelocity.java +++ b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/AziPluginMessagingVelocity.java @@ -9,9 +9,11 @@ import net.azisaba.azipluginmessaging.api.protocol.Protocol; import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.velocity.entity.PlayerImpl; +import net.azisaba.azipluginmessaging.velocity.server.ServerConnectionImpl; import org.jetbrains.annotations.NotNull; import java.util.Optional; +import java.util.UUID; public class AziPluginMessagingVelocity implements AziPluginMessaging { private final ProxyServer server; @@ -39,6 +41,11 @@ public AziPluginMessagingVelocity(@NotNull ProxyServer server, @NotNull org.slf4 return new Server() {}; } + @Override + public @NotNull Optional getPlayer(@NotNull UUID uuid) { + return server.getPlayer(uuid).map(PlayerImpl::new); + } + @SuppressWarnings("unchecked") @Override public PlayerAdapter getPlayerAdapter(@NotNull Class clazz) { @@ -46,12 +53,6 @@ public PlayerAdapter getPlayerAdapter(@NotNull Class clazz) { return (PlayerAdapter) (PlayerAdapter) PlayerImpl::new; } - public class ProxyImpl implements Proxy { - @Override - public @NotNull Optional getPacketSenderForServer(@NotNull String serverName) { - return AziPluginMessagingVelocity.this.server - .getServer(serverName) - .map(server -> data -> server.sendPluginMessage(MinecraftChannelIdentifier.from(Protocol.CHANNEL_ID), data)); - } + public static class ProxyImpl implements Proxy { } } diff --git a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/entity/PlayerImpl.java b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/entity/PlayerImpl.java index 9f45752..ec5a585 100644 --- a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/entity/PlayerImpl.java +++ b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/entity/PlayerImpl.java @@ -1,11 +1,16 @@ package net.azisaba.azipluginmessaging.velocity.entity; import net.azisaba.azipluginmessaging.api.entity.Player; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.jetbrains.annotations.NotNull; import java.util.UUID; public class PlayerImpl implements Player { + private static final LegacyComponentSerializer LEGACY_COMPONENT_SERIALIZER = LegacyComponentSerializer.builder() + .character('\u00A7') + .extractUrls() + .build(); private final com.velocitypowered.api.proxy.Player handle; public PlayerImpl(@NotNull com.velocitypowered.api.proxy.Player handle) { @@ -26,4 +31,9 @@ public com.velocitypowered.api.proxy.Player getHandle() { public @NotNull UUID getUniqueId() { return getHandle().getUniqueId(); } + + @Override + public void sendMessage(@NotNull String message) { + getHandle().sendMessage(LEGACY_COMPONENT_SERIALIZER.deserialize(message)); + } } diff --git a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/server/ServerConnectionImpl.java b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/server/ServerConnectionImpl.java index 3a38924..9bc9e27 100644 --- a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/server/ServerConnectionImpl.java +++ b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/server/ServerConnectionImpl.java @@ -1,5 +1,6 @@ package net.azisaba.azipluginmessaging.velocity.server; +import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import net.azisaba.azipluginmessaging.api.entity.Player; import net.azisaba.azipluginmessaging.api.protocol.Protocol; @@ -8,11 +9,48 @@ import net.azisaba.azipluginmessaging.velocity.entity.PlayerImpl; import org.jetbrains.annotations.NotNull; +import java.security.KeyPair; +import java.security.PublicKey; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + public class ServerConnectionImpl implements ServerConnection { + private static long lastCleaned = System.currentTimeMillis(); + private static final Map> KEY_MAP = new ConcurrentHashMap<>(); private final com.velocitypowered.api.proxy.ServerConnection handle; + private KeyPair keyPair; + private PublicKey publicKey; + private boolean encrypted = false; public ServerConnectionImpl(@NotNull com.velocitypowered.api.proxy.ServerConnection handle) { this.handle = handle; + Map.Entry entry = KEY_MAP.get(handle); + if (entry != null) { + keyPair = entry.getKey(); + publicKey = entry.getValue(); + encrypted = true; + } + // check last cleaned time + if (System.currentTimeMillis() - lastCleaned > 30000) { + clean(); + } + } + + private static void clean() { + lastCleaned = System.currentTimeMillis(); + List toRemove = new ArrayList<>(); + for (Map.Entry> e : KEY_MAP.entrySet()) { + if (!e.getKey().getPlayer().isActive()) { + toRemove.add(e.getKey()); + } + } + for (com.velocitypowered.api.proxy.ServerConnection connection : toRemove) { + KEY_MAP.remove(connection); + } } @NotNull @@ -32,7 +70,40 @@ public com.velocitypowered.api.proxy.ServerConnection getHandle() { @Override public boolean sendPacket(byte @NotNull [] data) { - return getHandle().sendPluginMessage(MinecraftChannelIdentifier.from(Protocol.CHANNEL_ID), data); + return getHandle().sendPluginMessage(new LegacyChannelIdentifier(Protocol.LEGACY_CHANNEL_ID), data) | // yes , one | to send both packets + getHandle().sendPluginMessage(MinecraftChannelIdentifier.from(Protocol.CHANNEL_ID), data); + } + + @Override + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } + + @Override + public boolean isEncrypted() { + return encrypted; + } + + @Override + public @NotNull KeyPair getKeyPair() { + return Objects.requireNonNull(keyPair); + } + + @Override + public void setKeyPair(@NotNull KeyPair keyPair) { + KEY_MAP.put(handle, new AbstractMap.SimpleImmutableEntry<>(keyPair, publicKey)); + this.keyPair = keyPair; + } + + @Override + public @NotNull PublicKey getRemotePublicKey() { + return publicKey; + } + + @Override + public void setRemotePublicKey(@NotNull PublicKey publicKey) { + KEY_MAP.put(handle, new AbstractMap.SimpleImmutableEntry<>(keyPair, publicKey)); + this.publicKey = publicKey; } @NotNull