binary_layout/fields/primitive/copy_access/
primitive_nonzero_int.rs

1use core::convert::Infallible;
2
3use super::{FieldCopyAccess, PrimitiveField};
4use crate::endianness::{EndianKind, Endianness};
5use crate::fields::primitive::view::FieldView;
6use crate::fields::{Field, StorageIntoFieldView, StorageToFieldView};
7
8macro_rules! nonzero_int_field {
9    ($type:ty, $zero_type:ty) => {
10        impl<E: Endianness, const OFFSET_: usize> FieldCopyAccess for PrimitiveField<$type, E, OFFSET_> {
11            /// See [FieldCopyAccess::ReadError]
12            type ReadError = NonZeroIsZeroError;
13            /// See [FieldCopyAccess::WriteError]
14            type WriteError = Infallible;
15            /// See [FieldCopyAccess::HighLevelType]
16            type HighLevelType = $type;
17
18            doc_comment::doc_comment! {
19                concat! {"
20                Read the integer field from a given data region, assuming the defined layout, using the [Field] API.
21
22                # Example:
23
24                ```
25                use binary_layout::prelude::*;
26
27                binary_layout!(my_layout, LittleEndian, {
28                    //... other fields ...
29                    some_integer_field: ", stringify!($type), "
30                    //... other fields ...
31                });
32
33                fn func(storage_data: &[u8]) -> Result<",stringify!($type), ", NonZeroIsZeroError>{
34                    let read: ", stringify!($type), " = my_layout::some_integer_field::try_read(storage_data)?;
35                    Ok(read)
36                }
37                ```
38                "},
39                #[inline(always)]
40                fn try_read(storage: &[u8]) -> Result<$type, NonZeroIsZeroError> {
41                    let value: [u8; core::mem::size_of::<$type>()] = storage[Self::OFFSET..(Self::OFFSET + core::mem::size_of::<$type>())].try_into().unwrap();
42                    let value = match E::KIND {
43                        EndianKind::Big => <$zero_type>::from_be_bytes(value),
44                        EndianKind::Little => <$zero_type>::from_le_bytes(value),
45                        EndianKind::Native => <$zero_type>::from_ne_bytes(value)
46                    };
47                    <$type>::new(value).ok_or(NonZeroIsZeroError(()))
48                }
49            }
50
51            doc_comment::doc_comment! {
52                concat! {"
53                Write the integer field to a given data region, assuming the defined layout, using the [Field] API.
54
55                # Example:
56
57                ```
58                use binary_layout::prelude::*;
59                use core::convert::Infallible;
60
61                binary_layout!(my_layout, LittleEndian, {
62                    //... other fields ...
63                    some_integer_field: ", stringify!($type), "
64                    //... other fields ...
65                });
66
67                fn func(storage_data: &mut [u8]) {
68                    let value = ", stringify!($type), "::new(10).unwrap();
69                    my_layout::some_integer_field::try_write(storage_data, value).unwrap();
70                }
71                ```
72                "},
73                #[inline(always)]
74                fn try_write(storage: &mut [u8], value: $type) -> Result<(), Infallible> {
75                    let value_as_bytes = match E::KIND {
76                        EndianKind::Big => value.get().to_be_bytes(),
77                        EndianKind::Little => value.get().to_le_bytes(),
78                        EndianKind::Native => value.get().to_ne_bytes(),
79                    };
80                    storage[Self::OFFSET..(Self::OFFSET + core::mem::size_of::<$type>())]
81                        .copy_from_slice(&value_as_bytes);
82                    Ok(())
83                }
84            }
85        }
86
87        impl_field_traits!($type);
88    };
89}
90
91/// This error is thrown when trying to read a non-zero integer type, e.g. [NonZeroU32](core::num::NonZeroU32),
92/// but the data being read was actually zero.
93#[derive(Debug)]
94pub struct NonZeroIsZeroError(pub(crate) ());
95
96impl core::fmt::Display for NonZeroIsZeroError {
97    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
98        write!(fmt, "NonZeroIsZeroError")
99    }
100}
101
102#[cfg(feature = "std")]
103impl std::error::Error for NonZeroIsZeroError {}
104
105nonzero_int_field!(core::num::NonZeroI8, i8);
106nonzero_int_field!(core::num::NonZeroI16, i16);
107nonzero_int_field!(core::num::NonZeroI32, i32);
108nonzero_int_field!(core::num::NonZeroI64, i64);
109nonzero_int_field!(core::num::NonZeroI128, i128);
110nonzero_int_field!(core::num::NonZeroU8, u8);
111nonzero_int_field!(core::num::NonZeroU16, u16);
112nonzero_int_field!(core::num::NonZeroU32, u32);
113nonzero_int_field!(core::num::NonZeroU64, u64);
114nonzero_int_field!(core::num::NonZeroU128, u128);
115
116#[cfg(test)]
117mod tests {
118    use crate::prelude::*;
119    use crate::PrimitiveField;
120
121    macro_rules! test_nonzero {
122        ($type:ty, $underlying_type:ty, $expected_size:expr, $value1:expr, $value2:expr) => {
123            test_nonzero!(@case, $type, $underlying_type, $expected_size, $value1, $value2, little, LittleEndian, from_le_bytes);
124            test_nonzero!(@case, $type, $underlying_type, $expected_size, $value1, $value2, big, BigEndian, from_be_bytes);
125            test_nonzero!(@case, $type, $underlying_type, $expected_size, $value1, $value2, native, NativeEndian, from_ne_bytes);
126        };
127        (@case, $type:ty, $underlying_type:ty, $expected_size:expr, $value1:expr, $value2: expr, $endian:ident, $endian_type:ty, $endian_fn:ident) => {
128            $crate::internal::paste! {
129                #[allow(non_snake_case)]
130                #[test]
131                fn [<test_ $type _ $endian endian_metadata>]() {
132                    type Field1 = PrimitiveField<$type, $endian_type, 5>;
133                    type Field2 = PrimitiveField<$type, $endian_type, 123>;
134                    type Field3 = PrimitiveField<$type, $endian_type, 150>;
135
136                    assert_eq!(Some($expected_size), Field1::SIZE);
137                    assert_eq!(5, Field1::OFFSET);
138                    assert_eq!(Some($expected_size), Field2::SIZE);
139                    assert_eq!(123, Field2::OFFSET);
140                    assert_eq!(Some($expected_size), Field3::SIZE);
141                    assert_eq!(150, Field3::OFFSET);
142                }
143
144                #[allow(non_snake_case)]
145                #[test]
146                fn [<test_ $type _ $endian endian_fieldapi_tryread_write>]() {
147                    let mut storage = [0; 1024];
148
149                    let value1 = <$type>::new($value1).unwrap();
150                    let value2 = <$type>::new($value2).unwrap();
151
152                    type Field1 = PrimitiveField<$type, $endian_type, 5>;
153                    type Field2 = PrimitiveField<$type, $endian_type, 123>;
154                    type Field3 = PrimitiveField<$type, $endian_type, 150>;
155
156                    Field1::write(&mut storage, value1);
157                    Field2::write(&mut storage, value2);
158                    // don't write Field3, that should leave it at zero
159
160                    assert_eq!(value1, Field1::try_read(&storage).unwrap());
161                    assert_eq!(value2, Field2::try_read(&storage).unwrap());
162                    assert!(matches!(Field3::try_read(&storage), Err(NonZeroIsZeroError(_))));
163
164                    assert_eq!(value1, $type::new($underlying_type::$endian_fn((&storage[5..(5+$expected_size)]).try_into().unwrap())).unwrap());
165                    assert_eq!(value2, $type::new($underlying_type::$endian_fn((&storage[123..(123+$expected_size)]).try_into().unwrap())).unwrap());
166                    assert_eq!(0, $underlying_type::$endian_fn((&storage[150..(150+$expected_size)]).try_into().unwrap()));
167                }
168
169                #[allow(non_snake_case)]
170                #[test]
171                fn [<test_ $type _ $endian endian_fieldapi_tryread_trywrite>]() {
172                    use crate::InfallibleResultExt;
173
174                    let mut storage = [0; 1024];
175
176                    let value1 = <$type>::new($value1).unwrap();
177                    let value2 = <$type>::new($value2).unwrap();
178
179                    type Field1 = PrimitiveField<$type, $endian_type, 5>;
180                    type Field2 = PrimitiveField<$type, $endian_type, 123>;
181                    type Field3 = PrimitiveField<$type, $endian_type, 150>;
182
183                    Field1::try_write(&mut storage, value1).infallible_unwrap();
184                    Field2::try_write(&mut storage, value2).infallible_unwrap();
185                    // don't write Field3, that should leave it at zero
186
187                    assert_eq!(value1, Field1::try_read(&storage).unwrap());
188                    assert_eq!(value2, Field2::try_read(&storage).unwrap());
189                    assert!(matches!(Field3::try_read(&storage), Err(NonZeroIsZeroError(_))));
190
191                    assert_eq!(value1, $type::new($underlying_type::$endian_fn((&storage[5..(5+$expected_size)]).try_into().unwrap())).unwrap());
192                    assert_eq!(value2, $type::new($underlying_type::$endian_fn((&storage[123..(123+$expected_size)]).try_into().unwrap())).unwrap());
193                    assert_eq!(0, $underlying_type::$endian_fn((&storage[150..(150+$expected_size)]).try_into().unwrap()));
194                }
195
196                #[allow(non_snake_case)]
197                #[test]
198                fn [<test_ $type _ $endian endian_viewapi_tryread_write>]() {
199                    binary_layout!(layout, $endian_type, {
200                        field1: $type,
201                        field2: $type,
202                        field3: $type,
203                    });
204                    let mut storage = [0; 1024];
205                    let mut view = layout::View::new(&mut storage);
206
207                    let value1 = <$type>::new($value1).unwrap();
208                    let value2 = <$type>::new($value2).unwrap();
209
210                    view.field1_mut().write(value1);
211                    view.field2_mut().write(value2);
212                    // don't write Field3, that should leave it at zero
213
214                    assert_eq!(value1, view.field1().try_read().unwrap());
215                    assert_eq!(value2, view.field2().try_read().unwrap());
216                    assert!(matches!(view.field3().try_read(), Err(NonZeroIsZeroError(_))));
217
218                    assert_eq!(value1, $type::new($underlying_type::$endian_fn((&storage[0..($expected_size)]).try_into().unwrap())).unwrap());
219                    assert_eq!(value2, $type::new($underlying_type::$endian_fn((&storage[$expected_size..(2*$expected_size)]).try_into().unwrap())).unwrap());
220                    assert_eq!(0, $underlying_type::$endian_fn((&storage[2*$expected_size..(3*$expected_size)]).try_into().unwrap()));
221                }
222
223                #[allow(non_snake_case)]
224                #[test]
225                fn [<test_ $type _ $endian endian_viewapi_tryread_trywrite>]() {
226                    binary_layout!(layout, $endian_type, {
227                        field1: $type,
228                        field2: $type,
229                        field3: $type,
230                    });
231                    let mut storage = [0; 1024];
232                    let mut view = layout::View::new(&mut storage);
233
234                    let value1 = <$type>::new($value1).unwrap();
235                    let value2 = <$type>::new($value2).unwrap();
236
237                    view.field1_mut().try_write(value1).infallible_unwrap();
238                    view.field2_mut().try_write(value2).infallible_unwrap();
239                    // don't write Field3, that should leave it at zero
240
241                    assert_eq!(value1, view.field1().try_read().unwrap());
242                    assert_eq!(value2, view.field2().try_read().unwrap());
243                    assert!(matches!(view.field3().try_read(), Err(NonZeroIsZeroError(_))));
244
245                    assert_eq!(value1, $type::new($underlying_type::$endian_fn((&storage[0..($expected_size)]).try_into().unwrap())).unwrap());
246                    assert_eq!(value2, $type::new($underlying_type::$endian_fn((&storage[$expected_size..(2*$expected_size)]).try_into().unwrap())).unwrap());
247                    assert_eq!(0, $underlying_type::$endian_fn((&storage[2*$expected_size..(3*$expected_size)]).try_into().unwrap()));
248                }
249            }
250        };
251    }
252
253    use core::num::{
254        NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16,
255        NonZeroU32, NonZeroU64, NonZeroU8,
256    };
257
258    test_nonzero!(NonZeroI8, i8, 1, 50, -20);
259    test_nonzero!(NonZeroI16, i16, 2, 500, -2000);
260    test_nonzero!(NonZeroI32, i32, 4, 10i32.pow(8), -(10i32.pow(7)));
261    test_nonzero!(NonZeroI64, i64, 8, 10i64.pow(15), -(10i64.pow(14)));
262    test_nonzero!(NonZeroI128, i128, 16, 10i128.pow(30), -(10i128.pow(28)));
263
264    test_nonzero!(NonZeroU8, u8, 1, 50, 20);
265    test_nonzero!(NonZeroU16, u16, 2, 500, 2000);
266    test_nonzero!(NonZeroU32, u32, 4, 10u32.pow(8), (10u32.pow(7)));
267    test_nonzero!(NonZeroU64, u64, 8, 10u64.pow(15), (10u64.pow(14)));
268    test_nonzero!(NonZeroU128, u128, 16, 10u128.pow(30), (10u128.pow(28)));
269}