lilliput_core/header/
map.rs

1#[cfg(any(test, feature = "testing"))]
2use proptest::prelude::*;
3#[cfg(any(test, feature = "testing"))]
4use proptest_derive::Arbitrary;
5
6use crate::config::PackingMode;
7
8/// Header representing a map of key-value pairs.
9#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
10#[derive(Copy, Clone, Eq, PartialEq, Debug)]
11pub enum MapHeader {
12    /// Compact header.
13    Compact(CompactMapHeader),
14    /// Extended header.
15    Extended(ExtendedMapHeader),
16}
17
18impl MapHeader {
19    /// Creates a compact header.
20    #[inline]
21    pub fn compact(len: u8) -> Self {
22        assert!(len <= Self::COMPACT_LEN_BITS);
23
24        Self::compact_unchecked(len)
25    }
26
27    /// Creates a compact header, without checking invariants.
28    #[inline]
29    pub fn compact_unchecked(len: u8) -> Self {
30        Self::Compact(CompactMapHeader { len })
31    }
32
33    /// Creates an extended header.
34    #[inline]
35    pub fn extended(len: usize) -> Self {
36        Self::Extended(ExtendedMapHeader { len })
37    }
38
39    /// Creates a header for a given map's length, for a given `packing_mode`.
40    #[inline]
41    pub fn for_len(len: usize, packing_mode: PackingMode) -> Self {
42        if let Some(len) = Self::as_compact_len(len, packing_mode) {
43            Self::compact_unchecked(len)
44        } else {
45            Self::extended(len)
46        }
47    }
48
49    /// Returns `true` if the associated value has a length of zero, otherwise `false`.
50    pub fn is_empty(&self) -> bool {
51        self.len() == 0
52    }
53
54    /// Returns the associated value's length.
55    pub fn len(&self) -> usize {
56        match self {
57            Self::Compact(compact) => compact.len().into(),
58            Self::Extended(extended) => extended.len(),
59        }
60    }
61
62    #[inline]
63    fn as_compact_len(len: usize, packing_mode: PackingMode) -> Option<u8> {
64        if packing_mode.is_optimal() && len <= (Self::COMPACT_MAX_LEN as usize) {
65            Some(len as u8)
66        } else {
67            None
68        }
69    }
70}
71
72/// Compact header representing a map of key-value pairs.
73#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
74#[derive(Copy, Clone, Eq, PartialEq, Debug)]
75#[repr(transparent)]
76pub struct CompactMapHeader {
77    #[cfg_attr(
78        any(test, feature = "testing"),
79        proptest(strategy = "(0..=MapHeader::COMPACT_MAX_LEN)")
80    )]
81    pub(crate) len: u8,
82}
83
84impl CompactMapHeader {
85    /// Returns `true` if the associated value has a length of zero, otherwise `false`.
86    pub fn is_empty(&self) -> bool {
87        self.len() == 0
88    }
89
90    /// Returns the associated value's length.
91    pub fn len(&self) -> u8 {
92        self.len
93    }
94}
95
96/// Extended header representing a map of key-value pairs.
97#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
98#[derive(Copy, Clone, Eq, PartialEq, Debug)]
99#[repr(transparent)]
100pub struct ExtendedMapHeader {
101    #[cfg_attr(
102        any(test, feature = "testing"),
103        proptest(strategy = "super::arbitrary_len()")
104    )]
105    pub(crate) len: usize,
106}
107
108impl ExtendedMapHeader {
109    /// Returns `true` if the associated value has a length of zero, otherwise `false`.
110    pub fn is_empty(&self) -> bool {
111        self.len() == 0
112    }
113
114    /// Returns the associated value's length.
115    pub fn len(&self) -> usize {
116        self.len
117    }
118}
119
120impl MapHeader {
121    pub(crate) const MASK: u8 = 0b00011111;
122    pub(crate) const TYPE_BITS: u8 = 0b00010000;
123
124    pub(crate) const COMPACT_VARIANT_BIT: u8 = 0b00001000;
125    pub(crate) const COMPACT_LEN_BITS: u8 = 0b00000111;
126
127    pub(crate) const EXTENDED_LEN_WIDTH_BITS: u8 = 0b00000111;
128
129    pub(crate) const COMPACT_MAX_LEN: u8 = Self::COMPACT_LEN_BITS;
130}
131
132#[cfg(test)]
133mod tests {
134    use proptest::prelude::*;
135    use test_log::test;
136
137    use crate::{
138        config::EncoderConfig,
139        decoder::Decoder,
140        encoder::Encoder,
141        io::{SliceReader, VecWriter},
142    };
143
144    use super::*;
145
146    proptest! {
147        #[test]
148        fn as_compact_len(len in usize::arbitrary(), packing_mode in PackingMode::arbitrary()) {
149            let compact_len = MapHeader::as_compact_len(len, packing_mode);
150
151            if packing_mode.is_optimal() && len <= (MapHeader::COMPACT_MAX_LEN as usize) {
152                prop_assert_eq!(compact_len, Some(len as u8));
153            } else {
154                prop_assert_eq!(compact_len, None);
155            }
156        }
157
158        #[test]
159        fn for_len(len in usize::arbitrary(), packing_mode in PackingMode::arbitrary()) {
160            let header = MapHeader::for_len(len, packing_mode);
161
162            match packing_mode {
163                PackingMode::None => {
164                    prop_assert!(matches!(header, MapHeader::Extended(_)));
165                    prop_assert!(header.len() == len);
166                },
167                PackingMode::Native => {
168                    prop_assert!(matches!(header, MapHeader::Extended(_)));
169                },
170                PackingMode::Optimal => {
171                    if len <= (MapHeader::COMPACT_MAX_LEN as usize) {
172                        prop_assert!(matches!(header, MapHeader::Compact(_)));
173                    } else {
174                        prop_assert!(matches!(header, MapHeader::Extended(_)));
175                    }
176                },
177            }
178        }
179
180        #[test]
181        fn encode_decode_roundtrip(header in MapHeader::arbitrary(), config in EncoderConfig::arbitrary()) {
182            let mut encoded: Vec<u8> = Vec::new();
183            let writer = VecWriter::new(&mut encoded);
184            let mut encoder = Encoder::new(writer, config);
185            encoder.encode_map_header(&header).unwrap();
186
187            prop_assert!(encoded.len() <= 1 + 8);
188
189            let reader = SliceReader::new(&encoded);
190            let mut decoder = Decoder::from_reader(reader);
191            let decoded = decoder.decode_map_header().unwrap();
192            prop_assert_eq!(&decoded, &header);
193        }
194    }
195}