diff --git a/src/dorkbox/network/DnsServer.java b/src/dorkbox/network/DnsServer.java index 094a8d40..597195fb 100644 --- a/src/dorkbox/network/DnsServer.java +++ b/src/dorkbox/network/DnsServer.java @@ -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 { diff --git a/src/dorkbox/network/connection/ConnectionImpl.java b/src/dorkbox/network/connection/ConnectionImpl.java index 7de26e7b..8a50d281 100644 --- a/src/dorkbox/network/connection/ConnectionImpl.java +++ b/src/dorkbox/network/connection/ConnectionImpl.java @@ -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 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 remoteClasses = new LinkedList(); - remoteClasses.add(new ClassObject(implementationClass, remotePrimaryObject)); + + // the @Rmi annotation allows an RMI object to have fields with objects that are ALSO RMI + LinkedList classesToCheck = new LinkedList(); + 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 diff --git a/src/dorkbox/network/connection/EndPointBase.java b/src/dorkbox/network/connection/EndPointBase.java index c6c633b1..c1ccb97f 100644 --- a/src/dorkbox/network/connection/EndPointBase.java +++ b/src/dorkbox/network/connection/EndPointBase.java @@ -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 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 diff --git a/src/dorkbox/network/rmi/AsmCachedMethod.java b/src/dorkbox/network/rmi/AsmCachedMethod.java index 0d46d22f..4f30b4e9 100644 --- a/src/dorkbox/network/rmi/AsmCachedMethod.java +++ b/src/dorkbox/network/rmi/AsmCachedMethod.java @@ -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); } } diff --git a/src/dorkbox/network/rmi/CachedMethod.java b/src/dorkbox/network/rmi/CachedMethod.java index 236afa11..88eb537b 100644 --- a/src/dorkbox/network/rmi/CachedMethod.java +++ b/src/dorkbox/network/rmi/CachedMethod.java @@ -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_COMPARATOR = new Comparator() { - @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, CachedMethod[]> methodCache = new ConcurrentHashMap, 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 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 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 getOverriddenMethods(final Class type, final ArrayList origMethods) { - final ArrayList implMethods = getMethods(type); - final IdentityMap overrideMap = new IdentityMap(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 getMethods(final Class type) { - final ArrayList allMethods = new ArrayList(); - - Class nextClass = type; - while (nextClass != null) { - Collections.addAll(allMethods, nextClass.getDeclaredMethods()); - nextClass = nextClass.getSuperclass(); - if (nextClass == Object.class) { - break; - } - } - - final ArrayList methods = new ArrayList(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); } } diff --git a/src/dorkbox/network/rmi/InvokeMethod.java b/src/dorkbox/network/rmi/InvokeMethod.java index f1385fa9..5c28c206 100644 --- a/src/dorkbox/network/rmi/InvokeMethod.java +++ b/src/dorkbox/network/rmi/InvokeMethod.java @@ -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; diff --git a/src/dorkbox/network/rmi/InvokeMethodSerializer.java b/src/dorkbox/network/rmi/InvokeMethodSerializer.java index afcce2ff..226e9a16 100644 --- a/src/dorkbox/network/rmi/InvokeMethodSerializer.java +++ b/src/dorkbox/network/rmi/InvokeMethodSerializer.java @@ -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 { @Override public InvokeMethod read(final Kryo kryo, final Input input, final Class 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; diff --git a/src/dorkbox/network/rmi/Rmi.java b/src/dorkbox/network/rmi/Rmi.java index 3737e660..c4068fe5 100644 --- a/src/dorkbox/network/rmi/Rmi.java +++ b/src/dorkbox/network/rmi/Rmi.java @@ -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. - *

- * 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. - *

- * It is mandatory for the correct implementation (as per the interface guideline) to exist, and should return null. - *

- * 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 {} diff --git a/src/dorkbox/network/rmi/RmiBridge.java b/src/dorkbox/network/rmi/RmiBridge.java index 4547e7a9..09bdbd7e 100644 --- a/src/dorkbox/network/rmi/RmiBridge.java +++ b/src/dorkbox/network/rmi/RmiBridge.java @@ -68,21 +68,18 @@ import dorkbox.util.collections.ObjectIntMap; *

*

* 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 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. *

* for example: - * Interface: foo(String x) - * Impl: foo(Connection c, String x) + * 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. *

- * 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 , 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)); } } diff --git a/src/dorkbox/network/rmi/RmiProxyHandler.java b/src/dorkbox/network/rmi/RmiProxyHandler.java index c1167af3..faa15ca3 100644 --- a/src/dorkbox/network/rmi/RmiProxyHandler.java +++ b/src/dorkbox/network/rmi/RmiProxyHandler.java @@ -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. + *

