First commit, first part of the tutorial

This commit is contained in:
Timothy Warren 2020-09-17 16:08:33 -04:00
commit 6cb42ec21b
7 changed files with 116 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "sqlite-parser"
version = "0.1.0"
authors = ["Timothy Warren <tim@timshomepage.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

BIN
data.sqlite Normal file

Binary file not shown.

24
src/error.rs Normal file
View File

@ -0,0 +1,24 @@
use std::fmt;
/// Representation of our possible errors.
/// Each variant will contain a string for more
/// detailed information
#[derive(Debug)]
pub enum Error {
/// An error related to the first 16 bytes
HeaderString(String),
/// An error parsing the page size
InvalidPageSize(String),
}
impl fmt::Display for Error {
/// This will be called whenever we print
fn fmt(&self, f: &mut fmt::Formatter) -> 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 {}

61
src/header.rs Normal file
View File

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

2
src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod error;
pub mod header;

19
src/main.rs Normal file
View File

@ -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(())
}