sqlite3-header 0.4.1

A crate to parse the sqlite3 header bytes.
Documentation
use std::{
    convert::TryInto,
    array::TryFromSliceError,
};

const MAGIC_HEADER_STRING: [u8; 16] = [
    0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00
];

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let contents = std::fs::read("data.sqlite")?;

    assert!(contents[0..16] == MAGIC_HEADER_STRING);
    println!("MAGIC HEADER STRING: {}", String::from_utf8_lossy(&contents[0..16]));

    let bytes: [u8; 2] = contents[16..18].try_into().unwrap();
    let page_size = u16::from_be_bytes(bytes);

    assert!(page_size.is_power_of_two());
    assert!(page_size > 511);
    assert!(page_size < 32769);

    println!("PAGE SIZE: {}", page_size);

    println!("FILE FORMAT WRITE VERSION: {}", match contents[18] {
        1 => "legacy",
        2 => "Write-Ahead Logging",
        _ => unimplemented!(),
    });
    println!("FILE FORMAT READ VERSION: {}", match contents[19] {
        1 => "legacy",
        2 => "Write-Ahead Logging",
        _ => unimplemented!(),
    });

    println!("RESERVED BYTES PER PAGE: {}", contents[20]);

    assert!(contents[21] == 64);
    println!("MAXIMUM EMBEDDED PAYLOAD FRACTION: {}", contents[21]);

    assert!(contents[22] == 32);
    println!("MINIMUM EMBEDDED PAYLOAD FRACTION: {}", contents[22]);

    assert!(contents[23] == 32);
    println!("LEAF PAYLOAD FRACTION: {}", contents[23]);

    let file_change_counter = slice_to_word(&contents[24..28])?;
    println!("FILE CHANGE COUNTER: {}", file_change_counter);

    let in_header_database_size = slice_to_word(&contents[28..32])?;
    println!("IN-HEADER DATABASE SIZE: {:?}", in_header_database_size);

    let freelist_index = slice_to_word(&contents[32..36])?;
    println!("FREELIST PAGE INDEX: {}", freelist_index);

    let freelist_count = slice_to_word(&contents[36..40])?;
    println!("FREELIST COUNT: {}", freelist_count);

    let schema_cookie = slice_to_word(&contents[40..44])?;
    println!("SCHEMA COOKIE: {}", schema_cookie);

    let schema_format = slice_to_word(&contents[44..48])?;
    assert!([1, 2, 3, 4].contains(&schema_format));
    println!("SCHEMA FORMAT: {}", schema_format);

    let default_page_cache_size = slice_to_word(&contents[48..52])?;
    println!("DEFAULT PAGE CACHE SIZE: {}", default_page_cache_size);

    let btree_largest_page_root = slice_to_word(&contents[52..56])?;
    println!("LARGEST ROOT B-TREE PAGE: {}", btree_largest_page_root);

    let db_encoding = slice_to_word(&contents[56..60])?;
    println!("DATABASE TEXT ENCODING: {}", match db_encoding {
        1 => "UTF-8",
        2 => "UTF-16le",
        3 => "UTF-16be",
        _ => unimplemented!(),
    });

    let user_version = slice_to_word(&contents[60..64])?;
    println!("USER VERSION: {}", user_version);

    let incremental_vacuum_mode = slice_to_word(&contents[64..68])? != 0;
    println!("INCREMENTAL-VACUUM MODE: {}", incremental_vacuum_mode);

    let application_id = slice_to_word(&contents[68..72])?;
    println!("APPLICATION ID: {}", application_id);

    let reserved = &contents[72..92];
    assert!(reserved.iter().all(|&v| v == 0));

    let version_valid_for_number = slice_to_word(&contents[92..96])?;
    println!("VERSION VALID FOR NUMBER: {}", version_valid_for_number);

    let sqlite_version_number = slice_to_word(&contents[96..100])?;
    println!("SQLITE VERSION NUMBER: {}", sqlite_version_number);

    Ok(())
}

fn slice_to_word(slice: &[u8]) -> Result<u32, TryFromSliceError> {
    let bytes: [u8; 4] = slice.try_into()?;
    Ok(u32::from_be_bytes(bytes))
}