Fix index out of bounds exception

Also make /all/ the IntelliJ code changes

Fixes #469
This commit is contained in:
Dan Mulloy 2018-05-31 20:15:47 -04:00
parent 015099b92e
commit fb2d5016e5

View File

@ -16,24 +16,14 @@
*/ */
package com.comphenix.protocol.injector.netty; package com.comphenix.protocol.injector.netty;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayDeque; import java.util.*;
import java.util.Deque;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
@ -58,12 +48,7 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel; import io.netty.channel.*;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;
@ -71,16 +56,20 @@ import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.internal.TypeParameterMatcher; import io.netty.util.internal.TypeParameterMatcher;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/** /**
* Represents a channel injector. * Represents a channel injector.
* @author Kristian * @author Kristian
*/ */
public class ChannelInjector extends ByteToMessageDecoder implements Injector { public class ChannelInjector extends ByteToMessageDecoder implements Injector {
public static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet."); private static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet.");
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet."); private static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread."); private static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger"); private static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
public static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
/** /**
* Indicates that a packet has bypassed packet listeners. * Indicates that a packet has bypassed packet listeners.
@ -98,12 +87,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
private static AttributeKey<Integer> PROTOCOL_KEY; private static AttributeKey<Integer> PROTOCOL_KEY;
static { static {
// Shout-outs to reloading PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL-" + keyId.getAndIncrement());
try {
PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL");
} catch (IllegalArgumentException ex) {
PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL-" + keyId.getAndIncrement());
}
} }
// Saved accessors // Saved accessors
@ -148,18 +132,13 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
/** /**
* A flag set by the main thread to indiciate that a packet should not be processed. * A flag set by the main thread to indiciate that a packet should not be processed.
*/ */
private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() { private final ThreadLocal<Boolean> scheduleProcessPackets = ThreadLocal.withInitial(() -> true);
@Override
protected Boolean initialValue() {
return true;
};
};
// Other handlers // Other handlers
private ByteToMessageDecoder vanillaDecoder; private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder; private MessageToByteEncoder<Object> vanillaEncoder;
private Deque<PacketEvent> finishQueue = new ArrayDeque<PacketEvent>(); private Deque<PacketEvent> finishQueue = new ArrayDeque<>();
// The channel listener // The channel listener
private ChannelListener channelListener; private ChannelListener channelListener;
@ -179,7 +158,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
* @param channelListener - a listener. * @param channelListener - a listener.
* @param factory - the factory that created this injector * @param factory - the factory that created this injector
*/ */
public ChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener, InjectionFactory factory) { ChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener, InjectionFactory factory) {
this.player = Preconditions.checkNotNull(player, "player cannot be NULL"); this.player = Preconditions.checkNotNull(player, "player cannot be NULL");
this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL"); this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL");
this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL"); this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL");
@ -188,8 +167,9 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter()); this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
// Get the channel field // Get the channel field
this.channelField = new VolatileField(FuzzyReflection.fromObject(networkManager, true).getFieldByType("channel", Channel.class), this.channelField = new VolatileField(FuzzyReflection
networkManager, true); .fromObject(networkManager, true)
.getFieldByType("channel", Channel.class), networkManager, true);
} }
/** /**
@ -217,12 +197,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// pipeline with only some of the handlers removed // pipeline with only some of the handlers removed
if (Bukkit.isPrimaryThread()) { if (Bukkit.isPrimaryThread()) {
// Just like in the close() method, we'll avoid blocking the main thread // Just like in the close() method, we'll avoid blocking the main thread
executeInChannelThread(new Runnable() { executeInChannelThread(this::inject);
@Override
public void run() {
inject();
}
});
return false; // We don't know return false; // We don't know
} }
@ -236,9 +211,9 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder"); vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder");
if (vanillaDecoder == null) if (vanillaDecoder == null)
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() ); throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline());
if (vanillaEncoder == null) if (vanillaEncoder == null)
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() ); throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline());
patchEncoder(vanillaEncoder); patchEncoder(vanillaEncoder);
if (DECODE_BUFFER == null) if (DECODE_BUFFER == null)
@ -251,7 +226,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Intercept sent packets // Intercept sent packets
MessageToByteEncoder<Object> protocolEncoder = new MessageToByteEncoder<Object>() { MessageToByteEncoder<Object> protocolEncoder = new MessageToByteEncoder<Object>() {
@Override @Override
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) {
if (packet instanceof WirePacket) { if (packet instanceof WirePacket) {
// Special case for wire format // Special case for wire format
ChannelInjector.this.encodeWirePacket((WirePacket) packet, output); ChannelInjector.this.encodeWirePacket((WirePacket) packet, output);
@ -263,17 +238,17 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
@Override @Override
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
super.write(ctx, packet, promise); super.write(ctx, packet, promise);
ChannelInjector.this.finalWrite(ctx, packet, promise); ChannelInjector.this.finalWrite();
} }
}; };
// Intercept recieved packets // Intercept recieved packets
ChannelInboundHandlerAdapter finishHandler = new ChannelInboundHandlerAdapter() { ChannelInboundHandlerAdapter finishHandler = new ChannelInboundHandlerAdapter() {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) {
// Execute context first // Execute context first
ctx.fireChannelRead(msg); ctx.fireChannelRead(msg);
ChannelInjector.this.finishRead(ctx, msg); ChannelInjector.this.finishRead();
} }
}; };
@ -313,17 +288,14 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
if (event != null && event.isCancelled()) if (event != null && event.isCancelled())
return null; return null;
return new Callable<T>() { return () -> {
@Override T result;
public T call() throws Exception {
T result = null;
// This field must only be updated in the pipeline thread // This field must only be updated in the pipeline thread
currentEvent = event; currentEvent = event;
result = callable.call(); result = callable.call();
currentEvent = null; currentEvent = null;
return result; return result;
}
}; };
} }
@ -335,17 +307,14 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
if (event != null && event.isCancelled()) if (event != null && event.isCancelled())
return null; return null;
return new Runnable() { return () -> {
@Override currentEvent = event;
public void run() { runnable.run();
currentEvent = event; currentEvent = null;
runnable.run();
currentEvent = null;
}
}; };
} }
protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) { PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
// Let the filters handle this packet // Let the filters handle this packet
Object original = accessor.get(instance); Object original = accessor.get(instance);
@ -361,15 +330,17 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
return BYPASSED_PACKET; return BYPASSED_PACKET;
} }
} }
PacketEvent event = processSending(original);
PacketEvent event = processSending(original);
if (event != null && !event.isCancelled()) { if (event != null && !event.isCancelled()) {
Object changed = event.getPacket().getHandle(); Object changed = event.getPacket().getHandle();
// Change packet to be scheduled // Change packet to be scheduled
if (original != changed) if (original != changed) {
accessor.set(instance, changed); accessor.set(instance, changed);
}; }
}
return event != null ? event : BYPASSED_PACKET; return event != null ? event : BYPASSED_PACKET;
} }
}); });
@ -406,17 +377,20 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
if (ENCODER_TYPE_MATCHER == null) { if (ENCODER_TYPE_MATCHER == null) {
ENCODER_TYPE_MATCHER = Accessors.getFieldAccessor(encoder.getClass(), "matcher", true); ENCODER_TYPE_MATCHER = Accessors.getFieldAccessor(encoder.getClass(), "matcher", true);
} }
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass())); ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (channelListener.isDebug()) if (channelListener.isDebug()) {
cause.printStackTrace(); cause.printStackTrace();
}
super.exceptionCaught(ctx, cause); super.exceptionCaught(ctx, cause);
} }
protected void encodeWirePacket(WirePacket packet, ByteBuf output) throws Exception { private void encodeWirePacket(WirePacket packet, ByteBuf output) {
packet.writeId(output); packet.writeId(output);
packet.writeBytes(output); packet.writeBytes(output);
} }
@ -426,9 +400,8 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
* @param ctx - the current context. * @param ctx - the current context.
* @param packet - the packet to encode to a byte array. * @param packet - the packet to encode to a byte array.
* @param output - the output byte array. * @param output - the output byte array.
* @throws Exception If anything went wrong.
*/ */
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { private void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) {
NetworkMarker marker = null; NetworkMarker marker = null;
PacketEvent event = currentEvent; PacketEvent event = currentEvent;
@ -457,6 +430,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
} }
} }
if (event != null) { if (event != null) {
// Retrieve marker without accidentally constructing it // Retrieve marker without accidentally constructing it
marker = NetworkMarker.getNetworkMarker(event); marker = NetworkMarker.getNetworkMarker(event);
@ -476,7 +450,6 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Sent listeners? // Sent listeners?
finalEvent = event; finalEvent = event;
return;
} }
} catch (Exception e) { } catch (Exception e) {
channelListener.getReporter().reportDetailed(this, channelListener.getReporter().reportDetailed(this,
@ -491,12 +464,9 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
/** /**
* Invoked when a packet has been written to the channel. * Invoked when a packet has been written to the channel
* @param ctx - current context.
* @param packet - the packet that has been written.
* @param promise - a promise.
*/ */
protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) { private void finalWrite() {
PacketEvent event = finalEvent; PacketEvent event = finalEvent;
if (event != null) { if (event != null) {
@ -510,17 +480,11 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
private void scheduleMainThread(final Object packetCopy) { private void scheduleMainThread(final Object packetCopy) {
// Don't use BukkitExecutors for this - it has a bit of overhead // Don't use BukkitExecutors for this - it has a bit of overhead
Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), new Runnable() { Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), () -> invokeSendPacket(packetCopy));
@Override
public void run() {
invokeSendPacket(packetCopy);
}
});
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) {
byteBuffer.markReaderIndex();
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets); DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
try { try {
@ -546,12 +510,13 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
if (output != null) { if (output != null) {
if (output.isCancelled()) { if (output.isCancelled()) {
it.remove(); it.remove();
continue; } else {
} else if (output.getPacket().getHandle() != input) { if (output.getPacket().getHandle() != input) {
it.set(output.getPacket().getHandle()); it.set(output.getPacket().getHandle());
} }
finishQueue.addLast(output); finishQueue.addLast(output);
}
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -561,11 +526,9 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
} }
/** /**
* Invoked after our decoder. * Invoked after our decoder
* @param ctx - current context.
* @param msg - the current packet.
*/ */
protected void finishRead(ChannelHandlerContext ctx, Object msg) { private void finishRead() {
// Assume same order // Assume same order
PacketEvent event = finishQueue.pollFirst(); PacketEvent event = finishQueue.pollFirst();
@ -583,7 +546,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
* @param packetClass - the packet class. * @param packetClass - the packet class.
* @param packet - the packet. * @param packet - the packet.
*/ */
protected void handleLogin(Class<?> packetClass, Object packet) { private void handleLogin(Class<?> packetClass, Object packet) {
// Try to find the login packet class // Try to find the login packet class
if (PACKET_LOGIN_CLIENT == null) { if (PACKET_LOGIN_CLIENT == null) {
PACKET_LOGIN_CLIENT = PacketType.Login.Client.START.getPacketClass(); PACKET_LOGIN_CLIENT = PacketType.Login.Client.START.getPacketClass();
@ -701,15 +664,12 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// TODO: Ensure the packet listeners are executed in the channel thread. // TODO: Ensure the packet listeners are executed in the channel thread.
// Execute this in the channel thread // Execute this in the channel thread
Runnable action = new Runnable() { Runnable action = () -> {
@Override try {
public void run() { MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet);
try { } catch (Exception e) {
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet); // Inform the user
} catch (Exception e) { ProtocolLibrary.getErrorReporter().reportMinimal(factory.getPlugin(), "recieveClientPacket", e);
// Inform the user
ProtocolLibrary.getErrorReporter().reportMinimal(factory.getPlugin(), "recieveClientPacket", e);
}
} }
}; };
@ -821,19 +781,16 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent. // the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
// //
// TL;DR: Concurrency is hard. // TL;DR: Concurrency is hard.
executeInChannelThread(new Runnable() { executeInChannelThread(() -> {
@Override String[] handlers = new String[] {
public void run() { "protocol_lib_decoder", "protocol_lib_finish", "protocol_lib_encoder"
String[] handlers = new String[] { };
"protocol_lib_decoder", "protocol_lib_finish", "protocol_lib_encoder"
};
for (String handler : handlers) { for (String handler : handlers) {
try { try {
originalChannel.pipeline().remove(handler); originalChannel.pipeline().remove(handler);
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
// Ignore // Ignore
}
} }
} }
}); });
@ -856,15 +813,12 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
* @param command - the command to execute. * @param command - the command to execute.
*/ */
private void executeInChannelThread(final Runnable command) { private void executeInChannelThread(final Runnable command) {
originalChannel.eventLoop().execute(new Runnable() { originalChannel.eventLoop().execute(() -> {
@Override try {
public void run() { command.run();
try { } catch (Exception e) {
command.run(); ProtocolLibrary.getErrorReporter().reportDetailed(ChannelInjector.this,
} catch (Exception e) {
ProtocolLibrary.getErrorReporter().reportDetailed(ChannelInjector.this,
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build()); Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
}
} }
}); });
} }
@ -875,7 +829,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
* @param clazz - the type. * @param clazz - the type.
* @return The first handler, or NULL. * @return The first handler, or NULL.
*/ */
public static ChannelHandler findChannelHandler(Channel channel, Class<?> clazz) { static ChannelHandler findChannelHandler(Channel channel, Class<?> clazz) {
for (Entry<String, ChannelHandler> entry : channel.pipeline()) { for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (clazz.isAssignableFrom(entry.getValue().getClass())) { if (clazz.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue(); return entry.getValue();
@ -891,27 +845,27 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
public static class ChannelSocketInjector implements SocketInjector { public static class ChannelSocketInjector implements SocketInjector {
private final ChannelInjector injector; private final ChannelInjector injector;
public ChannelSocketInjector(ChannelInjector injector) { ChannelSocketInjector(ChannelInjector injector) {
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL"); this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
} }
@Override @Override
public Socket getSocket() throws IllegalAccessException { public Socket getSocket() {
return SocketAdapter.adapt((SocketChannel) injector.originalChannel); return SocketAdapter.adapt((SocketChannel) injector.originalChannel);
} }
@Override @Override
public SocketAddress getAddress() throws IllegalAccessException { public SocketAddress getAddress() {
return injector.originalChannel.remoteAddress(); return injector.originalChannel.remoteAddress();
} }
@Override @Override
public void disconnect(String message) throws InvocationTargetException { public void disconnect(String message) {
injector.disconnect(message); injector.disconnect(message);
} }
@Override @Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException { public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
injector.sendServerPacket(packet, marker, filtered); injector.sendServerPacket(packet, marker, filtered);
} }
@ -935,7 +889,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
injector.setPlayer(updatedPlayer); injector.setPlayer(updatedPlayer);
} }
public ChannelInjector getChannelInjector() { ChannelInjector getChannelInjector() {
return injector; return injector;
} }
} }