Skip to main content

consortium_tee_macros_impl/
tee_command.rs

1// Copyright 2026 Ethan Wu
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16
17use consortium_macros_helpers::{
18    get_output_type, is_context_ref, is_mut_tee_param_ref, is_primitive, is_result_returning,
19    is_slice_ref, is_tee_param_path, is_tuple_u32_u32, is_vec_u8, mut_scalar_inner,
20};
21use heck::ToPascalCase;
22use proc_macro2::{Span, TokenStream as TokenStream2};
23use quote::quote;
24use syn::{Ident, ItemFn, LitInt, PatType, Token};
25
26// Parses `#[tee_command(codec = MyCodec, ctx = context)]` attributes.
27struct TeeArgs {
28    pub codec: Option<syn::Path>,
29    pub ctx: Option<CtxArg>,
30}
31
32enum CtxArg {
33    First(Ident),
34    Ident(Ident),
35    Index(LitInt),
36}
37
38impl syn::parse::Parse for TeeArgs {
39    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
40        let mut codec = None;
41        let mut ctx = None;
42
43        while !input.is_empty() {
44            let ident: Ident = input.parse()?;
45            if ident == "codec" {
46                if codec.is_some() {
47                    return Err(syn::Error::new(ident.span(), "duplicate `codec` argument"));
48                }
49                let _eq: Token![=] = input.parse()?;
50                codec = Some(input.parse()?);
51            } else if ident == "ctx" {
52                if ctx.is_some() {
53                    return Err(syn::Error::new(ident.span(), "duplicate `ctx` argument"));
54                }
55                ctx = if input.peek(Token![=]) {
56                    let _eq: Token![=] = input.parse()?;
57                    if input.peek(LitInt) {
58                        Some(CtxArg::Index(input.parse()?))
59                    } else {
60                        Some(CtxArg::Ident(input.parse()?))
61                    }
62                } else {
63                    Some(CtxArg::First(ident))
64                };
65            } else {
66                return Err(syn::Error::new(ident.span(), "expected `codec` or `ctx`"));
67            }
68
69            if input.is_empty() {
70                break;
71            }
72            input.parse::<Token![,]>()?;
73        }
74
75        Ok(TeeArgs { codec, ctx })
76    }
77}
78
79/// Generates two companion functions for a TA command handler.
80///
81/// **`function_name_dispatched`** (TA side, default, `#[cfg(feature = "ta")]`): unpacks `::optee_utee::Parameters` via the
82/// `TeeParam` trait, calls the original function, and flushes any output or inout values
83/// back into their slots.
84///
85/// **`call_function_name`** (CA side, `#[cfg(feature = "ca")]`): packs arguments via
86/// `TeeParam`, calls `invoke_command`, then reads output slots back.
87///
88/// The original function body is emitted unchanged.
89///
90/// # Codec attribute
91///
92/// When any parameter or return type implements `TeeParam<C>` for a serialized codec, you
93/// must specify the codec:
94///
95/// ```no_run,ignore
96/// #[tee_command(codec = PostcardCodec, ctx)]
97/// fn my_handler(ctx: &mut Context, config: MyConfig) -> Result<(), TeeError> { /* … */ }
98/// ```
99///
100/// The codec `C` is passed as the type parameter to all `TeeParam<C>` calls. For
101/// primitive types (integers, booleans) the codec is ignored — they always use the
102/// `TeeParamSlot::Primitive` variant. Omitting `codec` uses `::consortium_tee::NoCodec`
103/// as the default, which causes a compile error if any serialized `TeeParam` types are
104/// present (since `NoCodec` does not implement `CodecFor<T>`).
105///
106/// # Parameter type support
107///
108/// | Rust type            | TEE slot       | Notes                                          |
109/// |----------------------|----------------|------------------------------------------------|
110/// | `&[u8]`              | `MemrefInput`  |                                                |
111/// | `&mut [u8]`          | `MemrefInout`  |                                                |
112/// | `&mut u32`           | `ValueInout`   | TA reads initial, writes result back           |
113/// | `&mut i32`           | `ValueInout`   | bit-cast through u32                           |
114/// | `&mut u64`           | `ValueInout`   | split across a(low)/b(high)                    |
115/// | `T: TeeParam<C>`     | Value/Memref   | dispatched via `T::into_tee_param` / `from`    |
116/// | `&mut T: TeeParam<C>`| `MemrefInout`  | inout codec round-trip                         |
117///
118/// `ctx` marks the first parameter as TA context. The parameter must be a mutable
119/// reference to a named path type and is skipped when assigning TEE slots. `ctx = name`
120/// and `ctx = 0` are accepted for compatibility and emit a deprecation warning.
121///
122/// Return values use the same `TeeParam<C>` dispatch.
123pub fn tee_command(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
124    let TeeArgs { codec, ctx } = match syn::parse2::<TeeArgs>(attr) {
125        Ok(args) => args,
126        Err(err) => return err.into_compile_error(),
127    };
128
129    // Resolved codec type token, and use NoCodec when omitted.
130    let codec_ty: TokenStream2 = match &codec {
131        Some(c) => quote! { #c },
132        None => quote! { ::consortium_tee::NoCodec },
133    };
134
135    let input = match syn::parse2::<ItemFn>(item) {
136        Ok(input) => input,
137        Err(err) => return err.into_compile_error(),
138    };
139    let fn_name = &input.sig.ident;
140    let dispatched_name = Ident::new(&format!("{}_dispatched", fn_name), fn_name.span());
141    let call_name = Ident::new(&format!("call_{}", fn_name), fn_name.span());
142
143    let parameters: Vec<&PatType> = input
144        .sig
145        .inputs
146        .iter()
147        .filter_map(|arg| {
148            if let syn::FnArg::Typed(pt) = arg {
149                Some(pt)
150            } else {
151                None
152            }
153        })
154        .collect();
155
156    let (ctx_index, ctx_warning) = match resolve_context_index(ctx.as_ref(), &parameters) {
157        Ok(resolved) => resolved,
158        Err(err) => return err.into_compile_error(),
159    };
160    let ctx_param = ctx_index.map(|index| parameters[index]);
161    let tee_params: Vec<&PatType> = parameters
162        .iter()
163        .enumerate()
164        .filter_map(|(index, param)| (Some(index) != ctx_index).then_some(*param))
165        .collect();
166
167    // Return-type analysis.
168    let output_ty: Option<&syn::Type> = get_output_type(&input.sig.output);
169    let has_output = output_ty.is_some();
170    let ret_is_result = is_result_returning(&input.sig.output);
171
172    // TA side: unpack stmts
173    let mut slot_index = 0usize;
174    let mut unpack_stmts = vec![];
175    let mut post_call_stmts = vec![];
176    let mut dispatch_call_args = vec![];
177
178    for (param_index, param) in parameters.iter().enumerate() {
179        let pat = &param.pat;
180        let ty = &param.ty;
181        if Some(param_index) == ctx_index {
182            dispatch_call_args.push(quote! { #pat });
183            continue;
184        }
185        let (unpack, post, next) = gen_unpack(pat, ty, slot_index, &codec_ty);
186        unpack_stmts.push(unpack);
187        if let Some(p) = post {
188            post_call_stmts.push(p);
189        }
190        dispatch_call_args.push(quote! { #pat });
191        slot_index = next;
192    }
193
194    // Output slot index (immediately after the last input slot).
195    let output_slot = slot_index;
196
197    // TA side: call + optional flush
198    let call_and_flush = if let Some(out_ty) = output_ty {
199        let flush = gen_flush_ta(output_slot, out_ty, &codec_ty);
200        if ret_is_result {
201            quote! {
202                let __ret_val = #fn_name(#(#dispatch_call_args),*)?;
203                #(#post_call_stmts)*
204                #flush
205                Ok(())
206            }
207        } else {
208            quote! {
209                let __ret_val = #fn_name(#(#dispatch_call_args),*);
210                #(#post_call_stmts)*
211                #flush
212                Ok(())
213            }
214        }
215    } else if !post_call_stmts.is_empty() {
216        if ret_is_result {
217            quote! {
218                #fn_name(#(#dispatch_call_args),*)?;
219                #(#post_call_stmts)*
220                Ok(())
221            }
222        } else {
223            quote! {
224                #fn_name(#(#dispatch_call_args),*);
225                #(#post_call_stmts)*
226                Ok(())
227            }
228        }
229    } else {
230        quote! { #fn_name(#(#dispatch_call_args),*) }
231    };
232
233    let param_binding = if !tee_params.is_empty() || has_output {
234        quote! { params }
235    } else {
236        quote! { _params }
237    };
238
239    // TA dispatched fn (only compiled when the "ta" feature is active).
240    // Without this gate a CA-only consumer would pull in ::optee_utee even though
241    // it only needs the generated `call_*` functions on the CA side.
242    let dispatched_fn = if let Some(ctx) = ctx_param {
243        let ctx_pat = &ctx.pat;
244        let ctx_ty = &ctx.ty;
245        quote! {
246            #[cfg(feature = "ta")]
247            fn #dispatched_name(#ctx_pat: #ctx_ty, #param_binding: &mut ::optee_utee::Parameters) -> ::optee_utee::Result<()> {
248                #(#unpack_stmts)*
249                #call_and_flush
250            }
251        }
252    } else {
253        quote! {
254            #[cfg(feature = "ta")]
255            fn #dispatched_name(#param_binding: &mut ::optee_utee::Parameters) -> ::optee_utee::Result<()> {
256                #(#unpack_stmts)*
257                #call_and_flush
258            }
259        }
260    };
261
262    // CA side: pack stmts
263    let mut pack_stmts = vec![];
264    let mut post_invoke_stmts = vec![];
265    let mut ca_sig_args = vec![];
266    let mut new_params_args = vec![];
267
268    for (i, param) in tee_params.iter().enumerate() {
269        let pat = &param.pat;
270        let ty = &param.ty;
271        let slot_ident = Ident::new(&format!("_p{}", i), Span::call_site());
272        ca_sig_args.push(quote! { #pat: #ty });
273        let (pack, post) = gen_pack_ca(&slot_ident, pat, ty, i, &codec_ty);
274        pack_stmts.push(pack);
275        if let Some(p) = post {
276            post_invoke_stmts.push(p);
277        }
278        new_params_args.push(quote! { #slot_ident });
279    }
280
281    if has_output {
282        let out_ty = output_ty.unwrap();
283        let out_slot = tee_params.len();
284        let slot_ident = Ident::new(&format!("_p{}", out_slot), Span::call_site());
285        pack_stmts.push(gen_output_pack_ca(&slot_ident, out_ty, out_slot, &codec_ty));
286        new_params_args.push(quote! { #slot_ident });
287    }
288
289    while new_params_args.len() < 4 {
290        new_params_args.push(quote! { ::optee_teec::ParamNone });
291    }
292
293    let command_variant = Ident::new(&fn_name.to_string().to_pascal_case(), fn_name.span());
294
295    // CA side function (only compiled when the "ca" feature is active).
296    let ca_fn = if has_output {
297        let out_ty = output_ty.unwrap();
298        let read_back = gen_output_read_ca(tee_params.len(), out_ty, &codec_ty);
299        quote! {
300            #[cfg(feature = "ca")]
301            pub fn #call_name(session: &mut ::optee_teec::Session, #(#ca_sig_args),*) -> ::consortium_tee::TeeResult<#out_ty> {
302                #(#pack_stmts)*
303                let mut _operation = ::optee_teec::Operation::new(0, #(#new_params_args),*);
304                session.invoke_command(Command::#command_variant as u32, &mut _operation)?;
305                #(#post_invoke_stmts)*
306                Ok(#read_back)
307            }
308        }
309    } else if !post_invoke_stmts.is_empty() {
310        quote! {
311            #[cfg(feature = "ca")]
312            pub fn #call_name(session: &mut ::optee_teec::Session, #(#ca_sig_args),*) -> ::consortium_tee::TeeResult<()> {
313                #(#pack_stmts)*
314                let mut _operation = ::optee_teec::Operation::new(0, #(#new_params_args),*);
315                session.invoke_command(Command::#command_variant as u32, &mut _operation)?;
316                #(#post_invoke_stmts)*
317                Ok(())
318            }
319        }
320    } else {
321        quote! {
322            #[cfg(feature = "ca")]
323            pub fn #call_name(session: &mut ::optee_teec::Session, #(#ca_sig_args),*) -> ::consortium_tee::TeeResult<()> {
324                #(#pack_stmts)*
325                let mut _operation = ::optee_teec::Operation::new(0, #(#new_params_args),*);
326                session.invoke_command(Command::#command_variant as u32, &mut _operation)?;
327                Ok(())
328            }
329        }
330    };
331
332    quote! {
333        #input
334        #ctx_warning
335        #dispatched_fn
336        #ca_fn
337    }
338}
339
340fn resolve_context_index(
341    ctx: Option<&CtxArg>,
342    parameters: &[&PatType],
343) -> syn::Result<(Option<usize>, TokenStream2)> {
344    let Some(ctx) = ctx else {
345        return Ok((None, quote! {}));
346    };
347
348    let (index, warning) = match ctx {
349        CtxArg::First(ident) => {
350            if parameters.is_empty() {
351                return Err(syn::Error::new(
352                    ident.span(),
353                    "`ctx` requires a first parameter to use as context",
354                ));
355            }
356            (0, quote! {})
357        }
358        CtxArg::Ident(ident) => {
359            let index = parameters
360                .iter()
361                .position(|param| {
362                    if let syn::Pat::Ident(pat_ident) = param.pat.as_ref() {
363                        pat_ident.ident == *ident
364                    } else {
365                        false
366                    }
367                })
368                .ok_or_else(|| {
369                    syn::Error::new(
370                        ident.span(),
371                        format!("could not find context parameter `{ident}`"),
372                    )
373                })?;
374            (index, gen_deprecated_ctx_value_warning(index))
375        }
376        CtxArg::Index(lit) => {
377            let index = lit.base10_parse::<usize>()?;
378            if index >= parameters.len() {
379                return Err(syn::Error::new(
380                    lit.span(),
381                    format!("context parameter index {index} is out of range"),
382                ));
383            }
384            (index, gen_deprecated_ctx_value_warning(index))
385        }
386    };
387
388    let param = parameters[index];
389    if !is_context_ref(&param.ty) {
390        return Err(syn::Error::new_spanned(
391            &param.ty,
392            "`ctx` parameter must have type `&mut SomePath`",
393        ));
394    }
395
396    Ok((Some(index), warning))
397}
398
399fn gen_deprecated_ctx_value_warning(index: usize) -> TokenStream2 {
400    if index == 0 {
401        quote! {
402            const _: () = {
403                #[deprecated(
404                    note = "`#[tee_command(ctx = ...)]` is accepted for compatibility; prefer bare `ctx`"
405                )]
406                const __TEE_COMMAND_CTX_VALUE_WARNING: () = ();
407                __TEE_COMMAND_CTX_VALUE_WARNING
408            };
409        }
410    } else {
411        quote! {
412            const _: () = {
413                #[deprecated(
414                    note = "`#[tee_command(ctx = ...)]` is accepted for compatibility; move the context to the first parameter and use bare `ctx`"
415                )]
416                const __TEE_COMMAND_CTX_VALUE_WARNING: () = ();
417                __TEE_COMMAND_CTX_VALUE_WARNING
418            };
419        }
420    }
421}
422
423// ── TA-side unpack / flush code generation ───────────────────────────────────
424
425/// Returns `(unpack_stmt, optional_post_call_flush, next_slot_index)`.
426fn gen_unpack(
427    pat: &syn::Pat,
428    ty: &syn::Type,
429    slot: usize,
430    codec: &TokenStream2,
431) -> (TokenStream2, Option<TokenStream2>, usize) {
432    if slot >= 4 {
433        return (
434            quote! { compile_error!("Too many parameters: TEE only supports up to 4"); },
435            None,
436            slot,
437        );
438    }
439    let idx = syn::Index::from(slot);
440    let tmp = Ident::new(&format!("__p_{}", slot), Span::call_site());
441
442    // Priority 1: &[u8]
443    if is_slice_ref(ty, false) {
444        let stmt = quote! {
445            // SAFETY: slot #idx is MemrefInput - type match guaranteed by #[tee_command]
446            let mut #tmp = unsafe { params.#idx.as_memref()? };
447            let #pat: &[u8] = #tmp.buffer();
448        };
449        return (stmt, None, slot + 1);
450    }
451
452    // Priority 2: &mut [u8]
453    if is_slice_ref(ty, true) {
454        let stmt = quote! {
455            // SAFETY: slot #idx is MemrefInout - type match guaranteed by #[tee_command]
456            let mut #tmp = unsafe { params.#idx.as_memref()? };
457            let #pat: &mut [u8] = #tmp.buffer();
458        };
459        return (stmt, None, slot + 1);
460    }
461
462    // Priority 3: &mut u32 / &mut i32 / &mut u64 (ValueInout)
463    if let Some(scalar) = mut_scalar_inner(ty) {
464        let val_tmp = Ident::new(&format!("__val_{}", slot), Span::call_site());
465        let (unpack_stmt, flush_stmt) = match scalar {
466            "u32" => {
467                let u = quote! {
468                    // SAFETY: slot #idx is ValueInout - type match guaranteed by #[tee_command]
469                    let mut #tmp = unsafe { params.#idx.as_value()? };
470                    let mut #val_tmp: u32 = #tmp.a();
471                    let #pat: &mut u32 = &mut #val_tmp;
472                };
473                let f = quote! {
474                    // Flush &mut u32 back into ValueInout slot.
475                    #tmp.set_a(*#pat);
476                };
477                (u, f)
478            }
479            "i32" => {
480                let u = quote! {
481                    // SAFETY: slot #idx is ValueInout - type match guaranteed by #[tee_command]
482                    let mut #tmp = unsafe { params.#idx.as_value()? };
483                    let mut #val_tmp: i32 = #tmp.a() as i32;
484                    let #pat: &mut i32 = &mut #val_tmp;
485                };
486                let f = quote! {
487                    // Flush &mut i32 back into ValueInout slot (bit-cast).
488                    #tmp.set_a(*#pat as u32);
489                };
490                (u, f)
491            }
492            "u64" => {
493                let u = quote! {
494                    // SAFETY: slot #idx is ValueInout - type match guaranteed by #[tee_command]
495                    let mut #tmp = unsafe { params.#idx.as_value()? };
496                    let mut #val_tmp: u64 = (#tmp.b() as u64) << 32 | #tmp.a() as u64;
497                    let #pat: &mut u64 = &mut #val_tmp;
498                };
499                let f = quote! {
500                    // Flush &mut u64 back into ValueInout slot (split across a/b).
501                    #tmp.set_a(*#pat as u32);
502                    #tmp.set_b((*#pat >> 32) as u32);
503                };
504                (u, f)
505            }
506            _ => unreachable!(),
507        };
508        return (unpack_stmt, Some(flush_stmt), slot + 1);
509    }
510
511    // Priority 4: &mut T: TeeParam  (MemrefInout, round-trip via TeeParam<C>)
512    if is_mut_tee_param_ref(ty) {
513        let inner_ty = if let syn::Type::Reference(r) = ty {
514            r.elem.as_ref()
515        } else {
516            unreachable!()
517        };
518        let val_tmp = Ident::new(&format!("__val_{}", slot), Span::call_site());
519        let repr_flush = Ident::new(&format!("__repr_flush_{}", slot), Span::call_site());
520        let flush_bytes = Ident::new(&format!("__flush_bytes_{}", slot), Span::call_site());
521        let unpack = quote! {
522            // SAFETY: slot #idx is MemrefInout - type match guaranteed by #[tee_command]
523            let mut #tmp = unsafe { params.#idx.as_memref()? };
524            let mut #val_tmp: #inner_ty =
525                <#inner_ty as ::consortium_tee::TeeParam<#codec>>::from_tee_param(
526                    ::consortium_tee::TeeParamSlot::Serialized(#tmp.buffer())
527                ).map_err(|_| ::optee_utee::Error::new(::optee_utee::ErrorKind::BadParameters))?;
528            let #pat: &mut #inner_ty = &mut #val_tmp;
529        };
530        let flush = quote! {
531            // Re-serialize modified &mut T back into MemrefInout slot.
532            let #repr_flush =
533                <#inner_ty as ::consortium_tee::TeeParam<#codec>>::into_tee_param(&#val_tmp)
534                    .map_err(|_| ::optee_utee::Error::new(::optee_utee::ErrorKind::BadParameters))?;
535            let ::consortium_tee::TeeParamSlot::Serialized(ref #flush_bytes) = #repr_flush
536                else { unreachable!() };
537            #tmp.buffer()[..#flush_bytes.len()].copy_from_slice(#flush_bytes.as_slice());
538        };
539        return (unpack, Some(flush), slot + 1);
540    }
541
542    // Priority 5: any remaining owned type (primitives, (u32,u32), Vec<u8>, T: TeeParam).
543    // The slot kind (Value vs Memref) is determined by whether the TeeParam impl returns
544    // Primitive or Serialized; we use the type name to pick the right `as_value` / `as_memref`.
545    let is_value_slot = is_primitive(ty, "u8")
546        || is_primitive(ty, "u16")
547        || is_primitive(ty, "u32")
548        || is_primitive(ty, "u64")
549        || is_primitive(ty, "i8")
550        || is_primitive(ty, "i16")
551        || is_primitive(ty, "i32")
552        || is_primitive(ty, "i64")
553        || is_primitive(ty, "bool")
554        || is_tuple_u32_u32(ty);
555
556    if is_value_slot {
557        let stmt = quote! {
558            // SAFETY: slot #idx is ValueInput - type match guaranteed by #[tee_command]
559            let mut #tmp = unsafe { params.#idx.as_value()? };
560            let #pat = <#ty as ::consortium_tee::TeeParam<#codec>>::from_tee_param(
561                ::consortium_tee::TeeParamSlot::Primitive { a: #tmp.a(), b: #tmp.b() }
562            ).map_err(|_| ::optee_utee::Error::new(::optee_utee::ErrorKind::BadParameters))?;
563        };
564        return (stmt, None, slot + 1);
565    }
566
567    // Vec<u8> and T: TeeParam — Memref slot.
568    if is_vec_u8(ty) || is_tee_param_path(ty) {
569        let stmt = quote! {
570            // SAFETY: slot #idx is MemrefInput - type match guaranteed by #[tee_command]
571            let mut #tmp = unsafe { params.#idx.as_memref()? };
572            let #pat = <#ty as ::consortium_tee::TeeParam<#codec>>::from_tee_param(
573                ::consortium_tee::TeeParamSlot::Serialized(#tmp.buffer())
574            ).map_err(|_| ::optee_utee::Error::new(::optee_utee::ErrorKind::BadParameters))?;
575        };
576        return (stmt, None, slot + 1);
577    }
578
579    (
580        quote! { compile_error!("Unsupported TEE parameter type"); },
581        None,
582        slot,
583    )
584}
585
586/// Emits statements that write `__ret_val` into the output parameter slot at `slot`.
587fn gen_flush_ta(slot: usize, ty: &syn::Type, codec: &TokenStream2) -> TokenStream2 {
588    if slot >= 4 {
589        return quote! { compile_error!("Too many parameters: no room for the output slot (TEE max 4)"); };
590    }
591    let idx = syn::Index::from(slot);
592
593    let is_value_slot = is_primitive(ty, "u8")
594        || is_primitive(ty, "u16")
595        || is_primitive(ty, "u32")
596        || is_primitive(ty, "u64")
597        || is_primitive(ty, "i8")
598        || is_primitive(ty, "i16")
599        || is_primitive(ty, "i32")
600        || is_primitive(ty, "i64")
601        || is_primitive(ty, "bool")
602        || is_tuple_u32_u32(ty);
603
604    if is_value_slot {
605        return quote! {
606            // SAFETY: slot #idx is ValueOutput - type match guaranteed by #[tee_command]
607            let mut __p_out = unsafe { params.#idx.as_value()? };
608            let __repr_out = <#ty as ::consortium_tee::TeeParam<#codec>>::into_tee_param(&__ret_val)
609                .map_err(|_| ::optee_utee::Error::new(::optee_utee::ErrorKind::BadParameters))?;
610            let ::consortium_tee::TeeParamSlot::Primitive { a: __pa_out, b: __pb_out } = __repr_out
611                else { unreachable!() };
612            __p_out.set_a(__pa_out);
613            __p_out.set_b(__pb_out);
614        };
615    }
616
617    // Vec<u8> and T: TeeParam — Memref slot.
618    if is_vec_u8(ty) || is_tee_param_path(ty) {
619        return quote! {
620            // SAFETY: slot #idx is MemrefOutput - type match guaranteed by #[tee_command]
621            let mut __p_out = unsafe { params.#idx.as_memref()? };
622            let __repr_out = <#ty as ::consortium_tee::TeeParam<#codec>>::into_tee_param(&__ret_val)
623                .map_err(|_| ::optee_utee::Error::new(::optee_utee::ErrorKind::BadParameters))?;
624            let ::consortium_tee::TeeParamSlot::Serialized(ref __out_bytes) = __repr_out
625                else { unreachable!() };
626            __p_out.buffer()[..__out_bytes.len()].copy_from_slice(__out_bytes.as_slice());
627        };
628    }
629
630    quote! { compile_error!("Unsupported TEE return type"); }
631}
632
633// ── CA-side pack / read-back code generation ─────────────────────────────────
634
635/// Returns `(pack_stmt, optional_post_invoke_readback)`.
636fn gen_pack_ca(
637    slot_ident: &Ident,
638    pat: &syn::Pat,
639    ty: &syn::Type,
640    slot: usize,
641    codec: &TokenStream2,
642) -> (TokenStream2, Option<TokenStream2>) {
643    // Priority 1: &[u8]
644    if is_slice_ref(ty, false) {
645        return (
646            quote! { let #slot_ident = ::optee_teec::ParamTmpRef::new_input(#pat); },
647            None,
648        );
649    }
650
651    // Priority 2: &mut [u8]
652    if is_slice_ref(ty, true) {
653        return (
654            quote! { let #slot_ident = ::optee_teec::ParamTmpRef::new_inout(#pat); },
655            None,
656        );
657    }
658
659    // Priority 3: &mut u32 / &mut i32 / &mut u64 (ValueInout)
660    if let Some(scalar) = mut_scalar_inner(ty) {
661        let slot_idx = syn::Index::from(slot);
662        let (pack, read_back) = match scalar {
663            "u32" => (
664                quote! { let #slot_ident = ::optee_teec::ParamValue::new(*#pat, 0, ::optee_teec::ParamType::ValueInout); },
665                quote! { *#pat = _operation.parameters().#slot_idx.a(); },
666            ),
667            "i32" => (
668                quote! { let #slot_ident = ::optee_teec::ParamValue::new(*#pat as u32, 0, ::optee_teec::ParamType::ValueInout); },
669                quote! { *#pat = _operation.parameters().#slot_idx.a() as i32; },
670            ),
671            "u64" => (
672                quote! { let #slot_ident = ::optee_teec::ParamValue::new(*#pat as u32, (*#pat >> 32) as u32, ::optee_teec::ParamType::ValueInout); },
673                quote! {
674                    *#pat = (_operation.parameters().#slot_idx.b() as u64) << 32
675                        | _operation.parameters().#slot_idx.a() as u64;
676                },
677            ),
678            _ => unreachable!(),
679        };
680        return (pack, Some(read_back));
681    }
682
683    // Priority 4: &mut T: TeeParam  (MemrefInout)
684    if is_mut_tee_param_ref(ty) {
685        let inner_ty = if let syn::Type::Reference(r) = ty {
686            r.elem.as_ref()
687        } else {
688            unreachable!()
689        };
690        let buf_ident = Ident::new(&format!("__inout_buf_{}", slot), Span::call_site());
691        let repr_ident = Ident::new(&format!("__repr_inout_{}", slot), Span::call_site());
692        let src_ident = Ident::new(&format!("__src_inout_{}", slot), Span::call_site());
693        let slot_idx = syn::Index::from(slot);
694        let pack = quote! {
695            let mut #buf_ident = vec![0u8; <#inner_ty as ::consortium_tee::TeeParam<#codec>>::MAX_SIZE];
696            let #repr_ident = <#inner_ty as ::consortium_tee::TeeParam<#codec>>::into_tee_param(#pat)
697                .map_err(|_| ::consortium_tee::TeeError::ParamMismatch)?;
698            let ::consortium_tee::TeeParamSlot::Serialized(ref #src_ident) = #repr_ident
699                else { unreachable!() };
700            #buf_ident[..#src_ident.len()].copy_from_slice(#src_ident.as_slice());
701            let #slot_ident = ::optee_teec::ParamTmpRef::new_inout(&mut #buf_ident);
702        };
703        let read_back = quote! {
704            *#pat = {
705                let __updated_size = _operation.parameters().#slot_idx.updated_size();
706                <#inner_ty as ::consortium_tee::TeeParam<#codec>>::from_tee_param(
707                    ::consortium_tee::TeeParamSlot::Serialized(&#buf_ident[..__updated_size])
708                ).map_err(|_| ::consortium_tee::TeeError::ParamMismatch)?
709            };
710        };
711        return (pack, Some(read_back));
712    }
713
714    // Priority 5: Value-slot owned types.
715    let is_value_slot = is_primitive(ty, "u8")
716        || is_primitive(ty, "u16")
717        || is_primitive(ty, "u32")
718        || is_primitive(ty, "u64")
719        || is_primitive(ty, "i8")
720        || is_primitive(ty, "i16")
721        || is_primitive(ty, "i32")
722        || is_primitive(ty, "i64")
723        || is_primitive(ty, "bool")
724        || is_tuple_u32_u32(ty);
725
726    if is_value_slot {
727        let repr_ident = Ident::new(&format!("__repr_{}", slot), Span::call_site());
728        let pa_ident = Ident::new(&format!("__pa_{}", slot), Span::call_site());
729        let pb_ident = Ident::new(&format!("__pb_{}", slot), Span::call_site());
730        let pack = quote! {
731            let #repr_ident = <#ty as ::consortium_tee::TeeParam<#codec>>::into_tee_param(&#pat)
732                .map_err(|_| ::consortium_tee::TeeError::ParamMismatch)?;
733            let ::consortium_tee::TeeParamSlot::Primitive { a: #pa_ident, b: #pb_ident } = #repr_ident
734                else { unreachable!() };
735            let #slot_ident = ::optee_teec::ParamValue::new(#pa_ident, #pb_ident, ::optee_teec::ParamType::ValueInput);
736        };
737        return (pack, None);
738    }
739
740    // Priority 6: Vec<u8> and T: TeeParam — Memref slot.
741    if is_vec_u8(ty) || is_tee_param_path(ty) {
742        let repr_ident = Ident::new(&format!("__repr_{}", slot), Span::call_site());
743        let bytes_ident = Ident::new(&format!("__bytes_{}", slot), Span::call_site());
744        let pack = quote! {
745            let #repr_ident = <#ty as ::consortium_tee::TeeParam<#codec>>::into_tee_param(&#pat)
746                .map_err(|_| ::consortium_tee::TeeError::ParamMismatch)?;
747            let ::consortium_tee::TeeParamSlot::Serialized(ref #bytes_ident) = #repr_ident
748                else { unreachable!() };
749            let #slot_ident = ::optee_teec::ParamTmpRef::new_input(#bytes_ident.as_slice());
750        };
751        return (pack, None);
752    }
753
754    (
755        quote! {
756            let #slot_ident = ::optee_teec::ParamNone;
757            compile_error!("Unsupported TEE parameter type");
758        },
759        None,
760    )
761}
762
763/// Emits the declaration(s) that reserve a slot for the return value on the CA side.
764fn gen_output_pack_ca(
765    slot_ident: &Ident,
766    ty: &syn::Type,
767    slot: usize,
768    codec: &TokenStream2,
769) -> TokenStream2 {
770    let is_value_slot = is_primitive(ty, "u8")
771        || is_primitive(ty, "u16")
772        || is_primitive(ty, "u32")
773        || is_primitive(ty, "u64")
774        || is_primitive(ty, "i8")
775        || is_primitive(ty, "i16")
776        || is_primitive(ty, "i32")
777        || is_primitive(ty, "i64")
778        || is_primitive(ty, "bool")
779        || is_tuple_u32_u32(ty);
780
781    if is_value_slot {
782        return quote! { let #slot_ident = ::optee_teec::ParamValue::new(0, 0, ::optee_teec::ParamType::ValueOutput); };
783    }
784
785    if is_vec_u8(ty) || is_tee_param_path(ty) {
786        let buf_ident = Ident::new(&format!("__out_buf_{}", slot), Span::call_site());
787        return quote! {
788            let mut #buf_ident = vec![0u8; <#ty as ::consortium_tee::TeeParam<#codec>>::MAX_SIZE];
789            let #slot_ident = ::optee_teec::ParamTmpRef::new_output(&mut #buf_ident);
790        };
791    }
792
793    quote! { compile_error!("Unsupported TEE return type for output parameter"); }
794}
795
796/// Emits the expression that reads the return value back from `_operation` on the CA side.
797fn gen_output_read_ca(slot: usize, ty: &syn::Type, codec: &TokenStream2) -> TokenStream2 {
798    let slot_idx = syn::Index::from(slot);
799    let buf_ident = Ident::new(&format!("__out_buf_{}", slot), Span::call_site());
800
801    let is_value_slot = is_primitive(ty, "u8")
802        || is_primitive(ty, "u16")
803        || is_primitive(ty, "u32")
804        || is_primitive(ty, "u64")
805        || is_primitive(ty, "i8")
806        || is_primitive(ty, "i16")
807        || is_primitive(ty, "i32")
808        || is_primitive(ty, "i64")
809        || is_primitive(ty, "bool")
810        || is_tuple_u32_u32(ty);
811
812    if is_value_slot {
813        return quote! {
814            {
815                let __v = _operation.parameters().#slot_idx;
816                <#ty as ::consortium_tee::TeeParam<#codec>>::from_tee_param(
817                    ::consortium_tee::TeeParamSlot::Primitive { a: __v.a(), b: __v.b() }
818                ).map_err(|_| ::consortium_tee::TeeError::ParamMismatch)?
819            }
820        };
821    }
822
823    if is_vec_u8(ty) || is_tee_param_path(ty) {
824        // `__out_buf_N` was declared by gen_output_pack_ca. After invoke_command the
825        // TA has written into it; the reconstructed parameter's updated_size() reflects
826        // how many bytes were actually written.
827        return quote! {
828            {
829                let __updated_size = _operation.parameters().#slot_idx.updated_size();
830                <#ty as ::consortium_tee::TeeParam<#codec>>::from_tee_param(
831                    ::consortium_tee::TeeParamSlot::Serialized(&#buf_ident[..__updated_size])
832                ).map_err(|_| ::consortium_tee::TeeError::ParamMismatch)?
833            }
834        };
835    }
836
837    quote! { compile_error!("Unsupported TEE return type") }
838}