6 releases (2 stable)
1.1.0 | Jun 5, 2020 |
---|---|
1.0.0 | Mar 15, 2020 |
0.3.1 | Oct 7, 2019 |
0.3.0 | Jan 13, 2019 |
0.1.0 | Dec 19, 2018 |
#805 in Parser implementations
617 downloads per month
Used in 9 crates
(via twmap)
18KB
129 lines
structview
structview
is a Rust library for casting references to binary data into
references to higher-level data structures, such as structs, unions, and arrays.
The implemented approach is similar to a common pattern used when parsing binary
data formats in C, where char *
s representing the raw data are directly cast
to, for example, struct pointers. This technique has the benefits of being
simple and highly efficient. Unfortunately, it is also unsafe, as issues with
alignment and integer endianess are usually ignored. structview
avoids these
issues by providing a safe interface to its users.
The intended use-case for this crate is parsing of binary data formats,
particularly if one is only interested in the value of certain fields, not all
of them. In these cases structview
can be used to efficiently find the fields
of interest without having to potentially perform byteorder conversion for all
the irrelevant ones. If all fields need to be parsed anyway, it is probably more
straightforward to use the byteorder
crate
directly.
Example
The following example demonstrates viewing a slice of binary data as a simple struct:
use structview::{u32_le, View};
#[derive(Clone, Copy, View)]
#[repr(C)]
struct Animal {
name: [u8; 4],
number_of_heads: u8,
number_of_legs: u32_le,
}
fn main() -> Result<(), structview::Error> {
let data = [0x43, 0x61, 0x74, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00];
let animal = Animal::view(&data)?;
assert_eq!(animal.name, *b"Cat\x00");
assert_eq!(animal.number_of_heads, 1);
assert_eq!(animal.number_of_legs.to_int(), 4);
Ok(())
}
As the example shows, structview
makes reinterpreting raw data both safe and
convenient by providing an (automatically derivable) trait View
,
as well as types for safely viewing integer fields.
Requirements
Requires Rust version 1.38.0 or newer.
The View
Trait
By implementing the View
trait, a type promises it is safe to be cast from raw
binary data. The trait adds several view methods to implementing types, which
enable producing references to instances of these types from byte slices:
pub unsafe trait View: Copy {
fn view(data: &[u8]) -> Result<&Self, structview::Error> { ... }
fn view_slice(data: &[u8]) -> Result<&[Self], Error> { ... }
fn view_boxed_slice(data: Box<[u8]>) -> Result<Box<[Self]>, Error> { ... }
}
All view methods check the length of the given data
and return
Error::NotEnoughData
if there are too few bytes. Additionally, the two
slice view methods require that mem::size_of::<Self>() > 0
and will panic
otherwise.
Implementing View
is unsafe as one must ensure that:
- every possible raw byte value constitutes valid data for the implementing type
- the implementing type is 1-byte aligned
- the compiler doesn't change the order of the implementing type's fields (in case of a compound type)
structview
already implements View
for the 1-byte integer types (i8
and
u8
), arrays of View
types, and the provided integer views (u32_le
and
friends). Based on these primitives, users can create views for their own
structs and unions.
Manually implementing the View
trait is not recommended. Instead, it should be
automatically derived as demonstrated in the example above. The derive ensures
safety be enforcing that implementing structs and unions are repr(C)
and
contain only View
fields. This is sufficient to satisfy the safety
requirements mentioned above.
Integer Views
While the single-byte integers i8
and u8
can be safely cast from raw data,
wider integers can not: Their alignment is incompatible with the 1-byte
alignment of byte slices and their (application-defined) endianess might be
incompatible with the system's native byteorder.
structview
solves this by casting these wider integer types to special integer
views instead: wrappers around u8
arrays of appropriate sizes that provide
methods to parse the raw data into the actual integers.
The u32_le
type used in the example is one of these integer views. It is
actually an alias for U32<LittleEndian>
, a type generic over a ByteOrder
supplied by the byteorder
crate. The
following table lists all provided integer views:
bit-width | generic | little-endian | big-endian |
---|---|---|---|
16 | I16<BO> |
i16_le |
i16_be |
U16<BO> |
u16_le |
u16_be |
|
32 | I32<BO> |
i32_le |
i32_be |
U32<BO> |
u32_le |
u32_be |
|
64 | I64<BO> |
i64_le |
i64_be |
U64<BO> |
u64_le |
u64_be |
Each integer view provides a to_int
method that parses and returns the
respective integer value. Each integer view also implements the From
conversion trait for its
integer type.
Use in no_std
contexts
structview
has a feature, std
, that is enabled by default. To use
the crate in no_std
contexts, disable the default features in the
Cargo.toml
:
[dependencies.structview]
version = "1"
default-features = false
If std
is disabled:
structview::Error
does not implstd::error::Error
View::view_boxed_slice
is not available
License
This project is licensed under the MIT license (LICENSE or http://opensource.org/licenses/MIT).
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in structview by you, shall be licensed as above, without any additional terms or conditions.
Dependencies
~1.5MB
~37K SLoC