blob: 751bdb3d4986cf4fc58bdffc9d4a2d23c1453c6f [file] [log] [blame] [raw]
package protocolsupport.protocol.pipeline.initial;
import java.nio.charset.StandardCharsets;
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.ProtocolVersion;
import protocolsupport.protocol.ConnectionImpl;
import protocolsupport.protocol.pipeline.ChannelHandlers;
import protocolsupport.protocol.pipeline.IPipeLineBuilder;
import protocolsupport.protocol.storage.ProtocolStorage;
import protocolsupport.utils.ServerPlatformUtils;
import protocolsupport.utils.Utils;
import protocolsupport.utils.Utils.Converter;
import protocolsupport.utils.netty.ChannelUtils;
import protocolsupport.utils.netty.ReplayingDecoderBuffer;
import protocolsupport.utils.netty.ReplayingDecoderBuffer.EOFSignal;
public class InitialPacketDecoder extends SimpleChannelInboundHandler<ByteBuf> {
private static final int ping152delay = Utils.getJavaPropertyValue("ping152delay", 100, Converter.STRING_TO_INT);
private static final int pingLegacyDelay = Utils.getJavaPropertyValue("pinglegacydelay", 200, Converter.STRING_TO_INT);
public static void init() {
ProtocolSupport.logInfo("Assume 1.5.2 ping delay: "+ping152delay);
ProtocolSupport.logInfo("Assume legacy ping dealy: "+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_11, new protocolsupport.protocol.pipeline.version.v_1_11.PipeLineBuilder());
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 {
ProtocolVersion handshakeversion = null;
switch (firstbyte) {
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(new String(ChannelUtils.toArray(replayingBuffer.readSlice(replayingBuffer.readUnsignedShort() * 2)), StandardCharsets.UTF_16BE))
) {
//definitely 1.6
replayingBuffer.readUnsignedShort();
handshakeversion = 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
handshakeversion = attemptDecodeNettyHandshake(replayingBuffer);
}
} else {
//1.7+ handshake
handshakeversion = attemptDecodeNettyHandshake(replayingBuffer);
}
break;
}
case 0x02: { // <= 1.6.4 handshake
handshakeversion = ProtocolUtils.readOldHandshake(replayingBuffer);
break;
}
default: { // >= 1.7 handshake
handshakeversion = attemptDecodeNettyHandshake(replayingBuffer);
break;
}
}
//if we detected the protocol than we save it and process data
if (handshakeversion != null) {
setProtocol(channel, handshakeversion);
}
} catch (EOFSignal ex) {
}
}
protected void setProtocol(final Channel channel, ProtocolVersion version) {
if (ServerPlatformUtils.isDebugging()) {
System.err.println(ChannelUtils.getNetworkManagerSocketAddress(channel)+ " connected with protocol version "+version);
}
ConnectionImpl connection = ProtocolStorage.getConnection(ChannelUtils.getNetworkManagerSocketAddress(channel));
connection.setVersion(version);
channel.pipeline().remove(ChannelHandlers.INITIAL_DECODER);
pipelineBuilders.get(version).buildPipeLine(channel, connection);
receivedData.readerIndex(0);
channel.pipeline().firstContext().fireChannelRead(receivedData);
}
private static ProtocolVersion attemptDecodeNettyHandshake(ByteBuf bytebuf) {
bytebuf.readerIndex(0);
return ProtocolUtils.readNettyHandshake(bytebuf.readSlice(ChannelUtils.readVarInt(bytebuf)));
}
}