structview 0.1.0

Viewing binary data as high-level data structures, safely.
Documentation
#![deny(missing_docs)]

//! This crate enables 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 and convenient
//! interface to its users: an (automatically derivable) trait [`View`],
//! as well as types for safely viewing integer fields.
//!
//! # Example
//!
//! ```
//! use structview::{u32_le, View, ViewError};
//!
//! #[derive(Clone, Copy, View)]
//! #[repr(C)]
//! struct Animal {
//!     name: [u8; 4],
//!     number_of_heads: u8,
//!     number_of_legs: u32_le,
//! }
//!
//! fn main() -> Result<(), ViewError> {
//!     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(())
//! }
//! ```
//!
//! [`View`]: trait.View.html

use byteorder::{ByteOrder, ReadBytesExt};
use std::{error::Error, fmt, marker::PhantomData, mem};

pub use structview_derive::*;

/// Error type returned when creating a view fails.
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum ViewError {
    /// Creating a view failed because there was not enough data to fill it.
    NotEnoughData,
}

impl fmt::Display for ViewError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ViewError::NotEnoughData => f.write_str("not enough data"),
        }
    }
}

impl Error for ViewError {}

/// Trait for viewing byte data as a higher-level representation.
///
/// By implementing [`View`] a type declares that it is safe to be cast from
/// raw byte data.
///
/// # Safety
///
/// Implementing this trait is unsafe since implementing types must fulfill
/// some requirements that cannot be checked through the type system only:
///
///   - There must be no raw byte values that constitute invalid data for the
///     implementing type. For example, implementing [`View`] for
///     `std::num::NonZeroI32` would be unsafe.
///   - The implementing type must be 1-byte aligned.
///   - If the implementing type is a compound type, it must be ensured that
///     the compiler doesn't change the order of fields. This can be achieved
///     through `#[repr(C)]`.
///
/// It is recommended to use the custom `View` derive also provided by this
/// crate instead of implementing this trait manually.
///
/// [`View`]: #trait.View
pub unsafe trait View: Copy {
    /// View `data` as a value of the implementing type.
    ///
    /// This simply casts the `&[u8]` reference to a reference of the
    /// implementing type.
    ///
    /// # Errors
    ///
    /// If `data` is too short to fill the whole view,
    /// [`ViewError::NotEnoughData`] will be returned.
    ///
    /// [`ViewError::NotEnoughData`]: enum.ViewError.html
    fn view(data: &[u8]) -> Result<&Self, ViewError> {
        if data.len() < mem::size_of::<Self>() {
            return Err(ViewError::NotEnoughData);
        }

        let data_ptr = data.as_ptr();
        let struct_ptr = data_ptr as *const Self;
        let struct_ref = unsafe { &*struct_ptr };

        Ok(struct_ref)
    }
}

unsafe impl View for i8 {}
unsafe impl View for u8 {}

// We implement `View` for arrays of `View` elements of sizes 0 to 32, like std
// does for other traits. Once const generics land, we can get rid of this
// workaround (https://github.com/rust-lang/rust/issues/44580).

macro_rules! array_view_impls {
    ( $($N:expr)+ ) => {
        $( unsafe impl<V: View> View for [V; $N] {} )+
    };
}

array_view_impls! {
     0  1  2  3  4  5  6  7  8  9
    10 11 12 13 14 15 16 17 18 19
    20 21 22 23 24 25 26 27 28 29
    30 31 32
}

macro_rules! int_view {
    ( $name:ident, $int:ty, $read:ident, $le:ident, $be:ident ) => {
        int_view!($name, $int, stringify!($int), $read, $le, $be);
    };

    ( $name:ident, $int:ty, $int_name:expr, $read:ident, $le:ident, $be:ident ) => {
        #[doc = "View of an `"]
        #[doc = $int_name]
        #[doc = "` value."]
        #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
        pub struct $name<BO>([u8; mem::size_of::<$int>()], PhantomData<BO>);

        impl<BO: ByteOrder> $name<BO> {
            /// Return the viewed integer value.
            ///
            /// Calling this function incurs some runtime cost as the raw
            /// bytes have to be parsed according to the view's endianess.
            pub fn to_int(&self) -> $int {
                (&self.0[..]).$read::<BO>().unwrap()
            }
        }

        impl<BO: ByteOrder> fmt::Display for $name<BO> {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                write!(f, "[{}]", self.to_int())
            }
        }

        impl<BO: ByteOrder> From<$name<BO>> for $int {
            fn from(view: $name<BO>) -> Self {
                view.to_int()
            }
        }

        unsafe impl<BO: Copy> View for $name<BO> {}

        #[doc = "View of a little-endian `"]
        #[doc = $int_name]
        #[doc = "` value."]
        #[allow(non_camel_case_types)]
        pub type $le = $name<byteorder::LE>;

        #[doc = "View of a big-endian `"]
        #[doc = $int_name]
        #[doc = "` value."]
        #[allow(non_camel_case_types)]
        pub type $be = $name<byteorder::BE>;
    };
}

int_view!(I16, i16, read_i16, i16_le, i16_be);
int_view!(U16, u16, read_u16, u16_le, u16_be);

int_view!(I32, i32, read_i32, i32_le, i32_be);
int_view!(U32, u32, read_u32, u32_le, u32_be);

int_view!(I64, i64, read_i64, i64_le, i64_be);
int_view!(U64, u64, read_u64, u64_le, u64_be);