1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use core::convert::TryInto;

use crate::{errors::ErrorKind, slice_helpers::SliceWithStartOffset, TableRead, TableReadUnion};

#[derive(Copy, Clone, Debug)]
pub struct Table<'buf> {
    object: SliceWithStartOffset<'buf>,
    vtable: &'buf [u8],
}

impl<'buf> Table<'buf> {
    pub fn from_buffer(
        buffer: SliceWithStartOffset<'buf>,
        field_offset: usize,
    ) -> Result<Self, ErrorKind> {
        let field_value = u32::from_buffer(buffer, field_offset)?;
        let object_offset = field_offset
            .checked_add(field_value as usize)
            .ok_or(ErrorKind::InvalidOffset)?;
        let object = buffer.advance(object_offset)?;

        let vtable_offset_relative = i32::from_buffer(buffer, object_offset)?;
        let vtable_offset: usize = (object_offset as i64)
            .checked_sub(vtable_offset_relative as i64)
            .ok_or(ErrorKind::InvalidOffset)?
            .try_into()
            .map_err(|_| ErrorKind::InvalidOffset)?;

        let vtable_size = u16::from_buffer(buffer, vtable_offset)?;
        if vtable_size < 4 || vtable_size % 2 != 0 {
            return Err(ErrorKind::InvalidVtableLength {
                length: vtable_size,
            });
        }
        let vtable_full = buffer
            .advance(vtable_offset)?
            .as_slice()
            .get(..vtable_size as usize)
            .ok_or(ErrorKind::InvalidLength)?;
        let vtable = vtable_full.get(4..).ok_or(ErrorKind::InvalidOffset)?;

        Ok(Self { object, vtable })
    }

    pub fn access<T: TableRead<'buf>>(
        &self,
        vtable_offset: usize,
        type_: &'static str,
        method: &'static str,
    ) -> crate::Result<Option<T>> {
        let offset = self
            .vtable
            .get(2 * vtable_offset..2 * (vtable_offset + 1))
            .unwrap_or(&[0, 0]);
        let offset = u16::from_le_bytes(offset.try_into().unwrap()) as usize;
        if offset != 0 {
            T::from_buffer(self.object, offset)
                .map(Some)
                .map_err(|error_kind| crate::errors::Error {
                    source_location: crate::errors::ErrorLocation {
                        type_,
                        method,
                        byte_offset: self.object.offset_from_start,
                    },
                    error_kind,
                })
        } else {
            Ok(None)
        }
    }

    pub fn access_required<T: TableRead<'buf>>(
        &self,
        vtable_offset: usize,
        type_: &'static str,
        method: &'static str,
    ) -> crate::Result<T> {
        self.access(vtable_offset, type_, method)?
            .ok_or(crate::errors::Error {
                source_location: crate::errors::ErrorLocation {
                    type_,
                    method,
                    byte_offset: self.object.offset_from_start,
                },
                error_kind: ErrorKind::MissingRequired,
            })
    }

    pub fn access_union<T: TableReadUnion<'buf>>(
        &self,
        vtable_offset: usize,
        type_: &'static str,
        method: &'static str,
    ) -> crate::Result<Option<T>> {
        let make_error = |error_kind| crate::errors::Error {
            source_location: crate::errors::ErrorLocation {
                type_,
                method,
                byte_offset: self.object.offset_from_start,
            },
            error_kind,
        };

        if let Some(offset) = self.vtable.get(2 * vtable_offset..2 * (vtable_offset + 2)) {
            let tag_offset = u16::from_le_bytes(offset[..2].try_into().unwrap()) as usize;
            let value_offset = u16::from_le_bytes(offset[2..].try_into().unwrap()) as usize;
            let tag = u8::from_buffer(self.object, tag_offset).map_err(make_error)?;
            if tag_offset != 0 && value_offset != 0 && tag != 0 {
                T::from_buffer(self.object, value_offset, tag)
                    .map(Some)
                    .map_err(make_error)
            } else {
                Ok(None)
            }
        } else if self.vtable.len() <= 2 * vtable_offset {
            Ok(None)
        } else {
            Err(make_error(ErrorKind::InvalidVtableLength {
                // The slice does not contain the vtable or objects
                // size, but they are included for the error message
                length: self.vtable.len() as u16 + 4,
            }))
        }
    }

    pub fn access_union_required<T: TableReadUnion<'buf>>(
        &self,
        vtable_offset: usize,
        type_: &'static str,
        method: &'static str,
    ) -> crate::Result<T> {
        self.access_union(vtable_offset, type_, method)?
            .ok_or(crate::errors::Error {
                source_location: crate::errors::ErrorLocation {
                    type_,
                    method,
                    byte_offset: self.object.offset_from_start,
                },
                error_kind: ErrorKind::MissingRequired,
            })
    }
}