Fixed RMI, so now it will properly detect methods in parent

classes/interfaces, without having to register them explicitly.
CachedMethods are no longer static, and there are fewer lookups
This commit is contained in:
nathan 2018-01-17 21:48:07 +01:00
parent 3e28b68e16
commit 33f8f0843e
33 changed files with 1030 additions and 817 deletions

View File

@ -27,7 +27,17 @@ import io.netty.channel.socket.oio.OioDatagramChannel;
import io.netty.channel.socket.oio.OioServerSocketChannel;
/**
* from: https://blog.cloudflare.com/how-the-consumer-product-safety-commission-is-inadvertently-behind-the-internets-largest-ddos-attacks/
*
* NOTE: CloudFlare has anti-DNS reflection protections in place. Specifically, we automatically upgrade from UDP to TCP when a DNS response
* is particularly large (generally, over 512 bytes). Since TCP requires a handshake, it prevents source IP address spoofing which is
* necessary for a DNS amplification attack.
*
* In addition, we rate limit unknown resolvers. Again, this helps ensure that our infrastructure can't be abused to amplify attacks.
*
* Finally, across our DNS infrastructure we have deprecated ANY queries and have proposed to the IETF to restrict ANY queries to only
* authorized parties. By neutering ANY, we've significantly reduced the maximum size of responses even for zone files that need to be
* large due to a large number of records.
*/
public
class DnsServer extends EndPoint {

View File

@ -27,8 +27,6 @@ import java.util.concurrent.atomic.AtomicLong;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.slf4j.Logger;
import com.esotericsoftware.kryo.Registration;
import dorkbox.network.connection.bridge.ConnectionBridge;
import dorkbox.network.connection.idle.IdleBridge;
import dorkbox.network.connection.idle.IdleSender;
@ -44,7 +42,7 @@ import dorkbox.network.rmi.RemoteObjectCallback;
import dorkbox.network.rmi.Rmi;
import dorkbox.network.rmi.RmiBridge;
import dorkbox.network.rmi.RmiRegistration;
import dorkbox.network.serialization.CryptoSerializationManager;
import dorkbox.network.serialization.RmiSerializationManager;
import dorkbox.util.collections.IntMap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
@ -947,71 +945,94 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
TCP(message).flush();
}
private
void collectRmiFields(final RmiBridge rmiBridge,
final LinkedList<ClassObject> classesToCheck, final ClassObject remoteClassObject, final Field[] fields) {
}
final
void registerInternal(final ConnectionImpl connection, final RmiRegistration remoteRegistration) {
final Class<?> interfaceClass = remoteRegistration.interfaceClass;
final int rmiID = remoteRegistration.rmiID;
if (interfaceClass != null) {
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist)
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist as an implementation)
//
// CREATE a new ID, and register the ID and new object (must create a new one) in the object maps
Class<?> implementationClass;
// have to find the implementation from the specified interface
CryptoSerializationManager manager = getEndPoint().serializationManager;
KryoExtra kryo = manager.takeKryo();
Registration registration = kryo.getRegistration(interfaceClass);
// the interface class kryo ID == implementation class kryo ID, so they switcheroo automatically.
final Class<?> implementationClass = interfaceClass;
final RmiSerializationManager manager = getEndPoint().serializationManager;
if (registration == null) {
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
manager.returnKryo(kryo);
logger.error("Error getting RMI class interface for " + interfaceClass);
connection.TCP(new RmiRegistration(rmiID)).flush();
KryoExtra kryo = null;
final Object remotePrimaryObject;
try {
kryo = manager.takeKryo();
// this is what creates a new instance of the impl class, and stores it as an ID.
remotePrimaryObject = kryo.newInstance(implementationClass);
} catch (Exception e) {
logger.error("Error creating RMI class " + implementationClass, e);
connection.TCP(new RmiRegistration(rmiID))
.flush();
return;
} finally {
if (kryo != null) {
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
manager.returnKryo(kryo);
}
}
implementationClass = manager.getRmiImpl(registration.getId());
if (implementationClass == null) {
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
manager.returnKryo(kryo);
logger.error("Error getting RMI class implementation for " + interfaceClass);
connection.TCP(new RmiRegistration(rmiID)).flush();
return;
}
try {
// this is what creates a new instance of the impl class, and stores it as an ID.
final Object remotePrimaryObject = kryo.newInstance(implementationClass);
// we use kryo to create a new instance - so only return it on error or when it's done creating a new instance
manager.returnKryo(kryo);
rmiBridge.register(rmiBridge.nextObjectId(), remotePrimaryObject);
LinkedList<ClassObject> remoteClasses = new LinkedList<ClassObject>();
remoteClasses.add(new ClassObject(implementationClass, remotePrimaryObject));
// the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI
LinkedList<ClassObject> classesToCheck = new LinkedList<ClassObject>();
classesToCheck.add(new ClassObject(implementationClass, remotePrimaryObject));
ClassObject remoteClassObject;
while ((remoteClassObject = remoteClasses.pollFirst()) != null) {
// we have to check for any additional fields that will have proxy information
while (!classesToCheck.isEmpty()) {
remoteClassObject = classesToCheck.removeFirst();
// we have to check the IMPLEMENTATION for any additional fields that will have proxy information.
// we use getDeclaredFields() + walking the object hierarchy, so we get ALL the fields possible.
for (Field field : remoteClassObject.clazz.getDeclaredFields()) {
if (field.getAnnotation(Rmi.class) != null) {
boolean prev = field.isAccessible();
field.setAccessible(true);
final Object o = field.get(remoteClassObject.object);
field.setAccessible(prev);
final Class<?> type = field.getType();
rmiBridge.register(rmiBridge.nextObjectId(), o);
remoteClasses.offerLast(new ClassObject(type, o));
if (!type.isInterface()) {
// the type must be an interface, otherwise RMI cannot create a proxy object
logger.error("Error checking RMI fields for: {}.{} -- It is not an interface!", remoteClassObject.clazz, field.getName());
continue;
}
boolean prev = field.isAccessible();
field.setAccessible(true);
final Object o;
try {
o = field.get(remoteClassObject.object);
rmiBridge.register(rmiBridge.nextObjectId(), o);
classesToCheck.add(new ClassObject(type, o));
} catch (IllegalAccessException e) {
logger.error("Error checking RMI fields for: {}.{}", remoteClassObject.clazz, field.getName(), e);
} finally {
field.setAccessible(prev);
}
}
}
// have to check the object hierarchy as well
Class<?> superclass = remoteClassObject.clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
classesToCheck.add(new ClassObject(superclass, remoteClassObject.object));
}
}
connection.TCP(new RmiRegistration(remotePrimaryObject, rmiID)).flush();
@ -1021,7 +1042,7 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
}
}
else if (remoteRegistration.remoteObjectId > RmiBridge.INVALID_RMI) {
// THIS IS ON THE REMOTE CONNECTION (where the object will really exist)
// THIS IS ON THE REMOTE CONNECTION (where the object implementation will really exist)
//
// GET a LOCAL rmi object, if none get a specific, GLOBAL rmi object (objects that are not bound to a single connection).
Object object = getImplementationObject(remoteRegistration.remoteObjectId);
@ -1057,8 +1078,8 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
/**
* Used by RMI
*
* @return the registered ID for a specific object. This is used by the "local" side when setting up the to fetch an object for the
* "remote" side for RMI
* @return the registered ID for a specific object. This is used by the "client" side when setting up the to fetch an object for the
* "service" side for RMI
*/
@Override
public
@ -1070,17 +1091,18 @@ class ConnectionImpl extends ChannelInboundHandlerAdapter implements ICryptoConn
throw new NullPointerException("Unable to call 'getRegisteredId' when the globalRmiBridge is null!");
}
int object1 = globalRmiBridge.getRegisteredId(object);
if (object1 == Integer.MAX_VALUE) {
int objectId = globalRmiBridge.getRegisteredId(object);
if (objectId == Integer.MAX_VALUE) {
return rmiBridge.getRegisteredId(object);
} else {
return object1;
return objectId;
}
}
/**
* Used by RMI for the LOCAL side, to get the proxy object as an interface
* Used by RMI for the CLIENT side, to get the proxy object as an interface
*
* @param objectID is the RMI object ID
* @param type must be the interface the proxy will bind to
*/
@Override

View File

