ivila commented on issue #153:
URL:
https://github.com/apache/incubator-teaclave-trustzone-sdk/issues/153#issuecomment-2522064909
@DemesneGH
## For the questions:
* Should developers care about the shared buffer?
* Yes, not just my cases, in many of the use cases the response size is
bigger than request size, and actually they are not really care about the
shared buffer, but just want to make sure they can read all the data from REE.
* The plugin_invoke() takes these two parameters data_ptr and data_len. they
are description of the shared_buffer, or the data_len is size of input_buffer
(as the comment for tee_invoke_supp_plugin)?
* Yes, the `len` is the size of share buffer, **But we don't need to
consider the size of the input data in teaclave**, things is complicated here,
I will explain later.
* **Developers should manually set the input_data size this their own**
* Would zero-copy of Vec in Rust solve the problem?
* Yes, but as what I said, my real problem is not we can't copy, is that
I need to set the size of the buffer my own but rather than use a immutable
input slice with same size as the output Vector(**in my case it wastes 100M of
memory by using invoke**).
## How people use plugin with the share buffer
### 1. Read data from REE
Some people use it to read file content, or make a request to remote and
return its result, in scenarios like these, people prepare a large buffer and
use different ways to know the input data size from TA:
1. by const size: every command is combine with a const struct, which is the
same layout in both TEE and REE (just like repr(C)), and they read the buffer
directly, when upgrading, they use a new struct with new command pair(new
command_id or same command_id with different sub_cmd_id).
2. by sub_cmd: some people use protobuf or json or flatbuffer for request
parameter, and use sub cmd for lens.
3. by first 8 bytes: they write the length (which is u64) to the prefix of
buffer.
### 2. Post something to REE
Some people use it to write data into REE, for example, some secret keys,
and the share buffer is used to pass the error description(if result is not
TEE_SUCCESS), in scenarios like these, current implementation is enough.
### 3. Trigger something in REE
Some people use it to trigger something in REE, for example, TEE listening
to a trusty remote, and when the maintainers find something wrong with it (a
hacker hack in and take control of the system), they trigger a security alarm,
or shutdown the system immediately, etc. In scenarios like these, they don't
care about the share buffer, sometimes they don't use the sub_command_id either.
## Other considerations
* Actually plugin system have existed for a long time, there are some
plugins developed by C already, so should we keeps compatibility with them?
* Yes? => So people don't have to rewrite them.
* No? => So we can keep a clean architecture.
## My New Design
I have a new design, which is Command struct with chainable api, just like
[reqwest.Request](https://docs.rs/reqwest/latest/reqwest/struct.Request.html)
and [ureq.Request](https://docs.rs/ureq/latest/ureq/struct.Request.html).
First, we propose a Command struct, reference to the Plugin and keeps
arguments of tee_invoke_supp_plugin inside:
```rust
pub struct Plugin {
uuid: Uuid,
}
pub struct Command<'a> {
plugin: &'a Plugin,
cmd_id: u32,
sub_cmd_id: u32,
buffer: Vec<u8>,
}
```
Then, we provide chainable api for developers, just like the following:
```rust
impl<'a> Command<'a> {
// use this to set sub_cmd if needed
pub fn sub_cmd(mut self, sub_cmd_id: u32) -> Self {
self.sub_cmd_id = sub_cmd_id;
self
}
// use this to write request body if needed
pub fn write_body(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
}
// same with write_body, but chainable
pub fn chain_write_body(mut self, data: &[u8]) -> Self {
self.write_body(data);
self
}
// invoke the command, and get result from it
pub fn call(self) -> Result<Vec<u8>> {
let mut outlen: usize = 0;
let mut buffer = self.buffer;
buffer.resize(buffer.capacity(), 0); // resize to capacity first
match unsafe {
raw::tee_invoke_supp_plugin(
self.plugin.uuid.as_raw_ptr(),
self.cmd_id,
self.sub_cmd_id,
buffer.as_mut_slice().as_mut_ptr(),
buffer.len(),
&mut outlen as *mut usize,
)
} {
raw::TEE_SUCCESS => {
assert!(outlen <= buffer.len());
buffer.resize(outlen, 0);
Ok(buffer)
}
code => Err(Error::from_raw_error(code)),
}
}
}
```
Finally, we add extra api for Plugin, and rewrite the invoke method with it:
```rust
impl<'a> Command<'a> {
fn new(plugin: &'a Plugin, cmd_id: u32) -> Self {
const DEFAULT_CAPACITY: usize = 1024;
Self::new_with_capacity(&plugin, cmd_id, DEFAULT_CAPACITY)
}
fn new_with_capacity(plugin: &'a Plugin, cmd_id: u32, capacity: usize)
-> Self {
Self {
plugin,
cmd_id,
sub_cmd_id: 0,
buffer: Vec::with_capacity(capacity),
}
}
}
impl Plugin {
pub fn invoke(&self, command_id: u32, subcommand_id: u32, data: &[u8])
-> Result<Vec<u8>> {
self.new_command_with_capacity(command_id, data.len())
.sub_cmd(subcommand_id)
.chain_write_body(data)
.call()
}
// for people don't care about the response
pub fn new_command<'a>(&'a self, command_id: u32) -> Command<'a> {
Command::new(self, command_id)
}
// for people who wants to set the buffer size for response manually
pub fn new_command_with_capacity<'a>(
&'a self,
command_id: u32,
capacity: usize,
) -> Command<'a> {
Command::new_with_capacity(self, command_id, capacity)
}
}
```
By this design, developers of different scenarios could:
1. developers upgrade from old teaclave SDK: nothing changes.
2. developers who wants to fetch something from REE(which have bigger
response size):
``` rust
const RESPONSE_LEN: usize = 100;
let result_vec = plugin.new_command_with_capacity(command_id, RESPONSE_LEN)
.sub_cmd(subcommand_id)
.write_body(request)
.call()?;
```
3. developers who wants to put something to REE: just use the invoke method.
4. developers who just want to trigger something:
```rust
let _ = plugin.new_command(command_id)
.sub_cmd(subcommand_id)
.write_body(request)
.call()?;
```
5. they can even imply a writer of it:
```rust
// impl a writer for Command
struct Wrapper<'a, 'b>(&'b mut Command<'a>);
impl<'a, 'b> std::io::Write for Wrapper<'a, 'b> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.write_body(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
// serialize data into command directly
let test_data = serde_json::json!({
"code": 100,
"message": "error"
});
const OUTPUT_LEN: usize = 100;
let (sub_cmd, random) = generate_test_pair(OUTPUT_LEN);
let mut cmd = plugin
.new_command_with_capacity(1, OUTPUT_LEN)
.sub_cmd(sub_cmd);
serde_json::to_writer(Wrapper(&mut cmd), &test_data).unwrap();
let result_vec = cmd.call().unwrap();
```
How about this?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]