+ * 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 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 = ""; - 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() { @@ -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; diff --git a/src/dorkbox/network/rmi/RmiUtils.java b/src/dorkbox/network/rmi/RmiUtils.java new file mode 100644 index 00000000..5490a184 --- /dev/null +++ b/src/dorkbox/network/rmi/RmiUtils.java @@ -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_COMPARATOR = new Comparator() { + @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 allMethods = new ArrayList(); + final Map> accessibleMethods = new HashMap>(); + + LinkedList> classes = new LinkedList>(); + 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 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(); + } + 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; + } + +} diff --git a/src/dorkbox/network/serialization/RmiSerializationManager.java b/src/dorkbox/network/serialization/RmiSerializationManager.java index e00db0e2..5fbfbfa9 100644 --- a/src/dorkbox/network/serialization/RmiSerializationManager.java +++ b/src/dorkbox/network/serialization/RmiSerializationManager.java @@ -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 */ RmiSerializationManager registerRmiImplementation(Class ifaceClass, Class implClass); + + /** + * Gets the cached methods for the specified class ID + */ + CachedMethod[] getMethods(int classID); } diff --git a/src/dorkbox/network/serialization/SerializationManager.java b/src/dorkbox/network/serialization/Serialization.java similarity index 65% rename from src/dorkbox/network/serialization/SerializationManager.java rename to src/dorkbox/network/serialization/Serialization.java index 887ae6c0..c4d2c8dd 100644 --- a/src/dorkbox/network/serialization/SerializationManager.java +++ b/src/dorkbox/network/serialization/Serialization.java @@ -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. + *

+ * 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> marks = new ArrayList>(); - 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 + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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) + */ + 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 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 kryoPool; @@ -185,63 +252,73 @@ class SerializationManager implements CryptoSerializationManager, RmiSerializati private final List classesToRegister = new ArrayList(); private boolean usesRmi = false; + private InvokeMethodSerializer methodSerializer = null; private Serializer invocationSerializer = null; + + // the purpose of the method cache, is to accelerate looking up methods for specific class + private IntMap methodCache; private RemoteObjectSerializer remoteObjectSerializer; // used to track which interface -> implementation, for use by RMI private final IntMap> rmiIdToImpl = new IntMap>(); private final IntMap> rmiIdToIface = new IntMap>(); + private final IdentityMap, Class> rmiIfaceToImpl = new IdentityMap, Class>(); private final IdentityMap, Class> rmiImplToIface = new IdentityMap, 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 *

- * @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. - *

- * 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. - *

- * 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 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. - *

- * 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. - *

