This is an automated email from the ASF dual-hosted git repository.
ivila pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/teaclave-trustzone-sdk.git
The following commit(s) were added to refs/heads/main by this push:
new 820c990 optee-teec: refactor plugin support
820c990 is described below
commit 820c9906cfa4a037d980a7a828aa210848665a7e
Author: ivila <[email protected]>
AuthorDate: Thu Apr 30 11:21:46 2026 +0800
optee-teec: refactor plugin support
* Introduce optee-teec-build crate to generate plugin bindings for plugin
developers, replacing `plugin-static.rs` file.
* Refactor optee-teec::PluginParameters and add additional methods.
* Update optee-teec-sys::PluginMethod to latest version.
---
crates/Cargo.toml | 5 +
.../Cargo.toml | 9 +-
.../optee-teec-build/src/lib.rs | 12 +-
crates/optee-teec-build/src/plugin.rs | 120 +++++++++++++
crates/optee-teec-macros/Cargo.toml | 4 +
crates/optee-teec-macros/src/lib.rs | 183 +++++++++-----------
crates/optee-teec-sys/src/lib.rs | 2 +
.../optee-teec-sys/src/plugin_method.rs | 24 ++-
crates/optee-teec-systest/build.rs | 4 +
crates/optee-teec/Cargo.toml | 7 +-
crates/optee-teec/src/extension.rs | 186 +++++++++++++++------
crates/optee-teec/src/lib.rs | 8 +-
crates/optee-utee-build/Cargo.toml | 2 +-
examples/supp_plugin-rs/plugin/Cargo.toml | 7 +-
examples/supp_plugin-rs/plugin/build.rs | 31 +---
examples/supp_plugin-rs/plugin/src/lib.rs | 14 +-
16 files changed, 407 insertions(+), 211 deletions(-)
diff --git a/crates/Cargo.toml b/crates/Cargo.toml
index c062cbd..5995452 100644
--- a/crates/Cargo.toml
+++ b/crates/Cargo.toml
@@ -19,6 +19,7 @@
resolver = "3"
members = [
"optee-teec",
+ "optee-teec-build",
"optee-teec-macros",
"optee-teec-sys",
"optee-teec-systest",
@@ -40,6 +41,8 @@ description = "TEE internal core API."
edition = "2024"
[workspace.dependencies]
+optee-teec = { version = "0.8.0", path = "optee-teec" }
+optee-teec-build = { version = "0.8.0", path = "optee-teec-build" }
optee-teec-macros = { version = "0.8.0", path = "optee-teec-macros" }
optee-teec-sys = { version = "0.8.0", path = "optee-teec-sys" }
optee-utee = { version = "0.8.0", path = "optee-utee" }
@@ -52,8 +55,10 @@ uuid = { version = "1.23", default-features = false }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
quote = "1.0"
syn = { version = "2.0", features = ["full"] }
+proc-macro2 = "1.0.92"
libc = "0.2"
rand = "0.10"
once_cell = "1.20.2"
serde = { version = "1.0.228" }
serde_json = { version = "1.0.149" }
+log = "0.4.29"
diff --git a/crates/optee-utee-build/Cargo.toml
b/crates/optee-teec-build/Cargo.toml
similarity index 90%
copy from crates/optee-utee-build/Cargo.toml
copy to crates/optee-teec-build/Cargo.toml
index 8f0050c..e61c76b 100644
--- a/crates/optee-utee-build/Cargo.toml
+++ b/crates/optee-teec-build/Cargo.toml
@@ -16,8 +16,8 @@
# under the License.
[package]
-name = "optee-utee-build"
-description = "Build tool for TA"
+name = "optee-teec-build"
+description = "build tool for teec"
version.workspace = true
authors.workspace = true
license.workspace = true
@@ -26,7 +26,6 @@ edition.workspace = true
[dependencies]
quote.workspace = true
-proc-macro2 = "1.0.92"
-syn.workspace = true
-prettyplease = "0.2.25"
+proc-macro2.workspace = true
uuid.workspace = true
+syn.workspace = true
diff --git a/examples/supp_plugin-rs/plugin/plugin_static.rs
b/crates/optee-teec-build/src/lib.rs
similarity index 74%
copy from examples/supp_plugin-rs/plugin/plugin_static.rs
copy to crates/optee-teec-build/src/lib.rs
index 2378e4f..453636d 100644
--- a/examples/supp_plugin-rs/plugin/plugin_static.rs
+++ b/crates/optee-teec-build/src/lib.rs
@@ -15,13 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-#[no_mangle]
-pub static mut plugin_method: optee_teec::PluginMethod =
optee_teec::PluginMethod {
- name: plugin_name.as_ptr(),
- uuid: PLUGIN_UUID_STRUCT,
- init: _plugin_init,
- invoke: _plugin_invoke,
-};
+mod plugin;
+pub use uuid;
-#[no_mangle]
-pub static plugin_name: &[u8] = b"syslog\0";
+pub use plugin::{DEFAULT_INIT_FN_NAME, DEFAULT_INVOKE_FN_NAME, PluginConfig};
diff --git a/crates/optee-teec-build/src/plugin.rs
b/crates/optee-teec-build/src/plugin.rs
new file mode 100644
index 0000000..1d0c986
--- /dev/null
+++ b/crates/optee-teec-build/src/plugin.rs
@@ -0,0 +1,120 @@
+// 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::path::PathBuf;
+pub use uuid;
+
+pub const DEFAULT_INIT_FN_NAME: &str = "__plugin_bindgen_init";
+pub const DEFAULT_INVOKE_FN_NAME: &str = "__plugin_bindgen_invoke";
+
+pub struct PluginConfig {
+ name: String,
+ uuid: uuid::Uuid,
+ init_fn_name: String,
+ invoke_fn_name: String,
+ dest: Option<PathBuf>,
+}
+
+impl PluginConfig {
+ pub fn new(uuid: uuid::Uuid) -> Self {
+ Self {
+ name: env!("CARGO_PKG_NAME").to_string(),
+ uuid,
+ init_fn_name: DEFAULT_INIT_FN_NAME.to_owned(),
+ invoke_fn_name: DEFAULT_INVOKE_FN_NAME.to_owned(),
+ dest: None,
+ }
+ }
+ pub fn with_name(mut self, name: &str) -> Self {
+ self.name = name.to_string();
+ self
+ }
+ pub fn with_init_fn_name(mut self, fn_name: &str) -> Self {
+ self.init_fn_name = fn_name.to_string();
+ self
+ }
+ pub fn with_invoke_fn_name(mut self, fn_name: &str) -> Self {
+ self.invoke_fn_name = fn_name.to_string();
+ self
+ }
+ pub fn build(&self) -> std::io::Result<()> {
+ let codes = generate_binding(
+ &self.name,
+ &self.uuid,
+ &self.init_fn_name,
+ &self.invoke_fn_name,
+ )
+ .to_string();
+ let out_path = self.get_out_path();
+ if let Ok(v) = std::fs::read(&out_path)
+ && v.eq(codes.as_bytes())
+ {
+ return Ok(());
+ }
+
+ if let Some(parent_dir) = out_path.parent() {
+ std::fs::create_dir_all(parent_dir)?;
+ }
+ std::fs::write(out_path, codes.as_bytes())
+ }
+}
+
+impl PluginConfig {
+ fn get_out_path(&self) -> PathBuf {
+ match self.dest.as_ref() {
+ Some(v) => v.clone(),
+ None => {
+ let out_dir = PathBuf::from(
+ std::env::var("OUT_DIR").expect("Infallible when using in
build.rs"),
+ );
+ out_dir.join("plugin_static.rs")
+ }
+ }
+ }
+}
+
+fn generate_binding(
+ name: &str,
+ uuid: &uuid::Uuid,
+ init_fn_name: &str,
+ invoke_fn_name: &str,
+) -> proc_macro2::TokenStream {
+ let (uuid_f1, uuid_f2, uuid_f3, uuid_f4) = uuid.as_fields();
+ let name_bytes_with_null = format!("{}\0", name);
+ let init_fn_name = quote::format_ident!("{}", init_fn_name);
+ let invoke_fn_name = quote::format_ident!("{}", invoke_fn_name);
+ quote::quote! {
+ const _: () = {
+ use optee_teec::raw::{PluginMethod, TEEC_UUID};
+
+ static PLUGIN_NAME: &str = #name_bytes_with_null;
+
+ #[unsafe(no_mangle)]
+ pub static mut plugin_method: PluginMethod = PluginMethod {
+ name: PLUGIN_NAME.as_ptr() as *const _,
+ uuid: TEEC_UUID {
+ timeLow: #uuid_f1,
+ timeMid: #uuid_f2,
+ timeHiAndVersion: #uuid_f3,
+ clockSeqAndNode: [#(#uuid_f4),*],
+ },
+ init: #init_fn_name,
+ invoke: #invoke_fn_name,
+ };
+ };
+ }
+}
diff --git a/crates/optee-teec-macros/Cargo.toml
b/crates/optee-teec-macros/Cargo.toml
index 570ff1e..91a42d9 100644
--- a/crates/optee-teec-macros/Cargo.toml
+++ b/crates/optee-teec-macros/Cargo.toml
@@ -30,3 +30,7 @@ proc-macro = true
[dependencies]
quote.workspace = true
syn.workspace = true
+optee-teec-build.workspace = true
+
+[dev-dependencies]
+optee-teec.workspace = true
diff --git a/crates/optee-teec-macros/src/lib.rs
b/crates/optee-teec-macros/src/lib.rs
index dabd46b..02a62ae 100644
--- a/crates/optee-teec-macros/src/lib.rs
+++ b/crates/optee-teec-macros/src/lib.rs
@@ -15,19 +15,22 @@
// specific language governing permissions and limitations
// under the License.
-#![recursion_limit = "128"]
-
extern crate proc_macro;
+use optee_teec_build::{DEFAULT_INIT_FN_NAME, DEFAULT_INVOKE_FN_NAME};
use proc_macro::TokenStream;
-use quote::{ToTokens, quote};
-use syn::parse_macro_input;
+use quote::quote;
use syn::spanned::Spanned;
+use syn::{FnArg, parse_macro_input};
-/// Attribute to declare the init function of a plugin
+/// Attribute to derive the injected init function from an existing function
/// ``` no_run
+/// use optee_teec_macros::plugin_init;
+///
/// #[plugin_init]
-/// fn plugin_init() -> Result<()> {}
+/// fn plugin_init() -> optee_teec::Result<()> {
+/// Ok(())
+/// }
/// ```
#[proc_macro_attribute]
pub fn plugin_init(_args: TokenStream, input: TokenStream) -> TokenStream {
@@ -55,12 +58,15 @@ pub fn plugin_init(_args: TokenStream, input: TokenStream)
-> TokenStream {
.into();
}
+ let bindgen_fn_name = quote::format_ident!("{}", DEFAULT_INIT_FN_NAME);
+ let origin_fn_name = &f_sig.ident;
quote!(
- pub fn _plugin_init() -> optee_teec::raw::TEEC_Result {
- fn inner() -> optee_teec::Result<()> {
- #f_block
- }
- match inner() {
+ #f_vis #f_sig {
+ #f_block
+ }
+ const _: fn() -> optee_teec::Result<()> = #origin_fn_name;
+ unsafe extern "C" fn #bindgen_fn_name() ->
optee_teec::raw::TEEC_Result {
+ match #origin_fn_name() {
Ok(()) => optee_teec::raw::TEEC_SUCCESS,
Err(err) => err.raw_code(),
}
@@ -71,22 +77,51 @@ pub fn plugin_init(_args: TokenStream, input: TokenStream)
-> TokenStream {
// check if return_type of the function is `optee_teec::Result<()>`
fn check_return_type(item_fn: &syn::ItemFn) -> bool {
+ const EXPECTED: [&str; 2] = ["optee_teec", "Result"];
if let syn::ReturnType::Type(_, return_type) =
item_fn.sig.output.to_owned()
&& let syn::Type::Path(path) = return_type.as_ref()
{
- let expected_type = quote! { optee_teec::Result<()> };
- let actual_type = path.path.to_token_stream();
- if expected_type.to_string() == actual_type.to_string() {
- return true;
- }
+ return check_path_might_match(&path.path, &EXPECTED);
}
false
}
-/// Attribute to declare the invoke function of a plugin
+// check path match the expected values
+// it might still fail if developers re-export crate as other name.
+fn check_path_might_match(path: &syn::Path, exp: &[&str]) -> bool {
+ path.segments.len() <= exp.len()
+ && path
+ .segments
+ .iter()
+ .zip(&exp[exp.len() - path.segments.len()..])
+ .all(|(seg, exp)| seg.ident == *exp)
+}
+
+fn check_invoke_fn_params(item_fn: &syn::ItemFn) -> bool {
+ if item_fn.sig.inputs.len() != 1 {
+ return false;
+ }
+
+ let arg = item_fn.sig.inputs.first().expect("Infallible");
+ if let FnArg::Typed(typ) = arg
+ && let syn::Type::Reference(typ_ref) = typ.ty.as_ref()
+ && typ_ref.mutability.is_some()
+ && let syn::Type::Path(inner_type) = typ_ref.elem.as_ref()
+ {
+ const EXPECTED: [&str; 2] = ["optee_teec", "PluginParameters"];
+ return check_path_might_match(&inner_type.path, &EXPECTED);
+ }
+ false
+}
+
+/// Attribute to derive the injected invoke function from an existing function
/// ``` no_run
+/// use optee_teec_macros::plugin_invoke;
+///
/// #[plugin_invoke]
-/// fn plugin_invoke(params: &mut PluginParameters) {}
+/// fn plugin_invoke(params: &mut optee_teec::PluginParameters) ->
optee_teec::Result<()> {
+/// Ok(())
+/// }
/// ```
#[proc_macro_attribute]
pub fn plugin_invoke(_args: TokenStream, input: TokenStream) -> TokenStream {
@@ -103,7 +138,8 @@ pub fn plugin_invoke(_args: TokenStream, input:
TokenStream) -> TokenStream {
&& f_inputs.len() == 1
&& f_sig.generics.where_clause.is_none()
&& f_sig.variadic.is_none()
- && check_return_type(&f);
+ && check_return_type(&f)
+ && check_invoke_fn_params(&f);
if !valid_signature {
return syn::parse::Error::new(
@@ -117,97 +153,38 @@ pub fn plugin_invoke(_args: TokenStream, input:
TokenStream) -> TokenStream {
.into();
}
- let params = f_inputs
- .first()
- .expect("we have already verified its len")
- .into_token_stream();
+ let bindgen_fn_name = quote::format_ident!("{}", DEFAULT_INVOKE_FN_NAME);
+ let origin_fn_name = &f_sig.ident;
quote!(
- /// # Safety
- ///
- /// The `_plugin_invoke` function is the `extern "C"` entrypoint
called by OP-TEE OS.
- /// This SDK allows developers to implement the inner logic for a
Normal World plugin in Rust.
- /// More about plugins:
- ///
https://optee.readthedocs.io/en/latest/architecture/globalplatform_api.html#loadable-plugins-framework
- ///
- /// According to Clippy checks, any FFI function taking raw pointers
as parameters
- /// must be marked `unsafe`. This applies here because the function
directly
- /// dereferences `data` and `out_len`.
- ///
- /// ## Security Assumptions
- /// The caller (OP-TEE OS) must ensure:
- /// - `data` points to valid memory for reads and writes of at least
`in_len` bytes
- /// - `out_len` is a valid, writable, and properly aligned pointer to
a `u32` (cannot be null)
- /// - If `in_len == 0`, `data` may be null; otherwise it must be
non-null
- /// - The memory region pointed to by `data` must not be modified by
other threads
- /// or processes during plugin execution
- ///
- /// Additional guarantees enforced by `PluginParameters` in this SDK:
- /// - If `data` is null and `in_len` is 0, it is treated as an empty
input buffer;
- /// the inner logic (developer code) should consider this case
- /// - If the output length exceeds `in_len`, it will be rejected and a
short buffer
- /// error returned, with the required `out_len` set
- /// - Input and output share the same buffer, so overlap is
intentional and safely
- /// handled by [`PluginParameters::set_buf_from_slice`] when
`out_len <= in_len`
- /// - If no output is set for a success call, `out_len` will be `0`
- ///
- /// ## Usage Scenarios
- /// - **Valid empty call**: `data = null`, `in_len = 0` → allowed
(empty input to inner)
- /// - **Normal call**: `data` points to a buffer of size `in_len`; if
`out_len <= in_len`,
- /// the plugin writes up to `in_len` bytes and updates `*out_len`;
if `out_len > in_len`,
- /// it is rejected with a short buffer error
- /// - **Buffer overflow attempt**: if inner logic (developer code)
tries to return
- /// more bytes than `in_len` → rejected by `set_buf_from_slice`,
error returned with required `out_len`
- /// - **Invalid pointers**: null pointers are checked, but other
invalid cases of pointers
- /// such as dangling, misaligned, or read-only pointers will cause
undefined behavior
- /// and must be prevented by the caller
- pub unsafe fn _plugin_invoke(
+ #f_vis #f_sig {
+ #f_block
+ }
+ const _: fn(_: &mut optee_teec::PluginParameters) ->
optee_teec::Result<()> = #origin_fn_name;
+
+ unsafe extern "C" fn #bindgen_fn_name(
cmd: u32,
sub_cmd: u32,
- data: *mut core::ffi::c_char,
- in_len: u32,
- out_len: *mut u32,
+ data: *mut core::ffi::c_void,
+ in_len: optee_teec::raw::size_t,
+ out_len: *mut optee_teec::raw::size_t,
) -> optee_teec::raw::TEEC_Result {
- fn inner(#params) -> optee_teec::Result<()> {
- #f_block
- }
-
- // Check for null pointers
- if data.is_null() && in_len != 0 {
- return optee_teec::raw::TEEC_ERROR_BAD_PARAMETERS;
- }
- if out_len.is_null() {
- return optee_teec::raw::TEEC_ERROR_BAD_PARAMETERS;
- }
-
- // Prepare input buffer
- // `data` is guaranteed to be non-null if `in_len > 0` (checked
above)
- // If `data` is null, `in_len` must be 0, so we create an empty
slice
- // Otherwise, we create a mutable slice from the raw pointer and
length
- let inbuf = if data.is_null() {
- &mut []
- } else {
- // SAFETY: from_raw_parts_mut() is unsafe, but avoids copying
the memory
- // (which is unacceptable for large io buffers).
- // Note that the caller must ensure the memory is consistent
during the plugin execution.
- std::slice::from_raw_parts_mut(data, in_len as usize)
+ let mut parameter = match unsafe {
+ optee_teec::PluginParameters::from_raw(
+ cmd,
+ sub_cmd,
+ data,
+ in_len,
+ out_len,
+ )
+ } {
+ Ok(v) => v,
+ Err(err) => return err.raw_code(),
};
- let mut params = optee_teec::PluginParameters::new(cmd, sub_cmd,
inbuf);
- match inner(&mut params) {
- Ok(()) => {
- *out_len = params.get_required_out_len() as u32;
- optee_teec::raw::TEEC_SUCCESS
- }
- Err(err) => {
- if err.kind() == optee_teec::ErrorKind::ShortBuffer {
- // Inform the caller about the required buffer size
- *out_len = params.get_required_out_len() as u32;
- optee_teec::raw::TEEC_ERROR_SHORT_BUFFER
- } else {
- err.raw_code()
- }
- }
+ match #origin_fn_name(&mut parameter) {
+ Ok(()) => optee_teec::raw::TEEC_SUCCESS,
+ Err(err) => err.raw_code(),
}
}
)
diff --git a/crates/optee-teec-sys/src/lib.rs b/crates/optee-teec-sys/src/lib.rs
index a4e374e..f667972 100644
--- a/crates/optee-teec-sys/src/lib.rs
+++ b/crates/optee-teec-sys/src/lib.rs
@@ -17,7 +17,9 @@
#![allow(non_camel_case_types, non_snake_case)]
+pub use plugin_method::*;
pub use tee_client_api::*;
pub type size_t = usize;
+mod plugin_method;
mod tee_client_api;
diff --git a/examples/supp_plugin-rs/plugin/plugin_static.rs
b/crates/optee-teec-sys/src/plugin_method.rs
similarity index 66%
rename from examples/supp_plugin-rs/plugin/plugin_static.rs
rename to crates/optee-teec-sys/src/plugin_method.rs
index 2378e4f..dfa6120 100644
--- a/examples/supp_plugin-rs/plugin/plugin_static.rs
+++ b/crates/optee-teec-sys/src/plugin_method.rs
@@ -15,13 +15,19 @@
// specific language governing permissions and limitations
// under the License.
-#[no_mangle]
-pub static mut plugin_method: optee_teec::PluginMethod =
optee_teec::PluginMethod {
- name: plugin_name.as_ptr(),
- uuid: PLUGIN_UUID_STRUCT,
- init: _plugin_init,
- invoke: _plugin_invoke,
-};
+use crate::{TEEC_Result, TEEC_UUID, size_t};
+use core::ffi::{c_char, c_void};
-#[no_mangle]
-pub static plugin_name: &[u8] = b"syslog\0";
+#[repr(C)]
+pub struct PluginMethod {
+ pub name: *const c_char,
+ pub uuid: TEEC_UUID,
+ pub init: unsafe extern "C" fn() -> TEEC_Result,
+ pub invoke: unsafe extern "C" fn(
+ cmd: u32,
+ sub_cmd: u32,
+ data: *mut c_void,
+ in_len: size_t,
+ out_len: *mut size_t,
+ ) -> TEEC_Result,
+}
diff --git a/crates/optee-teec-systest/build.rs
b/crates/optee-teec-systest/build.rs
index 7e7b130..367014a 100644
--- a/crates/optee-teec-systest/build.rs
+++ b/crates/optee-teec-systest/build.rs
@@ -28,11 +28,15 @@ fn main() {
.edition(2024)
.target("aarch64-unknown-linux-gnu")
.header("tee_client_api.h")
+ .header("tee_plugin_method.h")
.include(path.display().to_string())
.rename_struct_ty(|ty| {
if ty.starts_with("TEEC") {
return Some(ty.to_string());
}
+ if ty.eq("PluginMethod") {
+ return Some("struct plugin_method".to_string());
+ }
None
})
.rename_union_ty(|ty| {
diff --git a/crates/optee-teec/Cargo.toml b/crates/optee-teec/Cargo.toml
index 5035ebe..70b10e4 100644
--- a/crates/optee-teec/Cargo.toml
+++ b/crates/optee-teec/Cargo.toml
@@ -24,12 +24,17 @@ license.workspace = true
repository.workspace = true
edition.workspace = true
+[features]
+default = []
+macros = ["dep:optee-teec-macros"]
+
[dependencies]
optee-teec-sys.workspace = true
-optee-teec-macros.workspace = true
+optee-teec-macros = { workspace = true, optional = true }
uuid.workspace = true
hex.workspace = true
num_enum.workspace = true
+log.workspace = true
[dev-dependencies]
optee-teec-sys = { workspace = true, features = ["no_link"] }
diff --git a/crates/optee-teec/src/extension.rs
b/crates/optee-teec/src/extension.rs
index 8f50b49..2da8868 100644
--- a/crates/optee-teec/src/extension.rs
+++ b/crates/optee-teec/src/extension.rs
@@ -15,65 +15,155 @@
// specific language governing permissions and limitations
// under the License.
-use crate::raw;
-use crate::{Error, ErrorKind, Result};
-use core::ffi::c_char;
+use crate::{
+ raw::size_t,
+ {ErrorKind, Result},
+};
+use core::ffi::c_void;
-#[repr(C)]
-pub struct PluginMethod {
- pub name: *const c_char,
- pub uuid: raw::TEEC_UUID,
- pub init: fn() -> raw::TEEC_Result,
- pub invoke: unsafe fn(
- cmd: u32,
- sub_cmd: u32,
- data: *mut c_char,
- in_len: u32,
- out_len: *mut u32,
- ) -> raw::TEEC_Result,
-}
-
-/// struct PluginParameters {
-/// @cmd: u32, plugin cmd, defined in proto/
-/// @sub_cmd: u32, plugin subcmd, defined in proto/
-/// @inout: &'a mut [u8], input/output buffer shared with TA and plugin
-/// @required_outlen, length of output sent to TA
-/// }
-pub struct PluginParameters<'a> {
+/// Parameters for a plugin invocation, carrying the command, sub-command,
+/// and the inout buffer.
+///
+/// The core design goal of this struct is to prevent developers from
forgetting
+/// to set `out_len`. In the C ABI, `out_len` is a raw pointer that the plugin
+/// must write to in order to report how many bytes it actually
+/// produced. Forgetting to set it is a silent bug — the TA caller receives
+/// garbage (uninitialized or stale) length, leading to buffer over-reads or
+/// truncated output that is extremely hard to diagnose.
+///
+/// To eliminate this class of bugs, `PluginParameters` ties `out_len` to
+/// every output-writing operation: `write_output_at` and `set_buf_from_slice`,
+/// both automatically update `out_len` on success, so the plugin developers
+/// never has to do it manually. If plugin developers need full control, they
+/// can use `get_buffer_mut` and `set_out_len` explicitly.
+pub struct PluginParameters<'a, 'b> {
+ /// Command identifier for the plugin invocation.
pub cmd: u32,
+ /// Sub-command identifier for the plugin invocation.
pub sub_cmd: u32,
- pub inout: &'a mut [u8],
- required_outlen: usize,
+ /// Inout buffer that carries input data into the plugin and receives
+ /// output data from it.
+ buf: &'a mut [u8],
+ /// Pointer to the output length that the plugin must set.
+ /// Wrapped in `Option` because the C API allows it to be NULL
+ /// (meaning the caller does not expect output). When present,
+ /// every output-writing method automatically updates this value,
+ /// ensuring `out_len` is never left unset.
+ out_len: Option<&'b mut size_t>,
}
-impl<'a> PluginParameters<'a> {
- pub fn new(cmd: u32, sub_cmd: u32, inout: &'a mut [u8]) -> Self {
- Self {
+
+impl<'a, 'b> PluginParameters<'a, 'b> {
+ /// Constructs a `PluginParameters` from raw C pointers.
+ ///
+ /// # Safety
+ /// - `buf` must be valid for reads/writes of `in_len` bytes if not null
+ /// - `out_len` must be valid for writes if not null
+ /// - both pointers must remain alive for the lifetime of the returned
+ /// `PluginParameters`
+ ///
+ /// When `out_len` is non-null, it will be tracked by the returned struct
+ /// so that output-writing methods can update it automatically — this is
+ /// the key mechanism that prevents the "forgot to set out_len" bug.
+ pub unsafe fn from_raw(
+ cmd: u32,
+ sub_cmd: u32,
+ buf: *mut c_void,
+ in_len: size_t,
+ out_len: *mut size_t,
+ ) -> Result<Self> {
+ // Reject obviously invalid parameter combinations:
+ // a non-zero in_len or a present out_len implies the caller expects
+ // to use the buffer, so a null buf pointer is illegal.
+ if (in_len != 0 || !out_len.is_null()) && buf.is_null() {
+ return Err(ErrorKind::BadParameters.into());
+ }
+ // Wrap the raw buffer pointer into a safe slice.
+ // For current OP-TEE, buf should always be non-null.
+ let buf = match buf.is_null() {
+ true => &mut [],
+ false => unsafe { core::slice::from_raw_parts_mut(buf as *mut _,
in_len) },
+ };
+ // Track the out_len pointer so output-writing methods can update it
+ // automatically — this is what prevents "forgot to set out_len" bugs.
+ let out_len = unsafe { out_len.as_mut() };
+
+ Ok(Self {
cmd,
sub_cmd,
- inout,
- required_outlen: 0_usize,
- }
+ buf,
+ out_len,
+ })
}
- // This function copies data from the provided slice to the inout buffer
- // Please note that the Result should be properly handled by the caller
- // If the inout buffer is smaller than the provided slice, an error will
be returned
- // Ignoring this error will lead to undefined behavior
+ /// Copies the entire `sendslice` into the inout buffer starting at offset
+ /// 0, and automatically sets `out_len` to `sendslice.len()`.
+ ///
+ /// This is the primary safe way to write output — callers do not need to
+ /// update `out_len` separately.
+ ///
+ /// Returns `ShortBuffer` if the buffer is too small, or `BadState` if
+ /// the output length pointer is not available.
pub fn set_buf_from_slice(&mut self, sendslice: &[u8]) -> Result<()> {
- if self.inout.len() < sendslice.len() {
- println!("Overflow: Input length is less than output length");
- self.required_outlen = sendslice.len();
- return Err(Error::new(ErrorKind::ShortBuffer));
+ self.write_output_at(0, sendslice)
+ }
+
+ /// Writes `data` into the inout buffer at the given `pos`, and
+ /// automatically updates `out_len` to `pos + data.len()`.
+ ///
+ /// By always updating `out_len` on a successful write, this method
+ /// eliminates the risk of the developer forgetting to set it.
+ ///
+ /// Returns `ShortBuffer` if the buffer is too small, or `BadState` if
+ /// the output length pointer is not available.
+ pub fn write_output_at(&mut self, pos: usize, data: &[u8]) -> Result<()> {
+ if let Some(out_len) = self.out_len.as_mut() {
+ let dest_len = pos + data.len();
+ if self.buf.len() < dest_len {
+ // Buffer overflow: not enough space for the write
+ log::debug!("Overflow: Input length is less than output
length");
+ return Err(ErrorKind::ShortBuffer.into());
+ }
+ self.buf[pos..dest_len].copy_from_slice(data);
+ (**out_len) = dest_len;
+ return Ok(());
}
- self.required_outlen = sendslice.len();
- self.inout[..self.required_outlen].copy_from_slice(sendslice);
- Ok(())
+ log::debug!("output is not allowed");
+ Err(ErrorKind::BadState.into())
+ }
+
+ /// Returns a shared reference to the inout buffer.
+ pub fn get_buffer(&self) -> &[u8] {
+ self.buf
}
- /// This function returns the required output length
- /// If the inout buffer is too small, this indicates the size needed
- /// If the inout buffer is large enough, this is the actual output length
- pub fn get_required_out_len(&self) -> usize {
- self.required_outlen
+ /// Returns a mutable reference to the inout buffer.
+ ///
+ /// # Safety
+ /// The caller is responsible for updating `out_len` (via [`set_out_len`])
+ /// after writing to the buffer.
+ pub unsafe fn get_buffer_mut(&mut self) -> &mut [u8] {
+ self.buf
+ }
+
+ /// Explicitly sets `out_len` to the given value.
+ ///
+ /// This is an escape hatch for cases where the caller needs full control
+ /// over the output length (e.g. after using `get_buffer_mut`). In most
+ /// cases should prefer `write_output_at` or `set_buf_from_slice`, which
set
+ /// `out_len` automatically and avoid the "forgot to set out_len" bug.
+ ///
+ /// Returns `BadParameters` if `out_len` exceeds the buffer size, or
+ /// `BadState` if the output length pointer is not available.
+ pub fn set_out_len(&mut self, out_len: usize) -> Result<()> {
+ if out_len > self.buf.len() {
+ return Err(ErrorKind::BadParameters.into());
+ }
+ match self.out_len.as_mut() {
+ None => Err(ErrorKind::BadState.into()),
+ Some(v) => {
+ **v = out_len;
+ Ok(())
+ }
+ }
}
}
diff --git a/crates/optee-teec/src/lib.rs b/crates/optee-teec/src/lib.rs
index 5220ce1..8510e54 100644
--- a/crates/optee-teec/src/lib.rs
+++ b/crates/optee-teec/src/lib.rs
@@ -22,11 +22,17 @@ pub use self::operation::Operation;
pub use self::parameter::{Param, ParamNone, ParamTmpRef, ParamType,
ParamTypes, ParamValue};
pub use self::session::{ConnectionMethods, Session};
pub use self::uuid::Uuid;
-pub use optee_teec_macros::{plugin_init, plugin_invoke};
// Re-export optee_teec_sys so developers don't have to add it to their cargo
// dependencies.
pub use optee_teec_sys as raw;
+// Re-export optee_teec_macros so developers don't have to add it as a separate
+// cargo dependency.
+// Gate it behind a feature flag to avoid unnecessary build-time overhead when
+// it’s not needed (see #283).
+#[cfg(feature = "macros")]
+pub use optee_teec_macros as macros;
+
mod context;
mod error;
mod extension;
diff --git a/crates/optee-utee-build/Cargo.toml
b/crates/optee-utee-build/Cargo.toml
index 8f0050c..2bb2ec3 100644
--- a/crates/optee-utee-build/Cargo.toml
+++ b/crates/optee-utee-build/Cargo.toml
@@ -26,7 +26,7 @@ edition.workspace = true
[dependencies]
quote.workspace = true
-proc-macro2 = "1.0.92"
+proc-macro2.workspace = true
syn.workspace = true
prettyplease = "0.2.25"
uuid.workspace = true
diff --git a/examples/supp_plugin-rs/plugin/Cargo.toml
b/examples/supp_plugin-rs/plugin/Cargo.toml
index 1d0410f..804d2dc 100644
--- a/examples/supp_plugin-rs/plugin/Cargo.toml
+++ b/examples/supp_plugin-rs/plugin/Cargo.toml
@@ -27,10 +27,11 @@ edition = "2018"
[dependencies]
libc = "0.2.48"
proto = { path = "../proto" }
-optee-teec = { path = "../../../crates/optee-teec" }
+optee-teec = { path = "../../../crates/optee-teec", features = ["macros"] }
[build-dependencies]
-uuid = { version = "0.8" }
+optee-teec-build = { path = "../../../crates/optee-teec-build" }
+uuid = { version = "1.23" }
proto = { path = "../proto" }
anyhow = "1.0"
@@ -42,4 +43,4 @@ crate-type = ["cdylib"]
name = "syslog_plugin"
[package.metadata.optee.plugin]
-uuid-path = "../plugin_uuid.txt"
\ No newline at end of file
+uuid-path = "../plugin_uuid.txt"
diff --git a/examples/supp_plugin-rs/plugin/build.rs
b/examples/supp_plugin-rs/plugin/build.rs
index fc924fc..4278c8a 100644
--- a/examples/supp_plugin-rs/plugin/build.rs
+++ b/examples/supp_plugin-rs/plugin/build.rs
@@ -14,33 +14,12 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
+use optee_teec_build::{uuid::Uuid, PluginConfig};
-use anyhow::{anyhow, Result};
-use std::env;
-use std::fs::File;
-use std::io::Write;
-use std::path::PathBuf;
-use uuid::Uuid;
-
-fn main() -> Result<()> {
- let out = &PathBuf::from(env::var_os("OUT_DIR").ok_or_else(||
anyhow!("OUT_DIR not set"))?);
- let mut buffer = File::create(out.join("plugin_static.rs"))?;
- buffer.write_all(include_bytes!("plugin_static.rs"))?;
-
- let plugin_uuid = Uuid::parse_str(proto::PLUGIN_UUID)?;
- let (time_low, time_mid, time_hi_and_version, clock_seq_and_node) =
plugin_uuid.as_fields();
-
- writeln!(buffer)?;
- write!(
- buffer,
- "const PLUGIN_UUID_STRUCT: optee_teec::raw::TEEC_UUID =
optee_teec::raw::TEEC_UUID {{
- timeLow: {:#x},
- timeMid: {:#x},
- timeHiAndVersion: {:#x},
- clockSeqAndNode: {:#x?},
-}};",
- time_low, time_mid, time_hi_and_version, clock_seq_and_node
- )?;
+fn main() -> anyhow::Result<()> {
+ PluginConfig::new(Uuid::parse_str(proto::PLUGIN_UUID)?)
+ .with_name("syslog")
+ .build()?;
Ok(())
}
diff --git a/examples/supp_plugin-rs/plugin/src/lib.rs
b/examples/supp_plugin-rs/plugin/src/lib.rs
index 65ffe5d..e9d36e3 100644
--- a/examples/supp_plugin-rs/plugin/src/lib.rs
+++ b/examples/supp_plugin-rs/plugin/src/lib.rs
@@ -15,24 +15,28 @@
// specific language governing permissions and limitations
// under the License.
-use optee_teec::{plugin_init, plugin_invoke, ErrorKind, PluginParameters};
+use optee_teec::{
+ macros::{plugin_init, plugin_invoke},
+ ErrorKind, Result,
+};
use proto::PluginCommand;
#[plugin_init]
-fn init() -> optee_teec::Result<()> {
+fn init() -> Result<()> {
println!("*plugin*: init, version: {}", env!("CARGO_PKG_VERSION"));
Ok(())
}
#[plugin_invoke]
-fn invoke(params: &mut PluginParameters) -> optee_teec::Result<()> {
+fn invoke(params: &mut optee_teec::PluginParameters) -> optee_teec::Result<()>
{
println!("*plugin*: invoke");
match PluginCommand::from(params.cmd) {
PluginCommand::Print => {
+ let input = params.get_buffer();
println!(
"*plugin*: receive value: {:?} length {:?}",
- params.inout,
- params.inout.len()
+ input,
+ input.len()
);
let send_slice: [u8; 9] = [0x40; 9];
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]