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 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, } impl TryFrom for PageSize { type Error = Error; fn try_from(v: u16) -> Result { 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 { // 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 { // 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() }