package net.md_5.bungee.netty; import com.google.common.base.Preconditions; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import java.util.concurrent.TimeUnit; import lombok.Getter; import net.md_5.bungee.compress.PacketCompressor; import net.md_5.bungee.compress.PacketDecompressor; import net.md_5.bungee.protocol.MinecraftDecoder; 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.packet.Kick; public class ChannelWrapper { private final Channel ch; @Getter private volatile boolean closed; @Getter private volatile boolean closing; public ChannelWrapper(ChannelHandlerContext ctx) { this.ch = ctx.channel(); } public void setProtocol(Protocol protocol) { ch.pipeline().get( MinecraftDecoder.class ).setProtocol( protocol ); ch.pipeline().get( MinecraftEncoder.class ).setProtocol( protocol ); } public void setVersion(int protocol) { ch.pipeline().get( MinecraftDecoder.class ).setProtocolVersion( protocol ); ch.pipeline().get( MinecraftEncoder.class ).setProtocolVersion( protocol ); } public void write(Object packet) { if ( !closed ) { if ( packet instanceof PacketWrapper ) { ( (PacketWrapper) packet ).setReleased( true ); ch.write( ( (PacketWrapper) packet ).buf, ch.voidPromise() ); } else { ch.write( packet, ch.voidPromise() ); } ch.flush(); } } public void markClosed() { closed = closing = true; } public void close() { close( null ); } public void close(Object packet) { if ( !closed ) { closed = closing = true; if ( packet != null && ch.isActive() ) { ch.writeAndFlush( packet ).addListeners( ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE, ChannelFutureListener.CLOSE ); ch.eventLoop().schedule( new Runnable() { @Override public void run() { ch.close(); } }, 250, TimeUnit.MILLISECONDS ); } else { ch.flush(); ch.close(); } } } public void delayedClose(final Kick kick) { if ( !closing ) { closing = true; // Minecraft client can take some time to switch protocols. // Sending the wrong disconnect packet whilst a protocol switch is in progress will crash it. // Delay 250ms to ensure that the protocol switch (if any) has definitely taken place. ch.eventLoop().schedule( new Runnable() { @Override public void run() { close( kick ); } }, 250, TimeUnit.MILLISECONDS ); } } public void addBefore(String baseName, String name, ChannelHandler handler) { Preconditions.checkState( ch.eventLoop().inEventLoop(), "cannot add handler outside of event loop" ); ch.pipeline().flush(); ch.pipeline().addBefore( baseName, name, handler ); } public Channel getHandle() { return ch; } public void setCompressionThreshold(int compressionThreshold) { if ( ch.pipeline().get( PacketCompressor.class ) == null && compressionThreshold != -1 ) { addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() ); } if ( compressionThreshold != -1 ) { ch.pipeline().get( PacketCompressor.class ).setThreshold( compressionThreshold ); } else { ch.pipeline().remove( "compress" ); } if ( ch.pipeline().get( PacketDecompressor.class ) == null && compressionThreshold != -1 ) { addBefore( PipelineUtils.PACKET_DECODER, "decompress", new PacketDecompressor() ); } if ( compressionThreshold == -1 ) { ch.pipeline().remove( "decompress" ); } } }