Skip to main content

sk_cbor/
macros.rs

1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Convenience macros for working with CBOR values.
16
17use crate::values::Value;
18use alloc::vec;
19use core::cmp::Ordering;
20use core::iter::Peekable;
21
22/// This macro generates code to extract multiple values from a `Vec<(Value, Value)>` at once
23/// in an optimized manner, consuming the input vector.
24///
25/// It takes as input a `Vec` as well as a list of identifiers and keys, and generates code
26/// that assigns the corresponding values to new variables using the given identifiers. Each of
27/// these variables has type `Option<Value>`, to account for the case where keys aren't found.
28///
29/// **Important:** Keys passed to the `destructure_cbor_map!` macro **must be sorted** in increasing
30/// order. If not, the algorithm can yield incorrect results, such a assigning `None` to a variable
31/// even if the corresponding key existed in the map. **No runtime checks** are made for this in the
32/// `destructure_cbor_map!` macro, in order to avoid overhead at runtime. However, assertions that
33/// keys are sorted are added in `cfg(test)` mode, so that unit tests can verify ahead of time that
34/// the keys are indeed sorted. This macro is therefore **not suitable for dynamic keys** that can
35/// change at runtime.
36///
37/// Example usage:
38///
39/// ```rust
40/// # extern crate alloc;
41/// # use sk_cbor::destructure_cbor_map;
42/// #
43/// # fn main() {
44/// #     let map = alloc::vec::Vec::new();
45/// destructure_cbor_map! {
46///     let {
47///         1 => x,
48///         "key" => y,
49///     } = map;
50/// }
51/// # }
52/// ```
53#[macro_export]
54macro_rules! destructure_cbor_map {
55    ( let { $( $key:expr => $variable:ident, )+ } = $map:expr; ) => {
56        // A pre-requisite for this algorithm to work is that the keys to extract from the map are
57        // sorted - the behavior is unspecified if the keys are not sorted.
58        // Therefore, in test mode we add assertions that the keys are indeed sorted.
59        #[cfg(test)]
60        $crate::assert_sorted_keys!($( $key, )+);
61
62        use $crate::values::{IntoCborValue, Value};
63        use $crate::macros::destructure_cbor_map_peek_value;
64
65        // This algorithm first converts the map into a peekable iterator - whose items are sorted
66        // in strictly increasing order of keys. Then, the repeated calls to the "peek value"
67        // helper function will consume this iterator and yield values (or `None`) when reaching
68        // the keys to extract.
69        //
70        // This is where the pre-requisite that keys to extract are sorted is important: the
71        // algorithm does a single linear scan over the iterator and therefore keys to extract have
72        // to come in the same order (i.e. sorted).
73        let mut it = $map.into_iter().peekable();
74        $(
75        let $variable: Option<Value> = destructure_cbor_map_peek_value(&mut it, $key.into_cbor_value());
76        )+
77    };
78}
79
80/// This function is an internal detail of the `destructure_cbor_map!` macro, but has public
81/// visibility so that users of the macro can use it.
82///
83/// Given a peekable iterator of key-value pairs sorted in strictly increasing key order and a
84/// needle key, this function consumes all items whose key compares less than or equal to the
85/// needle, and returns `Some(value)` if the needle was present as the key in the iterator and
86/// `None` otherwise.
87///
88/// The logic is separated into its own function to reduce binary size, as otherwise the logic
89/// would be inlined for every use case. As of June 2020, this saves ~40KB of binary size for the
90/// CTAP2 application of OpenSK.
91pub fn destructure_cbor_map_peek_value(
92    it: &mut Peekable<vec::IntoIter<(Value, Value)>>,
93    needle: Value,
94) -> Option<Value> {
95    loop {
96        let item = it.peek()?;
97        let key: &Value = &item.0;
98        match key.cmp(&needle) {
99            Ordering::Less => {
100                it.next();
101            }
102            Ordering::Equal => {
103                let value: Value = it.next().unwrap().1;
104                return Some(value);
105            }
106            Ordering::Greater => return None,
107        }
108    }
109}
110
111/// Assert that the keys in a vector of key-value pairs are in canonical order.
112#[macro_export]
113macro_rules! assert_sorted_keys {
114    // Last key
115    ( $key:expr, ) => {
116    };
117
118    ( $key1:expr, $key2:expr, $( $keys:expr, )* ) => {
119        {
120            use $crate::values::{IntoCborValue, Value};
121            let k1: Value = $key1.into_cbor_value();
122            let k2: Value = $key2.into_cbor_value();
123            assert!(
124                k1 < k2,
125                "{:?} < {:?} failed. The destructure_cbor_map! macro requires keys in sorted order.",
126                k1,
127                k2,
128            );
129        }
130        $crate::assert_sorted_keys!($key2, $( $keys, )*);
131    };
132}
133
134/// Creates a CBOR Value of type Map with the specified key-value pairs.
135///
136/// Keys and values are expressions and converted into CBOR Keys and Values.
137/// The syntax for these pairs is `key_expression => value_expression,`.
138/// Keys do not have to be sorted.
139///
140/// # Panics
141///
142/// You may not call this function with identical keys in its argument.
143///
144/// Example usage:
145///
146/// ```rust
147/// # extern crate alloc;
148/// # use sk_cbor::cbor_map;
149/// let map = cbor_map! {
150///   0x01 => false,
151///   "02" => -3,
152/// };
153/// ```
154#[macro_export]
155macro_rules! cbor_map {
156    // trailing comma case
157    ( $( $key:expr => $value:expr, )+ ) => {
158        cbor_map! ( $($key => $value),+ )
159    };
160
161    ( $( $key:expr => $value:expr ),* ) => {
162        {
163            // The import is unused if the list is empty.
164            #[allow(unused_imports)]
165            use $crate::values::IntoCborValue;
166            let mut _map = ::alloc::vec::Vec::new();
167            $(
168                _map.push(($key.into_cbor_value(), $value.into_cbor_value()));
169            )*
170            $crate::values::Value::map(_map)
171        }
172    };
173}
174
175/// Creates a CBOR Value of type Map with key-value pairs where values can be Options.
176///
177/// Keys and values are expressions and converted into CBOR Keys and Value Options.
178/// The map entry is included iff the Value is not an Option or Option is Some.
179/// The syntax for these pairs is `key_expression => value_expression,`.
180/// Duplicate keys will lead to invalid CBOR, i.e. writing these values fails.
181/// Keys do not have to be sorted.
182///
183/// Example usage:
184///
185/// ```rust
186/// # extern crate alloc;
187/// # use sk_cbor::cbor_map_options;
188/// let missing_value: Option<bool> = None;
189/// let map = cbor_map_options! {
190///   0x01 => Some(false),
191///   "02" => -3,
192///   "not in map" => missing_value,
193/// };
194/// ```
195#[macro_export]
196macro_rules! cbor_map_options {
197    // trailing comma case
198    ( $( $key:expr => $value:expr, )+ ) => {
199        cbor_map_options! ( $($key => $value),+ )
200    };
201
202    ( $( $key:expr => $value:expr ),* ) => {
203        {
204            // The import is unused if the list is empty.
205            #[allow(unused_imports)]
206            use $crate::values::{IntoCborValue, IntoCborValueOption};
207            let mut _map = ::alloc::vec::Vec::<(_, $crate::values::Value)>::new();
208            $(
209            {
210                let opt: Option<$crate::values::Value> = $value.into_cbor_value_option();
211                if let Some(val) = opt {
212                    _map.push(($key.into_cbor_value(), val));
213                }
214            }
215            )*
216            $crate::values::Value::map(_map)
217        }
218    };
219}
220
221/// Creates a CBOR Value of type Map from a Vec<(Value, Value)>.
222#[macro_export]
223macro_rules! cbor_map_collection {
224    ( $tree:expr ) => {{ $crate::values::Value::map($tree) }};
225}
226
227/// Creates a CBOR Value of type Array with the given elements.
228///
229/// Elements are expressions and converted into CBOR Values. Elements are comma-separated.
230///
231/// Example usage:
232///
233/// ```rust
234/// # extern crate alloc;
235/// # use sk_cbor::cbor_array;
236/// let array = cbor_array![1, "2"];
237/// ```
238#[macro_export]
239macro_rules! cbor_array {
240    // trailing comma case
241    ( $( $value:expr, )+ ) => {
242        cbor_array! ( $($value),+ )
243    };
244
245    ( $( $value:expr ),* ) => {
246        {
247            // The import is unused if the list is empty.
248            #[allow(unused_imports)]
249            use $crate::values::IntoCborValue;
250            $crate::values::Value::array(vec![ $( $value.into_cbor_value(), )* ])
251        }
252    };
253}
254
255/// Creates a CBOR Value of type Array from a Vec<Value>.
256#[macro_export]
257macro_rules! cbor_array_vec {
258    ( $vec:expr ) => {{
259        use $crate::values::IntoCborValue;
260        $crate::values::Value::array($vec.into_iter().map(|x| x.into_cbor_value()).collect())
261    }};
262}
263
264/// Creates a CBOR Value of type Simple with value true.
265#[macro_export]
266macro_rules! cbor_true {
267    ( ) => {
268        $crate::values::Value::bool_value(true)
269    };
270}
271
272/// Creates a CBOR Value of type Simple with value false.
273#[macro_export]
274macro_rules! cbor_false {
275    ( ) => {
276        $crate::values::Value::bool_value(false)
277    };
278}
279
280/// Creates a CBOR Value of type Simple with value null.
281#[macro_export]
282macro_rules! cbor_null {
283    ( ) => {
284        $crate::values::Value::null_value()
285    };
286}
287
288/// Creates a CBOR Value of type Simple with the undefined value.
289#[macro_export]
290macro_rules! cbor_undefined {
291    ( ) => {
292        $crate::values::Value::undefined()
293    };
294}
295
296/// Creates a CBOR Value of type Simple with the given bool value.
297#[macro_export]
298macro_rules! cbor_bool {
299    ( $x:expr ) => {
300        $crate::values::Value::bool_value($x)
301    };
302}
303
304/// Creates a CBOR Value of type Unsigned with the given numeric value.
305#[macro_export]
306macro_rules! cbor_unsigned {
307    ( $x:expr ) => {
308        $crate::values::Value::unsigned($x as u64)
309    };
310}
311
312/// Creates a CBOR Value of type Unsigned or Negative with the given numeric value.
313#[macro_export]
314macro_rules! cbor_int {
315    ( $x:expr ) => {
316        $crate::values::Value::integer($x as i64)
317    };
318}
319
320/// Creates a CBOR Value of type Text String with the given string.
321#[macro_export]
322macro_rules! cbor_text {
323    ( $x:expr ) => {
324        $crate::values::Value::text_string($x.into())
325    };
326}
327
328/// Creates a CBOR Value of type Byte String with the given slice or vector.
329#[macro_export]
330macro_rules! cbor_bytes {
331    ( $x:expr ) => {
332        $crate::values::Value::byte_string($x)
333    };
334}
335
336/// Creates a CBOR Value of type Tag with the given tag and object.
337#[macro_export]
338macro_rules! cbor_tagged {
339    ( $t:expr, $x: expr ) => {
340        $crate::values::Value::tag($t, $x)
341    };
342}
343
344/// Creates a CBOR Value of type Byte String with the given byte string literal.
345///
346/// Example usage:
347///
348/// ```rust
349/// # extern crate alloc;
350/// # use sk_cbor::cbor_bytes_lit;
351/// let byte_array = cbor_bytes_lit!(b"foo");
352/// ```
353#[macro_export]
354macro_rules! cbor_bytes_lit {
355    ( $x:expr ) => {
356        $crate::cbor_bytes!(($x as &[u8]).to_vec())
357    };
358}
359
360#[cfg(test)]
361mod test {
362    use super::super::values::Value;
363    use alloc::string::String;
364    use alloc::vec;
365    use alloc::vec::Vec;
366
367    #[test]
368    fn test_cbor_simple_values() {
369        assert_eq!(cbor_true!(), Value::bool_value(true));
370        assert_eq!(cbor_false!(), Value::bool_value(false));
371        assert_eq!(cbor_null!(), Value::null_value());
372        assert_eq!(cbor_undefined!(), Value::undefined());
373    }
374
375    #[test]
376    fn test_cbor_bool() {
377        assert_eq!(cbor_bool!(true), Value::bool_value(true));
378        assert_eq!(cbor_bool!(false), Value::bool_value(false));
379    }
380
381    #[test]
382    fn test_cbor_int_unsigned() {
383        assert_eq!(cbor_int!(0), Value::from(0));
384        assert_eq!(cbor_int!(1), Value::from(1));
385        assert_eq!(cbor_int!(123456), Value::from(123456));
386        assert_eq!(
387            cbor_int!(core::i64::MAX),
388            Value::from(core::i64::MAX as u64)
389        );
390    }
391
392    #[test]
393    fn test_cbor_int_negative() {
394        assert_eq!(cbor_int!(-1), Value::from(-1));
395        assert_eq!(cbor_int!(-123456), Value::from(-123456));
396        assert_eq!(cbor_int!(core::i64::MIN), Value::from(core::i64::MIN));
397    }
398
399    #[test]
400    fn test_cbor_int_literals() {
401        let a = cbor_array![
402            core::i64::MIN,
403            core::i32::MIN,
404            -123456,
405            -1,
406            0,
407            1,
408            123456,
409            core::i32::MAX,
410            core::i64::MAX,
411            core::u64::MAX,
412        ];
413        let b = Value::array(vec![
414            Value::from(core::i64::MIN),
415            Value::from(core::i32::MIN as i64),
416            Value::from(-123456),
417            Value::from(-1),
418            Value::from(0),
419            Value::from(1),
420            Value::from(123456),
421            Value::from(core::i32::MAX as u64),
422            Value::from(core::i64::MAX as u64),
423            Value::from(core::u64::MAX),
424        ]);
425        assert_eq!(a, b);
426    }
427
428    #[test]
429    fn test_cbor_array() {
430        let a = cbor_array![
431            -123,
432            456,
433            true,
434            cbor_null!(),
435            "foo",
436            b"bar",
437            cbor_array![],
438            cbor_array![0, 1],
439            cbor_map! {},
440            cbor_map! {2 => 3},
441        ];
442        let b = Value::array(vec![
443            Value::from(-123),
444            Value::from(456),
445            Value::bool_value(true),
446            Value::null_value(),
447            Value::from(String::from("foo")),
448            Value::from(b"bar".to_vec()),
449            Value::array(Vec::new()),
450            Value::array(vec![Value::from(0), Value::from(1)]),
451            Value::map(Vec::new()),
452            Value::map([(Value::from(2), Value::from(3))].iter().cloned().collect()),
453        ]);
454        assert_eq!(a, b);
455    }
456
457    #[test]
458    fn test_cbor_array_vec_empty() {
459        let a = cbor_array_vec!(Vec::<bool>::new());
460        let b = Value::array(Vec::new());
461        assert_eq!(a, b);
462    }
463
464    #[test]
465    fn test_cbor_array_vec_int() {
466        let a = cbor_array_vec!(vec![1, 2, 3, 4]);
467        let b = Value::array(vec![
468            Value::from(1),
469            Value::from(2),
470            Value::from(3),
471            Value::from(4),
472        ]);
473        assert_eq!(a, b);
474    }
475
476    #[test]
477    fn test_cbor_array_vec_text() {
478        let a = cbor_array_vec!(vec!["a", "b", "c"]);
479        let b = Value::array(vec![
480            Value::from(String::from("a")),
481            Value::from(String::from("b")),
482            Value::from(String::from("c")),
483        ]);
484        assert_eq!(a, b);
485    }
486
487    #[test]
488    fn test_cbor_array_vec_bytes() {
489        let a = cbor_array_vec!(vec![b"a", b"b", b"c"]);
490        let b = Value::array(vec![
491            Value::from(b"a".to_vec()),
492            Value::from(b"b".to_vec()),
493            Value::from(b"c".to_vec()),
494        ]);
495        assert_eq!(a, b);
496    }
497
498    #[test]
499    fn test_cbor_map() {
500        let a = cbor_map! {
501            4 => 56,
502            5 => "foo",
503            6 => b"bar",
504            7 => cbor_array![],
505            8 => cbor_array![0, 1],
506            9 => cbor_map!{},
507            10 => cbor_map!{2 => 3},
508            -1 => -23,
509            b"bar" => cbor_null!(),
510            "foo" => true,
511        };
512        let b = Value::map(
513            [
514                (Value::from(4), Value::from(56)),
515                (Value::from(5), Value::from(String::from("foo"))),
516                (Value::from(6), Value::from(b"bar".to_vec())),
517                (Value::from(7), Value::array(Vec::new())),
518                (
519                    Value::from(8),
520                    Value::array(vec![Value::from(0), Value::from(1)]),
521                ),
522                (Value::from(9), Value::map(Vec::new())),
523                (
524                    Value::from(10),
525                    Value::map([(Value::from(2), Value::from(3))].iter().cloned().collect()),
526                ),
527                (Value::from(-1), Value::from(-23)),
528                (Value::from(b"bar".to_vec()), Value::null_value()),
529                (Value::from(String::from("foo")), Value::bool_value(true)),
530            ]
531            .iter()
532            .cloned()
533            .collect(),
534        );
535        assert_eq!(a, b);
536    }
537
538    #[test]
539    fn test_cbor_map_options() {
540        let a = cbor_map_options! {
541            4 => Some(56),
542            5 => "foo",
543            6 => Some(b"bar" as &[u8]),
544            7 => cbor_array![],
545            8 => Some(cbor_array![0, 1]),
546            9 => cbor_map!{},
547            10 => Some(cbor_map!{2 => 3}),
548            11 => None::<String>,
549            12 => None::<&str>,
550            13 => None::<Vec<u8>>,
551            14 => None::<&[u8]>,
552            15 => None::<bool>,
553            16 => None::<i32>,
554            17 => None::<i64>,
555            18 => None::<u64>,
556            -1 => -23,
557            b"bar" => Some(cbor_null!()),
558            "foo" => true,
559        };
560        let b = Value::map(
561            [
562                (Value::from(4), Value::from(56)),
563                (Value::from(5), Value::from(String::from("foo"))),
564                (Value::from(6), Value::from(b"bar".to_vec())),
565                (Value::from(7), Value::array(Vec::new())),
566                (
567                    Value::from(8),
568                    Value::array(vec![Value::from(0), Value::from(1)]),
569                ),
570                (Value::from(9), Value::map(Vec::new())),
571                (
572                    Value::from(10),
573                    Value::map([(Value::from(2), Value::from(3))].iter().cloned().collect()),
574                ),
575                (Value::from(-1), Value::from(-23)),
576                (Value::from(b"bar".to_vec()), Value::null_value()),
577                (Value::from(String::from("foo")), Value::bool_value(true)),
578            ]
579            .iter()
580            .cloned()
581            .collect(),
582        );
583        assert_eq!(a, b);
584    }
585
586    #[test]
587    fn test_cbor_map_collection_empty() {
588        let a = cbor_map_collection!(Vec::<(_, _)>::new());
589        let b = Value::map(Vec::new());
590        assert_eq!(a, b);
591    }
592
593    #[test]
594    fn test_cbor_map_collection_foo() {
595        let a = cbor_map_collection!(vec![(Value::from(2), Value::from(3))]);
596        let b = Value::map(vec![(Value::from(2), Value::from(3))]);
597        assert_eq!(a, b);
598    }
599
600    fn extract_map(cbor_value: Value) -> Vec<(Value, Value)> {
601        cbor_value.extract_map().unwrap()
602    }
603
604    #[test]
605    fn test_destructure_cbor_map_simple() {
606        let map = cbor_map! {
607            1 => 10,
608            2 => 20,
609        };
610
611        destructure_cbor_map! {
612            let {
613                1 => x1,
614                2 => x2,
615            } = extract_map(map);
616        }
617
618        assert_eq!(x1, Some(cbor_unsigned!(10)));
619        assert_eq!(x2, Some(cbor_unsigned!(20)));
620    }
621
622    #[test]
623    #[should_panic]
624    fn test_destructure_cbor_map_unsorted() {
625        let map = cbor_map! {
626            1 => 10,
627            2 => 20,
628        };
629
630        destructure_cbor_map! {
631            // The keys are not sorted here, which violates the precondition of
632            // destructure_cbor_map. An assertion should catch that and make the test panic.
633            let {
634                2 => _x2,
635                1 => _x1,
636            } = extract_map(map);
637        }
638    }
639
640    #[test]
641    fn test_destructure_cbor_map_partial() {
642        let map = cbor_map! {
643            1 => 10,
644            2 => 20,
645            3 => 30,
646            4 => 40,
647            5 => 50,
648            6 => 60,
649            7 => 70,
650            8 => 80,
651            9 => 90,
652        };
653
654        destructure_cbor_map! {
655            let {
656                3 => x3,
657                7 => x7,
658            } = extract_map(map);
659        }
660
661        assert_eq!(x3, Some(cbor_unsigned!(30)));
662        assert_eq!(x7, Some(cbor_unsigned!(70)));
663    }
664
665    #[test]
666    fn test_destructure_cbor_map_missing() {
667        let map = cbor_map! {
668            1 => 10,
669            3 => 30,
670            4 => 40,
671        };
672
673        destructure_cbor_map! {
674            let {
675                0 => x0,
676                1 => x1,
677                2 => x2,
678                3 => x3,
679                4 => x4,
680                5 => x5,
681            } = extract_map(map);
682        }
683
684        assert_eq!(x0, None);
685        assert_eq!(x1, Some(cbor_unsigned!(10)));
686        assert_eq!(x2, None);
687        assert_eq!(x3, Some(cbor_unsigned!(30)));
688        assert_eq!(x4, Some(cbor_unsigned!(40)));
689        assert_eq!(x5, None);
690    }
691
692    #[test]
693    fn test_destructure_unsorted_cbor_map() {
694        let map = cbor_map! {
695            2 => 20,
696            1 => 10,
697        };
698
699        destructure_cbor_map! {
700            let {
701                1 => x1,
702                2 => x2,
703            } = extract_map(map);
704        }
705
706        assert_eq!(x1, Some(cbor_unsigned!(10)));
707        assert_eq!(x2, Some(cbor_unsigned!(20)));
708    }
709}