Finish post 2
This commit is contained in:
parent
2c96172e9d
commit
5a82f04b57
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
24
src/lib.rs
24
src/lib.rs
@ -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))
|
||||
}
|
||||
|
12
src/main.rs
12
src/main.rs
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user