lilliput_core/header/
string.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 string.
9#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
10#[derive(Copy, Clone, Eq, PartialEq, Debug)]
11pub enum StringHeader {
12    /// Compact header.
13    Compact(CompactStringHeader),
14    /// Extended header.
15    Extended(ExtendedStringHeader),
16}
17
18impl StringHeader {
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(CompactStringHeader { len })
31    }
32
33    /// Creates an extended header.
34    #[inline]
35    pub fn extended(len: usize) -> Self {
36        Self::Extended(ExtendedStringHeader { 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 string.
73#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
74#[derive(Copy, Clone, Eq, PartialEq, Debug)]
75#[repr(transparent)]
76pub struct CompactStringHeader {
77    #[cfg_attr(
78        any(test, feature = "testing"),
79        proptest(strategy = "(0..=StringHeader::COMPACT_MAX_LEN)")
80    )]
81    pub(crate) len: u8,
82}
83
84impl CompactStringHeader {
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 string.
97#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
98#[derive(Copy, Clone, Eq, PartialEq, Debug)]
99#[repr(transparent)]
100pub struct ExtendedStringHeader {
101    #[cfg_attr(
102        any(test, feature = "testing"),
103        proptest(strategy = "super::arbitrary_len()")
104    )]
105    pub(crate) len: usize,
106}
107
108impl ExtendedStringHeader {
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 StringHeader {
121    pub(crate) const MASK: u8 = 0b01111111;
122    pub(crate) const TYPE_BITS: u8 = 0b01000000;
123
124    pub(crate) const COMPACT_VARIANT_BIT: u8 = 0b00100000;
125    pub(crate) const COMPACT_LEN_BITS: u8 = 0b00011111;
126    pub(crate) const EXTENDED_LEN_WIDTH_BITS: u8 = 0b00000111;
127
128    #[allow(dead_code)]
129    pub(crate) const COMPACT_MAX_LEN: u8 = Self::COMPACT_LEN_BITS;
130    #[allow(dead_code)]
131    pub(crate) const EXTENDED_MAX_LEN_WIDTH: u8 = 1 + Self::EXTENDED_LEN_WIDTH_BITS;
132}
133
134#[cfg(test)]
135mod tests {
136    use proptest::prelude::*;
137    use test_log::test;
138
139    use crate::{
140        config::EncoderConfig,
141        decoder::Decoder,
142        encoder::Encoder,
143        io::{SliceReader, VecWriter},
144    };
145
146    use super::*;
147
148    proptest! {
149        #[test]
150        fn as_compact_len(len in usize::arbitrary(), packing_mode in PackingMode::arbitrary()) {
151            let compact_len = StringHeader::as_compact_len(len, packing_mode);
152            let is_optimal = packing_mode == PackingMode::Optimal;
153            let can_be_compact = len <= (StringHeader::COMPACT_MAX_LEN as usize);
154
155            if is_optimal && can_be_compact {
156                prop_assert_eq!(compact_len, Some(len as u8));
157            } else {
158                prop_assert_eq!(compact_len, None);
159            }
160        }
161
162        #[test]
163        fn encode_decode_roundtrip(header in StringHeader::arbitrary(), config in EncoderConfig::arbitrary()) {
164            let mut encoded: Vec<u8> = Vec::new();
165            let writer = VecWriter::new(&mut encoded);
166            let mut encoder = Encoder::new(writer, config);
167            encoder.encode_string_header(&header).unwrap();
168
169            prop_assert!(encoded.len() <= 1 + 8);
170
171            let reader = SliceReader::new(&encoded);
172            let mut decoder = Decoder::from_reader(reader);
173            let decoded = decoder.decode_string_header().unwrap();
174            prop_assert_eq!(&decoded, &header);
175        }
176    }
177}