Skip to main content

quantydb_sqlite/
header.rs

1//! The 100 byte database header at the start of page 1.
2//!
3//! Everything here is big endian, and every field that the format says must
4//! hold a particular value is checked rather than assumed. That is the
5//! difference between a reader and a parser of hostile input: a file that
6//! lies about its own page size is the cheapest way to make a naive reader
7//! index out of bounds, and the check costs one comparison at open time.
8//!
9//! Two rejections are worth explaining because they refuse files that other
10//! tools would happily open.
11//!
12//! Wal mode is recorded here rather than judged here. Whether a wal mode
13//! database can be read from its main file alone depends on something this
14//! parser cannot see: whether a `-wal` file exists next to it with commits
15//! in it. A database that was checkpointed and closed cleanly is complete
16//! and perfectly readable, and refusing it on the strength of a flag would
17//! turn away a large share of the databases in the world. The decision
18//! belongs to `Reader::open`, which can ask the source what it accounts
19//! for.
20//!
21//! The text encoding is recorded and applies to every text value in the
22//! file. All three are read; what is refused is text that does not decode,
23//! because getting utf-16 wrong produces text that looks almost right,
24//! which is the failure mode hardest to notice downstream.
25
26use crate::error::{Result, SqliteError};
27
28pub const HEADER_LEN: usize = 100;
29const MAGIC: &[u8; 16] = b"SQLite format 3\0";
30
31/// Text encoding of every text value in the database.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum TextEncoding {
34    Utf8,
35    Utf16Le,
36    Utf16Be,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct Header {
41    /// Bytes per page, 512 to 65536, always a power of two.
42    pub page_size: u32,
43    /// Unused bytes at the end of every page. Usually zero; extensions use
44    /// it for per page trailers.
45    pub reserved_space: u8,
46    /// Whether the database uses a write-ahead log. On its own this says
47    /// nothing about whether the main file is complete: see the note at the
48    /// top of this file.
49    pub wal_mode: bool,
50    pub file_change_counter: u32,
51    /// Page count as stated in the header, only present when the header
52    /// says it is current. Callers should prefer `Reader::page_count`.
53    pub header_page_count: Option<u32>,
54    pub first_freelist_trunk: u32,
55    pub freelist_page_count: u32,
56    pub schema_cookie: u32,
57    /// 1 to 4. Format 4 is what any SQLite since 2006 writes.
58    pub schema_format: u32,
59    pub text_encoding: TextEncoding,
60    pub user_version: i32,
61    pub application_id: i32,
62    /// Non zero when the database is in auto vacuum mode, in which case
63    /// pointer map pages are interleaved with the data.
64    pub largest_root_page: u32,
65    pub sqlite_version_number: u32,
66}
67
68impl Header {
69    /// Bytes of each page the b-tree layer may use. Everything in the file
70    /// that indexes into a page is bounded by this, not by the page size.
71    pub fn usable_size(&self) -> u32 {
72        self.page_size - self.reserved_space as u32
73    }
74
75    pub fn parse(bytes: &[u8]) -> Result<Header> {
76        if bytes.len() < HEADER_LEN {
77            return Err(SqliteError::not_sqlite(format!(
78                "file is {} bytes, shorter than the {HEADER_LEN} byte header",
79                bytes.len()
80            )));
81        }
82        if &bytes[0..16] != MAGIC {
83            return Err(SqliteError::not_sqlite(
84                "the file does not start with the sqlite format 3 magic string",
85            ));
86        }
87
88        // a page size of 1 encodes 65536, which does not fit the u16 field
89        let raw_page_size = be16(bytes, 16);
90        let page_size: u32 = if raw_page_size == 1 {
91            65536
92        } else {
93            raw_page_size as u32
94        };
95        if page_size < 512 || !page_size.is_power_of_two() {
96            return Err(SqliteError::malformed(
97                1,
98                format!("page size {page_size} is not a power of two of at least 512"),
99            ));
100        }
101
102        let write_version = bytes[18];
103        let read_version = bytes[19];
104        if read_version > 2 || write_version > 2 {
105            return Err(SqliteError::unsupported(format!(
106                "file format versions {write_version}/{read_version} are newer than this reader"
107            )));
108        }
109        let wal_mode = write_version == 2 || read_version == 2;
110
111        let reserved_space = bytes[20];
112        let usable = page_size as i64 - reserved_space as i64;
113        if usable < 480 {
114            return Err(SqliteError::malformed(
115                1,
116                format!(
117                    "reserved space {reserved_space} leaves {usable} usable bytes per page, \
118                     the format requires at least 480"
119                ),
120            ));
121        }
122
123        // the format fixes these three; anything else means the file was not
124        // written by a sqlite that agrees with the documented format
125        if bytes[21] != 64 || bytes[22] != 32 || bytes[23] != 32 {
126            return Err(SqliteError::malformed(
127                1,
128                format!(
129                    "payload fractions are {}/{}/{}, the format requires 64/32/32",
130                    bytes[21], bytes[22], bytes[23]
131                ),
132            ));
133        }
134
135        let file_change_counter = be32(bytes, 24);
136        let claimed_page_count = be32(bytes, 28);
137        let version_valid_for = be32(bytes, 92);
138        // the in-header page count only counts when it was written by the
139        // same transaction that last changed the file; older sqlite versions
140        // left it stale on purpose
141        let header_page_count =
142            if claimed_page_count != 0 && version_valid_for == file_change_counter {
143                Some(claimed_page_count)
144            } else {
145                None
146            };
147
148        let schema_format = be32(bytes, 44);
149        if !(1..=4).contains(&schema_format) {
150            return Err(SqliteError::malformed(
151                1,
152                format!("schema format {schema_format} is not one of 1 to 4"),
153            ));
154        }
155
156        let text_encoding = match be32(bytes, 56) {
157            1 => TextEncoding::Utf8,
158            2 => TextEncoding::Utf16Le,
159            3 => TextEncoding::Utf16Be,
160            other => {
161                return Err(SqliteError::malformed(
162                    1,
163                    format!("text encoding {other} is not one of 1, 2 or 3"),
164                ))
165            }
166        };
167
168        // reserved for expansion, and the format says it must be zero. a
169        // non zero value means either a newer format or a file that has been
170        // tampered with, and both deserve a stop rather than a guess
171        if bytes[72..92].iter().any(|b| *b != 0) {
172            return Err(SqliteError::malformed(
173                1,
174                "the reserved header range 72..92 is not zero",
175            ));
176        }
177
178        Ok(Header {
179            page_size,
180            reserved_space,
181            file_change_counter,
182            header_page_count,
183            wal_mode,
184            first_freelist_trunk: be32(bytes, 32),
185            freelist_page_count: be32(bytes, 36),
186            schema_cookie: be32(bytes, 40),
187            schema_format,
188            text_encoding,
189            user_version: be32(bytes, 60) as i32,
190            application_id: be32(bytes, 68) as i32,
191            largest_root_page: be32(bytes, 52),
192            sqlite_version_number: be32(bytes, 96),
193        })
194    }
195}
196
197fn be16(bytes: &[u8], at: usize) -> u16 {
198    u16::from_be_bytes([bytes[at], bytes[at + 1]])
199}
200
201fn be32(bytes: &[u8], at: usize) -> u32 {
202    u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]])
203}