- * 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) - *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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) */ 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() { @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> getHierarchy(Class clazz) { + final ArrayList> allClasses = new ArrayList>(); + LinkedList> parseClasses = new LinkedList>(); + 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(); + + 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. *

@@ -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. + *

+ * No crypto and no sequence number + *

+ * 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. + *

+ * 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. - *

- * No crypto and no sequence number - *

- * 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. - *

- * 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; } /** diff --git a/test/dorkbox/network/ChunkedDataIdleTest.java b/test/dorkbox/network/ChunkedDataIdleTest.java index 1ac72837..38be476a 100644 --- a/test/dorkbox/network/ChunkedDataIdleTest.java +++ b/test/dorkbox/network/ChunkedDataIdleTest.java @@ -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); diff --git a/test/dorkbox/network/ClientSendTest.java b/test/dorkbox/network/ClientSendTest.java index 93fb348b..f701383b 100644 --- a/test/dorkbox/network/ClientSendTest.java +++ b/test/dorkbox/network/ClientSendTest.java @@ -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); diff --git a/test/dorkbox/network/ConnectionTest.java b/test/dorkbox/network/ConnectionTest.java index 9ad36221..93a13c3e 100644 --- a/test/dorkbox/network/ConnectionTest.java +++ b/test/dorkbox/network/ConnectionTest.java @@ -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); diff --git a/test/dorkbox/network/IdleTest.java b/test/dorkbox/network/IdleTest.java index 384552ba..faf28d14 100644 --- a/test/dorkbox/network/IdleTest.java +++ b/test/dorkbox/network/IdleTest.java @@ -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); diff --git a/test/dorkbox/network/LargeResizeBufferTest.java b/test/dorkbox/network/LargeResizeBufferTest.java index 7f4f6e0c..fc996a8f 100644 --- a/test/dorkbox/network/LargeResizeBufferTest.java +++ b/test/dorkbox/network/LargeResizeBufferTest.java @@ -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); diff --git a/test/dorkbox/network/MultipleServerTest.java b/test/dorkbox/network/MultipleServerTest.java index 5b1df640..ed23fdb7 100644 --- a/test/dorkbox/network/MultipleServerTest.java +++ b/test/dorkbox/network/MultipleServerTest.java @@ -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); diff --git a/test/dorkbox/network/MultipleThreadTest.java b/test/dorkbox/network/MultipleThreadTest.java index 1fdf1b07..176ddc27 100644 --- a/test/dorkbox/network/MultipleThreadTest.java +++ b/test/dorkbox/network/MultipleThreadTest.java @@ -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); diff --git a/test/dorkbox/network/PingPongLocalTest.java b/test/dorkbox/network/PingPongLocalTest.java index 20e4b7f4..c9cfebc2 100644 --- a/test/dorkbox/network/PingPongLocalTest.java +++ b/test/dorkbox/network/PingPongLocalTest.java @@ -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); diff --git a/test/dorkbox/network/PingPongTest.java b/test/dorkbox/network/PingPongTest.java index abd2894d..deacafdb 100644 --- a/test/dorkbox/network/PingPongTest.java +++ b/test/dorkbox/network/PingPongTest.java @@ -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); diff --git a/test/dorkbox/network/UnregisteredClassTest.java b/test/dorkbox/network/UnregisteredClassTest.java index 50b99209..344987c3 100644 --- a/test/dorkbox/network/UnregisteredClassTest.java +++ b/test/dorkbox/network/UnregisteredClassTest.java @@ -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."); diff --git a/test/dorkbox/network/rmi/RmiGlobalTest.java b/test/dorkbox/network/rmi/RmiGlobalTest.java index c80f6c29..40b2a656 100644 --- a/test/dorkbox/network/rmi/RmiGlobalTest.java +++ b/test/dorkbox/network/rmi/RmiGlobalTest.java @@ -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) diff --git a/test/dorkbox/network/rmi/RmiSendObjectOverrideMethodTest.java b/test/dorkbox/network/rmi/RmiSendObjectOverrideMethodTest.java index 9dcf2a53..2d8c4c2c 100644 --- a/test/dorkbox/network/rmi/RmiSendObjectOverrideMethodTest.java +++ b/test/dorkbox/network/rmi/RmiSendObjectOverrideMethodTest.java @@ -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() { @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; diff --git a/test/dorkbox/network/rmi/RmiSendObjectTest.java b/test/dorkbox/network/rmi/RmiSendObjectTest.java index db093083..b0f453f0 100644 --- a/test/dorkbox/network/rmi/RmiSendObjectTest.java +++ b/test/dorkbox/network/rmi/RmiSendObjectTest.java @@ -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; diff --git a/test/dorkbox/network/rmi/RmiTest.java b/test/dorkbox/network/rmi/RmiTest.java index 09a6374e..46def678 100644 --- a/test/dorkbox/network/rmi/RmiTest.java +++ b/test/dorkbox/network/rmi/RmiTest.java @@ -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) diff --git a/test/dorkbox/network/rmi/TestCow.java b/test/dorkbox/network/rmi/TestCow.java index 527385f4..317cb405 100644 --- a/test/dorkbox/network/rmi/TestCow.java +++ b/test/dorkbox/network/rmi/TestCow.java @@ -4,9 +4,7 @@ package dorkbox.network.rmi; * */ public -interface TestCow { - void throwException(); - +interface TestCow extends TestCowBase { void moo(); void moo(String value); diff --git a/test/dorkbox/network/rmi/TestCowBase.java b/test/dorkbox/network/rmi/TestCowBase.java new file mode 100644 index 00000000..b725713f --- /dev/null +++ b/test/dorkbox/network/rmi/TestCowBase.java @@ -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(); +} diff --git a/test/dorkbox/network/rmi/TestCowBaseImpl.java b/test/dorkbox/network/rmi/TestCowBaseImpl.java new file mode 100644 index 00000000..7b2b6f8f --- /dev/null +++ b/test/dorkbox/network/rmi/TestCowBaseImpl.java @@ -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; + } +} diff --git a/test/dorkbox/network/rmi/TestCowImpl.java b/test/dorkbox/network/rmi/TestCowImpl.java index 01e3f496..0ed82499 100644 --- a/test/dorkbox/network/rmi/TestCowImpl.java +++ b/test/dorkbox/network/rmi/TestCowImpl.java @@ -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) { diff --git a/test/dorkbox/network/rmi/multiJVM/TestClient.java b/test/dorkbox/network/rmi/multiJVM/TestClient.java index 655d2483..519425c6 100644 --- a/test/dorkbox/network/rmi/multiJVM/TestClient.java +++ b/test/dorkbox/network/rmi/multiJVM/TestClient.java @@ -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(); } diff --git a/test/dorkbox/network/rmi/multiJVM/TestServer.java b/test/dorkbox/network/rmi/multiJVM/TestServer.java index 0cd6b9d1..471e7539 100644 --- a/test/dorkbox/network/rmi/multiJVM/TestServer.java +++ b/test/dorkbox/network/rmi/multiJVM/TestServer.java @@ -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);