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}