binary_layout/
lib.rs

1//! The [binary-layout](https://crates.io/crates/binary-layout) library allows type-safe, inplace, zero-copy access to structured binary data.
2//! You define a custom data layout and give it a slice of binary data, and it will allow you to read and
3//! write the fields defined in the layout from the binary data without having to copy any of the data.
4//! It's similar to transmuting to/from a `#[repr(packed)]` struct, but [much safer](#why-not-reprpacked).
5//!
6//! Note that the data does not go through serialization/deserialization or a parsing step.
7//! All accessors access the underlying packet data directly.
8//!
9//! This crate is `#[no_std]` compatible.
10//!
11//! # Example
12//! ```
13//! use binary_layout::prelude::*;
14//!
15//! // See https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol for ICMP packet layout
16//! binary_layout!(icmp_packet, BigEndian, {
17//!   packet_type: u8,
18//!   code: u8,
19//!   checksum: u16,
20//!   rest_of_header: [u8; 4],
21//!   data_section: [u8], // open ended byte array, matches until the end of the packet
22//! });
23//!
24//! fn func(packet_data: &mut [u8]) {
25//!   let mut view = icmp_packet::View::new(packet_data);
26//!
27//!   // read some data
28//!   let code: u8 = view.code().read();
29//!   // equivalent: let code: u8 = packet_data[1];
30//!
31//!   // write some data
32//!   view.checksum_mut().write(10);
33//!   // equivalent: packet_data[2..4].copy_from_slice(&10u16.to_be_bytes());
34//!
35//!   // access an open ended byte array
36//!   let data_section: &[u8] = view.data_section();
37//!   // equivalent: let data_section: &[u8] = &packet_data[8..];
38//!
39//!   // and modify it
40//!   view.data_section_mut()[..5].copy_from_slice(&[1, 2, 3, 4, 5]);
41//!   // equivalent: packet_data[8..13].copy_from_slice(&[1, 2, 3, 4, 5]);
42//! }
43//! ```
44//!
45//! See the [icmp_packet](crate::example::icmp_packet) module for what this [binary_layout!] macro generates for you.
46//!
47//! # What to use this library for?
48//! Anything that needs inplace zero-copy access to structured binary data.
49//! - Network packets are an obvious example
50//! - File system inodes
51//! - Structured binary data in files if you want to avoid explicit (de)serialization, possibly in combination with [memmap](https://docs.rs/memmap).
52//!
53//! ## Why use this library?
54//! - Inplace, zero-copy, type-safe access to your data.
55//! - Data layout is defined in one central place, call sites can't accidentally use wrong field offsets.
56//! - Convenient and simple macro DSL to define layouts.
57//! - Define a fixed endianness in the layout, ensuring cross platform compatibility.
58//! - Fully written in safe Rust, no [std::mem::transmute](https://doc.rust-lang.org/std/mem/fn.transmute.html) or similar shenanigans.
59//! - Const generics ensure that all offset calculations happen at compile time.
60//!   This, together with inlining annotations, makes this library zero-overhead.
61//!   Using it is just as performant as writing manual slice accesses into your code.
62//! - Comprehensive test coverage.
63//!
64//! ## Why not `#[repr(packed)]`?
65//! Annotating structs with `#[repr(packed)]` gives some of the features of this crate, namely it lays out the data fields exactly in the order they're specified
66//! without padding. But it has serious shortcomings that this library solves.
67//! - `#[repr(packed)]` uses the system byte order, which will be different depending on if you're running on a little endian or big endian system. `#[repr(packed)]` is not cross-platform compatible. This library is.
68//! - `#[repr(packed)]` [can cause undefined behavior on some CPUs when taking references to unaligned data](https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacked).
69//!    This library avoids that by not offering any API that takes references to unaligned data. Primitive integer types are allowed to be unaligned but they're copied and you can't get references to them.
70//!    The only data type you can get a reference to is byte arrays, and they only require an alignment of 1 which is trivially always fulfilled.
71//!
72//! ## When not to use this library?
73//! - You need dynamic data structures, e.g. a list that can change size. This library only supports static data layouts (with the exception of open ended byte arrays at the end of a layout).
74//! - Not all of your layout fits into the memory and you need to process streams of data.
75//!   Note that this crate can still be helpful if you have smaller layouted packets as part of a larger stream, as long as any one layouted packet fits into memory.
76//!
77//! ## Alternatives
78//! To the best of my knowledge, there is no other library offering inplace, zero-copy and type-safe access to structured binary data.
79//! But if you don't need direct access to your data and are ok with a serialization/deserialization step, then there is a number of amazing libraries out there.
80//! - [Nom](https://crates.io/crates/nom) is a great crate for all your parsing needs. It can for example parse binary data and put them in your custom structs.
81//! - [Binread](https://crates.io/crates/binread), [Binwrite](https://crates.io/crates/binwrite), [Binrw](https://crates.io/crates/binrw) are great libraries for (de)serializing binary data.
82//!
83//! # APIs
84//! Layouts are defined using the [binary_layout!] macro. Based on such a layout, this library offers two alternative APIs for data access:
85//! 1. The [trait@Field] API that offers free functions to read/write the data based on an underlying slice of storage (`packet_data` in the example above) holding the packet data. This API does not wrap the underlying slice of storage data, which means you have to pass it in to each accessor.
86//!    This is not the API used in the example above, see [trait@Field] for an API example.
87//! 2. The [struct@FieldView] API that wraps a slice of storage data and remembers it in a `View` object, allowing access to the fields without having to pass in the packed data slice each time. This is the API used in the example above. See [struct@FieldView] for another example.
88//!
89//! ## Supported field types
90//!
91//! ### Primitive integer types
92//! - [u8](https://doc.rust-lang.org/stable/core/primitive.u8.html), [u16](https://doc.rust-lang.org/stable/core/primitive.u16.html), [u32](https://doc.rust-lang.org/stable/core/primitive.u32.html), [u64](https://doc.rust-lang.org/stable/core/primitive.u64.html), [u128](https://doc.rust-lang.org/stable/core/primitive.u128.html)
93//! - [i8](https://doc.rust-lang.org/stable/core/primitive.i8.html), [i16](https://doc.rust-lang.org/stable/core/primitive.i16.html), [i32](https://doc.rust-lang.org/stable/core/primitive.i32.html), [i64](https://doc.rust-lang.org/stable/core/primitive.i64.html), [i128](https://doc.rust-lang.org/stable/core/primitive.i128.html)
94//!
95//! For these fields, the [trait@Field] API offers [FieldReadExt::read], [FieldWriteExt::write], [FieldCopyAccess::try_read], [FieldCopyAccess::try_write] and the [struct@FieldView] API offers [FieldView::read] and [FieldView::write].
96//!
97//! ### Primitive float types
98//! - [f32](https://doc.rust-lang.org/core/primitive.f32.html), [f64](https://doc.rust-lang.org/core/primitive.f64.html)
99//!
100//! ### Non-zero primitive integer types
101//! - [NonZeroU8](https://doc.rust-lang.org/core/num/struct.NonZeroU8.html), [NonZeroU16](https://doc.rust-lang.org/core/num/struct.NonZeroU16.html), [NonZeroU32](https://doc.rust-lang.org/core/num/struct.NonZeroU32.html), [NonZeroU64](https://doc.rust-lang.org/core/num/struct.NonZeroU64.html), [NonZeroU128](https://doc.rust-lang.org/core/num/struct.NonZeroU128.html)
102//! - [NonZeroI8](https://doc.rust-lang.org/core/num/struct.NonZeroI8.html), [NonZeroI16](https://doc.rust-lang.org/core/num/struct.NonZeroI16.html), [NonZeroI32](https://doc.rust-lang.org/core/num/struct.NonZeroI32.html), [NonZeroI64](https://doc.rust-lang.org/core/num/struct.NonZeroI64.html), [NonZeroI128](https://doc.rust-lang.org/core/num/struct.NonZeroI128.html)
103//!
104//! Reading a zero values will throw an error. Because of this, [FieldReadExt::read] and [FieldView::read] are not available for those types and you need to use [FieldCopyAccess::try_read] and [FieldView::try_read].
105//!
106//! ### bool, char
107//! [bool](https://doc.rust-lang.org/stable/core/primitive.bool.html) and [char](https://doc.rust-lang.org/stable/core/primitive.char.html) are supported using the `bool as u8` and `char as u32` data type notation.
108//!
109//! Note that not only `0u8` and `1u8` are valid boolean values and not all [u32](https://doc.rust-lang.org/stable/core/primitive.u32.html) values are valid unicode code points.
110//! Reading invalid values will throw an error. Because of this, [FieldReadExt::read] and [FieldView::read] are not available for those types and you need to use [FieldCopyAccess::try_read] and [FieldView::try_read].
111//!
112//! ### Primitive Zero-Sized Types (ZSTs)
113//!
114//! ZSTs neither read nor write to the underlying storage, but the appropriate traits are implemented for them to support derive macros which may require all members of a struct to implement or enum to also support the various traits.
115//!
116//! - [`()`](https://doc.rust-lang.org/core/primitive.unit.html), also known as the `unit` type.
117//!
118//! ### Fixed size byte arrays: `[u8; N]`.
119//! For these fields, the [trait@Field] API offers [FieldSliceAccess::data], [FieldSliceAccess::data_mut], and the [struct@FieldView] API returns a slice.
120//!
121//! ### Open ended byte arrays: `[u8]`.
122//! This field type can only occur as the last field of a layout and will mach the remaining data until the end of the storage.
123//! This field has a dynamic size, depending on how large the packet data is.
124//! For these fields, the [trait@Field] API offers [FieldSliceAccess::data], [FieldSliceAccess::data_mut] and the [struct@FieldView] API returns a slice.
125//!
126//! ### Custom field types
127//! You can define your own custom types as long as they implement the [trait@LayoutAs] trait to define how to convert them from/to a primitive type.
128//!
129//! # Data types maybe supported in the future
130//! These data types aren't supported yet, but they could be added in theory and might be added in future versions.
131//! - bit fields / [bool](https://doc.rust-lang.org/stable/core/primitive.bool.html) stored as 1 bit
132//!
133//! ### Data types with dynamic length
134//! This crate relies on a static layout, it cannot support data types with dynamic length.
135//! In theory, types with dynamic length could be supported if they either
136//! - are the last field of a layout, an already implemented example of this are open ended byte arrays.
137//! - or they may be in the middle of the packet but have a maximal size defined and will always reserve storage for their maximal size, even if smaller.
138//!   This way, the fields after it would still have a constant offset.
139//!
140//! Both of these, however, would be some effort to implement and it is unclear if that will ever happen (unless somebody opens a PR for it).
141//!
142//! ### Strings
143//! For strings, note that even fixed-size UTF-8 strings take a variable number of bytes because of the UTF-8 encoding and that brings all the issues of data types with dynamic length with it.
144//! This is why strings aren't supported yet.
145//!
146//! ### Fixed-size arrays other than `[u8; N]`
147//! Say we wanted to have a `[u32; N]` field. The API couldn't just return a zero-copy `&[u32; N]` to the caller because that would use the system byte order (i.e. endianness) which might be different from the byte order defined in the packet layout.
148//! To make this cross-platform compatible, we'd have to wrap these slices into our own slice type that enforces the correct byte order and return that from the API.
149//! This complexity is why it wasn't implemented yet, but feel free to open a PR if you need this.
150//!
151//! # Nesting
152//! Layouts can be nested within each other by using the `NestedView` type created by the [binary_layout!] macro for one layout as a field type in another layout.
153//!
154//! Example:
155//! ```
156//! use binary_layout::prelude::*;
157//!
158//! binary_layout!(icmp_header, BigEndian, {
159//!   packet_type: u8,
160//!   code: u8,
161//!   checksum: u16,
162//!   rest_of_header: [u8; 4],
163//! });
164//! binary_layout!(icmp_packet, BigEndian, {
165//!   header: icmp_header::NestedView,
166//!   data_section: [u8], // open ended byte array, matches until the end of the packet
167//! });
168//! # fn main() {}
169//! ```
170//!
171//! Nested layouts do not need to have the same endianess.  The following, which
172//! is copied from the complete example at `tests/nested.rs` in this repository,
173//! shows how you can mix different endian layouts together:
174//!
175//! ```rust
176//! use binary_layout::prelude::*;
177//! use core::convert::TryInto;
178//!
179//! binary_layout!(deep_nesting, LittleEndian, {
180//!     field1: u16,
181//! });
182//! binary_layout!(header, BigEndian, {
183//!     field1: i16,
184//! });
185//! binary_layout!(middle, NativeEndian, {
186//!     deep: deep_nesting::NestedView,
187//!     field1: u16,
188//! });
189//! binary_layout!(footer, BigEndian, {
190//!     field1: u32,
191//!     deep: deep_nesting::NestedView,
192//!     tail: [u8],
193//! });
194//! binary_layout!(whole, LittleEndian, {
195//!     head: header::NestedView,
196//!     field1: u64,
197//!     mid: middle::NestedView,
198//!     field2: u128,
199//!     foot: footer::NestedView,
200//! });
201//! # fn main() {}
202//! ```
203
204#![cfg_attr(not(feature = "std"), no_std)]
205#![forbid(unsafe_code)]
206#![deny(missing_docs)]
207
208mod endianness;
209mod fields;
210mod macro_binary_layout;
211mod utils;
212
213pub mod example;
214
215pub use endianness::{BigEndian, Endianness, LittleEndian, NativeEndian};
216pub use fields::{
217    primitive::{
218        FieldCopyAccess, FieldReadExt, FieldSliceAccess, FieldView, FieldWriteExt,
219        NonZeroIsZeroError, PrimitiveField,
220    },
221    wrapped::{LayoutAs, WrappedField, WrappedFieldError},
222    Field,
223};
224pub use utils::{data::Data, infallible::InfallibleResultExt};
225
226/// Import this to get everything into scope that you need for defining and using layouts.
227///
228/// # Example
229/// ```
230/// use binary_layout::prelude::*;
231/// ```
232pub mod prelude {
233    pub use super::{
234        BigEndian, Field, FieldCopyAccess, FieldReadExt, FieldSliceAccess, FieldWriteExt,
235        InfallibleResultExt, LittleEndian, NativeEndian, NonZeroIsZeroError,
236    };
237    pub use crate::binary_layout;
238    #[allow(deprecated)]
239    pub use crate::define_layout;
240}
241
242/// Internal things that need to be exported so our macros can use them. Don't use directly!
243#[doc(hidden)]
244pub mod internal {
245    pub use crate::fields::{
246        primitive::{BorrowingNestedView, NestedViewInfo, OwningNestedView},
247        StorageIntoFieldView, StorageToFieldView,
248    };
249    pub use crate::macro_binary_layout::{option_usize_add, unwrap_field_size};
250    pub use doc_comment::doc_comment;
251    pub use paste::paste;
252}