apdu_derive/
lib.rs

1//! Implementation of procedural macro for apdu crate.
2//! By deriving apdu_derive::Response, you can convert from APDU raw response to an Enum easily.
3//! Macro interface is inspired by thiserror crate.
4//!
5//! ## Examples
6//! Here is a simple example to derive Response:
7//! ```rust
8//! #[derive(apdu_derive::Response)]
9//! enum Response<'a> {
10//!     #[apdu(0x90, 0x00)]
11//!     Ok(&'a [u8]),
12//!
13//!     #[apdu(0x60..=0x69, _)]
14//!     #[apdu(0x12, 0x34)]
15//!     NotOk,
16//!
17//!     #[apdu(_, _)]
18//!     Unknown(u8, u8),
19//! }
20//! ```
21//!
22//! This is equivalent to implementing this:
23//! ```rust
24//! enum Response<'a> {
25//!     Ok(&'a [u8]),
26//!     NotOk,
27//!     Unknown(u8, u8),
28//! }
29//!
30//! impl<'a> From<apdu_core::Response<'a>> for Response<'a> {
31//!     fn from(response: apdu_core::Response<'a>) -> Self {
32//!         match response.trailer {
33//!             (0x90, 0x00) => Self::Ok(response.payload),
34//!             (0x60..=0x69, _) => Self::NotOk,
35//!             (_, _) => Self::Unknown(response.trailer.0, response.trailer.1),
36//!         }
37//!     }
38//! }
39//! ```
40//!
41//! Also you can combine them with thiserror derive:
42//! ```rust
43//! #[derive(Debug, apdu_derive::Response, thiserror::Error)]
44//! enum Response {
45//!     #[apdu(0x60..=0x69, _)]
46//!     #[error("not ok!")]
47//!     NotOk,
48//!
49//!     #[apdu(_, _)]
50//!     #[error("unknown: {0:#X} {1:#X}")]
51//!     Unknown(u8, u8),
52//! }
53//! ```
54//!
55//! Optionally you can select what to inject to the fields:
56//! ```rust
57//! #[derive(Debug, apdu_derive::Response, thiserror::Error)]
58//! enum Response {
59//!     #[apdu(0x63, 0xC0..=0xCF)]
60//!     #[error("verify failed: {0} tries left")]
61//!     VerifyFailed(#[sw2] #[mask(0x0F)] u8),
62//!
63//!     #[apdu(_, _)]
64//!     #[error("unknown: {0:#X} {1:#X}")]
65//!     Unknown(u8, u8),
66//! }
67//! ```
68
69extern crate proc_macro;
70
71use proc_macro::TokenStream;
72use quote::{quote, ToTokens};
73use syn::{parse_macro_input, Data, DeriveInput, Fields};
74
75#[proc_macro_derive(Response, attributes(apdu, sw1, sw2, payload, mask))]
76pub fn derive_response(input: TokenStream) -> TokenStream {
77    let item = parse_macro_input!(input as DeriveInput);
78    let output: proc_macro2::TokenStream = match item.data {
79        Data::Enum(d) => {
80            let ty = &item.ident;
81            let gen = &item.generics;
82            let gen_suffix = match gen.into_token_stream().is_empty() {
83                true => quote! {},
84                _ => quote! { ::#gen },
85            };
86
87            let arms = d
88                .variants
89                .iter()
90                .flat_map(|variant| {
91                    variant
92                        .attrs
93                        .iter()
94                        .filter(|attr| attr.path.is_ident("apdu"))
95                        .map(move |attr| (variant, attr))
96                })
97                .map(|(variant, attr)| {
98                    let ident = &variant.ident;
99                    let tokens = &attr.tokens;
100                    let left = match tokens.is_empty() {
101                        true => quote! { _ },
102                        _ => tokens.clone(),
103                    };
104
105                    let fields = match &variant.fields {
106                        Fields::Named(f) => f.named.iter().collect(),
107                        Fields::Unnamed(f) => f.unnamed.iter().collect(),
108                        Fields::Unit => vec![],
109                    };
110
111                    let right = if fields.iter().any(|f| !f.attrs.is_empty()) {
112                        let values = fields.iter().map(|f| {
113                            let mask = if let Some(attr) =
114                                f.attrs.iter().find(|a| a.path.is_ident("mask"))
115                            {
116                                let m = &attr.tokens;
117
118                                quote! { & #m }
119                            } else {
120                                quote! {}
121                            };
122
123                            if f.attrs.iter().any(|a| a.path.is_ident("sw1")) {
124                                quote! { (response.trailer.0 #mask).into(), }
125                            } else if f.attrs.iter().any(|a| a.path.is_ident("sw2")) {
126                                quote! { (response.trailer.1 #mask).into(), }
127                            } else if f.attrs.iter().any(|a| a.path.is_ident("payload")) {
128                                quote! { response.payload.into(), }
129                            } else {
130                                quote! {}
131                            }
132                        });
133
134                        quote! { #ty #gen_suffix::#ident(#(#values)*) }
135                    } else if variant.fields.is_empty() {
136                        quote! { #ty #gen_suffix::#ident }
137                    } else if variant.fields.len() == 1 {
138                        quote! { #ty #gen_suffix::#ident(response.payload) }
139                    } else if variant.fields.len() == 2 {
140                        quote! { #ty #gen_suffix::#ident(response.trailer.0, response.trailer.1) }
141                    } else {
142                        panic!("unsupported type of fields found")
143                    };
144
145                    quote! { #left => #right, }
146                });
147
148            quote! {
149                impl<'a> ::std::convert::From<::apdu_core::Response<'a>> for #ty #gen {
150                    fn from(response: ::apdu_core::Response<'a>) -> Self {
151                        let (sw1, sw2) = response.trailer;
152
153                        match (sw1, sw2) {
154                            #(#arms)*
155                        }
156                    }
157                }
158
159                impl<'a> ::std::convert::From<::apdu_core::Error<'a>> for #ty #gen {
160                    fn from(error: ::apdu_core::Error<'a>) -> Self {
161                        error.response.into()
162                    }
163                }
164
165                impl<'a> ::std::convert::From<&'a [u8]> for #ty #gen {
166                    fn from(bytes: &'a [u8]) -> Self {
167                        ::apdu_core::Response::from(bytes).into()
168                    }
169                }
170            }
171        }
172        _ => panic!("deriving for Enum is only supported"),
173    };
174
175    proc_macro::TokenStream::from(output)
176}