sqlite-parser/src/header.rs

141 lines
4.0 KiB
Rust

use crate::error::Error;
use std::convert::{TryFrom, TryInto};
use std::num::NonZeroU32;
static HEADER_STRING: &[u8] = &[
//S q l i t e ` ` f o r m a t ` ` 3 \u{0}
83, 81, 76, 105, 116, 101, 32, 102, 111, 114, 109, 97, 116, 32, 51, 0,
];
/// A value stored as a Write Format Verson or
/// Read Format Version
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FormatVersion {
/// Represents the rollback journal mode
Legacy,
/// Represents the Write Ahead Log mode
WriteAheadLog,
/// Represents any mode not 1 or 2, the value
/// will be provided
Unknown(u8),
}
impl From<u8> for FormatVersion {
fn from(v: u8) -> Self {
match v {
1 => Self::Legacy,
2 => Self::WriteAheadLog,
_ => Self::Unknown(v),
}
}
}
/// This struct will wrap our 32bit number allowing us
/// to control how it gets used later.
#[derive(Debug)]
pub struct PageSize(u32);
#[derive(Debug)]
pub struct DatabaseHeader {
pub page_size: PageSize,
pub write_version: FormatVersion,
pub read_version: FormatVersion,
pub reserved_bytes: u8,
pub change_counter: u32,
pub database_size: Option<NonZeroU32>,
}
impl TryFrom<u16> for PageSize {
type Error = Error;
fn try_from(v: u16) -> Result<PageSize, Self::Error> {
match v {
// Special case for the largest page size
1 => Ok(PageSize(65_536u32)),
// < 512, != 1
0 | 2..=511 => Err(Error::InvalidPageSize(format!(
"value must be >= 512, found: {}",
v
))),
_ => {
if v.is_power_of_two() {
Ok(PageSize(v as u32))
} else {
Err(Error::InvalidPageSize(format!(
"value must be a power of 2, found: {}",
v
)))
}
}
}
}
}
fn validate_fraction(byte: u8, target: u8, name: &str) -> Result<(), Error> {
if byte != target {
Err(Error::InvalidFraction(format!(
"{} must be {}, found: {}",
name, target, byte
)))
} else {
Ok(())
}
}
pub fn parse_header(bytes: &[u8]) -> Result<DatabaseHeader, Error> {
// Check that the first 16 bytes match the header string
validate_magic_string(&bytes)?;
// capture the page size
let page_size = parse_page_size(bytes)?;
// capture the write format version
let write_version = FormatVersion::from(bytes[18]);
// capture the read format version
let read_version = FormatVersion::from(bytes[19]);
let reserved_bytes = bytes[20];
validate_fraction(bytes[21], 64, "Maximum payload fraction")?;
validate_fraction(bytes[22], 32, "Minimum payload fraction")?;
validate_fraction(bytes[23], 32, "Leaf fraction")?;
let change_counter =
crate::try_parse_u32(&bytes[24..28]).map_err(|msg| Error::InvalidChangeCounter(msg))?;
let database_size = crate::try_parse_u32(&bytes[28..32])
.map(NonZeroU32::new)
.ok()
.flatten();
Ok(DatabaseHeader {
page_size,
write_version,
read_version,
reserved_bytes,
change_counter,
database_size,
})
}
/// Validate that the bytes provided match the special string
/// at the start of Sqlite3 files
pub fn validate_magic_string(bytes: &[u8]) -> Result<(), Error> {
let buf = &bytes[0..16];
if buf != HEADER_STRING {
return Err(Error::HeaderString(
String::from_utf8_lossy(buf).to_string(),
));
}
Ok(())
}
/// Attempt to generate the appropriate `PageSize` struct
pub fn parse_page_size(bytes: &[u8]) -> Result<PageSize, Error> {
// Convert into array, and convert error if needed, so that the `?` operator works
let page_size_bytes: [u8; 2] = bytes[16..18].try_into().map_err(|_| {
Error::InvalidPageSize(format!("expected a 2 byte slice, found: {:?}", bytes))
})?;
u16::from_be_bytes(page_size_bytes).try_into()
}