#cast #struct #zero-copy

no-std structview

Viewing binary data as high-level data structures, safely

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

#693 in Parser implementations

Download history 265/week @ 2023-11-20 181/week @ 2023-11-27 242/week @ 2023-12-04 354/week @ 2023-12-11 225/week @ 2023-12-18 309/week @ 2023-12-25 147/week @ 2024-01-01 220/week @ 2024-01-08 198/week @ 2024-01-15 127/week @ 2024-01-22 186/week @ 2024-01-29 220/week @ 2024-02-05 172/week @ 2024-02-12 132/week @ 2024-02-19 247/week @ 2024-02-26 198/week @ 2024-03-04

782 downloads per month
Used in 6 crates (via twmap)

MIT license

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 impl std::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
~36K SLoC