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}