use crate::error::{Result, SqliteError};
pub const HEADER_LEN: usize = 100;
const MAGIC: &[u8; 16] = b"SQLite format 3\0";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextEncoding {
Utf8,
Utf16Le,
Utf16Be,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Header {
pub page_size: u32,
pub reserved_space: u8,
pub wal_mode: bool,
pub file_change_counter: u32,
pub header_page_count: Option<u32>,
pub first_freelist_trunk: u32,
pub freelist_page_count: u32,
pub schema_cookie: u32,
pub schema_format: u32,
pub text_encoding: TextEncoding,
pub user_version: i32,
pub application_id: i32,
pub largest_root_page: u32,
pub sqlite_version_number: u32,
}
impl Header {
pub fn usable_size(&self) -> u32 {
self.page_size - self.reserved_space as u32
}
pub fn parse(bytes: &[u8]) -> Result<Header> {
if bytes.len() < HEADER_LEN {
return Err(SqliteError::not_sqlite(format!(
"file is {} bytes, shorter than the {HEADER_LEN} byte header",
bytes.len()
)));
}
if &bytes[0..16] != MAGIC {
return Err(SqliteError::not_sqlite(
"the file does not start with the sqlite format 3 magic string",
));
}
let raw_page_size = be16(bytes, 16);
let page_size: u32 = if raw_page_size == 1 {
65536
} else {
raw_page_size as u32
};
if page_size < 512 || !page_size.is_power_of_two() {
return Err(SqliteError::malformed(
1,
format!("page size {page_size} is not a power of two of at least 512"),
));
}
let write_version = bytes[18];
let read_version = bytes[19];
if read_version > 2 || write_version > 2 {
return Err(SqliteError::unsupported(format!(
"file format versions {write_version}/{read_version} are newer than this reader"
)));
}
let wal_mode = write_version == 2 || read_version == 2;
let reserved_space = bytes[20];
let usable = page_size as i64 - reserved_space as i64;
if usable < 480 {
return Err(SqliteError::malformed(
1,
format!(
"reserved space {reserved_space} leaves {usable} usable bytes per page, \
the format requires at least 480"
),
));
}
if bytes[21] != 64 || bytes[22] != 32 || bytes[23] != 32 {
return Err(SqliteError::malformed(
1,
format!(
"payload fractions are {}/{}/{}, the format requires 64/32/32",
bytes[21], bytes[22], bytes[23]
),
));
}
let file_change_counter = be32(bytes, 24);
let claimed_page_count = be32(bytes, 28);
let version_valid_for = be32(bytes, 92);
let header_page_count =
if claimed_page_count != 0 && version_valid_for == file_change_counter {
Some(claimed_page_count)
} else {
None
};
let schema_format = be32(bytes, 44);
if !(1..=4).contains(&schema_format) {
return Err(SqliteError::malformed(
1,
format!("schema format {schema_format} is not one of 1 to 4"),
));
}
let text_encoding = match be32(bytes, 56) {
1 => TextEncoding::Utf8,
2 => TextEncoding::Utf16Le,
3 => TextEncoding::Utf16Be,
other => {
return Err(SqliteError::malformed(
1,
format!("text encoding {other} is not one of 1, 2 or 3"),
))
}
};
if bytes[72..92].iter().any(|b| *b != 0) {
return Err(SqliteError::malformed(
1,
"the reserved header range 72..92 is not zero",
));
}
Ok(Header {
page_size,
reserved_space,
file_change_counter,
header_page_count,
wal_mode,
first_freelist_trunk: be32(bytes, 32),
freelist_page_count: be32(bytes, 36),
schema_cookie: be32(bytes, 40),
schema_format,
text_encoding,
user_version: be32(bytes, 60) as i32,
application_id: be32(bytes, 68) as i32,
largest_root_page: be32(bytes, 52),
sqlite_version_number: be32(bytes, 96),
})
}
}
fn be16(bytes: &[u8], at: usize) -> u16 {
u16::from_be_bytes([bytes[at], bytes[at + 1]])
}
fn be32(bytes: &[u8], at: usize) -> u32 {
u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]])
}