assembly_data/fdb/ro/
buffer.rs

1//! # General methods for aligned access to a byte buffer
2
3use crate::fdb::{
4    common::Latin1Str,
5    file::{
6        FDBHeader, FDBRowHeader, FDBRowHeaderListEntry, FDBTableDataHeader, FDBTableDefHeader,
7        FDBTableHeader,
8    },
9};
10use assembly_core::{
11    buffer::{CastError, MinimallyAligned},
12    displaydoc::Display,
13};
14use bytemuck::from_bytes;
15use std::{
16    cmp::Ordering,
17    convert::TryInto,
18    fmt,
19    mem::size_of,
20    ops::{Deref, Range},
21};
22use thiserror::Error;
23
24#[derive(Error, Debug, Display, Clone, PartialEq, Eq)]
25/// Error for handling a buffer
26pub enum BufferError {
27    /// index out of bounds {0:?}
28    OutOfBounds(Range<usize>),
29    /// index not aligned {0}
30    Unaligned(usize),
31}
32
33#[derive(Copy, Clone)]
34/// Wrapper around a immutable reference to a byte slice
35pub struct Buffer<'a>(&'a [u8]);
36
37/// Result with a [`BufferError`]
38pub type Res<T> = Result<T, BufferError>;
39
40impl<'a> Deref for Buffer<'a> {
41    type Target = [u8];
42
43    fn deref(&self) -> &Self::Target {
44        self.0
45    }
46}
47
48/// Compares two name strings
49///
50/// ## Safety
51///
52/// This panics if name_bytes does not contains a null terminator
53pub(crate) fn compare_bytes(bytes: &[u8], name_bytes: &[u8]) -> Ordering {
54    for i in 0..bytes.len() {
55        match name_bytes[i].cmp(&bytes[i]) {
56            Ordering::Equal => {}
57            Ordering::Less => {
58                // the null terminator is a special case of this one
59                return Ordering::Less;
60            }
61            Ordering::Greater => {
62                return Ordering::Greater;
63            }
64        }
65    }
66    if name_bytes[bytes.len()] == 0 {
67        Ordering::Equal
68    } else {
69        Ordering::Greater
70    }
71}
72
73/// Get a reference to a type at the given address of this buffer
74///
75/// This functions checks whether the offset and alignment is valid
76pub fn get_at<T>(buf: &[u8], addr: usize) -> Res<&T> {
77    let base = buf.as_ptr();
78    let len = buf.len();
79    let size = std::mem::size_of::<T>();
80    let align = std::mem::align_of::<T>();
81
82    let needed = addr
83        .checked_add(size)
84        .ok_or(BufferError::OutOfBounds(addr..len))?;
85
86    if needed > len {
87        return Err(BufferError::OutOfBounds(addr..needed));
88    }
89
90    let start = unsafe { base.add(addr) };
91    if 0 != start.align_offset(align) {
92        return Err(BufferError::Unaligned(addr));
93    }
94    Ok(unsafe { &*(start as *const T) })
95}
96
97/// Get a reference to a slice at the given address of this buffer
98///
99/// This functions checks whether the offset and alignment is valid
100pub fn get_slice_at<T>(buf: &[u8], addr: usize, count: usize) -> Res<&[T]> {
101    let base = buf.as_ptr();
102    let len = buf.len();
103    let size = std::mem::size_of::<T>();
104    let align = std::mem::align_of::<T>();
105
106    let slice_bytes = size
107        .checked_mul(count)
108        .ok_or(BufferError::OutOfBounds(addr..len))?;
109
110    let needed = addr
111        .checked_add(slice_bytes)
112        .ok_or(BufferError::OutOfBounds(addr..len))?;
113
114    if needed > len {
115        return Err(BufferError::OutOfBounds(addr..needed));
116    }
117
118    let start = unsafe { base.add(addr) };
119    if 0 != start.align_offset(align) {
120        return Err(BufferError::Unaligned(addr));
121    }
122
123    Ok(unsafe { &*(std::ptr::slice_from_raw_parts(start as *const T, count)) })
124}
125
126/// Get the database header
127#[cfg(target_endian = "little")]
128pub fn header_ref(buf: &[u8]) -> Res<&FDBHeader> {
129    get_at(buf, 0)
130}
131
132/// Get the header of the file.
133pub fn header(buf: &[u8], _: ()) -> Res<FDBHeader> {
134    Ok(*header_ref(buf)?)
135}
136
137/// Get the table slice
138pub fn table_headers<'a>(buf: &'a [u8], header: &'a FDBHeader) -> Res<&'a [FDBTableHeader]> {
139    get_slice_at(
140        buf,
141        header.tables.base_offset as usize,
142        header.tables.count as usize,
143    )
144}
145
146/// Get the table definition reference
147pub fn table_definition_ref(
148    buf: &[u8],
149    header: FDBTableHeader,
150) -> Res<&FDBTableDefHeader> {
151    get_at(buf, header.table_def_header_addr as usize)
152}
153
154/// Get the table data reference
155pub fn table_data_ref(buf: &[u8], header: FDBTableHeader) -> Res<&FDBTableDataHeader> {
156    get_at(buf, header.table_data_header_addr as usize)
157}
158
159/// Get the table definition header
160pub fn table_definition(buf: &[u8], header: FDBTableHeader) -> Res<FDBTableDefHeader> {
161    table_definition_ref(buf, header).map(|x| *x)
162}
163
164/// Get the table data header
165pub fn table_data(buf: &[u8], header: FDBTableHeader) -> Res<FDBTableDataHeader> {
166    table_data_ref(buf, header).map(|x| *x)
167}
168
169/// Compares the name given by `bytes` with the one referenced in `table_header`
170pub fn cmp_table_header_name(buf: &[u8], bytes: &[u8], table_header: FDBTableHeader) -> Ordering {
171    let def_header_addr = table_header.table_def_header_addr;
172    // FIXME: what to do with this unwrap?
173    let def_header = get_at::<FDBTableDefHeader>(buf, def_header_addr as usize).unwrap();
174    let name_addr = def_header.table_name_addr as usize;
175
176    let name_bytes = buf.get(name_addr..).unwrap();
177
178    compare_bytes(bytes, name_bytes)
179}
180
181impl<'a> Buffer<'a> {
182    /// Creates a new instance.
183    pub fn new(buf: &'a [u8]) -> Self {
184        Self(buf)
185    }
186
187    /// Returns the contained byte slice
188    pub fn as_bytes(self) -> &'a [u8] {
189        self.0
190    }
191
192    /// Try to cast to T
193    pub fn try_cast<T: MinimallyAligned>(
194        self,
195        offset: u32,
196    ) -> std::result::Result<&'a T, CastError> {
197        assembly_core::buffer::try_cast(self.as_bytes(), offset)
198    }
199
200    /// Try to cast to T
201    pub fn try_cast_slice<T: MinimallyAligned>(
202        self,
203        offset: u32,
204        len: u32,
205    ) -> std::result::Result<&'a [T], CastError> {
206        assembly_core::buffer::try_cast_slice(self.as_bytes(), offset, len)
207    }
208
209    /// Get a subslice a the given offset of the given length
210    pub fn get_len_at(self, start: usize, len: usize) -> Res<&'a [u8]> {
211        let end = start + len;
212        self.0
213            .get(Range { start, end })
214            .ok_or(BufferError::OutOfBounds(Range { start, end }))
215    }
216
217    /// Get a buffer as a latin1 string
218    pub fn string(self, addr: u32) -> Res<&'a Latin1Str> {
219        let start = addr as usize;
220        let mut buf = self.0.get(start..).ok_or_else(|| {
221            let end = self.0.len();
222            BufferError::OutOfBounds(Range { start, end })
223        })?;
224        if let Some(nullpos) = memchr::memchr(0, buf) {
225            buf = buf.split_at(nullpos).0;
226        }
227        Ok(Latin1Str::new(buf))
228    }
229
230    /// Get i64
231    pub fn i64(self, addr: u32) -> Res<i64> {
232        let start = addr as usize;
233        let end = start + size_of::<u64>();
234        if end > self.0.len() {
235            Err(BufferError::OutOfBounds(Range { start, end }))
236        } else {
237            let (_, base) = self.0.split_at(start);
238            let (bytes, _) = base.split_at(size_of::<u64>());
239            let val = i64::from_le_bytes(bytes.try_into().unwrap());
240            Ok(val)
241        }
242    }
243
244    /// Get the table definition header at the given addr.
245    pub fn table_def_header(&self, addr: u32) -> Res<FDBTableDefHeader> {
246        let buf = self.get_len_at(addr as usize, 12)?;
247        let (a, buf) = buf.split_at(4);
248        let (b, c) = buf.split_at(4);
249        Ok(FDBTableDefHeader {
250            column_count: u32::from_le_bytes(a.try_into().unwrap()),
251            table_name_addr: u32::from_le_bytes(b.try_into().unwrap()),
252            column_header_list_addr: u32::from_le_bytes(c.try_into().unwrap()),
253        })
254    }
255
256    /// Get the table data header at the given addr.
257    pub fn table_data_header(self, addr: u32) -> Res<FDBTableDataHeader> {
258        let buf = self.get_len_at(addr as usize, 8)?;
259        Ok(*from_bytes(buf))
260    }
261
262    /// Get the `FDBRowHeader` list entry at the given addr.
263    pub fn row_header_list_entry(self, addr: u32) -> Res<FDBRowHeaderListEntry> {
264        let buf = self.get_len_at(addr as usize, 8)?;
265        let (a, b) = buf.split_at(4);
266        Ok(FDBRowHeaderListEntry {
267            row_header_addr: u32::from_le_bytes(a.try_into().unwrap()),
268            row_header_list_next_addr: u32::from_le_bytes(b.try_into().unwrap()),
269        })
270    }
271
272    /// Get the `FDBRowHeader` at the given addr.
273    pub fn row_header(self, addr: u32) -> Res<FDBRowHeader> {
274        let buf = self.get_len_at(addr as usize, 8)?;
275        Ok(*from_bytes(buf))
276    }
277}
278
279impl<'a> fmt::Debug for Buffer<'a> {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        f.debug_struct("Buffer")
282            .field("base", &self.0.as_ptr())
283            .field("len", &self.0.len())
284            .finish()
285    }
286}