cbor_macro/lib.rs
1//! Macros for entering CBOR in diagnostic notation (EDN) or pretty printed format in Rust source
2//! as binary constants
3//!
4//! This is useful for test vectors, or when data structures that are handed out do not not depend
5//! on runtime data.
6
7/// Create a CBOR slice out of diagnostic notation (EDN)
8///
9/// It requires that the EDN also complies with Rust's tokenization rules. This is the case for
10/// many simple expressions:
11///
12/// ```
13/// use cbor_macro::cbor;
14/// let _ = cbor!([1, 2, "three"]);
15/// let _ = cbor!({-1: 4, "x" "y": 1(1234567890), []: 3.14, simple(0): 1});
16/// ```
17///
18/// Not all EDN is valid by Rust's tokenizer rules, including "complex" comments and some forms of
19/// string escaping:
20///
21/// ```compile_fail
22/// # use cbor_macro::cbor;
23/// let _ = cbor!([1 / Funny comment :-) /, 2]);
24/// ```
25/// ```compile_fail
26/// # use cbor_macro::cbor;
27/// let _ = cbor!("zero \u0000");
28/// ```
29///
30/// Also, there may exist EDN values that, while being both valid EDN and tokenizable Rust, lose
31/// information in the course of tokenization, and are reconstructed wrong. No working examples
32/// were found so far; the most "promising" candidates are line breaks within a string, exotic
33/// escapes, and application oriented literals.
34///
35/// For such cases, the [`cbo!`] macro provides a solution.
36///
37/// The output of the macro is a [cboritem::CborItem], which is a newtype around a slice. Thus,
38/// when storing the output of `cbor!` in static memory (as it is often useful), define a const to
39/// avoid the added indirection and static memory usage of the extra pointer:
40///
41/// ```
42/// # use cbor_macro::cbor;
43/// fn aead_feed_ad(data: &[u8]) { /* ... */ }
44/// use cboritem::CborItem;
45/// const ALGS: CborItem<'static> = cbor!([10, null, -8, -27]);
46/// aead_feed_ad(&ALGS);
47/// ```
48///
49/// Note that as always with macros, any style of parentheses around the expression is discarded,
50/// and not reported. Thus, while `cbor![1]` is legal, it is still just the number `1`, not an
51/// array containing 1.
52#[proc_macro]
53pub fn cbor(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
54 // FIXME: If any char is in input, raise a warning -- those just don't work that way.
55
56 let reconstructed = format!("{}", &input);
57 let parsed = cbor_diag::parse_diag(reconstructed).expect("Error parsing CBOR literal");
58 let binary = parsed.to_bytes();
59
60 // Once span join is stable, we could switch over to that. It'd have upsides and downsides --
61 // join might fail (but probably only when macros are involved), and concatenating the source
62 // tokens relies on no whitespace being significant in EDN (which AIU is OK).
63
64 let input_spans: Option<Vec<_>> = input
65 .into_iter()
66 .map(|tree| tree.span().source_text())
67 .collect();
68
69 if let Some(parts) = input_spans {
70 let joint = parts.join("");
71
72 let source_parsed =
73 cbor_diag::parse_diag(joint).expect("CBOR from source is invalid, but stringified token tree was valid. This is possibly a bug in cbor-macro. As a workaround, you can use the cbo!(R#\"...\"#) macro.");
74 if source_parsed.to_bytes() != binary {
75 panic!("CBOR got misrepresented by Rust tokenization and re-stringification. Use cbo!(r#\"...\"#) instead.");
76 }
77 } else {
78 // Not sure what level of error is appropriate here
79 panic!("Some parts of the CBOR expression were not found in source files, CBOR could not be verified. Use cbo!(r#\"...\") instead.")
80 }
81
82 proc_macro::TokenStream::from(quote::quote! {
83 // UNSAFE: We just constructed this as a CBOR item
84 unsafe { cboritem::CborItem::new(&[ #(#binary),* ])
85 } })
86}
87
88/// Create a CBOR slice out of the diagnostic notation (EDN) inside the given string.
89///
90/// This macro is useful for any EDN that does not happen to be compatible with Rust's
91/// tokenization.
92///
93/// This is called `cbo` because it is most useful with r-strings:
94///
95/// ```
96/// use cbor_macro::cbo;
97/// let parsed = cbo!(r#" ['CD', / Sad comment :-( / 123] "#);
98/// ```
99#[proc_macro]
100pub fn cbo(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
101 let input = syn::parse_macro_input!(input as syn::LitStr);
102
103 let parsed = cbor_diag::parse_diag(input.value()).expect("Error parsing CBOR literal");
104 let binary = parsed.to_bytes();
105 proc_macro::TokenStream::from(quote::quote! {
106 // UNSAFE: We just constructed this as a CBOR item
107 unsafe { cboritem::CborItem::new(&[ #(#binary),* ])
108 } })
109}
110
111/// Create a CBOR slice out of the pretty-printed hexadecimal CBOR
112///
113/// This macro is useful for when precise encoding is required exceeding what EDN can provide.
114/// (Note that this is mainly a tools issue, with this crate not understanding all of EDN that
115/// would allow great control: EDN itself can express well-formed CBOR.)
116///
117/// ```
118/// use cbor_macro::pretty;
119/// let parsed = pretty!("
120/// 83 # array(3)
121/// 01 # unsigned(1)
122/// 02 # unsigned(2)
123/// 03 # unsigned(3)
124/// ");
125/// ```
126///
127/// This macro validates that the hex decoded data is really well-formed, and transfers that
128/// information to the program by marking it as a [cboritem::CborItem].
129///
130/// ```compile_fail
131/// # use cbor_macro::pretty;
132/// let _ = pretty!("01 01 01");
133/// ```
134#[proc_macro]
135pub fn pretty(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
136 let hex = syn::parse_macro_input!(input as syn::LitStr);
137
138 let parsed = cbor_diag::parse_hex(hex.value()).expect("CBOR is not well-formed");
139 let binary = parsed.to_bytes();
140 proc_macro::TokenStream::from(quote::quote! {
141 // UNSAFE: We just constructed this as a CBOR item
142 unsafe { cboritem::CborItem::new(&[ #(#binary),* ])
143 } })
144}
145
146/// Create a slice out of a hex string that uses the convention of pretty CBOR
147///
148/// Unlike [`pretty!()`], this does not check whether the data is valid CBOR, and returns a slice
149/// instead.
150///
151/// The function is essentially equivalent to the [hex-literal crate's `hex!` macro], but allows
152/// comments in the style of CBOR's pretty printing.
153///
154/// ```
155/// # use cbor_macro::pretty_bytes;
156/// const PACKET: &'static [u8] = pretty_bytes!(r#"
157/// ff 63 48 8a 60 # application dependent header
158/// 65 # text(5)
159/// 68656C6C6F # "hello"
160/// "#);
161/// ```
162///
163/// [hex-literal crate's `hex!` macro]: https://docs.rs/hex-literal
164#[proc_macro]
165pub fn pretty_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
166 let hex = syn::parse_macro_input!(input as syn::LitStr);
167
168 let comment = regex::RegexBuilder::new("#.*$")
169 .multi_line(true)
170 .build()
171 .unwrap();
172 let space = regex::Regex::new("[[:space:]]").unwrap();
173
174 let hex = hex.value();
175 let hex = comment.replace_all(&hex, "");
176 let hex = space.replace_all(&hex, "");
177 let hex: &str = hex.as_ref();
178
179 let binary = hex::decode(hex).expect("Invalid hex");
180 proc_macro::TokenStream::from(quote::quote! {
181 {
182 let result = &[ #(#binary),* ];
183 result
184 }
185 })
186}