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}