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
1 changed files with 89 additions and 135 deletions

View File

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