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);