You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@teaclave.apache.org by sh...@apache.org on 2022/11/11 05:17:47 UTC

[incubator-teaclave-java-tee-sdk] 07/48: [Enc] Enclave invocation framework

This is an automated email from the ASF dual-hosted git repository.

shaojunwang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-teaclave-java-tee-sdk.git

commit 9ed2b341ea74544723197f66029859e764f051d5
Author: cengfeng.lzy <ce...@alibaba-inc.com>
AuthorDate: Mon Mar 14 21:03:54 2022 +0800

    [Enc] Enclave invocation framework
    
    Summary: Create new invocation framework in Enclave side.
    
    Test Plan: all Enclave tests pass
    
    Reviewers: lei.yul, jeffery.wsj, sanhong.lsh
    
    Issue: https://aone.alibaba-inc.com/task/40112920
    
    CR:
    https://code.aone.alibaba-inc.com/java-tee/JavaEnclave/codereview/8038632
---
 .../common/EnclaveInvocationContext.java           |   6 +-
 .../exception/ConfidentialComputingException.java  |   5 +-
 sdk/enclave/pom.xml                                |  33 ++++
 .../enclave/framework/EnclaveContext.java          |  89 +++++++++++
 .../enclave/framework/EnclaveMethodInvoker.java    |  15 ++
 .../enclave/framework/LoadServiceInvoker.java      |  25 +++
 .../enclave/framework/ServiceMethodInvoker.java    | 115 ++++++++++++++
 .../enclave/framework/UnloadServiceInvoker.java    |  24 +++
 .../enclave/EnclaveTestHelper.java                 |  12 ++
 .../framework/ServiceMethodInvokerTest.java        | 170 +++++++++++++++++++++
 .../enclave/framework/ServiceOperationTest.java    |  49 ++++++
 .../enclave/testservice/IntegerMath.java           |   8 +
 .../enclave/testservice/MathService.java           |  13 ++
 .../enclave/testservice/NumericMath.java           |  28 ++++
 .../enclave/testservice/Point.java                 |  11 ++
 .../enclave/testservice/PointMath.java             |  18 +++
 ...entialcomputing.enclave.testservice.MathService |   3 +
 17 files changed, 620 insertions(+), 4 deletions(-)

diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationContext.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationContext.java
index 9a1f3be..c0325a0 100644
--- a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationContext.java
+++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationContext.java
@@ -3,9 +3,9 @@ package com.alibaba.confidentialcomputing.common;
 import java.io.Serializable;
 
 /**
- * EnclaveInvocationInputMeta stores a method's necessary information for reflection
- * call, include object's unique instanceIdentity、interface name、class name、method
- * signature name, and its parameters.
+ * This class stores a method's necessary information for reflection
+ * call, including the service instance's unique instanceIdentity, interface name, class name,
+ * method name and its parameters.
  */
 public final class EnclaveInvocationContext implements Serializable {
     private static final long serialVersionUID = 6878585714134748604L;
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/exception/ConfidentialComputingException.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/exception/ConfidentialComputingException.java
similarity index 85%
rename from sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/exception/ConfidentialComputingException.java
rename to sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/exception/ConfidentialComputingException.java
index f525dcf..0887a33 100644
--- a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/exception/ConfidentialComputingException.java
+++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/exception/ConfidentialComputingException.java
@@ -1,4 +1,4 @@
-package com.alibaba.confidentialcomputing.enclave.exception;
+package com.alibaba.confidentialcomputing.common.exception;
 
 /**
  * ConfidentialComputingException {@link ConfidentialComputingException} is base exception in
@@ -7,6 +7,9 @@ package com.alibaba.confidentialcomputing.enclave.exception;
  * Programmers need to handle ConfidentialComputingException seriously.
  */
 public class ConfidentialComputingException extends Exception {
+
+    private static final long serialVersionUID = 5964126736764332957L;
+
     /**
      * @param info exception information.
      */
diff --git a/sdk/enclave/pom.xml b/sdk/enclave/pom.xml
index 9b47431..e533f1f 100644
--- a/sdk/enclave/pom.xml
+++ b/sdk/enclave/pom.xml
@@ -14,6 +14,21 @@
     <url></url>
     <build>
         <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>11</source>
+                    <target>11</target>
+                    <compilerArgs>
+                        <arg>--add-modules</arg>
+                        <arg>jdk.internal.vm.ci</arg>
+                        <arg>--add-exports</arg>
+                        <arg>jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
@@ -46,7 +61,25 @@
             </plugin>
         </plugins>
     </build>
+    <properties>
+        <graal.version>enclave-22.0.0</graal.version>
+    </properties>
     <dependencies>
+        <dependency>
+            <groupId>org.graalvm.sdk</groupId>
+            <artifactId>graal-sdk</artifactId>
+            <version>${graal.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.graalvm.nativeimage</groupId>
+            <artifactId>svm</artifactId>
+            <version>${graal.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.graalvm.nativeimage</groupId>
+            <artifactId>pointsto</artifactId>
+            <version>${graal.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.jacoco</groupId>
             <artifactId>jacoco-maven-plugin</artifactId>
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/EnclaveContext.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/EnclaveContext.java
new file mode 100644
index 0000000..43a1919
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/EnclaveContext.java
@@ -0,0 +1,89 @@
+package com.alibaba.confidentialcomputing.enclave.framework;
+
+import com.alibaba.confidentialcomputing.common.ServiceHandler;
+import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This class maintains the enclave context, i.e. the cached service instances.
+ */
+final class EnclaveContext {
+
+    private static final EnclaveContext instance = new EnclaveContext();
+
+    public static EnclaveContext getInstance() {
+        return instance;
+    }
+
+    private final Map<String, Object> cachedServiceInstances;
+
+    private final AtomicLong serviceCounter;
+
+    private EnclaveContext() {
+        cachedServiceInstances = new ConcurrentHashMap<>();
+        serviceCounter = new AtomicLong(0);
+    }
+
+    public Object removeCache(String key) {
+        return cachedServiceInstances.remove(key);
+    }
+
+    public void clearCache() {
+        cachedServiceInstances.clear();
+    }
+
+    public int servicesSize() {
+        return cachedServiceInstances.size();
+    }
+
+    /**
+     * Lookup the service instance with the given identity checksum, service name and implementation class name from
+     * cached map.
+     *
+     * @param instanceIdentity        service instance identity checksum
+     * @param serviceName             the name of the service
+     * @param implementationClassName the implementation class name
+     * @return cached service instance
+     */
+    public Object lookupServiceInstance(String instanceIdentity, String serviceName, String implementationClassName) throws ConfidentialComputingException {
+        if (!cachedServiceInstances.containsKey(instanceIdentity)) {
+            throw new ConfidentialComputingException(String.format("No stored service %s with identity %s", serviceName, instanceIdentity));
+        }
+        Object serviceInstance = cachedServiceInstances.get(instanceIdentity);
+        if (serviceInstance != null) {
+            Class<?> serviceInstanceClass = serviceInstance.getClass();
+            try {
+                Class<?> interfaceClass = Class.forName(serviceName);
+                if (!interfaceClass.isAssignableFrom(serviceInstanceClass)) {
+                    throw new ConfidentialComputingException(String.format("Cached service instance with identity %s doesn't implement the interface %s.",
+                            instanceIdentity, serviceName));
+                }
+            } catch (ClassNotFoundException e) {
+                throw new ConfidentialComputingException(String.format("Can't find the interface class %s.",
+                        serviceName), e);
+            }
+
+            String cachedImplementationClassName = serviceInstanceClass.getName();
+            if (!cachedImplementationClassName.equals(implementationClassName)) {
+                throw new ConfidentialComputingException(String.format("Implementation class does not match, expected is %s, but found is %s.", implementationClassName, cachedImplementationClassName));
+            }
+        }
+        return serviceInstance;
+    }
+
+    public ServiceHandler[] loadService(Class<?> service) {
+        List<ServiceHandler> serviceHandlerList = new ArrayList<>();
+        for (Object currentServiceInstance : ServiceLoader.load(service)) {
+            String identity = String.valueOf(serviceCounter.addAndGet(1));
+            cachedServiceInstances.put(identity, currentServiceInstance);
+            serviceHandlerList.add(new ServiceHandler(service.getName(), currentServiceInstance.getClass().getName(), identity));
+        }
+        return serviceHandlerList.toArray(new ServiceHandler[0]);
+    }
+}
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/EnclaveMethodInvoker.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/EnclaveMethodInvoker.java
new file mode 100644
index 0000000..f174d08
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/EnclaveMethodInvoker.java
@@ -0,0 +1,15 @@
+package com.alibaba.confidentialcomputing.enclave.framework;
+
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+
+/**
+ * There are two types of method invocations in Enclave:
+ * <p>
+ * <li>Business methods: The subclass {@link ServiceMethodInvoker} of this class takes care
+ * of the business method invocation.</li>
+ * <li>Framework methods: The SDK defined methods that run inside the enclave to maintain the framework. These methods
+ * must be static, are taken care by the subclass {@link LoadServiceInvoker}.</li>
+ */
+public interface EnclaveMethodInvoker<T> {
+    EnclaveInvocationResult callMethod(T input);
+}
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/LoadServiceInvoker.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/LoadServiceInvoker.java
new file mode 100644
index 0000000..d0c03ae
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/LoadServiceInvoker.java
@@ -0,0 +1,25 @@
+package com.alibaba.confidentialcomputing.enclave.framework;
+
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+
+/**
+ * This class handles loadService method invocation.
+ */
+public final class LoadServiceInvoker implements EnclaveMethodInvoker<String> {
+
+    /**
+     * Call loadService method.
+     *
+     * @param inputData name of the service to load.
+     */
+    @Override
+    public EnclaveInvocationResult callMethod(String inputData) {
+        Class<?> service;
+        try {
+            service = Class.forName(inputData);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException("Can't find the service interface class.", e);
+        }
+        return new EnclaveInvocationResult(EnclaveContext.getInstance().loadService(service), null);
+    }
+}
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvoker.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvoker.java
new file mode 100644
index 0000000..899ec5a
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvoker.java
@@ -0,0 +1,115 @@
+package com.alibaba.confidentialcomputing.enclave.framework;
+
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationContext;
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+import com.alibaba.confidentialcomputing.common.ServiceHandler;
+import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException;
+import jdk.vm.ci.meta.MetaUtil;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class handles the service method invocation. The user defined business methods that run inside the enclave follow the
+ * SPI (<a href="https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html">Service Provider Interface</a>)
+ * idiom, so they are defined in the form of service methods. This class delegates the user method invocation request
+ * wrapped in {@link EnclaveInvocationContext} to the actual method by reflection.
+ */
+public final class ServiceMethodInvoker implements EnclaveMethodInvoker<EnclaveInvocationContext> {
+
+    /**
+     * Prepare and make the target method call by reflection. Any exception thrown from method invocation is captured
+     * and saved in the returned {@link EnclaveInvocationResult}. This method can only throw exception happens at invocation
+     * preparation time.
+     *
+     * @param inputData all necessary information to reflectively invoke the target method.
+     * @return value returned by the target method invocation or the exception captured in method invocation.
+     */
+    @Override
+    public EnclaveInvocationResult callMethod(EnclaveInvocationContext inputData) {
+        Throwable throwable = null;
+        Object returnedValue = null;
+        List<Class<?>> parameterClassList = extractParamClasses(inputData.getParameterTypes());
+        ServiceHandler serviceHandler = inputData.getServiceHandler();
+        String instanceIdentity = serviceHandler.getInstanceIdentity();
+        String serviceName = serviceHandler.getServiceInterfaceName();
+        String implementationClassName = serviceHandler.getServiceImplClassName();
+        Object receiverInstance;
+        try {
+            receiverInstance = EnclaveContext.getInstance().lookupServiceInstance(instanceIdentity, serviceName, implementationClassName);
+        } catch (ConfidentialComputingException e) {
+            return new EnclaveInvocationResult(null, e);
+        }
+        if (receiverInstance != null) {
+            String methodName = inputData.getMethodName();
+            Method method;
+            // Get the public method to invoke
+            try {
+                Class<?> serviceClass = Class.forName(implementationClassName);
+                method = serviceClass.getMethod(methodName, parameterClassList.toArray(new Class<?>[0]));
+                method.setAccessible(true);
+            } catch (ReflectiveOperationException e) {
+                // Reflection exception is taken as framework's exception
+                return new EnclaveInvocationResult(null, new ConfidentialComputingException(e));
+            }
+            try {
+                // Call the actual method
+                returnedValue = method.invoke(receiverInstance, inputData.getArguments());
+            } catch (InvocationTargetException e) {
+                // The exception happens in the vocation is the user's exception, it will be returned to the user.
+                throwable = e.getCause();
+            } catch (Throwable t) {
+                return new EnclaveInvocationResult(null, new ConfidentialComputingException(t));
+            }
+        } else {
+            throwable = new ConfidentialComputingException(
+                    String.format("Didn't match any service implementation with the given class name: %s", implementationClassName));
+        }
+        return new EnclaveInvocationResult(returnedValue, throwable);
+    }
+
+    private static List<Class<?>> extractParamClasses(String[] parameterTypes) {
+        List<Class<?>> parameterClassList = new ArrayList<>();
+        for (String parameterType : parameterTypes) {
+            try {
+                parameterClassList.add(nameToType(parameterType));
+            } catch (ClassNotFoundException e) {
+                throw new RuntimeException("Can't found the specified class from parameters:", e);
+            }
+        }
+        return parameterClassList;
+    }
+
+    private static Class<?> nameToType(String typeName) throws ClassNotFoundException {
+        String name = typeName;
+        if (name.indexOf('[') != -1) {
+            /* accept "int[][]", "java.lang.String[]" */
+            name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true);
+        }
+        if (name.indexOf('.') == -1) {
+            switch (name) {
+                case "boolean":
+                    return boolean.class;
+                case "char":
+                    return char.class;
+                case "float":
+                    return float.class;
+                case "double":
+                    return double.class;
+                case "byte":
+                    return byte.class;
+                case "short":
+                    return short.class;
+                case "int":
+                    return int.class;
+                case "long":
+                    return long.class;
+                case "void":
+                    return void.class;
+            }
+        }
+        return Class.forName(name);
+    }
+}
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/UnloadServiceInvoker.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/UnloadServiceInvoker.java
new file mode 100644
index 0000000..5f5fb6b
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/framework/UnloadServiceInvoker.java
@@ -0,0 +1,24 @@
+package com.alibaba.confidentialcomputing.enclave.framework;
+
+
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+import com.alibaba.confidentialcomputing.common.ServiceHandler;
+import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException;
+
+/**
+ * This class handles the unloadService method to unload the specified service.
+ */
+public class UnloadServiceInvoker implements EnclaveMethodInvoker<ServiceHandler> {
+
+    @Override
+    public EnclaveInvocationResult callMethod(ServiceHandler inputData) {
+        Object ret = EnclaveContext.getInstance().removeCache(inputData.getInstanceIdentity());
+        Throwable t = null;
+        if (ret == null) {
+            t = new ConfidentialComputingException(String.format("No instance for service %s is found with the given identity %s", inputData.getServiceInterfaceName(),
+                    inputData.getInstanceIdentity()));
+        }
+        // unloadService method's return type is void.
+        return new EnclaveInvocationResult(null, t);
+    }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java
new file mode 100644
index 0000000..f47d64b
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java
@@ -0,0 +1,12 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import com.alibaba.confidentialcomputing.enclave.testservice.MathService;
+import com.alibaba.confidentialcomputing.enclave.testservice.NumericMath;
+
+public class EnclaveTestHelper {
+    public static final String MATH_SERVICE = MathService.class.getName();
+    public static final String NUMERIC_MATH = NumericMath.class.getName();
+    public static final String[] MATH_ADD_PARAM_TYPES = {"java.lang.Number", "java.lang.Number"};
+    public static final String[] EMPTY_STRING_ARRAY = new String[0];
+    public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvokerTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvokerTest.java
new file mode 100644
index 0000000..8b4bbe4
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvokerTest.java
@@ -0,0 +1,170 @@
+package com.alibaba.confidentialcomputing.enclave.framework;
+
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationContext;
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+import com.alibaba.confidentialcomputing.common.ServiceHandler;
+import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException;
+import com.alibaba.confidentialcomputing.enclave.testservice.MathService;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.EMPTY_OBJECT_ARRAY;
+import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.EMPTY_STRING_ARRAY;
+import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_ADD_PARAM_TYPES;
+import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_SERVICE;
+import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.NUMERIC_MATH;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ServiceMethodInvokerTest {
+
+    private static ServiceMethodInvoker serviceMethodInvoker = new ServiceMethodInvoker();
+    private ServiceHandler[] services;
+
+    @BeforeEach
+    public void setup() {
+        services = EnclaveContext.getInstance().loadService(MathService.class);
+        assertEquals(3, services.length);
+        assertEquals(MATH_SERVICE, services[0].getServiceInterfaceName());
+        assertEquals(NUMERIC_MATH, services[0].getServiceImplClassName());
+    }
+
+    @AfterEach
+    public void tear() {
+        EnclaveContext.getInstance().clearCache();
+    }
+
+    /**
+     * Test the invocation is successfully made.
+     */
+    @Test
+    public void testSuccessCall() {
+        EnclaveInvocationResult result = callNumericAdd(services[0], 1, 2);
+        assertNotNull(result);
+        Object wrappedResult = result.getResult();
+        assertNotNull(wrappedResult, "Expect to have non-null result from invoking service method call.");
+        assertNull(result.getException());
+        assertEquals(3, (Integer) wrappedResult);
+    }
+
+    /**
+     * Test the exception thrown from service implementation is properly returned.
+     */
+    @Test
+    public void testInvocationFail() {
+        // Prepare a div(1, 0) method call which should report a divide 0 exception.
+        EnclaveInvocationResult result = callServiceImplMethod(services[0],
+                "div", MATH_ADD_PARAM_TYPES, new Object[]{1, 0});
+        assertNotNull(result);
+        Object wrappedResult = result.getResult();
+        assertNull(wrappedResult, "Expect to have non-null result from invoking service method call.");
+        Throwable e = result.getException();
+        assertNotNull(e);
+        assertTrue(e instanceof ArithmeticException);
+    }
+
+    /**
+     * Call a not exist service
+     */
+    @Test
+    public void testCallNotExistService() {
+        EnclaveInvocationResult ret = callServiceImplMethod(new ServiceHandler("MATH_SERVICE", services[0].getServiceImplClassName(), services[0].getInstanceIdentity()),
+                "add",
+                MATH_ADD_PARAM_TYPES,
+                new Object[]{1, 2});
+        assertNotNull(ret);
+        assertNull(ret.getResult());
+        assertTrue(ret.getException() instanceof ConfidentialComputingException);
+    }
+
+    /**
+     * Call a not exist service implementation
+     */
+    @Test
+    public void testCallNotExistImpl() {
+        EnclaveInvocationResult ret = callServiceImplMethod(
+                new ServiceHandler(services[0].getServiceInterfaceName(), "NUMERIC_MATH", services[0].getInstanceIdentity()),
+                "add",
+                MATH_ADD_PARAM_TYPES,
+                new Object[]{1, 2});
+        assertNotNull(ret);
+        assertNull(ret.getResult());
+        assertTrue(ret.getException() instanceof ConfidentialComputingException);
+    }
+
+    /**
+     * Call a not exist service implementation method
+     */
+    @Test
+    public void testCallNotExistMethod() {
+        EnclaveInvocationResult ret = callServiceImplMethod(services[0],
+                "add123",
+                MATH_ADD_PARAM_TYPES,
+                new Object[]{1, 2});
+        assertNotNull(ret);
+        assertNull(ret.getResult());
+        assertTrue(ret.getException() instanceof ConfidentialComputingException);
+        assertTrue(ret.getException().getCause() instanceof NoSuchMethodException);
+    }
+
+    @Test
+    public void testServiceConsistency() {
+        ServiceHandler[] secondLoadings = EnclaveContext.getInstance().loadService(MathService.class);
+        int i = 0;
+        while (i++ < 2) {
+            callNumericAdd(services[0], 1, 2);
+        }
+        callNumericAdd(secondLoadings[0], 1, 2);
+        assertEquals(2, callGetCounter(services[0]).getResult(),
+                "Add method in service instance with identity " + services[0].getInstanceIdentity() + "has been called twice, the counter should be 2");
+        assertEquals(1, callGetCounter(secondLoadings[0]).getResult(),
+                "Add method in service instance with identity " + secondLoadings[0].getInstanceIdentity() + "has been called twice, the counter should be 1");
+    }
+
+    @Test
+    public void testDefaultMethod(){
+        EnclaveInvocationResult result = callServiceImplMethod(services[0],
+                "getConstant",
+                EMPTY_STRING_ARRAY,
+                EMPTY_OBJECT_ARRAY);
+        assertNotNull(result);
+        Object wrappedResult = result.getResult();
+        assertNotNull(wrappedResult, "Expect to have non-null result from invoking service method call.");
+        assertNull(result.getException());
+        assertEquals(100, (Integer) wrappedResult);
+    }
+
+    @Test
+    public void testGrandChildMethod(){
+        EnclaveInvocationResult result = callNumericAdd(services[2], 1, 2);
+        assertNotNull(result);
+        Object wrappedResult = result.getResult();
+        assertNotNull(wrappedResult, "Expect to have non-null result from invoking service method call.");
+        assertNull(result.getException());
+        assertEquals(3, (Integer) wrappedResult);
+    }
+
+
+    private static EnclaveInvocationResult callGetCounter(ServiceHandler serviceHandler) {
+        return callServiceImplMethod(serviceHandler,
+                "getCounter",
+                EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY);
+    }
+
+    private static EnclaveInvocationResult callNumericAdd(ServiceHandler serviceHandler, int x, int y) {
+        return callServiceImplMethod(serviceHandler,
+                "add",
+                MATH_ADD_PARAM_TYPES,
+                new Object[]{x, y});
+    }
+
+    private static EnclaveInvocationResult callServiceImplMethod(ServiceHandler serviceHandler, String method,
+                                                                 String[] paramTypes, Object[] paramValues) {
+        EnclaveInvocationContext input = new EnclaveInvocationContext(serviceHandler,
+                method, paramTypes, paramValues);
+        return serviceMethodInvoker.callMethod(input);
+    }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceOperationTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceOperationTest.java
new file mode 100644
index 0000000..726e8e5
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceOperationTest.java
@@ -0,0 +1,49 @@
+package com.alibaba.confidentialcomputing.enclave.framework;
+
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+import com.alibaba.confidentialcomputing.common.ServiceHandler;
+import com.alibaba.confidentialcomputing.enclave.testservice.NumericMath;
+import com.alibaba.confidentialcomputing.enclave.testservice.PointMath;
+import org.junit.jupiter.api.Test;
+
+import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_SERVICE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * This class tests loading and unloading service.
+ */
+public class ServiceOperationTest {
+
+    @Test
+    public void testLoadingAndUnloading() {
+        LoadServiceInvoker loadServiceInvoker = new LoadServiceInvoker();
+        // Call loadService twice, check the service identities are different.
+        EnclaveInvocationResult ret1 = loadServiceInvoker.callMethod(MATH_SERVICE);
+        EnclaveInvocationResult ret2 = loadServiceInvoker.callMethod(MATH_SERVICE);
+        ServiceHandler[] serviceHandlers1 = (ServiceHandler[]) ret1.getResult();
+        ServiceHandler[] serviceHandlers2 = (ServiceHandler[]) ret2.getResult();
+
+        // There should be two service implementation instances
+        assertEquals(3, serviceHandlers1.length);
+        // They should have the same order as defined in the service configuration file
+        assertTrue(serviceHandlers1[0].getServiceImplClassName().equals(NumericMath.class.getName()));
+        assertTrue(serviceHandlers1[1].getServiceImplClassName().equals(PointMath.class.getName()));
+
+        // The second group of service implementations should be the same
+        assertEquals(3, serviceHandlers2.length);
+        assertTrue(serviceHandlers2[0].getServiceImplClassName().equals(NumericMath.class.getName()));
+        assertTrue(serviceHandlers2[1].getServiceImplClassName().equals(PointMath.class.getName()));
+
+        // Compare the service instance identities, should be different
+        assertNotEquals(serviceHandlers1[0].getInstanceIdentity(), serviceHandlers2[0].getInstanceIdentity());
+        assertNotEquals(serviceHandlers1[1].getInstanceIdentity(), serviceHandlers2[1].getInstanceIdentity());
+
+        // There are 4 services cached in EnclaveContext, and should be 3 left after unloading one.
+        assertEquals(6, EnclaveContext.getInstance().servicesSize());
+        UnloadServiceInvoker unloadServiceInvoker = new UnloadServiceInvoker();
+        unloadServiceInvoker.callMethod(serviceHandlers1[0]);
+        assertEquals(5, EnclaveContext.getInstance().servicesSize());
+    }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/IntegerMath.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/IntegerMath.java
new file mode 100644
index 0000000..4beed50
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/IntegerMath.java
@@ -0,0 +1,8 @@
+package com.alibaba.confidentialcomputing.enclave.testservice;
+
+public class IntegerMath extends NumericMath {
+    @Override
+    public Number add(Number x, Number y) {
+        return super.add(x, y);
+    }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/MathService.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/MathService.java
new file mode 100644
index 0000000..9a01c09
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/MathService.java
@@ -0,0 +1,13 @@
+package com.alibaba.confidentialcomputing.enclave.testservice;
+
+public interface MathService<T> {
+    T add(T x, T y);
+
+    T minus(T x, T y);
+
+    T div(T x, T y);
+
+    default int getConstant(){
+        return 100;
+    }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/NumericMath.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/NumericMath.java
new file mode 100644
index 0000000..a26c861
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/NumericMath.java
@@ -0,0 +1,28 @@
+package com.alibaba.confidentialcomputing.enclave.testservice;
+
+public class NumericMath implements MathService<Number> {
+
+    private int counter = 0;
+
+    @Override
+    public Number add(Number x, Number y) {
+        counter++;
+        return x.intValue() + y.intValue();
+    }
+
+    @Override
+    public Number minus(Number x, Number y) {
+        counter++;
+        return x.intValue() - y.intValue();
+    }
+
+    @Override
+    public Number div(Number x, Number y) {
+        counter++;
+        return x.intValue() / y.intValue();
+    }
+
+    public int getCounter() {
+        return counter;
+    }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/Point.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/Point.java
new file mode 100644
index 0000000..f252d7f
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/Point.java
@@ -0,0 +1,11 @@
+package com.alibaba.confidentialcomputing.enclave.testservice;
+
+public class Point {
+    int x;
+    int y;
+
+    public Point(int x, int y){
+        this.x = x;
+        this.y = y;
+    }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/PointMath.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/PointMath.java
new file mode 100644
index 0000000..0805277
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/PointMath.java
@@ -0,0 +1,18 @@
+package com.alibaba.confidentialcomputing.enclave.testservice;
+
+public class PointMath implements MathService<Point>{
+    @Override
+    public Point add(Point x, Point y) {
+        return new Point(x.x + y.x, x.y + y.y);
+    }
+
+    @Override
+    public Point minus(Point x, Point y) {
+        return new Point(x.x - y.x, x.y - y.y);
+    }
+
+    @Override
+    public Point div(Point x, Point y) {
+        return new Point(x.x / y.x, x.y / y.y);
+    }
+}
diff --git a/sdk/enclave/src/test/resources/META-INF/services/com.alibaba.confidentialcomputing.enclave.testservice.MathService b/sdk/enclave/src/test/resources/META-INF/services/com.alibaba.confidentialcomputing.enclave.testservice.MathService
new file mode 100644
index 0000000..4753cf1
--- /dev/null
+++ b/sdk/enclave/src/test/resources/META-INF/services/com.alibaba.confidentialcomputing.enclave.testservice.MathService
@@ -0,0 +1,3 @@
+com.alibaba.confidentialcomputing.enclave.testservice.NumericMath
+com.alibaba.confidentialcomputing.enclave.testservice.PointMath
+com.alibaba.confidentialcomputing.enclave.testservice.IntegerMath


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@teaclave.apache.org
For additional commands, e-mail: commits-help@teaclave.apache.org