blob: fc3ef7ecffc752de128262628d44c04b52c0b874 [file] [log] [blame] [raw]
package protocolsupport.protocol.pipeline.initial;
import java.net.InetSocketAddress;
import java.text.MessageFormat;
import java.util.EnumMap;
import java.util.concurrent.TimeUnit;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.Future;
import protocolsupport.ProtocolSupport;
import protocolsupport.api.ProtocolSupportAPI;
import protocolsupport.api.ProtocolVersion;
import protocolsupport.protocol.ConnectionImpl;
import protocolsupport.protocol.pipeline.ChannelHandlers;
import protocolsupport.protocol.pipeline.IPipeLineBuilder;
import protocolsupport.protocol.pipeline.common.VarIntFrameDecoder;
import protocolsupport.protocol.pipeline.common.VarIntFrameEncoder;
import protocolsupport.protocol.pipeline.initial.EncapsulatedProtocolUtils.EncapsulatedProtocolInfo;
import protocolsupport.protocol.serializer.StringSerializer;
import protocolsupport.protocol.serializer.VarNumberSerializer;
import protocolsupport.utils.Utils;
import protocolsupport.utils.netty.ReplayingDecoderBuffer;
import protocolsupport.utils.netty.ReplayingDecoderBuffer.EOFSignal;
import protocolsupport.zplatform.PlatformUtils;
import protocolsupport.zplatform.ServerPlatform;
public class InitialPacketDecoder extends SimpleChannelInboundHandler<ByteBuf> {
private static final int ping152delay = Utils.getJavaPropertyValue("ping152delay", 100, Integer::parseInt);
private static final int pingLegacyDelay = Utils.getJavaPropertyValue("pinglegacydelay", 200, Integer::parseInt);
static {
ProtocolSupport.logInfo("Assume 1.5.2 ping delay: "+ping152delay);
ProtocolSupport.logInfo("Assume legacy ping delay: "+pingLegacyDelay);
}
private static final EnumMap<ProtocolVersion, IPipeLineBuilder> pipelineBuilders = new EnumMap<>(ProtocolVersion.class);
static {
pipelineBuilders.put(ProtocolVersion.MINECRAFT_FUTURE, new protocolsupport.protocol.pipeline.version.v_future.PipeLineBuilder());
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_12_1, new protocolsupport.protocol.pipeline.version.v_1_12.r2.PipeLineBuilder());
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_12, new protocolsupport.protocol.pipeline.version.v_1_12.r1.PipeLineBuilder());
IPipeLineBuilder builder111 = new protocolsupport.protocol.pipeline.version.v_1_11.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_11_1, builder111);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_11, builder111);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_10, new protocolsupport.protocol.pipeline.version.v_1_10.PipeLineBuilder());
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_9_4, new protocolsupport.protocol.pipeline.version.v_1_9.r2.PipeLineBuilder());
IPipeLineBuilder builder19r1 = new protocolsupport.protocol.pipeline.version.v_1_9.r1.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_9_2, builder19r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_9_1, builder19r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_9, builder19r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_8, new protocolsupport.protocol.pipeline.version.v_1_8.PipeLineBuilder());
IPipeLineBuilder builder17 = new protocolsupport.protocol.pipeline.version.v_1_7.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_7_10, builder17);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_7_5, builder17);
IPipeLineBuilder builder16 = new protocolsupport.protocol.pipeline.version.v_1_6.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_6_4, builder16);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_6_2, builder16);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_6_1, builder16);
IPipeLineBuilder builder15 = new protocolsupport.protocol.pipeline.version.v_1_5.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_5_2, builder15);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_5_1, builder15);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_4_7, new protocolsupport.protocol.pipeline.version.v_1_4.PipeLineBuilder());
pipelineBuilders.put(ProtocolVersion.MINECRAFT_LEGACY, new protocolsupport.protocol.pipeline.version.v_legacy.PipeLineBuilder());
}
protected final ByteBuf receivedData = Unpooled.buffer();
protected final ReplayingDecoderBuffer replayingBuffer = new ReplayingDecoderBuffer(receivedData);
protected Future<?> responseTask;
protected void scheduleTask(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit tu) {
responseTask = ctx.executor().schedule(task, delay, tu);
}
protected void cancelTask() {
if (responseTask != null) {
responseTask.cancel(true);
responseTask = null;
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
cancelTask();
super.channelInactive(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
cancelTask();
super.handlerRemoved(ctx);
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) {
if (!buf.isReadable()) {
return;
}
receivedData.writeBytes(buf);
receivedData.readerIndex(0);
decode(ctx);
}
private void decode(ChannelHandlerContext ctx) {
cancelTask();
Channel channel = ctx.channel();
int firstbyte = replayingBuffer.readUnsignedByte();
try {
switch (firstbyte) {
case 0x00: { // encapsulated protocol handsake
setEncapsulatedProtocol(channel, EncapsulatedProtocolUtils.readInfo(replayingBuffer));
break;
}
case 0xFE: { //old ping or a part of varint length
if (replayingBuffer.readableBytes() == 0) {
//no more data received, it may be old protocol, or we just not received all data yet, so delay assuming as really old protocol for some time
scheduleTask(ctx, new SetProtocolTask(this, channel, ProtocolVersion.MINECRAFT_LEGACY), pingLegacyDelay, TimeUnit.MILLISECONDS);
} else if (replayingBuffer.readUnsignedByte() == 1) {
//1.5-1.6 ping or maybe a finishing byte for 1.7+ packet length
if (replayingBuffer.readableBytes() == 0) {
//no more data received, it may be 1.5.2 or we just didn't receive 1.6 or 1.7+ data yet, so delay assuming as 1.5.2 for some time
scheduleTask(ctx, new SetProtocolTask(this, channel, ProtocolVersion.MINECRAFT_1_5_2), ping152delay, TimeUnit.MILLISECONDS);
} else if (
(replayingBuffer.readUnsignedByte() == 0xFA) &&
"MC|PingHost".equals(StringSerializer.readString(replayingBuffer, ProtocolVersion.MINECRAFT_1_6_4))
) {
//definitely 1.6
replayingBuffer.readUnsignedShort();
setNativeProtocol(channel, ProtocolUtils.get16PingVersion(replayingBuffer.readUnsignedByte()));
} else {
//it was 1.7+ handshake after all
//hope that there won't be any handshake packet with id 0xFA in future because it will be more difficult to support it
setNativeProtocol(channel, attemptDecodeNewHandshake(replayingBuffer));
}
} else {
//1.7+ handshake
setNativeProtocol(channel, attemptDecodeNewHandshake(replayingBuffer));
}
break;
}
case 0x02: { // <= 1.6.4 handshake
setNativeProtocol(channel, ProtocolUtils.readOldHandshake(replayingBuffer));
break;
}
default: { // >= 1.7 handshake
setNativeProtocol(channel, attemptDecodeNewHandshake(replayingBuffer));
break;
}
}
} catch (EOFSignal ex) {
}
}
private void setEncapsulatedProtocol(Channel channel, EncapsulatedProtocolInfo info) {
ConnectionImpl connection = ConnectionImpl.getFromChannel(channel);
if (ServerPlatform.get().getMiscUtils().isDebugging()) {
ProtocolSupport.logInfo(MessageFormat.format("{0} connected with protocol version {1}", connection.getAddress(), info.getVersion()));
}
channel.pipeline().remove(ChannelHandlers.INITIAL_DECODER);
connection.changeAddress(new InetSocketAddress(info.getAddress(), connection.getAddress().getPort()));
ProtocolVersion version = info.getVersion();
if (!ProtocolSupportAPI.isProtocolVersionEnabled(version)) {
version = version.isBeforeOrEq(ProtocolVersion.MINECRAFT_1_6_4) ? ProtocolVersion.MINECRAFT_LEGACY : ProtocolVersion.MINECRAFT_FUTURE;
}
connection.setVersion(version);
PlatformUtils putils = ServerPlatform.get().getMiscUtils();
if (info.hasCompression()) {
putils.enableCompression(channel.pipeline(), putils.getCompressionThreshold());
}
putils.setFraming(channel.pipeline(), new VarIntFrameDecoder(), new VarIntFrameEncoder());
pipelineBuilders.get(version).buildCodec(channel, connection);
}
protected void setNativeProtocol(Channel channel, ProtocolVersion version) {
ConnectionImpl connection = ConnectionImpl.getFromChannel(channel);
if (ServerPlatform.get().getMiscUtils().isDebugging()) {
ProtocolSupport.logInfo(MessageFormat.format("{0} connected with protocol version {1}", connection.getAddress(), version));
}
channel.pipeline().remove(ChannelHandlers.INITIAL_DECODER);
if (!ProtocolSupportAPI.isProtocolVersionEnabled(version)) {
version = version.isBeforeOrEq(ProtocolVersion.MINECRAFT_1_6_4) ? ProtocolVersion.MINECRAFT_LEGACY : ProtocolVersion.MINECRAFT_FUTURE;
}
connection.setVersion(version);
IPipeLineBuilder builder = pipelineBuilders.get(version);
builder.buildPipeLine(channel, connection);
builder.buildCodec(channel, connection);
receivedData.readerIndex(0);
channel.pipeline().firstContext().fireChannelRead(receivedData);
}
private static ProtocolVersion attemptDecodeNewHandshake(ByteBuf bytebuf) {
bytebuf.readerIndex(0);
return ProtocolUtils.readNewHandshake(bytebuf.readSlice(VarNumberSerializer.readVarInt(bytebuf)));
}
}