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}