blob: c4ab9a818b530ea804f006dffd68137020047295 [file] [log] [blame] [raw]
package protocolsupport.protocol.pipeline.initial;
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.handler.codec.DecoderException;
import io.netty.util.concurrent.Future;
import protocolsupport.ProtocolSupport;
import protocolsupport.api.ProtocolSupportAPI;
import protocolsupport.api.ProtocolType;
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.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;
import protocolsupport.zplatform.impl.encapsulated.EncapsulatedProtocolInfo;
import protocolsupport.zplatform.impl.encapsulated.EncapsulatedProtocolUtils;
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());
IPipeLineBuilder builder1122 = new protocolsupport.protocol.pipeline.version.v_1_12.r2.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_12_2, builder1122);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_12_1, builder1122);
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 ReplayingDecoderBuffer buffer = new ReplayingDecoderBuffer(Unpooled.buffer());
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;
}
buffer.writeBytes(buf);
buffer.readerIndex(0);
decode(ctx);
}
private EncapsulatedProtocolInfo encapsulatedinfo = null;
private void decode(ChannelHandlerContext ctx) {
cancelTask();
int firstbyte = buffer.readUnsignedByte();
try {
if ((encapsulatedinfo == null) && (firstbyte == 0)) {
encapsulatedinfo = EncapsulatedProtocolUtils.readInfo(buffer);
buffer.discardReadBytes();
}
if (encapsulatedinfo == null) {
decodeRaw(ctx, firstbyte);
} else {
decodeEncapsulated(ctx);
}
} catch (EOFSignal ex) {
}
}
private void decodeRaw(ChannelHandlerContext ctx, int firstbyte) {
Channel channel = ctx.channel();
switch (firstbyte) {
case 0xFE: { //old ping or a part of varint length
if (buffer.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 (buffer.readUnsignedByte() == 1) {
//1.5-1.6 ping or maybe a finishing byte for 1.7+ packet length
if (buffer.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 (
(buffer.readUnsignedByte() == 0xFA) &&
"MC|PingHost".equals(StringSerializer.readString(buffer, ProtocolVersion.MINECRAFT_1_6_4))
) {
//definitely 1.6
buffer.readUnsignedShort();
setProtocol(channel, ProtocolUtils.get16PingVersion(buffer.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
setProtocol(channel, attemptDecodeNewHandshake(buffer));
}
} else {
//1.7+ handshake
setProtocol(channel, attemptDecodeNewHandshake(buffer));
}
break;
}
case 0x02: { // <= 1.6.4 handshake
setProtocol(channel, ProtocolUtils.readOldHandshake(buffer));
break;
}
default: { // >= 1.7 handshake
setProtocol(channel, attemptDecodeNewHandshake(buffer));
break;
}
}
}
private static ProtocolVersion attemptDecodeNewHandshake(ByteBuf bytebuf) {
bytebuf.readerIndex(0);
return ProtocolUtils.readNewHandshake(bytebuf.readSlice(VarNumberSerializer.readVarInt(bytebuf)));
}
private void decodeEncapsulated(ChannelHandlerContext ctx) {
Channel channel = ctx.channel();
ByteBuf firstpacketdata = buffer.readSlice(VarNumberSerializer.readVarInt(buffer));
int firstbyte = firstpacketdata.readUnsignedByte();
switch (firstbyte) {
case 0xFE: { //legacy ping
if (firstpacketdata.readableBytes() == 0) {
//< 1.5.2
setProtocol(channel, ProtocolVersion.MINECRAFT_LEGACY);
} else if (firstpacketdata.readUnsignedByte() == 1) {
//1.5 or 1.6 ping
if (firstpacketdata.readableBytes() == 0) {
//1.5.*, we just assume 1.5.2
setProtocol(channel, ProtocolVersion.MINECRAFT_1_5_2);
} else if (
(firstpacketdata.readUnsignedByte() == 0xFA) &&
"MC|PingHost".equals(StringSerializer.readString(firstpacketdata, ProtocolVersion.MINECRAFT_1_6_4))
) {
//1.6.*
firstpacketdata.readUnsignedShort();
setProtocol(channel, ProtocolUtils.get16PingVersion(firstpacketdata.readUnsignedByte()));
} else {
throw new DecoderException("Unable to detect incoming protocol");
}
} else {
throw new DecoderException("Unable to detect incoming protocol");
}
break;
}
case 0x02: { // <= 1.6.4 handshake
setProtocol(channel, ProtocolUtils.readOldHandshake(firstpacketdata));
break;
}
case 0x00: { // >= 1.7 handshake
setProtocol(channel, ProtocolUtils.readNewHandshake(firstpacketdata));
break;
}
default: {
throw new DecoderException("Unable to detect incoming protocol");
}
}
}
protected void setProtocol(Channel channel, ProtocolVersion version) {
ConnectionImpl connection = prepare(channel, version);
IPipeLineBuilder builder = pipelineBuilders.get(connection.getVersion());
builder.buildCodec(channel, connection);
if (encapsulatedinfo == null) {
builder.buildPipeLine(channel, connection);
} else {
PlatformUtils putils = ServerPlatform.get().getMiscUtils();
putils.setFraming(channel.pipeline(), new VarIntFrameDecoder(), new VarIntFrameEncoder());
if (encapsulatedinfo.hasCompression()) {
putils.enableCompression(channel.pipeline(), putils.getCompressionThreshold());
}
if ((encapsulatedinfo.getAddress() != null) && connection.getRawAddress().getAddress().isLoopbackAddress()) {
connection.changeAddress(encapsulatedinfo.getAddress());
}
}
buffer.readerIndex(0);
channel.pipeline().firstContext().fireChannelRead(buffer.unwrap());
}
protected static ConnectionImpl prepare(Channel channel, ProtocolVersion version) {
channel.pipeline().remove(ChannelHandlers.INITIAL_DECODER);
ConnectionImpl connection = ConnectionImpl.getFromChannel(channel);
if (ServerPlatform.get().getMiscUtils().isDebugging()) {
ProtocolSupport.logInfo(MessageFormat.format("{0} connected with protocol version {1}", connection.getAddress(), version));
}
connection.getNetworkManagerWrapper().setPacketListener(ServerPlatform.get().getWrapperFactory().createHandshakeListener(connection.getNetworkManagerWrapper()));
if (!ProtocolSupportAPI.isProtocolVersionEnabled(version)) {
if (version.getProtocolType() == ProtocolType.PC) {
version = version.isBeforeOrEq(ProtocolVersion.MINECRAFT_1_6_4) ? ProtocolVersion.MINECRAFT_LEGACY : ProtocolVersion.MINECRAFT_FUTURE;
} else {
throw new IllegalArgumentException(MessageFormat.format("Unable to get legacy or future version for disabled protocol version {0}", version));
}
}
connection.setVersion(version);
return connection;
}
}