1#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6#[cfg(feature = "checksum")]
7use byteorder::{ByteOrder, LittleEndian};
8
9use crate::error::FormatError;
10
11#[derive(Debug, Clone)]
13pub struct BTreeV2Header {
14 pub tree_type: u8,
16 pub node_size: u32,
18 pub record_size: u16,
20 pub depth: u16,
22 pub root_node_address: u64,
24 pub num_records_in_root: u16,
26 pub total_records: u64,
28}
29
30#[derive(Debug, Clone)]
32pub struct BTreeV2Record {
33 pub data: Vec<u8>,
35}
36
37fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
38 let s = size as usize;
39 if pos + s > data.len() {
40 return Err(FormatError::UnexpectedEof {
41 expected: pos + s,
42 available: data.len(),
43 });
44 }
45 Ok(match size {
46 2 => u16::from_le_bytes([data[pos], data[pos + 1]]) as u64,
47 4 => u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as u64,
48 8 => u64::from_le_bytes([
49 data[pos], data[pos + 1], data[pos + 2], data[pos + 3],
50 data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7],
51 ]),
52 _ => return Err(FormatError::InvalidOffsetSize(size)),
53 })
54}
55
56fn ensure_len(data: &[u8], pos: usize, needed: usize) -> Result<(), FormatError> {
57 match pos.checked_add(needed) {
58 Some(end) if end <= data.len() => Ok(()),
59 _ => Err(FormatError::UnexpectedEof {
60 expected: pos.saturating_add(needed),
61 available: data.len(),
62 }),
63 }
64}
65
66fn bytes_for_max_records(max_nrec: u64) -> usize {
69 if max_nrec == 0 {
70 return 1;
71 }
72 let bits = 64 - max_nrec.leading_zeros() as usize;
73 bits.div_ceil(8)
74}
75
76fn read_var_uint(data: &[u8], pos: usize, width: usize) -> Result<u64, FormatError> {
78 ensure_len(data, pos, width)?;
79 let mut val = 0u64;
80 for i in 0..width {
81 val |= (data[pos + i] as u64) << (i * 8);
82 }
83 Ok(val)
84}
85
86impl BTreeV2Header {
87 pub fn parse(
89 file_data: &[u8],
90 offset: usize,
91 offset_size: u8,
92 length_size: u8,
93 ) -> Result<BTreeV2Header, FormatError> {
94 ensure_len(file_data, offset, 4)?;
95 if &file_data[offset..offset + 4] != b"BTHD" {
96 return Err(FormatError::InvalidBTreeV2Signature);
97 }
98
99 ensure_len(file_data, offset, 4 + 1 + 1 + 4 + 2 + 2 + 1 + 1)?;
100 let version = file_data[offset + 4];
101 if version != 0 {
102 return Err(FormatError::InvalidBTreeV2Version(version));
103 }
104
105 let tree_type = file_data[offset + 5];
106 let node_size = u32::from_le_bytes([
107 file_data[offset + 6],
108 file_data[offset + 7],
109 file_data[offset + 8],
110 file_data[offset + 9],
111 ]);
112 let record_size = u16::from_le_bytes([file_data[offset + 10], file_data[offset + 11]]);
113 let depth = u16::from_le_bytes([file_data[offset + 12], file_data[offset + 13]]);
114 let _split_percent = file_data[offset + 14];
115 let _merge_percent = file_data[offset + 15];
116
117 let mut pos = offset + 16;
118 let root_node_address = read_offset(file_data, pos, offset_size)?;
119 pos += offset_size as usize;
120
121 ensure_len(file_data, pos, 2)?;
122 let num_records_in_root =
123 u16::from_le_bytes([file_data[pos], file_data[pos + 1]]);
124 pos += 2;
125
126 let total_records = read_offset(file_data, pos, length_size)?;
127 #[allow(unused_assignments)]
128 {
129 pos += length_size as usize;
130 }
131
132 #[cfg(feature = "checksum")]
134 {
135 ensure_len(file_data, pos, 4)?;
136 let stored = LittleEndian::read_u32(&file_data[pos..pos + 4]);
137 let computed = crate::checksum::jenkins_lookup3(&file_data[offset..pos]);
138 if computed != stored {
139 return Err(FormatError::ChecksumMismatch {
140 expected: stored,
141 computed,
142 });
143 }
144 }
145
146 Ok(BTreeV2Header {
147 tree_type,
148 node_size,
149 record_size,
150 depth,
151 root_node_address,
152 num_records_in_root,
153 total_records,
154 })
155 }
156}
157
158fn max_records_leaf(node_size: u32, record_size: u16) -> u64 {
162 let overhead = 10u32;
164 if node_size <= overhead || record_size == 0 {
165 return 0;
166 }
167 ((node_size - overhead) / record_size as u32) as u64
168}
169
170pub fn collect_btree_v2_records(
172 file_data: &[u8],
173 header: &BTreeV2Header,
174 offset_size: u8,
175 length_size: u8,
176) -> Result<Vec<BTreeV2Record>, FormatError> {
177 if header.total_records == 0 || header.num_records_in_root == 0 {
178 return Ok(Vec::new());
179 }
180
181 let max_leaf_nrec = max_records_leaf(header.node_size, header.record_size);
182
183 if header.depth == 0 {
184 parse_leaf_records(
186 file_data,
187 header.root_node_address as usize,
188 header.num_records_in_root,
189 header.record_size,
190 )
191 } else {
192 let mut records = Vec::new();
194 collect_internal_records(
195 file_data,
196 header.root_node_address as usize,
197 header.num_records_in_root,
198 header.depth,
199 header.record_size,
200 header.node_size,
201 offset_size,
202 length_size,
203 max_leaf_nrec,
204 &mut records,
205 )?;
206 Ok(records)
207 }
208}
209
210fn parse_leaf_records(
212 file_data: &[u8],
213 offset: usize,
214 num_records: u16,
215 record_size: u16,
216) -> Result<Vec<BTreeV2Record>, FormatError> {
217 ensure_len(file_data, offset, 6)?;
219 if &file_data[offset..offset + 4] != b"BTLF" {
220 return Err(FormatError::InvalidBTreeV2Signature);
221 }
222
223 let pos = offset + 6;
224 let rs = record_size as usize;
225 let total = num_records as usize * rs;
226 ensure_len(file_data, pos, total)?;
227
228 #[cfg(feature = "checksum")]
230 {
231 let checksum_pos = pos + total;
232 if file_data.len() >= checksum_pos + 4 {
233 let stored = LittleEndian::read_u32(&file_data[checksum_pos..checksum_pos + 4]);
234 let computed = crate::checksum::jenkins_lookup3(&file_data[offset..checksum_pos]);
235 if computed != stored {
236 return Err(FormatError::ChecksumMismatch {
237 expected: stored,
238 computed,
239 });
240 }
241 }
242 }
243
244 let mut records = Vec::with_capacity(num_records as usize);
245 for i in 0..num_records as usize {
246 let start = pos + i * rs;
247 records.push(BTreeV2Record {
248 data: file_data[start..start + rs].to_vec(),
249 });
250 }
251 Ok(records)
252}
253
254#[allow(clippy::too_many_arguments, clippy::only_used_in_recursion)]
256fn collect_internal_records(
257 file_data: &[u8],
258 offset: usize,
259 num_records: u16,
260 depth: u16,
261 record_size: u16,
262 node_size: u32,
263 offset_size: u8,
264 length_size: u8,
265 max_leaf_nrec: u64,
266 out: &mut Vec<BTreeV2Record>,
267) -> Result<(), FormatError> {
268 ensure_len(file_data, offset, 6)?;
270 if &file_data[offset..offset + 4] != b"BTIN" {
271 return Err(FormatError::InvalidBTreeV2Signature);
272 }
273
274 let nr = num_records as usize;
275 let rs = record_size as usize;
276 let mut pos = offset + 6;
277
278 ensure_len(file_data, pos, nr * rs)?;
280 let records_start = pos;
281 pos += nr * rs;
282
283 let child_depth = depth - 1;
286 let max_nrec_child = if child_depth == 0 {
287 max_leaf_nrec
288 } else {
289 max_leaf_nrec * 2 };
293 let nrec_width = bytes_for_max_records(max_nrec_child);
294
295 let total_nrec_width = if depth > 1 {
297 let max_total = header_max_total_records(max_leaf_nrec, depth - 1);
300 bytes_for_max_records(max_total)
301 } else {
302 0
303 };
304
305 let num_children = nr + 1;
306 let child_ptr_size = offset_size as usize + nrec_width + total_nrec_width;
307 ensure_len(file_data, pos, num_children * child_ptr_size)?;
308
309 let mut children = Vec::with_capacity(num_children);
311 for _ in 0..num_children {
312 let addr = read_offset(file_data, pos, offset_size)?;
313 pos += offset_size as usize;
314 let child_nrec = read_var_uint(file_data, pos, nrec_width)? as u16;
315 pos += nrec_width;
316 pos += total_nrec_width; children.push((addr, child_nrec));
318 }
319
320 for (i, &(child_addr, child_nrec)) in children.iter().enumerate() {
323 if child_depth == 0 {
324 let leaf_recs = parse_leaf_records(
325 file_data,
326 child_addr as usize,
327 child_nrec,
328 record_size,
329 )?;
330 out.extend(leaf_recs);
331 } else {
332 collect_internal_records(
333 file_data,
334 child_addr as usize,
335 child_nrec,
336 child_depth,
337 record_size,
338 node_size,
339 offset_size,
340 length_size,
341 max_leaf_nrec,
342 out,
343 )?;
344 }
345
346 if i < nr {
348 let rec_start = records_start + i * rs;
349 out.push(BTreeV2Record {
350 data: file_data[rec_start..rec_start + rs].to_vec(),
351 });
352 }
353 }
354
355 Ok(())
356}
357
358fn header_max_total_records(max_leaf_nrec: u64, depth: u16) -> u64 {
360 let mut total = max_leaf_nrec;
362 for _ in 0..depth {
363 total = total.saturating_mul(max_leaf_nrec.max(2));
364 }
365 total
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 fn build_btree_v2_header(
373 tree_type: u8,
374 node_size: u32,
375 record_size: u16,
376 depth: u16,
377 root_addr: u64,
378 num_records_root: u16,
379 total_records: u64,
380 offset_size: u8,
381 length_size: u8,
382 ) -> Vec<u8> {
383 let mut buf = Vec::new();
384 buf.extend_from_slice(b"BTHD");
385 buf.push(0); buf.push(tree_type);
387 buf.extend_from_slice(&node_size.to_le_bytes());
388 buf.extend_from_slice(&record_size.to_le_bytes());
389 buf.extend_from_slice(&depth.to_le_bytes());
390 buf.push(85); buf.push(40); match offset_size {
393 4 => buf.extend_from_slice(&(root_addr as u32).to_le_bytes()),
394 8 => buf.extend_from_slice(&root_addr.to_le_bytes()),
395 _ => {}
396 }
397 buf.extend_from_slice(&num_records_root.to_le_bytes());
398 match length_size {
399 4 => buf.extend_from_slice(&(total_records as u32).to_le_bytes()),
400 8 => buf.extend_from_slice(&total_records.to_le_bytes()),
401 _ => {}
402 }
403 let checksum = crate::checksum::jenkins_lookup3(&buf);
404 buf.extend_from_slice(&checksum.to_le_bytes());
405 buf
406 }
407
408 fn build_leaf_node(tree_type: u8, records: &[&[u8]]) -> Vec<u8> {
409 let mut buf = Vec::new();
410 buf.extend_from_slice(b"BTLF");
411 buf.push(0); buf.push(tree_type);
413 for rec in records {
414 buf.extend_from_slice(rec);
415 }
416 let checksum = crate::checksum::jenkins_lookup3(&buf);
417 buf.extend_from_slice(&checksum.to_le_bytes());
418 buf
419 }
420
421 #[test]
422 fn parse_header() {
423 let data = build_btree_v2_header(5, 512, 11, 0, 0x1000, 3, 3, 8, 8);
424 let hdr = BTreeV2Header::parse(&data, 0, 8, 8).unwrap();
425 assert_eq!(hdr.tree_type, 5);
426 assert_eq!(hdr.node_size, 512);
427 assert_eq!(hdr.record_size, 11);
428 assert_eq!(hdr.depth, 0);
429 assert_eq!(hdr.root_node_address, 0x1000);
430 assert_eq!(hdr.num_records_in_root, 3);
431 assert_eq!(hdr.total_records, 3);
432 }
433
434 #[test]
435 fn parse_leaf_with_2_records() {
436 let rec1 = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
437 let rec2 = [11u8, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
438 let leaf = build_leaf_node(5, &[&rec1, &rec2]);
439
440 let leaf_offset = 256usize;
441 let header = build_btree_v2_header(5, 512, 11, 0, leaf_offset as u64, 2, 2, 8, 8);
442
443 let mut file_data = vec![0u8; 512];
444 file_data[..header.len()].copy_from_slice(&header);
445 file_data[leaf_offset..leaf_offset + leaf.len()].copy_from_slice(&leaf);
446
447 let hdr = BTreeV2Header::parse(&file_data, 0, 8, 8).unwrap();
448 let records = collect_btree_v2_records(&file_data, &hdr, 8, 8).unwrap();
449 assert_eq!(records.len(), 2);
450 assert_eq!(records[0].data, rec1.to_vec());
451 assert_eq!(records[1].data, rec2.to_vec());
452 }
453
454 #[test]
455 fn invalid_signature() {
456 let mut data = build_btree_v2_header(5, 512, 11, 0, 0, 0, 0, 8, 8);
457 data[0] = b'X';
458 let err = BTreeV2Header::parse(&data, 0, 8, 8).unwrap_err();
459 assert_eq!(err, FormatError::InvalidBTreeV2Signature);
460 }
461
462 #[test]
463 fn invalid_version() {
464 let mut data = build_btree_v2_header(5, 512, 11, 0, 0, 0, 0, 8, 8);
465 data[4] = 1; let err = BTreeV2Header::parse(&data, 0, 8, 8).unwrap_err();
467 assert_eq!(err, FormatError::InvalidBTreeV2Version(1));
468 }
469
470 #[test]
471 fn empty_tree() {
472 let header = build_btree_v2_header(5, 512, 11, 0, 0, 0, 0, 8, 8);
473 let hdr = BTreeV2Header::parse(&header, 0, 8, 8).unwrap();
474 let records = collect_btree_v2_records(&header, &hdr, 8, 8).unwrap();
475 assert!(records.is_empty());
476 }
477}