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:51 UTC
[incubator-teaclave-java-tee-sdk] 11/48: [Enc] Add native image level 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 edc95be328bb832a63c72c47c0365f6dbfaa6b5c
Author: cengfeng.lzy <ce...@alibaba-inc.com>
AuthorDate: Wed Mar 16 18:01:13 2022 +0800
[Enc] Add native image level framework
Summary: Create native image level framework that provides the following
functions:
- Library invocation entrypointss that follow the SVM protocals
- Data structure definition
- Data serialization and converting from WordBase to Object base
- Service state maintainance
Test Plan: all tests pass
Reviewers: lei.yul, jeffery.wsj, sanhong.lsh
Issue: https://aone.alibaba-inc.com/task/40646790
CR:
https://code.aone.alibaba-inc.com/java-tee/JavaEnclave/codereview/8303453
---
.gitignore | 19 ++
build.sh | 20 +-
.../common/annotations/EnclaveMethod.java | 45 ++++
.../common/annotations/EnclaveService.java | 52 ++++
sdk/enclave/pom.xml | 107 ++++++++-
.../enclave/EnclaveEntry.java | 79 ++++++
.../enclave/EnclaveFeature.java | 130 ++++++++++
.../enclave/EnclavePrologue.java | 22 ++
.../enclave/InvocationWrapper.java | 69 ++++++
.../enclave/c/EnclaveEnvironment.java | 126 ++++++++++
.../META-INF/native-image/reflect-config.json | 20 ++
.../native-image/serialization-config.json | 44 ++++
.../src/main/resources/native/enc_environment.h | 17 ++
.../enclave/AroundNativeTest.java | 49 ++++
.../enclave/EnclaveTestHelper.java | 107 +++++++++
.../enclave/NativeImageTest.java | 264 +++++++++++++++++++++
.../enclave/NativeImageTestable.java | 12 +
.../enclave/RunWithNativeImageTest.java | 166 +++++++++++++
.../enclave/SVMSimpleEnclaveCallTest.java | 123 ++++++++++
.../confidentialcomputing/enclave/TestTarget.java | 12 +
.../framework/ServiceMethodInvokerTest.java | 8 +
.../enclave/framework/ServiceOperationTest.java | 8 +
.../enclave/testservice/MathService.java | 3 +
.../enclave/testservice/Point.java | 11 +-
...nfidentialcomputing_enclave_EnclaveTestHelper.h | 53 +++++
.../test/resources/native/enc_invoke_entry_test.c | 76 ++++++
tools/cicd/Dockerfile | 5 +-
tools/cicd/make.sh | 7 +-
28 files changed, 1633 insertions(+), 21 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8e5dc50
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+
+# virtual machine crash logs
+hs_err_pid*
+
+# IDE config
+*.iml
+.idea/
+.classpath
+.project
+.settings/
+
+# Maven compiled directory
+target/
diff --git a/build.sh b/build.sh
index f9c39ec..b4dbae1 100644
--- a/build.sh
+++ b/build.sh
@@ -8,23 +8,19 @@ cd "${SHELL_FOLDER}"
# workspace dir is the same as build.sh path location.
WORKDIR="$PWD"
-# The necessary GraalVM jars are compiled from git@gitlab.alibaba-inc.com:graal/SGXGraalVM.git. When the patches are
-# accepted by the community, these jars will be gradually replaced by the official jars.
+# The necessary GraalVM jars are compiled from git@gitlab.alibaba-inc.com:graal/SGXGraalVM.git.
+# When the patches are accepted by the community, these jars will be gradually replaced by the official jars.
VERSION="enclave-22.0.0"
mkdir jartmp
pushd jartmp > /dev/null
-wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/graal-processor-22.0.0.jar
wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/graal-sdk-enclave-22.0.0.jar
-wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/native-image-base-enclave-22.0.0.jar
-wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/objectfile-enclave-22.0.0.jar
-wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/pointsto-enclave-22.0.0.jar
-wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/svm-enclave-22.0.0.jar
-mvn install:install-file -Dfile=graal-processor-22.0.0.jar -DgroupId=org.graalvm.compiler -DartifactId=graal-processor -Dversion=$VERSION -Dpackaging=jar
+
+mvn install:install-file -Dfile=$GRAALVM_HOME/lib/graal/graal-processor.jar -DgroupId=org.graalvm.compiler -DartifactId=graal-processor -Dversion=$VERSION -Dpackaging=jar
mvn install:install-file -Dfile=graal-sdk-enclave-22.0.0.jar -DgroupId=org.graalvm.sdk -DartifactId=graal-sdk -Dversion=$VERSION -Dpackaging=jar
-mvn install:install-file -Dfile=svm-enclave-22.0.0.jar -DgroupId=org.graalvm.nativeimage -DartifactId=svm -Dversion=$VERSION -Dpackaging=jar
-mvn install:install-file -Dfile=objectfile-enclave-22.0.0.jar -DgroupId=org.graalvm.nativeimage -DartifactId=objectfile -Dversion=$VERSION -Dpackaging=jar
-mvn install:install-file -Dfile=pointsto-enclave-22.0.0.jar -DgroupId=org.graalvm.nativeimage -DartifactId=pointsto -Dversion=$VERSION -Dpackaging=jar
-mvn install:install-file -Dfile=native-image-base-enclave-22.0.0.jar -DgroupId=org.graalvm.nativeimage -DartifactId=native-image-base -Dversion=$VERSION -Dpackaging=jar
+mvn install:install-file -Dfile=$GRAALVM_HOME/lib/svm/builder/svm.jar -DgroupId=org.graalvm.nativeimage -DartifactId=svm -Dversion=$VERSION -Dpackaging=jar
+mvn install:install-file -Dfile=$GRAALVM_HOME/lib/svm/builder/objectfile.jar -DgroupId=org.graalvm.nativeimage -DartifactId=objectfile -Dversion=$VERSION -Dpackaging=jar
+mvn install:install-file -Dfile=$GRAALVM_HOME/lib/svm/builder/pointsto.jar -DgroupId=org.graalvm.nativeimage -DartifactId=pointsto -Dversion=$VERSION -Dpackaging=jar
+mvn install:install-file -Dfile=$GRAALVM_HOME/lib/svm/builder/native-image-base.jar -DgroupId=org.graalvm.nativeimage -DartifactId=native-image-base -Dversion=$VERSION -Dpackaging=jar
popd > /dev/null
rm -rf jartmp
diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveMethod.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveMethod.java
new file mode 100644
index 0000000..7a5fa94
--- /dev/null
+++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveMethod.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.alibaba.confidentialcomputing.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark a method is running inside the Enclave, but can be directly invoked from the Host.
+ * So its parameters and returned value types are required to get serialized.
+ * If a service provider's interface has been marked with {@link EnclaveService}, there is no need to mark its methods with
+ * this annotation.
+ * <p>
+ * Please refer {@link EnclaveService} for the details about automatic serialization type registration in native image scenario.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnclaveMethod {
+}
diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveService.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveService.java
new file mode 100644
index 0000000..0b193a8
--- /dev/null
+++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.alibaba.confidentialcomputing.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark an interface is used as SPI service for Enclave.
+ * All its providers' public non-static methods are {@link EnclaveMethod}s.
+ * When the TEE side is native image (SVM), {@link EnclaveMethod}'s parameter and returned types may be automatically
+ * registered for reflection and serialization at native image build time. But a class can be automatically registered
+ * for serialization only when it:
+ * <ul>
+ * <li>Doesn't have {@code writeObject} method. The {@code writeObject} customizes the serialization rule, preventing
+ * native image generator automatically inferring the associated serialization types. A typical example is {@link java.util.ArrayList}. </li>
+ * <li>Is effective final, i.e. doesn't have subclasses.</li>
+ * </ul>
+ * Native image generator issues a warning when the parameter and returned value type don't obey the above two rules.
+ * To solve the problem, user can choose another class for replacement if possible, e.g. using array instead of {@link java.util.ArrayList};
+ * or generating the serialization configuration by <a href="https://github.com/ziyilin/ziyi-forked-graal/blob/master/docs/reference-manual/native-image/Agent.md">native-image-agent</a>.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnclaveService {
+}
diff --git a/sdk/enclave/pom.xml b/sdk/enclave/pom.xml
index 8c79f06..8b66556 100644
--- a/sdk/enclave/pom.xml
+++ b/sdk/enclave/pom.xml
@@ -12,6 +12,57 @@
<packaging>jar</packaging>
<name>JavaEnclave-Enclave</name>
<url></url>
+ <properties>
+ <graal.version>enclave-22.0.0</graal.version>
+ <svm.maven.version>0.9.10</svm.maven.version>
+ </properties>
+ <profiles>
+ <profile>
+ <id>native</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.graalvm.buildtools</groupId>
+ <artifactId>native-maven-plugin</artifactId>
+ <version>${svm.maven.version}</version>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>build-native</id>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ <configuration>
+ <imageName>libsvm_enclave_sdk</imageName>
+ <buildArgs>
+ <buildArg>--shared</buildArg>
+ <buildArg>--no-fallback</buildArg>
+ <buildArg>-H:+RunInEnclave</buildArg>
+ <buildArg>-H:OutputRelocatableImage=.</buildArg>
+ <buildArg>-H:Path=svm-output</buildArg>
+ </buildArgs>
+ </configuration>
+ <phase>package</phase>
+ </execution>
+ <execution>
+ <id>test-native</id>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <buildArgs>
+ <buildArg>--no-fallback</buildArg>
+ <buildArg>-H:+RunInEnclave</buildArg>
+ </buildArgs>
+ </configuration>
+ <phase>test</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
<build>
<plugins>
<plugin>
@@ -27,6 +78,17 @@
<arg>--add-exports</arg>
<arg>jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED</arg>
</compilerArgs>
+ <!--OptionProcessor can automatically generate OptionDescriptor classes at javac time-->
+ <annotationProcessorPaths>
+ <path>
+ <groupId>org.graalvm.compiler</groupId>
+ <artifactId>graal-processor</artifactId>
+ <version>${graal.version}</version>
+ </path>
+ </annotationProcessorPaths>
+ <annotationProcessors>
+ <annotationProcessor>org.graalvm.compiler.options.processor.OptionProcessor</annotationProcessor>
+ </annotationProcessors>
</configuration>
</plugin>
<plugin>
@@ -93,16 +155,47 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>3.0.0</version>
+ <executions>
+ <execution>
+ <id>preTest</id>
+ <phase>test-compile</phase>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ <configuration>
+ <mainClass>com.alibaba.confidentialcomputing.enclave.AroundNativeTest$PreTest</mainClass>
+ <classpathScope>test</classpathScope>
+ </configuration>
+ </execution>
+ <execution>
+ <id>postTest</id>
+ <phase>test</phase>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ <configuration>
+ <mainClass>com.alibaba.confidentialcomputing.enclave.AroundNativeTest$PostTest</mainClass>
+ <classpathScope>test</classpathScope>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.22.1</version>
+ <version>3.0.0-M5</version>
+ <configuration>
+ <environmentVariables>
+ <LD_LIBRARY_PATH>/tmp/javaenclavetest-native-libs</LD_LIBRARY_PATH>
+ </environmentVariables>
+ </configuration>
</plugin>
</plugins>
</build>
- <properties>
- <graal.version>enclave-22.0.0</graal.version>
- </properties>
<dependencies>
<dependency>
<groupId>org.graalvm.sdk</groupId>
@@ -129,6 +222,12 @@
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.graalvm.buildtools</groupId>
+ <artifactId>junit-platform-native</artifactId>
+ <version>${svm.maven.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>com.alibaba.confidentialcomputing</groupId>
<artifactId>common</artifactId>
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveEntry.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveEntry.java
new file mode 100644
index 0000000..04d091c
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveEntry.java
@@ -0,0 +1,79 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.CallBacks;
+import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.EncData;
+import com.alibaba.confidentialcomputing.enclave.framework.LoadServiceInvoker;
+import com.alibaba.confidentialcomputing.enclave.framework.ServiceMethodInvoker;
+import com.alibaba.confidentialcomputing.enclave.framework.UnloadServiceInvoker;
+import com.oracle.svm.core.c.function.CEntryPointOptions;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.Isolate;
+import org.graalvm.nativeimage.c.function.CEntryPoint;
+import org.graalvm.nativeimage.c.type.CTypeConversion;
+
+/**
+ * This class defines the entry points for native image (shared library) deployed in TEE enclave.
+ */
+public class EnclaveEntry {
+ private static CallBacks callBackMethods;
+
+ @SuppressWarnings("unused")
+ @CEntryPoint(name = "java_loadservice_invoke")
+ @CEntryPointOptions(prologue = EnclavePrologue.class)
+ public static int loadService(Isolate isolate, EncData input, EncData result, CallBacks callBacks) {
+ callBackMethods = callBacks;
+ int retCode = 0;
+ try {
+ InvocationWrapper.invoke(input, result, callBacks, ImageSingletons.lookup(LoadServiceInvoker.class));
+ } catch (Throwable t) {
+ retCode = handleFrameworkException(t);
+ }
+ return retCode;
+ }
+
+ @SuppressWarnings("unused")
+ @CEntryPoint(name = "java_unloadservice_invoke")
+ @CEntryPointOptions(prologue = EnclavePrologue.class)
+ public static int unloadService(Isolate isolate, EncData input, EncData result, CallBacks callBacks) {
+ callBackMethods = callBacks;
+ int retCode = 0;
+ try {
+ InvocationWrapper.invoke(input, result, callBacks, ImageSingletons.lookup(UnloadServiceInvoker.class));
+ } catch (Throwable t) {
+ retCode = handleFrameworkException(t);
+ }
+ return retCode;
+ }
+
+ @SuppressWarnings("unused")
+ @CEntryPoint(name = "java_enclave_invoke")
+ @CEntryPointOptions(prologue = EnclavePrologue.class)
+ public static int javaEnclaveInvoke(Isolate isolate, EncData input, EncData result, CallBacks callBacks) {
+ callBackMethods = callBacks;
+ int retCode = 0;
+ try {
+ InvocationWrapper.invoke(input, result, callBacks, ImageSingletons.lookup(ServiceMethodInvoker.class));
+ } catch (Throwable t) {
+ retCode = handleFrameworkException(t);
+ }
+ return retCode;
+ }
+
+ private static int handleFrameworkException(Throwable t) {
+ if (callBackMethods.isNonNull() && callBackMethods.getExceptionHandler().isNonNull()) {
+ StringBuilder stacktraceSB = new StringBuilder();
+ for (StackTraceElement se : t.getStackTrace()) {
+ stacktraceSB.append(se.toString()).append("\n");
+ }
+ try (
+ CTypeConversion.CCharPointerHolder stacktrace = CTypeConversion.toCString(stacktraceSB.toString());
+ CTypeConversion.CCharPointerHolder errMsg = CTypeConversion.toCString(t.getMessage());
+ CTypeConversion.CCharPointerHolder exception = CTypeConversion.toCString(t.getClass().toString())) {
+ callBackMethods.getExceptionHandler().invoke(errMsg.get(), stacktrace.get(), exception.get());
+ }
+ } else {
+ t.printStackTrace();
+ }
+ return 1;
+ }
+}
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveFeature.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveFeature.java
new file mode 100644
index 0000000..fe36075
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveFeature.java
@@ -0,0 +1,130 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import com.alibaba.confidentialcomputing.common.annotations.EnclaveMethod;
+import com.alibaba.confidentialcomputing.common.annotations.EnclaveService;
+import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException;
+import com.alibaba.confidentialcomputing.enclave.framework.LoadServiceInvoker;
+import com.alibaba.confidentialcomputing.enclave.framework.ServiceMethodInvoker;
+import com.alibaba.confidentialcomputing.enclave.framework.UnloadServiceInvoker;
+import com.oracle.svm.core.annotate.AutomaticFeature;
+import com.oracle.svm.core.jdk.resources.NativeImageResourceFileSystemUtil;
+import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.hosted.FeatureImpl;
+import com.oracle.svm.hosted.ImageClassLoader;
+import com.oracle.svm.hosted.ServiceLoaderFeature;
+import com.oracle.svm.reflect.hosted.ReflectionFeature;
+import com.oracle.svm.reflect.serialize.hosted.SerializationFeature;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.nativeimage.hosted.RuntimeReflection;
+import org.graalvm.nativeimage.hosted.RuntimeSerialization;
+import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+@AutomaticFeature
+public class EnclaveFeature implements Feature {
+
+ private ImageClassLoader imageClassLoader;
+ private final Map<Class<?>, Boolean> serializationCandidateTypes = new HashMap<>();
+ private final Map<Class<?>, Boolean> reflectionCandidateTypes = new HashMap<>();
+ private final Map<Method, Boolean> reflectionCandidateMethods = new HashMap<>();
+
+ @Override
+ public List<Class<? extends Feature>> getRequiredFeatures() {
+ return Arrays.asList(ReflectionFeature.class, SerializationFeature.class, ServiceLoaderFeature.class);
+ }
+
+ @Override
+ public void duringSetup(DuringSetupAccess access) {
+ ImageSingletons.add(ServiceMethodInvoker.class, new ServiceMethodInvoker());
+ ImageSingletons.add(LoadServiceInvoker.class, new LoadServiceInvoker());
+ ImageSingletons.add(UnloadServiceInvoker.class, new UnloadServiceInvoker());
+ ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtBuildTime("com.alibaba.confidentialcomputing.enclave.EnclavePrologue",
+ "Prologue class should be initialize at build time.");
+
+ FeatureImpl.DuringSetupAccessImpl config = (FeatureImpl.DuringSetupAccessImpl) access;
+ RuntimeSerialization.register(ConfidentialComputingException.class, RuntimeException.class,
+ ReflectiveOperationException.class, ClassNotFoundException.class);
+ RuntimeSerialization.registerAllAssociatedClasses(Collections.EMPTY_LIST.getClass());
+ imageClassLoader = config.getImageClassLoader();
+ }
+
+ /**
+ * Collect reflection and serialization configurations from {@link EnclaveService} marked interfaces.
+ */
+ @Override
+ public void duringAnalysis(DuringAnalysisAccess access) {
+ List<Class<?>> enclaveServices = imageClassLoader.findAnnotatedClasses(EnclaveService.class, true);
+ enclaveServices.forEach(serviceClazz -> {
+ reflectionCandidateTypes.putIfAbsent(serviceClazz, false);
+ byte[] serviceConfig = NativeImageResourceFileSystemUtil.getBytes("META-INF/services/" + serviceClazz.getName(), true);
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(serviceConfig), StandardCharsets.UTF_8))) {
+ while (true) {
+ String line = reader.readLine();
+ if (line == null) {
+ break;
+ }
+ Class<?> implementation = imageClassLoader.findClass(line).get();
+ collectConfigs(implementation, Arrays.stream(implementation.getMethods()).filter(method ->
+ serviceClazz.isAssignableFrom(method.getDeclaringClass())
+ ).collect(Collectors.toList()));
+ }
+ } catch (IOException e) {
+ VMError.shouldNotReachHere(e);
+ }
+ });
+ List<Method> extraEnclaveMethods = imageClassLoader.findAnnotatedMethods(EnclaveMethod.class);
+ extraEnclaveMethods.forEach(method -> collectConfigs(method.getDeclaringClass(), List.of(method)));
+
+ // Register all newly collected configures
+ if (registerCollectedConfigs()) {
+ access.requireAnalysisIteration();
+ }
+ }
+
+
+ private void collectConfigs(Class<?> clazz, List<Method> methods) {
+ reflectionCandidateTypes.putIfAbsent(clazz, false);
+ methods.stream().filter(m -> !Modifier.isStatic(m.getModifiers())).forEach(
+ method -> {
+ for (Class<?> pType : method.getParameterTypes()) {
+ serializationCandidateTypes.putIfAbsent(pType, false);
+ }
+ serializationCandidateTypes.putIfAbsent(method.getReturnType(), false);
+ reflectionCandidateMethods.putIfAbsent(method, false);
+ }
+ );
+ }
+
+ private boolean registerCollectedConfigs() {
+ boolean registeredNewSerializations = registerCollectedConfigs(serializationCandidateTypes, RuntimeSerialization::registerAllAssociatedClasses);
+ boolean registeredNewReflectionTypes = registerCollectedConfigs(reflectionCandidateTypes, RuntimeReflection::register);
+ boolean registeredNewReflectionMethods = registerCollectedConfigs(reflectionCandidateMethods, RuntimeReflection::register);
+ return registeredNewSerializations || registeredNewReflectionTypes || registeredNewReflectionMethods;
+ }
+
+ private <T> boolean registerCollectedConfigs(Map<T, Boolean> configs, Consumer<T> registerAction) {
+ boolean needRegisterNew = configs.entrySet().stream().anyMatch(entry -> !entry.getValue());
+ configs.entrySet().stream().filter(entry -> !entry.getValue()).map(Map.Entry::getKey).forEach(
+ key -> {
+ registerAction.accept(key);
+ configs.put(key, true);
+ }
+ );
+ return needRegisterNew;
+ }
+}
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclavePrologue.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclavePrologue.java
new file mode 100644
index 0000000..596010a
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclavePrologue.java
@@ -0,0 +1,22 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import com.oracle.svm.core.annotate.Uninterruptible;
+import com.oracle.svm.core.c.CGlobalData;
+import com.oracle.svm.core.c.CGlobalDataFactory;
+import com.oracle.svm.core.c.function.CEntryPointActions;
+import com.oracle.svm.core.c.function.CEntryPointOptions;
+import org.graalvm.nativeimage.Isolate;
+import org.graalvm.nativeimage.c.type.CCharPointer;
+
+public class EnclavePrologue implements CEntryPointOptions.Prologue {
+ private static final CGlobalData<CCharPointer> errorMessage = CGlobalDataFactory.createCString("Failed to enter (or attach to) the global isolate in the current thread.");
+
+ @Uninterruptible(reason = "prologue")
+ static void enter(Isolate isolate) {
+
+ int code = CEntryPointActions.enterAttachThread(isolate, true);
+ if (code != 0) {
+ CEntryPointActions.failFatally(code, errorMessage.get());
+ }
+ }
+}
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/InvocationWrapper.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/InvocationWrapper.java
new file mode 100644
index 0000000..c732f7c
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/InvocationWrapper.java
@@ -0,0 +1,69 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+import com.alibaba.confidentialcomputing.common.SerializationHelper;
+import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException;
+import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.CallBacks;
+import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.EncData;
+import com.alibaba.confidentialcomputing.enclave.framework.EnclaveMethodInvoker;
+import org.graalvm.nativeimage.c.type.CCharPointer;
+import org.graalvm.nativeimage.c.type.CTypeConversion;
+import org.graalvm.word.PointerBase;
+
+import java.io.IOException;
+
+/**
+ * This class deals with the whole method invocation process from native entry point to the actual Java invocation target.
+ * It is taken out in 3 steps:<p>
+ * <li>Transform the input data from C {@link PointerBase} to Java byte[] and then deserialize to get the actual input.</li>
+ * <li>Make the method invocation.</li>
+ * <li>Collect the returned value, serialize it to the Java byte[], and wrap it back to C {@link PointerBase}</li>
+ * </p>
+ */
+public class InvocationWrapper {
+
+ public static <T> void invoke(EncData input, EncData result, CallBacks callBacks, EnclaveMethodInvoker<T> invoker) throws IOException {
+ byte[] data = transformInput(input);
+ EnclaveInvocationResult ret;
+ try {
+ ret = invoker.callMethod((T) SerializationHelper.deserialize(data));
+ } catch (Throwable t) {
+ ret = new EnclaveInvocationResult(null, new ConfidentialComputingException(t));
+ }
+ // Set method returned value to result parameter
+ wrapReturnValue(result, callBacks, ret);
+ }
+
+ private static void wrapReturnValue(EncData result, CallBacks callBacks, EnclaveInvocationResult ret) throws IOException {
+ byte[] returnedValBytes;
+ returnedValBytes = SerializationHelper.serialize(ret);
+ int returnedValLen = returnedValBytes.length;
+ /*
+ * Data returned to C world should be allocated by the callback function in the C world. The memory hold by
+ * returnedValBytes shall be freed in the explicit finally clause.
+ */
+ try (CTypeConversion.CCharPointerHolder byteHolder = CTypeConversion.toCBytes(returnedValBytes)) {
+ CCharPointer returned;
+ if (callBacks.isNonNull() && callBacks.getMemCpyCCharPointerFunctionPointer().isNonNull()) {
+ returned = callBacks.getMemCpyCCharPointerFunctionPointer().invoke(byteHolder.get(), returnedValLen);
+
+ } else {
+ returned = byteHolder.get();
+ System.out.println("Warning: Not calling call backs in native, there is memory leak risk.");
+ //throw new RuntimeException("Function pointer memcpy_char_pointer is not set");
+ }
+ result.setData(returned);
+ result.setLen(returnedValLen);
+ }
+ }
+
+ /**
+ * Transform input data from WordBase type to Java type.
+ */
+ private static byte[] transformInput(EncData input) {
+ int len = input.getLen();
+ byte[] data = new byte[len];
+ CTypeConversion.asByteBuffer(input.getData(), len).get(data);
+ return data;
+ }
+}
diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/c/EnclaveEnvironment.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/c/EnclaveEnvironment.java
new file mode 100644
index 0000000..922ab29
--- /dev/null
+++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/c/EnclaveEnvironment.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.alibaba.confidentialcomputing.enclave.c;
+
+import com.oracle.svm.core.c.ProjectHeaderFile;
+import com.oracle.svm.core.c.libc.TemporaryBuildDirectoryProvider;
+import com.oracle.svm.core.util.VMError;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.c.CContext;
+import org.graalvm.nativeimage.c.function.CFunctionPointer;
+import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer;
+import org.graalvm.nativeimage.c.struct.CField;
+import org.graalvm.nativeimage.c.struct.CStruct;
+import org.graalvm.nativeimage.c.type.CCharPointer;
+import org.graalvm.word.PointerBase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+@CContext(EnclaveEnvironment.EnclaveDirectives.class)
+public class EnclaveEnvironment {
+
+ static class EnclaveDirectives implements CContext.Directives {
+
+ private static final String HEADER_FILE = "native/enc_environment.h";
+
+ @Override
+ public List<String> getHeaderFiles() {
+ // Register additional resolver to resolve header file from jar file as resource stream
+ ProjectHeaderFile.HeaderResolversRegistry.registerAdditionalResolver((projectName, headerFile) -> {
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ InputStream headerInputStream = EnclaveDirectives.class.getClassLoader().getResourceAsStream(HEADER_FILE);
+ if (headerInputStream == null) {
+ VMError.shouldNotReachHere("Can't find " + HEADER_FILE + " from classpath as resource");
+ }
+ headerInputStream.transferTo(os);
+ Path headerPath = ImageSingletons.lookup(TemporaryBuildDirectoryProvider.class).getTemporaryBuildDirectory().resolve(HEADER_FILE);
+ Path nativeDir = headerPath.getParent();
+ if (Files.notExists(nativeDir)) {
+ Files.createDirectory(nativeDir);
+ }
+ File tmpHeaderFile = Files.createFile(headerPath).toFile();
+ try (FileOutputStream fos = new FileOutputStream(tmpHeaderFile)) {
+ fos.write(os.toByteArray());
+ return new ProjectHeaderFile.HeaderSearchResult(Optional.of("\"" + tmpHeaderFile.getAbsolutePath() + "\""), tmpHeaderFile.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ VMError.shouldNotReachHere(e);
+ }
+ return null;
+ });
+ return Collections.singletonList(ProjectHeaderFile.resolve("enclave", HEADER_FILE));
+ }
+ }
+
+ @CStruct("enc_data")
+ public interface EncData extends PointerBase {
+ @CField("data_len")
+ int getLen();
+
+ @CField("data_len")
+ void setLen(int len);
+
+ @CField("data")
+ CCharPointer getData();
+
+ @CField("data")
+ void setData(CCharPointer data);
+ }
+
+ @CStruct("callbacks")
+ public interface CallBacks extends PointerBase {
+ @CField("exception_handler")
+ ExceptionHandleFunctionPointer getExceptionHandler();
+
+ @CField("exception_handler")
+ void setExceptionHandler(ExceptionHandleFunctionPointer functionPointer);
+
+ @CField("memcpy_char_pointer")
+ MemCpyCCharPointerFunctionPointer getMemCpyCCharPointerFunctionPointer();
+
+ @CField("memcpy_char_pointer")
+ void setMemCpyCCharPointerFunctionPointer(MemCpyCCharPointerFunctionPointer functionPointer);
+ }
+
+ public interface ExceptionHandleFunctionPointer extends CFunctionPointer {
+ @InvokeCFunctionPointer
+ void invoke(CCharPointer errorMsg, CCharPointer stackTrace, CCharPointer exception);
+ }
+
+ public interface MemCpyCCharPointerFunctionPointer extends CFunctionPointer {
+ @InvokeCFunctionPointer
+ CCharPointer invoke(CCharPointer source, int length);
+ }
+}
diff --git a/sdk/enclave/src/main/resources/META-INF/native-image/reflect-config.json b/sdk/enclave/src/main/resources/META-INF/native-image/reflect-config.json
new file mode 100644
index 0000000..1c5dc62
--- /dev/null
+++ b/sdk/enclave/src/main/resources/META-INF/native-image/reflect-config.json
@@ -0,0 +1,20 @@
+[
+ {
+ "name": "java.security.MessageDigestSpi"
+ },
+ {
+ "name": "[Ljava.lang.Object;"
+ },
+ {
+ "name": "[Ljava.lang.String;"
+ },
+ {
+ "name": "sun.security.provider.SHA",
+ "methods": [
+ {
+ "name": "<init>",
+ "parameterTypes": []
+ }
+ ]
+ }
+]
diff --git a/sdk/enclave/src/main/resources/META-INF/native-image/serialization-config.json b/sdk/enclave/src/main/resources/META-INF/native-image/serialization-config.json
new file mode 100644
index 0000000..45dfb55
--- /dev/null
+++ b/sdk/enclave/src/main/resources/META-INF/native-image/serialization-config.json
@@ -0,0 +1,44 @@
+[
+ {
+ "name":"com.alibaba.confidentialcomputing.common.EnclaveInvocationContext"
+ },
+ {
+ "name":"com.alibaba.confidentialcomputing.common.EnclaveInvocationResult"
+ },
+ {
+ "name":"com.alibaba.confidentialcomputing.common.ServiceHandler"
+ },
+ {
+ "name": "[Lcom.alibaba.confidentialcomputing.common.ServiceHandler;"
+ },
+ {
+ "name":"java.lang.String"
+ },
+ {
+ "name":"java.util.ArrayList"
+ },
+ {
+ "name": "java.util.Arrays$ArrayList"
+ },
+ {
+ "name":"java.lang.Throwable"
+ },
+ {
+ "name":"java.lang.Exception"
+ },
+ {
+ "name":"java.io.IOException"
+ },
+ {
+ "name":"java.io.ObjectStreamException"
+ },
+ {
+ "name":"java.io.NotSerializableException"
+ },
+ {
+ "name":"java.lang.StackTraceElement"
+ },
+ {
+ "name":"[Ljava.lang.StackTraceElement;"
+ }
+]
diff --git a/sdk/enclave/src/main/resources/native/enc_environment.h b/sdk/enclave/src/main/resources/native/enc_environment.h
new file mode 100644
index 0000000..dc6f683
--- /dev/null
+++ b/sdk/enclave/src/main/resources/native/enc_environment.h
@@ -0,0 +1,17 @@
+ typedef struct enc_data_struct{
+ //char array is used as byte array to store serialized data
+ char* data;
+ int data_len;
+ }enc_data;
+
+typedef struct callback_functions_struct{
+ /*
+ * This method is invoked inside java_enclave_invoke method's exception catch
+ * section, when the execution is aborted by exceptions. The caller side can
+ * decide what to do with the exception.
+ * Exception details are passed back with parameters.
+ */
+ void (*exception_handler)(char* err_msg, char* stack_trace, char* exception_name);
+
+ char* (*memcpy_char_pointer)(char* src, int len);
+}callbacks;
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/AroundNativeTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/AroundNativeTest.java
new file mode 100644
index 0000000..ec28e4e
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/AroundNativeTest.java
@@ -0,0 +1,49 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/**
+ * This class holds 2 main classes that are executed before and after maven surefire test by <a href="https://www.mojohaus.org/exec-maven-plugin/">exec-maven-plugin</a>.
+ */
+public class AroundNativeTest {
+ public static final Path tmpTestNativeLibsDir = Paths.get("/tmp/javaenclavetest-native-libs");
+
+ /**
+ * Before test starts, create the temporary directory to hold the dynamic native libraries that will be created
+ * during test. But the directory must be created beforehand, so that the {@code export LD_LIBRARY_PATH} action
+ * taken by surefire plugin can take effect.
+ */
+ public static class PreTest {
+ public static void main(String[] args) throws IOException {
+ if (Files.notExists(tmpTestNativeLibsDir)) {
+ Files.createDirectories(tmpTestNativeLibsDir);
+ }
+ }
+ }
+
+ public static class PostTest {
+ public static void main(String[] args) throws IOException {
+ if (Files.exists(tmpTestNativeLibsDir)) {
+ Files.walkFileTree(tmpTestNativeLibsDir, new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return super.visitFile(file, attrs);
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ Files.delete(dir);
+ return super.postVisitDirectory(dir, exc);
+ }
+ });
+ }
+ }
+ }
+}
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
index f47d64b..c51c90f 100644
--- a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java
@@ -1,12 +1,119 @@
package com.alibaba.confidentialcomputing.enclave;
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationContext;
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+import com.alibaba.confidentialcomputing.common.SerializationHelper;
+import com.alibaba.confidentialcomputing.common.ServiceHandler;
+import com.alibaba.confidentialcomputing.enclave.testservice.IntegerMath;
import com.alibaba.confidentialcomputing.enclave.testservice.MathService;
import com.alibaba.confidentialcomputing.enclave.testservice.NumericMath;
+import com.alibaba.confidentialcomputing.enclave.testservice.PointMath;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Function;
+
+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.fail;
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 INTEGER_MATH = IntegerMath.class.getName();
+ public static final String POINT_MATH = PointMath.class.getName();
public static final String[] MATH_ADD_PARAM_TYPES = {"java.lang.Number", "java.lang.Number"};
+ public static final String[] POINT_MATH_ADD_PARAM_TYPES = {"com.alibaba.confidentialcomputing.enclave.testservice.Point",
+ "com.alibaba.confidentialcomputing.enclave.testservice.Point"};
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+ public static final String VM_NAME = System.getProperty("java.vm.name");
+
+ public static native byte[] invokeEnclave(byte[] data);
+
+ public static native byte[] loadService(byte[] data);
+
+ public static native byte[] unloadService(byte[] data);
+
+ public static native void createIsolate();
+
+ public static native void destroyIsolate();
+
+ public static boolean isInNativeImage() {
+ return VM_NAME.equals("Substrate VM");
+ }
+
+ public static ServiceHandler[] callLoadService(String serviceName) throws IOException, ClassNotFoundException {
+ EnclaveInvocationResult ret = callEnclaveJNI(serviceName,
+ input -> loadService(input));
+ if (ret.getException() != null) {
+ ret.getException().printStackTrace();
+ fail();
+ }
+ return (ServiceHandler[]) ret.getResult();
+ }
+
+ public static EnclaveInvocationResult callEnclaveMethod(String service, String impl, String identity, String method, String[] paramTypes, Object[] values) throws IOException, ClassNotFoundException {
+ EnclaveInvocationContext enclaveInvocationContext = new EnclaveInvocationContext(new ServiceHandler(service, impl
+ , identity), method, paramTypes, values);
+ return callEnclaveJNI(enclaveInvocationContext,
+ input -> invokeEnclave(input));
+ }
+
+ private static EnclaveInvocationResult callEnclaveJNI(Object input, Function<byte[], byte[]> function) throws IOException, ClassNotFoundException {
+ byte[] data = SerializationHelper.serialize(input);
+ byte[] ret = function.apply(data);
+ return (EnclaveInvocationResult) SerializationHelper.deserialize(ret);
+ }
+
+ public static Path createTestTmpDir(Path root) {
+ Path tmpDir;
+ try {
+ tmpDir = Files.createTempDirectory(root, "native-test-");
+ } catch (IOException e) {
+ e.printStackTrace();
+ tmpDir = null;
+ }
+ return tmpDir;
+ }
+
+ public static String loadAndGetService(String serviceName, String implementation, int expectedServiceNum) {
+ ServiceHandler[] serviceHandlers = new ServiceHandler[0];
+ try {
+ serviceHandlers = callLoadService(serviceName);
+ } catch (IOException | ClassNotFoundException e) {
+ fail(e);
+ }
+ assertEquals(expectedServiceNum, serviceHandlers.length);
+ // SVM doesn't guarantee the service order
+ for (ServiceHandler serviceHandler : serviceHandlers) {
+ if (serviceHandler.getServiceImplClassName().equals(implementation)) {
+ return serviceHandler.getInstanceIdentity();
+ }
+ }
+ fail("Should not reach here");
+ return null;
+ }
+
+ public static Object call(String id, String serviceName, String className, String methodName, String[] paramTypes, Object[] paramValues) {
+ try {
+ EnclaveInvocationResult result = callEnclaveMethod(serviceName,
+ className, id,
+ methodName,
+ paramTypes,
+ paramValues);
+ Object wrappedResult = result.getResult();
+ if (result.getException() != null) {
+ result.getException().printStackTrace();
+ }
+ assertNotNull(wrappedResult, "Expect to have non-null result from invoking service method call.");
+ assertNull(result.getException());
+ return wrappedResult;
+ } catch (IOException | ClassNotFoundException e) {
+ fail(e);
+ return 0;
+ }
+ }
}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTest.java
new file mode 100644
index 0000000..ed676da
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTest.java
@@ -0,0 +1,264 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.SequenceInputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public abstract class NativeImageTest implements NativeImageTestable {
+ private static final String JNI_LIB_NAME = "encinvokeentrytest";
+ public static final Path GRAALVM_HOME = Paths.get(System.getenv("GRAALVM_HOME"));
+ public static final Path MVN_BUILD_DIR = Paths.get("target");
+ private static final String SVM_OUT = "svm-out";
+ private static final String SVM_ENCLAVE_LIB = "svm_enclave_sdk";
+
+ static {
+ if (!GRAALVM_HOME.toFile().exists()) {
+ throw new RuntimeException("System environment variable GRAALVM_HOME is set to " + GRAALVM_HOME
+ + ", but the directory does not exist!");
+ }
+ if (!MVN_BUILD_DIR.toFile().exists()) {
+ throw new RuntimeException("Maven default build directory " + MVN_BUILD_DIR.toAbsolutePath() + " doesn't exist." +
+ " Please check your maven's ${project.build.directory} property and make sure it's \"target\".");
+ }
+ }
+
+ protected String testName;
+ protected Path workingDir;
+ protected Path configRootDir;
+ protected Path configPathDir;
+ protected Path svmEncSDKClassDir;
+ protected Path serviceDir;
+ protected Path svmOutputDir;
+ protected Path testClassesDir;
+ protected Path svmCompileClassesDir;
+
+ static class SVMCompileElements {
+ private final List<String> serviceConfigs = new ArrayList<>();
+ private final List<String> svmConfigs = new ArrayList<>();
+ private final List<String> otherResources = new ArrayList<>();
+ private final List<Class<?>> classes = new ArrayList<>();
+
+ public void addServices(String... services) {
+ serviceConfigs.addAll(Arrays.asList(services));
+ }
+
+ public void addSvmConfigs(String... configs) {
+ svmConfigs.addAll(Arrays.asList(configs));
+ }
+
+ public void addResources(String... resources) {
+ otherResources.addAll(Arrays.asList(resources));
+ }
+
+ public void addClasses(Class<?>... classes2Compile) {
+ classes.addAll(Arrays.asList(classes2Compile));
+ }
+ }
+
+ public NativeImageTest() {
+ TestTarget targetTest = this.getClass().getAnnotation(TestTarget.class);
+ if (targetTest == null) {
+ throw new RuntimeException("The subclasses of NativeImageTest must use @TestTarget specify the test target class."
+ + " But class " + this.getClass().getName() + " doesn't have one.");
+ }
+ String targetTestClassName = targetTest.value().getName();
+ int lastDot = targetTestClassName.lastIndexOf('.');
+ testName = targetTestClassName.substring(lastDot + 1).toLowerCase();
+
+ workingDir = MVN_BUILD_DIR.resolve("native-work-dir-" + testName);
+ configRootDir = workingDir.resolve("config");
+ configPathDir = configRootDir.resolve("META-INF/native-image");
+ svmEncSDKClassDir = MVN_BUILD_DIR.resolve("classes");
+ serviceDir = configRootDir.resolve("META-INF/services");
+ svmOutputDir = workingDir.resolve(SVM_OUT);
+ svmCompileClassesDir = workingDir.resolve("bin");
+ List<Path> dirsToCreate = new ArrayList<>();
+ dirsToCreate.add(workingDir);
+ dirsToCreate.add(configRootDir);
+ dirsToCreate.add(configPathDir);
+ dirsToCreate.add(serviceDir);
+ dirsToCreate.add(svmCompileClassesDir);
+ dirsToCreate.forEach(p -> createDirs(p, "Can't create directory " + p + " at test preparation time"));
+ testClassesDir = MVN_BUILD_DIR.resolve("test-classes");
+ }
+
+ public void prepareNativeLibraries() {
+ collectSVMCompileItems();
+ runWithNativeImageAgent();
+ beforeSVMCompile();
+ svmCompile();
+ afterSVMCompile();
+ compileJNILibrary();
+ Path so1 = workingDir.resolve("lib" + JNI_LIB_NAME + ".so");
+ Path so2 = workingDir.resolve("lib" + SVM_ENCLAVE_LIB + ".so");
+ copyFile(so1, AroundNativeTest.tmpTestNativeLibsDir.resolve(so1.getFileName()), null);
+ copyFile(so2, AroundNativeTest.tmpTestNativeLibsDir.resolve(so2.getFileName()), null);
+ System.loadLibrary(JNI_LIB_NAME);
+ }
+
+ private void collectSVMCompileItems() {
+ SVMCompileElements items = specifyTestClasses();
+ if (items == null) {
+ throw new RuntimeException("Must specify the elements to be compiled by native-image for testing.");
+ }
+
+ if (!items.svmConfigs.isEmpty()) {
+ items.svmConfigs.stream().map(s -> testClassesDir.resolve(s).toAbsolutePath()).
+ forEach(p -> copyFile(p, configPathDir.resolve(p.getFileName()), "Fail to copy configuration file."));
+ }
+
+ if (items.classes.isEmpty()) {
+ throw new RuntimeException("Must specify the classes to be compiled by native-image for testing.");
+ } else {
+ items.classes.forEach(c ->
+ {
+ String classLocation = getClassFileName(c);
+ Path destPath = svmCompileClassesDir.resolve(classLocation);
+ createDirs(destPath.getParent(), "Can't create class directories for native-image compilation.");
+ copyFile(testClassesDir.resolve(classLocation).toAbsolutePath(), destPath, "Can't copy class file.");
+ });
+ }
+
+ if (!items.serviceConfigs.isEmpty()) {
+ items.serviceConfigs.stream().map(s -> testClassesDir.resolve(s).toAbsolutePath()).
+ forEach(p -> copyFile(p, serviceDir.resolve(p.getFileName()), null));
+ }
+
+ if (!items.otherResources.isEmpty()) {
+ items.otherResources.forEach(s -> {
+ Path destPath = svmCompileClassesDir.resolve(s);
+ createDirs(destPath.getParent(), "Can't create resource directories for native-image compilation.");
+ copyFile(testClassesDir.resolve(s).toAbsolutePath(), destPath, "Can't copy resource file.");
+ });
+ }
+ }
+
+ abstract SVMCompileElements specifyTestClasses();
+
+ protected void svmCompile() {
+ List<String> command = new ArrayList<>();
+ command.add(0, GRAALVM_HOME.resolve("bin/native-image").toString());
+ command.add("-cp");
+ StringBuilder sb = new StringBuilder();
+ List<Path> svmBinFiles = Arrays.asList(svmCompileClassesDir,
+ configRootDir,
+ svmEncSDKClassDir,
+ MVN_BUILD_DIR.toAbsolutePath().getParent().getParent().resolve("common/target/classes")
+ );
+ svmBinFiles.stream().map(p -> p.normalize().toAbsolutePath()).forEach(p -> {
+ if (Files.notExists(p)) {
+ throw new RuntimeException("File " + p + " on native-image class file doesn't exist.");
+ }
+ sb.append(p.toString()).append(File.pathSeparator);
+ });
+ command.add(sb.deleteCharAt(sb.length() - 1).toString());
+ command.add("--shared");
+ command.add("--no-fallback");
+ command.add("-H:OutputRelocatableImage=.");
+ command.add("-H:Path=" + SVM_OUT);
+ command.add("-H:+AllowIncompleteClasspath");
+ command.add("-H:+ReportExceptionStackTraces");
+ command.add("-H:Name=lib" + SVM_ENCLAVE_LIB);
+ command.add("-H:-DeleteLocalSymbols");
+ List<String> extraOptions = extraSVMOptions();
+ if (extraOptions != null && !extraOptions.isEmpty()) {
+ command.addAll(extraOptions);
+ }
+ NativeImageTest.executeNewProcess(command, workingDir);
+ }
+
+ private void compileJNILibrary() {
+ System.out.println("###Prepare JNI library ...###");
+ List<Path> requiredFilePaths = new ArrayList<>();
+ requiredFilePaths.add(testClassesDir.resolve("native/com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h"));
+ requiredFilePaths.add(testClassesDir.resolve("native/enc_invoke_entry_test.c"));
+ requiredFilePaths.add(svmOutputDir.resolve("lib" + SVM_ENCLAVE_LIB + ".h"));
+ requiredFilePaths.add(svmOutputDir.resolve("graal_isolate.h"));
+ requiredFilePaths.add(svmOutputDir.resolve("enc_environment.h"));
+ requiredFilePaths.add(svmOutputDir.resolve("lib" + SVM_ENCLAVE_LIB + ".so"));
+ requiredFilePaths.forEach(p -> copyFile(p, workingDir.resolve(p.getFileName()), null));
+
+ List<String> command = new ArrayList<>();
+ command.add("gcc");
+ command.add("-fPIC");
+ command.add("-I" + GRAALVM_HOME.toAbsolutePath() + "/include");
+ command.add("-I" + GRAALVM_HOME.toAbsolutePath() + "/include/linux");
+ command.add("enc_invoke_entry_test.c");
+ command.add("-I.");
+ command.add("-L.");
+ command.add("-std=c99");
+ command.add("-l" + SVM_ENCLAVE_LIB);
+ command.add("-lc");
+ command.add("-shared");
+ command.add("-o");
+ command.add("lib" + JNI_LIB_NAME + ".so");
+ executeNewProcess(command, workingDir);
+ }
+
+ public static int executeNewProcess(List<String> command, Path workDir) {
+ if (command == null || command.isEmpty()) {
+ throw new RuntimeException("Didn't provide any execution command.");
+ }
+ ProcessBuilder pb = new ProcessBuilder(command).directory(workDir.toFile());
+ String oneLineCommand = command.stream().reduce((e1, e2) -> e1 + " " + e2).orElse("");
+ System.out.println(oneLineCommand);
+ Process p = null;
+ try {
+ p = pb.start();
+ SequenceInputStream sis = new SequenceInputStream(p.getInputStream(), p.getErrorStream());
+ InputStreamReader inst = new InputStreamReader(sis, StandardCharsets.UTF_8);
+ StringBuilder sb = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(inst)) {
+ String res;
+ while ((res = br.readLine()) != null) {
+ sb.append(res).append("\n");
+ }
+ }
+ System.out.println(sb);
+ int exitCode = p.waitFor();
+ if (exitCode != 0) {
+ throw new RuntimeException("Failed to execute command:\n " + oneLineCommand +
+ "\n Working directory is :" + workDir.toString() + "\n The exit code is " + exitCode);
+ }
+ return 0;
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Failed to execute command:\n " + oneLineCommand, e);
+ } finally {
+ if (p != null) {
+ p.destroy();
+ }
+ }
+ }
+
+ public static void copyFile(Path source, Path dest, String errMSg) {
+ try {
+ Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException(errMSg, e);
+ }
+ }
+
+ private static void createDirs(Path p, String errMsg) {
+ if (Files.notExists(p)) {
+ try {
+ Files.createDirectories(p);
+ } catch (IOException e) {
+ throw new RuntimeException(errMsg, e);
+ }
+ }
+ }
+
+ public static String getClassFileName(Class<?> clazz) {
+ return clazz.getName().replace('.', '/') + ".class";
+ }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTestable.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTestable.java
new file mode 100644
index 0000000..7afe107
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTestable.java
@@ -0,0 +1,12 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import java.util.List;
+
+public interface NativeImageTestable {
+ default void runWithNativeImageAgent(){}
+ default void beforeSVMCompile(){}
+ default void afterSVMCompile(){}
+ default List<String> extraSVMOptions() {
+ return null;
+ }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/RunWithNativeImageTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/RunWithNativeImageTest.java
new file mode 100644
index 0000000..44ad76e
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/RunWithNativeImageTest.java
@@ -0,0 +1,166 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import com.alibaba.confidentialcomputing.enclave.testservice.IntegerMath;
+import com.alibaba.confidentialcomputing.enclave.testservice.MathService;
+import com.alibaba.confidentialcomputing.enclave.testservice.NumericMath;
+import com.alibaba.confidentialcomputing.enclave.testservice.Point;
+import com.alibaba.confidentialcomputing.enclave.testservice.PointMath;
+import com.oracle.svm.core.annotate.AutomaticFeature;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.nativeimage.hosted.RuntimeSerialization;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.security.NoSuchAlgorithmException;
+
+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.INTEGER_MATH;
+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 com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.POINT_MATH;
+import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.POINT_MATH_ADD_PARAM_TYPES;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class RunWithNativeImageTest {
+
+ @TestTarget(RunWithNativeImageTest.class)
+ static
+ class SimpleRunPreparation extends NativeImageTest {
+
+ @Override
+ public SVMCompileElements specifyTestClasses() {
+ SVMCompileElements ret = new SVMCompileElements();
+ // Specify the service file
+ ret.addServices("META-INF/services/com.alibaba.confidentialcomputing.enclave.testservice.MathService");
+
+ // Specify the classes need to be statically compiled into native image for this test
+ ret.addClasses(
+ UTFeature.class, MathService.class, NumericMath.class, PointMath.class, Point.class, IntegerMath.class
+ );
+ return ret;
+ }
+ }
+
+ static private NativeImageTest nativeImageTest;
+
+ @BeforeAll
+ public static void prepareLibraries() {
+ nativeImageTest = new SimpleRunPreparation();
+ nativeImageTest.prepareNativeLibraries();
+ }
+
+ @BeforeEach
+ public void setup() {
+ EnclaveTestHelper.createIsolate();
+ }
+
+ @AfterEach
+ public void teardown() {
+ EnclaveTestHelper.destroyIsolate();
+ }
+
+ /**
+ * Test calling a method with primitive parameters and returned value.
+ */
+ @Test
+ public void testSimpleRun() {
+ String identity = loadAndGetService(NUMERIC_MATH);
+ assertEquals(3, callIntAdd(identity, 1, 2));
+ }
+
+ /**
+ * Test a multithreading case.
+ */
+ @Test
+ public void testMultiThreadRun() {
+ String identity1 = loadAndGetService(NUMERIC_MATH);
+ String identity2 = loadAndGetService(NUMERIC_MATH);
+
+ Thread t1 = new Thread(() -> {
+ try {
+ assertEquals(3, callIntAdd(identity1, 1, 2));
+ } catch (Exception e) {
+ fail(e);
+ }
+ });
+ Thread t2 = new Thread(() -> {
+ try {
+ assertEquals(9, callIntAdd(identity1, 4, 5));
+ } catch (Exception e) {
+ fail(e);
+ }
+ });
+ Thread t3 = new Thread(() -> {
+ try {
+ assertEquals(9, callIntAdd(identity2, 4, 5));
+ } catch (Exception e) {
+ fail(e);
+ }
+ });
+ t1.start();
+ t2.start();
+ t3.start();
+ try {
+ t1.join();
+ t2.join();
+ t3.join();
+ } catch (InterruptedException e) {
+ fail(e);
+ }
+ assertEquals(2, callGetCounter(identity1));
+ assertEquals(1, callGetCounter(identity2));
+ }
+
+ /**
+ * Test calling an interface default method.
+ */
+ @Test
+ public void testServiceDefaultMethod() {
+ String identity = loadAndGetService(INTEGER_MATH);
+ int ret = (Integer) call(identity, INTEGER_MATH, "getConstant", EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY);
+ assertEquals(100, ret);
+ }
+
+ /**
+ * Test calling a method with referenced type of parameters and returned values.
+ */
+ @Test
+ public void testPointAdd() {
+ String id = loadAndGetService(POINT_MATH);
+ Point ret = (Point) call(id, POINT_MATH, "add", POINT_MATH_ADD_PARAM_TYPES,
+ new Object[]{new Point(1, 1), new Point(2, 2)});
+ assertEquals(3, ret.x);
+ assertEquals(3, ret.y);
+ }
+
+ private static String loadAndGetService(String implementation) {
+ return EnclaveTestHelper.loadAndGetService(MATH_SERVICE, implementation, 3);
+ }
+
+ private static int callIntAdd(String id, int x, int y) {
+ return (Integer) call(id, NUMERIC_MATH, "add", MATH_ADD_PARAM_TYPES, new Object[]{x, y});
+ }
+
+ private static int callGetCounter(String id) {
+ return (Integer) call(id, NUMERIC_MATH, "getCounter", EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY);
+ }
+
+ private static Object call(String id, String className, String methodName, String[] paramTypes, Object[] paramValues) {
+ return EnclaveTestHelper.call(id, MATH_SERVICE, className, methodName, paramTypes, paramValues);
+ }
+
+ @AutomaticFeature
+ static class UTFeature implements Feature {
+
+ @Override
+ public void beforeAnalysis(BeforeAnalysisAccess access) {
+ RuntimeSerialization.register(Number.class, NoSuchAlgorithmException.class);
+ }
+ }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/SVMSimpleEnclaveCallTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/SVMSimpleEnclaveCallTest.java
new file mode 100644
index 0000000..af9732c
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/SVMSimpleEnclaveCallTest.java
@@ -0,0 +1,123 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationContext;
+import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult;
+import com.alibaba.confidentialcomputing.common.SerializationHelper;
+import com.alibaba.confidentialcomputing.common.ServiceHandler;
+import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.CallBacks;
+import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.EncData;
+import org.graalvm.nativeimage.Isolate;
+import org.graalvm.nativeimage.IsolateThread;
+import org.graalvm.nativeimage.Isolates;
+import org.graalvm.nativeimage.StackValue;
+import org.graalvm.nativeimage.c.type.CTypeConversion;
+import org.graalvm.word.WordFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+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 com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.isInNativeImage;
+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.fail;
+
+public class SVMSimpleEnclaveCallTest {
+ private IsolateThread isolateThread;
+
+ @BeforeEach
+ public void setup() {
+ if (isInNativeImage()) {
+ isolateThread = Isolates.createIsolate(Isolates.CreateIsolateParameters.getDefault());
+ }
+ }
+
+ @AfterEach
+ public void teardown() {
+ if (isInNativeImage()) {
+ Isolates.tearDownIsolate(isolateThread);
+ }
+ }
+
+ @Test
+ public void test() throws IOException, ClassNotFoundException {
+ if (!isInNativeImage()) {
+ return;
+ }
+ ServiceHandler[] serviceHandlers = callLoadService(MATH_SERVICE);
+ assertEquals(3, serviceHandlers.length);
+ assertEquals(NUMERIC_MATH, serviceHandlers[0].getServiceImplClassName());
+ String identity = serviceHandlers[0].getInstanceIdentity();
+ EnclaveInvocationResult result = callEnclaveMethod(MATH_SERVICE,
+ NUMERIC_MATH, identity,
+ "add",
+ MATH_ADD_PARAM_TYPES,
+ new Object[]{1, 2});
+ assertNotNull(result);
+ Object wrappedResult = result.getResult();
+ if (result.getException() != null) {
+ result.getException().printStackTrace();
+ }
+ assertNotNull(wrappedResult, "Expect to have non-null result from invoking service method call.");
+ assertNull(result.getException());
+ assertEquals(3, (Integer) wrappedResult);
+ }
+
+ private ServiceHandler[] callLoadService(String serviceName) throws IOException, ClassNotFoundException {
+ EnclaveInvocationResult ret = callEnclaveEntryPoint(serviceName,
+ (isolateThread, input, result, callbacks) -> EnclaveEntry.loadService(isolateThread, input, result, callbacks));
+ if (ret.getException() != null) {
+ ret.getException().printStackTrace();
+ fail();
+ }
+ return (ServiceHandler[]) ret.getResult();
+ }
+
+ private EnclaveInvocationResult callUnloadService(String serviceName) throws IOException, ClassNotFoundException {
+ return callEnclaveEntryPoint(serviceName,
+ (isolateThread, input, result, callbacks) -> EnclaveEntry.unloadService(isolateThread, input, result, callbacks));
+ }
+
+ private EnclaveInvocationResult callEnclaveMethod(String service, String impl, String identity, String method, String[] paramTypes, Object[] values) throws IOException, ClassNotFoundException {
+ EnclaveInvocationContext enclaveInvocationContext = new EnclaveInvocationContext(new ServiceHandler(service, impl
+ , identity), method, paramTypes, values);
+ return callEnclaveEntryPoint(enclaveInvocationContext,
+ (isolateThread, input, result, callbacks) -> EnclaveEntry.javaEnclaveInvoke(isolateThread, input, result, callbacks));
+ }
+
+ private EnclaveInvocationResult callEnclaveEntryPoint(Object input, Caller caller) throws IOException, ClassNotFoundException {
+ byte[] data = SerializationHelper.serialize(input);
+ EncData entryInput = StackValue.get(EncData.class);
+ EncData entryResult = StackValue.get(EncData.class);
+ try (CTypeConversion.CCharPointerHolder byteHolder = CTypeConversion.toCBytes(data)) {
+ entryInput.setData(byteHolder.get());
+ entryInput.setLen(data.length);
+ }
+ CallBacks callBacks = StackValue.get(CallBacks.class);
+ callBacks.setExceptionHandler(WordFactory.nullPointer());
+ callBacks.setMemCpyCCharPointerFunctionPointer(WordFactory.nullPointer());
+ int ret = caller.call(Isolates.getIsolate(isolateThread), entryInput, entryResult, callBacks);
+ if (ret == 0) {
+ return (EnclaveInvocationResult) SerializationHelper.deserialize(transformInput(entryResult));
+ } else {
+ throw new RuntimeException("Fail to execute enclave method, return value is " + ret);
+ }
+ }
+
+ @FunctionalInterface
+ private interface Caller {
+ int call(Isolate isolate, EncData input, EncData result, CallBacks callBacks);
+ }
+
+ private static byte[] transformInput(EncData input) {
+ int len = input.getLen();
+ byte[] data = new byte[len];
+ CTypeConversion.asByteBuffer(input.getData(), len).get(data);
+ return data;
+ }
+}
diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/TestTarget.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/TestTarget.java
new file mode 100644
index 0000000..f604331
--- /dev/null
+++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/TestTarget.java
@@ -0,0 +1,12 @@
+package com.alibaba.confidentialcomputing.enclave;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TestTarget {
+ Class<?> value();
+}
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
index 7a7dc11..b0fea25 100644
--- 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
@@ -6,6 +6,7 @@ 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.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -14,10 +15,12 @@ import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.EMPTY_
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 com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.isInNativeImage;
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;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
import java.lang.reflect.InvocationTargetException;
@@ -26,6 +29,11 @@ public class ServiceMethodInvokerTest {
private static ServiceMethodInvoker serviceMethodInvoker = new ServiceMethodInvoker();
private ServiceHandler[] services;
+ @BeforeAll
+ public static void svmCheck(){
+ assumeFalse(isInNativeImage());
+ }
+
@BeforeEach
public void setup() {
services = EnclaveContext.getInstance().loadService(MathService.class);
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
index 726e8e5..1376e94 100644
--- 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
@@ -4,18 +4,26 @@ 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.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_SERVICE;
+import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.isInNativeImage;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
/**
* This class tests loading and unloading service.
*/
public class ServiceOperationTest {
+ @BeforeAll
+ public static void svmCheck(){
+ assumeFalse(isInNativeImage());
+ }
+
@Test
public void testLoadingAndUnloading() {
LoadServiceInvoker loadServiceInvoker = new LoadServiceInvoker();
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
index 9a01c09..8df60c8 100644
--- 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
@@ -1,5 +1,8 @@
package com.alibaba.confidentialcomputing.enclave.testservice;
+import com.alibaba.confidentialcomputing.common.annotations.EnclaveService;
+
+@EnclaveService
public interface MathService<T> {
T add(T x, T y);
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
index f252d7f..31623be 100644
--- 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
@@ -1,8 +1,13 @@
package com.alibaba.confidentialcomputing.enclave.testservice;
-public class Point {
- int x;
- int y;
+import java.io.Serializable;
+
+public class Point implements Serializable {
+
+ private static final long serialVersionUID = -3715916707782706029L;
+
+ public int x;
+ public int y;
public Point(int x, int y){
this.x = x;
diff --git a/sdk/enclave/src/test/resources/native/com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h b/sdk/enclave/src/test/resources/native/com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h
new file mode 100644
index 0000000..18b564b
--- /dev/null
+++ b/sdk/enclave/src/test/resources/native/com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h
@@ -0,0 +1,53 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper */
+
+#ifndef _Included_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper
+#define _Included_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper
+ * Method: invokeEnclave
+ * Signature: ([B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_invokeEnclave
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper
+ * Method: loadService
+ * Signature: ([B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_loadService
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper
+ * Method: unloadService
+ * Signature: ([B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_unloadService
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper
+ * Method: createIsolate
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_createIsolate
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper
+ * Method: destroyIsolate
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_destroyIsolate
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/sdk/enclave/src/test/resources/native/enc_invoke_entry_test.c b/sdk/enclave/src/test/resources/native/enc_invoke_entry_test.c
new file mode 100644
index 0000000..8faa6f1
--- /dev/null
+++ b/sdk/enclave/src/test/resources/native/enc_invoke_entry_test.c
@@ -0,0 +1,76 @@
+#include <stdlib.h>
+#include <string.h>
+#include "com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h"
+#include "enc_environment.h"
+#ifdef MUSL
+#include "libmusl_svmenclavesdk.h"
+#else
+#include "libsvm_enclave_sdk.h"
+#endif
+
+typedef int (*enclave_invoke)(graal_isolate_t* isolate, enc_data* input, enc_data* result, callbacks* callBacks);
+
+ char* memcpy_char_pointer(char* src, int len){
+ int size = sizeof(char);
+ char *dest = (char*)malloc(len*size);
+ memcpy(dest, src, len);
+ return dest;
+ }
+
+static graal_isolatethread_t *thread = NULL;
+static graal_isolate_t *isolate = NULL;
+
+jbyteArray enclave_call(JNIEnv* env, jclass clazz, jbyteArray data, enclave_invoke invoke){
+jboolean isCopy;
+ jbyte* a = (*env)->GetByteArrayElements(env, data, &isCopy);
+ enc_data invoke_data;
+ invoke_data.data=(char*)a;
+ invoke_data.data_len=(*env)->GetArrayLength(env, data);
+
+ callbacks callback_methods;
+ callback_methods.memcpy_char_pointer=&memcpy_char_pointer;
+ callback_methods.exception_handler=NULL; // Must explicitly set
+
+ enc_data ret;
+ int exit_code = invoke(isolate, &invoke_data, &ret, &callback_methods);
+ jbyteArray retVal;
+ if(exit_code == 0 ){
+ retVal = (*env)->NewByteArray(env, ret.data_len);
+ jbyte *buf = (*env)->GetByteArrayElements(env, retVal, NULL);
+ memcpy(buf, ret.data, ret.data_len);
+ (*env)->ReleaseByteArrayElements(env, retVal, buf, 0);
+ //printf("Returned type is %.*s\n", ret.verify_info_len, ret.verify_info);
+ free(ret.data);
+ }else{
+ retVal = NULL;
+ }
+ return retVal;
+}
+
+JNIEXPORT void JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_createIsolate
+ (JNIEnv *env, jclass clazz){
+ if (graal_create_isolate(NULL, &isolate, &thread) != 0) {
+ fprintf(stderr, "error on isolate creation or attach\n");
+ }
+}
+
+JNIEXPORT void JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_destroyIsolate
+ (JNIEnv *env, jclass clazz){
+ //graal_tear_down_isolate(thread);
+ graal_detach_all_threads_and_tear_down_isolate(thread);
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_loadService
+(JNIEnv* env, jclass clazz, jbyteArray data) {
+ return enclave_call(env, clazz, data, java_loadservice_invoke);
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_unloadService
+ (JNIEnv* env, jclass clazz, jbyteArray data){
+ return enclave_call(env, clazz, data, java_unloadservice_invoke);
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_invokeEnclave
+(JNIEnv* env, jclass clazz, jbyteArray data) {
+ return enclave_call(env, clazz, data, java_enclave_invoke);
+}
diff --git a/tools/cicd/Dockerfile b/tools/cicd/Dockerfile
index c3a7c4d..f988bd3 100644
--- a/tools/cicd/Dockerfile
+++ b/tools/cicd/Dockerfile
@@ -7,6 +7,9 @@ ENV DEBIAN_FRONTEND noninteractive
# install openjdk11 and maven.
RUN apt-get update && \
- echo -e 'yes\n' | apt-get install -y openjdk-11-jdk && \
echo -e 'yes\n' | apt-get install -y maven && \
+ echo -e 'yes\n' | apt-get install -y build-essential libz-dev zlib1g-dev && \
echo -e 'yes\n' | apt-get install -y wget
+ADD ["graalvm-enclave-22.0.0.tar", "/root/tools/"]
+ENV GRAALVM_HOME "/root/tools/graalvm-enclave-22.0.0"
+ENV JAVA_HOME "/root/tools/graalvm-enclave-22.0.0"
diff --git a/tools/cicd/make.sh b/tools/cicd/make.sh
index 0120911..04657d5 100755
--- a/tools/cicd/make.sh
+++ b/tools/cicd/make.sh
@@ -1,7 +1,7 @@
#!/bin/bash
BUILD_IMAGE=javaenclave_build
-BUILD_TAG=v0.1.1
+BUILD_TAG=v0.1.2
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
@@ -11,7 +11,11 @@ WORKDIR=$(dirname $(dirname "$PWD"))
# check target images exist or not, build it if not.
if [[ "$(docker images -q ${BUILD_IMAGE}:${BUILD_TAG} 2> /dev/null)" == "" ]]; then
+ # Get the customized Graal VM from git@gitlab.alibaba-inc.com:graal/SGXGraalVM.git
+ # This should be replaced to the offical version when all patches are accepted by the Graal community
+ wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/graalvm-enclave-22.0.0.tar
docker build -t ${BUILD_IMAGE}:${BUILD_TAG} .
+ rm -f graalvm-enclave-22.0.0.tar
fi
# test JavaEnclave's unit test cases and samples
@@ -19,3 +23,4 @@ docker run -i --rm --privileged --network host \
-w "${WORKDIR}" \
-v "${HOME}"/.m2:/root/.m2 -v "${WORKDIR}":"${WORKDIR}" \
${BUILD_IMAGE}:${BUILD_TAG} /bin/bash build.sh
+
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@teaclave.apache.org
For additional commands, e-mail: commits-help@teaclave.apache.org