From b638dde850130f265c0f150bd83657218f91893c Mon Sep 17 00:00:00 2001 From: Janmm14 Date: Sat, 7 Dec 2024 20:28:15 +0100 Subject: [PATCH] Combine packet length prepending and compressor, so we can prepend compressed length after compression (leaving some bytes free at start of buffer) --- .../Varint21LengthFieldExtraBufPrepender.java | 26 --- .../Varint21LengthFieldPrepender.java | 58 ------ .../java/net/md_5/bungee/UserConnection.java | 2 +- .../bungee/compress/PacketCompressor.java | 57 ------ .../bungee/connection/InitialHandler.java | 2 +- .../md_5/bungee/connection/PingHandler.java | 2 +- .../net/md_5/bungee/netty/ChannelWrapper.java | 27 +-- .../netty/LengthPrependerAndCompressor.java | 190 ++++++++++++++++++ .../net/md_5/bungee/netty/PipelineUtils.java | 13 +- 9 files changed, 206 insertions(+), 171 deletions(-) delete mode 100644 protocol/src/main/java/net/md_5/bungee/protocol/Varint21LengthFieldExtraBufPrepender.java delete mode 100644 protocol/src/main/java/net/md_5/bungee/protocol/Varint21LengthFieldPrepender.java delete mode 100644 proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java create mode 100644 proxy/src/main/java/net/md_5/bungee/netty/LengthPrependerAndCompressor.java diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21LengthFieldExtraBufPrepender.java b/protocol/src/main/java/net/md_5/bungee/protocol/Varint21LengthFieldExtraBufPrepender.java deleted file mode 100644 index c60ee9da70..0000000000 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21LengthFieldExtraBufPrepender.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.md_5.bungee.protocol; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageEncoder; -import java.util.List; - -/** - * Prepend length of the message as a Varint21 using an extra buffer for the - * length, avoiding copying packet data - */ -@ChannelHandler.Sharable -public class Varint21LengthFieldExtraBufPrepender extends MessageToMessageEncoder -{ - - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception - { - int bodyLen = msg.readableBytes(); - ByteBuf lenBuf = ctx.alloc().ioBuffer( Varint21LengthFieldPrepender.varintSize( bodyLen ) ); - DefinedPacket.writeVarInt( bodyLen, lenBuf ); - out.add( lenBuf ); - out.add( msg.retain() ); - } -} diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21LengthFieldPrepender.java b/protocol/src/main/java/net/md_5/bungee/protocol/Varint21LengthFieldPrepender.java deleted file mode 100644 index 3f0a866fc3..0000000000 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21LengthFieldPrepender.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.md_5.bungee.protocol; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageEncoder; -import java.util.List; -import lombok.Setter; - -/** - * Prepend length of the message as a Varint21 by writing length and data to a - * new buffer - */ -public class Varint21LengthFieldPrepender extends MessageToMessageEncoder -{ - - @Setter - private boolean compose = true; - - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List list) throws Exception - { - int bodyLen = msg.readableBytes(); - int headerLen = varintSize( bodyLen ); - if ( compose ) - { - ByteBuf buf = ctx.alloc().directBuffer( headerLen ); - DefinedPacket.writeVarInt( bodyLen, buf ); - list.add( ctx.alloc().compositeDirectBuffer( 2 ).addComponents( true, buf, msg.retain() ) ); - } else - { - ByteBuf buf = ctx.alloc().directBuffer( headerLen + bodyLen ); - DefinedPacket.writeVarInt( bodyLen, buf ); - buf.writeBytes( msg ); - list.add( buf ); - } - } - - static int varintSize(int paramInt) - { - if ( ( paramInt & 0xFFFFFF80 ) == 0 ) - { - return 1; - } - if ( ( paramInt & 0xFFFFC000 ) == 0 ) - { - return 2; - } - if ( ( paramInt & 0xFFE00000 ) == 0 ) - { - return 3; - } - if ( ( paramInt & 0xF0000000 ) == 0 ) - { - return 4; - } - return 5; - } -} diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index 8f121aa952..46df6b6634 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -369,7 +369,7 @@ protected void initChannel(Channel ch) throws Exception { PipelineUtils.BASE_SERVERSIDE.initChannel( ch ); ch.pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); - ch.pipeline().addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); + ch.pipeline().addAfter( PipelineUtils.FRAME_PREPENDER_AND_COMPRESS, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target ) ); } }; diff --git a/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java b/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java deleted file mode 100644 index 314cd00a8d..0000000000 --- a/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.md_5.bungee.compress; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageEncoder; -import java.util.List; -import java.util.zip.Deflater; -import lombok.Getter; -import lombok.Setter; -import net.md_5.bungee.jni.zlib.BungeeZlib; -import net.md_5.bungee.protocol.DefinedPacket; - -public class PacketCompressor extends MessageToMessageEncoder -{ - - @Getter - private final BungeeZlib zlib = CompressFactory.zlib.newInstance(); - @Setter - private int threshold = 256; - @Setter - private boolean compose = true; - - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception - { - zlib.init( true, Deflater.DEFAULT_COMPRESSION ); - } - - @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception - { - zlib.free(); - } - - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception - { - int origSize = msg.readableBytes(); - if ( origSize < threshold ) - { - if ( compose ) - { - // create a virtual buffer to avoid copying of data - out.add( ctx.alloc().compositeDirectBuffer( 2 ).addComponents( true, ctx.alloc().directBuffer( 1 ).writeByte( 0 ), msg.retain() ) ); - } else - { - out.add( ctx.alloc().directBuffer( origSize + 1 ).writeByte( 0 ).writeBytes( msg ) ); - } - } else - { - ByteBuf buf = ctx.alloc().directBuffer( BungeeZlib.OUTPUT_BUFFER_SIZE ); - DefinedPacket.writeVarInt( origSize, buf ); - zlib.process( msg, buf ); - out.add( buf ); - } - } -} diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index 2d365c39de..f245af46ee 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -514,7 +514,7 @@ public void handle(final EncryptionResponse encryptResponse) throws Exception BungeeCipher decrypt = EncryptionUtil.getCipher( false, sharedKey ); ch.addBefore( PipelineUtils.FRAME_DECODER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) ); BungeeCipher encrypt = EncryptionUtil.getCipher( true, sharedKey ); - ch.addBefore( PipelineUtils.FRAME_PREPENDER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) ); + ch.addBefore( PipelineUtils.FRAME_PREPENDER_AND_COMPRESS, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) ); // disable use of composite buffers if we use natives ch.updateComposite(); diff --git a/proxy/src/main/java/net/md_5/bungee/connection/PingHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/PingHandler.java index 6df3f3dd9f..ae97f79900 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/PingHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/PingHandler.java @@ -38,7 +38,7 @@ public void connected(ChannelWrapper channel) throws Exception MinecraftEncoder encoder = new MinecraftEncoder( Protocol.HANDSHAKE, false, protocol ); channel.getHandle().pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.STATUS, false, ProxyServer.getInstance().getProtocolVersion() ) ); - channel.getHandle().pipeline().addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, encoder ); + channel.getHandle().pipeline().addAfter( PipelineUtils.FRAME_PREPENDER_AND_COMPRESS, PipelineUtils.PACKET_ENCODER, encoder ); channel.write( new Handshake( protocol, target.getAddress().getHostString(), target.getAddress().getPort(), 1 ) ); diff --git a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java index a15ba5dfb7..b8832aa851 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java @@ -11,7 +11,6 @@ import lombok.Getter; import lombok.Setter; import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.compress.PacketCompressor; import net.md_5.bungee.compress.PacketDecompressor; import net.md_5.bungee.netty.cipher.CipherEncoder; import net.md_5.bungee.protocol.DefinedPacket; @@ -19,7 +18,6 @@ import net.md_5.bungee.protocol.MinecraftEncoder; import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.Protocol; -import net.md_5.bungee.protocol.Varint21LengthFieldPrepender; import net.md_5.bungee.protocol.packet.Kick; public class ChannelWrapper @@ -180,16 +178,17 @@ public Channel getHandle() public void setCompressionThreshold(int compressionThreshold) { - if ( ch.pipeline().get( PacketCompressor.class ) == null && compressionThreshold >= 0 ) + LengthPrependerAndCompressor handler = ch.pipeline().get( LengthPrependerAndCompressor.class ); + if ( !handler.isCompress() && compressionThreshold >= 0 ) { - addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() ); + handler.setCompress( true ); } if ( compressionThreshold >= 0 ) { - ch.pipeline().get( PacketCompressor.class ).setThreshold( compressionThreshold ); + handler.setThreshold( compressionThreshold ); } else { - ch.pipeline().remove( "compress" ); + handler.setCompress( false ); } if ( ch.pipeline().get( PacketDecompressor.class ) == null && compressionThreshold >= 0 ) @@ -210,26 +209,16 @@ public void setCompressionThreshold(int compressionThreshold) public void updateComposite() { CipherEncoder cipherEncoder = ch.pipeline().get( CipherEncoder.class ); - PacketCompressor packetCompressor = ch.pipeline().get( PacketCompressor.class ); - Varint21LengthFieldPrepender prepender = ch.pipeline().get( Varint21LengthFieldPrepender.class ); + LengthPrependerAndCompressor prependerAndCompressor = ch.pipeline().get( LengthPrependerAndCompressor.class ); boolean compressorCompose = cipherEncoder == null || cipherEncoder.getCipher().allowComposite(); - boolean prependerCompose = compressorCompose && ( packetCompressor == null || packetCompressor.getZlib().allowComposite() ); - if ( prepender != null ) + if ( prependerAndCompressor != null ) { ProxyServer.getInstance().getLogger().log( Level.FINE, "set prepender compose to {0} for {1}", new Object[] - { - prependerCompose, ch - } ); - prepender.setCompose( prependerCompose ); - } - if ( packetCompressor != null ) - { - ProxyServer.getInstance().getLogger().log( Level.FINE, "set packetCompressor compose to {0} for {1}", new Object[] { compressorCompose, ch } ); - packetCompressor.setCompose( compressorCompose ); + prependerAndCompressor.setCompose( compressorCompose ); } } diff --git a/proxy/src/main/java/net/md_5/bungee/netty/LengthPrependerAndCompressor.java b/proxy/src/main/java/net/md_5/bungee/netty/LengthPrependerAndCompressor.java new file mode 100644 index 0000000000..b330ee09c7 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/netty/LengthPrependerAndCompressor.java @@ -0,0 +1,190 @@ +package net.md_5.bungee.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageEncoder; +import java.util.List; +import java.util.zip.Deflater; +import lombok.Setter; +import net.md_5.bungee.compress.CompressFactory; +import net.md_5.bungee.jni.zlib.BungeeZlib; +import net.md_5.bungee.protocol.DefinedPacket; + +/** + * prepends length of message and optionally compresses message beforehand + *
+ * combining these operations allows to keep space infront of compressed data for length varint + */ +public class LengthPrependerAndCompressor extends MessageToMessageEncoder +{ + // reasonable to not support length varints > 4 byte (268435455 byte > 268MB) + // if ever changed to smaller than 4, also change varintSize method to check for that + private static final byte MAX_SUPPORTED_VARINT_LENGTH_LEN = 4; + private static final byte FLAG_COMPRESS = 0x01; + /** + * overridden by FLAG_TWO_BUFFERS if set + */ + private static final byte FLAG_COMPOSE = 0x02; + /** + * overwrites FLAG_COMPOSE if set + */ + private static final byte FLAG_TWO_BUFFERS = 0x04; + + public LengthPrependerAndCompressor(boolean compose, boolean twoBuffers) + { + setCompose( compose ); + setTwoBuffers( twoBuffers ); + } + + private BungeeZlib zlib; + @Setter + private int threshold = 256; + private byte flags = FLAG_COMPOSE; + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception + { + int oldBodyLen = msg.readableBytes(); + if ( ( flags & FLAG_COMPRESS ) != 0 ) + { + if ( oldBodyLen < threshold ) + { + byte lengthLen = varintSize( oldBodyLen + 1 ); + if ( ( flags & FLAG_TWO_BUFFERS ) != 0 ) + { + ByteBuf lenBuf = ctx.alloc().directBuffer( lengthLen ); + DefinedPacket.writeVarInt( oldBodyLen + 1, lenBuf ); + lenBuf.writeByte( 0 ); // indicates uncompressed + out.add( lenBuf ); + out.add( msg.retain() ); + } else if ( ( flags & FLAG_COMPOSE ) != 0 ) + { + // create a virtual buffer to avoid copying of data + ByteBuf pre = ctx.alloc().directBuffer( lengthLen + 1 ); + DefinedPacket.writeVarInt( oldBodyLen + 1, pre ); + pre.writeByte( 0 ); // indicates uncompressed + + out.add( ctx.alloc().compositeDirectBuffer( 2 ).addComponents( true, pre, msg.retain() ) ); + } else + { + ByteBuf buf = ctx.alloc().directBuffer( lengthLen + 1 + oldBodyLen ); + DefinedPacket.writeVarInt( oldBodyLen + 1, buf ); + out.add( buf.writeByte( 0 ).writeBytes( msg ) ); // 0 indicates uncompressed + } + } else + { + ByteBuf buf = ctx.alloc().directBuffer( BungeeZlib.OUTPUT_BUFFER_SIZE + MAX_SUPPORTED_VARINT_LENGTH_LEN + varintSize( oldBodyLen ) ); + buf.writerIndex( MAX_SUPPORTED_VARINT_LENGTH_LEN ); // Reserve space for packet length varint + DefinedPacket.writeVarInt( oldBodyLen, buf ); // write uncompressed length + zlib.process( msg, buf ); // compress data to buf + + // write varint length of compressed directly infront of compressed data + // leaves potential unused bytes at buffer start + int writerIndex = buf.writerIndex(); + int compressedLen = writerIndex - MAX_SUPPORTED_VARINT_LENGTH_LEN; + byte lengthLen = varintSize( compressedLen ); + int lengthStart = MAX_SUPPORTED_VARINT_LENGTH_LEN - lengthLen; + buf.writerIndex( lengthStart ); + DefinedPacket.writeVarInt( compressedLen, buf ); + + buf.readerIndex( lengthStart ); // set start of buffer to ignore potential unused bytes before length + buf.writerIndex( writerIndex ); // set end of buffer back to end of compressed data + out.add( buf ); + } + } else + { + byte lengthLen = varintSize( oldBodyLen ); + if ( ( flags & FLAG_TWO_BUFFERS ) != 0 ) + { + ByteBuf lenBuf = ctx.alloc().directBuffer( lengthLen ); + DefinedPacket.writeVarInt( oldBodyLen, lenBuf ); + out.add( lenBuf ); + out.add( msg.retain() ); + } else if ( ( flags & FLAG_COMPOSE ) != 0 ) + { + // create a virtual buffer to avoid copying of data + ByteBuf pre = ctx.alloc().directBuffer( lengthLen ); + DefinedPacket.writeVarInt( oldBodyLen, pre ); + out.add( ctx.alloc().compositeDirectBuffer( 2 ).addComponents( true, pre, msg.retain() ) ); + } else + { + ByteBuf buf = ctx.alloc().directBuffer( lengthLen + oldBodyLen ); + DefinedPacket.writeVarInt( oldBodyLen, buf ); + out.add( buf.writeBytes( msg ) ); // 0 indicates uncompressed + } + } + } + + public void setCompose(boolean compose) + { + if ( compose ) + { + flags |= FLAG_COMPOSE; + } else + { + flags &= ~FLAG_COMPOSE; + } + } + + public boolean isCompress() + { + return ( flags & FLAG_COMPRESS ) != 0; + } + + public void setCompress(boolean compress) + { + if ( compress ) + { + BungeeZlib zlib = this.zlib; + if ( zlib == null ) + { + this.zlib = zlib = CompressFactory.zlib.newInstance(); + } + zlib.init( true, Deflater.DEFAULT_COMPRESSION ); + flags |= FLAG_COMPRESS; + } else + { + flags &= ~FLAG_COMPRESS; + if ( zlib != null ) + { + zlib.free(); + } + } + } + + public void setTwoBuffers(boolean twoBuffers) + { + if ( twoBuffers ) + { + flags |= FLAG_TWO_BUFFERS; + } else + { + flags &= ~FLAG_TWO_BUFFERS; + } + } + + private static byte varintSize(int paramInt) + { + if ( ( paramInt & 0xFFFFFF80 ) == 0 ) + { + return 1; + } + if ( ( paramInt & 0xFFFFC000 ) == 0 ) + { + return 2; + } + if ( ( paramInt & 0xFFE00000 ) == 0 ) + { + return 3; + } + if ( ( paramInt & 0xF0000000 ) == 0 ) + { + return 4; + } + if ( MAX_SUPPORTED_VARINT_LENGTH_LEN < 5 ) + { + throw new IllegalArgumentException( "Packet length " + paramInt + " longer than supported (max. 268435455 for 4 byte varint)" ); + } + return 5; + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java index 2446f48919..d09731ab30 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java @@ -49,8 +49,6 @@ import net.md_5.bungee.protocol.MinecraftEncoder; import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.Varint21FrameDecoder; -import net.md_5.bungee.protocol.Varint21LengthFieldExtraBufPrepender; -import net.md_5.bungee.protocol.Varint21LengthFieldPrepender; public class PipelineUtils { @@ -80,8 +78,8 @@ protected void initChannel(Channel ch) throws Exception BASE.initChannel( ch ); ch.pipeline().addBefore( FRAME_DECODER, LEGACY_DECODER, new LegacyDecoder() ); ch.pipeline().addAfter( FRAME_DECODER, PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, true, ProxyServer.getInstance().getProtocolVersion() ) ); - ch.pipeline().addAfter( FRAME_PREPENDER, PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, true, ProxyServer.getInstance().getProtocolVersion() ) ); - ch.pipeline().addBefore( FRAME_PREPENDER, LEGACY_KICKER, legacyKicker ); + ch.pipeline().addAfter( FRAME_PREPENDER_AND_COMPRESS, PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, true, ProxyServer.getInstance().getProtocolVersion() ) ); + ch.pipeline().addBefore( FRAME_PREPENDER_AND_COMPRESS, LEGACY_KICKER, legacyKicker ); ch.pipeline().get( HandlerBoss.class ).setHandler( new InitialHandler( BungeeCord.getInstance(), listener ) ); if ( listener.isProxyProtocol() ) @@ -93,7 +91,6 @@ protected void initChannel(Channel ch) throws Exception public static final Base BASE = new Base( false ); public static final Base BASE_SERVERSIDE = new Base( true ); private static final KickStringWriter legacyKicker = new KickStringWriter(); - private static final Varint21LengthFieldExtraBufPrepender serverFramePrepender = new Varint21LengthFieldExtraBufPrepender(); public static final String TIMEOUT_HANDLER = "timeout"; public static final String PACKET_DECODER = "packet-decoder"; public static final String PACKET_ENCODER = "packet-encoder"; @@ -101,7 +98,7 @@ protected void initChannel(Channel ch) throws Exception public static final String ENCRYPT_HANDLER = "encrypt"; public static final String DECRYPT_HANDLER = "decrypt"; public static final String FRAME_DECODER = "frame-decoder"; - public static final String FRAME_PREPENDER = "frame-prepender"; + public static final String FRAME_PREPENDER_AND_COMPRESS = "frame-prepender-compress"; public static final String LEGACY_DECODER = "legacy-decoder"; public static final String LEGACY_KICKER = "legacy-kick"; @@ -201,8 +198,8 @@ public void initChannel(Channel ch) throws Exception ch.pipeline().addLast( TIMEOUT_HANDLER, new ReadTimeoutHandler( BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS ) ); // No encryption bungee -> server, therefore use extra buffer to avoid copying everything for length prepending // Not used bungee -> client as header would need to be encrypted separately through expensive JNI call - ch.pipeline().addLast( FRAME_PREPENDER, ( toServer ) ? serverFramePrepender : new Varint21LengthFieldPrepender() ); - + //TODO evaluate difference compose vs two buffers + ch.pipeline().addLast( FRAME_PREPENDER_AND_COMPRESS, new LengthPrependerAndCompressor( true, toServer ) ); ch.pipeline().addLast( BOSS_HANDLER, new HandlerBoss() ); } }