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#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
12#[derive(Copy, Clone, Eq, PartialEq, Debug)]
13pub enum IntHeader {
14 Compact(CompactIntHeader),
16 Extended(ExtendedIntHeader),
18}
19
20impl IntHeader {
21 #[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 #[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 #[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 #[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 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#[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 pub fn bits(&self) -> u8 {
107 self.bits
108 }
109
110 pub fn is_signed(&self) -> bool {
112 self.is_signed
113 }
114}
115
116#[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 pub fn width(&self) -> u8 {
131 self.width
132 }
133
134 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}