assembly_core/
buffer.rs

1//! Reading data directly from a buffer
2use displaydoc::Display;
3use thiserror::Error;
4
5/// Errors from casting a minimally-aligned type
6#[derive(Debug, Error, Display)]
7pub enum CastError {
8    /// Some byte between start and end was outside of the given buffer
9    OutOfBounds {
10        /// The offset that failed
11        offset: u32,
12    },
13}
14
15/// Asserts that the type has a minimal ABI alignment of `1`
16///
17/// ## Safety
18///
19/// Implementor need to verify that [`std::mem::align_of`]`::<Self>() == 1`
20pub unsafe trait MinimallyAligned: Sized {}
21
22/// Cast a buffer to a reference
23///
24/// ## Panics
25///
26/// - If the `[offset, offset + size_of::<Self>]` is not contained by the buffer
27pub fn cast<T: MinimallyAligned>(buffer: &[u8], offset: u32) -> &T {
28    try_cast(buffer, offset).unwrap()
29}
30
31/// Try to cast a buffer to a reference
32pub fn try_cast<T: MinimallyAligned>(buffer: &[u8], offset: u32) -> Result<&T, CastError> {
33    let base = buffer.as_ptr();
34    let len = buffer.len();
35
36    if offset as usize + std::mem::size_of::<T>() <= len {
37        unsafe {
38            let addr = base.offset(offset as isize);
39            Ok(&*(addr as *const T))
40        }
41    } else {
42        Err(CastError::OutOfBounds { offset })
43    }
44}
45
46/// Cast a buffer to a slice
47///
48/// ## Panics
49///
50/// - If the `[offset, offset + len]` is not contained by the buffer
51pub fn cast_slice<T: MinimallyAligned>(buffer: &[u8], offset: u32, len: u32) -> &[T] {
52    try_cast_slice(buffer, offset, len).unwrap()
53}
54
55/// Try to cast a buffer to a slice
56pub fn try_cast_slice<T: MinimallyAligned>(
57    buffer: &[u8],
58    offset: u32,
59    len: u32,
60) -> Result<&[T], CastError> {
61    let base = buffer.as_ptr();
62    let buf_len = buffer.len();
63
64    let ulen = len as usize;
65    let needed = std::mem::size_of::<T>() * ulen;
66
67    if offset as usize + needed <= buf_len {
68        unsafe {
69            let addr = base.offset(offset as isize) as *const T;
70            Ok(std::slice::from_raw_parts(addr, ulen))
71        }
72    } else {
73        Err(CastError::OutOfBounds { offset })
74    }
75}
76
77/// Additional methods on byte slices
78pub trait Buffer {
79    /// Try to cast to T
80    fn try_cast<T: MinimallyAligned>(&self, offset: u32) -> Result<&T, CastError>;
81
82    /// Try to cast to T
83    fn try_cast_slice<T: MinimallyAligned>(&self, offset: u32, len: u32)
84        -> Result<&[T], CastError>;
85
86    /// Cast to T
87    fn cast<T: MinimallyAligned>(&self, offset: u32) -> &T;
88
89    /// Cast to slice of T
90    fn cast_slice<T: MinimallyAligned>(&self, offset: u32, len: u32) -> &[T];
91}
92
93impl Buffer for [u8] {
94    /// Try to cast to T
95    fn try_cast<T: MinimallyAligned>(&self, offset: u32) -> Result<&T, CastError> {
96        try_cast(self, offset)
97    }
98
99    /// Try to cast to T
100    fn try_cast_slice<T: MinimallyAligned>(
101        &self,
102        offset: u32,
103        len: u32,
104    ) -> Result<&[T], CastError> {
105        try_cast_slice(self, offset, len)
106    }
107
108    /// Cast to T
109    fn cast<T: MinimallyAligned>(&self, offset: u32) -> &T {
110        cast(self, offset)
111    }
112
113    /// Cast to slice of T
114    fn cast_slice<T: MinimallyAligned>(&self, offset: u32, len: u32) -> &[T] {
115        cast_slice(self, offset, len)
116    }
117}
118
119/// Similar to `From<&U> for T`
120pub trait Repr {
121    /// The value that this struct encodes
122    type Value;
123
124    /// extract the contained value
125    fn extract(&self) -> Self::Value;
126}
127
128/// little-endian u16
129#[repr(C, align(1))]
130pub struct LEU16([u8; 2]);
131
132/// little-endian u32
133#[repr(C, align(1))]
134#[derive(Debug)]
135pub struct LEU32([u8; 4]);
136
137/// little-endian u64
138#[repr(C, align(1))]
139pub struct LEI64([u8; 8]);
140
141unsafe impl MinimallyAligned for LEU16 {}
142
143impl Repr for LEU16 {
144    type Value = u16;
145    fn extract(&self) -> Self::Value {
146        u16::from_le_bytes(self.0)
147    }
148}
149
150unsafe impl MinimallyAligned for LEU32 {}
151
152impl Repr for LEU32 {
153    type Value = u32;
154    fn extract(&self) -> Self::Value {
155        u32::from_le_bytes(self.0)
156    }
157}
158
159unsafe impl MinimallyAligned for LEI64 {}
160
161impl Repr for LEI64 {
162    type Value = i64;
163    fn extract(&self) -> Self::Value {
164        i64::from_le_bytes(self.0)
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test() {
174        let buffer: &[u8] = &[0, 20, 0, 30, 0, 1, 1];
175        let le: &LEU16 = cast(buffer, 1);
176        assert_eq!(le.extract(), 20);
177        let le: &LEU16 = cast(buffer, 3);
178        assert_eq!(le.extract(), 30);
179        let le: &LEU16 = cast(buffer, 5);
180        assert_eq!(le.extract(), 257);
181
182        let les: &[LEU16] = cast_slice(buffer, 1, 3);
183        assert_eq!(les[0].extract(), 20);
184        assert_eq!(les[1].extract(), 30);
185        assert_eq!(les[2].extract(), 257);
186
187        assert_eq!(std::mem::align_of::<LEU16>(), 1);
188    }
189}