Makes more sense to put this in the reflect lookup.

This commit is contained in:
Kristian S. Stangeland 2013-04-28 17:27:58 +02:00
parent bec05967d3
commit 8964246e22
2 changed files with 277 additions and 282 deletions

View File

@ -1,119 +1,87 @@
package com.comphenix.protocol.injector.server; package com.comphenix.protocol.injector.server;
import java.io.FilterInputStream; import java.io.InputStream;
import java.io.InputStream; import java.net.Socket;
import java.lang.reflect.Field; import java.net.SocketAddress;
import java.net.Socket; import org.bukkit.Server;
import java.net.SocketAddress; import org.bukkit.entity.Player;
import org.bukkit.Server;
import org.bukkit.entity.Player; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.ErrorReporter; public abstract class AbstractInputStreamLookup {
import com.comphenix.protocol.reflect.FieldAccessException; // Error reporter
import com.comphenix.protocol.reflect.FieldUtils; protected final ErrorReporter reporter;
import com.comphenix.protocol.reflect.FuzzyReflection;
// Reference to the server itself
public abstract class AbstractInputStreamLookup { protected final Server server;
// Used to access the inner input stream of a filtered input stream
private static Field filteredInputField; protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
this.reporter = reporter;
// Error reporter this.server = server;
protected final ErrorReporter reporter; }
// Reference to the server itself /**
protected final Server server; * Inject the given server thread or dedicated connection.
* @param container - class that contains a ServerSocket field.
protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) { */
this.reporter = reporter; public abstract void inject(Object container);
this.server = server;
} /**
* Invoked when the world has loaded.
/** */
* Retrieve the underlying input stream that is associated with a given filter input stream. public abstract void postWorldLoaded();
* @param filtered - the filter input stream.
* @return The underlying input stream that is being filtered. /**
* @throws FieldAccessException Unable to access input stream. * Retrieve the associated socket injector for a player.
*/ * @param input - the indentifying filtered input stream.
protected static InputStream getInputStream(FilterInputStream filtered) { * @return The socket injector we have associated with this player.
if (filteredInputField == null) */
filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true). public abstract SocketInjector waitSocketInjector(InputStream input);
getFieldByType("in", InputStream.class);
/**
InputStream current = filtered; * Retrieve an injector by its socket.
* @param socket - the socket.
try { * @return The socket injector.
// Iterate until we find the real input stream */
while (current instanceof FilterInputStream) { public abstract SocketInjector waitSocketInjector(Socket socket);
current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
} /**
return current; * Retrieve a injector by its address.
} catch (IllegalAccessException e) { * @param address - the address of the socket.
throw new FieldAccessException("Cannot access filtered input field.", e); * @return The socket injector, or NULL if not found.
} */
} public abstract SocketInjector waitSocketInjector(SocketAddress address);
/** /**
* Inject the given server thread or dedicated connection. * Attempt to get a socket injector without blocking the thread.
* @param container - class that contains a ServerSocket field. * @param address - the address to lookup.
*/ * @return The socket injector, or NULL if not found.
public abstract void inject(Object container); */
public abstract SocketInjector peekSocketInjector(SocketAddress address);
/**
* Invoked when the world has loaded. /**
*/ * Associate a given socket address to the provided socket injector.
public abstract void postWorldLoaded(); * @param address - the socket address to associate.
* @param injector - the injector.
/** */
* Retrieve the associated socket injector for a player. public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
* @param input - the indentifying filtered input stream.
* @return The socket injector we have associated with this player. /**
*/ * If a player can hold a reference to its parent injector, this method will update that reference.
public abstract SocketInjector waitSocketInjector(InputStream input); * @param previous - the previous injector.
* @param current - the new injector.
/** */
* Retrieve an injector by its socket. protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
* @param socket - the socket. Player player = previous.getPlayer();
* @return The socket injector.
*/ // Default implementation
public abstract SocketInjector waitSocketInjector(Socket socket); if (player instanceof InjectorContainer) {
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
/** }
* Retrieve a injector by its address. }
* @param address - the address of the socket.
* @return The socket injector, or NULL if not found. /**
*/ * Invoked when the injection should be undone.
public abstract SocketInjector waitSocketInjector(SocketAddress address); */
public abstract void cleanupAll();
/**
* Attempt to get a socket injector without blocking the thread.
* @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 address - the socket address to associate.
* @param injector - the 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.
* @param previous - the previous injector.
* @param current - the new injector.
*/
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
Player player = previous.getPlayer();
// Default implementation
if (player instanceof InjectorContainer) {
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
}
}
/**
* Invoked when the injection should be undone.
*/
public abstract void cleanupAll();
} }

