From 6cb42ec21be72ffe70d2970aa996cdb041c8dd9f Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Thu, 17 Sep 2020 16:08:33 -0400 Subject: [PATCH] First commit, first part of the tutorial --- .gitignore | 1 + Cargo.toml | 9 ++++++++ data.sqlite | Bin 0 -> 16384 bytes src/error.rs | 24 ++++++++++++++++++++ src/header.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ src/main.rs | 19 ++++++++++++++++ 7 files changed, 116 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 data.sqlite create mode 100644 src/error.rs create mode 100644 src/header.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7f8939f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sqlite-parser" +version = "0.1.0" +authors = ["Timothy Warren "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/data.sqlite b/data.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..deaa23c0cb49c50eaffda25afff541c540bf2829 GIT binary patch literal 16384 zcmeI(&q~8E90%|uoqsmadKo(jKM~QPD55tnn-XNY)mb}mr;%xtLEFu)1wHy=zJgES zYlsIACe^W=3qLX zT>GgNYW6xK z7n)6QZ>;Y!9bZkSg(F|q14%*suqkP(??$X5y=6vL9|fPQl*|0_Hos>!)`>0R+h}`+ zHcDh-a7_&_kjK&|?<^p%(`-`PZ?x)umriAu>Yd=M;hE7b=>^pB8s{Ckm5;PjQXnsb z&s0Zh5axNQx-Mob2W8F%@wLAHdQ_FA!#wZV*?YUTb`12q#a=K)xKGS|F%<#=5P$## zAOHafKmY;|fB*y_0D(U!uq+B}wc1u$NQYVcpyE-#rteiW3ibBYFk0aSwzlIB`#MRf zrN%l=YDu0&HnCkwW>vy{W$wGF5D1Rwwb2tWV=5P$##AOHaf{3C%GhjSqur&zS# q|GC}&WB31$-Y=E{0SG_<0uX=z1Rwwb2tWV=5U>LC+$r fmt::Result { + 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), + } + } +} + +impl std::error::Error for Error {} \ No newline at end of file diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000..d7785ea --- /dev/null +++ b/src/header.rs @@ -0,0 +1,61 @@ +use crate::error::Error; +use std::convert::{TryFrom, TryInto}; + +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, +]; + +/// This struct will wrap our 32bit number allowing us +/// to control how it gets used later. +#[derive(Debug)] +pub struct PageSize(u32); + +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 + ))) + } + } + } + } +} + +/// 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() +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f8da16c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod header; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..19e7056 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,19 @@ +use sqlite_parser::{ + header::{validate_magic_string, parse_page_size}, + error::Error, +}; +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(); + + validate_magic_string(&contents)?; + + let page_size = parse_page_size(&contents)?; + + println!("{:?}", page_size); + + Ok(()) +}