assembly_fdb/ro/
buffer.rs

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