use std::convert::TryInto;
use std::io::{self};
use serde::{Deserialize, Serialize};
use crate::sqlite::{SqliteError, SqliteResult};
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct SqliteHeader {
#[serde(serialize_with = "serialize_magic_string")]
magic_string: [u8; 16],
page_size: u16,
write_version: u8,
read_version: u8,
reserved_space: u8,
max_payload_fraction: u8,
min_payload_fraction: u8,
leaf_payload_fraction: u8,
file_change_counter: u32,
database_size: u32,
first_freelist_trunk_page: u32,
total_freelist_pages: u32,
schema_cookie: u32,
schema_format_number: u32,
default_page_cache_size: u32,
largest_root_btree_page: u32,
text_encoding: u32,
user_version: u32,
incremental_vacuum_mode: u32,
application_id: u32,
#[serde(skip)]
reserved: [u8; 20],
version_valid_for: u32,
sqlite_version_number: u32,
}
pub fn serialize_magic_string<S>(
magic_string: &[u8; 16],
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&String::from_utf8_lossy(magic_string))
}
impl SqliteHeader {
fn magic_ok(b: &[u8]) -> SqliteResult<()> {
if b == b"SQLite format 3\0" {
Ok(())
} else {
Err(SqliteError::InvalidSqliteMagic(format!("{b:?}")))
}
}
pub fn parse(buffer: &[u8; 100]) -> SqliteResult<Self> {
let magic: &[u8; 16] = &buffer[0..16].try_into().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "Invalid magic string")
})?;
Self::magic_ok(magic)?;
Ok(Self {
magic_string: *magic,
page_size: u16::from_be_bytes(
buffer[16..18]
.try_into()
.map_err(|_| SqliteError::ParseHeaderField("page_size".into()))?,
),
write_version: buffer[18],
read_version: buffer[19],
reserved_space: buffer[20],
max_payload_fraction: buffer[21],
min_payload_fraction: buffer[22],
leaf_payload_fraction: buffer[23],
file_change_counter: u32::from_be_bytes(
buffer[24..28].try_into().map_err(|_| {
SqliteError::ParseHeaderField("file_change_counter".into())
})?,
),
database_size: u32::from_be_bytes(
buffer[28..32].try_into().map_err(|_| {
SqliteError::ParseHeaderField("database_size".into())
})?,
),
first_freelist_trunk_page: u32::from_be_bytes(
buffer[32..36].try_into().map_err(|_| {
SqliteError::ParseHeaderField("first_freelist_trunk_page".into())
})?,
),
total_freelist_pages: u32::from_be_bytes(
buffer[36..40].try_into().map_err(|_| {
SqliteError::ParseHeaderField("total_freelist_pages".into())
})?,
),
schema_cookie: u32::from_be_bytes(
buffer[40..44].try_into().map_err(|_| {
SqliteError::ParseHeaderField("schema_cookie".into())
})?,
),
schema_format_number: u32::from_be_bytes(
buffer[44..48].try_into().map_err(|_| {
SqliteError::ParseHeaderField("schema_format_number".into())
})?,
),
default_page_cache_size: u32::from_be_bytes(
buffer[48..52].try_into().map_err(|_| {
SqliteError::ParseHeaderField("default_page_cache_size".into())
})?,
),
largest_root_btree_page: u32::from_be_bytes(
buffer[52..56].try_into().map_err(|_| {
SqliteError::ParseHeaderField("largest_root_btree_page".into())
})?,
),
text_encoding: u32::from_be_bytes(
buffer[56..60].try_into().map_err(|_| {
SqliteError::ParseHeaderField("text_encoding".into())
})?,
),
user_version: u32::from_be_bytes(
buffer[60..64].try_into().map_err(|_| {
SqliteError::ParseHeaderField("user_version".into())
})?,
),
incremental_vacuum_mode: u32::from_be_bytes(
buffer[64..68].try_into().map_err(|_| {
SqliteError::ParseHeaderField("incremental_vacuum_mode".into())
})?,
),
application_id: u32::from_be_bytes(
buffer[68..72].try_into().map_err(|_| {
SqliteError::ParseHeaderField("application_id".into())
})?,
),
reserved: buffer[72..92]
.try_into()
.map_err(|_| SqliteError::ParseHeaderField("reserved".into()))?,
version_valid_for: u32::from_be_bytes(buffer[92..96].try_into().map_err(
|_| SqliteError::ParseHeaderField("version_valid_for".into()),
)?),
sqlite_version_number: u32::from_be_bytes(
buffer[96..100].try_into().map_err(|_| {
SqliteError::ParseHeaderField("sqlite_version_number".into())
})?,
),
})
}
pub fn page_size_ok(&self) -> SqliteResult<()> {
if self.page_size == 1
|| (self.page_size.is_power_of_two()
&& self.page_size >= 512
&& self.page_size <= 32768)
{
Ok(())
} else {
Err(SqliteError::InvalidHeaderField("page_size".into()))
}
}
pub fn file_format_write_version_ok(&self) -> SqliteResult<()> {
if self.write_version == 1 || self.write_version == 2 {
Ok(())
} else {
Err(SqliteError::InvalidHeaderField("write_version".into()))
}
}
pub fn file_format_read_version_ok(&self) -> SqliteResult<()> {
if self.read_version == 1 || self.read_version == 2 {
Ok(())
} else {
Err(SqliteError::InvalidHeaderField("read_version".into()))
}
}
pub fn reserved_space_ok(&self) -> SqliteResult<()> {
if self.reserved_space <= 32 {
let usable_size =
u32::from(self.page_size) - u32::from(self.reserved_space);
if usable_size >= 480 {
Ok(())
} else {
Err(SqliteError::InvalidHeaderField("reserved_space".into()))
}
} else {
Err(SqliteError::InvalidHeaderField("reserved_space".into()))
}
}
pub fn payload_fractions_ok(&self) -> SqliteResult<()> {
if self.max_payload_fraction == 64
&& self.min_payload_fraction == 32
&& self.leaf_payload_fraction == 32
{
Ok(())
} else {
Err(SqliteError::InvalidHeaderField("payload_fractions".into()))
}
}
pub fn text_encoding_ok(&self) -> SqliteResult<()> {
if self.text_encoding == 1 || self.text_encoding == 2 || self.text_encoding == 3
{
Ok(())
} else {
Err(SqliteError::InvalidHeaderField("text_encoding".into()))
}
}
pub fn schema_format_number_ok(&self) -> SqliteResult<()> {
if self.schema_format_number == 1
|| self.schema_format_number == 2
|| self.schema_format_number == 3
|| self.schema_format_number == 4
{
Ok(())
} else {
Err(SqliteError::InvalidHeaderField(
"schema_format_number".into(),
))
}
}
pub fn reserved_expansion_space_ok(&self) -> SqliteResult<()> {
if self.reserved.iter().all(|&byte| byte == 0) {
Ok(())
} else {
Err(SqliteError::InvalidHeaderField(
"reserved_expansion_space".into(),
))
}
}
pub fn is_ok(&self) -> SqliteResult<()> {
self.page_size_ok()?;
self.file_format_write_version_ok()?;
self.file_format_read_version_ok()?;
self.reserved_space_ok()?;
self.payload_fractions_ok()?;
self.text_encoding_ok()?;
self.schema_format_number_ok()?;
self.reserved_expansion_space_ok()?;
Ok(())
}
}