s2n_codec/
unaligned.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use core::ops::Deref;
5
6// Unaligned integer types are integers which Rust does not provide natively.
7// This macro attempts to create wrapper types around the rounded up type
8// supported, e.g. 24 -> 32.
9//
10// For the scope of the QUIC implementation 24-bit integers are needed
11// for u24 encoded packet numbers:
12// https://www.rfc-editor.org/rfc/rfc9000.html#name-packet-number-encoding-and-
13//
14// 48-bit integers are also implemented for completeness.
15macro_rules! unaligned_integer_type {
16    ($name:ident, $bitsize:expr, $storage_type:ty, $min:expr, $max:expr, [$($additional_conversions:ty),*]) => {
17        #[allow(non_camel_case_types)]
18        #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
19        pub struct $name($storage_type);
20
21        impl $name {
22            pub const ZERO: Self = Self(0);
23            pub const MIN: Self = Self($min);
24            pub const MAX: Self = Self($max);
25
26            /// Truncate the storage value into the allowed range
27            #[inline]
28            pub fn new_truncated(value: $storage_type) -> Self {
29                Self(value & ((1 << $bitsize) - 1))
30            }
31
32            #[inline]
33            pub fn from_be_bytes(bytes: [u8; ($bitsize / 8)]) -> Self {
34                Self(UnalignedBytes::be_bytes_to_storage(bytes) as _)
35            }
36
37            #[inline]
38            pub fn to_be_bytes(self) -> [u8; ($bitsize / 8)] {
39                UnalignedBytes::storage_to_be_bytes(self.0 as _)
40            }
41        }
42
43        #[cfg(any(test, feature = "generator"))]
44        impl bolero_generator::TypeGenerator for $name {
45            fn generate<D: bolero_generator::Driver>(driver: &mut D) -> Option<Self> {
46                Some(Self::new_truncated(driver.produce()?))
47            }
48        }
49
50        impl TryFrom<$storage_type> for $name {
51            type Error = TryFromIntError;
52
53            #[inline]
54            fn try_from(value: $storage_type) -> Result<Self, Self::Error> {
55                if value < (1 << $bitsize) {
56                    Ok(Self(value))
57                } else {
58                    Err(TryFromIntError(()))
59                }
60            }
61        }
62
63        impl From<$name> for $storage_type {
64            #[inline]
65            fn from(value: $name) -> $storage_type {
66                value.0
67            }
68        }
69
70        $(
71            impl From<$additional_conversions> for $name {
72                #[inline]
73                fn from(value: $additional_conversions) -> Self {
74                    $name(value.into())
75                }
76            }
77
78            impl From<$name> for $additional_conversions {
79                #[inline]
80                fn from(value: $name) -> Self {
81                    value.0 as $additional_conversions
82                }
83            }
84        )*
85
86        impl Deref for $name {
87            type Target = $storage_type;
88
89            #[inline]
90            fn deref(&self) -> &Self::Target {
91                &self.0
92            }
93        }
94    };
95}
96
97/// A trait defining how to convert between storage types and unaligned bytes
98trait UnalignedBytes: Sized {
99    type Storage;
100
101    fn storage_to_be_bytes(storage: Self::Storage) -> Self;
102    fn be_bytes_to_storage(self) -> Self::Storage;
103}
104
105impl UnalignedBytes for [u8; 3] {
106    type Storage = u32;
107
108    #[inline]
109    fn storage_to_be_bytes(storage: Self::Storage) -> Self {
110        let [_, a, b, c] = storage.to_be_bytes();
111        [a, b, c]
112    }
113
114    #[inline]
115    fn be_bytes_to_storage(self) -> Self::Storage {
116        let [a, b, c] = self;
117        let bytes = [0, a, b, c];
118        Self::Storage::from_be_bytes(bytes)
119    }
120}
121
122impl UnalignedBytes for [u8; 6] {
123    type Storage = u64;
124
125    #[inline]
126    fn storage_to_be_bytes(storage: Self::Storage) -> Self {
127        let [_, _, a, b, c, d, e, f] = storage.to_be_bytes();
128        [a, b, c, d, e, f]
129    }
130
131    #[inline]
132    fn be_bytes_to_storage(self) -> Self::Storage {
133        let [a, b, c, d, e, f] = self;
134        let bytes = [0, 0, a, b, c, d, e, f];
135        Self::Storage::from_be_bytes(bytes)
136    }
137}
138
139macro_rules! signed_min {
140    ($bitsize:expr) => {
141        -(1 << ($bitsize - 1))
142    };
143}
144
145macro_rules! signed_max {
146    ($bitsize:expr) => {
147        ((1 << ($bitsize - 1)) - 1)
148    };
149}
150
151#[test]
152fn signed_min_max_test() {
153    assert_eq!(i8::MIN as i16, signed_min!(8));
154    assert_eq!(i8::MAX as i16, signed_max!(8));
155}
156
157unaligned_integer_type!(u24, 24, u32, 0, (1 << 24) - 1, [u8, u16]);
158unaligned_integer_type!(
159    i24,
160    24,
161    i32,
162    signed_min!(24),
163    signed_max!(24),
164    [u8, i8, u16, i16]
165);
166
167impl TryFrom<u64> for u24 {
168    type Error = TryFromIntError;
169
170    #[inline]
171    fn try_from(value: u64) -> Result<Self, Self::Error> {
172        let storage_value: u32 = value.try_into()?;
173        storage_value.try_into()
174    }
175}
176
177impl From<u24> for u64 {
178    #[inline]
179    fn from(value: u24) -> u64 {
180        value.0.into()
181    }
182}
183
184unaligned_integer_type!(u48, 48, u64, 0, (1 << 48) - 1, [u8, u16, u32]);
185unaligned_integer_type!(
186    i48,
187    48,
188    i64,
189    signed_min!(48),
190    signed_max!(48),
191    [u8, i8, u16, i16, u32, i32]
192);
193
194#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
195#[cfg_attr(test, derive(bolero::TypeGenerator))]
196pub struct TryFromIntError(());
197
198impl From<core::num::TryFromIntError> for TryFromIntError {
199    #[inline]
200    fn from(_: core::num::TryFromIntError) -> Self {
201        Self(())
202    }
203}
204
205impl core::fmt::Display for TryFromIntError {
206    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
207        write!(f, "TryFromIntError")
208    }
209}
210
211#[cfg(test)]
212mod tests {
213
214    use super::*;
215
216    #[test]
217    #[cfg_attr(kani, kani::proof)]
218    fn u8_len_3_be_bytes_to_storage() {
219        bolero::check!()
220            .with_type()
221            .for_each(|callee: &[u8; 3]| Some(callee.be_bytes_to_storage()));
222    }
223
224    #[test]
225    #[cfg_attr(kani, kani::proof)]
226    fn u8_len_6_be_bytes_to_storage() {
227        bolero::check!()
228            .with_type()
229            .for_each(|callee: &[u8; 6]| Some(callee.be_bytes_to_storage()));
230    }
231
232    #[test]
233    #[cfg_attr(kani, kani::proof)]
234    fn u24_new_truncated() {
235        bolero::check!()
236            .with_type()
237            .cloned()
238            .for_each(|value: u32| Some(u24::new_truncated(value)));
239    }
240
241    #[test]
242    #[cfg_attr(kani, kani::proof)]
243    fn u24_ref_to_be_bytes() {
244        bolero::check!()
245            .with_type()
246            .for_each(|callee: &u24| Some(callee.to_be_bytes()));
247    }
248
249    #[test]
250    #[cfg_attr(kani, kani::proof)]
251    fn u32_try_from() {
252        bolero::check!()
253            .with_type()
254            .cloned()
255            .for_each(|value: u32| Some(u24::try_from(value)));
256    }
257
258    #[test]
259    #[cfg_attr(kani, kani::proof)]
260    fn u8_from() {
261        bolero::check!()
262            .with_type()
263            .cloned()
264            .for_each(|value: u8| Some(u24::from(value)));
265    }
266
267    #[test]
268    #[cfg_attr(kani, kani::proof)]
269    fn u16_from() {
270        bolero::check!()
271            .with_type()
272            .cloned()
273            .for_each(|value: u16| Some(u24::from(value)));
274    }
275
276    #[test]
277    #[cfg_attr(kani, kani::proof)]
278    fn u24_deref() {
279        bolero::check!()
280            .with_type()
281            .cloned()
282            .for_each(|callee: u24| Some(*callee.deref()));
283    }
284
285    #[test]
286    #[cfg_attr(kani, kani::proof)]
287    fn i32_new_truncated() {
288        bolero::check!()
289            .with_type()
290            .cloned()
291            .for_each(|value: i32| Some(i24::new_truncated(value)));
292    }
293
294    #[test]
295    #[cfg_attr(kani, kani::proof)]
296    fn i24_to_be_bytes() {
297        bolero::check!()
298            .with_type()
299            .cloned()
300            .for_each(|callee: i24| Some(callee.to_be_bytes()));
301    }
302
303    #[test]
304    #[cfg_attr(kani, kani::proof)]
305    fn i32_try_from() {
306        bolero::check!()
307            .with_type()
308            .cloned()
309            .for_each(|value: i32| Some(i24::try_from(value)));
310    }
311
312    #[test]
313    #[cfg_attr(kani, kani::proof)]
314    fn i24_from_u8() {
315        bolero::check!()
316            .with_type()
317            .cloned()
318            .for_each(|value: u8| Some(i24::from(value)));
319    }
320
321    #[test]
322    #[cfg_attr(kani, kani::proof)]
323    fn i8_from() {
324        bolero::check!()
325            .with_type()
326            .cloned()
327            .for_each(|value: i8| Some(i24::from(value)));
328    }
329
330    #[test]
331    #[cfg_attr(kani, kani::proof)]
332    fn i24_from_u16() {
333        bolero::check!()
334            .with_type()
335            .cloned()
336            .for_each(|value: u16| Some(i24::from(value)));
337    }
338
339    #[test]
340    #[cfg_attr(kani, kani::proof)]
341    fn i16_from() {
342        bolero::check!()
343            .with_type()
344            .cloned()
345            .for_each(|value: i16| Some(i24::from(value)));
346    }
347
348    #[test]
349    #[cfg_attr(kani, kani::proof)]
350    fn i24_deref() {
351        bolero::check!()
352            .with_type()
353            .cloned()
354            .for_each(|callee: i24| Some(*callee.deref()));
355    }
356
357    #[test]
358    #[cfg_attr(kani, kani::proof)]
359    fn u64_try_from() {
360        bolero::check!()
361            .with_type()
362            .cloned()
363            .for_each(|value: u64| Some(u24::try_from(value)));
364    }
365
366    #[test]
367    #[cfg_attr(kani, kani::proof)]
368    fn u64_new_truncated() {
369        bolero::check!()
370            .with_type()
371            .cloned()
372            .for_each(|value: u64| Some(u48::new_truncated(value)));
373    }
374
375    #[test]
376    #[cfg_attr(kani, kani::proof)]
377    fn u48_to_be_bytes() {
378        bolero::check!()
379            .with_type()
380            .cloned()
381            .for_each(|callee: u48| Some(callee.to_be_bytes()));
382    }
383
384    #[test]
385    #[cfg_attr(kani, kani::proof)]
386    fn u48_try_from_u64() {
387        bolero::check!()
388            .with_type()
389            .cloned()
390            .for_each(|value: u64| Some(u48::try_from(value)));
391    }
392
393    #[test]
394    #[cfg_attr(kani, kani::proof)]
395    fn u8_from_u8() {
396        bolero::check!()
397            .with_type()
398            .cloned()
399            .for_each(|value: u8| Some(u48::from(value)));
400    }
401
402    #[test]
403    #[cfg_attr(kani, kani::proof)]
404    fn u48_from_u16() {
405        bolero::check!()
406            .with_type()
407            .cloned()
408            .for_each(|value: u16| Some(u48::from(value)));
409    }
410
411    #[test]
412    #[cfg_attr(kani, kani::proof)]
413    fn u32_from() {
414        bolero::check!()
415            .with_type()
416            .cloned()
417            .for_each(|value: u32| Some(u48::from(value)));
418    }
419
420    #[test]
421    #[cfg_attr(kani, kani::proof)]
422    fn u48_deref() {
423        bolero::check!()
424            .with_type()
425            .cloned()
426            .for_each(|callee: u48| Some(*callee.deref()));
427    }
428
429    #[test]
430    #[cfg_attr(kani, kani::proof)]
431    fn i64_new_truncated() {
432        bolero::check!()
433            .with_type()
434            .cloned()
435            .for_each(|value: i64| Some(i48::new_truncated(value)));
436    }
437
438    #[test]
439    #[cfg_attr(kani, kani::proof)]
440    fn i48_to_be_bytes() {
441        bolero::check!()
442            .with_type()
443            .cloned()
444            .for_each(|callee: i48| Some(callee.to_be_bytes()));
445    }
446
447    #[test]
448    #[cfg_attr(kani, kani::proof)]
449    fn i64_try_from() {
450        bolero::check!()
451            .with_type()
452            .cloned()
453            .for_each(|value: i64| Some(i48::try_from(value)));
454    }
455
456    #[test]
457    #[cfg_attr(kani, kani::proof)]
458    fn i48_from_u8() {
459        bolero::check!()
460            .with_type()
461            .cloned()
462            .for_each(|value: u8| Some(i48::from(value)));
463    }
464
465    #[test]
466    #[cfg_attr(kani, kani::proof)]
467    fn i48_from_i8() {
468        bolero::check!()
469            .with_type()
470            .cloned()
471            .for_each(|value: i8| Some(i48::from(value)));
472    }
473
474    #[test]
475    #[cfg_attr(kani, kani::proof)]
476    fn i48_from_i48() {
477        bolero::check!()
478            .with_type()
479            .cloned()
480            .for_each(|value: u16| Some(i48::from(value)));
481    }
482
483    #[test]
484    #[cfg_attr(kani, kani::proof)]
485    fn i48_from_i16() {
486        bolero::check!()
487            .with_type()
488            .cloned()
489            .for_each(|value: i16| Some(i48::from(value)));
490    }
491
492    #[test]
493    #[cfg_attr(kani, kani::proof)]
494    fn i48_from_u32() {
495        bolero::check!()
496            .with_type()
497            .cloned()
498            .for_each(|value: u32| Some(i48::from(value)));
499    }
500
501    #[test]
502    #[cfg_attr(kani, kani::proof)]
503    fn i32_from() {
504        bolero::check!()
505            .with_type()
506            .cloned()
507            .for_each(|value: i32| Some(i48::from(value)));
508    }
509
510    #[test]
511    #[cfg_attr(kani, kani::proof)]
512    fn i48_deref() {
513        bolero::check!()
514            .with_type()
515            .cloned()
516            .for_each(|callee: i48| Some(*callee.deref()));
517    }
518}