structview/
lib.rs

1#![deny(missing_docs)]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4//! This crate enables casting references to binary data into references to
5//! higher-level data structures, such as structs, unions, and arrays.
6//!
7//! The implemented approach is similar to a common pattern used when parsing
8//! binary data formats in C, where `char *`s representing the raw data are
9//! directly cast to, for example, struct pointers. This technique has the
10//! benefits of being simple and highly efficient. Unfortunately, it is also
11//! unsafe, as issues with alignment and integer endianess are usually ignored.
12//!
13//! `structview` avoids these issues by providing a safe and convenient
14//! interface to its users: an (automatically derivable) trait [`View`],
15//! as well as types for safely viewing integer fields.
16//!
17//! # Example
18//!
19//! ```
20//! use structview::{u32_le, View};
21//!
22//! #[derive(Clone, Copy, View)]
23//! #[repr(C)]
24//! struct Animal {
25//!     name: [u8; 4],
26//!     number_of_heads: u8,
27//!     number_of_legs: u32_le,
28//! }
29//!
30//! fn main() -> Result<(), structview::Error> {
31//!     let data = [0x43, 0x61, 0x74, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00];
32//!     let animal = Animal::view(&data)?;
33//!
34//!     assert_eq!(animal.name, *b"Cat\x00");
35//!     assert_eq!(animal.number_of_heads, 1);
36//!     assert_eq!(animal.number_of_legs.to_int(), 4);
37//!
38//!     Ok(())
39//! }
40//! ```
41//!
42//! [`View`]: trait.View.html
43
44use byteorder::ByteOrder;
45use core::{fmt, marker::PhantomData, mem, slice};
46
47pub use structview_derive::*;
48
49/// Error type returned when creating a view fails.
50#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
51pub enum Error {
52    /// Creating a view failed because there was not enough data to fill it.
53    NotEnoughData,
54}
55
56impl fmt::Display for Error {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            Self::NotEnoughData => f.write_str("not enough data"),
60        }
61    }
62}
63
64#[cfg(feature = "std")]
65impl std::error::Error for Error {}
66
67/// Trait for viewing byte data as a higher-level representation.
68///
69/// By implementing [`View`] a type declares that it is safe to be cast from
70/// raw byte data.
71///
72/// # Safety
73///
74/// Implementing this trait is unsafe since implementing types must fulfill
75/// some requirements that cannot be checked through the type system only:
76///
77///   - There must be no raw byte values that constitute invalid data for the
78///     implementing type. For example, implementing [`View`] for
79///     `std::num::NonZeroI32` would be unsafe.
80///   - The implementing type must be 1-byte aligned.
81///   - If the implementing type is a compound type, it must be ensured that
82///     the compiler doesn't change the order of fields. This can be achieved
83///     through `#[repr(C)]`.
84///
85/// It is recommended to use the custom `View` derive also provided by this
86/// crate instead of implementing this trait manually.
87///
88/// [`View`]: #trait.View
89pub unsafe trait View: Copy {
90    /// View `data` as a value of the implementing type.
91    ///
92    /// This simply casts the `&[u8]` reference to a reference of the
93    /// implementing type.
94    ///
95    /// # Errors
96    ///
97    /// If `data` is too short to fill the whole view,
98    /// [`Error::NotEnoughData`] will be returned.
99    ///
100    /// [`Error::NotEnoughData`]: enum.Error.html
101    fn view(data: &[u8]) -> Result<&Self, Error> {
102        if data.len() < mem::size_of::<Self>() {
103            return Err(Error::NotEnoughData);
104        }
105
106        let data_ptr = data.as_ptr();
107        let struct_ptr = data_ptr as *const Self;
108        let struct_ref = unsafe { &*struct_ptr };
109
110        Ok(struct_ref)
111    }
112
113    /// View boxed slice `data` as a boxed slice of the implementing type.
114    ///
115    /// This first reinterprets the data as a slice of the implementing type
116    /// and then as a boxed slice
117    ///
118    ///  # Errors
119    ///
120    /// If the length of the u8 boxed slice is not a multiple of the struct's
121    /// size, [`Error::NotEnoughData`] will be returned.
122    ///
123    /// # Panics
124    ///
125    /// If the implementing type has a size of 0.
126    ///
127    /// [`Error::NotEnoughData`]: enum.Error.html
128    #[cfg(feature = "std")]
129    fn view_boxed_slice(data: Box<[u8]>) -> Result<Box<[Self]>, Error> {
130        let size = mem::size_of::<Self>();
131        assert_ne!(size, 0);
132        let len = data.len();
133        if len % size != 0 {
134            return Err(Error::NotEnoughData);
135        }
136        let ptr = Box::into_raw(data);
137        let adj_len = len / size;
138        unsafe {
139            let slice = slice::from_raw_parts_mut(ptr as *mut Self, adj_len);
140            Ok(Box::from_raw(slice))
141        }
142    }
143
144    /// View slice `data` as a slice of the implementing type.
145    ///
146    /// This simply reinterprets the data as a slice of the implementing type.
147    ///
148    ///  # Errors
149    ///
150    /// If the length of the u8 slice is not a multiple of the struct's size,
151    /// [`Error::NotEnoughData`] will be returned.
152    ///
153    /// # Panics
154    ///
155    /// If the implementing type has a size of 0.
156    ///
157    /// [`Error::NotEnoughData`]: enum.Error.html
158    fn view_slice(data: &[u8]) -> Result<&[Self], Error> {
159        let size = mem::size_of::<Self>();
160        assert_ne!(size, 0);
161        let len = data.len();
162        if len % size != 0 {
163            return Err(Error::NotEnoughData);
164        }
165        let ptr = data.as_ptr();
166        let adj_len = len / size;
167        unsafe {
168            Ok(slice::from_raw_parts(ptr as *const Self, adj_len))
169        }
170    }
171}
172
173unsafe impl View for i8 {}
174unsafe impl View for u8 {}
175
176// We implement `View` for arrays of `View` elements of sizes 0 to 256.
177// Hopefully thats enough for most binary parsing needs. Once const generics
178// land (https://github.com/rust-lang/rust/issues/44580), we can get rid of
179// this workaround.
180
181macro_rules! array_view_impls {
182    ( $($N:expr)+ ) => {
183        $( unsafe impl<V: View> View for [V; $N] {} )+
184    };
185}
186
187array_view_impls! {
188      0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19
189     20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39
190     40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59
191     60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79
192     80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99
193
194    100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
195    120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
196    140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
197    160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
198    180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
199
200    200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
201    220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
202    240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
203}
204
205macro_rules! int_view {
206    ( $name:ident, $int:ty, $read:ident, $le:ident, $be:ident ) => {
207        int_view!($name, $int, stringify!($int), $read, $le, $be);
208    };
209
210    ( $name:ident, $int:ty, $int_name:expr, $read:ident, $le:ident, $be:ident ) => {
211        #[doc = "View of an `"]
212        #[doc = $int_name]
213        #[doc = "` value."]
214        #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
215        pub struct $name<BO>([u8; mem::size_of::<$int>()], PhantomData<BO>);
216
217        impl<BO: ByteOrder> $name<BO> {
218            /// Return the viewed integer value.
219            ///
220            /// Calling this function incurs some runtime cost as the raw
221            /// bytes have to be parsed according to the view's endianess.
222            pub fn to_int(&self) -> $int {
223                BO::$read(&self.0)
224            }
225        }
226
227        impl<BO: ByteOrder> fmt::Display for $name<BO> {
228            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229                write!(f, "[{}]", self.to_int())
230            }
231        }
232
233        impl<BO: ByteOrder> From<$name<BO>> for $int {
234            fn from(view: $name<BO>) -> Self {
235                view.to_int()
236            }
237        }
238
239        unsafe impl<BO: Copy> View for $name<BO> {}
240
241        #[doc = "View of a little-endian `"]
242        #[doc = $int_name]
243        #[doc = "` value."]
244        #[allow(non_camel_case_types)]
245        pub type $le = $name<byteorder::LE>;
246
247        #[doc = "View of a big-endian `"]
248        #[doc = $int_name]
249        #[doc = "` value."]
250        #[allow(non_camel_case_types)]
251        pub type $be = $name<byteorder::BE>;
252    };
253}
254
255int_view!(I16, i16, read_i16, i16_le, i16_be);
256int_view!(U16, u16, read_u16, u16_le, u16_be);
257
258int_view!(I32, i32, read_i32, i32_le, i32_be);
259int_view!(U32, u32, read_u32, u32_le, u32_be);
260
261int_view!(I64, i64, read_i64, i64_le, i64_be);
262int_view!(U64, u64, read_u64, u64_le, u64_be);