lilliput_core/header/
int.rs

1#[cfg(any(test, feature = "testing"))]
2use proptest::prelude::*;
3#[cfg(any(test, feature = "testing"))]
4use proptest_derive::Arbitrary;
5
6use num_traits::{Signed, Unsigned};
7
8use crate::{config::PackingMode, num::WithPackedBeBytes};
9
10/// Header representing an integer number.
11#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
12#[derive(Copy, Clone, Eq, PartialEq, Debug)]
13pub enum IntHeader {
14    /// Compact header.
15    Compact(CompactIntHeader),
16    /// Extended header.
17    Extended(ExtendedIntHeader),
18}
19
20impl IntHeader {
21    /// Creates a compact header.
22    #[inline]
23    pub fn compact(is_signed: bool, bits: u8) -> Self {
24        assert!(bits <= Self::COMPACT_VALUE_BITS);
25
26        Self::Compact(CompactIntHeader { is_signed, bits })
27    }
28
29    /// Creates an extended header.
30    #[inline]
31    pub fn extended(is_signed: bool, width: u8) -> Self {
32        assert!(width >= 1);
33        assert!((width - 1) <= Self::EXTENDED_WIDTH_BITS);
34
35        Self::Extended(ExtendedIntHeader { is_signed, width })
36    }
37
38    /// Creates a header for a given signed `value`, for a given `packing_mode`.
39    #[inline]
40    pub fn for_signed<T>(value: T, packing_mode: PackingMode) -> Self
41    where
42        T: Signed + WithPackedBeBytes,
43    {
44        value.with_packed_be_bytes(packing_mode, |be_bytes| {
45            Self::for_int_be_bytes(true, be_bytes, packing_mode)
46        })
47    }
48
49    /// Creates a header for a given unsigned `value`, for a given `packing_mode`.
50    #[inline]
51    pub fn for_unsigned<T>(value: T, packing_mode: PackingMode) -> Self
52    where
53        T: Unsigned + WithPackedBeBytes,
54    {
55        value.with_packed_be_bytes(packing_mode, |be_bytes| {
56            Self::for_int_be_bytes(true, be_bytes, packing_mode)
57        })
58    }
59
60    /// Returns the extended byte-width, or `None` if compact.
61    pub fn extended_width(&self) -> Option<u8> {
62        match self {
63            Self::Compact(_) => None,
64            Self::Extended(header) => Some(header.width),
65        }
66    }
67
68    #[inline]
69    pub(crate) fn for_int_be_bytes(
70        is_signed: bool,
71        be_bytes: &[u8],
72        packing_mode: PackingMode,
73    ) -> Self {
74        let width = be_bytes.len();
75
76        let mut header = Self::Extended(ExtendedIntHeader {
77            is_signed,
78            width: width as u8,
79        });
80
81        if packing_mode == PackingMode::Optimal && width == 1 {
82            let bits = be_bytes[width - 1];
83            if bits <= Self::COMPACT_VALUE_BITS {
84                header = Self::Compact(CompactIntHeader { is_signed, bits });
85            }
86        }
87
88        header
89    }
90}
91
92/// Compact header representing an integer number.
93#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
94#[derive(Copy, Clone, Eq, PartialEq, Debug)]
95pub struct CompactIntHeader {
96    pub(crate) is_signed: bool,
97    #[cfg_attr(
98        any(test, feature = "testing"),
99        proptest(strategy = "(0..=IntHeader::MAX_COMPACT_VALUE)")
100    )]
101    pub(crate) bits: u8,
102}
103
104impl CompactIntHeader {
105    /// Returns the associated value's compact representation.
106    pub fn bits(&self) -> u8 {
107        self.bits
108    }
109
110    /// Returns `true`, if the associated value's type is signed, otherwise `false`.
111    pub fn is_signed(&self) -> bool {
112        self.is_signed
113    }
114}
115
116/// Extended header representing an integer number.
117#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
118#[derive(Copy, Clone, Eq, PartialEq, Debug)]
119pub struct ExtendedIntHeader {
120    pub(crate) is_signed: bool,
121    #[cfg_attr(
122        any(test, feature = "testing"),
123        proptest(strategy = "(1..=IntHeader::MAX_EXTENDED_WIDTH)")
124    )]
125    pub(crate) width: u8,
126}
127
128impl ExtendedIntHeader {
129    /// Returns the associated value's byte-width.
130    pub fn width(&self) -> u8 {
131        self.width
132    }
133
134    /// Returns `true`, if the associated value's type is signed, otherwise `false`.
135    pub fn is_signed(&self) -> bool {
136        self.is_signed
137    }
138}
139
140impl IntHeader {
141    pub(crate) const MASK: u8 = 0b11111111;
142    pub(crate) const MAX_COMPACT_VALUE: u8 = Self::COMPACT_VALUE_BITS;
143    pub(crate) const MAX_EXTENDED_WIDTH: u8 = Self::EXTENDED_WIDTH_BITS + 1;
144
145    pub(crate) const TYPE_BITS: u8 = 0b10000000;
146
147    pub(crate) const SIGNEDNESS_BIT: u8 = 0b00100000;
148
149    pub(crate) const COMPACT_VARIANT_BIT: u8 = 0b01000000;
150    pub(crate) const COMPACT_VALUE_BITS: u8 = 0b00011111;
151
152    pub(crate) const EXTENDED_WIDTH_BITS: u8 = 0b00000111;
153}
154
155#[cfg(test)]
156mod tests {
157    use proptest::prelude::*;
158    use test_log::test;
159
160    use crate::{
161        config::EncoderConfig,
162        decoder::Decoder,
163        encoder::Encoder,
164        io::{SliceReader, VecWriter},
165        num::ToZigZag as _,
166    };
167
168    use super::*;
169
170    proptest! {
171        #[test]
172        fn for_u8(unsigned in u8::arbitrary(), packing_mode in PackingMode::arbitrary()) {
173            let header = IntHeader::for_unsigned(unsigned, packing_mode);
174
175            let extended_width = header.extended_width().unwrap_or(0);
176
177            match packing_mode {
178                PackingMode::None => prop_assert!(extended_width == 1),
179                PackingMode::Native => prop_assert!([1].contains(&extended_width)),
180                PackingMode::Optimal => {
181                    if unsigned <= IntHeader::COMPACT_VALUE_BITS {
182                        prop_assert!(extended_width == 0)
183                    } else {
184                        prop_assert!(extended_width <= 1)
185                    }
186                },
187            }
188        }
189
190        #[test]
191        fn for_u16(unsigned in u16::arbitrary(), packing_mode in PackingMode::arbitrary()) {
192            let header = IntHeader::for_unsigned(unsigned, packing_mode);
193
194            let extended_width = header.extended_width().unwrap_or(0);
195
196            match packing_mode {
197                PackingMode::None => prop_assert!(extended_width == 2),
198                PackingMode::Native => prop_assert!([1, 2].contains(&extended_width)),
199                PackingMode::Optimal => {
200                    if unsigned <= IntHeader::COMPACT_VALUE_BITS as u16 {
201                        prop_assert!(extended_width == 0)
202                    } else {
203                        prop_assert!(extended_width <= 2)
204                    }
205                },
206            }
207        }
208
209        #[test]
210        fn for_u32(unsigned in u32::arbitrary(), packing_mode in PackingMode::arbitrary()) {
211            let header = IntHeader::for_unsigned(unsigned, packing_mode);
212
213            let extended_width = header.extended_width().unwrap_or(0);
214
215            match packing_mode {
216                PackingMode::None => prop_assert!(extended_width == 4),
217                PackingMode::Native => prop_assert!([1, 2, 4].contains(&extended_width)),
218                PackingMode::Optimal => {
219                    if unsigned <= IntHeader::COMPACT_VALUE_BITS as u32 {
220                        prop_assert!(extended_width == 0)
221                    } else {
222                        prop_assert!(extended_width <= 4)
223                    }
224                },
225            }
226        }
227
228        #[test]
229        fn for_u64(unsigned in u64::arbitrary(), packing_mode in PackingMode::arbitrary()) {
230            let header = IntHeader::for_unsigned(unsigned, packing_mode);
231
232            let extended_width = header.extended_width().unwrap_or(0);
233
234            match packing_mode {
235                PackingMode::None => prop_assert!(extended_width == 8),
236                PackingMode::Native => prop_assert!([1, 2, 4, 8].contains(&extended_width)),
237                PackingMode::Optimal => {
238                    if unsigned <= IntHeader::COMPACT_VALUE_BITS as u64 {
239                        prop_assert!(extended_width == 0)
240                    } else {
241                        prop_assert!(extended_width <= 8)
242                    }
243                },
244            }
245        }
246
247        #[test]
248        fn for_i8(signed in i8::arbitrary(), packing_mode in PackingMode::arbitrary()) {
249            let unsigned = signed.to_zig_zag();
250            let header = IntHeader::for_unsigned(unsigned, packing_mode);
251
252            let extended_width = header.extended_width().unwrap_or(0);
253
254            match packing_mode {
255                PackingMode::None => prop_assert!(extended_width == 1),
256                PackingMode::Native => prop_assert!([1].contains(&extended_width)),
257                PackingMode::Optimal => {
258                    if unsigned <= IntHeader::COMPACT_VALUE_BITS {
259                        prop_assert!(extended_width == 0)
260                    } else {
261                        prop_assert!(extended_width <= 1)
262                    }
263                },
264            }
265        }
266
267        #[test]
268        fn for_i16(signed in i16::arbitrary(), packing_mode in PackingMode::arbitrary()) {
269            let unsigned = signed.to_zig_zag();
270            let header = IntHeader::for_unsigned(unsigned, packing_mode);
271
272            let extended_width = header.extended_width().unwrap_or(0);
273
274            match packing_mode {
275                PackingMode::None => prop_assert!(extended_width == 2),
276                PackingMode::Native => prop_assert!([1, 2].contains(&extended_width)),
277                PackingMode::Optimal => {
278                    if unsigned <= IntHeader::COMPACT_VALUE_BITS as u16 {
279                        prop_assert!(extended_width == 0)
280                    } else {
281                        prop_assert!(extended_width <= 2)
282                    }
283                },
284            }
285        }
286
287        #[test]
288        fn for_i32(signed in i32::arbitrary(), packing_mode in PackingMode::arbitrary()) {
289            let unsigned = signed.to_zig_zag();
290            let header = IntHeader::for_unsigned(unsigned, packing_mode);
291
292            let extended_width = header.extended_width().unwrap_or(0);
293
294            match packing_mode {
295                PackingMode::None => prop_assert!(extended_width == 4),
296                PackingMode::Native => prop_assert!([1, 2, 4].contains(&extended_width)),
297                PackingMode::Optimal => {
298                    if unsigned <= IntHeader::COMPACT_VALUE_BITS as u32 {
299                        prop_assert!(extended_width == 0)
300                    } else {
301                        prop_assert!(extended_width <= 4)
302                    }
303                },
304            }
305        }
306
307        #[test]
308        fn for_i64(signed in i64::arbitrary(), packing_mode in PackingMode::arbitrary()) {
309            let unsigned = signed.to_zig_zag();
310            let header = IntHeader::for_unsigned(unsigned, packing_mode);
311
312            let extended_width = header.extended_width().unwrap_or(0);
313
314            match packing_mode {
315                PackingMode::None => prop_assert!(extended_width == 8),
316                PackingMode::Native => prop_assert!([1, 2, 4, 8].contains(&extended_width)),
317                PackingMode::Optimal => {
318                    if unsigned <= IntHeader::COMPACT_VALUE_BITS as u64 {
319                        prop_assert!(extended_width == 0)
320                    } else {
321                        prop_assert!(extended_width <= 8)
322                    }
323                },
324            }
325        }
326
327        #[test]
328        fn encode_decode_roundtrip(header in IntHeader::arbitrary(), config in EncoderConfig::arbitrary()) {
329            let mut encoded: Vec<u8> = Vec::new();
330            let writer = VecWriter::new(&mut encoded);
331            let mut encoder = Encoder::new(writer, config);
332            encoder.encode_int_header(&header).unwrap();
333
334            prop_assert!(encoded.len() == 1);
335
336            let reader = SliceReader::new(&encoded);
337            let mut decoder = Decoder::from_reader(reader);
338            let decoded = decoder.decode_int_header().unwrap();
339            prop_assert_eq!(&decoded, &header);
340        }
341    }
342}