From e2e3e389710ff62cb025d7469809ef538700b221 Mon Sep 17 00:00:00 2001 From: artem Date: Sun, 28 Jun 2020 23:23:52 +0500 Subject: [PATCH 1/5] Removed netty-all, because you don't need HTTP codecs and etc --- build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 6c434da26..c7b36886f 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,9 @@ repositories { dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' - // https://mvnrepository.com/artifact/io.netty/netty-all - api group: 'io.netty', name: 'netty-all', version: '4.1.50.Final' + api 'io.netty:netty-handler:4.1.45.Final' + api 'io.netty:netty-codec:4.1.45.Final' + implementation 'io.netty:netty-transport-native-epoll:4.1.45.Final:linux-x86_64' api 'com.github.jhg023:Pbbl:1.0.2' From 6a6309377f82c7083c69792a2b5f4d650e9385e6 Mon Sep 17 00:00:00 2001 From: artem Date: Sun, 28 Jun 2020 23:29:36 +0500 Subject: [PATCH 2/5] Epoll support (optimization for linux) --- .../server/network/netty/NettyServer.java | 79 +++++++++++-------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/src/main/java/net/minestom/server/network/netty/NettyServer.java b/src/main/java/net/minestom/server/network/netty/NettyServer.java index b652a4cfd..c78ff6808 100644 --- a/src/main/java/net/minestom/server/network/netty/NettyServer.java +++ b/src/main/java/net/minestom/server/network/netty/NettyServer.java @@ -1,11 +1,12 @@ package net.minestom.server.network.netty; import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.EventLoopGroup; +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.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.network.PacketProcessor; @@ -16,15 +17,44 @@ import java.net.InetSocketAddress; public class NettyServer { - private PacketProcessor packetProcessor; - private EventLoopGroup group; + private final PacketProcessor packetProcessor; + + private final EventLoopGroup boss, worker; + private final ServerBootstrap bootstrap; + + private ServerSocketChannel serverChannel; private String address; private int port; public NettyServer(PacketProcessor packetProcessor) { this.packetProcessor = packetProcessor; - this.group = new NioEventLoopGroup(); + + Class channel; + + if (Epoll.isAvailable()) { + boss = new EpollEventLoopGroup(2); + worker = new EpollEventLoopGroup(); + + channel = EpollServerSocketChannel.class; + } else { + boss = new NioEventLoopGroup(2); + worker = new EpollEventLoopGroup(); + + channel = NioServerSocketChannel.class; + } + + bootstrap = new ServerBootstrap(); + bootstrap.group(boss, worker); + bootstrap.channel(channel); + + bootstrap.childHandler(new ChannelInitializer() { + protected void initChannel(SocketChannel socketChannel) { + socketChannel.pipeline().addLast(new NettyDecoder()); + socketChannel.pipeline().addLast(new ClientChannel(packetProcessor)); + } + }); + } public void start(String address, int port) { @@ -32,27 +62,15 @@ public class NettyServer { this.port = port; try { - ServerBootstrap serverBootstrap = new ServerBootstrap(); - serverBootstrap.group(group); - serverBootstrap.channel(NioServerSocketChannel.class); - serverBootstrap.localAddress(new InetSocketAddress(address, port)); + ChannelFuture cf = bootstrap.bind(new InetSocketAddress(address, port)).sync(); - serverBootstrap.childHandler(new ChannelInitializer() { - protected void initChannel(SocketChannel socketChannel) { - socketChannel.pipeline().addLast(new NettyDecoder()); - socketChannel.pipeline().addLast(new ClientChannel(packetProcessor)); - } - }); - ChannelFuture channelFuture = serverBootstrap.bind().sync(); - channelFuture.channel().closeFuture().sync(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - group.shutdownGracefully().sync(); - } catch (InterruptedException e) { - e.printStackTrace(); + if (!cf.isSuccess()) { + throw new IllegalStateException("Unable to bind server at " + address + ":" + port); } + + serverChannel = (ServerSocketChannel) cf.channel(); + } catch (InterruptedException ex) { + ex.printStackTrace(); } } @@ -65,10 +83,9 @@ public class NettyServer { } public void stop() { - try { - group.shutdownGracefully().sync(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + serverChannel.close(); + + worker.shutdownGracefully(); + boss.shutdownGracefully(); } } From f8dceb51b4f2e77aa3c7c568c02f20a24cc1cf9c Mon Sep 17 00:00:00 2001 From: artem Date: Sun, 28 Jun 2020 23:31:45 +0500 Subject: [PATCH 3/5] TCP_NODELAY --- .../server/network/netty/NettyServer.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/minestom/server/network/netty/NettyServer.java b/src/main/java/net/minestom/server/network/netty/NettyServer.java index c78ff6808..0f4ad6542 100644 --- a/src/main/java/net/minestom/server/network/netty/NettyServer.java +++ b/src/main/java/net/minestom/server/network/netty/NettyServer.java @@ -17,8 +17,6 @@ import java.net.InetSocketAddress; public class NettyServer { - private final PacketProcessor packetProcessor; - private final EventLoopGroup boss, worker; private final ServerBootstrap bootstrap; @@ -28,8 +26,6 @@ public class NettyServer { private int port; public NettyServer(PacketProcessor packetProcessor) { - this.packetProcessor = packetProcessor; - Class channel; if (Epoll.isAvailable()) { @@ -49,12 +45,15 @@ public class NettyServer { bootstrap.channel(channel); bootstrap.childHandler(new ChannelInitializer() { - protected void initChannel(SocketChannel socketChannel) { - socketChannel.pipeline().addLast(new NettyDecoder()); - socketChannel.pipeline().addLast(new ClientChannel(packetProcessor)); + protected void initChannel(SocketChannel ch) { + ChannelConfig config = ch.config(); + config.setOption(ChannelOption.TCP_NODELAY, true); + + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new NettyDecoder()); + pipeline.addLast(new ClientChannel(packetProcessor)); } }); - } public void start(String address, int port) { From 9502511095faf02596044a0cd19fad6ce8623c01 Mon Sep 17 00:00:00 2001 From: artem Date: Sun, 28 Jun 2020 23:35:35 +0500 Subject: [PATCH 4/5] Mark classes to delete --- .../net/minestom/server/network/netty/packet/PacketHandler.java | 1 + .../java/net/minestom/server/network/packet/PacketReader.java | 1 + .../java/net/minestom/server/network/packet/PacketWriter.java | 1 + 3 files changed, 3 insertions(+) diff --git a/src/main/java/net/minestom/server/network/netty/packet/PacketHandler.java b/src/main/java/net/minestom/server/network/netty/packet/PacketHandler.java index f5816c0ca..4f131125a 100644 --- a/src/main/java/net/minestom/server/network/netty/packet/PacketHandler.java +++ b/src/main/java/net/minestom/server/network/netty/packet/PacketHandler.java @@ -2,6 +2,7 @@ package net.minestom.server.network.netty.packet; import io.netty.buffer.ByteBuf; +// TODO dedlete public class PacketHandler { public int length; diff --git a/src/main/java/net/minestom/server/network/packet/PacketReader.java b/src/main/java/net/minestom/server/network/packet/PacketReader.java index 0c1b516dc..4ad8e94a4 100644 --- a/src/main/java/net/minestom/server/network/packet/PacketReader.java +++ b/src/main/java/net/minestom/server/network/packet/PacketReader.java @@ -9,6 +9,7 @@ import net.minestom.server.utils.Utils; import java.util.UUID; +// TODO delete public class PacketReader { private ByteBuf buffer; diff --git a/src/main/java/net/minestom/server/network/packet/PacketWriter.java b/src/main/java/net/minestom/server/network/packet/PacketWriter.java index 8dd2709e8..dbb6eba41 100644 --- a/src/main/java/net/minestom/server/network/packet/PacketWriter.java +++ b/src/main/java/net/minestom/server/network/packet/PacketWriter.java @@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.function.Consumer; +// TODO delete public class PacketWriter { private ByteArrayOutputStream output = new ByteArrayOutputStream(); From 53410c737abfaef5b03a62ed8e757d0a3d5f7c8f Mon Sep 17 00:00:00 2001 From: artem Date: Mon, 29 Jun 2020 01:07:48 +0500 Subject: [PATCH 5/5] getVarIntSize optimization, Compression, LegacyPing --- .../net/minestom/server/MinecraftServer.java | 1 + .../server/network/PacketProcessor.java | 24 +-- .../server/network/netty/NettyServer.java | 20 +- .../network/netty/channel/ClientChannel.java | 42 ++-- .../network/netty/channel/NettyDecoder.java | 51 ----- .../netty/codec/LegacyPingHandler.java | 187 ++++++++++++++++++ .../network/netty/codec/PacketCompressor.java | 101 ++++++++++ .../network/netty/codec/PacketDecoder.java | 19 ++ .../network/netty/codec/PacketEncoder.java | 16 ++ .../network/netty/codec/PacketFramer.java | 57 ++++++ .../network/netty/packet/InboundPacket.java | 15 ++ .../network/netty/packet/PacketHandler.java | 11 -- .../packet/client/login/LoginStartPacket.java | 8 + .../server/login/SetCompressionPacket.java | 23 +++ .../network/player/FakePlayerConnection.java | 7 +- .../network/player/NettyPlayerConnection.java | 32 +-- .../network/player/PlayerConnection.java | 2 + .../minestom/server/utils/PacketUtils.java | 26 +-- .../java/net/minestom/server/utils/Utils.java | 21 +- .../server/utils/buffer/BufferWrapper.java | 2 +- 20 files changed, 520 insertions(+), 145 deletions(-) delete mode 100644 src/main/java/net/minestom/server/network/netty/channel/NettyDecoder.java create mode 100644 src/main/java/net/minestom/server/network/netty/codec/LegacyPingHandler.java create mode 100644 src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java create mode 100644 src/main/java/net/minestom/server/network/netty/codec/PacketDecoder.java create mode 100644 src/main/java/net/minestom/server/network/netty/codec/PacketEncoder.java create mode 100644 src/main/java/net/minestom/server/network/netty/codec/PacketFramer.java create mode 100644 src/main/java/net/minestom/server/network/netty/packet/InboundPacket.java delete mode 100644 src/main/java/net/minestom/server/network/netty/packet/PacketHandler.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/login/SetCompressionPacket.java diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 0b954bc49..66c2525a0 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -62,6 +62,7 @@ public class MinecraftServer { // Config public static final int CHUNK_VIEW_DISTANCE = 10; public static final int ENTITY_VIEW_DISTANCE = 5; + public static final int COMPRESSION_THRESHOLD = 256; // Can be modified at performance cost when decreased private static final int MS_TO_SEC = 1000; public static final int TICK_MS = MS_TO_SEC / 20; diff --git a/src/main/java/net/minestom/server/network/PacketProcessor.java b/src/main/java/net/minestom/server/network/PacketProcessor.java index 149798afe..5d8669ef3 100644 --- a/src/main/java/net/minestom/server/network/PacketProcessor.java +++ b/src/main/java/net/minestom/server/network/PacketProcessor.java @@ -2,8 +2,10 @@ package net.minestom.server.network; import io.netty.buffer.ByteBuf; 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; import net.minestom.server.network.packet.PacketReader; import net.minestom.server.network.packet.client.ClientPlayPacket; import net.minestom.server.network.packet.client.ClientPreplayPacket; @@ -40,19 +42,22 @@ public class PacketProcessor { private List printBlackList = Arrays.asList(17, 18, 19); - public void process(ChannelHandlerContext channel, ByteBuf buffer, int id, int offset) { - PlayerConnection playerConnection = - connectionPlayerConnectionMap.computeIfAbsent(channel, c -> new NettyPlayerConnection(channel)); + public void process(ChannelHandlerContext channel, InboundPacket packet) { + PlayerConnection playerConnection = connectionPlayerConnectionMap.computeIfAbsent( + channel, c -> new NettyPlayerConnection((SocketChannel) channel.channel()) + ); + ConnectionState connectionState = playerConnection.getConnectionState(); + //if (!printBlackList.contains(id)) { //System.out.println("RECEIVED ID: 0x" + Integer.toHexString(id) + " State: " + connectionState); //} - PacketReader packetReader = new PacketReader(buffer); + PacketReader packetReader = new PacketReader(packet.body); if (connectionState == ConnectionState.UNKNOWN) { // Should be handshake packet - if (id == 0) { + if (packet.packetId == 0) { HandshakePacket handshakePacket = new HandshakePacket(); handshakePacket.read(packetReader); handshakePacket.process(playerConnection, connectionManager); @@ -63,26 +68,23 @@ public class PacketProcessor { switch (connectionState) { case PLAY: Player player = playerConnection.getPlayer(); - ClientPlayPacket playPacket = (ClientPlayPacket) playPacketsHandler.getPacketInstance(id); + ClientPlayPacket playPacket = (ClientPlayPacket) playPacketsHandler.getPacketInstance(packet.packetId); playPacket.read(packetReader); player.addPacketToQueue(playPacket); break; case LOGIN: - ClientPreplayPacket loginPacket = (ClientPreplayPacket) loginPacketsHandler.getPacketInstance(id); + ClientPreplayPacket loginPacket = (ClientPreplayPacket) loginPacketsHandler.getPacketInstance(packet.packetId); loginPacket.read(packetReader); loginPacket.process(playerConnection, connectionManager); break; case STATUS: - ClientPreplayPacket statusPacket = (ClientPreplayPacket) statusPacketsHandler.getPacketInstance(id); + ClientPreplayPacket statusPacket = (ClientPreplayPacket) statusPacketsHandler.getPacketInstance(packet.packetId); statusPacket.read(packetReader); statusPacket.process(playerConnection, connectionManager); break; - case UNKNOWN: - // Ignore packet (unexpected) - break; } } diff --git a/src/main/java/net/minestom/server/network/netty/NettyServer.java b/src/main/java/net/minestom/server/network/netty/NettyServer.java index 0f4ad6542..791ff7fd9 100644 --- a/src/main/java/net/minestom/server/network/netty/NettyServer.java +++ b/src/main/java/net/minestom/server/network/netty/NettyServer.java @@ -11,7 +11,10 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import net.minestom.server.network.PacketProcessor; import net.minestom.server.network.netty.channel.ClientChannel; -import net.minestom.server.network.netty.channel.NettyDecoder; +import net.minestom.server.network.netty.codec.LegacyPingHandler; +import net.minestom.server.network.netty.codec.PacketDecoder; +import net.minestom.server.network.netty.codec.PacketEncoder; +import net.minestom.server.network.netty.codec.PacketFramer; import java.net.InetSocketAddress; @@ -50,8 +53,19 @@ public class NettyServer { config.setOption(ChannelOption.TCP_NODELAY, true); ChannelPipeline pipeline = ch.pipeline(); - pipeline.addLast(new NettyDecoder()); - pipeline.addLast(new ClientChannel(packetProcessor)); + + pipeline.addLast("legacy-ping", new LegacyPingHandler()); + + // Adds packetLength at start | Reads framed bytebuf + pipeline.addLast("framer", new PacketFramer()); + + // Reads bytebuf and creating inbound packet + pipeline.addLast("decoder", new PacketDecoder()); + + // Writes packet to bytebuf + pipeline.addLast("encoder", new PacketEncoder()); + + pipeline.addLast("handler", new ClientChannel(packetProcessor)); } }); } 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 index 62e043198..dc4edbbb2 100644 --- a/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java +++ b/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java @@ -1,22 +1,18 @@ package net.minestom.server.network.netty.channel; -import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; +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.PacketHandler; -import net.minestom.server.network.packet.PacketReader; -import net.minestom.server.network.packet.client.status.LegacyServerListPingPacket; +import net.minestom.server.network.netty.packet.InboundPacket; import net.minestom.server.network.player.PlayerConnection; -import net.minestom.server.utils.Utils; -public class ClientChannel extends ChannelInboundHandlerAdapter { +public class ClientChannel extends SimpleChannelInboundHandler { - private ConnectionManager connectionManager = MinecraftServer.getConnectionManager(); - private PacketProcessor packetProcessor; + private final ConnectionManager connectionManager = MinecraftServer.getConnectionManager(); + private final PacketProcessor packetProcessor; public ClientChannel(PacketProcessor packetProcessor) { this.packetProcessor = packetProcessor; @@ -28,26 +24,20 @@ public class ClientChannel extends ChannelInboundHandlerAdapter { } @Override - public void channelRead(ChannelHandlerContext ctx, Object obj) { - PacketHandler packetHandler = (PacketHandler) obj; + public void channelRead0(ChannelHandlerContext ctx, InboundPacket packet) { + try { + packetProcessor.process(ctx, packet); + } finally { + int availableBytes = packet.body.readableBytes(); - int packetLength = packetHandler.length; - ByteBuf buffer = packetHandler.buffer; + if (availableBytes > 0) { + // TODO log4j2 + System.out.println("Packet 0x" + Integer.toHexString(packet.packetId) + + " not fully read (" + availableBytes + " bytes left)"); - if (packetLength == 0xFE) { // Legacy server ping - LegacyServerListPingPacket legacyServerListPingPacket = new LegacyServerListPingPacket(); - legacyServerListPingPacket.read(new PacketReader(buffer)); - legacyServerListPingPacket.process(null, null); - return; + packet.body.skipBytes(availableBytes); + } } - - final int varIntLength = Utils.lengthVarInt(packetLength); - int packetId = Utils.readVarInt(buffer); - - int offset = varIntLength + Utils.lengthVarInt(packetId); - packetProcessor.process(ctx, buffer, packetId, offset); - - buffer.release(); } @Override diff --git a/src/main/java/net/minestom/server/network/netty/channel/NettyDecoder.java b/src/main/java/net/minestom/server/network/netty/channel/NettyDecoder.java deleted file mode 100644 index 868dfb8d3..000000000 --- a/src/main/java/net/minestom/server/network/netty/channel/NettyDecoder.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.minestom.server.network.netty.channel; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; -import net.minestom.server.network.netty.packet.PacketHandler; -import net.minestom.server.utils.Utils; - -import java.util.List; - -public class NettyDecoder extends ByteToMessageDecoder { - - private int bytesToRead; - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List out) { - - // Fix cut packet - if (bytesToRead != 0) { - int readable = buffer.readableBytes(); - if (readable >= bytesToRead) { - PacketHandler packetHandler = new PacketHandler(); - packetHandler.length = bytesToRead; - packetHandler.buffer = buffer.readBytes(bytesToRead); - out.add(packetHandler); - bytesToRead = 0; - } - return; - } - - int packetLength = Utils.readVarInt(buffer); - PacketHandler packetHandler = new PacketHandler(); - packetHandler.length = packetLength; - if (packetLength == 0xFE) { // Legacy server ping - packetHandler.buffer = buffer.readBytes(2); - } else { - int readable = buffer.readableBytes(); - if (readable < packetLength) { - // Wait for bytes to arrive - bytesToRead = packetLength; - return; - } else { - // There are enough bytes, read them - packetHandler.buffer = buffer.readBytes(packetLength); - bytesToRead = 0; - } - } - - out.add(packetHandler); - } -} 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 new file mode 100644 index 000000000..9acb04800 --- /dev/null +++ b/src/main/java/net/minestom/server/network/netty/codec/LegacyPingHandler.java @@ -0,0 +1,187 @@ +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 java.nio.charset.StandardCharsets; + +// Copied from original minecraft :( +public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + private ByteBuf buf; + + @Override + public void channelRead(ChannelHandlerContext ctx, Object object) throws Exception { + ByteBuf buf = (ByteBuf) object; + + if (this.buf != null) { + try { + readLegacy1_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: + this.writeResponse(ctx, this.createResponse(formatResponse(-2))); + break; + case 1: + if (buf.readUnsignedByte() != 1) { + return; + } + + this.writeResponse(ctx, this.createResponse(formatResponse(-1))); + break; + default: + if (buf.readUnsignedByte() != 0x01 || buf.readUnsignedByte() != 0xFA) return; + + readLegacy1_6(ctx, buf); + break; + } + + buf.release(); + flag = false; + } + } finally { + if (flag) { + buf.resetReaderIndex(); + ctx.channel().pipeline().remove("legacy-ping"); + ctx.fireChannelRead(object); + } + } + } + + private static String readLegacyString(ByteBuf buf) { + int size = buf.readShort() * Character.BYTES; + if (!buf.isReadable(size)) { + return null; + } + + String result = buf.toString(buf.readerIndex(), size, StandardCharsets.UTF_16BE); + buf.skipBytes(size); + + return result; + } + + private void readLegacy1_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; + } + + 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; + + this.writeResponse(ctx, this.createResponse(formatResponse(protocolVersion))); + } + + private String formatResponse(int playerProtocol) { + // todo server motd, online and slots + final String motd = "Minestom"; + final String version = "1.15.2"; + final int online = 0; + final int max = 1; + final int protocol = 578; // 1.15.2 + + if (playerProtocol == -2) { + return String.format( + "%s\u00a7%d\u00a7%d", + motd, online, max + ); + } + + return String.format( + "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", + protocol, version, motd, online, max + ); + } + + 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; + } + } + + private void writeResponse(ChannelHandlerContext ctx, ByteBuf buf) { + ctx.pipeline().firstContext().writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE); + } + + private ByteBuf createResponse(String s) { + ByteBuf response = Unpooled.buffer(); + response.writeByte(255); + + char[] chars = s.toCharArray(); + + response.writeShort(chars.length); + + for (char c : chars) { + response.writeChar(c); + } + + return response; + } +} 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 new file mode 100644 index 000000000..8c9deacb1 --- /dev/null +++ b/src/main/java/net/minestom/server/network/netty/codec/PacketCompressor.java @@ -0,0 +1,101 @@ +/* + * 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.Utils; + +import java.util.List; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +// TODO Optimize +public class PacketCompressor extends ByteToMessageCodec { + + private final byte[] buffer = new byte[8192]; + + private final int threshold; + + private final Inflater inflater; + private final Deflater deflater; + + public PacketCompressor(int threshold) { + this.inflater = new Inflater(); + this.deflater = new Deflater(); + + this.threshold = threshold; + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) { + int i = from.readableBytes(); + + if (i < this.threshold) { + Utils.writeVarIntBuf(to, 0); + to.writeBytes(from); + } else { + byte[] abyte = new byte[i]; + from.readBytes(abyte); + + Utils.writeVarIntBuf(to, abyte.length); + this.deflater.setInput(abyte, 0, i); + this.deflater.finish(); + + while (!this.deflater.finished()) { + int j = this.deflater.deflate(this.buffer); + + to.writeBytes(this.buffer, 0, j); + } + + this.deflater.reset(); + } + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List out) throws Exception { + if (buf.readableBytes() != 0) { + int i = Utils.readVarInt(buf); + + if (i == 0) { + out.add(buf.readRetainedSlice(buf.readableBytes())); + } else { + if (i < this.threshold) { + throw new DecoderException("Badly compressed packet - size of " + i + " is below server threshold of " + this.threshold); + } + + if (i > 2097152) { + throw new DecoderException("Badly compressed packet - size of " + i + " is larger than protocol maximum of 2097152"); + } + + byte[] abyte = new byte[buf.readableBytes()]; + buf.readBytes(abyte); + + this.inflater.setInput(abyte); + byte[] abyte1 = new byte[i]; + + this.inflater.inflate(abyte1); + out.add(Unpooled.wrappedBuffer(abyte1)); + + this.inflater.reset(); + } + } + } +} 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 new file mode 100644 index 000000000..b5838c68c --- /dev/null +++ b/src/main/java/net/minestom/server/network/netty/codec/PacketDecoder.java @@ -0,0 +1,19 @@ +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) throws Exception { + if (buf.readableBytes() > 0) { + list.add(new InboundPacket(Utils.readVarInt(buf), 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 new file mode 100644 index 000000000..c23c19a85 --- /dev/null +++ b/src/main/java/net/minestom/server/network/netty/codec/PacketEncoder.java @@ -0,0 +1,16 @@ +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) throws Exception { + 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 new file mode 100644 index 000000000..6d1e9e9fc --- /dev/null +++ b/src/main/java/net/minestom/server/network/netty/codec/PacketFramer.java @@ -0,0 +1,57 @@ +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.utils.Utils; + +import java.util.List; + +public class PacketFramer extends ByteToMessageCodec { + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) { + int packetSize = from.readableBytes(); + int headerSize = Utils.getVarIntSize(packetSize); + + if (headerSize > 3) { + throw new IllegalStateException("Unable to fit " + headerSize + " into 3"); + } + + to.ensureWritable(packetSize + headerSize); + + Utils.writeVarIntBuf(to, packetSize); + to.writeBytes(from, from.readerIndex(), packetSize); + } + + @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; + } + + byte b = buf.readByte(); + + if (b >= 0) { + buf.resetReaderIndex(); + + int j = Utils.readVarInt(buf); + + if (buf.readableBytes() < j) { + buf.resetReaderIndex(); + return; + } + + out.add(buf.readRetainedSlice(j)); + return; + } + } + + throw new CorruptedFrameException("length wider than 21-bit"); + } +} 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 new file mode 100644 index 000000000..e42cc07c2 --- /dev/null +++ b/src/main/java/net/minestom/server/network/netty/packet/InboundPacket.java @@ -0,0 +1,15 @@ +package net.minestom.server.network.netty.packet; + +import io.netty.buffer.ByteBuf; + +public class InboundPacket { + + public final int packetId; + public final ByteBuf body; + + public InboundPacket(int id, ByteBuf body) { + this.packetId = id; + this.body = body; + } + +} diff --git a/src/main/java/net/minestom/server/network/netty/packet/PacketHandler.java b/src/main/java/net/minestom/server/network/netty/packet/PacketHandler.java deleted file mode 100644 index 4f131125a..000000000 --- a/src/main/java/net/minestom/server/network/netty/packet/PacketHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.minestom.server.network.netty.packet; - -import io.netty.buffer.ByteBuf; - -// TODO dedlete -public class PacketHandler { - - public int length; - public ByteBuf buffer; - -} diff --git a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java index 851e694bc..6a4775c86 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java @@ -1,10 +1,12 @@ package net.minestom.server.network.packet.client.login; +import net.minestom.server.MinecraftServer; import net.minestom.server.network.ConnectionManager; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.PacketReader; import net.minestom.server.network.packet.client.ClientPreplayPacket; import net.minestom.server.network.packet.server.login.LoginSuccessPacket; +import net.minestom.server.network.packet.server.login.SetCompressionPacket; import net.minestom.server.network.player.PlayerConnection; import java.util.UUID; @@ -19,6 +21,12 @@ public class LoginStartPacket implements ClientPreplayPacket { UUID playerUuid = connectionManager.getPlayerConnectionUuid(connection, username); + int threshold = MinecraftServer.COMPRESSION_THRESHOLD; + + if (threshold > 0) { + connection.enableCompression(threshold); + } + LoginSuccessPacket successPacket = new LoginSuccessPacket(playerUuid, username); connection.sendPacket(successPacket); diff --git a/src/main/java/net/minestom/server/network/packet/server/login/SetCompressionPacket.java b/src/main/java/net/minestom/server/network/packet/server/login/SetCompressionPacket.java new file mode 100644 index 000000000..08fe1d850 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/login/SetCompressionPacket.java @@ -0,0 +1,23 @@ +package net.minestom.server.network.packet.server.login; + +import net.minestom.server.network.packet.PacketWriter; +import net.minestom.server.network.packet.server.ServerPacket; + +public class SetCompressionPacket implements ServerPacket { + + public int threshold; + + public SetCompressionPacket(int threshold) { + this.threshold = threshold; + } + + @Override + public void write(PacketWriter writer) { + writer.writeVarInt(threshold); + } + + @Override + public int getId() { + return 0x03; + } +} diff --git a/src/main/java/net/minestom/server/network/player/FakePlayerConnection.java b/src/main/java/net/minestom/server/network/player/FakePlayerConnection.java index f3be6ccea..dc435c6ed 100644 --- a/src/main/java/net/minestom/server/network/player/FakePlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/FakePlayerConnection.java @@ -12,6 +12,11 @@ import java.net.SocketAddress; public class FakePlayerConnection extends PlayerConnection { + @Override + public void enableCompression(int threshold) { + throw new UnsupportedOperationException("FakePlayer cannot enable compression"); + } + @Override public void sendPacket(ByteBuf buffer) { throw new UnsupportedOperationException("FakePlayer cannot read Bytebuf"); @@ -19,7 +24,7 @@ public class FakePlayerConnection extends PlayerConnection { @Override public void writePacket(ByteBuf buffer) { - throw new UnsupportedOperationException("FakePlayer cannot read Bytebuf"); + throw new UnsupportedOperationException("FakePlayer cannot write to Bytebuf"); } @Override 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 5ae1438d4..833acc15a 100644 --- a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java @@ -1,9 +1,13 @@ package net.minestom.server.network.player; import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import net.minestom.server.network.netty.codec.PacketCompressor; import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.utils.PacketUtils; +import net.minestom.server.network.packet.server.login.SetCompressionPacket; import java.net.SocketAddress; @@ -13,40 +17,46 @@ import java.net.SocketAddress; */ public class NettyPlayerConnection extends PlayerConnection { - private ChannelHandlerContext channel; + private final SocketChannel channel; - public NettyPlayerConnection(ChannelHandlerContext channel) { + public NettyPlayerConnection(SocketChannel channel) { super(); + this.channel = channel; } + @Override + public void enableCompression(int threshold) { + sendPacket(new SetCompressionPacket(threshold)); + + channel.pipeline().addAfter("framer", "compressor", new PacketCompressor(threshold)); + } + @Override public void sendPacket(ByteBuf buffer) { buffer.retain(); - getChannel().writeAndFlush(buffer); + channel.writeAndFlush(buffer); } @Override public void writePacket(ByteBuf buffer) { buffer.retain(); - getChannel().write(buffer); + channel.write(buffer); } @Override public void sendPacket(ServerPacket serverPacket) { - ByteBuf buffer = PacketUtils.writePacket(serverPacket); - sendPacket(buffer); - buffer.release(); + channel.writeAndFlush(serverPacket); } @Override public void flush() { - getChannel().flush(); + channel.flush(); } @Override public SocketAddress getRemoteAddress() { - return getChannel().channel().remoteAddress(); + return channel.remoteAddress(); } @Override @@ -54,7 +64,7 @@ public class NettyPlayerConnection extends PlayerConnection { getChannel().close(); } - public ChannelHandlerContext getChannel() { + public Channel getChannel() { return channel; } 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 1392a2020..0cf393ce1 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -22,6 +22,8 @@ public abstract class PlayerConnection { this.connectionState = ConnectionState.UNKNOWN; } + public abstract void enableCompression(int threshold); + public abstract void sendPacket(ByteBuf buffer); public abstract void writePacket(ByteBuf buffer); diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index caadcf36e..40d9a8bcf 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -7,27 +7,19 @@ import net.minestom.server.network.packet.server.ServerPacket; public class PacketUtils { - public static ByteBuf writePacket(ServerPacket serverPacket) { - int id = serverPacket.getId(); - PacketWriter packetWriter = new PacketWriter(); + public static void writePacket(ByteBuf buf, ServerPacket packet) { + PacketWriter writer = new PacketWriter(); - packetWriter.writeVarInt(id); + Utils.writeVarIntBuf(buf, packet.getId()); + packet.write(writer); + buf.writeBytes(writer.toByteArray()); + } - serverPacket.write(packetWriter); + public static ByteBuf writePacket(ServerPacket packet) { + ByteBuf buffer = Unpooled.buffer(); - byte[] bytes = packetWriter.toByteArray(); - int length = bytes.length; + writePacket(buffer, packet); - int varIntSize = Utils.lengthVarInt(length); - - ByteBuf buffer = Unpooled.buffer(length + varIntSize); - Utils.writeVarIntBuf(buffer, length); - buffer.writeBytes(bytes); - - //if(!(serverPacket instanceof ChunkDataPacket) && !(serverPacket instanceof PlayerListHeaderAndFooterPacket)) - //System.out.println("WRITE PACKET: " + serverPacket.getClass().getSimpleName()); - - //Unpooled.copiedBuffer(buffer); return buffer; } diff --git a/src/main/java/net/minestom/server/utils/Utils.java b/src/main/java/net/minestom/server/utils/Utils.java index 2211d223d..b02a63f6d 100644 --- a/src/main/java/net/minestom/server/utils/Utils.java +++ b/src/main/java/net/minestom/server/utils/Utils.java @@ -18,6 +18,14 @@ import java.util.*; public class Utils { + public static int getVarIntSize(int input) { + return (input & 0xFFFFFF80) == 0 + ? 1 : (input & 0xFFFFC000) == 0 + ? 2 : (input & 0xFFE00000) == 0 + ? 3 : (input & 0xF0000000) == 0 + ? 4 : 5; + } + public static void writeVarIntBuf(ByteBuf buffer, int value) { do { byte temp = (byte) (value & 0b01111111); @@ -51,19 +59,6 @@ public class Utils { } while (value != 0); } - public static int lengthVarInt(int value) { - int i = 0; - do { - i++; - byte temp = (byte) (value & 0b01111111); - value >>>= 7; - if (value != 0) { - temp |= 0b10000000; - } - } while (value != 0); - return i; - } - public static int readVarInt(ByteBuf buffer) { int numRead = 0; int result = 0; diff --git a/src/main/java/net/minestom/server/utils/buffer/BufferWrapper.java b/src/main/java/net/minestom/server/utils/buffer/BufferWrapper.java index 0c0c992f5..4e71511b8 100644 --- a/src/main/java/net/minestom/server/utils/buffer/BufferWrapper.java +++ b/src/main/java/net/minestom/server/utils/buffer/BufferWrapper.java @@ -35,7 +35,7 @@ public class BufferWrapper { public void putVarInt(int n) { Utils.writeVarIntBuffer(this, n); - size += Utils.lengthVarInt(n); + size += Utils.getVarIntSize(n); } public void putBytes(byte[] bytes) {