Finish post 2

This commit is contained in:
Timothy Warren 2020-12-01 10:04:41 -05:00
parent 2c96172e9d
commit 5a82f04b57
4 changed files with 78 additions and 10 deletions

View File

@ -9,6 +9,11 @@ pub enum Error {
HeaderString(String),
/// An error parsing the page size
InvalidPageSize(String),
/// An error parsing the maximum/minimum payload fraction
/// or leaf fraction
InvalidFraction(String),
/// The change counter failed to parse
InvalidChangeCounter(String),
}
impl fmt::Display for Error {
@ -17,6 +22,8 @@ impl fmt::Display for Error {
match self {
Self::HeaderString(v) => write!(f, "Unexpected bytes at start of file, expected the magic string 'SQLite format 3\u{0}', found {:?}", v),
Self::InvalidPageSize(msg) => write!(f, "Invalid page size, {}", msg),
Self::InvalidFraction(msg) => write!(f, "{}", msg),
Self::InvalidChangeCounter(msg) => write!(f, "Invalid change counter: {}", msg),
}
}
}

View File

@ -1,5 +1,6 @@
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}
@ -34,6 +35,16 @@ impl From<u8> for FormatVersion {
#[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;
@ -62,7 +73,18 @@ impl TryFrom<u16> for PageSize {
}
}
pub fn parse_header(bytes: &[u8]) -> Result<(PageSize, FormatVersion, FormatVersion), Error> {
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
@ -71,8 +93,27 @@ pub fn parse_header(bytes: &[u8]) -> Result<(PageSize, FormatVersion, FormatVers
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")?;
Ok((page_size, write_version, read_version))
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

View File

@ -1,2 +1,26 @@
pub mod error;
pub mod header;
// A little strange but since this might end up being
// used in a large number of places, we can use a
// String in the error position of our result. This
// will allow the caller to insert their own error
// with more context
fn try_parse_u32(bytes: &[u8]) -> Result<u32, String> {
use std::convert::TryInto;
// Just like with our u16, we are going to need to convert
// a slice into an array of 4 bytes. Using the `try_into`
// method on a slice, we will fail if the slice isn't exactly
// 4 bytes. We can use `map_err` to build our string only if
// it fails
let arr: [u8; 4] = bytes.try_into().map_err(|_| {
format!(
"expected a 4 byte slice, found a {} byte slice",
bytes.len()
)
})?;
// Finally we can use the `from_be_bytes` constructor for a u32
Ok(u32::from_be_bytes(arr))
}

View File

@ -1,18 +1,14 @@
use sqlite_parser::{
header::parse_header,
error::Error,
};
use sqlite_parser::{error::Error, header::parse_header};
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")
.expect("Failed to read data.sqlite");
let contents = read("data.sqlite").expect("Failed to read data.sqlite");
let (page_size, write_format, read_format) = parse_header(&contents[0..100])?;
let db_header = parse_header(&contents[0..100])?;
println!("page_size {:?}, write_format {:?}, read_format {:?}", page_size, write_format, read_format);
println!("{:#?}", db_header);
Ok(())
}