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]


Reply via email to