Skip to main content

mcu_comms_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::quote;
4use syn::{Data, DeriveInput, Fields, Type};
5
6#[proc_macro_attribute]
7pub fn payload(_attr: TokenStream, item: TokenStream) -> TokenStream {
8    let input: DeriveInput = syn::parse(item).unwrap();
9    let name = &input.ident;
10
11    let field_sizes = field_sizes(&input.data);
12
13    let expanded = quote! {
14        #[derive(
15            ::mcu_comms::serde::Serialize,
16            ::mcu_comms::serde::Deserialize,
17        )]
18        #[serde(crate = "::mcu_comms::serde")]
19        #input
20
21        impl ::mcu_comms::payload_size::MaxSize for #name {
22            const MAX_SIZE: usize = 0 #(+ #field_sizes)*;
23        }
24        impl ::mcu_comms::payload_size::MaxPayloadSize for #name {
25            const FRAME_SIZE: usize = <#name as ::mcu_comms::payload_size::MaxSize>::MAX_SIZE
26                + ::mcu_comms::aesccm::HEADER_SIZE
27                + ::mcu_comms::aesccm::TAG_SIZE;
28            type FrameBuf = [u8; Self::FRAME_SIZE];
29            fn new_buf() -> Self::FrameBuf {
30                [0_u8; Self::FRAME_SIZE]
31            }
32        }
33        impl ::mcu_comms::payload_size::Payload for #name {}
34    };
35    expanded.into()
36}
37
38fn field_sizes(data: &syn::Data) -> Vec<TokenStream2> {
39    let field_sizes = match data {
40        Data::Struct(data) => match &data.fields {
41            Fields::Named(fields) => fields
42                .named
43                .iter()
44                .map(|f| {
45                    let ty = &f.ty;
46                    panic_if_uisize(&ty);
47                    quote! { <#ty as ::mcu_comms::payload_size::MaxSize>::MAX_SIZE }
48                })
49                .collect(),
50            Fields::Unnamed(fields) => fields
51                .unnamed
52                .iter()
53                .map(|f| {
54                    let ty = &f.ty;
55                    panic_if_uisize(&ty);
56                    quote! {<#ty as ::mcu_comms::payload_size::MaxSize>::MAX_SIZE}
57                })
58                .collect(),
59            Fields::Unit => vec![],
60        },
61        Data::Enum(data) => {
62            let variant_sizes: Vec<TokenStream2> = data
63                .variants
64                .iter()
65                .map(|v| {
66                    let field_sizes: Vec<TokenStream2> = v
67                        .fields
68                        .iter()
69                        .map(|f| {
70                            let ty = &f.ty;
71                            panic_if_uisize(&ty);
72                            quote! { <#ty as ::mcu_comms::payload_size::MaxSize>::MAX_SIZE }
73                        })
74                        .collect();
75                    quote! { (0 #(+ #field_sizes)*) }
76                })
77                .collect();
78
79            vec![quote! {{
80                const fn max(a: usize, b: usize) -> usize {
81                    if a > b { a } else { b }
82                }
83                let mut m = 0;
84                #( m = max(m, #variant_sizes); )*
85                m + 5
86            }}]
87        }
88        _ => panic!("Payload does not support this type"),
89    };
90    field_sizes
91}
92
93fn panic_if_uisize(ty: &Type) {
94    if let syn::Type::Path(p) = ty {
95        if let Some(seg) = p.path.segments.last() {
96            if seg.ident == "usize" || seg.ident == "isize" {
97                panic!(
98                    "usize/isize are not portable across differing pointer-width targets in payload structs — use a fixed-width type like u32 or u64 instead"
99                );
100            }
101        }
102    }
103}