View File

@ -1,164 +1,191 @@
package com.comphenix.protocol.injector.server; package com.comphenix.protocol.injector.server;
import java.io.FilterInputStream; import java.io.FilterInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Field; 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 java.util.concurrent.TimeUnit;
import org.bukkit.Server; import org.bukkit.Server;
import com.comphenix.protocol.concurrency.BlockingHashMap; 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;
import com.comphenix.protocol.reflect.FuzzyReflection; 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 {
// The default lookup timeout // Used to access the inner input stream of a filtered input stream
private static final long DEFAULT_TIMEOUT = 2000; // ms private static Field filteredInputField;
// Using weak keys and values ensures that we will not hold up garbage collection // The default lookup timeout
protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>(); private static final long DEFAULT_TIMEOUT = 2000; // ms
protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
// Using weak keys and values ensures that we will not hold up garbage collection
// The timeout protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
private final long injectorTimeout; protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
public InputStreamReflectLookup(ErrorReporter reporter, Server server) { // The timeout
this(reporter, server, DEFAULT_TIMEOUT); private final long injectorTimeout;
}
public InputStreamReflectLookup(ErrorReporter reporter, Server 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. * Initialize a reflect lookup with a given default injector timeout.
* @param server - the current Bukkit server. * <p>
* @param injectorTimeout - the injector timeout. * This timeout defines the maximum amount of time to wait until an injector has been discovered.
*/ * @param reporter - the error reporter.
public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) { * @param server - the current Bukkit server.
super(reporter, server); * @param injectorTimeout - the injector timeout.
this.injectorTimeout = injectorTimeout; */
} public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
super(reporter, server);
@Override this.injectorTimeout = injectorTimeout;
public void inject(Object container) { }
// Do nothing
} @Override
public void inject(Object container) {
@Override // Do nothing
public void postWorldLoaded() { }
// Nothing again
} @Override
public void postWorldLoaded() {
@Override // Nothing again
public SocketInjector peekSocketInjector(SocketAddress address) { }
try {
return addressLookup.get(address, 0, TimeUnit.MILLISECONDS); @Override
} catch (InterruptedException e) { public SocketInjector peekSocketInjector(SocketAddress address) {
// Whatever try {
return null; return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
} } catch (InterruptedException e) {
} // Whatever
return null;
@Override }
public SocketInjector waitSocketInjector(SocketAddress address) { }
try {
// Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to @Override
// periodically wake up waiting readers and writers. We have to wait for the dedicated server thread public SocketInjector waitSocketInjector(SocketAddress address) {
// to catch up, so we'll swallow these interrupts. try {
// // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
// TODO: Consider if we should raise the thread priority of the dedicated server listener thread. // periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true); // to catch up, so we'll swallow these interrupts.
} catch (InterruptedException e) { //
// This cannot be! // TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
throw new IllegalStateException("Impossible exception occured!", e); 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(Socket socket) {
@Override return waitSocketInjector(socket.getRemoteSocketAddress());
public SocketInjector waitSocketInjector(InputStream input) { }
try {
SocketAddress address = waitSocketAddress(input); @Override
public SocketInjector waitSocketInjector(InputStream input) {
// Guard against NPE try {
if (address != null) SocketAddress address = waitSocketAddress(input);
return waitSocketInjector(address);
else // Guard against NPE
return null; if (address != null)
} catch (IllegalAccessException e) { return waitSocketInjector(address);
throw new FieldAccessException("Cannot find or access socket field for " + input, e); else
} return null;
} } catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot find or access socket field for " + input, e);
/** }
* Use reflection to get the underlying socket address from an input stream. }
* @param stream - the socket stream to lookup.
* @return The underlying socket address, or NULL if not found. /**
* @throws IllegalAccessException Unable to access socket field. * Use reflection to get the underlying socket address from an input stream.
*/ * @param stream - the socket stream to lookup.
private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException { * @return The underlying socket address, or NULL if not found.
// Extra check, just in case * @throws IllegalAccessException Unable to access socket field.
if (stream instanceof FilterInputStream) */
return waitSocketAddress(getInputStream((FilterInputStream) stream)); private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException {
// Extra check, just in case
SocketAddress result = inputLookup.get(stream); if (stream instanceof FilterInputStream)
return waitSocketAddress(getInputStream((FilterInputStream) stream));
if (result == null) {
Socket socket = lookupSocket(stream); SocketAddress result = inputLookup.get(stream);
// Save it if (result == null) {
result = socket.getRemoteSocketAddress(); Socket socket = lookupSocket(stream);
inputLookup.put(stream, result);
} // Save it
return result; result = socket.getRemoteSocketAddress();
} inputLookup.put(stream, result);
}
@Override return result;
public void setSocketInjector(SocketAddress address, SocketInjector injector) { }
if (address == null)
throw new IllegalArgumentException("address cannot be NULL"); /**
if (injector == null) * Retrieve the underlying input stream that is associated with a given filter input stream.
throw new IllegalArgumentException("injector cannot be NULL."); * @param filtered - the filter input stream.
* @return The underlying input stream that is being filtered.
SocketInjector previous = addressLookup.put(address, injector); * @throws FieldAccessException Unable to access input stream.
*/
// Any previous temporary players will also be associated protected static InputStream getInputStream(FilterInputStream filtered) {
if (previous != null) { if (filteredInputField == null)
// Update the reference to any previous injector filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
onPreviousSocketOverwritten(previous, injector); getFieldByType("in", InputStream.class);
}
} InputStream current = filtered;
@Override try {
public void cleanupAll() { // Iterate until we find the real input stream
// Do nothing while (current instanceof FilterInputStream) {
} current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
}
/** return current;
* Lookup the underlying socket of a stream through reflection. } catch (IllegalAccessException e) {
* @param stream - the socket stream. throw new FieldAccessException("Cannot access filtered input field.", e);
* @return The underlying socket. }
* @throws IllegalAccessException If reflection failed. }
*/
private static Socket lookupSocket(InputStream stream) throws IllegalAccessException { @Override
if (stream instanceof FilterInputStream) { public void setSocketInjector(SocketAddress address, SocketInjector injector) {
return lookupSocket(getInputStream((FilterInputStream) stream)); if (address == null)
} else { throw new IllegalArgumentException("address cannot be NULL");
// Just do it if (injector == null)
Field socketField = FuzzyReflection.fromObject(stream, true). throw new IllegalArgumentException("injector cannot be NULL.");
getFieldByType("socket", Socket.class);
SocketInjector previous = addressLookup.put(address, injector);
return (Socket) FieldUtils.readField(socketField, stream, true);
} // Any previous temporary players will also be associated
} if (previous != null) {
} // Update the reference to any previous injector
onPreviousSocketOverwritten(previous, injector);
}
}
@Override
public void cleanupAll() {
// Do nothing
}
/**
* Lookup the underlying socket of a stream through reflection.
* @param stream - the socket stream.
* @return The underlying socket.
* @throws IllegalAccessException If reflection failed.
*/
private static Socket lookupSocket(InputStream stream) throws IllegalAccessException {
if (stream instanceof FilterInputStream) {
return lookupSocket(getInputStream((FilterInputStream) stream));
} else {
// Just do it
Field socketField = FuzzyReflection.fromObject(stream, true).
getFieldByType("socket", Socket.class);
return (Socket) FieldUtils.readField(socketField, stream, true);
}
}
}