Skip to main content

chat4n6_sqlite_forensics/
header.rs

1pub const SQLITE_MAGIC: &[u8] = b"SQLite format 3\x00";
2
3pub fn is_sqlite_header(data: &[u8]) -> bool {
4    data.len() >= 16 && &data[..16] == SQLITE_MAGIC
5}
6
7#[derive(Debug)]
8pub struct DbHeader {
9    pub page_size: u32,
10    pub page_count: u32,
11    pub freelist_trunk_page: u32,
12    pub freelist_page_count: u32,
13    pub user_version: u32,
14    pub text_encoding: u32,
15}
16
17impl DbHeader {
18    pub fn parse(data: &[u8]) -> Option<Self> {
19        if !is_sqlite_header(data) || data.len() < 100 {
20            return None;
21        }
22        let page_size = {
23            let raw = u16::from_be_bytes([data[16], data[17]]) as u32;
24            if raw == 1 {
25                65536
26            } else {
27                raw
28            }
29        };
30        Some(Self {
31            page_size,
32            page_count: u32::from_be_bytes([data[28], data[29], data[30], data[31]]),
33            freelist_trunk_page: u32::from_be_bytes([data[32], data[33], data[34], data[35]]),
34            freelist_page_count: u32::from_be_bytes([data[36], data[37], data[38], data[39]]),
35            text_encoding: u32::from_be_bytes([data[56], data[57], data[58], data[59]]),
36            user_version: u32::from_be_bytes([data[60], data[61], data[62], data[63]]),
37        })
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn test_sqlite_header_magic() {
47        assert!(is_sqlite_header(b"SQLite format 3\x00some more bytes"));
48        assert!(!is_sqlite_header(b"not sqlite"));
49    }
50
51    #[test]
52    fn test_db_header_parse_valid() {
53        // Minimal 100-byte buffer: magic + zeros for the rest
54        let mut buf = vec![0u8; 100];
55        buf[..16].copy_from_slice(b"SQLite format 3\x00");
56        // page_size = 4096 at bytes 16-17
57        buf[16] = 0x10;
58        buf[17] = 0x00;
59        let hdr = DbHeader::parse(&buf).unwrap();
60        assert_eq!(hdr.page_size, 4096);
61    }
62
63    #[test]
64    fn test_db_header_parse_invalid() {
65        assert!(DbHeader::parse(b"not sqlite").is_none());
66        assert!(DbHeader::parse(b"SQLite format 3\x00").is_none()); // too short (< 100 bytes)
67    }
68}