From 1c3bb5b0ff2929648f669a68f7ad8dc9354e3747 Mon Sep 17 00:00:00 2001 From: TheMode Date: Tue, 3 Aug 2021 12:57:13 +0200 Subject: [PATCH] First NIO attempt Signed-off-by: TheMode --- build.gradle | 6 - .../net/minestom/server/MinecraftServer.java | 105 ++------ .../builder/arguments/ArgumentString.java | 4 +- .../minestom/server/extras/lan/OpenToLAN.java | 2 +- .../server/extras/mojangAuth/CipherBase.java | 54 ---- .../server/extras/mojangAuth/Decrypter.java | 20 -- .../server/extras/mojangAuth/Encrypter.java | 19 -- .../minestom/server/extras/query/Query.java | 17 +- .../query/response/BasicQueryResponse.java | 4 +- .../extras/query/response/QueryKey.java | 4 +- .../server/extras/velocity/VelocityProxy.java | 10 +- .../net/minestom/server/instance/Chunk.java | 4 +- .../server/instance/DynamicChunk.java | 2 +- .../net/minestom/server/item/ItemMeta.java | 6 +- .../server/network/ConnectionManager.java | 13 +- .../server/network/PacketProcessor.java | 63 +---- .../server/network/netty/NettyServer.java | 202 -------------- .../network/netty/channel/ClientChannel.java | 90 ------- .../netty/codec/GroupedPacketHandler.java | 19 -- .../netty/codec/LegacyPingHandler.java | 184 ------------- .../network/netty/codec/PacketCompressor.java | 79 ------ .../network/netty/codec/PacketDecoder.java | 20 -- .../network/netty/codec/PacketEncoder.java | 16 -- .../network/netty/codec/PacketFramer.java | 74 ------ .../network/netty/packet/FramedPacket.java | 10 +- .../network/netty/packet/InboundPacket.java | 11 +- .../packet/server/play/ChunkDataPacket.java | 44 +--- .../packet/server/play/UpdateLightPacket.java | 35 +-- .../network/player/NettyPlayerConnection.java | 249 ++++++++++-------- .../network/player/PlayerConnection.java | 4 +- .../server/network/socket/Server.java | 97 +++++++ .../server/network/socket/Worker.java | 149 +++++++++++ .../server/ping/ServerListPingType.java | 6 +- .../net/minestom/server/utils/BufUtils.java | 13 - .../minestom/server/utils/PacketUtils.java | 116 ++------ .../java/net/minestom/server/utils/Utils.java | 66 ++--- .../server/utils/binary/BinaryReader.java | 63 ++--- .../server/utils/binary/BinaryWriter.java | 91 ++++--- .../server/utils/cache/CacheablePacket.java | 91 ------- .../server/utils/cache/TemporaryCache.java | 57 ---- .../utils/cache/TemporaryPacketCache.java | 18 -- .../server/utils/cache/TimedBuffer.java | 28 -- src/test/java/demo/Main.java | 1 + 43 files changed, 595 insertions(+), 1571 deletions(-) delete mode 100644 src/main/java/net/minestom/server/extras/mojangAuth/CipherBase.java delete mode 100644 src/main/java/net/minestom/server/extras/mojangAuth/Decrypter.java delete mode 100644 src/main/java/net/minestom/server/extras/mojangAuth/Encrypter.java delete mode 100644 src/main/java/net/minestom/server/network/netty/NettyServer.java delete mode 100644 src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java delete mode 100644 src/main/java/net/minestom/server/network/netty/codec/GroupedPacketHandler.java delete mode 100644 src/main/java/net/minestom/server/network/netty/codec/LegacyPingHandler.java delete mode 100644 src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java delete mode 100644 src/main/java/net/minestom/server/network/netty/codec/PacketDecoder.java delete mode 100644 src/main/java/net/minestom/server/network/netty/codec/PacketEncoder.java delete mode 100644 src/main/java/net/minestom/server/network/netty/codec/PacketFramer.java create mode 100644 src/main/java/net/minestom/server/network/socket/Server.java create mode 100644 src/main/java/net/minestom/server/network/socket/Worker.java delete mode 100644 src/main/java/net/minestom/server/utils/BufUtils.java delete mode 100644 src/main/java/net/minestom/server/utils/cache/CacheablePacket.java delete mode 100644 src/main/java/net/minestom/server/utils/cache/TemporaryCache.java delete mode 100644 src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java delete mode 100644 src/main/java/net/minestom/server/utils/cache/TimedBuffer.java diff --git a/build.gradle b/build.gradle index 986c6a9c4..1a019f754 100644 --- a/build.gradle +++ b/build.gradle @@ -107,12 +107,6 @@ dependencies { // Only here to ensure J9 module support for extensions and our classloaders testCompileOnly 'org.mockito:mockito-core:3.11.1' - // Netty - api 'io.netty:netty-handler:4.1.65.Final' - api 'io.netty:netty-codec:4.1.65.Final' - api 'io.netty:netty-transport-native-epoll:4.1.65.Final:linux-x86_64' - api 'io.netty:netty-transport-native-kqueue:4.1.65.Final:osx-x86_64' - // https://mvnrepository.com/artifact/it.unimi.dsi/fastutil api 'it.unimi.dsi:fastutil:8.5.4' diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 4d1b2a742..84605aa0e 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -21,10 +21,10 @@ import net.minestom.server.listener.manager.PacketListenerManager; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.network.ConnectionManager; import net.minestom.server.network.PacketProcessor; -import net.minestom.server.network.netty.NettyServer; import net.minestom.server.network.packet.server.play.PluginMessagePacket; import net.minestom.server.network.packet.server.play.ServerDifficultyPacket; import net.minestom.server.network.packet.server.play.UpdateViewDistancePacket; +import net.minestom.server.network.socket.Server; import net.minestom.server.ping.ResponseDataConsumer; import net.minestom.server.recipe.RecipeManager; import net.minestom.server.scoreboard.TeamManager; @@ -44,6 +44,9 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.InetSocketAddress; + /** * The main server class used to start the server and retrieve all the managers. *

@@ -85,9 +88,7 @@ public final class MinecraftServer { // Network private static PacketListenerManager packetListenerManager; private static PacketProcessor packetProcessor; - private static NettyServer nettyServer; - private static int nettyThreadCount = Runtime.getRuntime().availableProcessors(); - private static boolean processNettyErrors = true; + private static Server server; private static ExceptionManager exceptionManager; @@ -122,7 +123,6 @@ public final class MinecraftServer { private static int chunkViewDistance = 8; private static int entityViewDistance = 5; private static int compressionThreshold = 256; - private static boolean packetCaching = true; private static boolean groupedPacket = true; private static boolean terminalEnabled = System.getProperty("minestom.terminal.disabled") == null; private static ResponseDataConsumer responseDataConsumer; @@ -168,7 +168,11 @@ public final class MinecraftServer { tagManager = new TagManager(); - nettyServer = new NettyServer(packetProcessor); + try { + server = new Server(packetProcessor); + } catch (IOException e) { + e.printStackTrace(); + } initialized = true; @@ -277,16 +281,6 @@ public final class MinecraftServer { return packetListenerManager; } - /** - * Gets the netty server. - * - * @return the netty server - */ - public static NettyServer getNettyServer() { - checkInitStatus(nettyServer); - return nettyServer; - } - /** * Gets the manager handling all registered instances. * @@ -519,34 +513,6 @@ public final class MinecraftServer { MinecraftServer.compressionThreshold = compressionThreshold; } - /** - * Gets if the packet caching feature is enabled. - *

- * This feature allows some packets (implementing the {@link net.minestom.server.utils.cache.CacheablePacket} to be cached - * in order to do not have to be written and compressed over and over again), this is especially useful for chunk and light packets. - *

- * It is enabled by default and it is our recommendation, - * you should only disable it if you want to focus on low memory usage - * at the cost of many packet writing and compression. - * - * @return true if the packet caching feature is enabled, false otherwise - */ - public static boolean hasPacketCaching() { - return packetCaching; - } - - /** - * Enables or disable packet caching. - * - * @param packetCaching true to enable packet caching - * @throws IllegalStateException if this is called after the server started - * @see #hasPacketCaching() - */ - public static void setPacketCaching(boolean packetCaching) { - Check.stateCondition(started, "You cannot change the packet caching value after the server has been started."); - MinecraftServer.packetCaching = packetCaching; - } - /** * Gets if the packet caching feature is enabled. *

@@ -666,45 +632,9 @@ public final class MinecraftServer { return updateManager; } - /** - * Gets the number of threads used by Netty. - *

- * Is the number of vCPU by default. - * - * @return the number of netty threads - */ - public static int getNettyThreadCount() { - return nettyThreadCount; - } - - /** - * Changes the number of threads used by Netty. - * - * @param nettyThreadCount the number of threads - * @throws IllegalStateException if the server is already started - */ - public static void setNettyThreadCount(int nettyThreadCount) { - Check.stateCondition(started, "Netty thread count can only be changed before the server starts!"); - MinecraftServer.nettyThreadCount = nettyThreadCount; - } - - /** - * Gets if the server should process netty errors and other unnecessary netty events. - * - * @return should process netty errors - */ - public static boolean shouldProcessNettyErrors() { - return processNettyErrors; - } - - /** - * Sets if the server should process netty errors and other unnecessary netty events. - * false is faster - * - * @param processNettyErrors should process netty errors - */ - public static void setShouldProcessNettyErrors(boolean processNettyErrors) { - MinecraftServer.processNettyErrors = processNettyErrors; + public static Server getServer() { + checkInitStatus(server); + return server; } /** @@ -744,8 +674,11 @@ public final class MinecraftServer { updateManager.start(); // Init & start the TCP server - nettyServer.init(); - nettyServer.start(address, port); + try { + server.start(new InetSocketAddress(address, port)); + } catch (IOException e) { + e.printStackTrace(); + } if (extensionManager.shouldLoadOnStartup()) { final long loadStartTime = System.nanoTime(); @@ -779,7 +712,7 @@ public final class MinecraftServer { updateManager.stop(); schedulerManager.shutdown(); connectionManager.shutdown(); - nettyServer.stop(); + server.stop(); storageManager.getLoadedLocations().forEach(StorageLocation::close); LOGGER.info("Unloading all extensions."); extensionManager.shutdown(); diff --git a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentString.java b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentString.java index 696760381..f8aaed23d 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/ArgumentString.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/ArgumentString.java @@ -1,6 +1,5 @@ package net.minestom.server.command.builder.arguments; -import io.netty.util.internal.StringUtil; import net.minestom.server.command.builder.NodeMaker; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.network.packet.server.play.DeclareCommandsPacket; @@ -48,11 +47,10 @@ public class ArgumentString extends Argument { */ @Deprecated public static String staticParse(@NotNull String input) throws ArgumentSyntaxException { - // Return if not quoted if (!input.contains(String.valueOf(DOUBLE_QUOTE)) && !input.contains(String.valueOf(QUOTE)) && - !input.contains(String.valueOf(StringUtil.SPACE))) { + !input.contains(StringUtils.SPACE)) { return input; } diff --git a/src/main/java/net/minestom/server/extras/lan/OpenToLAN.java b/src/main/java/net/minestom/server/extras/lan/OpenToLAN.java index 350b05b83..a4777587a 100644 --- a/src/main/java/net/minestom/server/extras/lan/OpenToLAN.java +++ b/src/main/java/net/minestom/server/extras/lan/OpenToLAN.java @@ -121,7 +121,7 @@ public class OpenToLAN { * Performs the ping. */ private static void ping() { - if (MinecraftServer.getNettyServer().getPort() != 0) { + if (MinecraftServer.getServer().getPort() != 0) { if (packet == null || eventCooldown.isReady(System.currentTimeMillis())) { final ServerListPingEvent event = new ServerListPingEvent(OPEN_TO_LAN); EventDispatcher.call(event); diff --git a/src/main/java/net/minestom/server/extras/mojangAuth/CipherBase.java b/src/main/java/net/minestom/server/extras/mojangAuth/CipherBase.java deleted file mode 100644 index 9c77a7e52..000000000 --- a/src/main/java/net/minestom/server/extras/mojangAuth/CipherBase.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.minestom.server.extras.mojangAuth; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import org.jetbrains.annotations.NotNull; - -import javax.crypto.Cipher; -import javax.crypto.ShortBufferException; - -public class CipherBase { - - private final Cipher cipher; - private byte[] inTempArray = new byte[0]; - private byte[] outTempArray = new byte[0]; - - protected CipherBase(@NotNull Cipher cipher) { - this.cipher = cipher; - } - - private byte[] bufToByte(ByteBuf buffer) { - int remainingBytes = buffer.readableBytes(); - - // Need to resize temp array - if (inTempArray.length < remainingBytes) { - inTempArray = new byte[remainingBytes]; - } - - buffer.readBytes(inTempArray, 0, remainingBytes); - return inTempArray; - } - - protected ByteBuf decrypt(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn) throws ShortBufferException { - int remainingBytes = byteBufIn.readableBytes(); - byte[] bytes = bufToByte(byteBufIn); - - ByteBuf outputBuffer = channelHandlerContext.alloc().heapBuffer(cipher.getOutputSize(remainingBytes)); - outputBuffer.writerIndex(cipher.update(bytes, 0, remainingBytes, outputBuffer.array(), outputBuffer.arrayOffset())); - - return outputBuffer; - } - - protected void encrypt(ByteBuf byteBufIn, ByteBuf byteBufOut) throws ShortBufferException { - int remainingBytes = byteBufIn.readableBytes(); - byte[] bytes = bufToByte(byteBufIn); - int newSize = cipher.getOutputSize(remainingBytes); - - // Need to resize temp array - if (outTempArray.length < newSize) { - outTempArray = new byte[newSize]; - } - - byteBufOut.writeBytes(outTempArray, 0, cipher.update(bytes, 0, remainingBytes, outTempArray)); - } -} diff --git a/src/main/java/net/minestom/server/extras/mojangAuth/Decrypter.java b/src/main/java/net/minestom/server/extras/mojangAuth/Decrypter.java deleted file mode 100644 index 69c89ad6f..000000000 --- a/src/main/java/net/minestom/server/extras/mojangAuth/Decrypter.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.minestom.server.extras.mojangAuth; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; - -import javax.crypto.Cipher; -import java.util.List; - -public class Decrypter extends MessageToMessageDecoder { - private final CipherBase cipher; - - public Decrypter(Cipher cipher) { - this.cipher = new CipherBase(cipher); - } - - protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { - list.add(this.cipher.decrypt(channelHandlerContext, byteBuf)); - } -} diff --git a/src/main/java/net/minestom/server/extras/mojangAuth/Encrypter.java b/src/main/java/net/minestom/server/extras/mojangAuth/Encrypter.java deleted file mode 100644 index 053ca36cb..000000000 --- a/src/main/java/net/minestom/server/extras/mojangAuth/Encrypter.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.minestom.server.extras.mojangAuth; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; - -import javax.crypto.Cipher; - -public class Encrypter extends MessageToByteEncoder { - private final CipherBase cipher; - - public Encrypter(Cipher cipher) { - this.cipher = new CipherBase(cipher); - } - - protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn, ByteBuf byteBufOut) throws Exception { - this.cipher.encrypt(byteBufIn, byteBufOut); - } -} diff --git a/src/main/java/net/minestom/server/extras/query/Query.java b/src/main/java/net/minestom/server/extras/query/Query.java index a99225607..f80e88fc5 100644 --- a/src/main/java/net/minestom/server/extras/query/Query.java +++ b/src/main/java/net/minestom/server/extras/query/Query.java @@ -1,7 +1,5 @@ package net.minestom.server.extras.query; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -23,6 +21,7 @@ import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketAddress; import java.net.SocketException; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Random; @@ -151,18 +150,18 @@ public class Query { } // get the contents - ByteBuf data = Unpooled.wrappedBuffer(packet.getData()); + ByteBuffer data = ByteBuffer.wrap(packet.getData()); // check the magic field - if (data.readUnsignedShort() != 0xFEFD) { + if ((data.getShort() & 0xFFFF) != 0xFEFD) { continue; } // now check the query type - byte type = data.readByte(); + byte type = data.get(); if (type == 9) { // handshake - int sessionID = data.readInt(); + int sessionID = data.getInt(); int challengeToken = RANDOM.nextInt(); CHALLENGE_TOKENS.put(challengeToken, packet.getSocketAddress()); @@ -184,12 +183,12 @@ public class Query { } } } else if (type == 0) { // stat - int sessionID = data.readInt(); - int challengeToken = data.readInt(); + int sessionID = data.getInt(); + int challengeToken = data.getInt(); SocketAddress sender = packet.getSocketAddress(); if (CHALLENGE_TOKENS.containsKey(challengeToken) && CHALLENGE_TOKENS.get(challengeToken).equals(sender)) { - int remaining = data.readableBytes(); + int remaining = data.remaining(); if (remaining == 0) { // basic BasicQueryEvent event = new BasicQueryEvent(sender, sessionID); diff --git a/src/main/java/net/minestom/server/extras/query/response/BasicQueryResponse.java b/src/main/java/net/minestom/server/extras/query/response/BasicQueryResponse.java index da502f2a0..144c30064 100644 --- a/src/main/java/net/minestom/server/extras/query/response/BasicQueryResponse.java +++ b/src/main/java/net/minestom/server/extras/query/response/BasicQueryResponse.java @@ -142,7 +142,7 @@ public class BasicQueryResponse implements Writeable { writer.writeNullTerminatedString(this.map, Query.CHARSET); writer.writeNullTerminatedString(this.numPlayers, Query.CHARSET); writer.writeNullTerminatedString(this.maxPlayers, Query.CHARSET); - writer.getBuffer().writeShortLE(MinecraftServer.getNettyServer().getPort()); - writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getNettyServer().getAddress(), ""), Query.CHARSET); + writer.getBuffer().putShort((short) MinecraftServer.getServer().getPort()); // TODO little endian? + writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), ""), Query.CHARSET); } } diff --git a/src/main/java/net/minestom/server/extras/query/response/QueryKey.java b/src/main/java/net/minestom/server/extras/query/response/QueryKey.java index 066fb3f8a..0b5762e3b 100644 --- a/src/main/java/net/minestom/server/extras/query/response/QueryKey.java +++ b/src/main/java/net/minestom/server/extras/query/response/QueryKey.java @@ -20,8 +20,8 @@ public enum QueryKey { MAP(() -> "world"), NUM_PLAYERS("numplayers", () -> String.valueOf(MinecraftServer.getConnectionManager().getOnlinePlayers().size())), MAX_PLAYERS("maxplayers", () -> String.valueOf(MinecraftServer.getConnectionManager().getOnlinePlayers().size() + 1)), - HOST_PORT("hostport", () -> String.valueOf(MinecraftServer.getNettyServer().getPort())), - HOST_IP("hostip", () -> Objects.requireNonNullElse(MinecraftServer.getNettyServer().getAddress(), "localhost")); + HOST_PORT("hostport", () -> String.valueOf(MinecraftServer.getServer().getPort())), + HOST_IP("hostip", () -> Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), "localhost")); static QueryKey[] VALUES = QueryKey.values(); diff --git a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java index f4a133243..97e653109 100644 --- a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java +++ b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java @@ -1,6 +1,5 @@ package net.minestom.server.extras.velocity; -import io.netty.buffer.ByteBuf; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.PlayerSkin; import net.minestom.server.utils.binary.BinaryReader; @@ -10,6 +9,7 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -54,9 +54,11 @@ public final class VelocityProxy { final byte[] signature = reader.readBytes(32); - ByteBuf buf = reader.getBuffer(); - final byte[] data = new byte[buf.readableBytes()]; - buf.getBytes(buf.readerIndex(), data); + ByteBuffer buf = reader.getBuffer(); + buf.mark(); + final byte[] data = new byte[buf.remaining()]; + buf.get(data); + buf.reset(); try { final Mac mac = Mac.getInstance("HmacSHA256"); diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index f82bbff35..dec32ac4d 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -256,7 +256,7 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka List skyLights = new ArrayList<>(); List blockLights = new ArrayList<>(); - UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime()); + UpdateLightPacket updateLightPacket = new UpdateLightPacket(); updateLightPacket.chunkX = getChunkX(); updateLightPacket.chunkZ = getChunkZ(); @@ -392,7 +392,5 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka */ protected void unload() { this.loaded = false; - ChunkDataPacket.CACHE.invalidate(getIdentifier()); - UpdateLightPacket.CACHE.invalidate(getIdentifier()); } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index cdcd32edb..d44fe308a 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -124,7 +124,7 @@ public class DynamicChunk extends Chunk { if (packet != null && cachedPacketTime == getLastChangeTime()) { return packet; } - packet = new ChunkDataPacket(getIdentifier(), getLastChangeTime()); + packet = new ChunkDataPacket(); packet.biomes = biomes; packet.chunkX = chunkX; packet.chunkZ = chunkZ; diff --git a/src/main/java/net/minestom/server/item/ItemMeta.java b/src/main/java/net/minestom/server/item/ItemMeta.java index 9b6c5b211..a31518f88 100644 --- a/src/main/java/net/minestom/server/item/ItemMeta.java +++ b/src/main/java/net/minestom/server/item/ItemMeta.java @@ -1,6 +1,5 @@ package net.minestom.server.item; -import io.netty.buffer.ByteBuf; import net.kyori.adventure.text.Component; import net.minestom.server.instance.block.Block; import net.minestom.server.item.attribute.ItemAttribute; @@ -13,6 +12,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import java.nio.ByteBuffer; import java.util.*; import java.util.function.Consumer; @@ -36,7 +36,7 @@ public class ItemMeta implements TagReadable, Writeable { private final NBTCompound nbt; private String cachedSNBT; - private ByteBuf cachedBuffer; + private ByteBuffer cachedBuffer; protected ItemMeta(@NotNull ItemMetaBuilder metaBuilder) { this.damage = metaBuilder.damage; @@ -154,6 +154,6 @@ public class ItemMeta implements TagReadable, Writeable { this.cachedBuffer = w.getBuffer(); } writer.write(cachedBuffer); - this.cachedBuffer.resetReaderIndex(); + this.cachedBuffer.position(0); } } diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index cd364866e..d6b8d1fef 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -1,6 +1,5 @@ package net.minestom.server.network; -import io.netty.channel.Channel; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; @@ -24,6 +23,7 @@ import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -294,7 +294,7 @@ public final class ConnectionManager { // Close the player channel if he has been disconnected (kick) if (!player.isOnline()) { if (playerConnection instanceof NettyPlayerConnection) { - ((NettyPlayerConnection) playerConnection).getChannel().flush(); + ((NettyPlayerConnection) playerConnection).flush(); } //playerConnection.disconnect(); return; @@ -350,9 +350,12 @@ public final class ConnectionManager { final PlayerConnection playerConnection = player.getPlayerConnection(); if (playerConnection instanceof NettyPlayerConnection) { final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) playerConnection; - final Channel channel = nettyPlayerConnection.getChannel(); - channel.writeAndFlush(disconnectPacket); - channel.close(); + nettyPlayerConnection.writeAndFlush(disconnectPacket); + try { + nettyPlayerConnection.getChannel().close(); + } catch (IOException e) { + e.printStackTrace(); + } } } this.players.clear(); diff --git a/src/main/java/net/minestom/server/network/PacketProcessor.java b/src/main/java/net/minestom/server/network/PacketProcessor.java index c33f256b0..f89799602 100644 --- a/src/main/java/net/minestom/server/network/PacketProcessor.java +++ b/src/main/java/net/minestom/server/network/PacketProcessor.java @@ -1,7 +1,5 @@ package net.minestom.server.network; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.socket.SocketChannel; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.network.netty.packet.InboundPacket; @@ -16,13 +14,9 @@ import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.Readable; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - /** * Responsible for processing client packets. *

@@ -34,11 +28,8 @@ import java.util.concurrent.ConcurrentHashMap; * the same meaning as it is a login or play packet). */ public final class PacketProcessor { - private final static Logger LOGGER = LoggerFactory.getLogger(PacketProcessor.class); - private final Map connectionPlayerConnectionMap = new ConcurrentHashMap<>(); - // Protocols state private final ClientStatusPacketsHandler statusPacketsHandler; private final ClientLoginPacketsHandler loginPacketsHandler; @@ -50,33 +41,15 @@ public final class PacketProcessor { this.playPacketsHandler = new ClientPlayPacketsHandler(); } - public void process(@NotNull ChannelHandlerContext context, @NotNull InboundPacket packet) { - final SocketChannel socketChannel = (SocketChannel) context.channel(); - - // Create the netty player connection object if not existing - PlayerConnection playerConnection = connectionPlayerConnectionMap.get(context); - if (playerConnection == null) { - // Should never happen - context.close(); - return; - } - - // Prevent the client from sending packets when disconnected (kick) - if (!playerConnection.isOnline() || !socketChannel.isActive()) { - playerConnection.disconnect(); - return; - } - + public void process(@NotNull NettyPlayerConnection playerConnection, @NotNull InboundPacket packet) { // Increment packet count (checked in PlayerConnection#update) if (MinecraftServer.getRateLimit() > 0) { playerConnection.getPacketCounter().incrementAndGet(); } - - final ConnectionState connectionState = playerConnection.getConnectionState(); - final int packetId = packet.getPacketId(); BinaryReader binaryReader = new BinaryReader(packet.getBody()); + final ConnectionState connectionState = playerConnection.getConnectionState(); if (connectionState == ConnectionState.UNKNOWN) { // Should be handshake packet if (packetId == 0) { @@ -86,7 +59,6 @@ public final class PacketProcessor { } return; } - switch (connectionState) { case PLAY: final Player player = playerConnection.getPlayer(); @@ -108,34 +80,13 @@ public final class PacketProcessor { } } - /** - * Retrieves a player connection from its channel. - * - * @param context the connection context - * @return the connection of this channel, null if not found - */ - @Nullable - public PlayerConnection getPlayerConnection(ChannelHandlerContext context) { - return connectionPlayerConnectionMap.get(context); - } - - public void createPlayerConnection(@NotNull ChannelHandlerContext context) { - final PlayerConnection playerConnection = new NettyPlayerConnection((SocketChannel) context.channel()); - connectionPlayerConnectionMap.put(context, playerConnection); - } - - public PlayerConnection removePlayerConnection(@NotNull ChannelHandlerContext context) { - return connectionPlayerConnectionMap.remove(context); - } - /** * Gets the handler for client status packets. * * @return the status packets handler * @see Status packets */ - @NotNull - public ClientStatusPacketsHandler getStatusPacketsHandler() { + public @NotNull ClientStatusPacketsHandler getStatusPacketsHandler() { return statusPacketsHandler; } @@ -145,8 +96,7 @@ public final class PacketProcessor { * @return the status login handler * @see Login packets */ - @NotNull - public ClientLoginPacketsHandler getLoginPacketsHandler() { + public @NotNull ClientLoginPacketsHandler getLoginPacketsHandler() { return loginPacketsHandler; } @@ -156,8 +106,7 @@ public final class PacketProcessor { * @return the play packets handler * @see Play packets */ - @NotNull - public ClientPlayPacketsHandler getPlayPacketsHandler() { + public @NotNull ClientPlayPacketsHandler getPlayPacketsHandler() { return playPacketsHandler; } @@ -170,12 +119,10 @@ public final class PacketProcessor { */ private void safeRead(@NotNull PlayerConnection connection, @NotNull Readable readable, @NotNull BinaryReader reader) { final int readableBytes = reader.available(); - // Check if there is anything to read if (readableBytes == 0) { return; } - try { readable.read(reader); } catch (Exception e) { diff --git a/src/main/java/net/minestom/server/network/netty/NettyServer.java b/src/main/java/net/minestom/server/network/netty/NettyServer.java deleted file mode 100644 index c9adc82b9..000000000 --- a/src/main/java/net/minestom/server/network/netty/NettyServer.java +++ /dev/null @@ -1,202 +0,0 @@ -package net.minestom.server.network.netty; - -import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.ByteBufAllocator; -import io.netty.channel.*; -import io.netty.channel.epoll.Epoll; -import io.netty.channel.epoll.EpollEventLoopGroup; -import io.netty.channel.epoll.EpollServerSocketChannel; -import io.netty.channel.kqueue.KQueue; -import io.netty.channel.kqueue.KQueueEventLoopGroup; -import io.netty.channel.kqueue.KQueueServerSocketChannel; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.ServerSocketChannel; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import net.minestom.server.MinecraftServer; -import net.minestom.server.network.PacketProcessor; -import net.minestom.server.network.netty.channel.ClientChannel; -import net.minestom.server.network.netty.codec.*; -import net.minestom.server.utils.validate.Check; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; - -@ApiStatus.Internal -public final class NettyServer { - - public static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class); - public static final int BUFFER_SIZE = Integer.getInteger("minestom.channel-buffer-size", 65535); - - private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20, - 1 << 21); - - public static final String LEGACY_PING_HANDLER_NAME = "legacy-ping"; // Read - - public static final String ENCRYPT_HANDLER_NAME = "encrypt"; // Write - public static final String DECRYPT_HANDLER_NAME = "decrypt"; // Read - - public static final String GROUPED_PACKET_HANDLER_NAME = "grouped-packet"; // Write - public static final String FRAMER_HANDLER_NAME = "framer"; // Read/write - - public static final String COMPRESSOR_HANDLER_NAME = "compressor"; // Read/write - - public static final String DECODER_HANDLER_NAME = "decoder"; // Read - public static final String ENCODER_HANDLER_NAME = "encoder"; // Write - public static final String CLIENT_CHANNEL_NAME = "handler"; // Read - - private boolean initialized = false; - - private final PacketProcessor packetProcessor; - - private EventLoopGroup boss, worker; - private ServerBootstrap bootstrap; - - private ServerSocketChannel serverChannel; - - private String address; - private int port; - - public NettyServer(@NotNull PacketProcessor packetProcessor) { - this.packetProcessor = packetProcessor; - } - - /** - * Inits the server by choosing which transport layer to use, number of threads, pipeline order, etc... - *

- * Called just before {@link #start(String, int)}. - */ - public void init() { - Check.stateCondition(initialized, "Netty server has already been initialized!"); - this.initialized = true; - - Class channel; - final int workerThreadCount = MinecraftServer.getNettyThreadCount(); - - // Find boss/worker event group - { - if (Epoll.isAvailable()) { - boss = new EpollEventLoopGroup(2); - worker = new EpollEventLoopGroup(workerThreadCount); - - channel = EpollServerSocketChannel.class; - - LOGGER.info("Using epoll"); - } else if (KQueue.isAvailable()) { - boss = new KQueueEventLoopGroup(2); - worker = new KQueueEventLoopGroup(workerThreadCount); - - channel = KQueueServerSocketChannel.class; - - LOGGER.info("Using kqueue"); - } else { - boss = new NioEventLoopGroup(2); - worker = new NioEventLoopGroup(workerThreadCount); - - channel = NioServerSocketChannel.class; - - LOGGER.info("Using NIO"); - } - } - - bootstrap = new ServerBootstrap() - .group(boss, worker) - .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK) - .channel(channel); - - - bootstrap.childHandler(new ChannelInitializer() { - protected void initChannel(@NotNull SocketChannel ch) { - ChannelConfig config = ch.config(); - config.setOption(ChannelOption.TCP_NODELAY, true); - config.setOption(ChannelOption.SO_KEEPALIVE, true); - config.setOption(ChannelOption.SO_SNDBUF, BUFFER_SIZE); - config.setAllocator(ByteBufAllocator.DEFAULT); - - ChannelPipeline pipeline = ch.pipeline(); - - // First check should verify if the packet is a legacy ping (from 1.6 version and earlier) - // Removed from the pipeline later in LegacyPingHandler if unnecessary (>1.6) - pipeline.addLast(LEGACY_PING_HANDLER_NAME, new LegacyPingHandler()); - - // Used to bypass all the previous handlers by directly sending a framed buffer - pipeline.addLast(GROUPED_PACKET_HANDLER_NAME, new GroupedPacketHandler()); - - // Adds packetLength at start | Reads framed buffer - pipeline.addLast(FRAMER_HANDLER_NAME, new PacketFramer(packetProcessor)); - - // Reads buffer and create inbound packet - pipeline.addLast(DECODER_HANDLER_NAME, new PacketDecoder()); - - // Writes packet to buffer - pipeline.addLast(ENCODER_HANDLER_NAME, new PacketEncoder()); - - pipeline.addLast(CLIENT_CHANNEL_NAME, new ClientChannel(packetProcessor)); - } - }); - } - - /** - * Binds the address to start the server. - * - * @param address the server address - * @param port the server port - */ - public void start(@NotNull String address, int port) { - this.address = address; - this.port = port; - - // Bind address - try { - ChannelFuture cf = bootstrap.bind(new InetSocketAddress(address, port)).sync(); - - if (!cf.isSuccess()) { - throw new IllegalStateException("Unable to bind server at " + address + ":" + port); - } - - this.serverChannel = (ServerSocketChannel) cf.channel(); - } catch (InterruptedException ex) { - MinecraftServer.getExceptionManager().handleException(ex); - } - } - - public ServerSocketChannel getServerChannel() { - return serverChannel; - } - - /** - * Gets the address of the server. - * - * @return the server address, null if the address isn't bound yet - */ - @Nullable - public String getAddress() { - return address; - } - - /** - * Gets the port used by the server. - * - * @return the server port, 0 if the address isn't bound yet - */ - public int getPort() { - return port; - } - - /** - * Stops the server. - */ - public void stop() { - try { - this.boss.shutdownGracefully().sync(); - this.worker.shutdownGracefully().sync(); - this.serverChannel.closeFuture().sync(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java b/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java deleted file mode 100644 index 48bc2e0e1..000000000 --- a/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.minestom.server.network.netty.channel; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import net.minestom.server.MinecraftServer; -import net.minestom.server.entity.Player; -import net.minestom.server.network.ConnectionManager; -import net.minestom.server.network.PacketProcessor; -import net.minestom.server.network.netty.packet.InboundPacket; -import net.minestom.server.network.player.NettyPlayerConnection; -import net.minestom.server.network.player.PlayerConnection; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ClientChannel extends SimpleChannelInboundHandler { - - private final static Logger LOGGER = LoggerFactory.getLogger(ClientChannel.class); - - private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager(); - private final PacketProcessor packetProcessor; - - public ClientChannel(@NotNull PacketProcessor packetProcessor) { - this.packetProcessor = packetProcessor; - } - - @Override - public void channelActive(@NotNull ChannelHandlerContext ctx) { - //System.out.println("CONNECTION"); - packetProcessor.createPlayerConnection(ctx); - } - - @Override - public void channelRead0(ChannelHandlerContext ctx, InboundPacket packet) { - try { - packetProcessor.process(ctx, packet); - } catch (Exception e) { - MinecraftServer.getExceptionManager().handleException(e); - } finally { - // Check remaining - final ByteBuf body = packet.getBody(); - final int packetId = packet.getPacketId(); - - final int availableBytes = body.readableBytes(); - - if (availableBytes > 0) { - final PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx); - - LOGGER.warn("WARNING: Packet 0x{} not fully read ({} bytes left), {}", - Integer.toHexString(packetId), - availableBytes, - playerConnection); - - body.skipBytes(availableBytes); - } - } - } - - @Override - public void channelInactive(@NotNull ChannelHandlerContext ctx) { - PlayerConnection playerConnection = packetProcessor.removePlayerConnection(ctx); - if (playerConnection != null) { - // Remove the connection - playerConnection.refreshOnline(false); - Player player = playerConnection.getPlayer(); - if (player != null) { - player.remove(); - CONNECTION_MANAGER.removePlayer(playerConnection); - } - - // Release tick buffer - if (playerConnection instanceof NettyPlayerConnection) { - ((NettyPlayerConnection) playerConnection).releaseTickBuffer(); - } - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - if (!ctx.channel().isActive()) { - return; - } - - if (MinecraftServer.shouldProcessNettyErrors()) { - MinecraftServer.getExceptionManager().handleException(cause); - } - ctx.close(); - } -} diff --git a/src/main/java/net/minestom/server/network/netty/codec/GroupedPacketHandler.java b/src/main/java/net/minestom/server/network/netty/codec/GroupedPacketHandler.java deleted file mode 100644 index b11cfa118..000000000 --- a/src/main/java/net/minestom/server/network/netty/codec/GroupedPacketHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.minestom.server.network.netty.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; -import net.minestom.server.network.netty.packet.FramedPacket; - -public class GroupedPacketHandler extends MessageToByteEncoder { - - @Override - protected void encode(ChannelHandlerContext ctx, FramedPacket msg, ByteBuf out) { - } - - @Override - protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, FramedPacket msg, boolean preferDirect) { - return msg.getBody().retainedSlice(); - } - -} diff --git a/src/main/java/net/minestom/server/network/netty/codec/LegacyPingHandler.java b/src/main/java/net/minestom/server/network/netty/codec/LegacyPingHandler.java deleted file mode 100644 index 9631d5487..000000000 --- a/src/main/java/net/minestom/server/network/netty/codec/LegacyPingHandler.java +++ /dev/null @@ -1,184 +0,0 @@ -package net.minestom.server.network.netty.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import net.minestom.server.event.EventDispatcher; -import net.minestom.server.event.server.ServerListPingEvent; -import net.minestom.server.ping.ServerListPingType; -import org.jetbrains.annotations.NotNull; - -import java.nio.charset.StandardCharsets; - -public class LegacyPingHandler extends ChannelInboundHandlerAdapter { - - private ByteBuf buf; - - @Override - public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object object) { - final ByteBuf buf = (ByteBuf) object; - - if (this.buf != null) { - try { - handle1_6(ctx, buf); - } finally { - buf.release(); - } - return; - } - - buf.markReaderIndex(); - - boolean flag = true; - - try { - if (buf.readUnsignedByte() == 0xFE) { - int length = buf.readableBytes(); - - switch (length) { - case 0: - if (trySendResponse(ServerListPingType.LEGACY_UNVERSIONED, ctx)) return; - break; - case 1: - if (buf.readUnsignedByte() != 1) return; - - if (trySendResponse(ServerListPingType.LEGACY_VERSIONED, ctx)) return; - break; - default: - if (buf.readUnsignedByte() != 0x01 || buf.readUnsignedByte() != 0xFA) return; - - handle1_6(ctx, buf); - break; - } - - buf.release(); - flag = false; - } - } finally { - if (flag) { - buf.resetReaderIndex(); - ctx.channel().pipeline().remove("legacy-ping"); - ctx.fireChannelRead(object); - } - } - } - - private void handle1_6(ChannelHandlerContext ctx, ByteBuf part) { - ByteBuf buf = this.buf; - - if (buf == null) { - this.buf = buf = ctx.alloc().buffer(); - buf.markReaderIndex(); - } else { - buf.resetReaderIndex(); - } - - buf.writeBytes(part); - - if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) { - return; - } - - final String s = readLegacyString(buf); - - if (s == null) { - return; - } - - if (!s.equals("MC|PingHost")) { - removeHandler(ctx); - return; - } - - if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) { - return; - } - - int protocolVersion = buf.readByte(); - - if (readLegacyString(buf) == null) { - removeHandler(ctx); - return; - } - - buf.skipBytes(4); // port - - if (buf.isReadable()) { - removeHandler(ctx); - return; - } - - buf.release(); - - this.buf = null; - - trySendResponse(ServerListPingType.LEGACY_VERSIONED, ctx); - } - - private void removeHandler(ChannelHandlerContext ctx) { - ByteBuf buf = this.buf; - this.buf = null; - - buf.resetReaderIndex(); - ctx.pipeline().remove(this); - ctx.fireChannelRead(buf); - } - - @Override - public void handlerRemoved(ChannelHandlerContext ctx) { - if (this.buf != null) { - this.buf.release(); - this.buf = null; - } - } - - /** - * Calls a {@link ServerListPingEvent} and sends the response, if the event was not cancelled. - * - * @param version the version - * @param ctx the context - * @return {@code true} if the response was cancelled, {@code false} otherwise - */ - private static boolean trySendResponse(@NotNull ServerListPingType version, @NotNull ChannelHandlerContext ctx) { - final ServerListPingEvent event = new ServerListPingEvent(version); - EventDispatcher.call(event); - - if (event.isCancelled()) { - return true; - } else { - // get the response string - String s = version.getPingResponse(event.getResponseData()); - - // create the buffer - ByteBuf response = Unpooled.buffer(); - response.writeByte(255); - - final char[] chars = s.toCharArray(); - - response.writeShort(chars.length); - - for (char c : chars) { - response.writeChar(c); - } - - // write the buffer - ctx.pipeline().firstContext().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - - return false; - } - } - - private static String readLegacyString(ByteBuf buf) { - int size = buf.readShort() * Character.BYTES; - if (!buf.isReadable(size)) { - return null; - } - - final String result = buf.toString(buf.readerIndex(), size, StandardCharsets.UTF_16BE); - buf.skipBytes(size); - - return result; - } -} diff --git a/src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java b/src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java deleted file mode 100644 index 4ba5b0ef0..000000000 --- a/src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (2020) [artem] - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.minestom.server.network.netty.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageCodec; -import io.netty.handler.codec.DecoderException; -import net.minestom.server.utils.PacketUtils; -import net.minestom.server.utils.Utils; - -import java.util.List; -import java.util.zip.Deflater; -import java.util.zip.Inflater; - -public class PacketCompressor extends ByteToMessageCodec { - - private final static int MAX_SIZE = 2097152; - - private final int threshold; - - private final Deflater deflater = new Deflater(); - private final Inflater inflater = new Inflater(); - - public PacketCompressor(int threshold) { - this.threshold = threshold; - } - - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) { - PacketUtils.compressBuffer(deflater, from, to); - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - if (in.readableBytes() != 0) { - final int claimedUncompressedSize = Utils.readVarInt(in); - - if (claimedUncompressedSize == 0) { - out.add(in.readRetainedSlice(in.readableBytes())); - } else { - if (claimedUncompressedSize < this.threshold) { - throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is below server threshold of " + this.threshold); - } - - if (claimedUncompressedSize > MAX_SIZE) { - throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is larger than protocol maximum of " + MAX_SIZE); - } - - // TODO optimize to do not initialize arrays each time - - byte[] input = new byte[in.readableBytes()]; - in.readBytes(input); - - inflater.setInput(input); - byte[] output = new byte[claimedUncompressedSize]; - inflater.inflate(output); - inflater.reset(); - - out.add(Unpooled.wrappedBuffer(output)); - } - } - } -} diff --git a/src/main/java/net/minestom/server/network/netty/codec/PacketDecoder.java b/src/main/java/net/minestom/server/network/netty/codec/PacketDecoder.java deleted file mode 100644 index 3f5f669bc..000000000 --- a/src/main/java/net/minestom/server/network/netty/codec/PacketDecoder.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.minestom.server.network.netty.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; -import net.minestom.server.network.netty.packet.InboundPacket; -import net.minestom.server.utils.Utils; - -import java.util.List; - -public class PacketDecoder extends ByteToMessageDecoder { - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List list) { - if (buf.readableBytes() > 0) { - final int packetId = Utils.readVarInt(buf); - list.add(new InboundPacket(packetId, buf)); - } - } -} diff --git a/src/main/java/net/minestom/server/network/netty/codec/PacketEncoder.java b/src/main/java/net/minestom/server/network/netty/codec/PacketEncoder.java deleted file mode 100644 index 85781bc4d..000000000 --- a/src/main/java/net/minestom/server/network/netty/codec/PacketEncoder.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.minestom.server.network.netty.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.utils.PacketUtils; - -public class PacketEncoder extends MessageToByteEncoder { - - @Override - protected void encode(ChannelHandlerContext ctx, ServerPacket packet, ByteBuf buf) { - PacketUtils.writePacket(buf, packet); - } - -} diff --git a/src/main/java/net/minestom/server/network/netty/codec/PacketFramer.java b/src/main/java/net/minestom/server/network/netty/codec/PacketFramer.java deleted file mode 100644 index d0470e7cf..000000000 --- a/src/main/java/net/minestom/server/network/netty/codec/PacketFramer.java +++ /dev/null @@ -1,74 +0,0 @@ -package net.minestom.server.network.netty.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageCodec; -import io.netty.handler.codec.CorruptedFrameException; -import net.minestom.server.MinecraftServer; -import net.minestom.server.network.PacketProcessor; -import net.minestom.server.network.player.PlayerConnection; -import net.minestom.server.utils.PacketUtils; -import net.minestom.server.utils.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -public class PacketFramer extends ByteToMessageCodec { - - public final static Logger LOGGER = LoggerFactory.getLogger(PacketFramer.class); - - private final PacketProcessor packetProcessor; - - public PacketFramer(PacketProcessor packetProcessor) { - this.packetProcessor = packetProcessor; - } - - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) { - PacketUtils.frameBuffer(from, to); - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List out) { - buf.markReaderIndex(); - - for (int i = 0; i < 3; ++i) { - if (!buf.isReadable()) { - buf.resetReaderIndex(); - return; - } - - final byte b = buf.readByte(); - - if (b >= 0) { - buf.resetReaderIndex(); - - final int packetSize = Utils.readVarInt(buf); - - // Max packet size check - if (packetSize >= MinecraftServer.getMaxPacketSize()) { - final PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx); - if (playerConnection != null) { - final String identifier = playerConnection.getIdentifier(); - LOGGER.warn("An user ({}) sent a packet over the maximum size ({})", - identifier, packetSize); - } else { - LOGGER.warn("An unregistered user sent a packet over the maximum size ({})", packetSize); - } - ctx.close(); - } - - if (buf.readableBytes() < packetSize) { - buf.resetReaderIndex(); - return; - } - - out.add(buf.readRetainedSlice(packetSize)); - return; - } - } - - throw new CorruptedFrameException("length wider than 21-bit"); - } -} diff --git a/src/main/java/net/minestom/server/network/netty/packet/FramedPacket.java b/src/main/java/net/minestom/server/network/netty/packet/FramedPacket.java index 8197301ff..17fe194a7 100644 --- a/src/main/java/net/minestom/server/network/netty/packet/FramedPacket.java +++ b/src/main/java/net/minestom/server/network/netty/packet/FramedPacket.java @@ -1,22 +1,22 @@ package net.minestom.server.network.netty.packet; -import io.netty.buffer.ByteBuf; import org.jetbrains.annotations.NotNull; +import java.nio.ByteBuffer; + /** * Represents a packet which is already framed. (packet id+payload) + optional compression * Can be used if you want to send the exact same buffer to multiple clients without processing it more than once. */ public class FramedPacket { - private final ByteBuf body; + private final ByteBuffer body; - public FramedPacket(@NotNull ByteBuf body) { + public FramedPacket(@NotNull ByteBuffer body) { this.body = body; } - @NotNull - public ByteBuf getBody() { + public @NotNull ByteBuffer getBody() { return body; } } diff --git a/src/main/java/net/minestom/server/network/netty/packet/InboundPacket.java b/src/main/java/net/minestom/server/network/netty/packet/InboundPacket.java index 782c6731d..51ae25550 100644 --- a/src/main/java/net/minestom/server/network/netty/packet/InboundPacket.java +++ b/src/main/java/net/minestom/server/network/netty/packet/InboundPacket.java @@ -1,14 +1,14 @@ package net.minestom.server.network.netty.packet; -import io.netty.buffer.ByteBuf; import org.jetbrains.annotations.NotNull; +import java.nio.ByteBuffer; + public class InboundPacket { - private final int packetId; - private final ByteBuf body; + private final ByteBuffer body; - public InboundPacket(int id, @NotNull ByteBuf body) { + public InboundPacket(int id, @NotNull ByteBuffer body) { this.packetId = id; this.body = body; } @@ -17,8 +17,7 @@ public class InboundPacket { return packetId; } - @NotNull - public ByteBuf getBody() { + public @NotNull ByteBuffer getBody() { return body; } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java index 739c02f0a..5fc4b7e2f 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java @@ -1,7 +1,5 @@ package net.minestom.server.network.packet.server.play; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.Int2LongRBTreeMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minestom.server.MinecraftServer; @@ -15,22 +13,17 @@ import net.minestom.server.tag.Tag; import net.minestom.server.utils.Utils; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; -import net.minestom.server.utils.cache.CacheablePacket; -import net.minestom.server.utils.cache.TemporaryPacketCache; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.world.biomes.Biome; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.jglrxavpok.hephaistos.nbt.NBTException; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.*; -import java.util.concurrent.TimeUnit; -public class ChunkDataPacket implements ServerPacket, CacheablePacket { - - public static final TemporaryPacketCache CACHE = new TemporaryPacketCache(5, TimeUnit.MINUTES); +public class ChunkDataPacket implements ServerPacket { public Biome[] biomes; public int chunkX, chunkZ; @@ -42,22 +35,13 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket { private static final int MAX_BITS_PER_ENTRY = 16; private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES; - // Cacheable data - private final UUID identifier; - private final long timestamp; /** * Heightmaps NBT, as read from raw packet data. * Only filled by #read, and unused at the moment. */ public NBTCompound heightmapsNBT; - private ChunkDataPacket() { - this(new UUID(0, 0), 0); - } - - public ChunkDataPacket(@Nullable UUID identifier, long timestamp) { - this.identifier = identifier; - this.timestamp = timestamp; + public ChunkDataPacket() { } @Override @@ -65,7 +49,7 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket { writer.writeInt(chunkX); writer.writeInt(chunkZ); - ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE); + ByteBuffer blocks = ByteBuffer.allocate(MAX_BUFFER_SIZE); Int2LongRBTreeMap maskMap = new Int2LongRBTreeMap(); @@ -120,9 +104,8 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket { } // Data - writer.writeVarInt(blocks.writerIndex()); - writer.write(blocks); - blocks.release(); + writer.writeVarInt(blocks.position()); + writer.write(blocks.flip()); // Block entities if (entries == null || entries.isEmpty()) { @@ -244,19 +227,4 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket { public int getId() { return ServerPacketIdentifier.CHUNK_DATA; } - - @Override - public @NotNull TemporaryPacketCache getCache() { - return CACHE; - } - - @Override - public UUID getIdentifier() { - return identifier; - } - - @Override - public long getTimestamp() { - return timestamp; - } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/network/packet/server/play/UpdateLightPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/UpdateLightPacket.java index b5ba54808..cdffdf9d9 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/UpdateLightPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/UpdateLightPacket.java @@ -4,19 +4,12 @@ import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; -import net.minestom.server.utils.cache.CacheablePacket; -import net.minestom.server.utils.cache.TemporaryPacketCache; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -public class UpdateLightPacket implements ServerPacket, CacheablePacket { - - public static final TemporaryPacketCache CACHE = new TemporaryPacketCache(5, TimeUnit.MINUTES); +public class UpdateLightPacket implements ServerPacket { public int chunkX; public int chunkZ; @@ -32,21 +25,10 @@ public class UpdateLightPacket implements ServerPacket, CacheablePacket { public List skyLight = new ArrayList<>(); public List blockLight = new ArrayList<>(); - // Cacheable data - private final UUID identifier; - private final long timestamp; - /** * Default constructor, required for reflection operations. - * This one will make a packet that is not meant to be cached */ public UpdateLightPacket() { - this(UUID.randomUUID(), Long.MAX_VALUE); - } - - public UpdateLightPacket(@Nullable UUID identifier, long timestamp) { - this.identifier = identifier; - this.timestamp = timestamp; } @Override @@ -118,19 +100,4 @@ public class UpdateLightPacket implements ServerPacket, CacheablePacket { public int getId() { return ServerPacketIdentifier.UPDATE_LIGHT; } - - @Override - public @NotNull TemporaryPacketCache getCache() { - return CACHE; - } - - @Override - public UUID getIdentifier() { - return identifier; - } - - @Override - public long getTimestamp() { - return timestamp; - } } diff --git a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java index 9963b84c8..9ecfc5544 100644 --- a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java @@ -1,36 +1,36 @@ package net.minestom.server.network.player; -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.socket.SocketChannel; import net.kyori.adventure.translation.GlobalTranslator; import net.minestom.server.MinecraftServer; import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.entity.PlayerSkin; -import net.minestom.server.extras.mojangAuth.Decrypter; -import net.minestom.server.extras.mojangAuth.Encrypter; -import net.minestom.server.extras.mojangAuth.MojangCrypt; import net.minestom.server.network.ConnectionState; -import net.minestom.server.network.netty.NettyServer; -import net.minestom.server.network.netty.codec.PacketCompressor; +import net.minestom.server.network.PacketProcessor; import net.minestom.server.network.netty.packet.FramedPacket; +import net.minestom.server.network.netty.packet.InboundPacket; import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.login.SetCompressionPacket; -import net.minestom.server.utils.BufUtils; +import net.minestom.server.network.socket.Server; +import net.minestom.server.network.socket.Worker; import net.minestom.server.utils.PacketUtils; -import net.minestom.server.utils.cache.CacheablePacket; +import net.minestom.server.utils.Utils; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.crypto.SecretKey; +import java.io.IOException; import java.net.SocketAddress; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.DataFormatException; /** * Represents a networking connection with Netty. @@ -43,8 +43,8 @@ public class NettyPlayerConnection extends PlayerConnection { private SocketAddress remoteAddress; - private boolean encrypted = false; - private boolean compressed = false; + private volatile boolean encrypted = false; + private volatile boolean compressed = false; //Could be null. Only used for Mojang Auth private byte[] nonce = new byte[4]; @@ -64,12 +64,89 @@ public class NettyPlayerConnection extends PlayerConnection { private PlayerSkin bungeeSkin; private final Object tickBufferLock = new Object(); - private volatile ByteBuf tickBuffer = BufUtils.direct(); + private final ByteBuffer tickBuffer = ByteBuffer.allocateDirect(Server.SOCKET_BUFFER_SIZE); + private ByteBuffer cacheBuffer; public NettyPlayerConnection(@NotNull SocketChannel channel) { super(); this.channel = channel; - this.remoteAddress = channel.remoteAddress(); + try { + this.remoteAddress = channel.getRemoteAddress(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void processPackets(Worker.Context workerContext, PacketProcessor packetProcessor) { + final var readBuffer = workerContext.readBuffer; + final int limit = readBuffer.limit(); + // Read all packets + while (readBuffer.remaining() > 0) { + readBuffer.mark(); // Mark the beginning of the packet + try { + // Read packet + final int packetLength = Utils.readVarInt(readBuffer); + final int packetEnd = readBuffer.position() + packetLength; + if (packetEnd > readBuffer.limit()) { + // Integrity fail + throw new BufferUnderflowException(); + } + + readBuffer.limit(packetEnd); // Ensure that the reader doesn't exceed packet bound + + // Read protocol + var content = workerContext.contentBuffer.clear(); + { + if (!compressed) { + // Compression disabled, payload is following + content = readBuffer; + } else { + final int dataLength = Utils.readVarInt(readBuffer); + if (dataLength == 0) { + // Data is too small to be compressed, payload is following + content = readBuffer; + } else { + // Decompress to content buffer + try { + final var inflater = workerContext.inflater; + inflater.setInput(readBuffer); + inflater.inflate(content); + inflater.reset(); + } catch (DataFormatException e) { + e.printStackTrace(); + } + content.flip(); + } + } + } + + // Process packet + final int packetId = Utils.readVarInt(content); + try { + packetProcessor.process(this, new InboundPacket(packetId, content)); + } catch (Exception e) { + // Error while reading the packet + e.printStackTrace(); + break; + } + + // Return to original state (before writing) + readBuffer.limit(limit).position(packetEnd); + } catch (BufferUnderflowException e) { + readBuffer.reset(); + this.cacheBuffer = ByteBuffer.allocateDirect(readBuffer.remaining()); + this.cacheBuffer.put(readBuffer).flip(); + break; + } + } + } + + public void consumeCache(ByteBuffer buffer) { + if (cacheBuffer == null) { + return; + } + buffer.put(cacheBuffer); + this.cacheBuffer = null; } /** @@ -81,10 +158,7 @@ public class NettyPlayerConnection extends PlayerConnection { public void setEncryptionKey(@NotNull SecretKey secretKey) { Check.stateCondition(encrypted, "Encryption is already enabled!"); this.encrypted = true; - channel.pipeline().addBefore(NettyServer.GROUPED_PACKET_HANDLER_NAME, NettyServer.DECRYPT_HANDLER_NAME, - new Decrypter(MojangCrypt.getCipher(2, secretKey))); - channel.pipeline().addBefore(NettyServer.GROUPED_PACKET_HANDLER_NAME, NettyServer.ENCRYPT_HANDLER_NAME, - new Encrypter(MojangCrypt.getCipher(1, secretKey))); + // TODO } /** @@ -96,11 +170,8 @@ public class NettyPlayerConnection extends PlayerConnection { Check.stateCondition(compressed, "Compression is already enabled!"); final int threshold = MinecraftServer.getCompressionThreshold(); Check.stateCondition(threshold == 0, "Compression cannot be enabled because the threshold is equal to 0"); - this.compressed = true; writeAndFlush(new SetCompressionPacket(threshold)); - channel.pipeline().addAfter(NettyServer.FRAMER_HANDLER_NAME, NettyServer.COMPRESSOR_HANDLER_NAME, - new PacketCompressor(threshold)); } /** @@ -112,21 +183,12 @@ public class NettyPlayerConnection extends PlayerConnection { */ @Override public void sendPacket(@NotNull ServerPacket serverPacket, boolean skipTranslating) { - if (!channel.isActive()) + if (!channel.isConnected()) return; - if (shouldSendPacket(serverPacket)) { if (getPlayer() != null) { // Flush happen during #update() - if (serverPacket instanceof CacheablePacket && MinecraftServer.hasPacketCaching()) { - synchronized (tickBufferLock) { - if (tickBuffer.refCnt() == 0) - return; - CacheablePacket.writeCache(tickBuffer, serverPacket); - } - } else { - write(serverPacket, skipTranslating); - } + write(serverPacket, skipTranslating); } else { // Player is probably not logged yet writeAndFlush(serverPacket); @@ -141,93 +203,65 @@ public class NettyPlayerConnection extends PlayerConnection { public void write(@NotNull Object message, boolean skipTranslating) { if (message instanceof FramedPacket) { final FramedPacket framedPacket = (FramedPacket) message; - synchronized (tickBufferLock) { - if (tickBuffer.refCnt() == 0) - return; - final ByteBuf body = framedPacket.getBody(); - tickBuffer.writeBytes(body, body.readerIndex(), body.readableBytes()); - } + attemptWrite(framedPacket.getBody()); return; } else if (message instanceof ServerPacket) { ServerPacket serverPacket = (ServerPacket) message; - if ((MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && !skipTranslating) && getPlayer() != null && serverPacket instanceof ComponentHoldingServerPacket) { serverPacket = ((ComponentHoldingServerPacket) serverPacket).copyWithOperator(component -> GlobalTranslator.render(component, Objects.requireNonNullElseGet(getPlayer().getLocale(), MinestomAdventure::getDefaultLocale))); } - synchronized (tickBufferLock) { - if (tickBuffer.refCnt() == 0) - return; PacketUtils.writeFramedPacket(tickBuffer, serverPacket); } return; - } else if (message instanceof ByteBuf) { - synchronized (tickBufferLock) { - if (tickBuffer.refCnt() == 0) - return; - tickBuffer.writeBytes((ByteBuf) message); - } + } else if (message instanceof ByteBuffer) { + attemptWrite((ByteBuffer) message); return; } throw new UnsupportedOperationException("type " + message.getClass() + " is not supported"); } - public void writeAndFlush(@NotNull Object message) { - writeWaitingPackets(); - ChannelFuture channelFuture = channel.writeAndFlush(message); - - if (MinecraftServer.shouldProcessNettyErrors()) { - channelFuture.addListener(future -> { - if (!future.isSuccess() && channel.isActive()) { - MinecraftServer.getExceptionManager().handleException(future.cause()); - } - }); - } + public void writeAndFlush(@NotNull ServerPacket packet) { + attemptWrite(PacketUtils.createFramedPacket(packet)); + flush(); } - public void writeWaitingPackets() { - if (tickBuffer.writerIndex() == 0) { - // Nothing to write - return; - } - - // Retrieve safe copy - final ByteBuf copy; + public void attemptWrite(ByteBuffer buffer) { synchronized (tickBufferLock) { - if (tickBuffer.refCnt() == 0) - return; - copy = tickBuffer; - tickBuffer = tickBuffer.alloc().buffer(tickBuffer.writerIndex()); - } - - // Write copied buffer to netty - ChannelFuture channelFuture = channel.write(new FramedPacket(copy)); - channelFuture.addListener(future -> copy.release()); - - // Netty debug - if (MinecraftServer.shouldProcessNettyErrors()) { - channelFuture.addListener(future -> { - if (!future.isSuccess() && channel.isActive()) { - MinecraftServer.getExceptionManager().handleException(future.cause()); + try { + this.tickBuffer.put(buffer); + } catch (BufferOverflowException e) { + try { + this.channel.write(tickBuffer); + this.channel.write(buffer); + } catch (IOException ex) { + MinecraftServer.getExceptionManager().handleException(ex); + } finally { + this.tickBuffer.clear(); } - }); - } - } - - public void flush() { - final int bufferSize = tickBuffer.writerIndex(); - if (bufferSize > 0) { - if (channel.isActive()) { - writeWaitingPackets(); - channel.flush(); } } } - @NotNull + public void flush() { + if (tickBuffer.remaining() == 0) { + // Nothing to write + return; + } + // Retrieve safe copy + synchronized (tickBufferLock) { + try { + channel.write(tickBuffer); + } catch (IOException e) { + MinecraftServer.getExceptionManager().handleException(e); + } + this.tickBuffer.clear(); + } + } + @Override - public SocketAddress getRemoteAddress() { + public @NotNull SocketAddress getRemoteAddress() { return remoteAddress; } @@ -246,11 +280,14 @@ public class NettyPlayerConnection extends PlayerConnection { @Override public void disconnect() { refreshOnline(false); - this.channel.close(); + try { + this.channel.close(); + } catch (IOException e) { + e.printStackTrace(); + } } - @NotNull - public Channel getChannel() { + public @NotNull SocketChannel getChannel() { return channel; } @@ -261,8 +298,7 @@ public class NettyPlayerConnection extends PlayerConnection { * * @return the username given by the client, unchecked */ - @Nullable - public String getLoginUsername() { + public @Nullable String getLoginUsername() { return loginUsername; } @@ -323,9 +359,7 @@ public class NettyPlayerConnection extends PlayerConnection { this.protocolVersion = protocolVersion; } - - @Nullable - public UUID getBungeeUuid() { + public @Nullable UUID getBungeeUuid() { return bungeeUuid; } @@ -333,8 +367,7 @@ public class NettyPlayerConnection extends PlayerConnection { this.bungeeUuid = bungeeUuid; } - @Nullable - public PlayerSkin getBungeeSkin() { + public @Nullable PlayerSkin getBungeeSkin() { return bungeeSkin; } @@ -367,8 +400,7 @@ public class NettyPlayerConnection extends PlayerConnection { * @param messageId the message id * @return the channel linked to the message id, null if not found */ - @Nullable - public String getPluginRequestChannel(int messageId) { + public @Nullable String getPluginRequestChannel(int messageId) { return pluginRequestMap.get(messageId); } @@ -382,9 +414,6 @@ public class NettyPlayerConnection extends PlayerConnection { } public void releaseTickBuffer() { - synchronized (tickBufferLock) { - tickBuffer.release(); - } } public byte[] getNonce() { diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java index 6fc228b73..28feb23b5 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -135,7 +135,7 @@ public abstract class PlayerConnection { * @return the server address used */ public @Nullable String getServerAddress() { - return MinecraftServer.getNettyServer().getAddress(); + return MinecraftServer.getServer().getAddress(); } @@ -147,7 +147,7 @@ public abstract class PlayerConnection { * @return the server port used */ public int getServerPort() { - return MinecraftServer.getNettyServer().getPort(); + return MinecraftServer.getServer().getPort(); } /** diff --git a/src/main/java/net/minestom/server/network/socket/Server.java b/src/main/java/net/minestom/server/network/socket/Server.java new file mode 100644 index 000000000..b5c98dbf9 --- /dev/null +++ b/src/main/java/net/minestom/server/network/socket/Server.java @@ -0,0 +1,97 @@ +package net.minestom.server.network.socket; + +import net.minestom.server.network.PacketProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class Server { + public static final Logger LOGGER = LoggerFactory.getLogger(Server.class); + public static final int WORKER_COUNT = Integer.getInteger("minestom.workers", + Runtime.getRuntime().availableProcessors() * 2); + public static final int SOCKET_BUFFER_SIZE = Integer.getInteger("minestom.buffer-size", 262143); + public static final int MAX_PACKET_SIZE = 2097151; // 3 bytes var-int + public static final boolean NO_DELAY = true; + + private volatile boolean stop; + + private final List workers = new ArrayList<>(WORKER_COUNT); + private int index; + + private ServerSocketChannel serverSocket; + private String address; + private int port; + + public Server(PacketProcessor packetProcessor) throws IOException { + // Create all workers + for (int i = 0; i < WORKER_COUNT; i++) { + this.workers.add(new Worker(this, packetProcessor)); + } + } + + public void start(SocketAddress address) throws IOException { + Selector selector = Selector.open(); + this.serverSocket = ServerSocketChannel.open(); + serverSocket.bind(address); + serverSocket.configureBlocking(false); + serverSocket.register(selector, SelectionKey.OP_ACCEPT); + serverSocket.socket().setReceiveBufferSize(SOCKET_BUFFER_SIZE); + + LOGGER.info("Server starting, wait for connections"); + new Thread(() -> { + while (!stop) { + // Busy wait for connections + try { + serverTick(selector, serverSocket); + } catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + } + + public boolean isOpen() { + return !stop; + } + + public void stop() { + this.stop = true; + } + + public String getAddress() { + return address; + } + + public int getPort() { + return port; + } + + private void serverTick(Selector selector, ServerSocketChannel socketChannel) throws IOException { + selector.select(); + Set selectedKeys = selector.selectedKeys(); + for (SelectionKey key : selectedKeys) { + if (key.isAcceptable()) { + // Register socket and forward to thread + Worker thread = findWorker(); + final SocketChannel client = socketChannel.accept(); + thread.receiveConnection(client); + LOGGER.info("new connection: " + client); + } + } + selectedKeys.clear(); + } + + private Worker findWorker() { + this.index = ++index % WORKER_COUNT; + return workers.get(index); + } +} diff --git a/src/main/java/net/minestom/server/network/socket/Worker.java b/src/main/java/net/minestom/server/network/socket/Worker.java new file mode 100644 index 000000000..9e89d5396 --- /dev/null +++ b/src/main/java/net/minestom/server/network/socket/Worker.java @@ -0,0 +1,149 @@ +package net.minestom.server.network.socket; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.Player; +import net.minestom.server.network.PacketProcessor; +import net.minestom.server.network.player.NettyPlayerConnection; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +public class Worker { + private final Map connectionMap = new ConcurrentHashMap<>(); + private final Selector selector = Selector.open(); + private final PacketProcessor packetProcessor; + + public Worker(Server server, PacketProcessor packetProcessor) throws IOException { + this.packetProcessor = packetProcessor; + Thread.start(server, this::threadTick); + } + + private void threadTick(Context workerContext) { + try { + selector.select(); + } catch (IOException e) { + e.printStackTrace(); + return; + } + Set selectedKeys = selector.selectedKeys(); + for (SelectionKey key : selectedKeys) { + SocketChannel channel = (SocketChannel) key.channel(); + if (!channel.isOpen()) { + continue; + } + if (!key.isReadable()) { + // We only care about read + continue; + } + var connection = connectionMap.get(channel); + try { + ByteBuffer readBuffer = workerContext.readBuffer; + // Consume last incomplete packet + connection.consumeCache(readBuffer); + + // Read socket + if (channel.read(readBuffer) == -1) { + // EOS + throw new IOException("Disconnected"); + } + // Process data + readBuffer.flip(); + connection.processPackets(workerContext, packetProcessor); + } catch (IOException e) { + e.printStackTrace(); + try { + disconnect(connection, channel); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + } finally { + workerContext.clearBuffers(); + } + } + selectedKeys.clear(); + } + + public void receiveConnection(SocketChannel channel) throws IOException { + var connection = new NettyPlayerConnection(channel); + this.connectionMap.put(channel, connection); + register(channel); + this.selector.wakeup(); + } + + private void register(SocketChannel channel) throws IOException { + channel.configureBlocking(false); + channel.register(selector, SelectionKey.OP_READ); + var socket = channel.socket(); + socket.setSendBufferSize(Server.SOCKET_BUFFER_SIZE); + socket.setReceiveBufferSize(Server.SOCKET_BUFFER_SIZE); + socket.setTcpNoDelay(Server.NO_DELAY); + } + + private void disconnect(NettyPlayerConnection connection, SocketChannel channel) throws IOException { + // Client close + channel.close(); + connectionMap.remove(channel); + // Remove the connection + connection.refreshOnline(false); + Player player = connection.getPlayer(); + if (player != null) { + player.remove(); + MinecraftServer.getConnectionManager().removePlayer(connection); + } + } + + static class Thread extends java.lang.Thread { + private static final AtomicInteger COUNTER = new AtomicInteger(); + + private Thread(Runnable runnable) { + super(null, runnable, "worker-" + COUNTER.getAndIncrement()); + } + + protected static void start(Server server, Consumer runnable) { + new Thread(() -> { + Context workerContext = new Context(); + while (server.isOpen()) { + try { + runnable.accept(workerContext); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + } + + /** + * Contains objects that we can be shared across all the connection of a {@link Worker worker}. + */ + public static final class Context { + public final ByteBuffer readBuffer = allocate(Server.SOCKET_BUFFER_SIZE); + public final ByteBuffer writeBuffer = allocate(Server.SOCKET_BUFFER_SIZE); + /** + * Stores a single packet payload to be read. + */ + public final ByteBuffer contentBuffer = allocate(Server.MAX_PACKET_SIZE); + public final Deflater deflater = new Deflater(); + public final Inflater inflater = new Inflater(); + + public void clearBuffers() { + this.readBuffer.clear(); + this.writeBuffer.clear(); + } + + private static ByteBuffer allocate(int size) { + return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + } + } +} diff --git a/src/main/java/net/minestom/server/ping/ServerListPingType.java b/src/main/java/net/minestom/server/ping/ServerListPingType.java index 3da6e89f0..1a9e943cf 100644 --- a/src/main/java/net/minestom/server/ping/ServerListPingType.java +++ b/src/main/java/net/minestom/server/ping/ServerListPingType.java @@ -75,13 +75,13 @@ public enum ServerListPingType { * @see OpenToLAN */ public static @NotNull String getOpenToLANPing(@NotNull ResponseData data) { - return String.format(LAN_PING_FORMAT, SECTION.serialize(data.getDescription()), MinecraftServer.getNettyServer().getPort()); + return String.format(LAN_PING_FORMAT, SECTION.serialize(data.getDescription()), MinecraftServer.getServer().getPort()); } /** * Creates a legacy ping response for client versions below the Netty rewrite (1.6-). * - * @param data the response data + * @param data the response data * @param supportsVersions if the client supports recieving the versions of the server * @return the response */ @@ -99,7 +99,7 @@ public enum ServerListPingType { /** * Creates a modern ping response for client versions above the Netty rewrite (1.7+). * - * @param data the response data + * @param data the response data * @param supportsFullRgb if the client supports full RGB * @return the response */ diff --git a/src/main/java/net/minestom/server/utils/BufUtils.java b/src/main/java/net/minestom/server/utils/BufUtils.java deleted file mode 100644 index d87734a1c..000000000 --- a/src/main/java/net/minestom/server/utils/BufUtils.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.minestom.server.utils; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.PooledByteBufAllocator; - -public class BufUtils { - - private static final PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT; - - public static ByteBuf direct() { - return alloc.ioBuffer(); - } -} diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index a0e4e7f5b..cd8dc753c 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -1,6 +1,5 @@ package net.minestom.server.utils; -import io.netty.buffer.ByteBuf; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; import net.minestom.server.MinecraftServer; @@ -23,11 +22,10 @@ import java.util.Collection; import java.util.zip.Deflater; /** - * Utils class for packets. Including writing a {@link ServerPacket} into a {@link ByteBuf} + * Utils class for packets. Including writing a {@link ServerPacket} into a {@link ByteBuffer} * for network processing. */ public final class PacketUtils { - private static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager(); private static final ThreadLocal COMPRESSOR = ThreadLocal.withInitial(Deflater::new); @@ -91,9 +89,8 @@ public final class PacketUtils { // Send grouped packet... final boolean success = PACKET_LISTENER_MANAGER.processServerPacket(packet, players); if (success) { - final ByteBuf finalBuffer = createFramedPacket(packet); + final ByteBuffer finalBuffer = createFramedPacket(packet); final FramedPacket framedPacket = new FramedPacket(finalBuffer); - // Send packet to all players for (Player player : players) { if (!player.isOnline()) @@ -110,7 +107,6 @@ public final class PacketUtils { playerConnection.sendPacket(packet); } } - finalBuffer.release(); // Release last reference } } else { // Write the same packet for each individual players @@ -118,7 +114,6 @@ public final class PacketUtils { // Verify if the player should receive the packet if (playerValidator != null && !playerValidator.isValid(player)) continue; - final PlayerConnection playerConnection = player.getPlayerConnection(); playerConnection.sendPacket(packet, false); } @@ -136,23 +131,14 @@ public final class PacketUtils { } /** - * Writes a {@link ServerPacket} into a {@link ByteBuf}. + * Writes a {@link ServerPacket} into a {@link ByteBuffer}. * * @param buf the recipient of {@code packet} * @param packet the packet to write into {@code buf} */ - public static void writePacket(@NotNull ByteBuf buf, @NotNull ServerPacket packet) { + public static void writePacket(@NotNull ByteBuffer buf, @NotNull ServerPacket packet) { Utils.writeVarInt(buf, packet.getId()); - writePacketPayload(buf, packet); - } - - /** - * Writes a packet payload. - * - * @param packet the packet to write - */ - private static void writePacketPayload(@NotNull ByteBuf buffer, @NotNull ServerPacket packet) { - BinaryWriter writer = new BinaryWriter(buffer); + BinaryWriter writer = new BinaryWriter(buf); try { packet.write(writer); } catch (Exception e) { @@ -160,96 +146,42 @@ public final class PacketUtils { } } - /** - * Frames a buffer for it to be understood by a Minecraft client. - *

- * The content of {@code packetBuffer} can be either a compressed or uncompressed packet buffer, - * it depends of it the client did receive a {@link net.minestom.server.network.packet.server.login.SetCompressionPacket} packet before. - * - * @param packetBuffer the buffer containing compressed or uncompressed packet data - * @param frameTarget the buffer which will receive the framed version of {@code from} - */ - public static void frameBuffer(@NotNull ByteBuf packetBuffer, @NotNull ByteBuf frameTarget) { - final int packetSize = packetBuffer.readableBytes(); - final int headerSize = Utils.getVarIntSize(packetSize); - if (headerSize > 3) { - throw new IllegalStateException("Unable to fit " + headerSize + " into 3"); - } - - frameTarget.ensureWritable(packetSize + headerSize); - - Utils.writeVarInt(frameTarget, packetSize); - frameTarget.writeBytes(packetBuffer, packetBuffer.readerIndex(), packetSize); - } - - /** - * Compress using zlib the content of a packet. - *

- * {@code packetBuffer} needs to be the packet content without any header (if you want to use it to write a Minecraft packet). - * - * @param deflater the deflater for zlib compression - * @param packetBuffer the buffer containing all the packet fields - * @param compressionTarget the buffer which will receive the compressed version of {@code packetBuffer} - */ - public static void compressBuffer(@NotNull Deflater deflater, @NotNull ByteBuf packetBuffer, @NotNull ByteBuf compressionTarget) { - final int packetLength = packetBuffer.readableBytes(); - final boolean compression = packetLength > MinecraftServer.getCompressionThreshold(); - Utils.writeVarInt(compressionTarget, compression ? packetLength : 0); - if (compression) { - compress(deflater, packetBuffer, compressionTarget); - } else { - compressionTarget.writeBytes(packetBuffer); - } - } - - private static void compress(@NotNull Deflater deflater, @NotNull ByteBuf uncompressed, @NotNull ByteBuf compressed) { - deflater.setInput(uncompressed.nioBuffer()); - deflater.finish(); - - while (!deflater.finished()) { - ByteBuffer nioBuffer = compressed.nioBuffer(compressed.writerIndex(), compressed.writableBytes()); - compressed.writerIndex(deflater.deflate(nioBuffer) + compressed.writerIndex()); - - if (compressed.writableBytes() == 0) { - compressed.ensureWritable(8192); - } - } - - deflater.reset(); - } - - public static void writeFramedPacket(@NotNull ByteBuf buffer, + public static void writeFramedPacket(@NotNull ByteBuffer buffer, @NotNull ServerPacket serverPacket) { final int compressionThreshold = MinecraftServer.getCompressionThreshold(); // Index of the var-int containing the complete packet length - final int packetLengthIndex = Utils.writeEmpty3BytesVarInt(buffer); - final int startIndex = buffer.writerIndex(); // Index where the content starts (after length) + final int packetLengthIndex = Utils.writeEmptyVarIntHeader(buffer); + final int startIndex = buffer.position(); // Index where the content starts (after length) if (compressionThreshold > 0) { // Index of the uncompressed payload length - final int dataLengthIndex = Utils.writeEmpty3BytesVarInt(buffer); + final int dataLengthIndex = Utils.writeEmptyVarIntHeader(buffer); // Write packet - final int contentIndex = buffer.writerIndex(); + final int contentIndex = buffer.position(); writePacket(buffer, serverPacket); - final int packetSize = buffer.writerIndex() - contentIndex; + final int packetSize = buffer.position() - contentIndex; final int uncompressedLength = packetSize >= compressionThreshold ? packetSize : 0; - Utils.write3BytesVarInt(buffer, dataLengthIndex, uncompressedLength); + Utils.writeVarIntHeader(buffer, dataLengthIndex, uncompressedLength); if (uncompressedLength > 0) { // Packet large enough, compress - ByteBuf uncompressedCopy = buffer.copy(contentIndex, packetSize); - buffer.writerIndex(contentIndex); - compress(COMPRESSOR.get(), uncompressedCopy, buffer); - uncompressedCopy.release(); + ByteBuffer uncompressedCopy = buffer.duplicate().position(contentIndex).limit(contentIndex + packetSize); + buffer.position(contentIndex); + + var deflater = COMPRESSOR.get(); + deflater.setInput(uncompressedCopy); + deflater.finish(); + deflater.deflate(buffer); + deflater.reset(); } } else { // No compression, write packet id + payload writePacket(buffer, serverPacket); } // Total length - final int totalPacketLength = buffer.writerIndex() - startIndex; - Utils.write3BytesVarInt(buffer, packetLengthIndex, totalPacketLength); + final int totalPacketLength = buffer.position() - startIndex; + Utils.writeVarIntHeader(buffer, packetLengthIndex, totalPacketLength); } /** @@ -259,8 +191,8 @@ public final class PacketUtils { * Can be used if you want to store a raw buffer and send it later without the additional writing cost. * Compression is applied if {@link MinecraftServer#getCompressionThreshold()} is greater than 0. */ - public static @NotNull ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket) { - ByteBuf packetBuf = BufUtils.direct(); + public static @NotNull ByteBuffer createFramedPacket(@NotNull ServerPacket serverPacket) { + ByteBuffer packetBuf = ByteBuffer.allocate(2_000_000); writeFramedPacket(packetBuf, serverPacket); return packetBuf; } diff --git a/src/main/java/net/minestom/server/utils/Utils.java b/src/main/java/net/minestom/server/utils/Utils.java index 27549130a..7fb846b24 100644 --- a/src/main/java/net/minestom/server/utils/Utils.java +++ b/src/main/java/net/minestom/server/utils/Utils.java @@ -1,13 +1,15 @@ package net.minestom.server.utils; -import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.shorts.Short2ShortLinkedOpenHashMap; import net.minestom.server.instance.palette.Palette; import net.minestom.server.utils.binary.BinaryWriter; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import java.nio.ByteBuffer; import java.util.UUID; +@ApiStatus.Internal public final class Utils { private Utils() { @@ -21,43 +23,46 @@ public final class Utils { ? 4 : 5; } - public static void writeVarInt(@NotNull ByteBuf buf, int value) { - // Took from velocity + public static void writeVarInt(ByteBuffer buf, int value) { if ((value & (0xFFFFFFFF << 7)) == 0) { - buf.writeByte(value); + buf.put((byte) value); } else if ((value & (0xFFFFFFFF << 14)) == 0) { - int w = (value & 0x7F | 0x80) << 8 | (value >>> 7); - buf.writeShort(w); + buf.putShort((short) ((value & 0x7F | 0x80) << 8 | (value >>> 7))); } else if ((value & (0xFFFFFFFF << 21)) == 0) { - int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); - buf.writeMedium(w); + buf.put((byte) (value & 0x7F | 0x80)); + buf.put((byte) ((value >>> 7) & 0x7F | 0x80)); + buf.put((byte) (value >>> 14)); + } else if ((value & (0xFFFFFFFF << 28)) == 0) { + buf.putInt((value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16) + | ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21)); } else { - int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16 - | ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80); - buf.writeInt(w); - buf.writeByte(value >>> 28); + buf.putInt((value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16 + | ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80)); + buf.put((byte) (value >>> 28)); } } - public static void write3BytesVarInt(@NotNull ByteBuf buffer, int startIndex, int value) { - final int indexCache = buffer.writerIndex(); - buffer.writerIndex(startIndex); - final int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); - buffer.writeMedium(w); - buffer.writerIndex(indexCache); + public static void writeVarIntHeader(@NotNull ByteBuffer buffer, int startIndex, int value) { + final int indexCache = buffer.position(); + buffer.position(startIndex); + buffer.put((byte) (value & 0x7F | 0x80)); + buffer.put((byte) ((value >>> 7) & 0x7F | 0x80)); + buffer.put((byte) (value >>> 14)); + buffer.position(indexCache); } - public static int writeEmpty3BytesVarInt(@NotNull ByteBuf buffer) { - final int index = buffer.writerIndex(); - buffer.writeMedium(0); + public static int writeEmptyVarIntHeader(@NotNull ByteBuffer buffer) { + final int index = buffer.position(); + buffer.putShort((short) 0); + buffer.put((byte) 0); return index; } - public static int readVarInt(ByteBuf buf) { + public static int readVarInt(ByteBuffer buf) { int i = 0; - final int maxRead = Math.min(5, buf.readableBytes()); + final int maxRead = Math.min(5, buf.remaining()); for (int j = 0; j < maxRead; j++) { - final int k = buf.readByte(); + final int k = buf.get(); i |= (k & 0x7F) << j * 7; if ((k & 0x80) != 128) { return i; @@ -66,12 +71,12 @@ public final class Utils { throw new RuntimeException("VarInt is too big"); } - public static long readVarLong(@NotNull ByteBuf buffer) { + public static long readVarLong(@NotNull ByteBuffer buffer) { int numRead = 0; long result = 0; byte read; do { - read = buffer.readByte(); + read = buffer.get(); long value = (read & 0b01111111); result |= (value << (7 * numRead)); @@ -117,13 +122,12 @@ public final class Utils { return new UUID(uuidMost, uuidLeast); } - public static void writePaletteBlocks(ByteBuf buffer, Palette palette) { - + public static void writePaletteBlocks(ByteBuffer buffer, Palette palette) { final short blockCount = palette.getBlockCount(); final int bitsPerEntry = palette.getBitsPerEntry(); - buffer.writeShort(blockCount); - buffer.writeByte((byte) bitsPerEntry); + buffer.putShort(blockCount); + buffer.put((byte) bitsPerEntry); // Palette if (bitsPerEntry < 9) { @@ -138,7 +142,7 @@ public final class Utils { final long[] blocks = palette.getBlocks(); writeVarInt(buffer, blocks.length); for (long datum : blocks) { - buffer.writeLong(datum); + buffer.putLong(datum); } } diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java index 9ea0cfa5e..d529ae4e7 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryReader.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryReader.java @@ -1,7 +1,5 @@ package net.minestom.server.utils.binary; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.coordinate.Point; @@ -17,6 +15,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTReader; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.function.Supplier; @@ -27,16 +26,15 @@ import java.util.function.Supplier; * WARNING: not thread-safe. */ public class BinaryReader extends InputStream { - - private final ByteBuf buffer; + private final ByteBuffer buffer; private final NBTReader nbtReader = new NBTReader(this, false); - public BinaryReader(@NotNull ByteBuf buffer) { + public BinaryReader(@NotNull ByteBuffer buffer) { this.buffer = buffer; } public BinaryReader(byte[] bytes) { - this(Unpooled.wrappedBuffer(bytes)); + this(ByteBuffer.wrap(bytes)); } public int readVarInt() { @@ -48,49 +46,49 @@ public class BinaryReader extends InputStream { } public boolean readBoolean() { - return buffer.readBoolean(); + return buffer.get() == 1; } public byte readByte() { - return buffer.readByte(); + return buffer.get(); } public short readShort() { - return buffer.readShort(); + return buffer.getShort(); } public char readChar() { - return buffer.readChar(); + return buffer.getChar(); } public int readUnsignedShort() { - return buffer.readUnsignedShort(); + return buffer.getShort() & 0xFFFF; } /** * Same as readInt */ public int readInteger() { - return buffer.readInt(); + return buffer.getInt(); } /** * Same as readInteger, created for parity with BinaryWriter */ public int readInt() { - return buffer.readInt(); + return buffer.getInt(); } public long readLong() { - return buffer.readLong(); + return buffer.getLong(); } public float readFloat() { - return buffer.readFloat(); + return buffer.getFloat(); } public double readDouble() { - return buffer.readDouble(); + return buffer.getDouble(); } /** @@ -105,12 +103,9 @@ public class BinaryReader extends InputStream { */ public String readSizedString(int maxLength) { final int length = readVarInt(); - Check.stateCondition(!buffer.isReadable(length), - "Trying to read a string that is too long (wanted {0}, only have {1})", - length, - buffer.readableBytes()); - final String str = buffer.toString(buffer.readerIndex(), length, StandardCharsets.UTF_8); - buffer.skipBytes(length); + byte[] bytes = new byte[length]; + buffer.get(bytes); + final String str = new String(bytes, StandardCharsets.UTF_8); Check.stateCondition(str.length() > maxLength, "String length ({0}) was higher than the max length of {1}", length, maxLength); return str; @@ -121,10 +116,8 @@ public class BinaryReader extends InputStream { } public byte[] readBytes(int length) { - ByteBuf buf = buffer.readBytes(length); - byte[] bytes = new byte[buf.readableBytes()]; - buf.readBytes(bytes); - buf.release(); + byte[] bytes = new byte[length]; + buffer.get(bytes); return bytes; } @@ -164,14 +157,11 @@ public class BinaryReader extends InputStream { } public Point readBlockPosition() { - final long value = buffer.readLong(); - return SerializerUtils.longToBlockPosition(value); + return SerializerUtils.longToBlockPosition(buffer.getLong()); } public UUID readUuid() { - final long most = readLong(); - final long least = readLong(); - return new UUID(most, least); + return new UUID(readLong(), readLong()); } /** @@ -225,7 +215,7 @@ public class BinaryReader extends InputStream { return (T[]) result; } - public ByteBuf getBuffer() { + public ByteBuffer getBuffer() { return buffer; } @@ -236,7 +226,7 @@ public class BinaryReader extends InputStream { @Override public int available() { - return buffer.readableBytes(); + return buffer.remaining(); } public NBT readTag() throws IOException, NBTException { @@ -251,11 +241,12 @@ public class BinaryReader extends InputStream { * @param extractor the extraction code, simply call the reader's read* methods here. */ public byte[] extractBytes(Runnable extractor) { - int startingPosition = getBuffer().readerIndex(); + int startingPosition = buffer.position(); extractor.run(); - int endingPosition = getBuffer().readerIndex(); + int endingPosition = getBuffer().position(); byte[] output = new byte[endingPosition - startingPosition]; - getBuffer().getBytes(startingPosition, output); + buffer.get(output, 0, output.length); + //buffer.get(startingPosition, output); return output; } } diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java index fc20c0aad..ce800e491 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java @@ -1,8 +1,5 @@ package net.minestom.server.utils.binary; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minestom.server.MinecraftServer; @@ -16,8 +13,11 @@ import org.jglrxavpok.hephaistos.nbt.NBTWriter; import java.io.IOException; import java.io.OutputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.UUID; import java.util.function.Consumer; @@ -26,8 +26,7 @@ import java.util.function.Consumer; * WARNING: not thread-safe. */ public class BinaryWriter extends OutputStream { - - private ByteBuf buffer; + private ByteBuffer buffer; private NBTWriter nbtWriter; // Lazily initialized /** @@ -36,7 +35,7 @@ public class BinaryWriter extends OutputStream { * @param initialCapacity the initial capacity of the binary writer */ public BinaryWriter(int initialCapacity) { - this.buffer = Unpooled.buffer(initialCapacity); + this.buffer = ByteBuffer.allocate(initialCapacity); } /** @@ -44,24 +43,15 @@ public class BinaryWriter extends OutputStream { * * @param buffer the writer buffer */ - public BinaryWriter(@NotNull ByteBuf buffer) { + public BinaryWriter(@NotNull ByteBuffer buffer) { this.buffer = buffer; } - /** - * Creates a {@link BinaryWriter} from multiple buffers. - * - * @param buffers the buffers making this - */ - public BinaryWriter(@NotNull ByteBuf... buffers) { - this.buffer = Unpooled.wrappedBuffer(buffers); - } - /** * Creates a {@link BinaryWriter} with a "reasonably small initial capacity". */ public BinaryWriter() { - this.buffer = Unpooled.buffer(); + this(500); // TODO prevent OOB } /** @@ -79,7 +69,7 @@ public class BinaryWriter extends OutputStream { * @param b the boolean to write */ public void writeBoolean(boolean b) { - buffer.writeBoolean(b); + buffer.put((byte) (b ? 1 : 0)); } /** @@ -88,7 +78,7 @@ public class BinaryWriter extends OutputStream { * @param b the byte to write */ public void writeByte(byte b) { - buffer.writeByte(b); + buffer.put(b); } /** @@ -97,7 +87,7 @@ public class BinaryWriter extends OutputStream { * @param c the char to write */ public void writeChar(char c) { - buffer.writeChar(c); + buffer.putChar(c); } /** @@ -106,7 +96,7 @@ public class BinaryWriter extends OutputStream { * @param s the short to write */ public void writeShort(short s) { - buffer.writeShort(s); + buffer.putShort(s); } /** @@ -115,7 +105,7 @@ public class BinaryWriter extends OutputStream { * @param i the int to write */ public void writeInt(int i) { - buffer.writeInt(i); + buffer.putInt(i); } /** @@ -124,7 +114,7 @@ public class BinaryWriter extends OutputStream { * @param l the long to write */ public void writeLong(long l) { - buffer.writeLong(l); + buffer.putLong(l); } /** @@ -133,7 +123,7 @@ public class BinaryWriter extends OutputStream { * @param f the float to write */ public void writeFloat(float f) { - buffer.writeFloat(f); + buffer.putFloat(f); } /** @@ -142,7 +132,7 @@ public class BinaryWriter extends OutputStream { * @param d the double to write */ public void writeDouble(double d) { - buffer.writeDouble(d); + buffer.putDouble(d); } /** @@ -171,9 +161,9 @@ public class BinaryWriter extends OutputStream { * @param string the string to write */ public void writeSizedString(@NotNull String string) { - final int utf8Bytes = ByteBufUtil.utf8Bytes(string); - writeVarInt(utf8Bytes); - buffer.writeCharSequence(string, StandardCharsets.UTF_8); + final var bytes = string.getBytes(StandardCharsets.UTF_8); + writeVarInt(bytes.length); + writeBytes(bytes); } /** @@ -184,7 +174,8 @@ public class BinaryWriter extends OutputStream { * @param charset the charset to encode in */ public void writeNullTerminatedString(@NotNull String string, @NotNull Charset charset) { - buffer.writeCharSequence(string + '\0', charset); + final var bytes = (string + '\0').getBytes(charset); + writeBytes(bytes); } /** @@ -223,8 +214,8 @@ public class BinaryWriter extends OutputStream { * * @param bytes the byte array to write */ - public void writeBytes(@NotNull byte[] bytes) { - buffer.writeBytes(bytes); + public void writeBytes(byte @NotNull [] bytes) { + buffer.put(bytes); } /** @@ -296,12 +287,12 @@ public class BinaryWriter extends OutputStream { writeable.write(this); } - public void write(@NotNull BinaryWriter writer) { - this.buffer.writeBytes(writer.getBuffer()); + public void write(@NotNull ByteBuffer buffer) { + this.buffer.put(buffer); } - public void write(@NotNull ByteBuf buffer) { - this.buffer.writeBytes(buffer); + public void write(@NotNull BinaryWriter writer) { + write(writer.getBuffer()); } /** @@ -323,46 +314,51 @@ public class BinaryWriter extends OutputStream { * @return the byte array containing all the {@link BinaryWriter} data */ public byte[] toByteArray() { - byte[] bytes = new byte[buffer.readableBytes()]; - final int readerIndex = buffer.readerIndex(); - buffer.getBytes(readerIndex, bytes); + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); return bytes; } /** - * Adds a {@link BinaryWriter}'s {@link ByteBuf} at the beginning of this writer. + * Adds a {@link BinaryWriter}'s {@link ByteBuffer} at the beginning of this writer. * * @param headerWriter the {@link BinaryWriter} to add at the beginning */ public void writeAtStart(@NotNull BinaryWriter headerWriter) { // Get the buffer of the header - final ByteBuf headerBuf = headerWriter.getBuffer(); + final var headerBuf = headerWriter.getBuffer(); // Merge both the headerBuf and this buffer - final ByteBuf finalBuffer = Unpooled.wrappedBuffer(headerBuf, buffer); + final var finalBuffer = concat(headerBuf, buffer); // Change the buffer used by this writer setBuffer(finalBuffer); } /** - * Adds a {@link BinaryWriter}'s {@link ByteBuf} at the end of this writer. + * Adds a {@link BinaryWriter}'s {@link ByteBuffer} at the end of this writer. * * @param footerWriter the {@link BinaryWriter} to add at the end */ public void writeAtEnd(@NotNull BinaryWriter footerWriter) { // Get the buffer of the footer - final ByteBuf footerBuf = footerWriter.getBuffer(); + final var footerBuf = footerWriter.getBuffer(); // Merge both this buffer and the footerBuf - final ByteBuf finalBuffer = Unpooled.wrappedBuffer(buffer, footerBuf); + final var finalBuffer = concat(buffer, footerBuf); // Change the buffer used by this writer setBuffer(finalBuffer); } + public static ByteBuffer concat(final ByteBuffer... buffers) { + final ByteBuffer combined = ByteBuffer.allocate(Arrays.stream(buffers).mapToInt(Buffer::remaining).sum()); + Arrays.stream(buffers).forEach(b -> combined.put(b.duplicate())); + return combined; + } + /** * Gets the raw buffer used by this binary writer. * * @return the raw buffer */ - public @NotNull ByteBuf getBuffer() { + public @NotNull ByteBuffer getBuffer() { return buffer; } @@ -371,7 +367,7 @@ public class BinaryWriter extends OutputStream { * * @param buffer the new buffer used by this binary writer */ - public void setBuffer(ByteBuf buffer) { + public void setBuffer(ByteBuffer buffer) { this.buffer = buffer; } @@ -381,7 +377,8 @@ public class BinaryWriter extends OutputStream { } public void writeUnsignedShort(int yourShort) { - buffer.writeShort(yourShort & 0xFFFF); + // FIXME unsigned + buffer.putShort((short) (yourShort & 0xFFFF)); } /** diff --git a/src/main/java/net/minestom/server/utils/cache/CacheablePacket.java b/src/main/java/net/minestom/server/utils/cache/CacheablePacket.java deleted file mode 100644 index 348bf7424..000000000 --- a/src/main/java/net/minestom/server/utils/cache/CacheablePacket.java +++ /dev/null @@ -1,91 +0,0 @@ -package net.minestom.server.utils.cache; - -import io.netty.buffer.ByteBuf; -import net.minestom.server.network.netty.packet.FramedPacket; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.utils.PacketUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.UUID; - -/** - * Implemented by {@link ServerPacket server packets} which can be temporary cached in memory to be re-sent later - * without having to go through all the writing and compression. - *

- * {@link #getIdentifier()} is to differentiate this packet from the others of the same type. - */ -public interface CacheablePacket { - - /** - * Gets the cache linked to this packet. - *

- * WARNING: the cache needs to be shared between all the object instances, tips is to make it static. - * - * @return the temporary packet cache - */ - @NotNull TemporaryPacketCache getCache(); - - /** - * Gets the identifier of this packet. - *

- * Used to verify if this packet is already cached or not. - * - * @return this packet identifier, null to prevent caching - */ - @Nullable UUID getIdentifier(); - - /** - * Gets the last time this packet changed. - * - * @return the last packet update time in milliseconds - */ - long getTimestamp(); - - static @Nullable FramedPacket getCache(@NotNull ServerPacket serverPacket) { - if (!(serverPacket instanceof CacheablePacket)) - return null; - - final CacheablePacket cacheablePacket = (CacheablePacket) serverPacket; - final UUID identifier = cacheablePacket.getIdentifier(); - if (identifier == null) { - // This packet explicitly asks to do not retrieve the cache - return null; - } else { - final long timestamp = cacheablePacket.getTimestamp(); - // Try to retrieve the cached buffer - TemporaryCache temporaryCache = cacheablePacket.getCache(); - TimedBuffer timedBuffer = temporaryCache.retrieve(identifier); - - // Update the buffer if non-existent or outdated - final boolean shouldUpdate = timedBuffer == null || - timestamp > timedBuffer.getTimestamp(); - - if (shouldUpdate) { - // Buffer freed by guava cache #removalListener - final ByteBuf buffer = PacketUtils.createFramedPacket(serverPacket); - timedBuffer = new TimedBuffer(buffer, timestamp); - temporaryCache.cache(identifier, timedBuffer); - } - - return new FramedPacket(timedBuffer.getBuffer()); - } - } - - static void writeCache(@NotNull ByteBuf buffer, @NotNull ServerPacket serverPacket) { - FramedPacket framedPacket = CacheablePacket.getCache(serverPacket); - if (framedPacket == null) { - PacketUtils.writeFramedPacket(buffer, serverPacket); - return; - } - final ByteBuf body = framedPacket.getBody(); - synchronized (body) { - if (framedPacket.getBody().refCnt() != 0) { - buffer.writeBytes(body, body.readerIndex(), body.readableBytes()); - } else { - PacketUtils.writeFramedPacket(buffer, serverPacket); - } - } - } - -} diff --git a/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java b/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java deleted file mode 100644 index beb1bff2b..000000000 --- a/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.minestom.server.utils.cache; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.RemovalListener; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -/** - * Cache objects with a timeout. - * - * @param the object type to cache - */ -public class TemporaryCache { - - private final Cache cache; - - /** - * Creates a new temporary cache. - * - * @param duration the time before considering an object unused - */ - public TemporaryCache(long duration, TimeUnit timeUnit, RemovalListener removalListener) { - this.cache = Caffeine.newBuilder() - .expireAfterWrite(duration, timeUnit) - .removalListener(removalListener) - .build(); - } - - /** - * Caches an object. - * - * @param identifier the object identifier - * @param value the object to cache - */ - public void cache(@NotNull UUID identifier, T value) { - this.cache.put(identifier, value); - } - - public void invalidate(@NotNull UUID identifier) { - this.cache.invalidate(identifier); - } - - /** - * Retrieves an object from cache. - * - * @param identifier the object identifier - * @return the retrieved object or null if not found - */ - @Nullable - public T retrieve(@NotNull UUID identifier) { - return cache.getIfPresent(identifier); - } -} diff --git a/src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java b/src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java deleted file mode 100644 index 7d805e904..000000000 --- a/src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.minestom.server.utils.cache; - -import io.netty.buffer.ByteBuf; - -import java.util.concurrent.TimeUnit; - -public class TemporaryPacketCache extends TemporaryCache { - public TemporaryPacketCache(long duration, TimeUnit timeUnit) { - super(duration, timeUnit, (key, value, cause) -> { - if (value == null) - return; - final ByteBuf buffer = value.getBuffer(); - synchronized (buffer) { - buffer.release(); - } - }); - } -} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/utils/cache/TimedBuffer.java b/src/main/java/net/minestom/server/utils/cache/TimedBuffer.java deleted file mode 100644 index a74c8f764..000000000 --- a/src/main/java/net/minestom/server/utils/cache/TimedBuffer.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.minestom.server.utils.cache; - -import io.netty.buffer.ByteBuf; -import org.jetbrains.annotations.NotNull; - -/** - * Object containing a {@link ByteBuf buffer} and its timestamp. - * Used for packet-caching to use the most recent. - */ -public class TimedBuffer { - - private final ByteBuf buffer; - private final long timestamp; - - public TimedBuffer(@NotNull ByteBuf buffer, long timestamp) { - this.buffer = buffer; - this.timestamp = timestamp; - } - - @NotNull - public ByteBuf getBuffer() { - return buffer; - } - - public long getTimestamp() { - return timestamp; - } -} diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java index 1343f44e9..1ea170024 100644 --- a/src/test/java/demo/Main.java +++ b/src/test/java/demo/Main.java @@ -24,6 +24,7 @@ public class Main { public static void main(String[] args) { MinecraftServer minecraftServer = MinecraftServer.init(); + MinecraftServer.setCompressionThreshold(0); BlockManager blockManager = MinecraftServer.getBlockManager();