From 33853d44e3497f22b850099a09a43cb3d90720fd Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 9 Jan 2019 18:19:20 +0100 Subject: [PATCH] RMI is now more robust and forgiving --- src/dorkbox/network/rmi/CachedMethod.java | 8 +- src/dorkbox/network/rmi/InvokeMethod.java | 1 + .../network/rmi/InvokeMethodSerializer.java | 31 +- .../network/rmi/RemoteObjectSerializer.java | 15 +- src/dorkbox/network/rmi/RmiBridge.java | 8 +- .../network/rmi/RmiObjectLocalHandler.java | 2 +- .../network/rmi/RmiObjectNetworkHandler.java | 6 +- src/dorkbox/network/rmi/RmiRegistration.java | 6 - .../rmi/RmiRegistrationSerializer.java | 83 +++++ src/dorkbox/network/rmi/RmiUtils.java | 161 ++++----- .../RmiSerializationManager.java | 21 +- .../network/serialization/Serialization.java | 307 ++++++++---------- test/dorkbox/network/rmi/RmiGlobalTest.java | 17 +- .../rmi/RmiObjectIdExhaustionTest.java | 24 +- .../rmi/RmiSendObjectOverrideMethodTest.java | 12 +- .../network/rmi/RmiSendObjectTest.java | 12 +- test/dorkbox/network/rmi/RmiTest.java | 16 +- .../network/rmi/multiJVM/TestClient.java | 13 +- .../network/rmi/multiJVM/TestServer.java | 7 +- 19 files changed, 416 insertions(+), 334 deletions(-) create mode 100644 src/dorkbox/network/rmi/RmiRegistrationSerializer.java diff --git a/src/dorkbox/network/rmi/CachedMethod.java b/src/dorkbox/network/rmi/CachedMethod.java index 88eb537b..2a06c598 100644 --- a/src/dorkbox/network/rmi/CachedMethod.java +++ b/src/dorkbox/network/rmi/CachedMethod.java @@ -53,7 +53,7 @@ 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 boolean overriddenMethod; + public Method overriddenMethod; @SuppressWarnings("rawtypes") public Serializer[] serializers; @@ -62,4 +62,10 @@ class CachedMethod { Object invoke(final Connection connection, Object target, Object[] args) throws Exception { return this.method.invoke(target, args); } + + @Override + public + String toString() { + return "CachedMethod{" + method.getName() + ", methodClassID=" + methodClassID + ", methodIndex=" + methodIndex + '}'; + } } diff --git a/src/dorkbox/network/rmi/InvokeMethod.java b/src/dorkbox/network/rmi/InvokeMethod.java index b69bd8d4..89830c8f 100644 --- a/src/dorkbox/network/rmi/InvokeMethod.java +++ b/src/dorkbox/network/rmi/InvokeMethod.java @@ -42,6 +42,7 @@ public class InvokeMethod implements RmiMessage { public int objectID; // the registered kryo ID for the object + // This class is NOT sent across the wire. We use a custom serializer to manage this. public CachedMethod cachedMethod; public Object[] args; diff --git a/src/dorkbox/network/rmi/InvokeMethodSerializer.java b/src/dorkbox/network/rmi/InvokeMethodSerializer.java index 226e9a16..f0ef8b3d 100644 --- a/src/dorkbox/network/rmi/InvokeMethodSerializer.java +++ b/src/dorkbox/network/rmi/InvokeMethodSerializer.java @@ -34,6 +34,8 @@ */ package dorkbox.network.rmi; +import java.lang.reflect.Method; + import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.KryoException; import com.esotericsoftware.kryo.Serializer; @@ -47,6 +49,7 @@ import dorkbox.network.connection.KryoExtra; */ public class InvokeMethodSerializer extends Serializer { + private static final boolean DEBUG = false; public InvokeMethodSerializer() { @@ -56,9 +59,12 @@ class InvokeMethodSerializer extends Serializer { @SuppressWarnings("rawtypes") public void write(final Kryo kryo, final Output output, final InvokeMethod object) { - // System.err.println(":: objectID " + object.objectID); - // System.err.println(":: methodClassID " + object.cachedMethod.methodClassID); - // System.err.println(":: methodIndex " + object.cachedMethod.methodIndex); + if (DEBUG) { + System.err.println("WRITING"); + System.err.println(":: objectID " + object.objectID); + System.err.println(":: methodClassID " + object.cachedMethod.methodClassID); + System.err.println(":: methodIndex " + object.cachedMethod.methodIndex); + } output.writeInt(object.objectID, true); output.writeInt(object.cachedMethod.methodClassID, true); @@ -90,9 +96,12 @@ class InvokeMethodSerializer extends Serializer { int methodClassID = input.readInt(true); byte methodIndex = input.readByte(); - // System.err.println(":: objectID " + objectID); - // System.err.println(":: methodClassID " + methodClassID); - // System.err.println(":: methodIndex " + methodIndex); + if (DEBUG) { + System.err.println("READING"); + System.err.println(":: objectID " + objectID); + System.err.println(":: methodClassID " + methodClassID); + System.err.println(":: methodIndex " + methodIndex); + } CachedMethod cachedMethod; try { @@ -107,10 +116,13 @@ class InvokeMethodSerializer extends Serializer { Object[] args; Serializer[] serializers = cachedMethod.serializers; + Method method; int argStartIndex; - if (cachedMethod.overriddenMethod) { + if (cachedMethod.overriddenMethod != null) { // did we override our cached method? This is not common. + method = cachedMethod.overriddenMethod; + // this is specifically when we override an interface method, with an implementation method + Connection parameter (@ index 0) argStartIndex = 1; @@ -118,12 +130,15 @@ class InvokeMethodSerializer extends Serializer { args[0] = ((KryoExtra) kryo).connection; } else { + method = cachedMethod.method; argStartIndex = 0; args = new Object[serializers.length]; } - Class[] parameterTypes = cachedMethod.method.getParameterTypes(); + Class[] parameterTypes = method.getParameterTypes(); + + // we don't start at 0 for the arguments, in case we have an overwritten method (in which case, the 1st arg is always "Connection.class") for (int i = 0, n = serializers.length, j = argStartIndex; i < n; i++, j++) { Serializer serializer = serializers[i]; diff --git a/src/dorkbox/network/rmi/RemoteObjectSerializer.java b/src/dorkbox/network/rmi/RemoteObjectSerializer.java index daafad53..743bcd16 100644 --- a/src/dorkbox/network/rmi/RemoteObjectSerializer.java +++ b/src/dorkbox/network/rmi/RemoteObjectSerializer.java @@ -38,6 +38,7 @@ import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.Serializer; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.util.IdentityMap; import dorkbox.network.connection.KryoExtra; @@ -50,8 +51,12 @@ import dorkbox.network.connection.KryoExtra; public class RemoteObjectSerializer extends Serializer { + private final IdentityMap, Class> rmiImplToIface; + public - RemoteObjectSerializer() { + RemoteObjectSerializer(final IdentityMap, Class> rmiImplToIface) { + super(false); + this.rmiImplToIface = rmiImplToIface; } @Override @@ -65,9 +70,13 @@ class RemoteObjectSerializer extends Serializer { @SuppressWarnings({"rawtypes", "unchecked"}) @Override public - T read(Kryo kryo, Input input, Class type) { + T read(Kryo kryo, Input input, Class implementationType) { KryoExtra kryoExtra = (KryoExtra) kryo; int objectID = input.readInt(true); - return (T) kryoExtra.connection.getProxyObject(objectID, type); + + // We have to lookup the iface, since the proxy object requires it + Class iface = rmiImplToIface.get(implementationType); + + return (T) kryoExtra.connection.getProxyObject(objectID, iface); } } diff --git a/src/dorkbox/network/rmi/RmiBridge.java b/src/dorkbox/network/rmi/RmiBridge.java index 46a59a0c..8ad6fcae 100644 --- a/src/dorkbox/network/rmi/RmiBridge.java +++ b/src/dorkbox/network/rmi/RmiBridge.java @@ -54,7 +54,7 @@ import dorkbox.util.collections.LockFreeIntBiMap; * object transformation (because there is no serialization occurring) using a series of weak hashmaps. *

*

- * Objects are {@link RmiSerializationManager#registerRmiInterface(Class)}, and endpoint connections can then {@link + * Objects are {@link RmiSerializationManager#registerRmi(Class, Class)}, and endpoint connections can then {@link * Connection#createRemoteObject(Class, RemoteObjectCallback)} for the registered objects. *

* It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a return value which @@ -236,7 +236,7 @@ class RmiBridge { .append(argString) .append(")"); - if (cachedMethod.overriddenMethod) { + if (cachedMethod.overriddenMethod != null) { // did we override our cached method? This is not common. stringBuilder.append(" [Connection method override]"); } @@ -279,8 +279,8 @@ class RmiBridge { } InvokeMethodResult invokeMethodResult = new InvokeMethodResult(); - invokeMethodResult.objectID = invokeMethod.objectID; - invokeMethodResult.responseID = (byte) responseID; + invokeMethodResult.rmiObjectId = invokeMethod.objectID; + invokeMethodResult.responseId = (byte) responseID; // Do not return non-primitives if transmitReturnVal is false diff --git a/src/dorkbox/network/rmi/RmiObjectLocalHandler.java b/src/dorkbox/network/rmi/RmiObjectLocalHandler.java index f00699be..e20d9a48 100644 --- a/src/dorkbox/network/rmi/RmiObjectLocalHandler.java +++ b/src/dorkbox/network/rmi/RmiObjectLocalHandler.java @@ -101,7 +101,7 @@ class RmiObjectLocalHandler extends RmiObjectHandler { int argStartIndex; - if (cachedMethod.overriddenMethod) { + if (cachedMethod.overriddenMethod != null) { // 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; diff --git a/src/dorkbox/network/rmi/RmiObjectNetworkHandler.java b/src/dorkbox/network/rmi/RmiObjectNetworkHandler.java index 34d31756..d9390b00 100644 --- a/src/dorkbox/network/rmi/RmiObjectNetworkHandler.java +++ b/src/dorkbox/network/rmi/RmiObjectNetworkHandler.java @@ -54,8 +54,12 @@ class RmiObjectNetworkHandler extends RmiObjectHandler { // // CREATE a new ID, and register the ID and new object (must create a new one) in the object maps + // have to lookup the implementation class + Class rmiImpl = connection.getEndPoint().getSerialization().getRmiImpl(interfaceClass); + + // For network connections, the interface class kryo ID == implementation class kryo ID, so they switch automatically. - RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, interfaceClass, callbackId); + RmiRegistration registrationResult = connection.createNewRmiObject(interfaceClass, rmiImpl, callbackId); connection.send(registrationResult); // connection transport is flushed in calling method (don't need to do it here) } diff --git a/src/dorkbox/network/rmi/RmiRegistration.java b/src/dorkbox/network/rmi/RmiRegistration.java index f64e4608..2bc924ea 100644 --- a/src/dorkbox/network/rmi/RmiRegistration.java +++ b/src/dorkbox/network/rmi/RmiRegistration.java @@ -42,12 +42,6 @@ class RmiRegistration implements RmiMessage { */ public int callbackId; - @SuppressWarnings("unused") - private - RmiRegistration() { - // for serialization - } - /** * When requesting a new or existing remote object * SENT FROM "local" -> "remote" diff --git a/src/dorkbox/network/rmi/RmiRegistrationSerializer.java b/src/dorkbox/network/rmi/RmiRegistrationSerializer.java new file mode 100644 index 00000000..e6c82b58 --- /dev/null +++ b/src/dorkbox/network/rmi/RmiRegistrationSerializer.java @@ -0,0 +1,83 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.network.rmi; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +import dorkbox.network.connection.KryoExtra; + +/** + * This is required, because with RMI, it is possible that the IMPL and IFACE can have DIFFERENT class IDs, in which case, the "client" cannot read the correct + * objects (because the IMPL class might not be registered, or that ID might be registered to a different class) + */ +public +class RmiRegistrationSerializer extends Serializer { + + public + RmiRegistrationSerializer() { + } + + @Override + public + void write(Kryo kryo, Output output, RmiRegistration object) { + output.writeBoolean(object.isRequest); + output.writeInt(object.rmiId, true); + output.writeInt(object.callbackId, true); + + int id = kryo.getRegistration(object.interfaceClass).getId(); + output.writeInt(id, true); + + if (object.remoteObject != null) { + KryoExtra kryoExtra = (KryoExtra) kryo; + id = kryoExtra.connection.getRegisteredId(object.remoteObject); + } else { + // can be < 0 or >= RmiBridge.INVALID_RMI (Integer.MAX_VALUE) + id = -1; + } + output.writeInt(id, false); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public + RmiRegistration read(Kryo kryo, Input input, Class implementationType) { + + boolean isRequest = input.readBoolean(); + int rmiId = input.readInt(true); + int callbackId = input.readInt(true); + + int interfaceClassId = input.readInt(true); + int remoteObjectId = input.readInt(false); + + + // // We have to lookup the iface, since the proxy object requires it + Class iface = kryo.getRegistration(interfaceClassId).getType(); + + Object remoteObject = null; + if (remoteObjectId >= 0 && remoteObjectId < RmiBridge.INVALID_RMI) { + KryoExtra kryoExtra = (KryoExtra) kryo; + remoteObject = kryoExtra.connection.getProxyObject(remoteObjectId, iface); + } + + RmiRegistration rmiRegistration = new RmiRegistration(iface, rmiId, callbackId, remoteObject); + rmiRegistration.isRequest = isRequest; + + return rmiRegistration; + } +} diff --git a/src/dorkbox/network/rmi/RmiUtils.java b/src/dorkbox/network/rmi/RmiUtils.java index 31f44283..dc914234 100644 --- a/src/dorkbox/network/rmi/RmiUtils.java +++ b/src/dorkbox/network/rmi/RmiUtils.java @@ -100,18 +100,13 @@ class RmiUtils { /** - * @param logger - * @param kryo - * @param asmEnabled * @param iFace this is never null. * @param impl this is NULL on the rmi "client" side. This is NOT NULL on the "server" side (where the object lives) - * @param classId - * @return */ 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; + MethodAccess ifaceAsmMethodAccess = null; + MethodAccess implAsmMethodAccess = 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. @@ -120,22 +115,24 @@ class RmiUtils { final int size = methods.length; final CachedMethod[] cachedMethods = new CachedMethod[size]; + final Method[] implMethods; + 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); - + implMethods = getMethods(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) { - implMethodAccess = getReflectAsmMethod(logger, impl); + implAsmMethodAccess = getReflectAsmMethod(logger, impl); } + } else { + implMethods = null; } // reflectASM @@ -143,7 +140,7 @@ class RmiUtils { // 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); + ifaceAsmMethodAccess = getReflectAsmMethod(logger, iFace); } for (int i = 0; i < size; i++) { @@ -153,50 +150,44 @@ class RmiUtils { Class[] parameterTypes = method.getParameterTypes(); // copy because they can be overridden - boolean overriddenMethod = false; - MethodAccess tweakMethodAccess = ifaceMethodAccess; + CachedMethod cachedMethod = null; + MethodAccess ifaceORimplMethodAccess = ifaceAsmMethodAccess; + // reflectAsm doesn't like "Object" class methods + boolean canUseAsm = asmEnabled && method.getDeclaringClass() != Object.class; + + Method overwrittenMethod = null; // 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 (implMethods != null) { + overwrittenMethod = getOverwriteMethodWithConnectionParam(implMethods, method); + if (overwrittenMethod != null) { + if (logger.isTraceEnabled()) { + logger.trace("Overridden method: {}.{}", impl, method.getName()); + } - if (logger.isTraceEnabled()) { - logger.trace("Overridden method: {}.{}", impl, method.getName()); + // still might be null! + ifaceORimplMethodAccess = implAsmMethodAccess; } } - CachedMethod cachedMethod = null; - // reflectAsm doesn't like "Object" class methods... - if (tweakMethodAccess != null && method.getDeclaringClass() != Object.class) { + if (canUseAsm) { try { - final int index = tweakMethodAccess.getIndex(method.getName(), parameterTypes); + int index; + if (overwrittenMethod != null) { + // have to take into account the overwritten method's first parameter will ALWAYS be "Connection" + index = ifaceORimplMethodAccess.getIndex(method.getName(), overwrittenMethod.getParameterTypes()); + } else { + index = ifaceORimplMethodAccess.getIndex(method.getName(), parameterTypes); + } + AsmCachedMethod asmCachedMethod = new AsmCachedMethod(); asmCachedMethod.methodAccessIndex = index; - asmCachedMethod.methodAccess = tweakMethodAccess; + asmCachedMethod.methodAccess = ifaceORimplMethodAccess; asmCachedMethod.name = method.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 {}.{} (using java reflection instead)", declaringClass, method.getName(), e); @@ -207,10 +198,11 @@ class RmiUtils { cachedMethod = new CachedMethod(); } - cachedMethod.overriddenMethod = overriddenMethod; + cachedMethod.methodClassID = classId; - // we ALSO have to setup "normal" reflection access to these methods + // this MIGHT be null, but if it is not, this is the method we will invoke INSTEAD of the "normal" method + cachedMethod.overriddenMethod = overwrittenMethod; cachedMethod.method = method; cachedMethod.methodIndex = i; @@ -229,9 +221,9 @@ class RmiUtils { 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) + * NOTE: does not null check * * @param implMethods methods from the implementation * @param origMethods methods from the interface @@ -241,47 +233,62 @@ class RmiUtils { 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; + Method overwriteMethodsWithConnectionParam = getOverwriteMethodWithConnectionParam(implMethods, origMethod); + if (overwriteMethodsWithConnectionParam != null) { + origMethods[i] = overwriteMethodsWithConnectionParam; + } + } + } - for (Method implMethod : implMethods) { - String implName = implMethod.getName(); - Class[] implTypes = implMethod.getParameterTypes(); - int implLength = implTypes.length; + /** + * 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) + * NOTE: does not null check + * + * @param implMethods methods from the implementation + * @param origMethod original method from the interface + */ + private static + Method getOverwriteMethodWithConnectionParam(final Method[] implMethods, final Method origMethod) { + String origName = origMethod.getName(); + Class[] origTypes = origMethod.getParameterTypes(); + int origLength = origTypes.length + 1; - if (origLength != implLength || !(origName.equals(implName))) { - continue; + 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 + return implMethod; } - - // 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; + 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) { + return implMethod; + } } } } + + return null; } /** diff --git a/src/dorkbox/network/serialization/RmiSerializationManager.java b/src/dorkbox/network/serialization/RmiSerializationManager.java index 097fa78b..0210ae6d 100644 --- a/src/dorkbox/network/serialization/RmiSerializationManager.java +++ b/src/dorkbox/network/serialization/RmiSerializationManager.java @@ -103,30 +103,37 @@ interface RmiSerializationManager extends SerializationManager { Class getRmiImpl(Class iFace); /** - * Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI. + * Enable this endpoint (a "client") to access methods and create objects (RMI) on a "remote server". This is NOT bi-directional, and the "remote server" + * cannot access or create remote objects on this endpoint. + *

+ * This is the same as calling {@link RmiSerializationManager#registerRmi(Class, Class)} with a null parameter for the implementation class. + *

+ * There is additional overhead to using RMI. *

* Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a * return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written. *

* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter. */ - RmiSerializationManager registerRmiInterface(Class ifaceClass); + RmiSerializationManager registerRmi(Class ifaceClass); /** - * Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI. + * Enable a "remote client" to access methods and create objects (RMI) for this endpoint. This is NOT bi-directional, and this endpoint cannot access or \ + * create remote objects on the "remote client". + *

+ * Calling this method with a null parameter for the implementation class is the same as calling {@link RmiSerializationManager#registerRmi(Class)} + *

+ * There is additional overhead to using RMI. *

* Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a * return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written. *

* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter. *

- * This method overrides the interface -> implementation. This is so incoming proxy objects will get auto-changed into their correct - * implementation type, so this side of the connection knows what to do with the proxy object. - *

* * @throws IllegalArgumentException if the iface/impl have previously been overridden */ - RmiSerializationManager registerRmiImplementation(Class ifaceClass, Class implClass); + RmiSerializationManager registerRmi(Class ifaceClass, Class implClass); /** * Gets the cached methods for the specified class ID diff --git a/src/dorkbox/network/serialization/Serialization.java b/src/dorkbox/network/serialization/Serialization.java index 5efc0839..a3364173 100644 --- a/src/dorkbox/network/serialization/Serialization.java +++ b/src/dorkbox/network/serialization/Serialization.java @@ -31,7 +31,6 @@ import org.slf4j.Logger; import com.esotericsoftware.kryo.ClassResolver; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.KryoException; -import com.esotericsoftware.kryo.Registration; import com.esotericsoftware.kryo.Serializer; import com.esotericsoftware.kryo.factories.ReflectionSerializerFactory; import com.esotericsoftware.kryo.factories.SerializerFactory; @@ -55,6 +54,7 @@ import dorkbox.network.rmi.InvokeMethodResult; import dorkbox.network.rmi.InvokeMethodSerializer; import dorkbox.network.rmi.RemoteObjectSerializer; import dorkbox.network.rmi.RmiRegistration; +import dorkbox.network.rmi.RmiRegistrationSerializer; import dorkbox.network.rmi.RmiUtils; import dorkbox.objectPool.ObjectPool; import dorkbox.objectPool.PoolableObject; @@ -127,23 +127,12 @@ class Serialization implements CryptoSerializationMa } } - private static - class RemoteIfaceClass { - private final Class ifaceClass; - - RemoteIfaceClass(final Class ifaceClass) { - this.ifaceClass = ifaceClass; - } - } - - - private static - class RemoteImplClass { + class RmiClassSerializer { private final Class ifaceClass; private final Class implClass; - RemoteImplClass(final Class ifaceClass, final Class implClass) { + RmiClassSerializer(final Class ifaceClass, final Class implClass) { this.ifaceClass = ifaceClass; this.implClass = implClass; } @@ -171,8 +160,8 @@ class Serialization implements CryptoSerializationMa *

* 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. + * @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmi(Class, Class)} and + * {@link #registerRmi(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. @@ -263,10 +252,7 @@ class Serialization implements CryptoSerializationMa // 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> rmiKryoIdToImpl = new IntMap>(); - private final IntMap> rmiKryoIdToIface = new IntMap>(); + private RmiRegistrationSerializer rmiRegistrationSerializer; private final IdentityMap, Class> rmiIfaceToImpl = new IdentityMap, Class>(); private final IdentityMap, Class> rmiImplToIface = new IdentityMap, Class>(); @@ -296,8 +282,8 @@ class Serialization implements CryptoSerializationMa * 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. + * @param implementationRequired If true, interfaces are not permitted to be registered, outside of the {@link #registerRmi(Class, Class)} and + * {@link #registerRmi(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. @@ -325,6 +311,8 @@ class Serialization implements CryptoSerializationMa // we HAVE to pre-allocate the KRYOs KryoExtra kryo = new KryoExtra(Serialization.this); + boolean traceEnabled = logger.isTraceEnabled(); + kryo.getFieldSerializerConfig() .setUseAsm(useAsm); kryo.setRegistrationRequired(registrationRequired); @@ -333,13 +321,13 @@ class Serialization implements CryptoSerializationMa if (usesRmi) { kryo.register(Class.class); - kryo.register(RmiRegistration.class); + kryo.register(RmiRegistration.class, rmiRegistrationSerializer); kryo.register(InvokeMethod.class, methodSerializer); kryo.register(Object[].class); // This has to be for each kryo instance! InvocationResultSerializer resultSerializer = new InvocationResultSerializer(kryo); - resultSerializer.removeField("objectID"); + resultSerializer.removeField("rmiObjectId"); kryo.register(InvokeMethodResult.class, resultSerializer); kryo.register(InvocationHandler.class, invocationSerializer); @@ -349,63 +337,56 @@ class Serialization implements CryptoSerializationMa for (Object clazz : classesToRegister) { if (clazz instanceof Class) { Class aClass = (Class) clazz; - kryo.register(aClass); + int id = kryo.register(aClass).getId(); + + if (traceEnabled) { + logger.trace("Registering {} -> {}", id, aClass.getName()); + } } else if (clazz instanceof ClassSerializer) { ClassSerializer classSerializer = (ClassSerializer) clazz; - kryo.register(classSerializer.clazz, classSerializer.serializer); + int id = kryo.register(classSerializer.clazz, classSerializer.serializer).getId(); + + if (traceEnabled) { + logger.trace("Registering {} -> {} using {}", id, classSerializer.clazz.getName(), classSerializer.serializer.getClass().getName()); + } } else if (clazz instanceof ClassSerializer1) { ClassSerializer1 classSerializer = (ClassSerializer1) clazz; kryo.register(classSerializer.clazz, classSerializer.id); + + if (traceEnabled) { + logger.trace("Registering {} (specified) -> {}", classSerializer.id, classSerializer.clazz); + } } else if (clazz instanceof ClassSerializer2) { ClassSerializer2 classSerializer = (ClassSerializer2) clazz; kryo.register(classSerializer.clazz, classSerializer.serializer, classSerializer.id); - } - else 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; - // 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" - // "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; - - // check to see if the interface is already registered. If so, we override it with the implementation class - EditableDefaultClassResolver classResolver = (EditableDefaultClassResolver) kryo.getClassResolver(); - Registration registration = classResolver.getRegistration(remoteImplClass.ifaceClass); - if (registration != null) { - // override it with the implementation - int oldId = registration.getId(); - id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer, oldId).getId(); - - // delete the old one (since it shouldn't be stored anywhere) - classResolver.deleteRegistrationType(remoteImplClass.ifaceClass); + if (traceEnabled) { + logger.trace("Registering {} (specified) -> {} using {}", classSerializer.id, classSerializer.clazz.getName(), classSerializer.serializer.getClass().getName()); } - else { - // we have a new registration. + } + else if (clazz instanceof RmiClassSerializer) { + // "client" always sends kryo-ID (of iface) with RMI-ID (or command to create/get one) + // "server" always responds with kryo-ID (of impl) with RMI-ID of remote object. When creating proxy object, "client" looks-up kryo-id of iface from impl - // registers the implementation, so that when it is WRITTEN, it becomes a "magic" proxy object - id = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId(); + RmiClassSerializer remoteImplClass = (RmiClassSerializer) clazz; + + int id = kryo.register(remoteImplClass.ifaceClass, remoteObjectSerializer).getId(); + if (traceEnabled) { + logger.trace("Registering {} -> RMI interface : {}", id, remoteImplClass.ifaceClass); } - // 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. - rmiKryoIdToImpl.put(id, remoteImplClass.implClass); // the "server" translates the ID back to the impl on kryo read - rmiKryoIdToIface.put(id, remoteImplClass.ifaceClass); // the "server" translates the ID to the iface on kryo write + // we only want to use an implementation class IFF we plan on having one, otherwise ignore it + if (remoteImplClass.implClass != null) { + // NOTE: as a "server" we must have the "remote object" serializer write out the INTERFACE CLASS ID so that the "client" (if it DOES NOT have an impl) + // can read this as the correct class! + int id2 = kryo.register(remoteImplClass.implClass, remoteObjectSerializer).getId(); + if (traceEnabled) { + logger.trace("Registering {} -> RMI implementation: {}", id2, remoteImplClass.implClass); + } + } } } @@ -568,7 +549,8 @@ class Serialization implements CryptoSerializationMa methodSerializer = new InvokeMethodSerializer(); invocationSerializer = new InvocationHandlerSerializer(logger); - remoteObjectSerializer = new RemoteObjectSerializer(); + remoteObjectSerializer = new RemoteObjectSerializer(rmiImplToIface); + rmiRegistrationSerializer = new RmiRegistrationSerializer(); methodCache = new IntMap(); return true; @@ -604,7 +586,12 @@ class Serialization implements CryptoSerializationMa } /** - * Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI. + * Enable this endpoint (a "client") to access methods and create objects (RMI) on a "remote server". This is NOT bi-directional, and the "remote server" + * cannot access or create remote objects on this endpoint. + *

+ * This is the same as calling {@link RmiSerializationManager#registerRmi(Class, Class)} with a null parameter for the implementation class. + *

+ * There is additional overhead to using RMI. *

* Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a * return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written. @@ -613,67 +600,120 @@ class Serialization implements CryptoSerializationMa */ @Override public synchronized - RmiSerializationManager registerRmiInterface(Class ifaceClass) { - if (initialized) { - logger.warn("Serialization manager already initialized. Ignoring duplicate registerRemote(Class) call."); - return this; - } - - else if (!ifaceClass.isInterface()) { - throw new IllegalArgumentException("Cannot register an implementation for RMI access. It must be an interface."); - } - - usesRmi = true; - classesToRegister.add(new RemoteIfaceClass(ifaceClass)); - - return this; + RmiSerializationManager registerRmi(Class ifaceClass) { + return registerRmi(ifaceClass, null); } /** - * Enable remote method invocation (RMI) for this connection. There is additional overhead to using RMI. + * Enable a "remote client" to access methods and create objects (RMI) for this endpoint. This is NOT bi-directional, and this endpoint cannot access or \ + * create remote objects on the "remote client". + *

+ * Calling this method with a null parameter for the implementation class is the same as calling {@link RmiSerializationManager#registerRmi(Class)} + *

+ * There is additional overhead to using RMI. *

* Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a * return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written. *

* If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter. *

- * This method overrides the interface -> implementation. This is so incoming proxy objects will get auto-changed into their correct - * implementation type, so this side of the connection knows what to do with the proxy object. - *

* * @throws IllegalArgumentException if the iface/impl have previously been overridden */ @Override public synchronized - RmiSerializationManager registerRmiImplementation(Class ifaceClass, Class implClass) { + RmiSerializationManager registerRmi(Class ifaceClass, Class implClass) { if (initialized) { - logger.warn("Serialization manager already initialized. Ignoring duplicate registerRemote(Class, Class) call."); + logger.warn("Serialization manager already initialized. Ignoring duplicate registerRmiImplementation(Class, Class) call."); return this; } if (!ifaceClass.isInterface()) { throw new IllegalArgumentException("Cannot register an implementation for RMI access. It must be an interface."); } - if (implClass.isInterface()) { + if (implClass != null && implClass.isInterface()) { throw new IllegalArgumentException("Cannot register an interface for RMI implementations. It must be an implementation."); } usesRmi = true; - classesToRegister.add(new RemoteImplClass(ifaceClass, implClass)); + classesToRegister.add(new RmiClassSerializer(ifaceClass, implClass)); - // this MUST BE UNIQUE otherwise unexpected and BAD things can happen. - Class a = this.rmiIfaceToImpl.put(ifaceClass, implClass); - Class b = this.rmiImplToIface.put(implClass, ifaceClass); + // we only want to assign an implementation class IFF we plan on having one, otherwise ignore it + if (implClass != null) { + // rmiIfaceToImpl tells the "server" how to create a (requested) remote object + // this MUST BE UNIQUE otherwise unexpected and BAD things can happen. + Class a = this.rmiIfaceToImpl.put(ifaceClass, implClass); + Class b = this.rmiImplToIface.put(implClass, ifaceClass); - if (a != null || b != null) { - throw new IllegalArgumentException("Unable to override interface (" + ifaceClass + ") and implementation (" + implClass + ") " + - "because they have already been overridden by something else. It is critical that they are" + - " both unique per JVM"); + if (a != null || b != null) { + throw new IllegalArgumentException("Unable to override interface (" + ifaceClass + ") and implementation (" + implClass + ") " + + "because they have already been overridden by something else. It is critical that they are" + + " both unique per JVM"); + } } return this; } + /** + * 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(final Logger wireReadLogger, final Logger wireWriteLogger) { + this.wireReadLogger = wireReadLogger; + this.wireWriteLogger = wireWriteLogger; + + 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 = kryoPool.take(); + + 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 RmiClassSerializer) { + // 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. + RmiClassSerializer remoteImplClass = (RmiClassSerializer) clazz; + int id = classResolver.getRegistration(remoteImplClass.ifaceClass) + .getId(); + + CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(Serialization.logger, kryo, useAsm, + remoteImplClass.ifaceClass, + remoteImplClass.implClass, + id); + methodCache.put(id, cachedMethods); + } + } + + } finally { + if (kryo != null) { + kryoPool.put(kryo); + } + } + } + + @Override + public + CachedMethod[] getMethods(final int classId) { + return methodCache.get(classId); + } + + @Override + public synchronized + boolean initialized() { + return initialized; + } + /** * Waits until a kryo is available to write, using CAS operations to prevent having to synchronize. *

@@ -780,81 +820,6 @@ class Serialization implements CryptoSerializationMa } } - /** - * 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(final Logger wireReadLogger, final Logger wireWriteLogger) { - this.wireReadLogger = wireReadLogger; - this.wireWriteLogger = wireWriteLogger; - - 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 = kryoPool.take(); - - 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(Serialization.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(Serialization.logger, kryo, useAsm, - remoteImplClass.ifaceClass, - remoteImplClass.implClass, - id); - methodCache.put(id, cachedMethods); - } - } - - } finally { - if (kryo != null) { - kryoPool.put(kryo); - } - } - } - - @Override - public - CachedMethod[] getMethods(final int classId) { - return methodCache.get(classId); - } - - @Override - public synchronized - boolean initialized() { - return initialized; - } - /** * Waits until a kryo is available to write, using CAS operations to prevent having to synchronize. *

diff --git a/test/dorkbox/network/rmi/RmiGlobalTest.java b/test/dorkbox/network/rmi/RmiGlobalTest.java index 495a9805..f84d4636 100644 --- a/test/dorkbox/network/rmi/RmiGlobalTest.java +++ b/test/dorkbox/network/rmi/RmiGlobalTest.java @@ -222,11 +222,8 @@ class RmiGlobalTest extends BaseTest { configuration.serialization = Serialization.DEFAULT(); register(configuration.serialization); - // 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) - NOTICE: none of the super classes/interfaces are registered! - configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class); + // NOTICE: none of the super classes/interfaces are registered! + configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class); final Server server = new Server(configuration); server.setIdleTimeout(0); @@ -286,11 +283,8 @@ class RmiGlobalTest extends BaseTest { configuration.serialization = Serialization.DEFAULT(); register(configuration.serialization); - // for Server -> Client RMI (ID: CLIENT_GLOBAL_OBJECT_ID) - configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class); - - // for Client -> Server RMI (ID: SERVER_GLOBAL_OBJECT_ID) - configuration.serialization.registerRmiInterface(TestCow.class); + // NOTICE: none of the super classes/interfaces are registered! + configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class); final Client client = new Client(configuration); @@ -340,7 +334,8 @@ class RmiGlobalTest extends BaseTest { } }); - client.connect(5000); + // client.connect(5000); + client.connect(0); waitForThreads(); } diff --git a/test/dorkbox/network/rmi/RmiObjectIdExhaustionTest.java b/test/dorkbox/network/rmi/RmiObjectIdExhaustionTest.java index 291b422a..ddd5d25a 100644 --- a/test/dorkbox/network/rmi/RmiObjectIdExhaustionTest.java +++ b/test/dorkbox/network/rmi/RmiObjectIdExhaustionTest.java @@ -41,7 +41,7 @@ class RmiObjectIdExhaustionTest extends BaseTest { manager.register(UnsupportedOperationException.class); } - // @Test // NOTE: change final to test! + // @Test // NOTE: remove final from RmiBridge.INVALID_RMI to test! public void rmiNetwork() throws SecurityException, IOException, InterruptedException { rmi(new Config() { @@ -60,7 +60,7 @@ class RmiObjectIdExhaustionTest extends BaseTest { Thread.sleep(2000L); } - // @Test // NOTE: change final to test! + // @Test // NOTE: remove final from RmiBridge.INVALID_RMI to test! public void rmiLocal() throws SecurityException, IOException, InterruptedException { rmi(new Config() { @@ -80,7 +80,7 @@ class RmiObjectIdExhaustionTest extends BaseTest { public void rmi(final Config config) throws SecurityException, IOException { - // NOTE: change final to test! + // NOTE: remove final from RmiBridge.INVALID_RMI to test! // RmiBridge.INVALID_RMI = 4; Configuration configuration = new Configuration(); @@ -88,13 +88,7 @@ class RmiObjectIdExhaustionTest extends BaseTest { configuration.serialization = Serialization.DEFAULT(); register(configuration.serialization); - - // for Client -> Server RMI (ID 1) - configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class); - - // for Server -> Client RMI (ID 2) - configuration.serialization.registerRmiInterface(TestCow.class); - + configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class); final Server server = new Server(configuration); server.setIdleTimeout(0); @@ -102,18 +96,16 @@ class RmiObjectIdExhaustionTest extends BaseTest { addEndPoint(server); server.bind(false); + + + // ---- configuration = new Configuration(); config.apply(configuration); configuration.serialization = Serialization.DEFAULT(); register(configuration.serialization); - - // for Client -> Server RMI (ID 1) - configuration.serialization.registerRmiInterface(TestCow.class); - - // for Server -> Client RMI (ID 2) - configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class); + configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class); final Client client = new Client(configuration); diff --git a/test/dorkbox/network/rmi/RmiSendObjectOverrideMethodTest.java b/test/dorkbox/network/rmi/RmiSendObjectOverrideMethodTest.java index d98d0135..c188512f 100644 --- a/test/dorkbox/network/rmi/RmiSendObjectOverrideMethodTest.java +++ b/test/dorkbox/network/rmi/RmiSendObjectOverrideMethodTest.java @@ -106,9 +106,9 @@ class RmiSendObjectOverrideMethodTest extends BaseTest { final boolean isUDP = configuration.udpPort > 0; - configuration.serialization = Serialization.DEFAULT(); - configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class); - configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class); + configuration.serialization = Serialization.DEFAULT(true, true, false, null); + configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class); + configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class); Server server = new Server(configuration); server.setIdleTimeout(0); @@ -138,9 +138,9 @@ class RmiSendObjectOverrideMethodTest extends BaseTest { configuration = new Configuration(); config.apply(configuration); - configuration.serialization = Serialization.DEFAULT(); - configuration.serialization.registerRmiInterface(TestObject.class); - configuration.serialization.registerRmiInterface(OtherObject.class); + configuration.serialization = Serialization.DEFAULT(true, true, false, null); + configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class); + configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class); Client client = new Client(configuration); client.setIdleTimeout(0); diff --git a/test/dorkbox/network/rmi/RmiSendObjectTest.java b/test/dorkbox/network/rmi/RmiSendObjectTest.java index 59d0fbc2..35043fba 100644 --- a/test/dorkbox/network/rmi/RmiSendObjectTest.java +++ b/test/dorkbox/network/rmi/RmiSendObjectTest.java @@ -90,9 +90,9 @@ class RmiSendObjectTest extends BaseTest { Configuration configuration = new Configuration(); config.apply(configuration); - configuration.serialization = Serialization.DEFAULT(); - configuration.serialization.registerRmiImplementation(TestObject.class, TestObjectImpl.class); - configuration.serialization.registerRmiImplementation(OtherObject.class, OtherObjectImpl.class); + configuration.serialization = Serialization.DEFAULT(true, true, false, null); + configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class); + configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class); @@ -124,9 +124,9 @@ class RmiSendObjectTest extends BaseTest { configuration = new Configuration(); config.apply(configuration); - configuration.serialization = Serialization.DEFAULT(); - configuration.serialization.registerRmiInterface(TestObject.class); - configuration.serialization.registerRmiInterface(OtherObject.class); + configuration.serialization = Serialization.DEFAULT(true, true, false, null); + configuration.serialization.registerRmi(TestObject.class, TestObjectImpl.class); + configuration.serialization.registerRmi(OtherObject.class, OtherObjectImpl.class); Client client = new Client(configuration); diff --git a/test/dorkbox/network/rmi/RmiTest.java b/test/dorkbox/network/rmi/RmiTest.java index 274dff82..2cd19927 100644 --- a/test/dorkbox/network/rmi/RmiTest.java +++ b/test/dorkbox/network/rmi/RmiTest.java @@ -171,6 +171,7 @@ class RmiTest extends BaseTest { public static void register(dorkbox.network.serialization.CryptoSerializationManager manager) { manager.register(Object.class); // Needed for Object#toString, hashCode, etc. + manager.register(TestCow.class); manager.register(MessageWithTestCow.class); manager.register(UnsupportedOperationException.class); } @@ -217,14 +218,14 @@ class RmiTest extends BaseTest { Configuration configuration = new Configuration(); config.apply(configuration); - configuration.serialization = Serialization.DEFAULT(); + configuration.serialization = Serialization.DEFAULT(true, true, false, null); register(configuration.serialization); // for Client -> Server RMI (ID 1) - configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class); + configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class); // for Server -> Client RMI (ID 2) - configuration.serialization.registerRmiInterface(TestCow.class); + // configuration.serialization.registerRmiInterface(TestCow.class); final Server server = new Server(configuration); @@ -274,14 +275,15 @@ class RmiTest extends BaseTest { configuration = new Configuration(); config.apply(configuration); - configuration.serialization = Serialization.DEFAULT(); + configuration.serialization = Serialization.DEFAULT(true, true, false, null); register(configuration.serialization); // for Client -> Server RMI (ID 1) - configuration.serialization.registerRmiInterface(TestCow.class); + // the 'TestCow' object lives on the 'Server'. The 'Client' accesses remote methods on it + // configuration.serialization.registerRmiInterface(TestCow.class); - // for Server -> Client RMI (ID 2) - configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class); + // // for Server -> Client RMI (ID 2) + configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class); final Client client = new Client(configuration); diff --git a/test/dorkbox/network/rmi/multiJVM/TestClient.java b/test/dorkbox/network/rmi/multiJVM/TestClient.java index f1bb7bda..274bef7a 100644 --- a/test/dorkbox/network/rmi/multiJVM/TestClient.java +++ b/test/dorkbox/network/rmi/multiJVM/TestClient.java @@ -37,8 +37,8 @@ class TestClient // rootLogger.setLevel(Level.OFF); - rootLogger.setLevel(Level.DEBUG); - // rootLogger.setLevel(Level.TRACE); + // rootLogger.setLevel(Level.DEBUG); + rootLogger.setLevel(Level.TRACE); // rootLogger.setLevel(Level.ALL); @@ -78,14 +78,15 @@ class TestClient configuration.udpPort = 2001; configuration.host = "localhost"; - configuration.serialization = Serialization.DEFAULT(); + configuration.serialization = Serialization.DEFAULT(true, true, false, null); RmiTest.register(configuration.serialization); - configuration.serialization.registerRmiInterface(TestCow.class); + configuration.serialization.registerRmi(TestCow.class); try { final Client client = new Client(configuration); - // client.setIdleTimeout(0); + client.disableRemoteKeyValidation(); + client.setIdleTimeout(0); client.listeners() .add(new dorkbox.network.connection.Listener.OnConnected() { @@ -121,7 +122,7 @@ class TestClient } }); - client.connect(3330); + client.connect(0); client.waitForShutdown(); } catch (Exception e) { diff --git a/test/dorkbox/network/rmi/multiJVM/TestServer.java b/test/dorkbox/network/rmi/multiJVM/TestServer.java index 1d89fbfd..b9bbf2dd 100644 --- a/test/dorkbox/network/rmi/multiJVM/TestServer.java +++ b/test/dorkbox/network/rmi/multiJVM/TestServer.java @@ -21,17 +21,18 @@ class TestServer configuration.tcpPort = 2000; configuration.udpPort = 2001; - configuration.serialization = Serialization.DEFAULT(); + configuration.serialization = Serialization.DEFAULT(true, true, false, null); RmiTest.register(configuration.serialization); - configuration.serialization.registerRmiImplementation(TestCow.class, TestCowImpl.class); + configuration.serialization.registerRmi(TestCow.class, TestCowImpl.class); Server server = null; try { server = new Server(configuration); + server.disableRemoteKeyValidation(); } catch (SecurityException e) { e.printStackTrace(); } - // server.setIdleTimeout(0); + server.setIdleTimeout(0); server.bind(true); }}