@ -36,7 +36,7 @@ import dorkbox.network.connection.wrapper.ChannelWrapper;
import dorkbox.network.pipeline.KryoEncoder;
import dorkbox.network.pipeline.KryoEncoderCrypto;
import dorkbox.network.rmi.RmiBridge;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.network.store.NullSettingsStore;
import dorkbox.network.store.SettingsStore;
import dorkbox.util.Property;
@ -135,7 +135,7 @@ class EndPointBase<C extends Connection> extends EndPoint {
if (config.serialization != null) {
serializationManager = config.serialization;
} else {
serializationManager = SerializationManager.DEFAULT();
serializationManager = Serialization.DEFAULT();
}
// setup our RMI serialization managers. Can only be called once

View File

@ -35,32 +35,18 @@
package dorkbox.network.rmi;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.network.connection.Connection;
import java.lang.reflect.InvocationTargetException;
import dorkbox.network.connection.Connection;
public
class AsmCachedMethod extends CachedMethod {
public String name;
public MethodAccess methodAccess;
public int methodAccessIndex = -1;
@Override
public
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
try {
if (origMethod == method) {
return this.methodAccess.invoke(target, this.methodAccessIndex, args);
}
else {
int length = args.length;
Object[] newArgs = new Object[length + 1];
newArgs[0] = connection;
System.arraycopy(args, 0, newArgs, 1, length);
return this.methodAccess.invoke(target, this.methodAccessIndex, newArgs);
}
} catch (Exception ex) {
throw new InvocationTargetException(ex);
}
Object invoke(final Connection connection, Object target, Object[] args) {
return this.methodAccess.invoke(target, this.methodAccessIndex, args);
}
}

View File

@ -34,330 +34,17 @@
*/
package dorkbox.network.rmi;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.util.IdentityMap;
import com.esotericsoftware.kryo.util.Util;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPointBase;
import dorkbox.network.connection.KryoExtra;
import dorkbox.network.serialization.CryptoSerializationManager;
import dorkbox.network.serialization.RmiSerializationManager;
import dorkbox.util.ClassHelper;
/**
* This class is NOT sent across the wire
*/
public
class CachedMethod {
private static final Logger logger = LoggerFactory.getLogger(CachedMethod.class);
private static final Comparator<Method> METHOD_COMPARATOR = new Comparator<Method>() {
@Override
public
int compare(Method o1, Method o2) {
// Methods are sorted so they can be represented as an index.
String o1Name = o1.getName();
String o2Name = o2.getName();
int diff = o1Name.compareTo(o2Name);
if (diff != 0) {
return diff;
}
Class<?>[] argTypes1 = o1.getParameterTypes();
Class<?>[] argTypes2 = o2.getParameterTypes();
if (argTypes1.length > argTypes2.length) {
return 1;
}
if (argTypes1.length < argTypes2.length) {
return -1;
}
for (int i = 0; i < argTypes1.length; i++) {
diff = argTypes1[i].getName()
.compareTo(argTypes2[i].getName());
if (diff != 0) {
return diff;
}
}
// Impossible, should never happen
throw new RuntimeException("Two methods with same signature! ('" + o1Name + "', '" + o2Name + "'");
}
};
// the purpose of the method cache, is to accelerate looking up methods for specific class
private static final Map<Class<?>, CachedMethod[]> methodCache = new ConcurrentHashMap<Class<?>, CachedMethod[]>(EndPointBase.DEFAULT_THREAD_POOL_SIZE);
/**
* Called when we read a RMI method invocation on the "server" side (by kryo)
*
* @param type is the implementation type
*/
static
CachedMethod[] getMethods(final Kryo kryo, final Class<?> type, final int classId) {
CachedMethod[] cachedMethods = methodCache.get(type);
if (cachedMethods != null) {
return cachedMethods;
}
cachedMethods = getCachedMethods(kryo, type, classId);
methodCache.put(type, cachedMethods);
return cachedMethods;
}
/**
* Called when we write an RMI method invocation on the "client" side (by RmiProxyHandler)
*
* @param type this is the interface.
*/
static
CachedMethod[] getMethods(final RmiSerializationManager serializationManager, final Class<?> type, final int classId) {
CachedMethod[] cachedMethods = methodCache.get(type);
if (cachedMethods != null) {
return cachedMethods;
}
final KryoExtra kryo = serializationManager.takeKryo();
try {
cachedMethods = getCachedMethods(kryo, type, classId);
methodCache.put(type, cachedMethods);
} finally {
serializationManager.returnKryo(kryo);
}
return cachedMethods;
}
// race-conditions are OK, because we just recreate the same thing.
private static
CachedMethod[] getCachedMethods(final Kryo kryo, final Class<?> type, final int classId) {
// sometimes, the method index is based upon an interface and NOT the implementation. We have to clear that up here.
CryptoSerializationManager serialization = ((KryoExtra) kryo).getSerializationManager();
// when there is an interface available, we want to use that instead of the implementation. This is because the incoming
// implementation is ACTUALLY mapped (on the "client" side) to the interface. If we don't use the interface, we will have the
// wrong order of methods, so invoking a method by it's index will fail.
Class<?> interfaceClass = serialization.getRmiIface(classId);
final ArrayList<Method> methods;
if (interfaceClass == null) {
methods = getMethods(type);
} else {
methods = getMethods(interfaceClass);
}
final int size = methods.size();
final CachedMethod[] cachedMethods = new CachedMethod[size];
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
// an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
// the interface, and the implType may override the method, so that we add the connection as the first in
// the list of parameters.
//
// for example:
// Interface: foo(String x)
// Impl: foo(Connection c, String x)
//
// The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
// This MUST hold valid for both remote and local connection types.
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
// will only be valid if implementation type is NOT NULL (otherwise will be null)
final IdentityMap<Method, Method> overriddenMethods;
if (interfaceClass == null) {
overriddenMethods = null;
} else {
// type here must be the implementation
overriddenMethods = getOverriddenMethods(type, methods);
}
final boolean asmEnabled = kryo.getFieldSerializerConfig().isUseAsm();
MethodAccess methodAccess = null;
// reflectASM can't get any method from the 'Object' object, and it MUST be public
if (asmEnabled && type != Object.class && !Util.isAndroid && Modifier.isPublic(type.getModifiers())) {
methodAccess = MethodAccess.get(type);
if (methodAccess.getMethodNames().length == 0 && methodAccess.getParameterTypes().length == 0 &&
methodAccess.getReturnTypes().length == 0) {
// there was NOTHING reflectASM found, so trying to use it doesn't do us any good
methodAccess = null;
}
}
for (int i = 0; i < size; i++) {
final Method origMethod = methods.get(i);
Method method = origMethod; // copy because one or more can be overridden
Class<?> declaringClass = method.getDeclaringClass();
MethodAccess localMethodAccess = methodAccess; // copy because one or more can be overridden
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?>[] asmParameterTypes = parameterTypes;
if (overriddenMethods != null) {
Method overriddenMethod = overriddenMethods.remove(method);
if (overriddenMethod != null) {
// we can override the details of this method BECAUSE (and only because) our kryo registration override will return
// the correct object for this overridden method to be called on.
method = overriddenMethod;
Class<?> overrideType = declaringClass;
if (asmEnabled && !Util.isAndroid && Modifier.isPublic(overrideType.getModifiers())) {
localMethodAccess = MethodAccess.get(overrideType);
asmParameterTypes = method.getParameterTypes();
}
}
}
CachedMethod cachedMethod = null;
if (localMethodAccess != null) {
try {
final int index = localMethodAccess.getIndex(method.getName(), asmParameterTypes);
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
asmCachedMethod.methodAccessIndex = index;
asmCachedMethod.methodAccess = localMethodAccess;
cachedMethod = asmCachedMethod;
} catch (Exception e) {
if (logger.isTraceEnabled()) {
logger.trace("Unable to use ReflectAsm for {}.{}", declaringClass, method.getName(), e);
}
}
}
if (cachedMethod == null) {
cachedMethod = new CachedMethod();
}
cachedMethod.method = method;
cachedMethod.origMethod = origMethod;
// on the "server", we only register the implementation. NOT THE INTERFACE, so for RMI classes, we have to get the impl
Class<?> impl = serialization.getRmiImpl(declaringClass);
if (impl != null) {
cachedMethod.methodClassID = kryo.getRegistration(impl)
.getId();
}
else {
cachedMethod.methodClassID = kryo.getRegistration(declaringClass)
.getId();
}
cachedMethod.methodIndex = i;
// Store the serializer for each final parameter.
// ONLY for the ORIGINAL method, not he overridden one.
cachedMethod.serializers = new Serializer<?>[parameterTypes.length];
for (int ii = 0, nn = parameterTypes.length; ii < nn; ii++) {
if (kryo.isFinal(parameterTypes[ii])) {
cachedMethod.serializers[ii] = kryo.getSerializer(parameterTypes[ii]);
}
}
cachedMethods[i] = cachedMethod;
}
return cachedMethods;
}
// does not null check
private static
IdentityMap<Method, Method> getOverriddenMethods(final Class<?> type, final ArrayList<Method> origMethods) {
final ArrayList<Method> implMethods = getMethods(type);
final IdentityMap<Method, Method> overrideMap = new IdentityMap<Method, Method>(implMethods.size());
for (Method origMethod : origMethods) {
String name = origMethod.getName();
Class<?>[] origTypes = origMethod.getParameterTypes();
int origLength = origTypes.length + 1;
METHOD_CHECK:
for (Method implMethod : implMethods) {
String checkName = implMethod.getName();
Class<?>[] checkTypes = implMethod.getParameterTypes();
int checkLength = checkTypes.length;
if (origLength != checkLength || !(name.equals(checkName))) {
continue;
}
// checkLength > 0
Class<?> shouldBeConnectionType = checkTypes[0];
if (ClassHelper.hasInterface(dorkbox.network.connection.Connection.class, shouldBeConnectionType)) {
// now we check to see if our "check" method is equal to our "cached" method + Connection
if (checkLength == 1) {
overrideMap.put(origMethod, implMethod);
break;
}
else {
for (int k = 1; k < checkLength; k++) {
if (origTypes[k - 1] == checkTypes[k]) {
overrideMap.put(origMethod, implMethod);
break METHOD_CHECK;
}
}
}
}
}
}
return overrideMap;
}
private static
ArrayList<Method> getMethods(final Class<?> type) {
final ArrayList<Method> allMethods = new ArrayList<Method>();
Class<?> nextClass = type;
while (nextClass != null) {
Collections.addAll(allMethods, nextClass.getDeclaredMethods());
nextClass = nextClass.getSuperclass();
if (nextClass == Object.class) {
break;
}
}
final ArrayList<Method> methods = new ArrayList<Method>(Math.max(1, allMethods.size()));
for (int i = 0, n = allMethods.size(); i < n; i++) {
Method method = allMethods.get(i);
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers)) {
continue;
}
if (Modifier.isPrivate(modifiers)) {
continue;
}
if (method.isSynthetic()) {
continue;
}
methods.add(method);
}
Collections.sort(methods, METHOD_COMPARATOR);
return methods;
}
public Method method;
public int methodClassID;
public int methodIndex;
@ -366,24 +53,13 @@ class CachedMethod {
* in some cases, we want to override the cached method, with one that supports passing 'Connection' as the first argument. This is
* completely OPTIONAL, however - greatly adds functionality to RMI methods.
*/
public transient Method origMethod;
public boolean overriddenMethod;
@SuppressWarnings("rawtypes")
public Serializer[] serializers;
public
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
// did we override our cached method?
if (method == origMethod) {
return this.method.invoke(target, args);
}
else {
int length = args.length;
Object[] newArgs = new Object[length + 1];
newArgs[0] = connection;
System.arraycopy(args, 0, newArgs, 1, length);
return this.method.invoke(target, newArgs);
}
Object invoke(final Connection connection, Object target, Object[] args) throws Exception {
return this.method.invoke(target, args);
}
}

