1use crate::error::{Result, SqliteError};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PageKind {
20 InteriorIndex,
21 InteriorTable,
22 LeafIndex,
23 LeafTable,
24}
25
26impl PageKind {
27 fn from_byte(b: u8) -> Option<PageKind> {
28 match b {
29 2 => Some(PageKind::InteriorIndex),
30 5 => Some(PageKind::InteriorTable),
31 10 => Some(PageKind::LeafIndex),
32 13 => Some(PageKind::LeafTable),
33 _ => None,
34 }
35 }
36
37 pub fn is_interior(self) -> bool {
38 matches!(self, PageKind::InteriorIndex | PageKind::InteriorTable)
39 }
40
41 pub fn is_table(self) -> bool {
42 matches!(self, PageKind::InteriorTable | PageKind::LeafTable)
43 }
44
45 fn header_len(self) -> usize {
48 if self.is_interior() {
49 12
50 } else {
51 8
52 }
53 }
54}
55
56pub struct BtreePage {
58 pub number: u32,
59 pub kind: PageKind,
60 pub right_most: Option<u32>,
63 bytes: Vec<u8>,
64 usable: usize,
65 cell_pointers: Vec<u16>,
66}
67
68impl BtreePage {
69 pub(crate) fn parse(
72 number: u32,
73 bytes: Vec<u8>,
74 usable: usize,
75 body_offset: usize,
76 ) -> Result<BtreePage> {
77 if body_offset + 8 > usable {
80 return Err(SqliteError::malformed(
81 number,
82 "page is too small to hold a b-tree header",
83 ));
84 }
85
86 let kind = PageKind::from_byte(bytes[body_offset]).ok_or_else(|| {
87 SqliteError::malformed(
88 number,
89 format!(
90 "page type {} is not one of 2, 5, 10 or 13",
91 bytes[body_offset]
92 ),
93 )
94 })?;
95 let header_len = kind.header_len();
96 if body_offset + header_len > usable {
97 return Err(SqliteError::malformed(
98 number,
99 "page is too small to hold an interior b-tree header",
100 ));
101 }
102
103 let cell_count = be16(&bytes, body_offset + 3) as usize;
104 let pointers_end = body_offset + header_len + cell_count * 2;
105 if pointers_end > usable {
106 return Err(SqliteError::malformed(
107 number,
108 format!("{cell_count} cell pointers do not fit in {usable} usable bytes"),
109 ));
110 }
111
112 let content_start = match be16(&bytes, body_offset + 5) {
115 0 => 65536,
116 n => n as usize,
117 };
118 if content_start > usable {
119 return Err(SqliteError::malformed(
120 number,
121 format!("cell content area starts at {content_start}, past the usable {usable}"),
122 ));
123 }
124 if cell_count > 0 && content_start < pointers_end {
125 return Err(SqliteError::malformed(
126 number,
127 format!(
128 "cell content area starts at {content_start}, inside the header and pointer \
129 array which end at {pointers_end}"
130 ),
131 ));
132 }
133
134 let mut cell_pointers = Vec::with_capacity(cell_count);
135 for i in 0..cell_count {
136 let at = be16(&bytes, body_offset + header_len + i * 2) as usize;
137 if at < pointers_end || at >= usable {
138 return Err(SqliteError::malformed(
139 number,
140 format!(
141 "cell {i} points at offset {at}, outside the content area \
142 {pointers_end}..{usable}"
143 ),
144 ));
145 }
146 cell_pointers.push(at as u16);
147 }
148
149 let right_most = if kind.is_interior() {
150 let child = be32(&bytes, body_offset + 8);
151 if child == 0 {
152 return Err(SqliteError::malformed(
153 number,
154 "interior page has a right most child pointer of 0",
155 ));
156 }
157 Some(child)
158 } else {
159 None
160 };
161
162 Ok(BtreePage {
163 number,
164 kind,
165 right_most,
166 bytes,
167 usable,
168 cell_pointers,
169 })
170 }
171
172 pub fn cell_count(&self) -> usize {
173 self.cell_pointers.len()
174 }
175
176 pub fn cell(&self, index: usize) -> Result<&[u8]> {
181 let at = *self.cell_pointers.get(index).ok_or_else(|| {
182 SqliteError::malformed(
183 self.number,
184 format!("cell {index} requested, page has {}", self.cell_count()),
185 )
186 })? as usize;
187 Ok(&self.bytes[at..self.usable])
188 }
189}
190
191pub(crate) fn be16(bytes: &[u8], at: usize) -> u16 {
192 u16::from_be_bytes([bytes[at], bytes[at + 1]])
193}
194
195pub(crate) fn be32(bytes: &[u8], at: usize) -> u32 {
196 u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]])
197}