mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-24 11:36:51 +01:00
Identify player connections by socket address.
It's the only thing that will not not be removed when a network manager closes, making it relatively safe to block on.
This commit is contained in:
parent
fbfbd28bea
commit
d60ab3e953
@ -19,15 +19,20 @@ package com.comphenix.protocol.concurrency;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.google.common.collect.MapMaker;
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.RemovalListener;
|
||||||
|
import com.google.common.cache.RemovalNotification;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map that supports blocking on read operations. Null keys are not supported.
|
* A map that supports blocking on read operations. Null keys are not supported.
|
||||||
* <p>
|
* <p>
|
||||||
* Keys are stored as weak references, and will be automatically removed once they've all been dereferenced.
|
* Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
|
||||||
* <p>
|
* <p>
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*
|
*
|
||||||
@ -35,10 +40,10 @@ import com.google.common.collect.MapMaker;
|
|||||||
* @param <TValue> - type of the value.
|
* @param <TValue> - type of the value.
|
||||||
*/
|
*/
|
||||||
public class BlockingHashMap<TKey, TValue> {
|
public class BlockingHashMap<TKey, TValue> {
|
||||||
|
|
||||||
// Map of values
|
// Map of values
|
||||||
|
private final Cache<TKey, TValue> backingCache;
|
||||||
private final ConcurrentMap<TKey, TValue> backingMap;
|
private final ConcurrentMap<TKey, TValue> backingMap;
|
||||||
|
|
||||||
// Map of locked objects
|
// Map of locked objects
|
||||||
private final ConcurrentMap<TKey, Object> locks;
|
private final ConcurrentMap<TKey, Object> locks;
|
||||||
|
|
||||||
@ -46,8 +51,24 @@ public class BlockingHashMap<TKey, TValue> {
|
|||||||
* Initialize a new map.
|
* Initialize a new map.
|
||||||
*/
|
*/
|
||||||
public BlockingHashMap() {
|
public BlockingHashMap() {
|
||||||
backingMap = new MapMaker().weakKeys().makeMap();
|
backingCache = CacheBuilder.newBuilder().weakValues().removalListener(
|
||||||
locks = new MapMaker().weakKeys().makeMap();
|
new RemovalListener<TKey, TValue>() {
|
||||||
|
@Override
|
||||||
|
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
|
||||||
|
// Clean up locks too
|
||||||
|
locks.remove(entry.getKey());
|
||||||
|
}
|
||||||
|
}).build(
|
||||||
|
new CacheLoader<TKey, TValue>() {
|
||||||
|
@Override
|
||||||
|
public TValue load(TKey key) throws Exception {
|
||||||
|
throw new IllegalStateException("Illegal use. Access the map directly instead.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
backingMap = backingCache.asMap();
|
||||||
|
|
||||||
|
// Normal concurrent hash map
|
||||||
|
locks = new ConcurrentHashMap<TKey, Object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,34 +115,57 @@ public class BlockingHashMap<TKey, TValue> {
|
|||||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||||
*/
|
*/
|
||||||
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
return get(key, timeout, unit, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||||
|
* <p>
|
||||||
|
* If timeout is zero, this method will return immediately if it can't find an socket injector.
|
||||||
|
*
|
||||||
|
* @param key - the key whose associated value is to be returned
|
||||||
|
* @param timeout - the amount of time to wait until an association has been made.
|
||||||
|
* @param unit - unit of timeout.
|
||||||
|
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
|
||||||
|
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||||
|
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||||
|
*/
|
||||||
|
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
|
||||||
if (key == null)
|
if (key == null)
|
||||||
throw new IllegalArgumentException("key cannot be NULL.");
|
throw new IllegalArgumentException("key cannot be NULL.");
|
||||||
if (unit == null)
|
if (unit == null)
|
||||||
throw new IllegalArgumentException("Unit cannot be NULL.");
|
throw new IllegalArgumentException("Unit cannot be NULL.");
|
||||||
|
if (timeout < 0)
|
||||||
|
throw new IllegalArgumentException("Timeout cannot be less than zero.");
|
||||||
|
|
||||||
TValue value = backingMap.get(key);
|
TValue value = backingMap.get(key);
|
||||||
|
|
||||||
// Only lock if no value is available
|
// Only lock if no value is available
|
||||||
if (value == null) {
|
if (value == null && timeout > 0) {
|
||||||
final Object lock = getLock(key);
|
final Object lock = getLock(key);
|
||||||
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
||||||
|
|
||||||
// Don't exceed the timeout
|
// Don't exceed the timeout
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
while (value == null) {
|
while (value == null) {
|
||||||
long remainingTime = stopTimeNS - System.nanoTime();
|
try {
|
||||||
|
long remainingTime = stopTimeNS - System.nanoTime();
|
||||||
if (remainingTime > 0) {
|
|
||||||
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
if (remainingTime > 0) {
|
||||||
value = backingMap.get(key);
|
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
||||||
} else {
|
value = backingMap.get(key);
|
||||||
// Timeout elapsed
|
} else {
|
||||||
break;
|
// Timeout elapsed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// This is fairly dangerous - but we might HAVE to block the thread
|
||||||
|
if (!ignoreInterrupted)
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,18 +17,14 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol.injector.player;
|
package com.comphenix.protocol.injector.player;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.injector.GamePhase;
|
import com.comphenix.protocol.injector.GamePhase;
|
||||||
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
@ -39,23 +35,21 @@ import com.google.common.collect.Maps;
|
|||||||
*/
|
*/
|
||||||
class NetLoginInjector {
|
class NetLoginInjector {
|
||||||
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
||||||
|
|
||||||
private static Field networkManagerField;
|
|
||||||
private static Field socketAddressField;
|
|
||||||
|
|
||||||
// Handles every hook
|
// Handles every hook
|
||||||
private ProxyPlayerInjectionHandler injectionHandler;
|
private ProxyPlayerInjectionHandler injectionHandler;
|
||||||
|
|
||||||
|
// Create temporary players
|
||||||
|
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
||||||
|
|
||||||
// Associate input streams and injectors
|
// The current error reporter
|
||||||
private AbstractInputStreamLookup inputStreamLookup;
|
|
||||||
|
|
||||||
// The current error rerporter
|
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
|
private Server server;
|
||||||
|
|
||||||
public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, AbstractInputStreamLookup inputStreamLookup) {
|
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
|
this.server = server;
|
||||||
this.injectionHandler = injectionHandler;
|
this.injectionHandler = injectionHandler;
|
||||||
this.inputStreamLookup = inputStreamLookup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,22 +63,16 @@ class NetLoginInjector {
|
|||||||
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
||||||
return inserting;
|
return inserting;
|
||||||
|
|
||||||
Object networkManager = getNetworkManager(inserting);
|
Player temporary = playerFactory.createTemporaryPlayer(server);
|
||||||
SocketAddress address = getAddress(networkManager);
|
PlayerInjector injector = injectionHandler.injectPlayer(temporary, inserting, GamePhase.LOGIN);
|
||||||
|
|
||||||
// Get the underlying socket
|
if (injector != null) {
|
||||||
SocketInjector socketInjector = inputStreamLookup.getSocketInjector(address);
|
// Update injector as well
|
||||||
|
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
|
||||||
// This is the case if we're dealing with a connection initiated by the injected server socket
|
injector.updateOnLogin = true;
|
||||||
if (socketInjector != null) {
|
|
||||||
PlayerInjector injector = injectionHandler.injectPlayer(socketInjector.getPlayer(), inserting, GamePhase.LOGIN);
|
// Save the login
|
||||||
|
injectedLogins.putIfAbsent(inserting, injector);
|
||||||
if (injector != null) {
|
|
||||||
injector.updateOnLogin = true;
|
|
||||||
|
|
||||||
// Save the login
|
|
||||||
injectedLogins.putIfAbsent(inserting, injector);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
|
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
|
||||||
@ -98,36 +86,6 @@ class NetLoginInjector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the network manager from a given pending connection.
|
|
||||||
* @param inserting - the pending connection.
|
|
||||||
* @return The referenced network manager.
|
|
||||||
* @throws IllegalAccessException If we are unable to read the network manager.
|
|
||||||
*/
|
|
||||||
private Object getNetworkManager(Object inserting) throws IllegalAccessException {
|
|
||||||
if (networkManagerField == null) {
|
|
||||||
networkManagerField = FuzzyReflection.fromObject(inserting, true).
|
|
||||||
getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
return FieldUtils.readField(networkManagerField, inserting, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the socket address stored in a network manager.
|
|
||||||
* @param networkManager - the network manager.
|
|
||||||
* @return The associated socket address.
|
|
||||||
* @throws IllegalAccessException If we are unable to read the address.
|
|
||||||
*/
|
|
||||||
private SocketAddress getAddress(Object networkManager) throws IllegalAccessException {
|
|
||||||
if (socketAddressField == null) {
|
|
||||||
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
|
|
||||||
getFieldByType("socketAddress", SocketAddress.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a NetLoginHandler should be reverted.
|
* Invoked when a NetLoginHandler should be reverted.
|
||||||
* @param inserting - the original NetLoginHandler.
|
* @param inserting - the original NetLoginHandler.
|
||||||
|
@ -180,6 +180,7 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!tryInjectManager()) {
|
if (!tryInjectManager()) {
|
||||||
|
Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
|
||||||
|
|
||||||
// Try to override the proxied object
|
// Try to override the proxied object
|
||||||
if (proxyServerField != null) {
|
if (proxyServerField != null) {
|
||||||
@ -188,6 +189,8 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
|
|
||||||
if (serverHandler == null)
|
if (serverHandler == null)
|
||||||
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
|
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
|
||||||
|
else
|
||||||
|
serverHandlerClass = serverHandler.getClass();
|
||||||
|
|
||||||
// Try again
|
// Try again
|
||||||
if (tryInjectManager()) {
|
if (tryInjectManager()) {
|
||||||
@ -198,7 +201,7 @@ class NetworkServerInjector extends PlayerInjector {
|
|||||||
|
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Cannot hook player: Unable to find a valid constructor for the "
|
"Cannot hook player: Unable to find a valid constructor for the "
|
||||||
+ MinecraftReflection.getNetServerHandlerClass().getName() + " object.");
|
+ serverHandlerClass.getName() + " object.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ abstract class PlayerInjector implements SocketInjector {
|
|||||||
protected static Field networkManagerField;
|
protected static Field networkManagerField;
|
||||||
protected static Field netHandlerField;
|
protected static Field netHandlerField;
|
||||||
protected static Field socketField;
|
protected static Field socketField;
|
||||||
|
protected static Field socketAddressField;
|
||||||
|
|
||||||
private static Field inputField;
|
private static Field inputField;
|
||||||
private static Field entityPlayerField;
|
private static Field entityPlayerField;
|
||||||
@ -87,8 +88,9 @@ abstract class PlayerInjector implements SocketInjector {
|
|||||||
protected Object serverHandler;
|
protected Object serverHandler;
|
||||||
protected Object netHandler;
|
protected Object netHandler;
|
||||||
|
|
||||||
// Current socket
|
// Current socket and address
|
||||||
protected Socket socket;
|
protected Socket socket;
|
||||||
|
protected SocketAddress socketAddress;
|
||||||
|
|
||||||
// The packet manager and filters
|
// The packet manager and filters
|
||||||
protected ListenerInvoker invoker;
|
protected ListenerInvoker invoker;
|
||||||
@ -250,7 +252,8 @@ abstract class PlayerInjector implements SocketInjector {
|
|||||||
public Socket getSocket() throws IllegalAccessException {
|
public Socket getSocket() throws IllegalAccessException {
|
||||||
try {
|
try {
|
||||||
if (socketField == null)
|
if (socketField == null)
|
||||||
socketField = FuzzyReflection.fromObject(networkManager, true).getFieldListByType(Socket.class).get(0);
|
socketField = FuzzyReflection.fromObject(networkManager, true).
|
||||||
|
getFieldListByType(Socket.class).get(0);
|
||||||
if (socket == null)
|
if (socket == null)
|
||||||
socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
|
socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
|
||||||
return socket;
|
return socket;
|
||||||
@ -261,19 +264,23 @@ abstract class PlayerInjector implements SocketInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the associated address of this player.
|
* Retrieve the associated remote address of a player.
|
||||||
* @return The associated address.
|
* @return The associated remote address..
|
||||||
* @throws IllegalAccessException If we're unable to read the socket field.
|
* @throws IllegalAccessException If we're unable to read the socket address field.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SocketAddress getAddress() throws IllegalAccessException {
|
public SocketAddress getAddress() throws IllegalAccessException {
|
||||||
Socket socket = getSocket();
|
try {
|
||||||
|
if (socketAddressField == null)
|
||||||
// Guard against NULL
|
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
|
||||||
if (socket != null)
|
getFieldListByType(SocketAddress.class).get(0);
|
||||||
return socket.getRemoteSocketAddress();
|
if (socketAddress == null)
|
||||||
else
|
socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
|
||||||
return null;
|
return socketAddress;
|
||||||
|
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new IllegalAccessException("Unable to read the socket address field.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,9 +20,12 @@ package com.comphenix.protocol.injector.player;
|
|||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.SocketAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import net.sf.cglib.proxy.Factory;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
@ -39,7 +42,7 @@ import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
|||||||
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||||
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
|
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
|
||||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
@ -105,7 +108,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
build();
|
build();
|
||||||
|
|
||||||
// Create net login injectors and the server connection injector
|
// Create net login injectors and the server connection injector
|
||||||
this.netLoginInjector = new NetLoginInjector(reporter, this, inputStreamLookup);
|
this.netLoginInjector = new NetLoginInjector(reporter, server, this);
|
||||||
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
|
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
|
||||||
serverInjection.injectList();
|
serverInjection.injectList();
|
||||||
}
|
}
|
||||||
@ -216,7 +219,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
@Override
|
@Override
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream) {
|
public Player getPlayerByConnection(DataInputStream inputStream) {
|
||||||
// Wait until the connection owner has been established
|
// Wait until the connection owner has been established
|
||||||
SocketInjector injector = inputStreamLookup.getSocketInjector(inputStream);
|
SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream);
|
||||||
|
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
return injector.getPlayer();
|
return injector.getPlayer();
|
||||||
@ -309,24 +312,18 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
injector.initialize(injectionPoint);
|
injector.initialize(injectionPoint);
|
||||||
|
|
||||||
// Get socket and socket injector
|
// Get socket and socket injector
|
||||||
Socket socket = injector.getSocket();
|
SocketAddress address = injector.getAddress();
|
||||||
SocketInjector previous = null;
|
SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
|
||||||
|
|
||||||
// Due to a race condition, the main server "accept connections" thread may
|
|
||||||
// get a closed network manager with a NULL input stream,
|
|
||||||
if (socket == null) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close any previously associated hooks before we proceed
|
// Close any previously associated hooks before we proceed
|
||||||
if (previous != null && previous instanceof PlayerInjector) {
|
if (previous != null && !(player instanceof Factory)) {
|
||||||
uninjectPlayer(previous.getPlayer(), true);
|
uninjectPlayer(previous.getPlayer(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
injector.injectManager();
|
injector.injectManager();
|
||||||
|
|
||||||
// Save injector
|
// Save injector
|
||||||
inputStreamLookup.setSocketInjector(socket, injector);
|
inputStreamLookup.setSocketInjector(address, injector);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,7 +450,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
@Override
|
@Override
|
||||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||||
if (!hasClosed && address != null) {
|
if (!hasClosed && address != null) {
|
||||||
SocketInjector injector = inputStreamLookup.getSocketInjector(address);
|
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
if (injector != null)
|
if (injector != null)
|
||||||
@ -495,7 +492,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||||
|
|
||||||
PlayerInjector injector = getInjector(player);
|
PlayerInjector injector = getInjector(player);
|
||||||
|
|
||||||
// Process the given packet, or simply give up
|
// Process the given packet, or simply give up
|
||||||
@ -518,16 +514,13 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
|
|
||||||
if (injector == null) {
|
if (injector == null) {
|
||||||
// Try getting it from the player itself
|
// Try getting it from the player itself
|
||||||
SocketInjector socket = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
SocketAddress address = player.getAddress();
|
||||||
|
// Look that up without blocking
|
||||||
// Only accept it if it's a player injector
|
SocketInjector result = inputStreamLookup.peekSocketInjector(address);
|
||||||
if (!(socket instanceof PlayerInjector)) {
|
|
||||||
socket = inputStreamLookup.getSocketInjector(player.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that it is a player injector
|
// Ensure that it is non-null and a player injector
|
||||||
if (socket instanceof PlayerInjector)
|
if (result instanceof PlayerInjector)
|
||||||
return (PlayerInjector) socket;
|
return (PlayerInjector) result;
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -63,43 +63,40 @@ public abstract class AbstractInputStreamLookup {
|
|||||||
*/
|
*/
|
||||||
public abstract void postWorldLoaded();
|
public abstract void postWorldLoaded();
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the associated socket injector for a player.
|
|
||||||
* @param filtered - the indentifying filtered input stream.
|
|
||||||
* @return The socket injector we have associated with this player.
|
|
||||||
* @throws FieldAccessException Unable to access input stream.
|
|
||||||
*/
|
|
||||||
public SocketInjector getSocketInjector(FilterInputStream filtered) {
|
|
||||||
return getSocketInjector(getInputStream(filtered));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the associated socket injector for a player.
|
* Retrieve the associated socket injector for a player.
|
||||||
* @param input - the indentifying filtered input stream.
|
* @param input - the indentifying filtered input stream.
|
||||||
* @return The socket injector we have associated with this player.
|
* @return The socket injector we have associated with this player.
|
||||||
*/
|
*/
|
||||||
public abstract SocketInjector getSocketInjector(InputStream input);
|
public abstract SocketInjector waitSocketInjector(InputStream input);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an injector by its socket.
|
* Retrieve an injector by its socket.
|
||||||
* @param socket - the socket.
|
* @param socket - the socket.
|
||||||
* @return The socket injector.
|
* @return The socket injector.
|
||||||
*/
|
*/
|
||||||
public abstract SocketInjector getSocketInjector(Socket socket);
|
public abstract SocketInjector waitSocketInjector(Socket socket);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a injector by its address.
|
* Retrieve a injector by its address.
|
||||||
* @param address - the address of the socket.
|
* @param address - the address of the socket.
|
||||||
* @return The socket injector, or NULL if not found.
|
* @return The socket injector, or NULL if not found.
|
||||||
*/
|
*/
|
||||||
public abstract SocketInjector getSocketInjector(SocketAddress address);
|
public abstract SocketInjector waitSocketInjector(SocketAddress address);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associate a given socket the provided socket injector.
|
* Attempt to get a socket injector without blocking the thread.
|
||||||
* @param input - the socket to associate.
|
* @param address - the address to lookup.
|
||||||
|
* @return The socket injector, or NULL if not found.
|
||||||
|
*/
|
||||||
|
public abstract SocketInjector peekSocketInjector(SocketAddress address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate a given socket address to the provided socket injector.
|
||||||
|
* @param input - the socket address to associate.
|
||||||
* @param injector - the injector.
|
* @param injector - the injector.
|
||||||
*/
|
*/
|
||||||
public abstract void setSocketInjector(Socket socket, SocketInjector injector);
|
public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a player can hold a reference to its parent injector, this method will update that reference.
|
* If a player can hold a reference to its parent injector, this method will update that reference.
|
||||||
@ -111,8 +108,7 @@ public abstract class AbstractInputStreamLookup {
|
|||||||
|
|
||||||
// Default implementation
|
// Default implementation
|
||||||
if (player instanceof InjectorContainer) {
|
if (player instanceof InjectorContainer) {
|
||||||
InjectorContainer container = (InjectorContainer) player;
|
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
|
||||||
container.setInjector(current);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,11 @@ import java.lang.reflect.Field;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
|
import com.comphenix.protocol.concurrency.BlockingHashMap;
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
@ -17,18 +18,33 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
|
|||||||
import com.google.common.collect.MapMaker;
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
||||||
// Using weak keys and values ensures that we will not hold up garbage collection
|
// The default lookup timeout
|
||||||
protected ConcurrentMap<Socket, SocketInjector> ownerSocket = new MapMaker().weakKeys().makeMap();
|
private static final long DEFAULT_TIMEOUT = 2000; // ms
|
||||||
protected ConcurrentMap<SocketAddress, Socket> addressLookup = new MapMaker().weakValues().makeMap();
|
|
||||||
protected ConcurrentMap<InputStream, Socket> inputLookup = new MapMaker().weakValues().makeMap();
|
// Using weak keys and values ensures that we will not hold up garbage collection
|
||||||
|
protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
|
||||||
|
protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
|
||||||
|
|
||||||
|
// The timeout
|
||||||
|
private final long injectorTimeout;
|
||||||
|
|
||||||
// Used to create fake players
|
|
||||||
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
|
||||||
|
|
||||||
public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
|
public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
|
||||||
super(reporter, server);
|
this(reporter, server, DEFAULT_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a reflect lookup with a given default injector timeout.
|
||||||
|
* <p>
|
||||||
|
* This timeout defines the maximum amount of time to wait until an injector has been discovered.
|
||||||
|
* @param reporter - the error reporter.
|
||||||
|
* @param server - the current Bukkit server.
|
||||||
|
* @param injectorTimeout - the injector timeout.
|
||||||
|
*/
|
||||||
|
public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
|
||||||
|
super(reporter, server);
|
||||||
|
this.injectorTimeout = injectorTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inject(Object container) {
|
public void inject(Object container) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
@ -38,34 +54,45 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
|||||||
public void postWorldLoaded() {
|
public void postWorldLoaded() {
|
||||||
// Nothing again
|
// Nothing again
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SocketInjector getSocketInjector(Socket socket) {
|
public SocketInjector peekSocketInjector(SocketAddress address) {
|
||||||
SocketInjector result = ownerSocket.get(socket);
|
try {
|
||||||
|
return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
|
||||||
if (result == null) {
|
} catch (InterruptedException e) {
|
||||||
Player player = tempPlayerFactory.createTemporaryPlayer(server);
|
// Whatever
|
||||||
SocketInjector created = new TemporarySocketInjector(player, socket);
|
return null;
|
||||||
|
|
||||||
result = ownerSocket.putIfAbsent(socket, created);
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
// We won - use our created injector
|
|
||||||
TemporaryPlayerFactory.setInjectorInPlayer(player, created);
|
|
||||||
result = created;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SocketInjector getSocketInjector(InputStream input) {
|
public SocketInjector waitSocketInjector(SocketAddress address) {
|
||||||
try {
|
try {
|
||||||
Socket socket = getSocket(input);
|
// Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
|
||||||
|
// periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
|
||||||
|
// to catch up, so we'll swallow these interrupts.
|
||||||
|
//
|
||||||
|
// TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
|
||||||
|
return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// This cannot be!
|
||||||
|
throw new IllegalStateException("Impossible exception occured!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketInjector waitSocketInjector(Socket socket) {
|
||||||
|
return waitSocketInjector(socket.getRemoteSocketAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketInjector waitSocketInjector(InputStream input) {
|
||||||
|
try {
|
||||||
|
SocketAddress address = getSocketAddress(input);
|
||||||
|
|
||||||
// Guard against NPE
|
// Guard against NPE
|
||||||
if (socket != null)
|
if (address != null)
|
||||||
return getSocketInjector(socket);
|
return waitSocketInjector(address);
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
@ -74,38 +101,36 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use reflection to get the underlying socket from an input stream.
|
* Use reflection to get the underlying socket address from an input stream.
|
||||||
* @param stream - the socket stream to lookup.
|
* @param stream - the socket stream to lookup.
|
||||||
* @return The underlying socket, or NULL if not found.
|
* @return The underlying socket address, or NULL if not found.
|
||||||
* @throws IllegalAccessException Unable to access socket field.
|
* @throws IllegalAccessException Unable to access socket field.
|
||||||
*/
|
*/
|
||||||
private Socket getSocket(InputStream stream) throws IllegalAccessException {
|
private SocketAddress getSocketAddress(InputStream stream) throws IllegalAccessException {
|
||||||
// Extra check, just in case
|
// Extra check, just in case
|
||||||
if (stream instanceof FilterInputStream)
|
if (stream instanceof FilterInputStream)
|
||||||
return getSocket(getInputStream((FilterInputStream) stream));
|
return getSocketAddress(getInputStream((FilterInputStream) stream));
|
||||||
|
|
||||||
Socket result = inputLookup.get(stream);
|
SocketAddress result = inputLookup.get(stream);
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = lookupSocket(stream);
|
Socket socket = lookupSocket(stream);
|
||||||
|
|
||||||
// Save it
|
// Save it
|
||||||
|
result = socket.getRemoteSocketAddress();
|
||||||
inputLookup.put(stream, result);
|
inputLookup.put(stream, result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSocketInjector(Socket socket, SocketInjector injector) {
|
public void setSocketInjector(SocketAddress address, SocketInjector injector) {
|
||||||
if (socket == null)
|
if (address == null)
|
||||||
throw new IllegalArgumentException("socket cannot be NULL");
|
throw new IllegalArgumentException("address cannot be NULL");
|
||||||
if (injector == null)
|
if (injector == null)
|
||||||
throw new IllegalArgumentException("injector cannot be NULL.");
|
throw new IllegalArgumentException("injector cannot be NULL.");
|
||||||
|
|
||||||
SocketInjector previous = ownerSocket.put(socket, injector);
|
SocketInjector previous = addressLookup.put(address, injector);
|
||||||
|
|
||||||
// Save the address lookup too
|
|
||||||
addressLookup.put(socket.getRemoteSocketAddress(), socket);
|
|
||||||
|
|
||||||
// Any previous temporary players will also be associated
|
// Any previous temporary players will also be associated
|
||||||
if (previous != null) {
|
if (previous != null) {
|
||||||
@ -113,16 +138,6 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
|||||||
onPreviousSocketOverwritten(previous, injector);
|
onPreviousSocketOverwritten(previous, injector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public SocketInjector getSocketInjector(SocketAddress address) {
|
|
||||||
Socket socket = addressLookup.get(address);
|
|
||||||
|
|
||||||
if (socket != null)
|
|
||||||
return getSocketInjector(socket);
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cleanupAll() {
|
public void cleanupAll() {
|
||||||
|
Loading…
Reference in New Issue
Block a user