oasis_cbor_value/
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 alloc::vec;
18use core::{cmp::Ordering, iter::Peekable};
19
20use crate::values::Value;
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 oasis_cbor_value::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        match it.peek() {
97            None => return None,
98            Some(item) => {
99                let key: &Value = &item.0;
100                match key.cmp(&needle) {
101                    Ordering::Less => {
102                        it.next();
103                    }
104                    Ordering::Equal => {
105                        let value: Value = it.next().unwrap().1;
106                        return Some(value);
107                    }
108                    Ordering::Greater => return None,
109                }
110            }
111        }
112    }
113}
114
115/// Assert that the keys in a vector of key-value pairs are in canonical order.
116#[macro_export]
117macro_rules! assert_sorted_keys {
118    // Last key
119    ( $key:expr, ) => {
120    };
121
122    ( $key1:expr, $key2:expr, $( $keys:expr, )* ) => {
123        {
124            use $crate::values::{IntoCborValue, Value};
125            let k1: Value = $key1.into_cbor_value();
126            let k2: Value = $key2.into_cbor_value();
127            assert!(
128                k1 < k2,
129                "{:?} < {:?} failed. The destructure_cbor_map! macro requires keys in sorted order.",
130                k1,
131                k2,
132            );
133        }
134        $crate::assert_sorted_keys!($key2, $( $keys, )*);
135    };
136}
137
138/// Creates a CBOR Value of type Map with the specified key-value pairs.
139///
140/// Keys and values are expressions and converted into CBOR Keys and Values.
141/// The syntax for these pairs is `key_expression => value_expression,`.
142/// Duplicate keys will lead to invalid CBOR, i.e. writing these values fails.
143/// Keys do not have to be sorted.
144///
145/// Example usage:
146///
147/// ```rust
148/// # extern crate alloc;
149/// # use oasis_cbor_value::cbor_map;
150/// let map = cbor_map! {
151///   0x01 => false,
152///   "02" => -3,
153/// };
154/// ```
155#[macro_export]
156macro_rules! cbor_map {
157    // trailing comma case
158    ( $( $key:expr => $value:expr, )+ ) => {
159        cbor_map! ( $($key => $value),+ )
160    };
161
162    ( $( $key:expr => $value:expr ),* ) => {
163        {
164            // The import is unused if the list is empty.
165            #[allow(unused_imports)]
166            use $crate::values::IntoCborValue;
167            let mut _map = ::alloc::vec::Vec::new();
168            $(
169                _map.push(($key.into_cbor_value(), $value.into_cbor_value()));
170            )*
171            $crate::values::Value::Map(_map)
172        }
173    };
174}
175
176/// Creates a CBOR Value of type Map with key-value pairs where values can be Options.
177///
178/// Keys and values are expressions and converted into CBOR Keys and Value Options.
179/// The map entry is included iff the Value is not an Option or Option is Some.
180/// The syntax for these pairs is `key_expression => value_expression,`.
181/// Duplicate keys will lead to invalid CBOR, i.e. writing these values fails.
182/// Keys do not have to be sorted.
183///
184/// Example usage:
185///
186/// ```rust
187/// # extern crate alloc;
188/// # use oasis_cbor_value::cbor_map_options;
189/// let missing_value: Option<bool> = None;
190/// let map = cbor_map_options! {
191///   0x01 => Some(false),
192///   "02" => -3,
193///   "not in map" => missing_value,
194/// };
195/// ```
196#[macro_export]
197macro_rules! cbor_map_options {
198    // trailing comma case
199    ( $( $key:expr => $value:expr, )+ ) => {
200        cbor_map_options! ( $($key => $value),+ )
201    };
202
203    ( $( $key:expr => $value:expr ),* ) => {
204        {
205            // The import is unused if the list is empty.
206            #[allow(unused_imports)]
207            use $crate::values::{IntoCborValue, IntoCborValueOption};
208            let mut _map = ::alloc::vec::Vec::<(_, $crate::values::Value)>::new();
209            $(
210            {
211                let opt: Option<$crate::values::Value> = $value.into_cbor_value_option();
212                if let Some(val) = opt {
213                    _map.push(($key.into_cbor_value(), val));
214                }
215            }
216            )*
217            $crate::values::Value::Map(_map)
218        }
219    };
220}
221
222/// Creates a CBOR Value of type Map from a Vec<(Value, Value)>.
223#[macro_export]
224macro_rules! cbor_map_collection {
225    ( $tree:expr ) => {{
226        $crate::values::Value::from($tree)
227    }};
228}
229
230/// Creates a CBOR Value of type Array with the given elements.
231///
232/// Elements are expressions and converted into CBOR Values. Elements are comma-separated.
233///
234/// Example usage:
235///
236/// ```rust
237/// # extern crate alloc;
238/// # use oasis_cbor_value::cbor_array;
239/// let array = cbor_array![1, "2"];
240/// ```
241#[macro_export]
242macro_rules! cbor_array {
243    // trailing comma case
244    ( $( $value:expr, )+ ) => {
245        cbor_array! ( $($value),+ )
246    };
247
248    ( $( $value:expr ),* ) => {
249        {
250            // The import is unused if the list is empty.
251            #[allow(unused_imports)]
252            use $crate::values::IntoCborValue;
253            $crate::values::Value::Array(vec![ $( $value.into_cbor_value(), )* ])
254        }
255    };
256}
257
258/// Creates a CBOR Value of type Array from a Vec<Value>.
259#[macro_export]
260macro_rules! cbor_array_vec {
261    ( $vec:expr ) => {{
262        use $crate::values::IntoCborValue;
263        $crate::values::Value::Array($vec.into_iter().map(|x| x.into_cbor_value()).collect())
264    }};
265}
266
267/// Creates a CBOR Value of type Simple with value true.
268#[macro_export]
269macro_rules! cbor_true {
270    ( ) => {
271        $crate::values::Value::Simple($crate::values::SimpleValue::TrueValue)
272    };
273}
274
275/// Creates a CBOR Value of type Simple with value false.
276#[macro_export]
277macro_rules! cbor_false {
278    ( ) => {
279        $crate::values::Value::Simple($crate::values::SimpleValue::FalseValue)
280    };
281}
282
283/// Creates a CBOR Value of type Simple with value null.
284#[macro_export]
285macro_rules! cbor_null {
286    ( ) => {
287        $crate::values::Value::Simple($crate::values::SimpleValue::NullValue)
288    };
289}
290
291/// Creates a CBOR Value of type Simple with the undefined value.
292#[macro_export]
293macro_rules! cbor_undefined {
294    ( ) => {
295        $crate::values::Value::Simple($crate::values::SimpleValue::Undefined)
296    };
297}
298
299/// Creates a CBOR Value of type Simple with the given bool value.
300#[macro_export]
301macro_rules! cbor_bool {
302    ( $x:expr ) => {
303        $crate::values::Value::bool_value($x)
304    };
305}
306
307/// Creates a CBOR Value of type Unsigned with the given numeric value.
308#[macro_export]
309macro_rules! cbor_unsigned {
310    ( $x:expr ) => {
311        $crate::values::Value::Unsigned($x)
312    };
313}
314
315/// Creates a CBOR Value of type Unsigned or Negative with the given numeric value.
316#[macro_export]
317macro_rules! cbor_int {
318    ( $x:expr ) => {
319        $crate::values::Value::integer($x)
320    };
321}
322
323/// Creates a CBOR Value of type Text String with the given string.
324#[macro_export]
325macro_rules! cbor_text {
326    ( $x:expr ) => {
327        $crate::values::Value::TextString($x.into())
328    };
329}
330
331/// Creates a CBOR Value of type Byte String with the given slice or vector.
332#[macro_export]
333macro_rules! cbor_bytes {
334    ( $x:expr ) => {
335        $crate::values::Value::ByteString($x)
336    };
337}
338
339/// Creates a CBOR Value of type Tag with the given tag and object.
340#[macro_export]
341macro_rules! cbor_tagged {
342    ( $t:expr, $x: expr ) => {
343        $crate::values::Value::Tag($t, ::alloc::boxed::Box::new($x))
344    };
345}
346
347/// Creates a CBOR Value of type Byte String with the given byte string literal.
348///
349/// Example usage:
350///
351/// ```rust
352/// # extern crate alloc;
353/// # use oasis_cbor_value::cbor_bytes_lit;
354/// let byte_array = cbor_bytes_lit!(b"foo");
355/// ```
356#[macro_export]
357macro_rules! cbor_bytes_lit {
358    ( $x:expr ) => {
359        $crate::cbor_bytes!(($x as &[u8]).to_vec())
360    };
361}
362
363#[cfg(test)]
364mod test {
365    use alloc::{string::String, vec, vec::Vec};
366
367    use super::super::values::{SimpleValue, Value};
368
369    #[test]
370    fn test_cbor_simple_values() {
371        assert_eq!(cbor_true!(), Value::Simple(SimpleValue::TrueValue));
372        assert_eq!(cbor_false!(), Value::Simple(SimpleValue::FalseValue));
373        assert_eq!(cbor_null!(), Value::Simple(SimpleValue::NullValue));
374        assert_eq!(cbor_undefined!(), Value::Simple(SimpleValue::Undefined));
375    }
376
377    #[test]
378    fn test_cbor_bool() {
379        assert_eq!(cbor_bool!(true), Value::Simple(SimpleValue::TrueValue));
380        assert_eq!(cbor_bool!(false), Value::Simple(SimpleValue::FalseValue));
381    }
382
383    #[test]
384    fn test_cbor_int_unsigned() {
385        assert_eq!(cbor_int!(0), Value::Unsigned(0));
386        assert_eq!(cbor_int!(1), Value::Unsigned(1));
387        assert_eq!(cbor_int!(123456), Value::Unsigned(123456));
388        assert_eq!(
389            cbor_int!(core::i64::MAX),
390            Value::Unsigned(core::i64::MAX as u64)
391        );
392    }
393
394    #[test]
395    fn test_cbor_int_negative() {
396        assert_eq!(cbor_int!(-1), Value::Negative(-1));
397        assert_eq!(cbor_int!(-123456), Value::Negative(-123456));
398        assert_eq!(
399            cbor_int!(core::i64::MIN),
400            Value::Negative(core::i64::MIN as i128)
401        );
402    }
403
404    #[test]
405    fn test_cbor_int_literals() {
406        let a = cbor_array![
407            core::i64::MIN,
408            core::i32::MIN,
409            -123456,
410            -1,
411            0,
412            1,
413            123456,
414            core::i32::MAX,
415            core::i64::MAX,
416            core::u64::MAX,
417        ];
418        let b = Value::Array(vec![
419            Value::Negative(core::i64::MIN as i128),
420            Value::Negative(core::i32::MIN as i128),
421            Value::Negative(-123456),
422            Value::Negative(-1),
423            Value::Unsigned(0),
424            Value::Unsigned(1),
425            Value::Unsigned(123456),
426            Value::Unsigned(core::i32::MAX as u64),
427            Value::Unsigned(core::i64::MAX as u64),
428            Value::Unsigned(core::u64::MAX),
429        ]);
430        assert_eq!(a, b);
431    }
432
433    #[test]
434    fn test_cbor_array() {
435        let a = cbor_array![
436            -123,
437            456,
438            true,
439            cbor_null!(),
440            "foo",
441            b"bar",
442            cbor_array![],
443            cbor_array![0, 1],
444            cbor_map! {},
445            cbor_map! {2 => 3},
446        ];
447        let b = Value::Array(vec![
448            Value::Negative(-123),
449            Value::Unsigned(456),
450            Value::Simple(SimpleValue::TrueValue),
451            Value::Simple(SimpleValue::NullValue),
452            Value::TextString(String::from("foo")),
453            Value::ByteString(b"bar".to_vec()),
454            Value::Array(Vec::new()),
455            Value::Array(vec![Value::Unsigned(0), Value::Unsigned(1)]),
456            Value::Map(Vec::new()),
457            Value::Map(
458                [(Value::Unsigned(2), Value::Unsigned(3))]
459                    .iter()
460                    .cloned()
461                    .collect(),
462            ),
463        ]);
464        assert_eq!(a, b);
465    }
466
467    #[test]
468    fn test_cbor_array_vec_empty() {
469        let a = cbor_array_vec!(Vec::<bool>::new());
470        let b = Value::Array(Vec::new());
471        assert_eq!(a, b);
472    }
473
474    #[test]
475    fn test_cbor_array_vec_int() {
476        let a = cbor_array_vec!(vec![1, 2, 3, 4]);
477        let b = Value::Array(vec![
478            Value::Unsigned(1),
479            Value::Unsigned(2),
480            Value::Unsigned(3),
481            Value::Unsigned(4),
482        ]);
483        assert_eq!(a, b);
484    }
485
486    #[test]
487    fn test_cbor_array_vec_text() {
488        let a = cbor_array_vec!(vec!["a", "b", "c"]);
489        let b = Value::Array(vec![
490            Value::TextString(String::from("a")),
491            Value::TextString(String::from("b")),
492            Value::TextString(String::from("c")),
493        ]);
494        assert_eq!(a, b);
495    }
496
497    #[test]
498    fn test_cbor_array_vec_bytes() {
499        let a = cbor_array_vec!(vec![b"a", b"b", b"c"]);
500        let b = Value::Array(vec![
501            Value::ByteString(b"a".to_vec()),
502            Value::ByteString(b"b".to_vec()),
503            Value::ByteString(b"c".to_vec()),
504        ]);
505        assert_eq!(a, b);
506    }
507
508    #[test]
509    fn test_cbor_map() {
510        let a = cbor_map! {
511            -1 => -23,
512            4 => 56,
513            "foo" => true,
514            b"bar" => cbor_null!(),
515            5 => "foo",
516            6 => b"bar",
517            7 => cbor_array![],
518            8 => cbor_array![0, 1],
519            9 => cbor_map!{},
520            10 => cbor_map!{2 => 3},
521        };
522        let b = Value::Map(
523            [
524                (Value::Negative(-1), Value::Negative(-23)),
525                (Value::Unsigned(4), Value::Unsigned(56)),
526                (
527                    Value::TextString(String::from("foo")),
528                    Value::Simple(SimpleValue::TrueValue),
529                ),
530                (
531                    Value::ByteString(b"bar".to_vec()),
532                    Value::Simple(SimpleValue::NullValue),
533                ),
534                (Value::Unsigned(5), Value::TextString(String::from("foo"))),
535                (Value::Unsigned(6), Value::ByteString(b"bar".to_vec())),
536                (Value::Unsigned(7), Value::Array(Vec::new())),
537                (
538                    Value::Unsigned(8),
539                    Value::Array(vec![Value::Unsigned(0), Value::Unsigned(1)]),
540                ),
541                (Value::Unsigned(9), Value::Map(Vec::new())),
542                (
543                    Value::Unsigned(10),
544                    Value::Map(
545                        [(Value::Unsigned(2), Value::Unsigned(3))]
546                            .iter()
547                            .cloned()
548                            .collect(),
549                    ),
550                ),
551            ]
552            .iter()
553            .cloned()
554            .collect(),
555        );
556        assert_eq!(a, b);
557    }
558
559    #[test]
560    fn test_cbor_map_options() {
561        let a = cbor_map_options! {
562            -1 => -23,
563            4 => Some(56),
564            11 => None::<String>,
565            "foo" => true,
566            12 => None::<&str>,
567            b"bar" => Some(cbor_null!()),
568            13 => None::<Vec<u8>>,
569            5 => "foo",
570            14 => None::<&[u8]>,
571            6 => Some(b"bar" as &[u8]),
572            15 => None::<bool>,
573            7 => cbor_array![],
574            16 => None::<i32>,
575            8 => Some(cbor_array![0, 1]),
576            17 => None::<i64>,
577            9 => cbor_map!{},
578            18 => None::<u64>,
579            10 => Some(cbor_map!{2 => 3}),
580        };
581        let b = Value::Map(
582            [
583                (Value::Negative(-1), Value::Negative(-23)),
584                (Value::Unsigned(4), Value::Unsigned(56)),
585                (
586                    Value::TextString(String::from("foo")),
587                    Value::Simple(SimpleValue::TrueValue),
588                ),
589                (
590                    Value::ByteString(b"bar".to_vec()),
591                    Value::Simple(SimpleValue::NullValue),
592                ),
593                (Value::Unsigned(5), Value::TextString(String::from("foo"))),
594                (Value::Unsigned(6), Value::ByteString(b"bar".to_vec())),
595                (Value::Unsigned(7), Value::Array(Vec::new())),
596                (
597                    Value::Unsigned(8),
598                    Value::Array(vec![Value::Unsigned(0), Value::Unsigned(1)]),
599                ),
600                (Value::Unsigned(9), Value::Map(Vec::new())),
601                (
602                    Value::Unsigned(10),
603                    Value::Map(
604                        [(Value::Unsigned(2), Value::Unsigned(3))]
605                            .iter()
606                            .cloned()
607                            .collect(),
608                    ),
609                ),
610            ]
611            .iter()
612            .cloned()
613            .collect(),
614        );
615        assert_eq!(a, b);
616    }
617
618    #[test]
619    fn test_cbor_map_collection_empty() {
620        let a = cbor_map_collection!(Vec::<(_, _)>::new());
621        let b = Value::Map(Vec::new());
622        assert_eq!(a, b);
623    }
624
625    #[test]
626    fn test_cbor_map_collection_foo() {
627        let a = cbor_map_collection!(vec![(Value::Unsigned(2), Value::Unsigned(3))]);
628        let b = Value::Map(vec![(Value::Unsigned(2), Value::Unsigned(3))]);
629        assert_eq!(a, b);
630    }
631
632    fn extract_map(cbor_value: Value) -> Vec<(Value, Value)> {
633        match cbor_value {
634            Value::Map(map) => map,
635            _ => panic!("Expected CBOR map."),
636        }
637    }
638
639    #[test]
640    fn test_destructure_cbor_map_simple() {
641        let map = cbor_map! {
642            1 => 10,
643            2 => 20,
644        };
645
646        destructure_cbor_map! {
647            let {
648                1 => x1,
649                2 => x2,
650            } = extract_map(map);
651        }
652
653        assert_eq!(x1, Some(cbor_unsigned!(10)));
654        assert_eq!(x2, Some(cbor_unsigned!(20)));
655    }
656
657    #[test]
658    #[should_panic]
659    fn test_destructure_cbor_map_unsorted() {
660        let map = cbor_map! {
661            1 => 10,
662            2 => 20,
663        };
664
665        destructure_cbor_map! {
666            // The keys are not sorted here, which violates the precondition of
667            // destructure_cbor_map. An assertion should catch that and make the test panic.
668            let {
669                2 => _x2,
670                1 => _x1,
671            } = extract_map(map);
672        }
673    }
674
675    #[test]
676    fn test_destructure_cbor_map_partial() {
677        let map = cbor_map! {
678            1 => 10,
679            2 => 20,
680            3 => 30,
681            4 => 40,
682            5 => 50,
683            6 => 60,
684            7 => 70,
685            8 => 80,
686            9 => 90,
687        };
688
689        destructure_cbor_map! {
690            let {
691                3 => x3,
692                7 => x7,
693            } = extract_map(map);
694        }
695
696        assert_eq!(x3, Some(cbor_unsigned!(30)));
697        assert_eq!(x7, Some(cbor_unsigned!(70)));
698    }
699
700    #[test]
701    fn test_destructure_cbor_map_missing() {
702        let map = cbor_map! {
703            1 => 10,
704            3 => 30,
705            4 => 40,
706        };
707
708        destructure_cbor_map! {
709            let {
710                0 => x0,
711                1 => x1,
712                2 => x2,
713                3 => x3,
714                4 => x4,
715                5 => x5,
716            } = extract_map(map);
717        }
718
719        assert_eq!(x0, None);
720        assert_eq!(x1, Some(cbor_unsigned!(10)));
721        assert_eq!(x2, None);
722        assert_eq!(x3, Some(cbor_unsigned!(30)));
723        assert_eq!(x4, Some(cbor_unsigned!(40)));
724        assert_eq!(x5, None);
725    }
726}