From 2c96172e9dc71e4110b3c39c9beb4c6d1ed999ed Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 1 Dec 2020 09:32:18 -0500 Subject: [PATCH] Encapsulate header parsing logic --- src/error.rs | 2 +- src/header.rs | 44 +++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 2 +- src/main.rs | 11 +++++------ 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/error.rs b/src/error.rs index 2c6db91..ca56d57 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,4 +21,4 @@ impl fmt::Display for Error { } } -impl std::error::Error for Error {} \ No newline at end of file +impl std::error::Error for Error {} diff --git a/src/header.rs b/src/header.rs index d7785ea..5aef665 100644 --- a/src/header.rs +++ b/src/header.rs @@ -6,6 +6,29 @@ static HEADER_STRING: &[u8] = &[ 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)] @@ -39,12 +62,27 @@ impl TryFrom for PageSize { } } +pub fn parse_header(bytes: &[u8]) -> Result<(PageSize, FormatVersion, FormatVersion), 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]); + + Ok((page_size, write_version, read_version)) +} + /// 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())); + return Err(Error::HeaderString( + String::from_utf8_lossy(buf).to_string(), + )); } Ok(()) @@ -54,8 +92,8 @@ pub fn validate_magic_string(bytes: &[u8]) -> Result<(), Error> { 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)) + Error::InvalidPageSize(format!("expected a 2 byte slice, found: {:?}", bytes)) })?; u16::from_be_bytes(page_size_bytes).try_into() -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index f8da16c..8f708c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,2 @@ pub mod error; -pub mod header; \ No newline at end of file +pub mod header; diff --git a/src/main.rs b/src/main.rs index 19e7056..ba902eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use sqlite_parser::{ - header::{validate_magic_string, parse_page_size}, + header::parse_header, error::Error, }; use std::fs::read; @@ -7,13 +7,12 @@ use std::fs::read; fn main() -> Result<(), Error> { // first, read in all the bytes of our file // using unwrap to just panic if this fails - let contents = read("data.sqlite").unwrap(); + let contents = read("data.sqlite") + .expect("Failed to read data.sqlite"); - validate_magic_string(&contents)?; + let (page_size, write_format, read_format) = parse_header(&contents[0..100])?; - let page_size = parse_page_size(&contents)?; - - println!("{:?}", page_size); + println!("page_size {:?}, write_format {:?}, read_format {:?}", page_size, write_format, read_format); Ok(()) }