binary_layout/
macro_binary_layout.rs

1/// This macro defines a data layout. Given such a layout, the [Field](crate::Field) or [FieldView](crate::FieldView) APIs can be used to access data based on it.
2///
3/// Data layouts define
4/// - a name for the layout
5/// - and endianness for its fields ([BigEndian](crate::BigEndian) or [LittleEndian](crate::LittleEndian))
6/// - and an ordered collection of typed fields.
7///
8/// See [supported field types](crate#supported-field-types) for a list of supported field types.
9///
10/// # API
11/// ```text
12/// binary_layout!(<<Name>>, <<Endianness>>, {
13///   <<FieldName>>: <<FieldType>>,
14///   <<FieldName>>: <<FieldType>>,
15///   ...
16/// });
17/// ```
18///
19/// ## Field names
20/// Field names can be any valid Rust identifiers, but it is recommended to avoid names that contain `storage`, `into_` or `_mut`.
21/// This is because the [binary_layout!](crate::binary_layout!) macro creates a [View class with several accessors](#struct-view) for each field that contain those identifier parts.
22///
23/// ## Example
24/// ```
25/// use binary_layout::prelude::*;
26///
27/// binary_layout!(icmp_packet, BigEndian, {
28///   packet_type: u8,
29///   code: u8,
30///   checksum: u16,
31///   rest_of_header: [u8; 4],
32///   data_section: [u8], // open ended byte array, matches until the end of the packet
33/// });
34/// ```
35///
36/// # Generated code
37/// See [icmp_packet](crate::example::icmp_packet) for an example.
38///
39/// This macro will define a module for you with several members:
40/// - For each field, there will be a struct containing
41///   - metadata like [OFFSET](crate::Field::OFFSET) and [SIZE](crate::Field::SIZE) as rust `const`s
42///   - data accessors for the [Field](crate::Field) API
43/// - The module will also contain a `View` struct that offers the [FieldView](crate::FieldView) API.
44///
45/// This macro will also generate rustdoc documentation for everything it generates. One of the best ways to figure out
46/// how to use the generated layouts is to read the rustdoc documentation that was generated for them.
47///
48/// ## Metadata Example
49/// ```
50/// use binary_layout::prelude::*;
51///
52/// binary_layout!(my_layout, LittleEndian, {
53///   field1: u16,
54///   field2: u32,
55/// });
56/// assert_eq!(2, my_layout::field2::OFFSET);
57/// assert_eq!(Some(4), my_layout::field2::SIZE);
58/// ```
59///
60/// ## struct View
61/// See [icmp_packet::View](crate::example::icmp_packet::View) for an example.
62///
63/// You can create views over a storage by calling `View::new`. Views can be created based on
64/// - Immutable borrowed storage: `&[u8]`
65/// - Mutable borrowed storage: `&mut [u8]`
66/// - Owning storage: impl `AsRef<u8>` (for example: `Vec<u8>`)
67///
68/// The generated `View` struct will offer
69/// - `View::new(storage)` to create a `View`
70/// - `View::into_storage(self)` to destroy a `View` and return the storage held
71///
72/// and it will offer the following accessors for each field
73/// - `${field_name}()`: Read access. This returns a [FieldView](crate::FieldView) instance with read access.
74/// - `${field_name}_mut()`: Read access. This returns a [FieldView](crate::FieldView) instance with write access.
75/// - `into_${field_name}`: Extract access. This destroys the `View` and returns a [FieldView](crate::FieldView) instance owning the storage. Mostly useful for slice fields when you want to return an owning slice.
76#[macro_export]
77macro_rules! binary_layout {
78    ($name: ident, $endianness: ident, {$($field_name: ident : $field_type: ty $(as $underlying_type: ty)?),* $(,)?}) => {
79        $crate::internal::doc_comment!{
80            concat!{"
81            This module is autogenerated. It defines a layout using the [binary_layout] crate based on the following definition:
82            ```ignore
83            binary_layout!(", stringify!($name), ", ", stringify!($endianness), ", {", $("
84                ", stringify!($field_name), ": ", stringify!($field_type), $(" as ", stringify!($underlying_type), )? ",", )* "
85            });
86            ```
87            "},
88            #[allow(dead_code)]
89            pub mod $name {
90                #[allow(unused_imports)]
91                use super::*;
92
93                $crate::binary_layout!(@impl_fields $crate::$endianness, Some(0), {$($field_name : $field_type $(as $underlying_type)?),*});
94
95                $crate::internal::doc_comment!{
96                    concat!{"
97                    The [View] struct defines the [FieldView](crate::FieldView) API.
98                    An instance of [View] wraps a storage (either borrowed or owned)
99                    and allows accessors for the layout fields.
100
101                    This view is based on the following layout definition:
102                    ```ignore
103                    binary_layout!(", stringify!($name), ", ", stringify!($endianness), ", {", $("
104                        ", stringify!($field_name), ": ", stringify!($field_type), $(" as ", stringify!($underlying_type), )? ",",)* "
105                    });
106                    ```
107                    "},
108                    pub struct View<S: AsRef<[u8]>> {
109                        storage: S,
110                    }
111                }
112                impl <S: AsRef<[u8]>> View<S> {
113                    /// You can create views over a storage by calling [View::new].
114                    ///
115                    /// `S` is the type of underlying storage. It can be
116                    /// - Immutable borrowed storage: `&[u8]`
117                    /// - Mutable borrowed storage: `&mut [u8]`
118                    /// - Owning storage: impl `AsRef<u8>` (for example: `Vec<u8>`)
119                    #[inline]
120                    pub fn new(storage: S) -> Self {
121                        Self {storage}
122                    }
123
124                    /// This destroys the view and returns the underlying storage back to you.
125                    /// This is useful if you created an owning view (e.g. based on `Vec<u8>`)
126                    /// and now need the underlying `Vec<u8>` back.
127                    #[inline]
128                    pub fn into_storage(self) -> S {
129                        self.storage
130                    }
131
132                    $crate::binary_layout!(@impl_view_into {$($field_name),*});
133                }
134                impl <S: AsRef<[u8]>> View<S> {
135                    $crate::binary_layout!(@impl_view_asref {$($field_name),*});
136                }
137                impl <S: AsRef<[u8]> + AsMut<[u8]>> View<S> {
138                    $crate::binary_layout!(@impl_view_asmut {$($field_name),*});
139                }
140
141                /// Use this as a marker type for using this layout as a nested field within another layout.
142                ///
143                /// # Example
144                /// ```
145                /// use binary_layout::prelude::*;
146                ///
147                /// binary_layout!(icmp_header, BigEndian, {
148                ///   packet_type: u8,
149                ///   code: u8,
150                ///   checksum: u16,
151                ///   rest_of_header: [u8; 4],
152                /// });
153                /// binary_layout!(icmp_packet, BigEndian, {
154                ///   header: icmp_header::NestedView,
155                ///   data_section: [u8], // open ended byte array, matches until the end of the packet
156                /// });
157                /// # fn main() {}
158                /// ```
159                pub struct NestedView;
160                impl <S: AsRef<[u8]>> $crate::internal::OwningNestedView<$crate::Data<S>> for NestedView where S: AsRef<[u8]> {
161                    type View = View<$crate::Data<S>>;
162
163                    #[inline(always)]
164                    fn into_view(storage: $crate::Data<S>) -> Self::View {
165                        Self::View {storage}
166                    }
167                }
168                impl <S: AsRef<[u8]>> $crate::internal::BorrowingNestedView<S> for NestedView {
169                    type View = View<S>;
170
171                    #[inline(always)]
172                    fn view(storage: S) -> Self::View {
173                        Self::View {storage: storage.into()}
174                    }
175                }
176
177                impl $crate::internal::NestedViewInfo for NestedView {
178                    const SIZE: Option<usize> = SIZE;
179                }
180            }
181        }
182    };
183
184    (@impl_fields $endianness: ty, $offset_accumulator: expr, {}) => {
185        /// Total size of the layout in number of bytes.
186        /// This can be None if the layout ends with an open ended field like a byte slice.
187        pub const SIZE: Option<usize> = $offset_accumulator;
188    };
189    (@impl_fields $endianness: ty, $offset_accumulator: expr, {$name: ident : $type: ty as $underlying_type: ty $(, $($tail:tt)*)?}) => {
190        $crate::internal::doc_comment!{
191            concat!("Metadata and [Field](crate::Field) API accessors for the `", stringify!($name), "` field"),
192            #[allow(non_camel_case_types)]
193            pub type $name = $crate::WrappedField::<$underlying_type, $type, $crate::PrimitiveField::<$underlying_type, $endianness, {$crate::internal::unwrap_field_size($offset_accumulator)}>>;
194        }
195        $crate::binary_layout!(@impl_fields $endianness, ($crate::internal::option_usize_add(<$name as $crate::Field>::OFFSET, <$name as $crate::Field>::SIZE)), {$($($tail)*)?});
196    };
197    (@impl_fields $endianness: ty, $offset_accumulator: expr, {$name: ident : $type: ty $(, $($tail:tt)*)?}) => {
198        $crate::internal::doc_comment!{
199            concat!("Metadata and [Field](crate::Field) API accessors for the `", stringify!($name), "` field"),
200            #[allow(non_camel_case_types)]
201            pub type $name = $crate::PrimitiveField::<$type, $endianness, {$crate::internal::unwrap_field_size($offset_accumulator)}>;
202        }
203        $crate::binary_layout!(@impl_fields $endianness, ($crate::internal::option_usize_add(<$name as $crate::Field>::OFFSET, <$name as $crate::Field>::SIZE)), {$($($tail)*)?});
204    };
205
206    (@impl_view_asref {}) => {};
207    (@impl_view_asref {$name: ident $(, $name_tail: ident)*}) => {
208        $crate::internal::doc_comment!{
209            concat!("Return a [FieldView](crate::FieldView) with read access to the `", stringify!($name), "` field"),
210            #[inline]
211            pub fn $name(&self) -> <$name as $crate::internal::StorageToFieldView<&[u8]>>::View {
212                <$name as $crate::internal::StorageToFieldView<&[u8]>>::view(self.storage.as_ref())
213            }
214        }
215        $crate::binary_layout!(@impl_view_asref {$($name_tail),*});
216    };
217
218    (@impl_view_asmut {}) => {};
219    (@impl_view_asmut {$name: ident $(, $name_tail: ident)*}) => {
220        $crate::internal::paste!{
221            $crate::internal::doc_comment!{
222                concat!("Return a [FieldView](crate::FieldView) with write access to the `", stringify!($name), "` field"),
223                #[inline]
224                pub fn [<$name _mut>](&mut self) -> <$name as $crate::internal::StorageToFieldView<&mut [u8]>>::View {
225                    <$name as $crate::internal::StorageToFieldView<&mut [u8]>>::view(self.storage.as_mut())
226                }
227            }
228        }
229        $crate::binary_layout!(@impl_view_asmut {$($name_tail),*});
230    };
231
232    (@impl_view_into {}) => {};
233    (@impl_view_into {$name: ident $(, $name_tail: ident)*}) => {
234        $crate::internal::paste!{
235            $crate::internal::doc_comment!{
236                concat!("Destroy the [View] and return a field accessor to the `", stringify!($name), "` field owning the storage. This is mostly useful for [FieldView::extract](crate::FieldView::extract)"),
237                #[inline]
238                pub fn [<into_ $name>](self) -> <$name as $crate::internal::StorageIntoFieldView<S>>::View {
239                    <$name as $crate::internal::StorageIntoFieldView<S>>::into_view(self.storage)
240                }
241            }
242        }
243        $crate::binary_layout!(@impl_view_into {$($name_tail),*});
244    };
245}
246
247/// Deprecated name for [crate::binary_layout!]. Please switch to [crate::binary_layout!].
248#[deprecated = "The `define_layout!` macro was renamed to `binary_layout!` and the old name will be removed in future versions."]
249#[macro_export]
250macro_rules! define_layout {
251    ($name: ident, $endianness: ident, {$($field_name: ident : $field_type: ty $(as $underlying_type: ty)?),* $(,)?}) => {
252        $crate::binary_layout!($name, $endianness, {$($field_name : $field_type $(as $underlying_type)?),*});
253    }
254}
255
256// TODO This only exists because Option<usize>::unwrap() isn't const. Remove this once it is.
257/// Internal function, don't use!
258/// Unwraps an `Option<usize>`
259#[inline(always)]
260pub const fn unwrap_field_size(opt: Option<usize>) -> usize {
261    match opt {
262        Some(x) => x,
263        None => {
264            panic!("Error: Fields without a static size (e.g. open-ended byte arrays) can only be used at the end of a layout");
265        }
266    }
267}
268
269/// Internal function, don't use!
270#[inline(always)]
271pub const fn option_usize_add(lhs: usize, rhs: Option<usize>) -> Option<usize> {
272    match (lhs, rhs) {
273        (lhs, Some(rhs)) => Some(lhs + rhs),
274        (_, None) => None,
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use crate::prelude::*;
281
282    use rand::{rngs::StdRng, RngCore, SeedableRng};
283
284    #[cfg(feature = "std")]
285    fn data_region_vec(size: usize, seed: u64) -> Vec<u8> {
286        let mut rng = StdRng::seed_from_u64(seed);
287        let mut res = vec![0; size];
288        rng.fill_bytes(&mut res);
289        res
290    }
291
292    fn data_region(seed: u64) -> [u8; 1024] {
293        let mut rng = StdRng::seed_from_u64(seed);
294        let mut res = [0; 1024];
295        rng.fill_bytes(&mut res);
296        res
297    }
298
299    binary_layout!(module_level_layout, LittleEndian, {
300        first: i8,
301        second: i64,
302        third: u16,
303    });
304
305    #[test]
306    fn layouts_can_be_defined_at_module_level() {
307        let storage: [u8; 1024] = [0; 1024];
308        let view = module_level_layout::View::new(storage);
309        assert_eq!(0, view.third().read());
310    }
311
312    #[test]
313    fn layouts_can_be_defined_at_function_level() {
314        binary_layout!(function_level_layout, LittleEndian, {
315            first: i8,
316            second: i64,
317            third: u16,
318        });
319
320        let storage: [u8; 1024] = [0; 1024];
321        let view = function_level_layout::View::new(storage);
322        assert_eq!(0, view.third().read());
323    }
324
325    #[test]
326    fn can_be_created_with_and_without_trailing_comma() {
327        binary_layout!(first, LittleEndian, { field: u8 });
328        binary_layout!(second, LittleEndian, {
329            field: u8,
330            second: u16
331        });
332        binary_layout!(third, LittleEndian, {
333            field: u8,
334        });
335        binary_layout!(fourth, LittleEndian, {
336            field: u8,
337            second: u16,
338        });
339    }
340
341    #[cfg(feature = "std")]
342    #[test]
343    fn there_can_be_multiple_views_if_readonly_vec() {
344        binary_layout!(my_layout, BigEndian, {
345            field1: u16,
346            field2: i64,
347        });
348
349        let storage = data_region_vec(1024, 0);
350        let view1 = my_layout::View::new(&storage);
351        let view2 = my_layout::View::new(&storage);
352        view1.field1().read();
353        view2.field1().read();
354    }
355
356    #[test]
357    fn there_can_be_multiple_views_if_readonly_array() {
358        binary_layout!(my_layout, BigEndian, {
359            field1: u16,
360            field2: i64,
361        });
362
363        let storage = data_region(0);
364        let view1 = my_layout::View::new(&storage);
365        let view2 = my_layout::View::new(&storage);
366        view1.field1().read();
367        view2.field1().read();
368    }
369
370    #[test]
371    fn size_of_sized_layout() {
372        binary_layout!(my_layout, LittleEndian, {
373            field1: u16,
374            field2: i64,
375        });
376        assert_eq!(Some(10), my_layout::SIZE);
377    }
378
379    #[test]
380    fn size_of_unsized_layout() {
381        binary_layout!(my_layout, LittleEndian, {
382            field: u16,
383            tail: [u8],
384        });
385        assert_eq!(None, my_layout::SIZE);
386    }
387}