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

commit f76348c1301f1fef794ce091d377dd2070536ec4
Author: ivila <[email protected]>
AuthorDate: Wed Jun 24 17:43:53 2026 +0800

    docs: add TA parameter migration guide
---
 docs/README.md                         |   1 +
 docs/optee-utee-parameter-migration.md | 463 +++++++++++++++++++++++++++++++++
 2 files changed, 464 insertions(+)

diff --git a/docs/README.md b/docs/README.md
index 1a80dd2..1b81bdd 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -15,6 +15,7 @@ permalink: /trustzone-sdk-docs
 * [TA Development Modes](ta-development-modes.md)
 * [Overview of OP-TEE Rust Examples](overview-of-optee-rust-examples.md)
 * [Writing Rust TAs using 
optee-utee-build](writing-rust-tas-using-optee-utee-build.md)
+* [Migrating to the Typed optee-utee Parameter 
API](optee-utee-parameter-migration.md)
 * [OPTEE-UTEE: Writing Unit Tests with 
Mocks](optee-utee-writing-unit-tests-with-mocks.md)
 * [Building Rust CA as Android ELF](building-rust-ca-as-android-elf.md)
 
diff --git a/docs/optee-utee-parameter-migration.md 
b/docs/optee-utee-parameter-migration.md
new file mode 100644
index 0000000..bfa4f08
--- /dev/null
+++ b/docs/optee-utee-parameter-migration.md
@@ -0,0 +1,463 @@
+---
+permalink: /trustzone-sdk-docs/optee-utee-parameter-migration.md
+---
+
+# Migrating TA Code to the Typed Parameter API
+
+This guide explains how to migrate OP-TEE TA code from the deprecated
+`optee_utee::Parameters` API to the typed parameter API. The public types are
+re-exported from `optee_utee` and `optee_utee::prelude`.
+
+The typed API keeps the same OP-TEE parameter model: every entry point receives
+up to four parameters, and each slot has one runtime type tag. The difference 
is
+that TA code now represents those tags with Rust types such as
+`ParameterMemrefInput<'_>`, `ParameterValueOutput<'_>`, and `ParameterNone`.
+The Normal World client application may be implemented with any OP-TEE client
+stack; this document only describes the Secure World TA side.
+
+## Why migrate
+
+The old API exposed each slot as a generic `Parameter` and required unsafe
+accessors:
+
+```rust
+let mut p0 = unsafe { params.0.as_memref()? };
+let buf = p0.buffer();
+```
+
+That code allowed TA-side code to request a value or memref dynamically at each
+use site. A mismatch was discovered only when that unsafe accessor ran, or
+worse, hidden behind mutable access to a buffer whose intended direction was
+not visible in the Rust type.
+
+The new API makes the expected direction explicit:
+
+```rust
+let input = p0.as_memref_input()?.get_buffer();
+let output = p1.as_memref_output()?;
+output.set_output(bytes)?;
+```
+
+This does not make the client application and TA parameter layouts a
+compile-time contract. They are built as separate OP-TEE components, and the 
TEE
+still delivers parameter type tags at runtime. The typed API improves the TA
+side: it records the TA's expected layout in Rust types, performs consistent
+runtime validation at the entry point or branch accessor, and removes most
+call-site unsafe code.
+
+For commands with a fixed parameter layout, the entry-point signature can use a
+concrete 4-tuple. For commands where different command IDs use different
+layouts, use `ParametersAny<'_>` and validate each slot inside the command
+branch.
+
+## Imports
+
+Most TA code should import the prelude:
+
+```rust
+use optee_utee::prelude::*;
+use optee_utee::{ErrorKind, Result};
+```
+
+The prelude exports the TA entry-point macros, `ParametersAny`,
+`ParametersNone`, all typed parameter wrappers, and the read/write traits 
needed
+for methods such as `get_buffer`, `get_a`, and `set_output`.
+
+## Parameter Directions
+
+Choose typed wrappers from the TA's perspective:
+
+| Data flow | TA typed parameter |
+| --- | --- |
+| No parameter in this slot | `ParameterNone` |
+| Client application provides two `u32` values for the TA to read | 
`ParameterValueInput` |
+| TA writes two `u32` values back to the client application | 
`ParameterValueOutput<'_>` |
+| TA reads and writes two `u32` values | `ParameterValueInout<'_>` |
+| Client application provides a buffer for the TA to read | 
`ParameterMemrefInput<'_>` |
+| TA writes bytes into a client-provided output buffer | 
`ParameterMemrefOutput<'_>` |
+| TA reads and writes the same buffer | `ParameterMemrefInout<'_>` |
+
+The direction names are strict. If the TA writes to a buffer, use
+`ParameterMemrefOutput<'_>` or `ParameterMemrefInout<'_>`. If the TA only reads
+from a buffer, use `ParameterMemrefInput<'_>` or `ParameterMemrefInout<'_>`.
+
+## Entry-Point Signatures
+
+The `#[ta_open_session]` and `#[ta_invoke_command]` macros accept any parameter
+type that implements `FromRawParameters<'_>`. In normal code this means either:
+
+1. `ParametersNone`
+2. `ParametersAny<'_>`
+3. A concrete 4-tuple of typed wrappers
+
+### No Parameters
+
+Use `ParametersNone` when no parameters are expected.
+
+```rust
+#[ta_open_session]
+fn open_session(_params: &mut ParametersNone) -> Result<()> {
+    Ok(())
+}
+
+#[ta_invoke_command]
+fn invoke_command(cmd_id: u32, _params: &mut ParametersNone) -> Result<()> {
+    match Command::from(cmd_id) {
+        Command::Ping => Ok(()),
+        _ => Err(ErrorKind::BadParameters.into()),
+    }
+}
+```
+
+### Fixed Layout
+
+Use a concrete tuple when every supported command for an entry point uses the
+same parameter layout. The macro converts the raw OP-TEE parameters into the
+tuple before your function runs, and returns `TEE_ERROR_BAD_PARAMETERS` if any
+runtime slot type does not match the tuple.
+
+```rust
+#[ta_invoke_command]
+fn invoke_command(
+    cmd_id: u32,
+    (input, output, _, _): &mut (
+        ParameterMemrefInput<'_>,
+        ParameterMemrefOutput<'_>,
+        ParameterNone,
+        ParameterNone,
+    ),
+) -> Result<()> {
+    let bytes = input.get_buffer();
+    let response = handle_request(Command::from(cmd_id), bytes)?;
+    output.set_output(response)
+}
+```
+
+Use this style for examples such as a single request buffer plus a single
+response buffer.
+
+### Command-Dependent Layout
+
+Use `ParametersAny<'_>` when different command IDs use different layouts. Each
+slot is first decoded into `ParameterAny`, and command branches then request 
the
+specific type they expect.
+
+```rust
+#[ta_invoke_command]
+fn invoke_command(cmd_id: u32, (p0, p1, p2, _): &mut ParametersAny<'_>) -> 
Result<()> {
+    match Command::from(cmd_id) {
+        Command::Update => {
+            let input = p0.as_memref_input()?.get_buffer();
+            digest_update(input);
+            Ok(())
+        }
+        Command::DoFinal => {
+            let input = p0.as_memref_input()?.get_buffer();
+            let output = p1.as_memref_output()?;
+            let size = digest_final(input, output.get_buffer_mut())?;
+            p2.as_value_output()?.set_a(size as u32);
+            output.set_updated_size(size)
+        }
+        _ => Err(ErrorKind::BadParameters.into()),
+    }
+}
+```
+
+This is the right choice for cryptographic examples where `Prepare`, `Update`,
+and `Final` commands use different parameter directions.
+
+## Replacing Value Access
+
+Old code:
+
+```rust
+let value = unsafe { params.0.as_value()? };
+let mode = value.a();
+let flags = value.b();
+```
+
+New code for an input value:
+
+```rust
+let value = p0.as_value_input()?;
+let mode = value.get_a();
+let flags = value.get_b();
+```
+
+New code for an output value:
+
+```rust
+let value = p0.as_value_output()?;
+value.set_a(result_len as u32);
+value.set_b(status);
+```
+
+New code for an in/out value:
+
+```rust
+let value = &mut params.0;
+value.set_a(value.get_a() + 1);
+```
+
+The trait split is intentional:
+
+| Operation | Trait | Implemented by |
+| --- | --- | --- |
+| `get_a`, `get_b` | `ParameterValueRead` | `ParameterValueInput`, 
`ParameterValueInout` |
+| `set_a`, `set_b` | `ParameterValueWrite` | `ParameterValueOutput`, 
`ParameterValueInout` |
+
+## Replacing Memref Access
+
+Old code:
+
+```rust
+let mut p0 = unsafe { params.0.as_memref()? };
+let input = p0.buffer();
+```
+
+New code for an input memref:
+
+```rust
+let input = p0.as_memref_input()?.get_buffer();
+```
+
+Old code for output:
+
+```rust
+let mut p1 = unsafe { params.1.as_memref()? };
+let output = p1.buffer();
+output[..bytes.len()].copy_from_slice(&bytes);
+p1.set_updated_size(bytes.len());
+```
+
+New code:
+
+```rust
+let output = p1.as_memref_output()?;
+output.set_output(bytes)?;
+```
+
+Use `set_output` for the common case where the output starts at offset 0. Use
+`write_at` when appending or writing at a specific offset:
+
+```rust
+let output = p0.as_memref_output()?;
+output.write_at(0, header)?;
+output.write_at(header.len(), body)?;
+```
+
+Use `get_buffer_mut` when an API writes directly into the output buffer:
+
+```rust
+let output = p1.as_memref_output()?;
+let written = cipher.update(input.get_buffer(), output.get_buffer_mut())?;
+output.set_updated_size(written)
+```
+
+When using `get_buffer_mut`, always call `set_updated_size` afterward.
+Otherwise the client application may observe an incorrect output size.
+
+The memref traits are:
+
+| Operation | Trait | Implemented by |
+| --- | --- | --- |
+| `get_buffer` | `ParameterMemrefRead` | `ParameterMemrefInput`, 
`ParameterMemrefInout` |
+| `get_buffer_mut`, `set_updated_size`, `set_output`, `write_at` | 
`ParameterMemrefWrite` | `ParameterMemrefOutput`, `ParameterMemrefInout` |
+
+## Open Session Parameters
+
+Session opening is migrated the same way as command invocation.
+
+If the TA expects no parameters during session opening, use `ParametersNone`:
+
+```rust
+#[ta_open_session]
+fn open_session(_params: &mut ParametersNone) -> Result<()> {
+    Ok(())
+}
+```
+
+If the TA expects open-session parameters, express that expected layout with a
+concrete tuple or `ParametersAny<'_>`. For example, this TA expects slot 0 to 
be
+a client-provided input buffer containing an `f64` learning rate:
+
+```rust
+#[ta_open_session]
+fn open_session(
+    (p0, _, _, _): &mut (
+        ParameterMemrefInput<'_>,
+        ParameterNone,
+        ParameterNone,
+        ParameterNone,
+    ),
+) -> Result<()> {
+    let learning_rate = f64::from_le_bytes(
+        p0.get_buffer()
+            .try_into()
+            .map_err(|_| ErrorKind::BadParameters)?,
+    );
+    init(learning_rate)
+}
+```
+
+When the runtime parameters do not match this tuple, the generated entry point
+returns `TEE_ERROR_BAD_PARAMETERS` before calling `open_session`.
+
+## Session Context
+
+The parameter migration does not change session-context handling. If the old
+entry point had a context parameter, keep it and only change the parameter 
type:
+
+```rust
+#[ta_open_session]
+fn open_session(_params: &mut ParametersNone, ctx: &mut AesCipher) -> 
Result<()> {
+    *ctx = AesCipher::default();
+    Ok(())
+}
+
+#[ta_invoke_command]
+fn invoke_command(
+    ctx: &mut AesCipher,
+    cmd_id: u32,
+    params: &mut ParametersAny<'_>,
+) -> Result<()> {
+    dispatch(ctx, cmd_id, params)
+}
+```
+
+The context type must still implement `Default` for `#[ta_open_session]` with a
+context parameter.
+
+## Complete Migration Example
+
+Old code:
+
+```rust
+#[ta_invoke_command]
+fn invoke_command(cmd_id: u32, params: &mut Parameters) -> Result<()> {
+    match Command::from(cmd_id) {
+        Command::Serialize => {
+            let mut p0 = unsafe { params.0.as_memref()? };
+            let output = p0.buffer();
+            let bytes = serde_json::to_vec(&Point { x: 1, y: 2 })
+                .map_err(|_| ErrorKind::BadParameters)?;
+
+            if bytes.len() > output.len() {
+                p0.set_updated_size(bytes.len());
+                return Err(ErrorKind::ShortBuffer.into());
+            }
+
+            output[..bytes.len()].copy_from_slice(&bytes);
+            p0.set_updated_size(bytes.len());
+            Ok(())
+        }
+        _ => Err(ErrorKind::BadParameters.into()),
+    }
+}
+```
+
+New code:
+
+```rust
+#[ta_invoke_command]
+fn invoke_command(cmd_id: u32, (p0, _, _, _): &mut ParametersAny<'_>) -> 
Result<()> {
+    match Command::from(cmd_id) {
+        Command::Serialize => {
+            let output = p0.as_memref_output()?;
+            let bytes = serde_json::to_vec(&Point { x: 1, y: 2 })
+                .map_err(|_| ErrorKind::BadParameters)?;
+            output.set_output(bytes)
+        }
+        _ => Err(ErrorKind::BadParameters.into()),
+    }
+}
+```
+
+If `Serialize` is the only command and the layout is fixed, the TA entry-point
+validation can be made stricter:
+
+```rust
+#[ta_invoke_command]
+fn invoke_command(
+    cmd_id: u32,
+    (p0, _, _, _): &mut (
+        ParameterMemrefOutput<'_>,
+        ParameterNone,
+        ParameterNone,
+        ParameterNone,
+    ),
+) -> Result<()> {
+    match Command::from(cmd_id) {
+        Command::Serialize => {
+            let bytes = serde_json::to_vec(&Point { x: 1, y: 2 })
+                .map_err(|_| ErrorKind::BadParameters)?;
+            p0.set_output(bytes)
+        }
+        _ => Err(ErrorKind::BadParameters.into()),
+    }
+}
+```
+
+## Migration Checklist
+
+1. Replace `use optee_utee::{..., Parameters, ...}` with
+   `use optee_utee::prelude::*`.
+2. For each `#[ta_open_session]`, write down the TA's expected four-slot
+   parameter layout. Use `ParametersNone` only when all four slots are expected
+   to be `None`.
+3. For each `#[ta_invoke_command]`, write down the TA's expected four-slot
+   layout for each command ID.
+4. Use a concrete 4-tuple when every command in the entry point uses the same
+   layout.
+5. Use `ParametersAny<'_>` when command IDs use different layouts.
+6. Replace `as_value()?.a()` and `as_value()?.b()` with `get_a()` and
+   `get_b()` on the correct value wrapper.
+7. Replace `set_a()` and `set_b()` on old `ParamValue` with the same methods on
+   `ParameterValueOutput` or `ParameterValueInout`.
+8. Replace `as_memref()?.buffer()` reads with `get_buffer()`.
+9. Replace manual output-buffer copies with `set_output`, `write_at`, or
+   `get_buffer_mut` plus `set_updated_size`.
+10. Ensure every unused slot is represented as `ParameterNone`, not omitted.
+
+## Common Errors
+
+### `TEE_ERROR_BAD_PARAMETERS` During Open Session
+
+This means the runtime open-session parameters did not match the TA
+open-session signature. If the TA expects any value or memref slot, do not use
+`ParametersNone`; use a concrete tuple or `ParametersAny<'_>` that describes
+the expected runtime layout.
+
+### `TEE_ERROR_BAD_PARAMETERS` During Invoke Command
+
+For `ParametersAny<'_>`, this usually means a branch called the wrong accessor:
+for example `as_memref_input()` when the runtime slot is an output memref.
+
+For a concrete tuple, the macro validates all four slots before entering the
+function. Check the complete tuple, including the unused slots.
+
+### Output Size Is Wrong for the Client Application
+
+If TA code writes through `get_buffer_mut`, it must call `set_updated_size`.
+Prefer `set_output` when possible because it copies and updates the size in one
+step.
+
+### Input/Output Direction Is Ambiguous
+
+Choose the wrapper from the TA's perspective:
+
+| Data flow | TA wrapper |
+| --- | --- |
+| Client application writes, TA reads | `ParameterMemrefInput<'_>` |
+| TA writes, client application reads | `ParameterMemrefOutput<'_>` |
+| Both read and write | `ParameterMemrefInout<'_>` |
+
+## Legacy Compatibility
+
+The deprecated API is still available as `optee_utee::deprecated`, and it
+implements `FromRawParameters<'_>` for compatibility with the entry-point
+macros. New and migrated code should use the typed wrappers instead.
+
+Keeping the deprecated API in new code should be limited to transitional
+patches, because it keeps the unsafe raw-pointer access pattern and does not
+express parameter direction in the function signature.


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

Reply via email to