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 b177b47  [management] Handle function in management service (#221)
b177b47 is described below

commit b177b4779cd31bca9ec0f23067ca0c069b46bf8c
Author: TX <[email protected]>
AuthorDate: Tue Feb 11 13:04:51 2020 -0800

    [management] Handle function in management service (#221)
---
 services/management/enclave/src/function.rs        |  79 +++++++
 services/management/enclave/src/lib.rs             |   2 +
 services/management/enclave/src/service.rs         | 120 ++++++++++-
 .../src/proto/teaclave_frontend_service.proto      |  39 ++++
 .../src/proto/teaclave_management_service.proto    |   2 +
 services/proto/src/teaclave_frontend_service.rs    | 239 +++++++++++++++++++++
 .../enclave/src/teaclave_management_service.rs     |  70 +++++-
 7 files changed, 549 insertions(+), 2 deletions(-)

diff --git a/services/management/enclave/src/function.rs 
b/services/management/enclave/src/function.rs
new file mode 100644
index 0000000..4c7834e
--- /dev/null
+++ b/services/management/enclave/src/function.rs
@@ -0,0 +1,79 @@
+// 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 anyhow::{anyhow, Result};
+use serde::{Deserialize, Serialize};
+use serde_json;
+use std::prelude::v1::*;
+use teaclave_proto::teaclave_frontend_service::{
+    FunctionInput, FunctionOutput, RegisterFunctionRequest,
+};
+
+use uuid::Uuid;
+const SCRIPT_PREFIX: &str = "function-";
+const NATIVE_PREFIX: &str = "native-";
+#[derive(Debug, Deserialize, Serialize)]
+pub(crate) struct Function {
+    pub(crate) function_id: String,
+    pub(crate) name: String,
+    pub(crate) description: String,
+    pub(crate) payload: Vec<u8>,
+    pub(crate) is_public: bool,
+    pub(crate) arg_list: Vec<String>,
+    pub(crate) input_list: Vec<FunctionInput>,
+    pub(crate) output_list: Vec<FunctionOutput>,
+    pub(crate) owner: String,
+    pub(crate) is_native: bool,
+}
+
+impl Function {
+    pub(crate) fn new_from_register_request(
+        request: RegisterFunctionRequest,
+        owner: String,
+    ) -> Function {
+        let function_id = format!("{}{}", SCRIPT_PREFIX, 
Uuid::new_v4().to_string());
+        Function {
+            function_id,
+            name: request.name,
+            description: request.description,
+            payload: request.payload,
+            is_public: request.is_public,
+            arg_list: request.arg_list,
+            input_list: request.input_list,
+            output_list: request.output_list,
+            owner,
+            is_native: false,
+        }
+    }
+
+    pub(crate) fn from_slice(bytes: &[u8]) -> Result<Self> {
+        let ret: Function =
+            serde_json::from_slice(&bytes).map_err(|_| anyhow!("failed to 
Deserialize"))?;
+        Ok(ret)
+    }
+
+    pub(crate) fn to_vec(&self) -> Result<Vec<u8>> {
+        serde_json::to_vec(&self).map_err(|_| anyhow!("failed to Serialize"))
+    }
+
+    pub(crate) fn get_key_vec(&self) -> Vec<u8> {
+        self.function_id.as_bytes().to_vec()
+    }
+
+    pub(crate) fn is_function_id(id: &str) -> bool {
+        id.starts_with(NATIVE_PREFIX) || id.starts_with(SCRIPT_PREFIX)
+    }
+}
diff --git a/services/management/enclave/src/lib.rs 
b/services/management/enclave/src/lib.rs
index 1d78230..10fb682 100644
--- a/services/management/enclave/src/lib.rs
+++ b/services/management/enclave/src/lib.rs
@@ -40,6 +40,7 @@ use teaclave_rpc::server::SgxTrustedTlsServer;
 use teaclave_service_enclave_utils::ServiceEnclave;
 use teaclave_types::{EnclaveInfo, TeeServiceError, TeeServiceResult};
 mod file;
+mod function;
 mod fusion_data;
 mod service;
 
@@ -152,6 +153,7 @@ pub mod tests {
             service::tests::handle_input_file,
             service::tests::handle_output_file,
             service::tests::handle_fusion_data,
+            service::tests::handle_function,
         )
     }
 }
diff --git a/services/management/enclave/src/service.rs 
b/services/management/enclave/src/service.rs
index fd5069e..b934b21 100644
--- a/services/management/enclave/src/service.rs
+++ b/services/management/enclave/src/service.rs
@@ -1,10 +1,12 @@
 use crate::file::{InputFile, OutputFile};
+use crate::function::Function;
 use crate::fusion_data::FusionData;
 use anyhow::{anyhow, Result};
 use std::prelude::v1::*;
 use std::sync::{Arc, SgxMutex as Mutex};
 use teaclave_proto::teaclave_frontend_service::{
-    GetFusionDataRequest, GetFusionDataResponse, GetOutputFileRequest, 
GetOutputFileResponse,
+    GetFunctionRequest, GetFunctionResponse, GetFusionDataRequest, 
GetFusionDataResponse,
+    GetOutputFileRequest, GetOutputFileResponse, RegisterFunctionRequest, 
RegisterFunctionResponse,
     RegisterInputFileRequest, RegisterInputFileResponse, 
RegisterOutputFileRequest,
     RegisterOutputFileResponse,
 };
@@ -151,17 +153,103 @@ impl TeaclaveManagement for TeaclaveManagementService {
         };
         Ok(response)
     }
+
+    fn register_function(
+        &self,
+        request: Request<RegisterFunctionRequest>,
+    ) -> TeaclaveServiceResponseResult<RegisterFunctionResponse> {
+        let user_id = request
+            .metadata
+            .get("id")
+            .ok_or_else(|| TeaclaveManagementError::InvalidRequest)?
+            .to_string();
+
+        let request = request.message;
+        let function = Function::new_from_register_request(request, user_id);
+        let key = function.get_key_vec();
+        let value = function
+            .to_vec()
+            .map_err(|_| TeaclaveManagementError::DataError)?;
+
+        self.write_to_storage(&key, &value)
+            .map_err(|_| TeaclaveManagementError::StorageError)?;
+        let response = RegisterFunctionResponse {
+            function_id: function.function_id,
+        };
+        Ok(response)
+    }
+
+    fn get_function(
+        &self,
+        request: Request<GetFunctionRequest>,
+    ) -> TeaclaveServiceResponseResult<GetFunctionResponse> {
+        let user_id = request
+            .metadata
+            .get("id")
+            .ok_or_else(|| TeaclaveManagementError::InvalidRequest)?
+            .to_string();
+        let function_id = request.message.function_id;
+        if !Function::is_function_id(&function_id) {
+            return Err(TeaclaveManagementError::PermissionDenied.into());
+        }
+        let key: &[u8] = function_id.as_bytes();
+        let value = self
+            .read_from_storage(key)
+            .map_err(|_| TeaclaveManagementError::StorageError)?;
+        let function =
+            Function::from_slice(&value).map_err(|_| 
TeaclaveManagementError::DataError)?;
+        if !(function.is_public || function.owner == user_id) {
+            return Err(TeaclaveManagementError::PermissionDenied.into());
+        }
+        let response = GetFunctionResponse {
+            name: function.name,
+            description: function.description,
+            owner: function.owner,
+            payload: function.payload,
+            is_public: function.is_public,
+            arg_list: function.arg_list,
+            input_list: function.input_list,
+            output_list: function.output_list,
+        };
+        Ok(response)
+    }
 }
 
 impl TeaclaveManagementService {
     #[cfg(test_mode)]
     fn add_mock_data(&self) -> Result<()> {
+        use teaclave_proto::teaclave_frontend_service::{FunctionInput, 
FunctionOutput};
         let mut fusion_data =
             FusionData::new(vec!["mock_user_a".to_string(), 
"mock_user_b".to_string()])?;
         fusion_data.data_id = "fusion-data-mock-data".to_string();
         let key = fusion_data.get_key_vec();
         let value = fusion_data.to_vec()?;
         self.write_to_storage(&key, &value)?;
+
+        let function_input = FunctionInput {
+            name: "input".to_string(),
+            description: "input_desc".to_string(),
+        };
+        let function_output = FunctionOutput {
+            name: "output".to_string(),
+            description: "output_desc".to_string(),
+        };
+
+        let native_function = Function {
+            function_id: "native-mock-native-func".to_string(),
+            name: "mock-native-func".to_string(),
+            description: "mock-desc".to_string(),
+            payload: b"mock-payload".to_vec(),
+            is_public: true,
+            arg_list: vec!["arg".to_string()],
+            input_list: vec![function_input],
+            output_list: vec![function_output],
+            owner: "teaclave".to_string(),
+            is_native: true,
+        };
+        let key = native_function.get_key_vec();
+        let value = native_function.to_vec()?;
+        self.write_to_storage(&key, &value)?;
         Ok(())
     }
 
@@ -202,6 +290,7 @@ impl TeaclaveManagementService {
 #[cfg(feature = "enclave_unit_test")]
 pub mod tests {
     use super::*;
+    use teaclave_proto::teaclave_frontend_service::{FunctionInput, 
FunctionOutput};
     use teaclave_types::{TeaclaveFileCryptoInfo, TeaclaveFileRootKey128};
     use url::Url;
 
@@ -249,4 +338,33 @@ pub mod tests {
         let deserialized_data = FusionData::from_slice(&value).unwrap();
         info!("data: {:?}", deserialized_data);
     }
+
+    pub fn handle_function() {
+        let function_input = FunctionInput {
+            name: "input".to_string(),
+            description: "input_desc".to_string(),
+        };
+        let function_output = FunctionOutput {
+            name: "output".to_string(),
+            description: "output_desc".to_string(),
+        };
+        let register_request = RegisterFunctionRequest {
+            name: "mock_function".to_string(),
+            description: "mock function".to_string(),
+            payload: b"python script".to_vec(),
+            is_public: true,
+            arg_list: vec!["arg".to_string()],
+            input_list: vec![function_input],
+            output_list: vec![function_output],
+        };
+        let function =
+            Function::new_from_register_request(register_request, 
"mock_user".to_string());
+        let key = function.get_key_vec();
+        let key_str = std::str::from_utf8(&key).unwrap();
+        info!("key: {}", key_str);
+        assert!(Function::is_function_id(key_str));
+        let value = function.to_vec().unwrap();
+        let deserialized_data = Function::from_slice(&value).unwrap();
+        info!("data: {:?}", deserialized_data);
+    }
 }
diff --git a/services/proto/src/proto/teaclave_frontend_service.proto 
b/services/proto/src/proto/teaclave_frontend_service.proto
index a5d85da..c41e585 100644
--- a/services/proto/src/proto/teaclave_frontend_service.proto
+++ b/services/proto/src/proto/teaclave_frontend_service.proto
@@ -40,6 +40,45 @@ message GetFusionDataResponse {
   repeated string data_owner_id_list = 2;
 }
 
+message FunctionInput {
+  string name = 1;
+  string description = 2;
+}
+
+message FunctionOutput {
+  string name = 1;
+  string description = 2;
+}
+
+message RegisterFunctionRequest {
+  string name = 1;
+  string description = 2;
+  bytes payload = 3;
+  bool is_public = 4;
+  repeated string arg_list = 5;
+  repeated FunctionInput input_list = 6;
+  repeated FunctionOutput output_list = 7;
+}
+
+message RegisterFunctionResponse {
+  string function_id = 1;
+}
+
+message GetFunctionRequest {
+  string function_id = 1;
+}
+
+message GetFunctionResponse {
+  string name = 1;
+  string description = 2;
+  string owner = 3;
+  bytes payload = 4;
+  bool is_public = 5;
+  repeated string arg_list = 6;
+  repeated FunctionInput input_list = 7;
+  repeated FunctionOutput output_list = 8;
+}
+
 service TeaclaveFrontend {
   rpc RegisterInputFile (RegisterInputFileRequest) returns 
(RegisterInputFileResponse);
   rpc RegisterOutputFile (RegisterOutputFileRequest) returns 
(RegisterOutputFileResponse);
diff --git a/services/proto/src/proto/teaclave_management_service.proto 
b/services/proto/src/proto/teaclave_management_service.proto
index d7a9c85..c0b154f 100644
--- a/services/proto/src/proto/teaclave_management_service.proto
+++ b/services/proto/src/proto/teaclave_management_service.proto
@@ -9,4 +9,6 @@ service TeaclaveManagement {
   rpc RegisterOutputFile 
(teaclave_frontend_service_proto.RegisterOutputFileRequest) returns 
(teaclave_frontend_service_proto.RegisterOutputFileResponse);
   rpc GetOutputFile (teaclave_frontend_service_proto.GetOutputFileRequest) 
returns (teaclave_frontend_service_proto.GetOutputFileResponse);
   rpc GetFusionData (teaclave_frontend_service_proto.GetFusionDataRequest) 
returns (teaclave_frontend_service_proto.GetFusionDataResponse);
+  rpc RegisterFunction 
(teaclave_frontend_service_proto.RegisterFunctionRequest) returns 
(teaclave_frontend_service_proto.RegisterFunctionResponse);
+  rpc GetFunction (teaclave_frontend_service_proto.GetFunctionRequest) returns 
(teaclave_frontend_service_proto.GetFunctionResponse);
 }
\ No newline at end of file
diff --git a/services/proto/src/teaclave_frontend_service.rs 
b/services/proto/src/teaclave_frontend_service.rs
index 801d694..e7f9865 100644
--- a/services/proto/src/teaclave_frontend_service.rs
+++ b/services/proto/src/teaclave_frontend_service.rs
@@ -2,6 +2,7 @@ use crate::teaclave_frontend_service_proto as proto;
 use anyhow::anyhow;
 use anyhow::{Error, Result};
 use core::convert::TryInto;
+use serde::{Deserialize, Serialize};
 use teaclave_types::TeaclaveFileCryptoInfo;
 use url::Url;
 
@@ -54,6 +55,51 @@ pub struct GetFusionDataResponse {
     pub data_owner_id_list: std::vec::Vec<std::string::String>,
 }
 
+#[derive(Debug, Deserialize, Serialize)]
+pub struct FunctionInput {
+    pub name: std::string::String,
+    pub description: std::string::String,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct FunctionOutput {
+    pub name: std::string::String,
+    pub description: std::string::String,
+}
+
+#[derive(Debug)]
+pub struct RegisterFunctionRequest {
+    pub name: std::string::String,
+    pub description: std::string::String,
+    pub payload: std::vec::Vec<u8>,
+    pub is_public: bool,
+    pub arg_list: std::vec::Vec<std::string::String>,
+    pub input_list: std::vec::Vec<FunctionInput>,
+    pub output_list: std::vec::Vec<FunctionOutput>,
+}
+
+#[derive(Debug)]
+pub struct RegisterFunctionResponse {
+    pub function_id: std::string::String,
+}
+
+#[derive(Debug)]
+pub struct GetFunctionRequest {
+    pub function_id: std::string::String,
+}
+
+#[derive(Debug)]
+pub struct GetFunctionResponse {
+    pub name: std::string::String,
+    pub description: std::string::String,
+    pub owner: std::string::String,
+    pub payload: std::vec::Vec<u8>,
+    pub is_public: bool,
+    pub arg_list: std::vec::Vec<std::string::String>,
+    pub input_list: std::vec::Vec<FunctionInput>,
+    pub output_list: std::vec::Vec<FunctionOutput>,
+}
+
 impl std::convert::TryFrom<proto::RegisterInputFileRequest> for 
RegisterInputFileRequest {
     type Error = Error;
 
@@ -217,3 +263,196 @@ impl From<GetFusionDataResponse> for 
proto::GetFusionDataResponse {
         }
     }
 }
+
+impl std::convert::TryFrom<proto::FunctionInput> for FunctionInput {
+    type Error = Error;
+
+    fn try_from(proto: proto::FunctionInput) -> Result<Self> {
+        let ret = Self {
+            name: proto.name,
+            description: proto.description,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<FunctionInput> for proto::FunctionInput {
+    fn from(input: FunctionInput) -> Self {
+        Self {
+            name: input.name,
+            description: input.description,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::FunctionOutput> for FunctionOutput {
+    type Error = Error;
+
+    fn try_from(proto: proto::FunctionOutput) -> Result<Self> {
+        let ret = Self {
+            name: proto.name,
+            description: proto.description,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<FunctionOutput> for proto::FunctionOutput {
+    fn from(output: FunctionOutput) -> Self {
+        Self {
+            name: output.name,
+            description: output.description,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::RegisterFunctionRequest> for 
RegisterFunctionRequest {
+    type Error = Error;
+
+    fn try_from(proto: proto::RegisterFunctionRequest) -> Result<Self> {
+        let input_list: Result<std::vec::Vec<FunctionInput>> = proto
+            .input_list
+            .into_iter()
+            .map(FunctionInput::try_from)
+            .collect();
+        let output_list: Result<std::vec::Vec<FunctionOutput>> = proto
+            .output_list
+            .into_iter()
+            .map(FunctionOutput::try_from)
+            .collect();
+
+        let ret = Self {
+            name: proto.name,
+            description: proto.description,
+            payload: proto.payload,
+            is_public: proto.is_public,
+            arg_list: proto.arg_list,
+            input_list: input_list?,
+            output_list: output_list?,
+        };
+        Ok(ret)
+    }
+}
+
+impl From<RegisterFunctionRequest> for proto::RegisterFunctionRequest {
+    fn from(request: RegisterFunctionRequest) -> Self {
+        let input_list: std::vec::Vec<proto::FunctionInput> = request
+            .input_list
+            .into_iter()
+            .map(proto::FunctionInput::from)
+            .collect();
+        let output_list: std::vec::Vec<proto::FunctionOutput> = request
+            .output_list
+            .into_iter()
+            .map(proto::FunctionOutput::from)
+            .collect();
+
+        Self {
+            name: request.name,
+            description: request.description,
+            payload: request.payload,
+            is_public: request.is_public,
+            arg_list: request.arg_list,
+            input_list,
+            output_list,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::RegisterFunctionResponse> for 
RegisterFunctionResponse {
+    type Error = Error;
+
+    fn try_from(proto: proto::RegisterFunctionResponse) -> Result<Self> {
+        let ret = Self {
+            function_id: proto.function_id,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<RegisterFunctionResponse> for proto::RegisterFunctionResponse {
+    fn from(response: RegisterFunctionResponse) -> Self {
+        Self {
+            function_id: response.function_id,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::GetFunctionRequest> for GetFunctionRequest {
+    type Error = Error;
+
+    fn try_from(proto: proto::GetFunctionRequest) -> Result<Self> {
+        let ret = Self {
+            function_id: proto.function_id,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<GetFunctionRequest> for proto::GetFunctionRequest {
+    fn from(request: GetFunctionRequest) -> Self {
+        Self {
+            function_id: request.function_id,
+        }
+    }
+}
+
+impl std::convert::TryFrom<proto::GetFunctionResponse> for GetFunctionResponse 
{
+    type Error = Error;
+
+    fn try_from(proto: proto::GetFunctionResponse) -> Result<Self> {
+        let input_list: Result<std::vec::Vec<FunctionInput>> = proto
+            .input_list
+            .into_iter()
+            .map(FunctionInput::try_from)
+            .collect();
+        let output_list: Result<std::vec::Vec<FunctionOutput>> = proto
+            .output_list
+            .into_iter()
+            .map(FunctionOutput::try_from)
+            .collect();
+
+        let ret = Self {
+            name: proto.name,
+            description: proto.description,
+            owner: proto.owner,
+            payload: proto.payload,
+            is_public: proto.is_public,
+            arg_list: proto.arg_list,
+            input_list: input_list?,
+            output_list: output_list?,
+        };
+
+        Ok(ret)
+    }
+}
+
+impl From<GetFunctionResponse> for proto::GetFunctionResponse {
+    fn from(response: GetFunctionResponse) -> Self {
+        let input_list: std::vec::Vec<proto::FunctionInput> = response
+            .input_list
+            .into_iter()
+            .map(proto::FunctionInput::from)
+            .collect();
+        let output_list: std::vec::Vec<proto::FunctionOutput> = response
+            .output_list
+            .into_iter()
+            .map(proto::FunctionOutput::from)
+            .collect();
+
+        Self {
+            name: response.name,
+            description: response.description,
+            owner: response.owner,
+            payload: response.payload,
+            is_public: response.is_public,
+            arg_list: response.arg_list,
+            input_list,
+            output_list,
+        }
+    }
+}
diff --git a/tests/functional_tests/enclave/src/teaclave_management_service.rs 
b/tests/functional_tests/enclave/src/teaclave_management_service.rs
index b83e022..21e25d3 100644
--- a/tests/functional_tests/enclave/src/teaclave_management_service.rs
+++ b/tests/functional_tests/enclave/src/teaclave_management_service.rs
@@ -16,8 +16,10 @@ pub fn run_tests() -> bool {
     run_tests!(
         test_register_input_file,
         test_register_output_file,
+        test_register_function,
         test_get_output_file,
-        test_get_fusion_data
+        test_get_fusion_data,
+        test_get_function,
     )
 }
 
@@ -132,3 +134,69 @@ fn test_get_fusion_data() {
     let response = client.get_fusion_data(request);
     assert!(response.is_err());
 }
+
+fn test_register_function() {
+    let function_input = FunctionInput {
+        name: "input".to_string(),
+        description: "input_desc".to_string(),
+    };
+    let function_output = FunctionOutput {
+        name: "output".to_string(),
+        description: "output_desc".to_string(),
+    };
+    let request = RegisterFunctionRequest {
+        name: "mock_function".to_string(),
+        description: "mock function".to_string(),
+        payload: b"python script".to_vec(),
+        is_public: true,
+        arg_list: vec!["arg".to_string()],
+        input_list: vec![function_input],
+        output_list: vec![function_output],
+    };
+
+    let mut client = get_client("mock_user");
+    let response = client.register_function(request);
+
+    assert!(response.is_ok());
+}
+
+fn test_get_function() {
+    let function_input = FunctionInput {
+        name: "input".to_string(),
+        description: "input_desc".to_string(),
+    };
+    let function_output = FunctionOutput {
+        name: "output".to_string(),
+        description: "output_desc".to_string(),
+    };
+    let request = RegisterFunctionRequest {
+        name: "mock_function".to_string(),
+        description: "mock function".to_string(),
+        payload: b"python script".to_vec(),
+        is_public: false,
+        arg_list: vec!["arg".to_string()],
+        input_list: vec![function_input],
+        output_list: vec![function_output],
+    };
+
+    let mut client = get_client("mock_user");
+    let response = client.register_function(request);
+    let function_id = response.unwrap().function_id;
+
+    let request = GetFunctionRequest {
+        function_id: function_id.clone(),
+    };
+    let response = client.get_function(request);
+    assert!(response.is_ok());
+    info!("{:?}", response.unwrap());
+    let mut client = get_client("mock_unauthorized_user");
+    let request = GetFunctionRequest { function_id };
+    let response = client.get_function(request);
+    assert!(response.is_err());
+    let request = GetFunctionRequest {
+        function_id: "native-mock-native-func".to_string(),
+    };
+    let response = client.get_function(request);
+    assert!(response.is_ok());
+    info!("{:?}", response.unwrap());
+}


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

Reply via email to