View File

@ -40,7 +40,7 @@ package dorkbox.network.rmi;
*/
public
class InvokeMethod implements RmiMessages {
public int objectID;
public int objectID; // the registered kryo ID for the object
public CachedMethod cachedMethod;
public Object[] args;

View File

@ -40,6 +40,8 @@ import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import dorkbox.network.connection.KryoExtra;
/**
* Internal message to invoke methods remotely.
*/
@ -84,38 +86,60 @@ class InvokeMethodSerializer extends Serializer<InvokeMethod> {
@Override
public
InvokeMethod read(final Kryo kryo, final Input input, final Class<InvokeMethod> type) {
InvokeMethod invokeMethod = new InvokeMethod();
invokeMethod.objectID = input.readInt(true);
int objectID = input.readInt(true);
int methodClassID = input.readInt(true);
Class<?> methodClass = kryo.getRegistration(methodClassID)
.getType();
byte methodIndex = input.readByte();
// System.err.println(":: objectID " + objectID);
// System.err.println(":: methodClassID " + methodClassID);
// System.err.println(":: methodIndex " + methodIndex);
CachedMethod cachedMethod;
try {
cachedMethod = CachedMethod.getMethods(kryo, methodClass, methodClassID)[methodIndex];
invokeMethod.cachedMethod = cachedMethod;
} catch (IndexOutOfBoundsException ex) {
cachedMethod = ((KryoExtra) kryo).getSerializationManager().getMethods(methodClassID)[methodIndex];
} catch (Exception ex) {
Class<?> methodClass = kryo.getRegistration(methodClassID)
.getType();
throw new KryoException("Invalid method index " + methodIndex + " for class: " + methodClass.getName());
}
Serializer<?>[] serializers = cachedMethod.serializers;
Class<?>[] parameterTypes = cachedMethod.method.getParameterTypes();
Object[] args = new Object[serializers.length];
invokeMethod.args = args;
for (int i = 0, n = args.length; i < n; i++) {
Object[] args;
Serializer<?>[] serializers = cachedMethod.serializers;
int argStartIndex;
if (cachedMethod.overriddenMethod) {
// did we override our cached method? This is not common.
// this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0)
argStartIndex = 1;
args = new Object[serializers.length + 1];
args[0] = ((KryoExtra) kryo).connection;
}
else {
argStartIndex = 0;
args = new Object[serializers.length];
}
Class<?>[] parameterTypes = cachedMethod.method.getParameterTypes();
for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) {
Serializer<?> serializer = serializers[i];
if (serializer != null) {
args[i] = kryo.readObjectOrNull(input, parameterTypes[i], serializer);
args[j] = kryo.readObjectOrNull(input, parameterTypes[i], serializer);
}
else {
args[i] = kryo.readClassAndObject(input);
args[j] = kryo.readClassAndObject(input);
}
}
InvokeMethod invokeMethod = new InvokeMethod();
invokeMethod.objectID = objectID;
invokeMethod.cachedMethod = cachedMethod;
invokeMethod.args = args;
invokeMethod.responseData = input.readByte();
return invokeMethod;

View File

@ -24,16 +24,9 @@ import java.lang.annotation.Target;
/**
* This specifies to the serializer, that this class contains an RMI object, and that a specific field is an RMI object. Both are
* necessary.
* <p/>
* Additional behavior of RMI methods, is if there is another method (of the same name and signature), with the addition of a Connection
* parameter in the first position, THAT method will be called instead, an will have the current connection object passed into the method.
* <p/>
* It is mandatory for the correct implementation (as per the interface guideline) to exist, and should return null.
* <p/>
* IE: foo(String something)... -> foo(Connection connection, String something)....
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited
@Target(value = {ElementType.TYPE, ElementType.FIELD})
@Target(value = {ElementType.FIELD})
public
@interface Rmi {}

View File

@ -68,21 +68,18 @@ import dorkbox.util.collections.ObjectIntMap;
* <p/>
* <p/>
* In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
* <p/>
* This is to support calling RMI methods from an interface (that does pass the connection reference) to an implementation, that DOES pass
* the connection reference. The remote side (that initiates the RMI calls), MUST use the interface, and the implementation may override the
* method, so that we add the connection as the first in the list of parameters.
* This is to support calling RMI methods from an interface (that does pass the connection reference) to
* an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
* the interface, and the implType may override the method, so that we add the connection as the first in
* the list of parameters.
* <p/>
* for example:
* Interface: foo(String x)
* Impl: foo(Connection c, String x)
* Interface: foo(String x)
* Impl: foo(Connection c, String x)
* <p/>
* The implType (if it exists, with the same name, and with the same signature + connection parameter) will be called from the interface
* instead of the method that would NORMALLY be called.
* <p/>
* The implementation (if it exists, with the same name, and with the same signature+connection) will be called from the interface. This
* MUST hold valid for both remote and local connection types.
*
* To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
* interface as the first parameter, and CachedMethod.registerOverridden(ifaceClass, implClass) must be called.
*
*
* @author Nathan Sweet <misc@n4te.com>, Nathan Robinson
*/
@ -199,6 +196,8 @@ class RmiBridge {
* Invokes the method on the object and, if necessary, sends the result back to the connection that made the invocation request. This
* method is invoked on the update thread of the {@link EndPointBase} for this RmiBridge and unless an executor has been set.
*
* This is the response to the invoke method in the RmiProxyHandler
*
* @param connection
* The remote side of this connection requested the invocation.
*/
@ -229,7 +228,8 @@ class RmiBridge {
.append(argString)
.append(")");
if (cachedMethod.origMethod != null) {
if (cachedMethod.overriddenMethod) {
// did we override our cached method? This is not common.
stringBuilder.append(" [Connection method override]");
}
logger2.trace(stringBuilder.toString());
@ -259,8 +259,10 @@ class RmiBridge {
result = cause;
}
else {
throw new IOException("Error invoking method: " + cachedMethod.method.getDeclaringClass()
.getName() + "." + cachedMethod.method.getName(), ex);
String message = "Error invoking method: " + cachedMethod.method.getDeclaringClass()
.getName() + "." + cachedMethod.method.getName();
logger.error(message, ex);
throw new IOException(message, ex);
}
}
@ -439,6 +441,9 @@ class RmiBridge {
if (iface == null) {
throw new IllegalArgumentException("iface cannot be null.");
}
if (!iface.isInterface()) {
throw new IllegalArgumentException("iface must be an interface.");
}
Class<?>[] temp = new Class<?>[2];
temp[0] = RemoteObject.class;
@ -446,6 +451,6 @@ class RmiBridge {
return (RemoteObject) Proxy.newProxyInstance(RmiBridge.class.getClassLoader(),
temp,
new RmiProxyHandler(connection, objectID));
new RmiProxyHandler(connection, objectID, iface));
}
}

View File

@ -48,10 +48,13 @@ import org.slf4j.LoggerFactory;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPointBase;
import dorkbox.network.connection.KryoExtra;
import dorkbox.network.serialization.RmiSerializationManager;
/**
* Handles network communication when methods are invoked on a proxy.
* <p>
* The only methods than can be invoked are INTERFACE methods and OBJECT methods
*/
class RmiProxyHandler implements InvocationHandler {
private final Logger logger;
@ -63,7 +66,11 @@ class RmiProxyHandler implements InvocationHandler {
private final boolean[] pendingResponses = new boolean[64];
private final Connection connection;
public final int objectID;
private final Class<?> iFace;
public final int objectID; // this is the RMI id
public final int ID; // this is the KRYO id
private final String proxyString;
private final RemoteInvocationResponse<Connection> responseListener;
@ -80,14 +87,34 @@ class RmiProxyHandler implements InvocationHandler {
private Byte lastResponseID;
private byte nextResponseId = (byte) 1;
RmiProxyHandler(final Connection connection, final int objectID) {
/**
* @param connection this is really the network client -- there is ONLY ever 1 connection
* @param objectID this is the remote object ID (assigned by RMI). This is NOT the kryo registration ID
* @param iFace this is the RMI interface
*/
RmiProxyHandler(final Connection connection, final int objectID, final Class<?> iFace) {
super();
this.connection = connection;
this.iFace = iFace;
this.objectID = objectID;
this.proxyString = "<proxy #" + objectID + ">";
logger = LoggerFactory.getLogger(connection.getEndPoint().getName() + ":" + this.getClass().getSimpleName());
EndPointBase endPointBaseConnection = this.connection.getEndPoint();
final RmiSerializationManager serializationManager = endPointBaseConnection.getSerialization();
KryoExtra kryoExtra = null;
try {
kryoExtra = serializationManager.takeKryo();
this.ID = kryoExtra.getRegistration(iFace)
.getId();
} finally {
if (kryoExtra != null) {
serializationManager.returnKryo(kryoExtra);
}
}
this.logger = LoggerFactory.getLogger(connection.getEndPoint().getName() + ":" + this.getClass().getSimpleName());
this.responseListener = new RemoteInvocationResponse<Connection>() {
@ -195,39 +222,18 @@ class RmiProxyHandler implements InvocationHandler {
return proxyString;
}
EndPointBase endPointBaseConnection = this.connection.getEndPoint();
final RmiSerializationManager serializationManager = endPointBaseConnection.getSerialization();
InvokeMethod invokeMethod = new InvokeMethod();
invokeMethod.objectID = this.objectID;
invokeMethod.args = args;
// which method do we access?
CachedMethod[] cachedMethods = CachedMethod.getMethods(serializationManager, method.getDeclaringClass(), invokeMethod.objectID);
CachedMethod[] cachedMethods = connection.getEndPoint()
.getSerialization()
.getMethods(ID);
for (int i = 0, n = cachedMethods.length; i < n; i++) {
CachedMethod cachedMethod = cachedMethods[i];
Method checkMethod = cachedMethod.origMethod;
if (checkMethod == null) {
checkMethod = cachedMethod.method;
}
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
// an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
// the interface, and the implType may override the method, so that we add the connection as the first in
// the list of parameters.
//
// for example:
// Interface: foo(String x)
// Impl: foo(Connection c, String x)
//
// The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
// This MUST hold valid for both remote and local connection types.
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
Method checkMethod = cachedMethod.method;
if (checkMethod.equals(method)) {
invokeMethod.cachedMethod = cachedMethod;

View File

@ -0,0 +1,367 @@
package dorkbox.network.rmi;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import org.slf4j.Logger;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.network.connection.Connection;
import dorkbox.util.ClassHelper;
/**
* Utility methods for creating a method cache for a class or interface.
*
* Additionally, this will override methods on the implementation so that methods can be called with a {@link Connection} parameter as the
* first parameter, with all other parameters being equal to the interface.
*
* This is to support calling RMI methods from an interface (that does pass the connection reference) to
* an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
* the interface, and the implType may override the method, so that we add the connection as the first in
* the list of parameters.
*
* for example:
* Interface: foo(String x)
*
* Impl: foo(String x) -> not called
* Impl: foo(Connection c, String x) -> this is called instead
*
* The implType (if it exists, with the same name, and with the same signature + connection parameter) will be called from the interface
* instead of the method that would NORMALLY be called.
*/
public
class RmiUtils {
private static final Comparator<Method> METHOD_COMPARATOR = new Comparator<Method>() {
@Override
public
int compare(Method o1, Method o2) {
// Methods are sorted so they can be represented as an index.
String o1Name = o1.getName();
String o2Name = o2.getName();
int diff = o1Name.compareTo(o2Name);
if (diff != 0) {
return diff;
}
Class<?>[] argTypes1 = o1.getParameterTypes();
Class<?>[] argTypes2 = o2.getParameterTypes();
if (argTypes1.length > argTypes2.length) {
return 1;
}
if (argTypes1.length < argTypes2.length) {
return -1;
}
for (int i = 0; i < argTypes1.length; i++) {
diff = argTypes1[i].getName()
.compareTo(argTypes2[i].getName());
if (diff != 0) {
return diff;
}
}
// Impossible, should never happen
// return 0;
throw new RuntimeException("Two methods with same signature! ('" + o1Name + "', '" + o2Name + "'");
}
};
private static
MethodAccess getReflectAsmMethod(final Logger logger, final Class<?> clazz) {
try {
MethodAccess methodAccess = MethodAccess.get(clazz);
if (methodAccess.getMethodNames().length == 0 &&
methodAccess.getParameterTypes().length == 0 &&
methodAccess.getReturnTypes().length == 0) {
// there was NOTHING that reflectASM found, so trying to use it doesn't do us any good
return null;
}
return methodAccess;
} catch (Exception e) {
logger.error("Unable to create ReflectASM method access", e);
return null;
}
}
public static
CachedMethod[] getCachedMethods(final Logger logger, final Kryo kryo, final boolean asmEnabled, final Class<?> iFace, final Class<?> impl, final int classId) {
MethodAccess ifaceMethodAccess = null;
MethodAccess implMethodAccess = null;
// RMI is **ALWAYS** based upon an interface, so we must always make sure to get the methods of the interface, instead of the
// implementation, otherwise we will have the wrong order of methods, so invoking a method by it's index will fail.
final Method[] methods = getMethods(iFace);
final int size = methods.length;
final CachedMethod[] cachedMethods = new CachedMethod[size];
if (impl != null) {
if (impl.isInterface()) {
throw new IllegalArgumentException("Cannot have type as an interface, it must be an implementation");
}
final Method[] implMethods = getMethods(impl);
overwriteMethodsWithConnectionParam(implMethods, methods);
// reflectASM
// doesn't work on android (set correctly by the serialization manager)
// can't get any method from the 'Object' object (we get from the interface, which is NOT 'Object')
// and it MUST be public (iFace is always public)
if (asmEnabled) {
implMethodAccess = getReflectAsmMethod(logger, impl);
}
}
// reflectASM
// doesn't work on android (set correctly by the serialization manager)
// can't get any method from the 'Object' object (we get from the interface, which is NOT 'Object')
// and it MUST be public (iFace is always public)
if (asmEnabled) {
ifaceMethodAccess = getReflectAsmMethod(logger, iFace);
}
for (int i = 0; i < size; i++) {
final Method method = methods[i];
Class<?> declaringClass = method.getDeclaringClass();
Class<?>[] parameterTypes = method.getParameterTypes();
// copy because they can be overridden
boolean overriddenMethod = false;
Method tweakMethod = method;
MethodAccess tweakMethodAccess = ifaceMethodAccess;
// this is how we detect if the method has been changed from the interface -> implementation + connection parameter
if (declaringClass.equals(impl)) {
tweakMethodAccess = implMethodAccess;
overriddenMethod = true;
if (logger.isTraceEnabled())
logger.trace("Overridden method: {}.{}", impl, method.getName());
}
CachedMethod cachedMethod = null;
if (tweakMethodAccess != null) {
try {
final int index = tweakMethodAccess.getIndex(tweakMethod.getName(), parameterTypes);
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
asmCachedMethod.methodAccessIndex = index;
asmCachedMethod.methodAccess = tweakMethodAccess;
asmCachedMethod.name = tweakMethod.getName();
if (overriddenMethod) {
// logger.error(tweakMethod.getName() + " " + Arrays.toString(parameterTypes) + " index: " + index +
// " methodIndex: " + i + " classID: " + classId);
// This is because we have to store the serializer for each parameter, but ONLY for the ORIGINAL method, not the overridden one.
// this gets our parameters "back to the original" method. We do this to minimize the overhead of sending the args over
int length = parameterTypes.length;
if (length == 1) {
parameterTypes = new Class<?>[0];
}
else {
length--;
Class<?>[] newArgs = new Class<?>[length];
System.arraycopy(parameterTypes, 1, newArgs, 0, length);
parameterTypes = newArgs;
}
}
cachedMethod = asmCachedMethod;
} catch (Exception e) {
logger.trace("Unable to use ReflectAsm for {}.{}", declaringClass, tweakMethod.getName(), e);
}
}
if (cachedMethod == null) {
cachedMethod = new CachedMethod();
}
cachedMethod.overriddenMethod = overriddenMethod;
cachedMethod.methodClassID = classId;
// we ALSO have to setup "normal" reflection access to these methods
cachedMethod.method = tweakMethod;
cachedMethod.methodIndex = i;
// Store the serializer for each final parameter.
// ONLY for the ORIGINAL method, not the overridden one.
cachedMethod.serializers = new Serializer<?>[parameterTypes.length];
for (int ii = 0, nn = parameterTypes.length; ii < nn; ii++) {
if (kryo.isFinal(parameterTypes[ii])) {
cachedMethod.serializers[ii] = kryo.getSerializer(parameterTypes[ii]);
}
}
cachedMethods[i] = cachedMethod;
}
return cachedMethods;
}
// NOTE: does not null check
/**
* This will overwrite an original (iface based) method with a method from the implementation ONLY if there is the extra 'Connection' parameter (as per above)
*
* @param implMethods methods from the implementation
* @param origMethods methods from the interface
*/
private static
void overwriteMethodsWithConnectionParam(final Method[] implMethods, final Method[] origMethods) {
for (int i = 0, origMethodsSize = origMethods.length; i < origMethodsSize; i++) {
final Method origMethod = origMethods[i];
String origName = origMethod.getName();
Class<?>[] origTypes = origMethod.getParameterTypes();
int origLength = origTypes.length + 1;
for (Method implMethod : implMethods) {
String implName = implMethod.getName();
Class<?>[] implTypes = implMethod.getParameterTypes();
int implLength = implTypes.length;
if (origLength != implLength || !(origName.equals(implName))) {
continue;
}
// checkLength > 0
Class<?> shouldBeConnectionType = implTypes[0];
if (ClassHelper.hasInterface(Connection.class, shouldBeConnectionType)) {
// now we check to see if our "check" method is equal to our "cached" method + Connection
if (implLength == 1) {
// we only have "Connection" as a parameter
origMethods[i] = implMethod;
break;
}
else {
boolean found = true;
for (int k = 1; k < implLength; k++) {
if (origTypes[k - 1] != implTypes[k]) {
// make sure all the parameters match. Cannot use arrays.equals(*), because one will have "Connection" as
// a parameter - so we check that the rest match
found = false;
break;
}
}
if (found) {
origMethods[i] = implMethod;
break;
}
}
}
}
}
}
/**
* This will methods from an interface (for RMI), and from an implementation (for "connection" overriding the method signature).
*
* @return an array list of all found methods for this class
*/
private static
Method[] getMethods(final Class<?> type) {
final ArrayList<Method> allMethods = new ArrayList<Method>();
final Map<String, ArrayList<Class[]>> accessibleMethods = new HashMap<String, ArrayList<Class[]>>();
LinkedList<Class<?>> classes = new LinkedList<Class<?>>();
classes.add(type);
// explicitly add Object.class because that can always be called, because it is common to 100% of all java objects (and it's methods
// are not added via parentClass.getMethods()
classes.add(Object.class);
Class<?> nextClass;
while (!classes.isEmpty()) {
nextClass = classes.removeFirst();
Method[] methods = nextClass.getMethods();
for (int i = 0; i < methods.length; i++) {
final Method method = methods[i];
// static and private methods cannot be called via RMI.
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers)) {
continue;
}
if (Modifier.isPrivate(modifiers)) {
continue;
}
if (method.isSynthetic()) {
continue;
}
// methods that have been over-ridden by another method cannot be called.
// the first one in the map, is the "highest" level method, and is what can be called.
String name = method.getName();
Class<?>[] types = method.getParameterTypes(); // length 0 if there are no parameters
ArrayList<Class[]> existingTypes = accessibleMethods.get(name);
if (existingTypes != null) {
boolean found = false;
for (Class[] existingType : existingTypes) {
if (Arrays.equals(types, existingType)) {
found = true;
break;
}
}
if (found) {
// the method is overridden, so it should not be called.
continue;
}
}
if (existingTypes == null) {
existingTypes = new ArrayList<Class[]>();
}
existingTypes.add(types);
// add to the map for checking later
accessibleMethods.put(name, existingTypes);
// safe to add this method to the list of recognized methods
allMethods.add(method);
}
// add all interfaces from our class (if any)
classes.addAll(Arrays.asList(nextClass.getInterfaces()));
// If we are an interface, one CANNOT call any methods NOT defined by the interface!
// also, interfaces don't have a super-class.
Class<?> superclass = nextClass.getSuperclass();
if (superclass != null) {
classes.add(superclass);
}
}
accessibleMethods.clear();
Collections.sort(allMethods, METHOD_COMPARATOR);
Method[] methodsArray = new Method[allMethods.size()];
allMethods.toArray(methodsArray);
return methodsArray;
}
}

View File

@ -20,6 +20,7 @@ import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Serializer;
import dorkbox.network.connection.KryoExtra;
import dorkbox.network.rmi.CachedMethod;
import dorkbox.util.SerializationManager;
public
@ -94,31 +95,6 @@ interface RmiSerializationManager extends SerializationManager {
*/
void returnKryo(KryoExtra kryo);
/**
* Gets the RMI interface based on the specified ID (which is the ID for the registered implementation)
*
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
*
* @return the implementation for the interface, or null
*/
Class<?> getRmiIface(int objectId);
/**
* Gets the RMI implementation based on the specified ID (which is the ID for the registered interface)
*
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
*
* @return the implementation for the interface, or null
*/
Class<?> getRmiImpl(int objectId);
/**
* Gets the RMI implementation based on the specified interface
*
* @return the corresponding implementation
*/
Class<?> getRmiImpl(Class<?> iface);
/**
* Gets the RMI interface based on the specified implementation
*
@ -151,4 +127,9 @@ interface RmiSerializationManager extends SerializationManager {
* @throws IllegalArgumentException if the iface/impl have previously been overridden
*/
<Iface, Impl extends Iface> RmiSerializationManager registerRmiImplementation(Class<Iface> ifaceClass, Class<Impl> implClass);
/**
* Gets the cached methods for the specified class ID
*/
CachedMethod[] getMethods(int classID);
}

View File

@ -16,11 +16,10 @@
package dorkbox.network.serialization;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
@ -29,6 +28,7 @@ import org.bouncycastle.crypto.params.IESParameters;
import org.bouncycastle.crypto.params.IESWithCipherParameters;
import org.slf4j.Logger;
import com.esotericsoftware.kryo.ClassResolver;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Serializer;
@ -41,10 +41,12 @@ import com.esotericsoftware.kryo.serializers.FieldSerializer;
import com.esotericsoftware.kryo.util.IdentityMap;
import com.esotericsoftware.kryo.util.IntMap;
import com.esotericsoftware.kryo.util.MapReferenceResolver;
import com.esotericsoftware.kryo.util.Util;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.KryoExtra;
import dorkbox.network.connection.ping.PingMessage;
import dorkbox.network.rmi.CachedMethod;
import dorkbox.network.rmi.InvocationHandlerSerializer;
import dorkbox.network.rmi.InvocationResultSerializer;
import dorkbox.network.rmi.InvokeMethod;
@ -52,28 +54,30 @@ import dorkbox.network.rmi.InvokeMethodResult;
import dorkbox.network.rmi.InvokeMethodSerializer;
import dorkbox.network.rmi.RemoteObjectSerializer;
import dorkbox.network.rmi.RmiRegistration;
import dorkbox.network.rmi.RmiUtils;
import dorkbox.objectPool.ObjectPool;
import dorkbox.objectPool.PoolableObject;
import dorkbox.util.Property;
import dorkbox.util.serialization.ArraysAsListSerializer;
import dorkbox.util.serialization.EccPrivateKeySerializer;
import dorkbox.util.serialization.EccPublicKeySerializer;
import dorkbox.util.serialization.FieldAnnotationAwareSerializer;
import dorkbox.util.serialization.IesParametersSerializer;
import dorkbox.util.serialization.IesWithCipherParametersSerializer;
import dorkbox.util.serialization.IgnoreSerialization;
import dorkbox.util.serialization.UnmodifiableCollectionsSerializer;
import io.netty.buffer.ByteBuf;
/**
* Threads reading/writing, it messes up a single instance. it is possible to use a single kryo with the use of synchronize, however - that
* defeats the point of having multi-threaded serialization
* defeats the point of having multi-threaded serialization.
* <p>
* Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
* serialization scheme for a specific class in an objects hierarchy, you must register that first.
*/
@SuppressWarnings({"unused", "StaticNonFinalField"})
public
class SerializationManager implements CryptoSerializationManager, RmiSerializationManager {
class Serialization implements CryptoSerializationManager, RmiSerializationManager {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(SerializationManager.class);
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Serialization.class.getSimpleName());
/**
* Specify if we want KRYO to use unsafe memory for serialization, or to use the ASM backend. Unsafe memory use is WAY faster, but is
@ -82,49 +86,9 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
@Property
public static boolean useUnsafeMemory = false;
public static
SerializationManager DEFAULT() {
return DEFAULT(true, true, true);
}
public static
SerializationManager DEFAULT(final boolean references, final boolean registrationRequired, final boolean forbidInterfaceRegistration) {
// ignore fields that have the "@IgnoreSerialization" annotation.
Collection<Class<? extends Annotation>> marks = new ArrayList<Class<? extends Annotation>>();
marks.add(IgnoreSerialization.class);
SerializerFactory disregardingFactory = new FieldAnnotationAwareSerializer.Factory(marks, true);
final SerializationManager serializationManager = new SerializationManager(references,
registrationRequired,
forbidInterfaceRegistration,
disregardingFactory);
serializationManager.register(PingMessage.class);
serializationManager.register(byte[].class);
serializationManager.register(IESParameters.class, new IesParametersSerializer());
serializationManager.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
serializationManager.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
serializationManager.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
serializationManager.register(dorkbox.network.connection.registration.Registration.class); // must use full package name!
// necessary for the transport of exceptions.
serializationManager.register(ArrayList.class, new CollectionSerializer());
serializationManager.register(StackTraceElement.class);
serializationManager.register(StackTraceElement[].class);
// extra serializers
//noinspection ArraysAsListWithZeroOrOneArgument
serializationManager.register(Arrays.asList("")
.getClass(), new ArraysAsListSerializer());
UnmodifiableCollectionsSerializer.registerSerializers(serializationManager);
return serializationManager;
}
private static class ClassSerializer {
private static
class ClassSerializer {
final Class<?> clazz;
final Serializer<?> serializer;
@ -133,7 +97,10 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
this.serializer = serializer;
}
}
private static class ClassSerializer1 {
private static
class ClassSerializer1 {
final Class<?> clazz;
final int id;
@ -142,7 +109,10 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
this.id = id;
}
}
private static class ClassSerializer2 {
private static
class ClassSerializer2 {
final Class<?> clazz;
final Serializer<?> serializer;
final int id;
@ -153,7 +123,10 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
this.id = id;
}
}
private static class RemoteIfaceClass {
private static
class RemoteIfaceClass {
private final Class<?> ifaceClass;
RemoteIfaceClass(final Class<?> ifaceClass) {
@ -161,7 +134,9 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
}
}
private static class RemoteImplClass {
private static
class RemoteImplClass {
private final Class<?> ifaceClass;
private final Class<?> implClass;
@ -171,8 +146,100 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
}
}
public static
Serialization DEFAULT() {
return DEFAULT(true, true, true, null);
}
/**
* By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
* <p>
* Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
* serialization scheme for a specific class in an objects hierarchy, you must register that first.
*
* @param references If true, each appearance of an object in the graph after the first is stored as an integer ordinal. When set to true,
* {@link MapReferenceResolver} is used. This enables references to the same object and cyclic graphs to be serialized,
* but typically adds overhead of one byte per object. (should be true)
* @param registrationRequired If true, an exception is thrown when an unregistered class is encountered.
* <p>
* If false, when an unregistered class is encountered, its fully qualified class name will be serialized and the {@link
* Kryo#addDefaultSerializer(Class, Class) default serializer} for the class used to serialize the object. Subsequent
* appearances of the class within the same object graph are serialized as an int id.
* <p>
* Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
* drawback of needing to know the classes to be serialized up front.
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmiInterface(Class)} and
* {@link #registerRmiImplementation(Class, Class)} methods. If false, then interfaces can also be registered.
* <p>
* Enabling interface registration permits matching a different RMI client/server serialization scheme, since
* interfaces are generally in a "common" package, accessible to both the RMI client and server.
* <p>
* Generally, one should not register interfaces, because they have no meaning (ignoring "default" implementations in
* newer versions of java...)
* <p>
* @param factory Sets the serializer factory to use when no {@link Kryo#addDefaultSerializer(Class, Class) default serializers} match
* an object's type. Default is {@link ReflectionSerializerFactory} with {@link FieldSerializer}. @see
* Kryo#newDefaultSerializer(Class)
*/
public static
Serialization DEFAULT(final boolean references,
final boolean registrationRequired,
final boolean implementationRequired,
final SerializerFactory factory) {
final Serialization serialization = new Serialization(references,
registrationRequired,
implementationRequired,
factory);
serialization.register(PingMessage.class);
serialization.register(byte[].class);
serialization.register(IESParameters.class, new IesParametersSerializer());
serialization.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
serialization.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
serialization.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
serialization.register(dorkbox.network.connection.registration.Registration.class); // must use full package name!
// necessary for the transport of exceptions.
serialization.register(ArrayList.class, new CollectionSerializer());
serialization.register(StackTraceElement.class);
serialization.register(StackTraceElement[].class);
// extra serializers
//noinspection ArraysAsListWithZeroOrOneArgument
serialization.register(Arrays.asList("")
.getClass(), new ArraysAsListSerializer());
UnmodifiableCollectionsSerializer.registerSerializers(serialization);
return serialization;
}
public static
Serializer resolveSerializerInstance(Kryo k, Class superClass, Class<? extends Serializer> serializerClass) {
try {
try {
return serializerClass.getConstructor(Kryo.class, Class.class)
.newInstance(k, superClass);
} catch (NoSuchMethodException ex1) {
try {
return serializerClass.getConstructor(Kryo.class)
.newInstance(k);
} catch (NoSuchMethodException ex2) {
try {
return serializerClass.getConstructor(Class.class)
.newInstance(superClass);
} catch (NoSuchMethodException ex3) {
return serializerClass.newInstance();
}
}
}
} catch (Exception ex) {
throw new IllegalArgumentException(
"Unable to create serializer \"" + serializerClass.getName() + "\" for class: " + superClass.getName(), ex);
}
}
private boolean initialized = false;
private final ObjectPool<KryoExtra> kryoPool;
@ -185,63 +252,73 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
private final List<Object> classesToRegister = new ArrayList<Object>();
private boolean usesRmi = false;
private InvokeMethodSerializer methodSerializer = null;
private Serializer<Object> invocationSerializer = null;
// the purpose of the method cache, is to accelerate looking up methods for specific class
private IntMap<CachedMethod[]> methodCache;
private RemoteObjectSerializer remoteObjectSerializer;
// used to track which interface -> implementation, for use by RMI
private final IntMap<Class<?>> rmiIdToImpl = new IntMap<Class<?>>();
private final IntMap<Class<?>> rmiIdToIface = new IntMap<Class<?>>();
private final IdentityMap<Class<?>, Class<?>> rmiIfaceToImpl = new IdentityMap<Class<?>, Class<?>>();
private final IdentityMap<Class<?>, Class<?>> rmiImplToIface = new IdentityMap<Class<?>, Class<?>>();
// reflectASM doesn't work on android
private final boolean useAsm = !useUnsafeMemory && !Util.IS_ANDROID;
/**
* By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
* <p>
* @param references
* If true, each appearance of an object in the graph after the first is stored as an integer ordinal. When set to true,
* {@link MapReferenceResolver} is used. This enables references to the same object and cyclic graphs to be serialized,
* but typically adds overhead of one byte per object. (should be true)
* <p>
* @param registrationRequired
* If true, an exception is thrown when an unregistered class is encountered.
* <p>
* If false, when an unregistered class is encountered, its fully qualified class name will be serialized and the {@link
* Kryo#addDefaultSerializer(Class, Class) default serializer} for the class used to serialize the object. Subsequent
* appearances of the class within the same object graph are serialized as an int id.
* <p>
* Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
* drawback of needing to know the classes to be serialized up front.
* <p>
* @param forbidInterfaceRegistration
* If true, interfaces are not permitted to be registered, outside of the {@link #registerRmiInterface(Class)} and
* {@link #registerRmiImplementation(Class, Class)} methods. If false, then interfaces can also be registered.
* <p>
* Enabling interface registration permits matching a different RMI client/server serialization scheme, since
* interfaces are generally in a "common" package, accessible to both the RMI client and server.
* <p>
* Generally, one should not register interfaces, because they have no meaning (ignoring "default" implementations in
* newer versions of java...)
* @param factory
* Sets the serializer factory to use when no {@link Kryo#addDefaultSerializer(Class, Class) default serializers} match
* an object's type. Default is {@link ReflectionSerializerFactory} with {@link FieldSerializer}. @see
* Kryo#newDefaultSerializer(Class)
* <p>
* Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
* serialization scheme for a specific class in an objects hierarchy, you must register that first.
*
* @param references If true, each appearance of an object in the graph after the first is stored as an integer ordinal. When set to true,
* {@link MapReferenceResolver} is used. This enables references to the same object and cyclic graphs to be serialized,
* but typically adds overhead of one byte per object. (should be true)
* <p>
* @param registrationRequired If true, an exception is thrown when an unregistered class is encountered.
* <p>
* If false, when an unregistered class is encountered, its fully qualified class name will be serialized and the {@link
* Kryo#addDefaultSerializer(Class, Class) default serializer} for the class used to serialize the object. Subsequent
* appearances of the class within the same object graph are serialized as an int id.
* <p>
* Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
* drawback of needing to know the classes to be serialized up front.
* <p>
* @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmiInterface(Class)} and
* {@link #registerRmiImplementation(Class, Class)} methods. If false, then interfaces can also be registered.
* <p>
* Enabling interface registration permits matching a different RMI client/server serialization scheme, since
* interfaces are generally in a "common" package, accessible to both the RMI client and server.
* <p>
* Generally, one should not register interfaces, because they have no meaning (ignoring "default" implementations in
* newer versions of java...)
* <p>
* @param factory Sets the serializer factory to use when no {@link Kryo#addDefaultSerializer(Class, Class) default serializers} match
* an object's type. Default is {@link ReflectionSerializerFactory} with {@link FieldSerializer}. @see
* Kryo#newDefaultSerializer(Class)
*/
public
SerializationManager(final boolean references, final boolean registrationRequired, final boolean forbidInterfaceRegistration, final SerializerFactory factory) {
this.forbidInterfaceRegistration = forbidInterfaceRegistration;
Serialization(final boolean references,
final boolean registrationRequired,
final boolean implementationRequired,
final SerializerFactory factory) {
this.forbidInterfaceRegistration = implementationRequired;
this.kryoPool = ObjectPool.NonBlockingSoftReference(new PoolableObject<KryoExtra>() {
@Override
public
KryoExtra create() {
synchronized (SerializationManager.this) {
KryoExtra kryo = new KryoExtra(SerializationManager.this);
synchronized (Serialization.this) {
// we HAVE to pre-allocate the KRYOs
boolean useAsm = !useUnsafeMemory;
KryoExtra kryo = new KryoExtra(Serialization.this);
kryo.getFieldSerializerConfig().setUseAsm(useAsm);
kryo.getFieldSerializerConfig()
.setUseAsm(useAsm);
kryo.setRegistrationRequired(registrationRequired);
kryo.setReferences(references);
@ -260,11 +337,11 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
kryo.register(InvocationHandler.class, invocationSerializer);
}
// All registration MUST happen in-order of when the register(*) method was called, otherwise there are problems.
for (Object clazz : classesToRegister) {
if (clazz instanceof Class) {
kryo.register((Class)clazz);
Class aClass = (Class) clazz;
kryo.register(aClass);
}
else if (clazz instanceof ClassSerializer) {
ClassSerializer classSerializer = (ClassSerializer) clazz;
@ -279,31 +356,32 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
kryo.register(classSerializer.clazz, classSerializer.serializer, classSerializer.id);
}
else if (clazz instanceof RemoteIfaceClass) {
// THIS IS DONE ON THE CLIENT
// THIS IS DONE ON THE "CLIENT"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RemoteIfaceClass remoteIfaceClass = (RemoteIfaceClass) clazz;
// registers the interface, so that when it is READ, it becomes a "magic" proxy object
kryo.register(remoteIfaceClass.ifaceClass, remoteObjectSerializer);
}
else if (clazz instanceof RemoteImplClass) {
// THIS IS DONE ON THE SERVER
// THIS IS DONE ON THE "SERVER"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RemoteImplClass remoteImplClass = (RemoteImplClass) clazz;
// registers the implementation, so that when it is WRITTEN, it becomes a "magic" proxy object
int id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId();
int id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer)
.getId();
// sets up the RMI, so when we receive the iface class from the client, we know what impl to use
// if this is over-written, we don't care.
rmiIdToImpl.put(id, remoteImplClass.implClass);
rmiIdToIface.put(id, remoteImplClass.ifaceClass);
rmiIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read
rmiIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write
}
}
@ -317,6 +395,32 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
});
}
private static
ArrayList<Class<?>> getHierarchy(Class<?> clazz) {
final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
LinkedList<Class<?>> parseClasses = new LinkedList<Class<?>>();
parseClasses.add(clazz);
Class<?> nextClass;
while (!parseClasses.isEmpty()) {
nextClass = parseClasses.removeFirst();
allClasses.add(nextClass);
// add all interfaces from our class (if any)
parseClasses.addAll(Arrays.asList(nextClass.getInterfaces()));
Class<?> superclass = nextClass.getSuperclass();
if (superclass != null) {
parseClasses.add(superclass);
}
}
// remove the first class, because we don't need it
allClasses.remove(clazz);
return allClasses;
}
/**
* Registers the class using the lowest, next available integer ID and the {@link Kryo#getDefaultSerializer(Class) default serializer}.
* If the class is already registered, the existing entry is updated with the new serializer.
@ -334,7 +438,8 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
}
else if (forbidInterfaceRegistration && clazz.isInterface()) {
throw new IllegalArgumentException("Cannot register an interface for serialization. It must be an implementation.");
} else {
}
else {
classesToRegister.add(clazz);
}
@ -420,6 +525,60 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
return this;
}
/**
* Necessary to register classes for RMI, only called once if/when the RMI bridge is created.
*
* @return true if there are classes that have been registered for RMI
*/
@Override
public synchronized
boolean initRmiSerialization() {
if (!usesRmi) {
return false;
}
if (initialized) {
logger.warn("Serialization manager already initialized. Ignoring duplicate initRmiSerialization() call.");
return true;
}
methodSerializer = new InvokeMethodSerializer();
invocationSerializer = new InvocationHandlerSerializer(logger);
remoteObjectSerializer = new RemoteObjectSerializer();
methodCache = new IntMap<CachedMethod[]>();
return true;
}
/**
* @return takes a kryo instance from the pool.
*/
@Override
public
KryoExtra takeKryo() {
return kryoPool.take();
}
/**
* Returns a kryo instance to the pool.
*/
@Override
public
void returnKryo(KryoExtra kryo) {
kryoPool.put(kryo);
}
/**
* Gets the RMI interface based on the specified implementation
*
* @return the corresponding interface
*/
@Override
public
Class<?> getRmiIface(Class<?> implementation) {
return rmiImplToIface.get(implementation);
}
/**
* Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI.
* <p>
@ -442,6 +601,7 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
usesRmi = true;
classesToRegister.add(new RemoteIfaceClass(ifaceClass));
return this;
}
@ -491,112 +651,39 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
}
/**
* Necessary to register classes for RMI, only called once if/when the RMI bridge is created.
*
* @return true if there are classes that have been registered for RMI
* Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
* <p>
* No crypto and no sequence number
* <p>
* There is a small speed penalty if there were no kryo's available to use.
*/
@Override
public synchronized
boolean initRmiSerialization() {
if (!usesRmi) {
return false;
public final
void write(final ByteBuf buffer, final Object message) throws IOException {
final KryoExtra kryo = kryoPool.take();
try {
kryo.write(buffer, message);
} finally {
kryoPool.put(kryo);
}
}
if (initialized) {
logger.warn("Serialization manager already initialized. Ignoring duplicate initRmiSerialization() call.");
return true;
/**
* Reads an object from the buffer.
* <p>
* No crypto and no sequence number
*
* @param length should ALWAYS be the length of the expected object!
*/
@Override
public final
Object read(final ByteBuf buffer, final int length) throws IOException {
final KryoExtra kryo = kryoPool.take();
try {
return kryo.read(buffer);
} finally {
kryoPool.put(kryo);
}
methodSerializer = new InvokeMethodSerializer();
invocationSerializer = new InvocationHandlerSerializer(logger);
remoteObjectSerializer = new RemoteObjectSerializer();
return true;
}
/**
* Called when initialization is complete. This is to prevent (and recognize) out-of-order class/serializer registration. If an ID
* is already in use by a different type, a {@link KryoException} is thrown.
*/
@Override
public synchronized
void finishInit() {
initialized = true;
// initialize the kryo pool with at least 1 kryo instance. This ALSO makes sure that all of our class registration is done
// correctly and (if not) we are are notified on the initial thread (instead of on the network udpate thread)
kryoPool.put(takeKryo());
}
@Override
public synchronized
boolean initialized() {
return initialized;
}
/**
* Gets the RMI interface based on the specified ID (which is the ID for the registered implementation)
*
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
* @return the implementation for the interface, or null
*/
@Override
public
Class<?> getRmiIface(int objectId) {
return rmiIdToIface.get(objectId);
}
/**
* Gets the RMI implementation based on the specified ID (which is the ID for the registered interface)
*
* @param objectId ID of the registered interface, which will map to the corresponding implementation.
*
* @return the implementation for the interface, or null
*/
@Override
public
Class<?> getRmiImpl(int objectId) {
return rmiIdToImpl.get(objectId);
}
/**
* Gets the RMI implementation based on the specified interface
*
* @return the corresponding implementation
*/
@Override
public
Class<?> getRmiImpl(Class<?> iface) {
return rmiIfaceToImpl.get(iface);
}
/**
* Gets the RMI interface based on the specified implementation
*
* @return the corresponding interface
*/
@Override
public
Class<?> getRmiIface(Class<?> implementation) {
return rmiImplToIface.get(implementation);
}
/**
* @return takes a kryo instance from the pool.
*/
@Override
public
KryoExtra takeKryo() {
return kryoPool.take();
}
/**
* Returns a kryo instance to the pool.
*/
@Override
public
void returnKryo(KryoExtra kryo) {
kryoPool.put(kryo);
}
/**
@ -652,39 +739,75 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati
}
/**
* Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
* <p>
* No crypto and no sequence number
* <p>
* There is a small speed penalty if there were no kryo's available to use.
* Called when initialization is complete. This is to prevent (and recognize) out-of-order class/serializer registration. If an ID
* is already in use by a different type, a {@link KryoException} is thrown.
*/
@Override
public final
void write(final ByteBuf buffer, final Object message) throws IOException {
final KryoExtra kryo = kryoPool.take();
public synchronized
void finishInit() {
initialized = true;
// initialize the kryo pool with at least 1 kryo instance. This ALSO makes sure that all of our class registration is done
// correctly and (if not) we are are notified on the initial thread (instead of on the network update thread)
KryoExtra kryo = null;
try {
kryo.write(buffer, message);
kryo = takeKryo();
ClassResolver classResolver = kryo.getClassResolver();
// now initialize the RMI cached methods, so that they are "final" when the network threads need access to it.
for (Object clazz : classesToRegister) {
if (clazz instanceof RemoteIfaceClass) {
// THIS IS DONE ON THE "CLIENT"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RemoteIfaceClass remoteIfaceClass = (RemoteIfaceClass) clazz;
int id = classResolver.getRegistration(remoteIfaceClass.ifaceClass)
.getId();
CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(logger, kryo, useAsm,
remoteIfaceClass.ifaceClass,
null,
id);
methodCache.put(id, cachedMethods);
}
else if (clazz instanceof RemoteImplClass) {
// THIS IS DONE ON THE "SERVER"
// "server" means the side of the connection that has the implementation details for the RMI object
// "client" means the side of the connection that accesses the "server" side object via a proxy object
// the client will NEVER send this object to the server.
// the server will ONLY send this object to the client, where on the client it becomes the proxy/interface.
RemoteImplClass remoteImplClass = (RemoteImplClass) clazz;
int id = classResolver.getRegistration(remoteImplClass.implClass)
.getId();
CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(logger, kryo, useAsm,
remoteImplClass.ifaceClass,
remoteImplClass.implClass,
id);
methodCache.put(id, cachedMethods);
}
}
} finally {
kryoPool.put(kryo);
if (kryo != null) {
kryoPool.put(kryo);
}
}
}
/**
* Reads an object from the buffer.
* <p>
* No crypto and no sequence number
*
* @param length should ALWAYS be the length of the expected object!
*/
@Override
public final
Object read(final ByteBuf buffer, final int length) throws IOException {
final KryoExtra kryo = kryoPool.take();
try {
return kryo.read(buffer);
} finally {
kryoPool.put(kryo);
}
public
CachedMethod[] getMethods(final int classId) {
return methodCache.get(classId);
}
@Override
public synchronized
boolean initialized() {
return initialized;
}
/**

View File

@ -31,7 +31,7 @@ import dorkbox.network.PingPongTest.TYPE;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.connection.idle.IdleBridge;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -54,7 +54,7 @@ public class ChunkedDataIdleTest extends BaseTest {
Configuration configuration = new Configuration();
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
sendObject(mainData, configuration, ConnectionType.TCP);
@ -65,7 +65,7 @@ public class ChunkedDataIdleTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
sendObject(mainData, configuration, ConnectionType.UDP);

View File

@ -28,7 +28,7 @@ import org.junit.Test;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -43,7 +43,7 @@ class ClientSendTest extends BaseTest {
Configuration configuration = new Configuration();
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
Server server = new Server(configuration);

View File

@ -28,7 +28,7 @@ import org.junit.Test;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPointBase;
import dorkbox.network.connection.Listener;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -42,7 +42,7 @@ class ConnectionTest extends BaseTest {
Configuration configuration = new Configuration();
configuration.localChannelName = EndPointBase.LOCAL_CHANNEL;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
startServer(configuration);
@ -58,7 +58,7 @@ class ConnectionTest extends BaseTest {
Configuration configuration = new Configuration();
configuration.tcpPort = tcpPort;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
startServer(configuration);
@ -77,7 +77,7 @@ class ConnectionTest extends BaseTest {
Configuration configuration = new Configuration();
configuration.tcpPort = tcpPort;
configuration.udpPort = udpPort;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
startServer(configuration);

View File

@ -36,7 +36,7 @@ import dorkbox.network.connection.idle.IdleListener;
import dorkbox.network.connection.idle.IdleListenerTCP;
import dorkbox.network.connection.idle.IdleListenerUDP;
import dorkbox.network.connection.idle.InputStreamSender;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -60,7 +60,7 @@ class IdleTest extends BaseTest {
Configuration configuration = new Configuration();
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT(false, false, true);
configuration.serialization = Serialization.DEFAULT(false, false, true, null);
streamSpecificType(largeDataSize, configuration, ConnectionType.TCP);
@ -70,7 +70,7 @@ class IdleTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT(false, false, true);
configuration.serialization = Serialization.DEFAULT(false, false, true, null);
streamSpecificType(largeDataSize, configuration, ConnectionType.UDP);
}
@ -89,7 +89,7 @@ class IdleTest extends BaseTest {
Configuration configuration = new Configuration();
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
sendObject(mainData, configuration, ConnectionType.TCP);
@ -100,7 +100,7 @@ class IdleTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
sendObject(mainData, configuration, ConnectionType.TCP);

View File

@ -29,7 +29,7 @@ import org.junit.Test;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -50,7 +50,7 @@ class LargeResizeBufferTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
Server server = new Server(configuration);

View File

@ -28,7 +28,7 @@ import org.junit.Test;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -43,7 +43,7 @@ class MultipleServerTest extends BaseTest {
configuration1.tcpPort = tcpPort;
configuration1.udpPort = udpPort;
configuration1.localChannelName = "chan1";
configuration1.serialization = SerializationManager.DEFAULT();
configuration1.serialization = Serialization.DEFAULT();
configuration1.serialization.register(String[].class);
Server server1 = new Server(configuration1);
@ -68,7 +68,7 @@ class MultipleServerTest extends BaseTest {
configuration2.tcpPort = tcpPort + 1;
configuration2.udpPort = udpPort + 1;
configuration2.localChannelName = "chan2";
configuration2.serialization = SerializationManager.DEFAULT();
configuration2.serialization = Serialization.DEFAULT();
configuration2.serialization.register(String[].class);
Server server2 = new Server(configuration2);

View File

@ -33,7 +33,7 @@ import org.junit.Test;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.connection.Listeners;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -61,7 +61,7 @@ class MultipleThreadTest extends BaseTest {
Configuration configuration = new Configuration();
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.register(String[].class);
configuration.serialization.register(DataClass.class);

View File

@ -30,7 +30,7 @@ import org.junit.Test;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.connection.Listeners;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -47,7 +47,7 @@ class PingPongLocalTest extends BaseTest {
populateData(dataLOCAL);
Configuration configuration = Configuration.localOnly();
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);

View File

@ -32,7 +32,7 @@ import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPointBase;
import dorkbox.network.connection.Listener;
import dorkbox.network.connection.Listeners;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -60,7 +60,7 @@ class PingPongTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);

View File

@ -32,7 +32,7 @@ import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPointBase;
import dorkbox.network.connection.Listener;
import dorkbox.network.connection.Listeners;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -54,7 +54,7 @@ class UnregisteredClassTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT(false, false, true);
configuration.serialization = Serialization.DEFAULT(false, false, true, null);
System.err.println("Running test " + this.tries + " times, please wait for it to finish.");

View File

@ -50,7 +50,7 @@ import dorkbox.network.Server;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.Listener;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -64,10 +64,10 @@ class RmiGlobalTest extends BaseTest {
private final TestCow globalRemoteClientObject = new TestCowImpl();
private static
void runTest(final Connection connection, final TestCow rObject, final TestCow test, final int remoteObjectID) {
void runTest(final Connection connection, final TestCow globalObject, final TestCow test, final int remoteObjectID) {
System.err.println("Starting test for: " + remoteObjectID);
assertEquals(rObject.hashCode(), test.hashCode());
assertEquals(globalObject.hashCode(), test.hashCode());
RemoteObject remoteObject = (RemoteObject) test;
// Default behavior. RMI is transparent, method calls behave like normal
@ -110,7 +110,7 @@ class RmiGlobalTest extends BaseTest {
try {
test.throwException();
} catch (UnsupportedOperationException ex) {
System.err.println("\tExpected.");
System.err.println("\tExpected exception! " + ex.getMessage());
caught = true;
}
assertTrue(caught);
@ -195,16 +195,15 @@ class RmiGlobalTest extends BaseTest {
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID) - NOTICE: none of the super classes/interfaces are registered!
configuration.serialization.registerRmiInterface(TestCow.class);
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID)
// for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID) - NOTICE: none of the super classes/interfaces are registered!
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);
final Server server = new Server(configuration);
server.setIdleTimeout(0);
@ -267,7 +266,7 @@ class RmiGlobalTest extends BaseTest {
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
// for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID)

View File

@ -29,10 +29,9 @@ import dorkbox.network.Configuration;
import dorkbox.network.Server;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
import dorkbox.util.serialization.IgnoreSerialization;
@SuppressWarnings("Duplicates")
public
@ -48,21 +47,18 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
*
* Specifically, from CachedMethod.java
*
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
// an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
// the interface, and the implType may override the method, so that we add the connection as the first in
// the list of parameters.
//
// for example:
// Interface: foo(String x)
// Impl: foo(Connection c, String x)
//
// The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
// This MUST hold valid for both remote and local connection types.
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
* In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
* This is to support calling RMI methods from an interface (that does pass the connection reference) to
* an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
* the interface, and the implType may override the method, so that we add the connection as the first in
* the list of parameters.
*
* for example:
* Interface: foo(String x)
* Impl: foo(Connection c, String x)
*
* The implType (if it exists, with the same name, and with the same signature + connection parameter) will be called from the interface
* instead of the method that would NORMALLY be called.
*/
@Test
public
@ -71,7 +67,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
@ -82,7 +78,6 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
addEndPoint(server);
server.bind(false);
server.listeners()
.add(new Listener.OnMessageReceived<Connection, OtherObjectImpl>() {
@Override
@ -105,7 +100,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.registerRmiInterface(TestObject.class);
configuration.serialization.registerRmiInterface(OtherObject.class);
@ -124,7 +119,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
@Override
public
void created(final TestObject remoteObject) {
// MUST run on a separate thread because remote object method invocations are blocking
// MUST run on a separate thread because remote object method invocations are blocking
new Thread() {
@Override
public
@ -190,8 +185,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
private static
class TestObjectImpl implements TestObject {
@IgnoreSerialization
private final int ID = idCounter.getAndIncrement();
private final transient int ID = idCounter.getAndIncrement();
@Rmi
private final OtherObject otherObject = new OtherObjectImpl();
@ -241,8 +235,7 @@ class RmiSendObjectOverrideMethodTest extends BaseTest {
private static
class OtherObjectImpl implements OtherObject {
@IgnoreSerialization
private final int ID = idCounter.getAndIncrement();
private final transient int ID = idCounter.getAndIncrement();
private float aFloat;

View File

@ -48,10 +48,9 @@ import dorkbox.network.Configuration;
import dorkbox.network.Server;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
import dorkbox.util.serialization.IgnoreSerialization;
@SuppressWarnings("Duplicates")
public
@ -68,7 +67,7 @@ class RmiSendObjectTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class);
configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class);
@ -103,7 +102,7 @@ class RmiSendObjectTest extends BaseTest {
configuration.tcpPort = tcpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
configuration.serialization.registerRmiInterface(TestObject.class);
configuration.serialization.registerRmiInterface(OtherObject.class);
@ -180,8 +179,7 @@ class RmiSendObjectTest extends BaseTest {
private static
class TestObjectImpl implements TestObject {
@IgnoreSerialization
private final int ID = idCounter.getAndIncrement();
private final transient int ID = idCounter.getAndIncrement();
@Rmi
private final OtherObject otherObject = new OtherObjectImpl();
@ -216,8 +214,7 @@ class RmiSendObjectTest extends BaseTest {
private static
class OtherObjectImpl implements OtherObject {
@IgnoreSerialization
private final int ID = idCounter.getAndIncrement();
private final transient int ID = idCounter.getAndIncrement();
private float aFloat;

View File

@ -50,7 +50,7 @@ import dorkbox.network.Server;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.Listener;
import dorkbox.network.connection.Listeners;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -167,6 +167,7 @@ class RmiTest extends BaseTest {
.TCP(m)
.flush();
System.out.println("Finished tests");
}
public static
@ -184,7 +185,7 @@ class RmiTest extends BaseTest {
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
// for Client -> Server RMI (ID 1)
@ -248,7 +249,7 @@ class RmiTest extends BaseTest {
configuration.udpPort = udpPort;
configuration.host = host;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
register(configuration.serialization);
// for Client -> Server RMI (ID 1)

View File

@ -4,9 +4,7 @@ package dorkbox.network.rmi;
*
*/
public
interface TestCow {
void throwException();
interface TestCow extends TestCowBase {
void moo();
void moo(String value);

View File

@ -0,0 +1,9 @@
package dorkbox.network.rmi;
/**
* This is a different interface so we can also test CachedMethod operations
*/
public
interface TestCowBase {
void throwException();
}

View File

@ -0,0 +1,27 @@
package dorkbox.network.rmi;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
*/
public
class TestCowBaseImpl implements TestCowBase {
// has to start at 1, because UDP method invocations ignore return values
static final AtomicInteger ID_COUNTER = new AtomicInteger(1);
public
TestCowBaseImpl() {
}
@Override
public
void throwException() {
throw new UnsupportedOperationException("Why would I do that?");
}
public
int id() {
return Integer.MAX_VALUE;
}
}

View File

@ -1,48 +1,36 @@
package dorkbox.network.rmi;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
*/
public
class TestCowImpl implements TestCow {
// has to start at 1, because UDP method invocations ignore return values
static final AtomicInteger ID_COUNTER = new AtomicInteger(1);
public long value = System.currentTimeMillis();
public int moos;
class TestCowImpl extends TestCowBaseImpl implements TestCow {
private int moos;
private final int id = ID_COUNTER.getAndIncrement();
public
TestCowImpl() {
}
@Override
public
void throwException() {
throw new UnsupportedOperationException("Why would I do that?");
}
@Override
public
void moo() {
this.moos++;
System.out.println("Moo!");
System.out.println("Moo! " + this.moos);
}
@Override
public
void moo(String value) {
this.moos += 2;
System.out.println("Moo: " + value);
System.out.println("Moo! " + this.moos + " :" + value);
}
@Override
public
void moo(String value, long delay) {
this.moos += 4;
System.out.println("Moo: " + value + " (" + delay + ")");
System.out.println("Moo! " + this.moos + " :" + value + " (" + delay + ")");
try {
Thread.sleep(delay);
} catch (InterruptedException e) {

View File

@ -19,7 +19,7 @@ import dorkbox.network.connection.Connection;
import dorkbox.network.rmi.RemoteObjectCallback;
import dorkbox.network.rmi.RmiTest;
import dorkbox.network.rmi.TestCow;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import io.netty.util.ResourceLeakDetector;
/**
@ -82,13 +82,13 @@ class TestClient
configuration.udpPort = 2001;
configuration.host = "localhost";
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
RmiTest.register(configuration.serialization);
configuration.serialization.registerRmiInterface(TestCow.class);
try {
Client client = new Client(configuration);
final Client client = new Client(configuration);
// client.setIdleTimeout(0);
client.listeners()
@ -110,7 +110,15 @@ class TestClient
public
void run() {
RmiTest.runTests(connection, remoteObject, 1);
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("DONE");
client.stop();
}
}.start();
}
@ -124,7 +132,7 @@ class TestClient
client.connect(3330);
Thread.sleep(999999999);
client.waitForShutdown();
} catch (Exception e) {
e.printStackTrace();
}

View File

@ -6,7 +6,7 @@ import dorkbox.network.Server;
import dorkbox.network.rmi.RmiTest;
import dorkbox.network.rmi.TestCow;
import dorkbox.network.rmi.TestCowImpl;
import dorkbox.network.serialization.SerializationManager;
import dorkbox.network.serialization.Serialization;
import dorkbox.util.exceptions.InitializationException;
import dorkbox.util.exceptions.SecurityException;
@ -24,7 +24,7 @@ class TestServer
configuration.tcpPort = 2000;
configuration.udpPort = 2001;
configuration.serialization = SerializationManager.DEFAULT();
configuration.serialization = Serialization.DEFAULT();
RmiTest.register(configuration.serialization);
configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class);