lilliput_core/header/
string.rs1#[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#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
10#[derive(Copy, Clone, Eq, PartialEq, Debug)]
11pub enum StringHeader {
12 Compact(CompactStringHeader),
14 Extended(ExtendedStringHeader),
16}
17
18impl StringHeader {
19 #[inline]
21 pub fn compact(len: u8) -> Self {
22 assert!(len <= Self::COMPACT_LEN_BITS);
23
24 Self::compact_unchecked(len)
25 }
26
27 #[inline]
29 pub fn compact_unchecked(len: u8) -> Self {
30 Self::Compact(CompactStringHeader { len })
31 }
32
33 #[inline]
35 pub fn extended(len: usize) -> Self {
36 Self::Extended(ExtendedStringHeader { len })
37 }
38
39 #[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 pub fn is_empty(&self) -> bool {
51 self.len() == 0
52 }
53
54 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#[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 pub fn is_empty(&self) -> bool {
87 self.len() == 0
88 }
89
90 pub fn len(&self) -> u8 {
92 self.len
93 }
94}
95
96#[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 pub fn is_empty(&self) -> bool {
111 self.len() == 0
112 }
113
114 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}