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

mssun pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-teaclave.git


The following commit(s) were added to refs/heads/develop by this push:
     new 06a9410  [services] Add access control service (#216)
06a9410 is described below

commit 06a9410b382c3c5ed3985ab723c4513a168e4bd3
Author: TX <[email protected]>
AuthorDate: Tue Feb 4 15:12:48 2020 -0800

    [services] Add access control service (#216)
---
 CMakeLists.txt                                     |  39 +-
 cmake/scripts/sgx_link_sign.sh                     |   2 +-
 cmake/scripts/test.sh                              |   1 +
 cmake/tomls/Cargo.sgx_trusted_lib.toml             |   1 +
 cmake/tomls/Cargo.sgx_untrusted_app.toml           |   1 +
 config/runtime.config.toml                         |   1 +
 config/src/runtime.rs                              |   1 +
 services/access_control/app/Cargo.toml             |  19 +
 services/access_control/app/build.rs               |  51 ++
 services/access_control/app/src/main.rs            |  48 ++
 services/access_control/enclave/Cargo.toml         |  48 ++
 services/access_control/enclave/Enclave.config.xml |  12 +
 services/access_control/enclave/src/acs.rs         | 341 +++++++++++
 services/access_control/enclave/src/lib.rs         | 112 ++++
 services/access_control/enclave/src/service.rs     | 350 ++++++++++++
 services/access_control/model.conf                 |  28 +
 services/access_control/python/acs_engine.py       | 631 +++++++++++++++++++++
 services/access_control/python/acs_engine_test.py  |  82 +++
 services/access_control/python/ffi.py              |  15 +
 services/proto/build.rs                            |   1 +
 services/proto/src/lib.rs                          |   5 +
 .../proto/teaclave_access_control_service.proto    |  48 ++
 .../proto/src/teaclave_access_control_service.rs   | 219 +++++++
 tests/fixtures/runtime.config.toml                 |   1 +
 tests/functional_tests/enclave/src/lib.rs          |   2 +
 .../enclave/src/teaclave_access_control_service.rs | 222 ++++++++
 tests/unit_tests/enclave/Cargo.toml                |   3 +
 tests/unit_tests/enclave/src/lib.rs                |   4 +-
 28 files changed, 2266 insertions(+), 22 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 81bc9f3..1f4e722 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -92,24 +92,24 @@ foreach(_i RANGE ${SGX_APP_LAST_INDEX})
 endforeach()
 
 # mesapy components
-# add_custom_command(
-#     OUTPUT ${MESATEE_OUT_DIR}/acs_py_enclave.c
-#     COMMAND env
-#     ARGS PYTHONPATH=${PROJECT_SOURCE_DIR}/third_party/mesapy/sgx
-#          PYPY_FFI_OUTDIR=${MESATEE_OUT_DIR}
-#          pypy ${PROJECT_SOURCE_DIR}/mesatee_services/acs/python/ffi.py
-#     DEPENDS prep
-#             ${PROJECT_SOURCE_DIR}/mesatee_services/acs/python/ffi.py
-#             ${PROJECT_SOURCE_DIR}/mesatee_services/acs/python/acs_engine.py
-#     COMMENT "Generating mesapy ffi stubs"
-# )
-# add_library(pycomponent STATIC ${MESATEE_OUT_DIR}/acs_py_enclave.c)
-# set_target_properties(pycomponent PROPERTIES ARCHIVE_OUTPUT_DIRECTORY 
${MESATEE_OUT_DIR})
-# target_compile_definitions(pycomponent PUBLIC SGX)
-# if(NOT EXISTS "/usr/lib/pypy/include")
-#     message(FATAL_ERROR "pypy development package not found\nFor Ubuntu, 
please run `apt-get install pypy-dev`")
-# endif()
-# target_compile_options(pycomponent PUBLIC -UWITH_THREAD -O2 -fPIC -Wimplicit 
-I/usr/lib/pypy/include)
+add_custom_command(
+     OUTPUT ${MESATEE_OUT_DIR}/acs_py_enclave.c
+     COMMAND env
+     ARGS PYTHONPATH=${PROJECT_SOURCE_DIR}/third_party/mesapy/sgx
+          PYPY_FFI_OUTDIR=${MESATEE_OUT_DIR}
+          pypy ${PROJECT_SOURCE_DIR}/services/access_control/python/ffi.py
+     DEPENDS prep
+             ${PROJECT_SOURCE_DIR}/services/access_control/python/ffi.py
+             ${PROJECT_SOURCE_DIR}/services/access_control/python/acs_engine.py
+     COMMENT "Generating mesapy ffi stubs"
+)
+add_library(pycomponent STATIC ${MESATEE_OUT_DIR}/acs_py_enclave.c)
+set_target_properties(pycomponent PROPERTIES ARCHIVE_OUTPUT_DIRECTORY 
${MESATEE_OUT_DIR})
+target_compile_definitions(pycomponent PUBLIC SGX)
+if(NOT EXISTS "/usr/lib/pypy/include")
+    message(FATAL_ERROR "pypy development package not found\nFor Ubuntu, 
please run `apt-get install pypy-dev`")
+endif()
+target_compile_options(pycomponent PUBLIC -UWITH_THREAD -O2 -fPIC -Wimplicit 
-I/usr/lib/pypy/include)
 
 
 # sgx_trusted_lib
@@ -120,8 +120,7 @@ foreach(_i RANGE ${SGX_LIB_LAST_INDEX})
     list(GET SGX_LIB_PATHS ${_i} _pkg_path)
     list(GET SGX_LIB_CATEGORIES ${_i} _category)
     add_sgx_build_target(${_pkg_path} ${_pkg_name}
-        # TODO: DEPENDS prep pycomponent
-        DEPENDS prep
+        DEPENDS prep pycomponent
         INSTALL_DIR ${MESATEE_INSTALL_DIR}/${_category}
     )
 endforeach()
diff --git a/cmake/scripts/sgx_link_sign.sh b/cmake/scripts/sgx_link_sign.sh
index 84a6b45..4432239 100755
--- a/cmake/scripts/sgx_link_sign.sh
+++ b/cmake/scripts/sgx_link_sign.sh
@@ -28,7 +28,7 @@ ${CMAKE_C_COMPILER} libEnclave_t.o -o \
     -Wl,--no-whole-archive -Wl,--start-group \
     -l${Service_Library_Name} -lsgx_tprotected_fs -lsgx_tkey_exchange \
     -lsgx_tstdc -lsgx_tcxx -lsgx_tservice -lsgx_tcrypto \
-    -L${MESATEE_OUT_DIR} ffi.o -lpypy-c -lsgx_tlibc_ext -lffi \
+    -L${MESATEE_OUT_DIR} -lpycomponent ffi.o -lpypy-c -lsgx_tlibc_ext -lffi \
     -L${TRUSTED_TARGET_DIR}/${TARGET} -l${CUR_PKG_NAME} -Wl,--end-group \
     -Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined \
     -Wl,-pie,-eenclave_entry -Wl,--export-dynamic  \
diff --git a/cmake/scripts/test.sh b/cmake/scripts/test.sh
index 3303053..b3fad6e 100755
--- a/cmake/scripts/test.sh
+++ b/cmake/scripts/test.sh
@@ -37,6 +37,7 @@ cargo test --manifest-path 
${MESATEE_PROJECT_ROOT}/common/protected_fs_rs/Cargo.
 echo_title "functional tests"
 trap 'kill $(jobs -p)' EXIT
 pushd ${MESATEE_SERVICE_INSTALL_DIR}
+./teaclave_access_control_service &
 ./teaclave_authentication_service &
 sleep 3    # wait for authentication service
 ./teaclave_storage_service &
diff --git a/cmake/tomls/Cargo.sgx_trusted_lib.toml 
b/cmake/tomls/Cargo.sgx_trusted_lib.toml
index f8f805d..33be7e8 100644
--- a/cmake/tomls/Cargo.sgx_trusted_lib.toml
+++ b/cmake/tomls/Cargo.sgx_trusted_lib.toml
@@ -1,6 +1,7 @@
 [workspace]
 
 members = [
+  "services/access_control/enclave",
   "services/authentication/enclave",
   "services/storage/enclave",
   "services/execution/enclave",
diff --git a/cmake/tomls/Cargo.sgx_untrusted_app.toml 
b/cmake/tomls/Cargo.sgx_untrusted_app.toml
index f7517a9..ab4268c 100644
--- a/cmake/tomls/Cargo.sgx_untrusted_app.toml
+++ b/cmake/tomls/Cargo.sgx_untrusted_app.toml
@@ -1,6 +1,7 @@
 [workspace]
 
 members = [
+  "services/access_control/app",
   "services/authentication/app",
   "services/storage/app",
   "services/execution/app",
diff --git a/config/runtime.config.toml b/config/runtime.config.toml
index 3de5dda..b34cce5 100644
--- a/config/runtime.config.toml
+++ b/config/runtime.config.toml
@@ -9,6 +9,7 @@ authentication = { listen_address = "0.0.0.0:7776" }
 frontend = { listen_address = "0.0.0.0:7777" }
 
 [internal_endpoints]
+access_control = { listen_address = "0.0.0.0:7779", advertised_address = 
"localhost:7779" }
 authentication = { listen_address = "0.0.0.0:17776", advertised_address = 
"localhost:17776", inbound_services = ["frontend"] }
 management = { listen_address = "0.0.0.0:17777", advertised_address = 
"localhost:17777" }
 storage = { listen_address = "0.0.0.0:7778", advertised_address = 
"127.0.0.1:7778", inbound_services = ["frontend"] }
diff --git a/config/src/runtime.rs b/config/src/runtime.rs
index 04cf6b0..8c6ba6c 100644
--- a/config/src/runtime.rs
+++ b/config/src/runtime.rs
@@ -31,6 +31,7 @@ pub struct ApiEndpointsConfig {
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct InternalEndpointsConfig {
+    pub access_control: InternalEndpoint,
     pub authentication: InternalEndpoint,
     pub management: InternalEndpoint,
     pub storage: InternalEndpoint,
diff --git a/services/access_control/app/Cargo.toml 
b/services/access_control/app/Cargo.toml
new file mode 100644
index 0000000..c3df477
--- /dev/null
+++ b/services/access_control/app/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "teaclave_access_control_service"
+version = "0.1.0"
+authors = ["Teaclave Contributors <[email protected]>"]
+description = "Teaclave Access Control Service"
+license = "Apache-2.0"
+build = "build.rs"
+edition = "2018"
+
+[dependencies]
+log        = { version = "0.4.6" }
+env_logger = { version = "0.7.1" }
+anyhow     = { version = "1.0.26" }
+
+teaclave_ipc               = { path = "../../../ipc" }
+teaclave_binder            = { path = "../../../binder" }
+teaclave_config            = { path = "../../../config" }
+
+sgx_types = { version = "1.1.0" }
diff --git a/services/access_control/app/build.rs 
b/services/access_control/app/build.rs
new file mode 100644
index 0000000..b92afb5
--- /dev/null
+++ b/services/access_control/app/build.rs
@@ -0,0 +1,51 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::env;
+use std::path::PathBuf;
+
+fn choose_sgx_dylib(is_sim: bool) {
+    if is_sim {
+        println!("cargo:rustc-link-lib=dylib=sgx_urts_sim");
+        println!("cargo:rustc-link-lib=dylib=sgx_uae_service_sim");
+    } else {
+        println!("cargo:rustc-link-lib=dylib=sgx_urts");
+        println!("cargo:rustc-link-lib=dylib=sgx_uae_service");
+    }
+}
+
+fn main() {
+    let sdk_dir = env::var("SGX_SDK").unwrap_or("/opt/intel/sgxsdk".into());
+    println!("cargo:rustc-link-search=native={}/lib64", sdk_dir);
+
+    let out_path = env::var_os("ENCLAVE_OUT_DIR").unwrap_or("out".into());
+    let out_dir = &PathBuf::from(out_path);
+
+    println!("cargo:rustc-link-search=native={}", out_dir.display());
+    println!("cargo:rustc-link-lib=static=Enclave_u");
+
+    let is_sim = match env::var("SGX_MODE") {
+        Ok(ref v) if v == "SW" => true,
+        Ok(ref v) if v == "HW" => false,
+        Err(env::VarError::NotPresent) => false,
+        _ => {
+            panic!("Stop build process, wrong SGX_MODE env provided.");
+        }
+    };
+
+    choose_sgx_dylib(is_sim);
+}
diff --git a/services/access_control/app/src/main.rs 
b/services/access_control/app/src/main.rs
new file mode 100644
index 0000000..5e16411
--- /dev/null
+++ b/services/access_control/app/src/main.rs
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#[macro_use]
+extern crate log;
+
+use teaclave_ipc::proto::{ECallCommand, StartServiceInput, StartServiceOutput};
+
+use anyhow::Result;
+use teaclave_binder::TeeBinder;
+
+fn main() -> Result<()> {
+    env_logger::init();
+    let tee = TeeBinder::new(env!("CARGO_PKG_NAME"), 1)?;
+    run(&tee)?;
+
+    Ok(())
+}
+
+fn start_enclave_service(tee: &TeeBinder) -> Result<()> {
+    info!("Start enclave service");
+    let config = 
teaclave_config::RuntimeConfig::from_toml("runtime.config.toml")?;
+    let input = StartServiceInput { config };
+    let cmd = ECallCommand::StartService;
+    let _ = tee.invoke::<StartServiceInput, StartServiceOutput>(cmd.into(), 
input);
+
+    Ok(())
+}
+
+fn run(tee: &TeeBinder) -> Result<()> {
+    start_enclave_service(tee)?;
+
+    Ok(())
+}
diff --git a/services/access_control/enclave/Cargo.toml 
b/services/access_control/enclave/Cargo.toml
new file mode 100644
index 0000000..359c862
--- /dev/null
+++ b/services/access_control/enclave/Cargo.toml
@@ -0,0 +1,48 @@
+[package]
+name = "teaclave_access_control_service_enclave"
+version = "0.1.0"
+authors = ["Teaclave Contributors <[email protected]>"]
+description = "Teaclave Access Control Service enclave"
+license = "Apache-2.0"
+edition = "2018"
+
+[lib]
+name = "teaclave_access_control_service_enclave"
+crate-type = ["staticlib", "rlib"]
+
+[features]
+default = []
+mesalock_sgx = [
+  "sgx_tstd",
+  "teaclave_attestation/mesalock_sgx",
+  "teaclave_proto/mesalock_sgx",
+  "teaclave_ipc/mesalock_sgx",
+  "teaclave_rpc/mesalock_sgx",
+  "teaclave_service_enclave_utils/mesalock_sgx",
+  "teaclave_types/mesalock_sgx",
+  "teaclave_config/mesalock_sgx",
+]
+cov = ["teaclave_service_enclave_utils/cov"]
+enclave_unit_test = ["teaclave_ipc/enclave_unit_test", 
"teaclave_test_utils/mesalock_sgx"]
+
+[dependencies]
+anyhow    = { version = "1.0.26" }
+cfg-if    = { version = "0.1.9" }
+log       = { version = "0.4.6" }
+serde     = { version = "1.0.92" }
+serde_json = { version = "1.0.39" }
+thiserror = { version = "1.0.9" }
+ring      = { version = "0.16.5" }
+rand      = { version = "0.7.0" }
+
+teaclave_attestation           = { path = "../../../attestation" }
+teaclave_config                = { path = "../../../config" }
+teaclave_proto                 = { path = "../../proto" }
+teaclave_ipc                   = { path = "../../../ipc" }
+teaclave_rpc                   = { path = "../../../rpc" }
+teaclave_service_enclave_utils = { path = 
"../../../utils/service_enclave_utils" }
+teaclave_types                 = { path = "../../../types" }
+teaclave_test_utils            = { path = "../../../tests/test_utils" }
+
+sgx_tstd      = { version = "1.1.0", features = ["net", "thread", 
"backtrace"], optional = true }
+sgx_types     = { version = "1.1.0" }
diff --git a/services/access_control/enclave/Enclave.config.xml 
b/services/access_control/enclave/Enclave.config.xml
new file mode 100644
index 0000000..705edcd
--- /dev/null
+++ b/services/access_control/enclave/Enclave.config.xml
@@ -0,0 +1,12 @@
+<!-- Please refer to User's Guide for the explanation of each field -->
+<EnclaveConfiguration>
+  <ProdID>0</ProdID>
+  <ISVSVN>0</ISVSVN>
+  <StackMaxSize>0x200000</StackMaxSize>
+  <HeapMaxSize>0x1000000</HeapMaxSize>
+  <TCSNum>22</TCSNum>
+  <TCSPolicy>0</TCSPolicy>
+  <DisableDebug>0</DisableDebug>
+  <MiscSelect>0</MiscSelect>
+  <MiscMask>0xFFFFFFFF</MiscMask>
+</EnclaveConfiguration>
diff --git a/services/access_control/enclave/src/acs.rs 
b/services/access_control/enclave/src/acs.rs
new file mode 100644
index 0000000..7f8f753
--- /dev/null
+++ b/services/access_control/enclave/src/acs.rs
@@ -0,0 +1,341 @@
+use anyhow::{anyhow, Result};
+use cfg_if::cfg_if;
+use std::collections::HashSet;
+use std::ffi::CString;
+use std::os::raw::c_char;
+use std::prelude::v1::*;
+use std::sync::Arc;
+cfg_if! {
+    if #[cfg(feature = "mesalock_sgx")]  {
+        use std::sync::SgxMutex as Mutex;
+    } else {
+        use std::sync::Mutex;
+    }
+}
+
+const MODEL_TEXT: &str = include_str!("../../model.conf");
+extern "C" {
+    fn acs_setup_model(model_text: *const c_char) -> i32;
+    fn acs_enforce_request(request_type: *const c_char, request_content: 
*const c_char) -> i32;
+}
+#[cfg(test_mode)]
+extern "C" {
+    fn acs_announce_fact(fact_type: *const c_char, fact_vals: *const c_char) 
-> i32;
+}
+
+pub(crate) enum EnforceRequest {
+    // user_access_data = usr, data
+    UserAccessData(String, String),
+    // user_access_function = usr, function
+    UserAccessFunction(String, String),
+    // user_access_task= usr, task
+    UserAccessTask(String, String),
+    // task_access_function = task, function
+    TaskAccessFunction(String, String),
+    // task_access_data = task, data
+    TaskAccessData(String, String),
+}
+
+#[cfg(test_mode)]
+pub(crate) enum AccessControlTerms {
+    // data_owner = data, usr
+    DataOwner(String, String),
+    // function_owner = functoin, usr
+    FunctionOwner(String, String),
+    // is_public_function = function
+    IsPublicFunction(String),
+    // task_participant = task, usr
+    TaskParticipant(String, String),
+}
+
+pub trait PyMarshallable {
+    fn marshal(&self, buffer: &mut String);
+}
+
+impl<T> PyMarshallable for (T,)
+where
+    T: PyMarshallable,
+{
+    fn marshal(&self, buffer: &mut String) {
+        buffer.push('[');
+        self.0.marshal(buffer);
+        buffer.push(']');
+    }
+}
+
+impl<U, V> PyMarshallable for (U, V)
+where
+    U: PyMarshallable,
+    V: PyMarshallable,
+{
+    fn marshal(&self, buffer: &mut String) {
+        buffer.push('[');
+        self.0.marshal(buffer);
+        buffer.push(',');
+        self.1.marshal(buffer);
+        buffer.push(']');
+    }
+}
+
+impl<X, Y, Z> PyMarshallable for (X, Y, Z)
+where
+    X: PyMarshallable,
+    Y: PyMarshallable,
+    Z: PyMarshallable,
+{
+    fn marshal(&self, buffer: &mut String) {
+        buffer.push('[');
+        self.0.marshal(buffer);
+        buffer.push(',');
+        self.1.marshal(buffer);
+        buffer.push(',');
+        self.2.marshal(buffer);
+        buffer.push(']');
+    }
+}
+
+impl<T> PyMarshallable for [T]
+where
+    T: PyMarshallable,
+{
+    fn marshal(&self, buffer: &mut String) {
+        buffer.push('[');
+        for t in self {
+            t.marshal(buffer);
+            buffer.push(',');
+        }
+        buffer.push(']');
+    }
+}
+
+impl<T: PyMarshallable> PyMarshallable for &HashSet<T> {
+    fn marshal(&self, buffer: &mut String) {
+        buffer.push_str("set([");
+        for t in *self {
+            t.marshal(buffer);
+            buffer.push(',');
+        }
+        buffer.push_str("])");
+    }
+}
+
+impl PyMarshallable for String {
+    fn marshal(&self, buffer: &mut String) {
+        buffer.push('\'');
+        buffer.push_str(self);
+        buffer.push('\'');
+    }
+}
+
+impl PyMarshallable for &String {
+    fn marshal(&self, buffer: &mut String) {
+        buffer.push('\'');
+        buffer.push_str(self);
+        buffer.push('\'');
+    }
+}
+
+#[cfg(test_mode)]
+pub(crate) fn init_mock_data() -> Result<()> {
+    // mock data for AuthorizeData
+    let term = AccessControlTerms::DataOwner("mock_data".to_string(), 
"mock_user_a".to_string());
+    announce_fact(term)?;
+    let term = AccessControlTerms::DataOwner("mock_data".to_string(), 
"mock_user_b".to_string());
+    announce_fact(term)?;
+    let term = AccessControlTerms::DataOwner("mock_data".to_string(), 
"mock_user_c".to_string());
+    announce_fact(term)?;
+
+    // mock data for AuthorizeFunction
+    let term = AccessControlTerms::FunctionOwner(
+        "mock_private_function".to_string(),
+        "mock_private_function_owner".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::FunctionOwner(
+        "mock_public_function".to_string(),
+        "mock_public_function_owner".to_string(),
+    );
+    announce_fact(term)?;
+    let term = 
AccessControlTerms::IsPublicFunction("mock_public_function".to_string());
+    announce_fact(term)?;
+
+    // mock data for AuthorizeTask
+    let term = AccessControlTerms::TaskParticipant(
+        "mock_task".to_string(),
+        "mock_participant_a".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::TaskParticipant(
+        "mock_task".to_string(),
+        "mock_participant_b".to_string(),
+    );
+    announce_fact(term)?;
+
+    // mock data for AuthorizeStagedTask
+    let term = AccessControlTerms::TaskParticipant(
+        "mock_staged_task".to_string(),
+        "mock_staged_participant_a".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::TaskParticipant(
+        "mock_staged_task".to_string(),
+        "mock_staged_participant_b".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::FunctionOwner(
+        "mock_staged_allowed_private_function".to_string(),
+        "mock_staged_participant_a".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::FunctionOwner(
+        "mock_staged_disallowed_private_function".to_string(),
+        "mock_staged_non_participant".to_string(),
+    );
+    announce_fact(term)?;
+    let term = 
AccessControlTerms::IsPublicFunction("mock_staged_public_function".to_string());
+    announce_fact(term)?;
+
+    let term = AccessControlTerms::DataOwner(
+        "mock_staged_allowed_data1".to_string(),
+        "mock_staged_participant_a".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::DataOwner(
+        "mock_staged_allowed_data2".to_string(),
+        "mock_staged_participant_b".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::DataOwner(
+        "mock_staged_allowed_data3".to_string(),
+        "mock_staged_participant_a".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::DataOwner(
+        "mock_staged_allowed_data3".to_string(),
+        "mock_staged_participant_b".to_string(),
+    );
+    announce_fact(term)?;
+
+    let term = AccessControlTerms::DataOwner(
+        "mock_staged_disallowed_data1".to_string(),
+        "mock_staged_non_participant".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::DataOwner(
+        "mock_staged_disallowed_data2".to_string(),
+        "mock_staged_participant_a".to_string(),
+    );
+    announce_fact(term)?;
+    let term = AccessControlTerms::DataOwner(
+        "mock_staged_disallowed_data2".to_string(),
+        "mock_staged_non_participant".to_string(),
+    );
+    announce_fact(term)?;
+
+    Ok(())
+}
+
+#[derive(Clone)]
+pub(crate) struct AccessControlModule {
+    lock: Arc<Mutex<u32>>,
+}
+
+impl AccessControlModule {
+    pub(crate) fn new() -> Self {
+        AccessControlModule {
+            lock: Arc::new(Mutex::new(0)),
+        }
+    }
+
+    pub(crate) fn enforce_request(&self, request: EnforceRequest) -> 
Result<bool> {
+        let (request_type, request_content) = match request {
+            EnforceRequest::UserAccessData(usr, data) => {
+                let mut buffer = String::new();
+                (usr, data).marshal(&mut buffer);
+                ("user_access_data", buffer)
+            }
+            EnforceRequest::UserAccessFunction(usr, function) => {
+                let mut buffer = String::new();
+                (usr, function).marshal(&mut buffer);
+                ("user_access_function", buffer)
+            }
+            EnforceRequest::UserAccessTask(usr, task) => {
+                let mut buffer = String::new();
+                (usr, task).marshal(&mut buffer);
+                ("user_access_task", buffer)
+            }
+            EnforceRequest::TaskAccessFunction(task, function) => {
+                let mut buffer = String::new();
+                (task, function).marshal(&mut buffer);
+                ("task_access_function", buffer)
+            }
+            EnforceRequest::TaskAccessData(task, data) => {
+                let mut buffer = String::new();
+                (task, data).marshal(&mut buffer);
+                ("task_access_data", buffer)
+            }
+        };
+
+        let c_request_type = CString::new(request_type.to_string())?;
+        let c_request_content = CString::new(request_content)?;
+        let _lock = self
+            .lock
+            .lock()
+            .map_err(|_| anyhow!("failed to accquire lock"))?;
+        let py_ret =
+            unsafe { acs_enforce_request(c_request_type.as_ptr(), 
c_request_content.as_ptr()) };
+
+        match py_ret {
+            0 => Ok(false),
+            1 => Ok(true),
+            _ => Err(anyhow!("mesapy error")),
+        }
+    }
+}
+pub(crate) fn init_acs() -> Result<()> {
+    let ec = unsafe { 
acs_setup_model(CString::new(MODEL_TEXT).unwrap().as_ptr()) };
+
+    if ec != 0 {
+        Err(anyhow!("failed to init mesapy"))
+    } else {
+        #[cfg(test_mode)]
+        init_mock_data()?;
+        Ok(())
+    }
+}
+
+#[cfg(test_mode)]
+fn announce_fact(term: AccessControlTerms) -> Result<()> {
+    let (term_type, term_fact) = match term {
+        AccessControlTerms::DataOwner(data, usr) => {
+            let mut buffer = String::new();
+            (data, usr).marshal(&mut buffer);
+            ("data_owner", buffer)
+        }
+        AccessControlTerms::FunctionOwner(function, usr) => {
+            let mut buffer = String::new();
+            (function, usr).marshal(&mut buffer);
+            ("function_owner", buffer)
+        }
+        AccessControlTerms::IsPublicFunction(function) => {
+            let mut buffer = String::new();
+            (function,).marshal(&mut buffer);
+            ("is_public_function", buffer)
+        }
+        AccessControlTerms::TaskParticipant(task, usr) => {
+            let mut buffer = String::new();
+            (task, usr).marshal(&mut buffer);
+            ("task_participant", buffer)
+        }
+    };
+    let c_term_type = CString::new(term_type.to_string())?;
+    let c_term_fact = CString::new(term_fact)?;
+
+    let py_ret = unsafe { acs_announce_fact(c_term_type.as_ptr(), 
c_term_fact.as_ptr()) };
+
+    if py_ret != 0 {
+        Err(anyhow!("mesapy error"))
+    } else {
+        Ok(())
+    }
+}
diff --git a/services/access_control/enclave/src/lib.rs 
b/services/access_control/enclave/src/lib.rs
new file mode 100644
index 0000000..883a24b
--- /dev/null
+++ b/services/access_control/enclave/src/lib.rs
@@ -0,0 +1,112 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![cfg_attr(feature = "mesalock_sgx", no_std)]
+#[cfg(feature = "mesalock_sgx")]
+#[macro_use]
+extern crate sgx_tstd as std;
+
+#[macro_use]
+extern crate log;
+
+use anyhow::Result;
+use std::prelude::v1::*;
+use teaclave_attestation::{AttestationConfig, RemoteAttestation};
+use teaclave_ipc::proto::{
+    ECallCommand, FinalizeEnclaveInput, FinalizeEnclaveOutput, 
InitEnclaveInput, InitEnclaveOutput,
+    StartServiceInput, StartServiceOutput,
+};
+use teaclave_ipc::{handle_ecall, register_ecall_handler};
+use teaclave_proto::teaclave_access_control_service::{
+    TeaclaveAccessControlRequest, TeaclaveAccessControlResponse,
+};
+use teaclave_rpc::config::SgxTrustedTlsServerConfig;
+use teaclave_rpc::server::SgxTrustedTlsServer;
+use teaclave_service_enclave_utils::ServiceEnclave;
+
+mod acs;
+mod service;
+
+#[handle_ecall]
+fn handle_start_service(args: &StartServiceInput) -> 
Result<StartServiceOutput> {
+    debug!("handle_start_service");
+    let listen_address = 
args.config.internal_endpoints.access_control.listen_address;
+    let ias_config = args.config.ias.as_ref().unwrap();
+    let attestation = 
RemoteAttestation::generate_and_endorse(&AttestationConfig::ias(
+        &ias_config.ias_key,
+        &ias_config.ias_spid,
+    ))
+    .unwrap();
+    let config = SgxTrustedTlsServerConfig::new_without_verifier(
+        &attestation.cert,
+        &attestation.private_key,
+    )
+    .unwrap();
+
+    acs::init_acs().unwrap();
+    let mut server = SgxTrustedTlsServer::<
+        TeaclaveAccessControlResponse,
+        TeaclaveAccessControlRequest,
+    >::new(listen_address, &config);
+    let service = service::TeaclaveAccessControlService::new();
+    match server.start(service) {
+        Ok(_) => (),
+        Err(e) => {
+            error!("Service exit, error: {}.", e);
+        }
+    }
+
+    Ok(StartServiceOutput::default())
+}
+
+#[handle_ecall]
+fn handle_init_enclave(_args: &InitEnclaveInput) -> Result<InitEnclaveOutput> {
+    ServiceEnclave::init(env!("CARGO_PKG_NAME"))?;
+    Ok(InitEnclaveOutput::default())
+}
+
+#[handle_ecall]
+fn handle_finalize_enclave(_args: &FinalizeEnclaveInput) -> 
Result<FinalizeEnclaveOutput> {
+    ServiceEnclave::finalize()?;
+    Ok(FinalizeEnclaveOutput::default())
+}
+
+register_ecall_handler!(
+    type ECallCommand,
+    (ECallCommand::StartService, StartServiceInput, StartServiceOutput),
+    (ECallCommand::InitEnclave, InitEnclaveInput, InitEnclaveOutput),
+    (ECallCommand::FinalizeEnclave, FinalizeEnclaveInput, 
FinalizeEnclaveOutput),
+);
+
+#[cfg(feature = "enclave_unit_test")]
+pub mod tests {
+    use super::*;
+    use teaclave_test_utils::*;
+
+    pub fn run_tests() -> bool {
+        if crate::acs::init_acs().is_err() {
+            return false;
+        }
+        run_tests!(
+            service::tests::user_access_data,
+            service::tests::user_access_function,
+            service::tests::user_access_task,
+            service::tests::task_access_function,
+            service::tests::task_access_data,
+        )
+    }
+}
diff --git a/services/access_control/enclave/src/service.rs 
b/services/access_control/enclave/src/service.rs
new file mode 100644
index 0000000..9325a7a
--- /dev/null
+++ b/services/access_control/enclave/src/service.rs
@@ -0,0 +1,350 @@
+use crate::acs::{AccessControlModule, EnforceRequest};
+use std::prelude::v1::*;
+use teaclave_proto::teaclave_access_control_service::{
+    AuthorizeDataRequest, AuthorizeDataResponse, AuthorizeFunctionRequest,
+    AuthorizeFunctionResponse, AuthorizeStagedTaskRequest, 
AuthorizeStagedTaskResponse,
+    AuthorizeTaskRequest, AuthorizeTaskResponse, TeaclaveAccessControl,
+};
+use teaclave_rpc::Request;
+use teaclave_service_enclave_utils::teaclave_service;
+use teaclave_types::{TeaclaveServiceResponseError, 
TeaclaveServiceResponseResult};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+enum TeaclavAccessControlError {
+    #[error("access control error")]
+    AccessControlError,
+}
+
+impl From<TeaclavAccessControlError> for TeaclaveServiceResponseError {
+    fn from(error: TeaclavAccessControlError) -> Self {
+        TeaclaveServiceResponseError::RequestError(error.to_string())
+    }
+}
+
+#[teaclave_service(teaclave_access_control_service, TeaclaveAccessControl)]
+#[derive(Clone)]
+pub(crate) struct TeaclaveAccessControlService {
+    access_control_module: AccessControlModule,
+}
+
+impl TeaclaveAccessControlService {
+    pub(crate) fn new() -> Self {
+        TeaclaveAccessControlService {
+            access_control_module: AccessControlModule::new(),
+        }
+    }
+}
+impl TeaclaveAccessControl for TeaclaveAccessControlService {
+    fn authorize_data(
+        &self,
+        request: Request<AuthorizeDataRequest>,
+    ) -> TeaclaveServiceResponseResult<AuthorizeDataResponse> {
+        let request = request.message;
+        let request =
+            EnforceRequest::UserAccessData(request.subject_user_id, 
request.object_data_id);
+        match self.access_control_module.enforce_request(request) {
+            Ok(accept) => {
+                let response = AuthorizeDataResponse { accept };
+                Ok(response)
+            }
+            Err(_) => 
Err(TeaclavAccessControlError::AccessControlError.into()),
+        }
+    }
+
+    fn authorize_function(
+        &self,
+        request: Request<AuthorizeFunctionRequest>,
+    ) -> TeaclaveServiceResponseResult<AuthorizeFunctionResponse> {
+        let request = request.message;
+        let request =
+            EnforceRequest::UserAccessFunction(request.subject_user_id, 
request.object_function_id);
+        match self.access_control_module.enforce_request(request) {
+            Ok(accept) => {
+                let response = AuthorizeFunctionResponse { accept };
+                Ok(response)
+            }
+            Err(_) => 
Err(TeaclavAccessControlError::AccessControlError.into()),
+        }
+    }
+
+    fn authorize_task(
+        &self,
+        request: Request<AuthorizeTaskRequest>,
+    ) -> TeaclaveServiceResponseResult<AuthorizeTaskResponse> {
+        let request = request.message;
+        let request =
+            EnforceRequest::UserAccessTask(request.subject_user_id, 
request.object_task_id);
+        match self.access_control_module.enforce_request(request) {
+            Ok(accept) => {
+                let response = AuthorizeTaskResponse { accept };
+                Ok(response)
+            }
+            Err(_) => 
Err(TeaclavAccessControlError::AccessControlError.into()),
+        }
+    }
+
+    fn authorize_staged_task(
+        &self,
+        request: Request<AuthorizeStagedTaskRequest>,
+    ) -> TeaclaveServiceResponseResult<AuthorizeStagedTaskResponse> {
+        let request = request.message;
+        let enforce_access_function_request = 
EnforceRequest::TaskAccessFunction(
+            request.subject_task_id.clone(),
+            request.object_function_id,
+        );
+        match self
+            .access_control_module
+            .enforce_request(enforce_access_function_request)
+        {
+            Ok(accept) => {
+                if !accept {
+                    return Ok(AuthorizeStagedTaskResponse { accept: false });
+                }
+            }
+            Err(_) => return 
Err(TeaclavAccessControlError::AccessControlError.into()),
+        }
+        for object_data_id in request.object_input_data_id_list.iter() {
+            let enforce_access_data_request = EnforceRequest::TaskAccessData(
+                request.subject_task_id.clone(),
+                object_data_id.to_string(),
+            );
+            match self
+                .access_control_module
+                .enforce_request(enforce_access_data_request)
+            {
+                Ok(accept) => {
+                    if !accept {
+                        return Ok(AuthorizeStagedTaskResponse { accept: false 
});
+                    }
+                }
+                Err(_) => return 
Err(TeaclavAccessControlError::AccessControlError.into()),
+            }
+        }
+        for object_data_id in request.object_output_data_id_list.iter() {
+            let enforce_access_data_request = EnforceRequest::TaskAccessData(
+                request.subject_task_id.clone(),
+                object_data_id.to_string(),
+            );
+            match self
+                .access_control_module
+                .enforce_request(enforce_access_data_request)
+            {
+                Ok(accept) => {
+                    if !accept {
+                        return Ok(AuthorizeStagedTaskResponse { accept: false 
});
+                    }
+                }
+                Err(_) => return 
Err(TeaclavAccessControlError::AccessControlError.into()),
+            }
+        }
+        Ok(AuthorizeStagedTaskResponse { accept: true })
+    }
+}
+
+#[cfg(feature = "enclave_unit_test")]
+pub mod tests {
+    use super::*;
+
+    pub fn user_access_data() {
+        let service = TeaclaveAccessControlService::new();
+        let request = AuthorizeDataRequest {
+            subject_user_id: "mock_user_a".to_string(),
+            object_data_id: "mock_data".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_data(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let request = AuthorizeDataRequest {
+            subject_user_id: "mock_user_b".to_string(),
+            object_data_id: "mock_data".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_data(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let request = AuthorizeDataRequest {
+            subject_user_id: "mock_user_c".to_string(),
+            object_data_id: "mock_data".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_data(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let request = AuthorizeDataRequest {
+            subject_user_id: "mock_user_d".to_string(),
+            object_data_id: "mock_data".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_data(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+
+        let request = AuthorizeDataRequest {
+            subject_user_id: "mock_user_a".to_string(),
+            object_data_id: "mock_data_b".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_data(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+    }
+
+    pub fn user_access_function() {
+        let service = TeaclaveAccessControlService::new();
+        let request = AuthorizeFunctionRequest {
+            subject_user_id: "mock_public_function_owner".to_string(),
+            object_function_id: "mock_public_function".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_function(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let request = AuthorizeFunctionRequest {
+            subject_user_id: "mock_private_function_owner".to_string(),
+            object_function_id: "mock_private_function".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_function(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let request = AuthorizeFunctionRequest {
+            subject_user_id: "mock_private_function_owner".to_string(),
+            object_function_id: "mock_public_function".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_function(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let request = AuthorizeFunctionRequest {
+            subject_user_id: "mock_public_function_owner".to_string(),
+            object_function_id: "mock_private_function".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_function(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+    }
+
+    pub fn user_access_task() {
+        let service = TeaclaveAccessControlService::new();
+        let request = AuthorizeTaskRequest {
+            subject_user_id: "mock_participant_a".to_string(),
+            object_task_id: "mock_task".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_task(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let request = AuthorizeTaskRequest {
+            subject_user_id: "mock_participant_b".to_string(),
+            object_task_id: "mock_task".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_task(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let request = AuthorizeTaskRequest {
+            subject_user_id: "mock_participant_c".to_string(),
+            object_task_id: "mock_task".to_string(),
+        };
+        let request = Request::new(request);
+        let response = service.authorize_task(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+    }
+
+    pub fn task_access_function() {
+        let service = TeaclaveAccessControlService::new();
+        let mut request = get_correct_authorized_stage_task_req();
+        request.object_function_id = 
"mock_staged_allowed_private_function".to_string();
+        let request = Request::new(request);
+        let response = service.authorize_staged_task(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let mut request = get_correct_authorized_stage_task_req();
+        request.object_function_id = "mock_staged_public_function".to_string();
+        let request = Request::new(request);
+        let response = service.authorize_staged_task(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let mut request = get_correct_authorized_stage_task_req();
+        request.object_function_id = 
"mock_staged_disallowed_private_function".to_string();
+        let request = Request::new(request);
+        let response = service.authorize_staged_task(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+    }
+
+    fn get_correct_authorized_stage_task_req() -> AuthorizeStagedTaskRequest {
+        AuthorizeStagedTaskRequest {
+            subject_task_id: "mock_staged_task".to_string(),
+            object_function_id: 
"mock_staged_allowed_private_function".to_string(),
+            object_input_data_id_list: vec![
+                "mock_staged_allowed_data1".to_string(),
+                "mock_staged_allowed_data2".to_string(),
+                "mock_staged_allowed_data3".to_string(),
+            ],
+            object_output_data_id_list: vec![
+                "mock_staged_allowed_data1".to_string(),
+                "mock_staged_allowed_data2".to_string(),
+                "mock_staged_allowed_data3".to_string(),
+            ],
+        }
+    }
+    pub fn task_access_data() {
+        let service = TeaclaveAccessControlService::new();
+        let request = get_correct_authorized_stage_task_req();
+        let request = Request::new(request);
+        let response = service.authorize_staged_task(request);
+        assert!(response.is_ok());
+        assert!(response.unwrap().accept);
+
+        let mut request = get_correct_authorized_stage_task_req();
+        request
+            .object_input_data_id_list
+            .push("mock_staged_disallowed_data1".to_string());
+        let request = Request::new(request);
+        let response = service.authorize_staged_task(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+
+        let mut request = get_correct_authorized_stage_task_req();
+        request
+            .object_input_data_id_list
+            .push("mock_staged_disallowed_data2".to_string());
+        let request = Request::new(request);
+        let response = service.authorize_staged_task(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+
+        let mut request = get_correct_authorized_stage_task_req();
+        request
+            .object_output_data_id_list
+            .push("mock_staged_disallowed_data1".to_string());
+        let request = Request::new(request);
+        let response = service.authorize_staged_task(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+
+        let mut request = get_correct_authorized_stage_task_req();
+        request
+            .object_output_data_id_list
+            .push("mock_staged_disallowed_data2".to_string());
+        let request = Request::new(request);
+        let response = service.authorize_staged_task(request);
+        assert!(response.is_ok());
+        assert!(!response.unwrap().accept);
+    }
+}
diff --git a/services/access_control/model.conf 
b/services/access_control/model.conf
new file mode 100644
index 0000000..d05e8d1
--- /dev/null
+++ b/services/access_control/model.conf
@@ -0,0 +1,28 @@
+[requests]
+user_access_data = usr, data
+user_access_function = usr, function
+user_access_task = usr, task
+
+task_access_function = task, function
+task_access_data = task, data
+
+[terms]
+data_owner = data, usr
+function_owner = function, usr
+is_public_function = function
+task_participant = task, usr
+
+[matchers]
+user_access_data = data_owner(user_access_data.data, user_access_data.usr)
+
+user_access_function = \
+    is_public_function(user_access_function.function) or \
+    function_owner(user_access_function.function, user_access_function.usr)
+
+user_access_task = task_participant(user_access_task.task, 
user_access_task.usr)
+
+task_access_function = \
+    is_public_function(task_access_function.function) or \
+    function_owner(task_access_function.function, _) <= 
task_participant(task_access_function.task, _)
+
+task_access_data = data_owner(task_access_data.data, _) <= 
task_participant(task_access_data.task, _)
\ No newline at end of file
diff --git a/services/access_control/python/acs_engine.py 
b/services/access_control/python/acs_engine.py
new file mode 100644
index 0000000..4a12bf4
--- /dev/null
+++ b/services/access_control/python/acs_engine.py
@@ -0,0 +1,631 @@
+###############################################################################
+# Parser Combinators
+###############################################################################
+class Pair(tuple):
+    def __new__(cls, a, b):
+        return super(Pair, cls).__new__(cls, [a, b])
+
+class Either(object):
+    def __init__(self, left, right):
+        self.__left = left
+        self.__right = right
+
+    def left(self):
+        if not self.is_left():
+            raise ValueError('wrong extractor for either')
+        return self.__left
+
+    def right(self):
+        if not self.is_right():
+            raise ValueError('wrong extractor for either')
+        return self.__right
+
+    def is_right(self):
+        return False
+
+    def is_left(self):
+        return False
+
+    def get(self):
+        if self.is_right():
+            return self.right()
+        if self.is_left():
+            return self.left()
+        raise ValueError('incomplete Either object')
+
+    def __str__(self):
+        if self.is_left():
+            return 'Left(' + str(self.left()) + ')'
+        else:
+            return 'Right(' + str(self.right()) + ')'
+
+    def __repr__(self):
+        if self.is_left():
+            return 'Left(' + repr(self.left()) + ')'
+        else:
+            return 'Right(' + repr(self.right()) + ')'
+
+class Left(Either):
+    def __init__(self, payload):
+        super(Left, self).__init__(payload, None)
+
+    def is_left(self):
+        return True
+
+class Right(Either):
+    def __init__(self, payload):
+        super(Right, self).__init__(None, payload)
+
+    def is_right(self):
+        return True
+
+class Stream(object):
+    WHITESPACES = [' ', '\t', '\r']
+    def __init__(self, items, pos = 0):
+        self.__items = items
+        self.__pos = pos
+
+    def accept_strlit(self, string):
+        # Typically parsers want to skip white spaces except line breaks
+        # In the future this should be configurable
+        pos = self.__pos
+        l = len(self.__items)
+        while pos < l and self.__items[pos] in self.WHITESPACES:
+            pos += 1
+
+        match_pos = 0
+        l = len(string)        
+        while match_pos < l and string[match_pos] in self.WHITESPACES:
+            match_pos += 1
+        if pos < match_pos:
+            raise ParsingError(self, 'expecting "{}"'.format(string))
+        if match_pos:
+            string = string[match_pos:]
+        if self.__items.startswith(string, pos):
+            return Stream(self.__items, pos + len(string))
+        raise ParsingError(self, 'expecting "{}"'.format(string))
+
+    def accept_matcher(self, matcher):
+        pos = self.__pos
+        l = len(self.__items)
+        while pos < l and self.__items[pos] in self.WHITESPACES:
+            pos += 1
+
+        res = matcher(self.__items, pos)
+        if res is None:
+            raise ParsingError(self, 'matcher for {} 
failed'.format(matcher.__doc__))
+        obj, npos = res
+        return obj, Stream(self.__items, npos)
+
+    def end(self):
+        return self.__pos == len(self.__items)
+
+    def pos(self):
+        return self.__pos
+
+    def __repr__(self):
+        line_start = self.__items.rfind('\n', 0, self.__pos) + 1
+        line_end = self.__items.find('\n', self.__pos)
+        if line_end == -1:
+            line_end = self.__pos
+
+        if line_end - line_start > 80:
+            line_start = max(line_start, self.__pos - 40)
+            line_end = min(line_start + 80, len(self.__items))
+
+        return ''.join([
+            self.__items[line_start:line_end],
+            '\n',
+            ' ' * (self.__pos - line_start),
+            '^',
+            ' ' * (line_end - self.__pos),
+            '\nerror at character ',
+            str(self.__pos),
+        ])
+
+class State(object):
+    def __init__(self, stream, payload = None, success = True):
+        self.stream = stream
+        self.payload = payload
+        self.success = success
+
+    def __bool__(self):
+        return self.success
+
+    def __nonzero__(self):
+        return self.__bool__()
+
+    def fmap(self, f):
+        if self:
+            return State(self.stream, f(self.payload))
+        return self
+
+class ParsingError(Exception):
+    def __init__(self, stream, msg = ''):
+        super(ParsingError, self).__init__(msg)
+        self.stream = stream
+
+    def __repr__(self):
+        return repr(self.stream)
+
+class Parser(object):
+    def __init__(self):
+        pass
+
+    def __call__(self, stream):
+        raise NotImplementedError("pure abstract parser cannot be called")
+
+    def parse_from(self, stream):
+        n_state = self(stream)
+        if not n_state:
+            raise ParsingError(n_state.stream, n_state.payload)
+        elif not n_state.stream.end():
+            raise ParsingError(n_state.stream, 'trailing unparsable input')
+        return n_state
+
+    def fail(self, exception):
+        return State(exception.stream, str(exception), False)
+
+    def ignore(self):
+        return Ignore(self)
+
+    def __or__(self, p):
+        return Or(self, p)
+
+    def __add__(self, p):
+        if isinstance(self, Ignore) and isinstance(p, Ignore):
+            return Ignore(Concat(self, p))
+        else:
+            return Concat(self, p)
+
+    def __invert__(self):
+        return Rep(self)
+
+    def __neg__(self):
+        return Optional(self)
+
+    def __pow__(self, f):
+        return Apply(self, f)
+
+class Optional(Parser):
+    def __init__(self, opt):
+        super(Optional, self).__init__()
+        self.__opt = opt
+
+    def __call__(self, stream):
+        n_state = self.__opt(stream)
+        if n_state:
+            return n_state.fmap(lambda x: Left(x))
+        return State(stream, Right(None))
+
+class StrLiteral(Parser):
+    def __init__(self, string):
+        super(StrLiteral, self).__init__()
+        self.__string = string
+
+    def __call__(self, stream):
+        if stream.end():
+            return self.fail(ParsingError(
+                stream, 'insufficient input, expecting 
{}'.format(self.__string))
+            )
+        try:
+            n_stream = stream.accept_strlit(self.__string)
+        except ParsingError as e:
+            return self.fail(e)
+
+        return State(n_stream, self.__string)
+
+class CustomMatcher(Parser):
+    def __init__(self, matcher):
+        super(CustomMatcher, self).__init__()
+        self.__matcher = matcher
+
+    def __call__(self, stream):
+        try:
+            res = stream.accept_matcher(self.__matcher)
+        except ParsingError as e:
+            return self.fail(e)
+
+        obj, n_stream = res
+        return State(n_stream, obj)
+
+class Concat(Parser):
+    def __init__(self, c1, c2):
+        super(Concat, self).__init__()
+        assert not isinstance(self, Ignore) or not isinstance(p, Ignore)
+        self.__first = c1
+        self.__second = c2
+
+    def __call__(self, stream):
+        n_state = self.__first(stream)
+        if not n_state:
+            return n_state
+        p1 = n_state.payload
+        n_state = self.__second(n_state.stream)
+        if not n_state:
+            return n_state
+        p2 = n_state.payload
+
+        if isinstance(self.__first, Ignore):
+            return State(n_state.stream, p2)
+        if isinstance(self.__second, Ignore):
+            return State(n_state.stream, p1)
+        # The construction of Concat ensures that at least
+        # one of this children is not Ignore
+        return State(n_state.stream, Pair(p1, p2))
+
+class Or(Parser):
+    def __init__(self, c1, c2):
+        super(Or, self).__init__()
+        self.__if = c1
+        self.__else = c2
+
+    def __call__(self, stream):
+        n_state = self.__if(stream)
+        if n_state:
+            return n_state.fmap(lambda x: Left(x))
+        n_state = self.__else(stream)
+        if n_state:
+            return n_state.fmap(lambda x: Right(x))
+        return n_state
+
+class Rep(Parser):
+    def __init__(self, c):
+        super(Rep, self).__init__()
+        self.__loop = c
+
+    def __call__(self, stream):
+        payload = []
+
+        n_state = self.__loop(stream)
+        if n_state:
+            payload.append(n_state.payload)
+            stream = n_state.stream
+            n_state = self(stream)
+            if n_state:
+                payload = payload + n_state.payload
+                stream = n_state.stream
+        return State(stream, payload)
+
+class Apply(Parser):
+    def __init__(self, base, f):
+        super(Apply, self).__init__()
+        self.__base = base
+        self.__trans = f
+
+    def __call__(self, stream):
+        return self.__base(stream).fmap(self.__trans)
+
+class Ignore(Parser):
+    def __init__(self, base):
+        super(Ignore, self).__init__()
+        self.__base = base
+
+    def __call__(self, stream):
+        return self.__base(stream)
+
+###############################################################################
+# Grammars for PERM model configuration 
+###############################################################################
+from operator import or_, add
+
+def extract(nested_or):
+    while isinstance(nested_or, Either):
+        nested_or = nested_or.left() if nested_or.is_left() else 
nested_or.right()
+    return nested_or
+
+def flatten(nested_concat):
+    res = []
+
+    def pre_order(pair, res):
+        if isinstance(pair, Pair):
+            pre_order(pair[0], res)
+            pre_order(pair[1], res)
+        else:
+            res.append(pair)
+
+    pre_order(nested_concat, res)
+    return res
+
+def one_of(parsers):
+    nested = reduce(or_, parsers)
+    return nested ** extract
+
+def join(sl):
+    return ''.join(sl)
+
+def rep_with_sep(to_rep, sep):
+    if not isinstance(sep, Ignore):
+        sep = sep.ignore()
+    r = to_rep + ~(sep + to_rep)
+    r = r ** (lambda x: [x[0]] + x[1])
+    return r
+
+ALPHA = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
+DIGIT = set('0123456789')
+ALPHA_DIGIT = ALPHA | DIGIT
+
+Alpha = one_of(map(StrLiteral, ALPHA))
+Digit = one_of(map(StrLiteral, DIGIT))
+
+Equal, Comma, Dot = [StrLiteral(c).ignore() for c in ['=', ',', '.']]
+Underscore = StrLiteral('_')
+NewLine = (~ StrLiteral('\n')).ignore()
+
+def identifier_matcher(text, pos):
+    """identifier"""
+    end = len(text)
+    start = pos
+    if pos >= end:
+        return None
+    first = text[pos]
+    if first != '_' and first not in ALPHA:
+        return None
+    pos += 1
+    while pos < end:
+        char = text[pos]
+        if char == '_' or char in ALPHA_DIGIT:
+            pos += 1
+        else:
+            break
+    return text[start:pos], pos
+
+Identifier = CustomMatcher(identifier_matcher)
+
+IdTuple = rep_with_sep(Identifier, Comma)
+
+Definition = Identifier + Equal + IdTuple + NewLine
+
+Relation = Identifier + Equal + IdTuple + NewLine
+Relation = Relation ** (lambda x: (x[0], 1 + len(x[1][1])))
+
+def pyparser_matcher(text, pos):
+    """syntactically correct python code"""
+    line_end = text.find('\n', pos)
+    if line_end == -1:
+        return None
+    try:
+        c = compile(text[pos:line_end], '__abac_model__.py', 'eval')
+    except SyntaxError:
+        return None
+    return c, line_end
+
+PyExpr = CustomMatcher(pyparser_matcher)
+Matcher = Identifier + Equal + PyExpr + NewLine
+
+RequestDefHeader = StrLiteral('[requests]') + NewLine
+TermDefHeader    = StrLiteral('[terms]') + NewLine
+MatchersHeader   = StrLiteral('[matchers]') + NewLine
+
+RequestDefSec = RequestDefHeader.ignore() + ~Definition
+TermDefSec = TermDefHeader.ignore() + ~Definition
+MatchersSec = MatchersHeader.ignore() + ~Matcher
+
+ModelDef = (RequestDefSec + TermDefSec + MatchersSec) ** flatten
+
+def preprocess(conf):
+    # process escaped line breaks
+    conf = conf.replace('\\\n', '')
+    # remove comments    
+    conf = '\n'.join(line.partition('#')[0] for line in conf.splitlines())
+    # remove redundant new lines
+    conf = conf.strip()
+
+    return conf + '\n'
+
+def parse_model(text):
+    text = preprocess(text)
+    raw_model = ModelDef.parse_from(Stream(text)).payload
+    return raw_model
+
+class InvalidModelDefinition(Exception):
+    def __init__(self, msg = ''):
+        super(InvalidModelDefinition, self).__init__(msg)        
+
+    @staticmethod
+    def redundant_def(redefined_vars, g1, g2):
+        msg_parts = [
+            'multiple definition(s) of identifiers(s)',
+            ', '.join(redfined_vars),
+            'found in sections',
+            g1, g2
+        ]
+        return InvalidModelDefinition(''.join(msg_parts))
+
+    @staticmethod
+    def missing_matchers(missing_matchers):
+        msg = 'missing matcher(s) for request type(s): {}'
+        return InvalidModelDefinition(msg.format(', '.join(missing_matchers)))
+
+    @staticmethod
+    def unknown_requests(unknown_requests):
+        msg = 'matcher(s) defined for unknown request type(s): {}'
+        return InvalidModelDefinition(msg.format(', '.join(unknown_requests)))
+
+class Request(object):
+    def __init__(self, attrs, vals):
+        assert len(attrs) == len(vals)
+        self.__named_attrs = attrs
+        for attr, val in zip(attrs, vals):
+            setattr(self, attr, val)
+
+    def __repr__(self):
+        parts = ['Request {\n']
+        for attr in self.__named_attrs:
+            parts.append('  ')
+            parts.append(attr)
+            parts.append(': ')
+            parts.append(repr(getattr(self, attr)))
+            parts.append('\n')
+        parts.append('}\n')
+        return ''.join(parts)
+
+class QueryResult(object):
+    def __init__(self, generator):
+        self.__gen = generator
+
+    def __iter__(self):
+        return self.__gen
+
+    def __le__(self, iterable):
+        return set(self) <= set(iterable)
+
+    def __lt__(self, iterable):
+        return set(self) < set(iterable)
+
+    def __ge__(self, iterable):
+        return set(self) >= set(iterable)
+
+    def __gt__(self, iterable):
+        return set(self) > set(iterable)
+
+class Term(object):
+    PLACEHOLDER = object()
+    WILDCARD = None
+    def __init__(self, arity):
+        self.__arity = arity
+        self.__facts = set()
+
+    def add_facts(self, facts):
+        for fact in facts:
+            self.add_fact(fact)
+
+    def add_fact(self, fact):
+        assert len(fact) == self.__arity
+        if not isinstance(fact, tuple):
+            fact = tuple(fact)
+        self.__facts.add(fact)
+
+    def __call__(self, *args):
+        assert len(args) == self.__arity
+        # When all arguments are concrete, calling a term just returns boolean 
results
+        # indicating whether the called tuple is part of the known facts
+        n_placeholders = sum(arg is Term.PLACEHOLDER for arg in args)
+        if not n_placeholders:
+            return any(all(a == b for a, b in zip(fact, args)) for fact in 
self.__facts)
+        # If arguments contain one or more placeholders, calling a term is 
more like a
+        # query. The call returns a generator that iterates all facts that 
match with
+        # the pattern described by the arguments
+        def gen():
+            for fact in self.__facts:
+                rns = []
+                matched = True
+                for a, b in zip(fact, args):
+                    if b is Term.PLACEHOLDER:
+                        rns.append(a)
+                    else:
+                        if a != b:
+                            matched = False
+                            break
+                if matched:
+                    if n_placeholders == 1:
+                        yield rns[0]
+                    else:
+                        yield tuple(rns)
+        return QueryResult(gen())
+
+class Model(object):
+    def __init__(self, raw_model):
+        request_def, term_def, matchers = raw_model
+        self.__request_template = { r[0]:r[1] for r in request_def }
+        self.__term_template = { t[0]:t[1] for t in term_def }
+        self.__matchers = { m[0]:m[1] for m in matchers }
+
+        def_sections = zip(
+            ['[requests]', '[terms]'],
+            [self.__request_template, self.__term_template],
+        )
+
+        n_sec = len(def_sections)
+        for i in range(n_sec):
+            for j in range(i + 1, n_sec):
+                overlap = set(def_sections[i][1].keys()) & 
set(def_sections[j][1].keys())
+                if overlap:
+                    raise InvalidModelDefinition.redundant_def(
+                        overalp, def_sections[i][0], def_sections[j][0]
+                    )
+
+        missing_matchers = set(self.__request_template.keys()) - 
set(self.__matchers.keys())
+        if missing_matchers:
+            raise InvalidModelDefinition.missing_matchers(missing_matchers)
+
+        unknown_requests = set(self.__matchers.keys()) - 
set(self.__request_template.keys())
+        if unknown_requests:
+            raise InvalidModelDefinition.unknown_requests(unknown_requests)
+
+        self.__term_knowledge_base = {
+            term_name:Term(len(term_tpl)) for term_name, term_tpl in 
self.__term_template.items()
+        }
+
+    def add_term_items(self, term_items):
+        for ti in term_items:
+            self.add_term_item(ti[0], ti[1:])
+
+    def add_term_item(self, term_name, fact):
+        term = self.__term_knowledge_base[term_name]
+        term.add_fact(fact)
+
+    def get_matcher_proxy(self, request_type, env):
+        def matcher_proxy():
+            return eval(self.__matchers[request_type], env)
+        return matcher_proxy
+
+    def enforce(self, request_type, request_content):
+        tpl = self.__request_template[request_type]
+        request = Request(tpl, request_content)
+
+        enforcer_env = {
+            request_type: request,
+            'true': True, 'false': False, 'null': None,
+            '_': Term.PLACEHOLDER,
+            'X': Term.WILDCARD,
+        }
+        enforcer_env.update(self.__term_knowledge_base)
+
+        return eval(self.__matchers[request_type], enforcer_env)
+
+global_perm_model = None
+
+if __name__ == '__builtin__':
+    from acs_py_enclave import ffi
+else:
+    class ffi:
+        @staticmethod
+        def def_extern():
+            return lambda x: x
+
+        @staticmethod
+        def string(s):
+            return s
+
[email protected]_extern()
+def acs_setup_model(conf):
+    try:
+        global global_perm_model
+        conf = ffi.string(conf)
+        global_perm_model = Model(parse_model(conf))
+    except:
+        return -1
+    return 0
+
[email protected]_extern()
+def acs_enforce_request(request_type, request_content):
+    try:
+        request_type = ffi.string(request_type)
+        # request_content is a list of ffi c strings which are syntactically 
valid
+        # python primitive-type objects, including strings, integers, foating 
point
+        # numbers, and lists/dictionaries of primitive-type objects
+        request_content = eval(ffi.string(request_content))
+        return global_perm_model.enforce(request_type, request_content)
+    except:
+        return -1
+
[email protected]_extern()
+def acs_announce_fact(term_type, term_fact):
+    try:
+        term_type = ffi.string(term_type)
+        term_fact = eval(ffi.string(term_fact))
+        global_perm_model.add_term_item(term_type, term_fact)
+    except:
+        return -1
+    return 0
diff --git a/services/access_control/python/acs_engine_test.py 
b/services/access_control/python/acs_engine_test.py
new file mode 100644
index 0000000..b951d41
--- /dev/null
+++ b/services/access_control/python/acs_engine_test.py
@@ -0,0 +1,82 @@
+if __name__ == '__main__':
+    import sys
+    import os
+    from acs_engine import *
+
+    model_path = os.path.join(os.path.dirname(__file__), '../model.conf')
+    test_model = open(model_path).read()
+    acs_setup_model(test_model)
+
+    FUSION_TASK               = "data_fusion"
+    FUSION_TASK_PARTY_1       = "usr_party1"
+    FUSION_TASK_DATA_1        = "data1"
+    FUSION_TASK_PARTY_2       = "usr_party2"
+    FUSION_TASK_DATA_2        = "data2"
+    FUSION_TASK_SCRIPT        = "fusion_script"
+    FUSION_TASK_SCRIPT_WRITER = "usr_party3"
+    PUBLIC_SCRIPT             = "public_script"
+    PUBLIC_SCRIPT_WRITER      = "usr_party4"
+
+    IRRELEVANT_TASK           = "task_irrelevant"
+    IRRELEVANT_PARTY          = "usr_irrelevant"
+    IRRELEVANT_DATA           = "data_irrelevant"
+    
+    acs_announce_fact('task_creator', repr([FUSION_TASK, FUSION_TASK_PARTY_1]))
+    acs_announce_fact('task_participant', repr([FUSION_TASK, 
FUSION_TASK_PARTY_1]))
+    acs_announce_fact('task_participant', repr([FUSION_TASK, 
FUSION_TASK_PARTY_2]))
+    
+    acs_announce_fact('data_owner', repr([FUSION_TASK_DATA_1, 
FUSION_TASK_PARTY_1]))
+    acs_announce_fact('data_owner', repr([FUSION_TASK_DATA_2, 
FUSION_TASK_PARTY_2]))
+    acs_announce_fact('data_owner', repr([IRRELEVANT_DATA, IRRELEVANT_PARTY]))
+    
+    acs_announce_fact('script_owner', repr([FUSION_TASK_SCRIPT, 
FUSION_TASK_SCRIPT_WRITER]))
+
+    acs_announce_fact('script_owner', repr([PUBLIC_SCRIPT, 
PUBLIC_SCRIPT_WRITER]))
+    acs_announce_fact('is_public_script', repr([PUBLIC_SCRIPT]))
+
+
+    assert acs_enforce_request('launch_task', repr([FUSION_TASK, 
set([FUSION_TASK_PARTY_1, FUSION_TASK_PARTY_2])]))
+    assert not acs_enforce_request('launch_task', repr([FUSION_TASK, set()]))
+    assert not acs_enforce_request('launch_task', repr([FUSION_TASK, 
set([FUSION_TASK_PARTY_1])]))
+    assert not acs_enforce_request('launch_task', repr([FUSION_TASK, 
set([FUSION_TASK_PARTY_2])]))
+    
+    assert acs_enforce_request('access_data', repr([FUSION_TASK, 
FUSION_TASK_DATA_1]))
+    assert acs_enforce_request('access_data', repr([FUSION_TASK, 
FUSION_TASK_DATA_2]))
+    assert not acs_enforce_request('access_data', repr([FUSION_TASK, 
IRRELEVANT_DATA]))
+
+    assert acs_enforce_request('access_script', repr([FUSION_TASK, 
PUBLIC_SCRIPT]))
+    assert not acs_enforce_request('access_script', repr([FUSION_TASK, 
FUSION_TASK_SCRIPT]))
+    
+    acs_announce_fact('task_participant', repr([FUSION_TASK, 
FUSION_TASK_SCRIPT_WRITER]))
+    assert acs_enforce_request('access_script', repr([FUSION_TASK, 
FUSION_TASK_SCRIPT]))
+
+    acs_announce_fact('task_participant', repr([FUSION_TASK, 
IRRELEVANT_PARTY]))
+    assert acs_enforce_request('access_script', repr([FUSION_TASK, 
FUSION_TASK_SCRIPT]))
+
+    acs_announce_fact('task_creator', repr([IRRELEVANT_TASK, 
IRRELEVANT_PARTY]))
+    acs_announce_fact('task_participant', repr([IRRELEVANT_TASK, 
IRRELEVANT_PARTY]))
+    acs_announce_fact('task_participant', repr([IRRELEVANT_TASK, 
FUSION_TASK_PARTY_2]))
+    
+    assert acs_enforce_request('launch_task', repr([IRRELEVANT_TASK, 
set([IRRELEVANT_PARTY, FUSION_TASK_PARTY_2])]))
+    assert not acs_enforce_request('access_data', repr([IRRELEVANT_TASK, 
FUSION_TASK_DATA_1]))
+    assert acs_enforce_request('access_data', repr([IRRELEVANT_TASK, 
FUSION_TASK_DATA_2]))
+    assert not acs_enforce_request('access_script', repr([IRRELEVANT_TASK, 
FUSION_TASK_SCRIPT]))
+    assert acs_enforce_request('access_script', repr([IRRELEVANT_TASK, 
PUBLIC_SCRIPT]))
+
+    assert acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_1, 
FUSION_TASK_DATA_1]))
+    assert not acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_1, 
FUSION_TASK_DATA_2]))
+    assert not acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_1, 
IRRELEVANT_DATA]))
+    assert not acs_enforce_request('delete_script', repr([FUSION_TASK_PARTY_1, 
FUSION_TASK_SCRIPT]))
+    assert not acs_enforce_request('delete_script', repr([FUSION_TASK_PARTY_1, 
PUBLIC_SCRIPT]))
+
+    assert not acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_2, 
FUSION_TASK_DATA_1]))
+    assert acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_2, 
FUSION_TASK_DATA_2]))
+    assert not acs_enforce_request('delete_data', repr([FUSION_TASK_PARTY_2, 
IRRELEVANT_DATA]))
+    assert not acs_enforce_request('delete_script', repr([FUSION_TASK_PARTY_2, 
FUSION_TASK_SCRIPT]))
+    assert not acs_enforce_request('delete_script', repr([FUSION_TASK_PARTY_2, 
PUBLIC_SCRIPT]))
+
+    assert not acs_enforce_request('delete_data', 
repr([FUSION_TASK_SCRIPT_WRITER, FUSION_TASK_DATA_1]))
+    assert not acs_enforce_request('delete_data', 
repr([FUSION_TASK_SCRIPT_WRITER, FUSION_TASK_DATA_2]))
+    assert not acs_enforce_request('delete_data', 
repr([FUSION_TASK_SCRIPT_WRITER, IRRELEVANT_DATA]))
+    assert acs_enforce_request('delete_script', 
repr([FUSION_TASK_SCRIPT_WRITER, FUSION_TASK_SCRIPT]))
+    assert not acs_enforce_request('delete_script', 
repr([FUSION_TASK_SCRIPT_WRITER, PUBLIC_SCRIPT]))
diff --git a/services/access_control/python/ffi.py 
b/services/access_control/python/ffi.py
new file mode 100644
index 0000000..3f56be7
--- /dev/null
+++ b/services/access_control/python/ffi.py
@@ -0,0 +1,15 @@
+import os
+import sgx_cffi
+import _cffi_backend as backend
+
+ffi = sgx_cffi.FFI(backend)
+
+ffi.embedding_api("int acs_setup_model(const char *configuration);")
+ffi.embedding_api("""int acs_enforce_request(const char *request_type, 
+                                             const char *request_content);""")
+ffi.embedding_api("""int acs_announce_fact(const char *term_type, 
+                                           const char *term_fact);""")
+with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 
"acs_engine.py")) as f:
+    ffi.embedding_init_code(f.read())
+ffi.set_source('acs_py_enclave', '')
+ffi.emit_c_code(os.environ.get('PYPY_FFI_OUTDIR', ".") + "/acs_py_enclave.c")
diff --git a/services/proto/build.rs b/services/proto/build.rs
index 8f4b885..205fc4a 100644
--- a/services/proto/build.rs
+++ b/services/proto/build.rs
@@ -21,6 +21,7 @@ use std::str;
 
 fn main() {
     let proto_files = [
+        "src/proto/teaclave_access_control_service.proto",
         "src/proto/teaclave_authentication_service.proto",
         "src/proto/teaclave_common.proto",
         "src/proto/teaclave_storage_service.proto",
diff --git a/services/proto/src/lib.rs b/services/proto/src/lib.rs
index c069174..6783c79 100644
--- a/services/proto/src/lib.rs
+++ b/services/proto/src/lib.rs
@@ -19,6 +19,7 @@
 #[cfg(feature = "mesalock_sgx")]
 extern crate sgx_tstd as std;
 
+pub mod teaclave_access_control_service;
 pub mod teaclave_authentication_service;
 pub mod teaclave_common;
 pub mod teaclave_execution_service;
@@ -55,3 +56,7 @@ pub mod teaclave_frontend_service_proto {
 pub mod teaclave_management_service_proto {
     include_proto!("teaclave_management_service_proto");
 }
+
+pub mod teaclave_access_control_service_proto {
+    include_proto!("teaclave_access_control_service_proto");
+}
diff --git a/services/proto/src/proto/teaclave_access_control_service.proto 
b/services/proto/src/proto/teaclave_access_control_service.proto
new file mode 100644
index 0000000..af94428
--- /dev/null
+++ b/services/proto/src/proto/teaclave_access_control_service.proto
@@ -0,0 +1,48 @@
+syntax = "proto3";
+
+package teaclave_access_control_service_proto;
+
+message AuthorizeDataRequest {
+  string subject_user_id = 1;
+  string object_data_id = 2;
+}
+
+message AuthorizeDataResponse {
+  bool accept = 1;
+}
+
+message AuthorizeFunctionRequest {
+  string subject_user_id = 1;
+  string object_function_id = 2;
+}
+
+message AuthorizeFunctionResponse {
+  bool accept = 1;
+}
+
+message AuthorizeTaskRequest {
+  string subject_user_id = 1;
+  string object_task_id = 2;
+}
+
+message AuthorizeTaskResponse {
+  bool accept = 1;
+}
+
+message AuthorizeStagedTaskRequest {
+  string subject_task_id = 1;
+  string object_function_id = 2;
+  repeated string object_input_data_id_list = 3;
+  repeated string object_output_data_id_list = 4;
+}
+
+message AuthorizeStagedTaskResponse {
+  bool accept = 1;
+}
+
+service TeaclaveAccessControl {
+  rpc AuthorizeData (AuthorizeDataRequest) returns (AuthorizeDataResponse);
+  rpc AuthorizeFunction (AuthorizeFunctionRequest) returns 
(AuthorizeFunctionResponse);
+  rpc AuthorizeTask (AuthorizeTaskRequest) returns (AuthorizeTaskResponse);
+  rpc AuthorizeStagedTask (AuthorizeStagedTaskRequest) returns 
(AuthorizeStagedTaskResponse);
+}
diff --git a/services/proto/src/teaclave_access_control_service.rs 
b/services/proto/src/teaclave_access_control_service.rs
new file mode 100644
index 0000000..2a51071
--- /dev/null
+++ b/services/proto/src/teaclave_access_control_service.rs
@@ -0,0 +1,219 @@
+use crate::teaclave_access_control_service_proto as proto;
+use anyhow::{Error, Result};
+use serde::{Deserialize, Serialize};
+use std::prelude::v1::*;
+
+pub use proto::TeaclaveAccessControl;
+pub use proto::TeaclaveAccessControlClient;
+pub use proto::TeaclaveAccessControlRequest;
+pub use proto::TeaclaveAccessControlResponse;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeDataRequest {
+    pub subject_user_id: String,
+    pub object_data_id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeDataResponse {
+    pub accept: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeFunctionRequest {
+    pub subject_user_id: String,
+    pub object_function_id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeFunctionResponse {
+    pub accept: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeTaskRequest {
+    pub subject_user_id: String,
+    pub object_task_id: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeTaskResponse {
+    pub accept: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeStagedTaskRequest {
+    pub subject_task_id: String,
+    pub object_function_id: String,
+    pub object_input_data_id_list: Vec<String>,
+    pub object_output_data_id_list: Vec<String>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AuthorizeStagedTaskResponse {
+    pub accept: bool,
+}
+
+impl std::convert::TryFrom<proto::AuthorizeDataRequest> for 
AuthorizeDataRequest {
+    type Error = Error;
+
+    fn try_from(proto: proto::AuthorizeDataRequest) -> Result<Self> {
+        let ret = Self {
+            subject_user_id: proto.subject_user_id,
+            object_data_id: proto.object_data_id,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<AuthorizeDataRequest> for proto::AuthorizeDataRequest {
+    fn from(request: AuthorizeDataRequest) -> Self {
+        Self {
+            subject_user_id: request.subject_user_id,
+            object_data_id: request.object_data_id,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeDataResponse> for 
AuthorizeDataResponse {
+    type Error = Error;
+
+    fn try_from(proto: proto::AuthorizeDataResponse) -> Result<Self> {
+        Ok(Self {
+            accept: proto.accept,
+        })
+    }
+}
+
+impl From<AuthorizeDataResponse> for proto::AuthorizeDataResponse {
+    fn from(response: AuthorizeDataResponse) -> Self {
+        Self {
+            accept: response.accept,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeFunctionRequest> for 
AuthorizeFunctionRequest {
+    type Error = Error;
+
+    fn try_from(proto: proto::AuthorizeFunctionRequest) -> Result<Self> {
+        let ret = Self {
+            subject_user_id: proto.subject_user_id,
+            object_function_id: proto.object_function_id,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<AuthorizeFunctionRequest> for proto::AuthorizeFunctionRequest {
+    fn from(request: AuthorizeFunctionRequest) -> Self {
+        Self {
+            subject_user_id: request.subject_user_id,
+            object_function_id: request.object_function_id,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeFunctionResponse> for 
AuthorizeFunctionResponse {
+    type Error = Error;
+
+    fn try_from(proto: proto::AuthorizeFunctionResponse) -> Result<Self> {
+        Ok(Self {
+            accept: proto.accept,
+        })
+    }
+}
+
+impl From<AuthorizeFunctionResponse> for proto::AuthorizeFunctionResponse {
+    fn from(response: AuthorizeFunctionResponse) -> Self {
+        Self {
+            accept: response.accept,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeTaskRequest> for 
AuthorizeTaskRequest {
+    type Error = Error;
+
+    fn try_from(proto: proto::AuthorizeTaskRequest) -> Result<Self> {
+        let ret = Self {
+            subject_user_id: proto.subject_user_id,
+            object_task_id: proto.object_task_id,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<AuthorizeTaskRequest> for proto::AuthorizeTaskRequest {
+    fn from(request: AuthorizeTaskRequest) -> Self {
+        Self {
+            subject_user_id: request.subject_user_id,
+            object_task_id: request.object_task_id,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeTaskResponse> for 
AuthorizeTaskResponse {
+    type Error = Error;
+
+    fn try_from(proto: proto::AuthorizeTaskResponse) -> Result<Self> {
+        Ok(Self {
+            accept: proto.accept,
+        })
+    }
+}
+
+impl From<AuthorizeTaskResponse> for proto::AuthorizeTaskResponse {
+    fn from(response: AuthorizeTaskResponse) -> Self {
+        Self {
+            accept: response.accept,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeStagedTaskRequest> for 
AuthorizeStagedTaskRequest {
+    type Error = Error;
+
+    fn try_from(proto: proto::AuthorizeStagedTaskRequest) -> Result<Self> {
+        let ret = Self {
+            subject_task_id: proto.subject_task_id,
+            object_function_id: proto.object_function_id,
+            object_input_data_id_list: proto.object_input_data_id_list,
+            object_output_data_id_list: proto.object_output_data_id_list,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<AuthorizeStagedTaskRequest> for proto::AuthorizeStagedTaskRequest {
+    fn from(request: AuthorizeStagedTaskRequest) -> Self {
+        Self {
+            subject_task_id: request.subject_task_id,
+            object_function_id: request.object_function_id,
+            object_input_data_id_list: request.object_input_data_id_list,
+            object_output_data_id_list: request.object_output_data_id_list,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::AuthorizeStagedTaskResponse> for 
AuthorizeStagedTaskResponse {
+    type Error = Error;
+
+    fn try_from(proto: proto::AuthorizeStagedTaskResponse) -> Result<Self> {
+        Ok(Self {
+            accept: proto.accept,
+        })
+    }
+}
+
+impl From<AuthorizeStagedTaskResponse> for proto::AuthorizeStagedTaskResponse {
+    fn from(response: AuthorizeStagedTaskResponse) -> Self {
+        Self {
+            accept: response.accept,
+        }
+    }
+}
diff --git a/tests/fixtures/runtime.config.toml 
b/tests/fixtures/runtime.config.toml
index cd544c8..716b478 100644
--- a/tests/fixtures/runtime.config.toml
+++ b/tests/fixtures/runtime.config.toml
@@ -3,6 +3,7 @@ authentication = { listen_address = "0.0.0.0:7776" }
 frontend = { listen_address = "0.0.0.0:7777" }
 
 [internal_endpoints]
+access_control = { listen_address = "0.0.0.0:7779", advertised_address = 
"localhost:7779" }
 authentication = { listen_address = "0.0.0.0:17776", advertised_address = 
"localhost:17776" }
 management = { listen_address = "0.0.0.0:17777", advertised_address = 
"localhost:17777" }
 storage = { listen_address = "0.0.0.0:7778", advertised_address = 
"127.0.0.1:7778", inbound_services = ["frontend"] }
diff --git a/tests/functional_tests/enclave/src/lib.rs 
b/tests/functional_tests/enclave/src/lib.rs
index 9f3ad37..b4b3106 100644
--- a/tests/functional_tests/enclave/src/lib.rs
+++ b/tests/functional_tests/enclave/src/lib.rs
@@ -36,6 +36,7 @@ use teaclave_ipc::{handle_ecall, register_ecall_handler};
 use teaclave_service_enclave_utils::ServiceEnclave;
 use teaclave_test_utils::check_all_passed;
 
+mod teaclave_access_control_service;
 mod teaclave_authentication_service;
 mod teaclave_execution_service;
 mod teaclave_frontend_service;
@@ -44,6 +45,7 @@ mod teaclave_storage_service;
 #[handle_ecall]
 fn handle_run_test(_args: &RunTestInput) -> Result<RunTestOutput> {
     let ret = check_all_passed!(
+        teaclave_access_control_service::run_tests(),
         teaclave_authentication_service::run_tests(),
         teaclave_storage_service::run_tests(),
         teaclave_execution_service::run_tests(),
diff --git 
a/tests/functional_tests/enclave/src/teaclave_access_control_service.rs 
b/tests/functional_tests/enclave/src/teaclave_access_control_service.rs
new file mode 100644
index 0000000..32ebb43
--- /dev/null
+++ b/tests/functional_tests/enclave/src/teaclave_access_control_service.rs
@@ -0,0 +1,222 @@
+use std::prelude::v1::*;
+use teaclave_attestation::verifier;
+use teaclave_config::RuntimeConfig;
+use teaclave_config::BUILD_CONFIG;
+use teaclave_proto::teaclave_access_control_service::*;
+use teaclave_rpc::config::SgxTrustedTlsClientConfig;
+use teaclave_rpc::endpoint::Endpoint;
+use teaclave_types::EnclaveInfo;
+
+pub fn run_tests() -> bool {
+    use teaclave_test_utils::*;
+
+    run_tests!(
+        test_authorize_data_success,
+        test_authorize_data_fail,
+        test_authorize_function_success,
+        test_authorize_function_fail,
+        test_authorize_task_success,
+        test_authorize_task_fail,
+        test_authorize_staged_task_success,
+        test_authorize_staged_task_fail,
+        test_concurrency,
+    )
+}
+
+fn get_client() -> TeaclaveAccessControlClient {
+    let runtime_config = 
RuntimeConfig::from_toml("runtime.config.toml").expect("runtime");
+    let enclave_info =
+        
EnclaveInfo::from_bytes(&runtime_config.audit.enclave_info_bytes.as_ref().unwrap());
+    let enclave_attr = enclave_info
+        .get_enclave_attr("teaclave_access_control_service")
+        .expect("access_control");
+    let config = SgxTrustedTlsClientConfig::new().attestation_report_verifier(
+        vec![enclave_attr],
+        BUILD_CONFIG.ias_root_ca_cert,
+        verifier::universal_quote_verifier,
+    );
+
+    let channel = Endpoint::new(
+        &runtime_config
+            .internal_endpoints
+            .access_control
+            .advertised_address,
+    )
+    .config(config)
+    .connect()
+    .unwrap();
+    TeaclaveAccessControlClient::new(channel).unwrap()
+}
+
+fn test_authorize_data_success() {
+    let mut client = get_client();
+
+    let request = AuthorizeDataRequest {
+        subject_user_id: "mock_user_a".to_string(),
+        object_data_id: "mock_data".to_string(),
+    };
+    let response_result = client.authorize_data(request);
+    assert!(response_result.is_ok());
+    assert!(response_result.unwrap().accept);
+}
+
+fn test_authorize_data_fail() {
+    let mut client = get_client();
+
+    let request = AuthorizeDataRequest {
+        subject_user_id: "mock_user_d".to_string(),
+        object_data_id: "mock_data".to_string(),
+    };
+    let response_result = client.authorize_data(request);
+    assert!(response_result.is_ok());
+    assert!(!response_result.unwrap().accept);
+
+    let request = AuthorizeDataRequest {
+        subject_user_id: "mock_user_a".to_string(),
+        object_data_id: "mock_data_b".to_string(),
+    };
+    let response_result = client.authorize_data(request);
+    assert!(response_result.is_ok());
+    assert!(!response_result.unwrap().accept);
+}
+
+fn test_authorize_function_success() {
+    let mut client = get_client();
+
+    let request = AuthorizeFunctionRequest {
+        subject_user_id: "mock_public_function_owner".to_string(),
+        object_function_id: "mock_public_function".to_string(),
+    };
+    let response_result = client.authorize_function(request);
+    assert!(response_result.is_ok());
+    assert!(response_result.unwrap().accept);
+
+    let request = AuthorizeFunctionRequest {
+        subject_user_id: "mock_private_function_owner".to_string(),
+        object_function_id: "mock_private_function".to_string(),
+    };
+    let response_result = client.authorize_function(request);
+    assert!(response_result.is_ok());
+    assert!(response_result.unwrap().accept);
+
+    let request = AuthorizeFunctionRequest {
+        subject_user_id: "mock_private_function_owner".to_string(),
+        object_function_id: "mock_public_function".to_string(),
+    };
+    let response_result = client.authorize_function(request);
+    assert!(response_result.is_ok());
+    assert!(response_result.unwrap().accept);
+}
+
+fn test_authorize_function_fail() {
+    let mut client = get_client();
+    let request = AuthorizeFunctionRequest {
+        subject_user_id: "mock_public_function_owner".to_string(),
+        object_function_id: "mock_private_function".to_string(),
+    };
+    let response_result = client.authorize_function(request);
+    assert!(response_result.is_ok());
+    assert!(!response_result.unwrap().accept);
+}
+
+fn test_authorize_task_success() {
+    let mut client = get_client();
+    let request = AuthorizeTaskRequest {
+        subject_user_id: "mock_participant_a".to_string(),
+        object_task_id: "mock_task".to_string(),
+    };
+    let response_result = client.authorize_task(request);
+    assert!(response_result.is_ok());
+    assert!(response_result.unwrap().accept);
+
+    let request = AuthorizeTaskRequest {
+        subject_user_id: "mock_participant_b".to_string(),
+        object_task_id: "mock_task".to_string(),
+    };
+    let response_result = client.authorize_task(request);
+    assert!(response_result.is_ok());
+    assert!(response_result.unwrap().accept);
+}
+
+fn test_authorize_task_fail() {
+    let mut client = get_client();
+    let request = AuthorizeTaskRequest {
+        subject_user_id: "mock_participant_c".to_string(),
+        object_task_id: "mock_task".to_string(),
+    };
+    let response_result = client.authorize_task(request);
+    assert!(response_result.is_ok());
+    assert!(!response_result.unwrap().accept);
+}
+
+fn test_authorize_staged_task_success() {
+    let mut client = get_client();
+    let request = AuthorizeStagedTaskRequest {
+        subject_task_id: "mock_staged_task".to_string(),
+        object_function_id: "mock_staged_allowed_private_function".to_string(),
+        object_input_data_id_list: vec![
+            "mock_staged_allowed_data1".to_string(),
+            "mock_staged_allowed_data2".to_string(),
+            "mock_staged_allowed_data3".to_string(),
+        ],
+        object_output_data_id_list: vec![
+            "mock_staged_allowed_data1".to_string(),
+            "mock_staged_allowed_data2".to_string(),
+            "mock_staged_allowed_data3".to_string(),
+        ],
+    };
+    let response_result = client.authorize_staged_task(request);
+    assert!(response_result.is_ok());
+    assert!(response_result.unwrap().accept);
+}
+
+fn test_authorize_staged_task_fail() {
+    let mut client = get_client();
+    let request = AuthorizeStagedTaskRequest {
+        subject_task_id: "mock_staged_task".to_string(),
+        object_function_id: 
"mock_staged_disallowed_private_function".to_string(),
+        object_input_data_id_list: vec![],
+        object_output_data_id_list: vec![],
+    };
+    let response_result = client.authorize_staged_task(request);
+    assert!(response_result.is_ok());
+    assert!(!response_result.unwrap().accept);
+
+    let request = AuthorizeStagedTaskRequest {
+        subject_task_id: "mock_staged_task".to_string(),
+        object_function_id: "mock_staged_allowed_private_function".to_string(),
+        object_input_data_id_list: 
vec!["mock_staged_disallowed_data1".to_string()],
+        object_output_data_id_list: vec![],
+    };
+    let response_result = client.authorize_staged_task(request);
+    assert!(response_result.is_ok());
+    assert!(!response_result.unwrap().accept);
+
+    let request = AuthorizeStagedTaskRequest {
+        subject_task_id: "mock_staged_task".to_string(),
+        object_function_id: "mock_staged_allowed_private_function".to_string(),
+        object_input_data_id_list: vec![],
+        object_output_data_id_list: 
vec!["mock_staged_disallowed_data2".to_string()],
+    };
+    let response_result = client.authorize_staged_task(request);
+    assert!(response_result.is_ok());
+    assert!(!response_result.unwrap().accept);
+}
+
+fn test_concurrency() {
+    let mut thread_pool = Vec::new();
+    for _i in 0..10 {
+        let child = std::thread::spawn(move || {
+            for _j in 0..10 {
+                test_authorize_data_fail();
+                test_authorize_function_fail();
+                test_authorize_task_success();
+                test_authorize_staged_task_fail();
+            }
+        });
+        thread_pool.push(child);
+    }
+    for thr in thread_pool.into_iter() {
+        assert!(thr.join().is_ok());
+    }
+}
diff --git a/tests/unit_tests/enclave/Cargo.toml 
b/tests/unit_tests/enclave/Cargo.toml
index 1e30aab..9256c77 100644
--- a/tests/unit_tests/enclave/Cargo.toml
+++ b/tests/unit_tests/enclave/Cargo.toml
@@ -21,6 +21,8 @@ mesalock_sgx = [
   "teaclave_types/mesalock_sgx",
   "teaclave_types/enclave_unit_test",
   "teaclave_config/mesalock_sgx",
+  "teaclave_access_control_service_enclave/mesalock_sgx",
+  "teaclave_access_control_service_enclave/enclave_unit_test",
   "teaclave_authentication_service_enclave/mesalock_sgx",
   "teaclave_authentication_service_enclave/enclave_unit_test",
   "teaclave_storage_service_enclave/mesalock_sgx",
@@ -38,6 +40,7 @@ anyhow      = { version = "1.0.26" }
 serde       = { version = "1.0.92" }
 thiserror   = { version = "1.0.9" }
 
+teaclave_access_control_service_enclave = { path = 
"../../../services/access_control/enclave" }
 teaclave_authentication_service_enclave = { path = 
"../../../services/authentication/enclave" }
 teaclave_storage_service_enclave = { path = 
"../../../services/storage/enclave" }
 teaclave_execution_service_enclave = { path = 
"../../../services/execution/enclave" }
diff --git a/tests/unit_tests/enclave/src/lib.rs 
b/tests/unit_tests/enclave/src/lib.rs
index 722bbcc..74c5fae 100644
--- a/tests/unit_tests/enclave/src/lib.rs
+++ b/tests/unit_tests/enclave/src/lib.rs
@@ -34,6 +34,7 @@ use teaclave_ipc::proto::{
 use teaclave_ipc::{handle_ecall, register_ecall_handler};
 use teaclave_service_enclave_utils::ServiceEnclave;
 
+use teaclave_access_control_service_enclave;
 use teaclave_authentication_service_enclave;
 use teaclave_execution_service_enclave;
 use teaclave_test_utils::check_all_passed;
@@ -43,10 +44,11 @@ use teaclave_worker;
 fn handle_run_test(_args: &RunTestInput) -> Result<RunTestOutput> {
     let ret = check_all_passed!(
         teaclave_storage_service_enclave::tests::run_tests(),
+        teaclave_access_control_service_enclave::tests::run_tests(),
         teaclave_execution_service_enclave::tests::run_tests(),
         teaclave_authentication_service_enclave::tests::run_tests(),
         teaclave_worker::tests::run_tests(),
-        teaclave_types::tests::run_tests()
+        teaclave_types::tests::run_tests(),
     );
 
     assert_eq!(ret, true);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to