1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
//! Reference: https://www.sqlite.org/fileformat2.html
pub(self) mod db_filesize_in_pages;
pub(self) mod file_change_counter;
pub(self) mod file_format_version_numbers;
pub(self) mod magic_header_string;
pub(self) mod page_size;
pub(self) mod payload_fractions;
pub(self) mod reserved_bytes_per_page;
use self::{
db_filesize_in_pages::DatabaseFileSizeInPages,
file_change_counter::FileChangeCounter,
file_format_version_numbers::FileFormatVersionNumbers,
magic_header_string::MagicHeaderString, page_size::PageSize,
};
use crate::{
header::{
payload_fractions::PayloadFractions,
reserved_bytes_per_page::ReservedBytesPerPage,
},
result::{SQLiteError, SQLiteResult},
};
use anyhow::bail;
/// # Database File Format
///
/// |Offset | Size | Description|
/// |-------|-------|------------|
/// |0 | 16 | The header string: "SQLite format 3\000" |
/// |16 | 2 | The database page size in bytes. Must be a power of two between 512 and 32768 inclusive, or the bytes 1 representing a page size of 65536. |
/// |18 | 1 | File format write version. 1 for legacy; 2 for WAL. |
/// |19 | 1 | File format read version. 1 for legacy; 2 for WAL. |
/// |20 | 1 | Bytes of unused "reserved" space at the end of each page. Usually 0. |
/// |21 | 1 | Maximum embedded payload fraction. Must be 64. |
/// |22 | 1 | Minimum embedded payload fraction. Must be 32. |
/// |23 | 1 | Leaf payload fraction. Must be 32. |
/// |24 | 4 | File change counter. |
/// |28 | 4 | Size of the database file in pages. The "in-header database size". |
/// |32 | 4 | Page number of the first freelist trunk page. |
/// |36 | 4 | Total number of freelist pages. |
/// |40 | 4 | The schema cookie. |
/// |44 | 4 | The schema format number. Supported schema formats are 1, 2, 3, and 4. |
/// |48 | 4 | Default page cache size. |
/// |52 | 4 | The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise. |
/// |56 | 4 | The database text encoding. A bytes of 1 means UTF-8. A bytes of 2 means UTF-16le. A bytes of 3 means UTF-16be. |
/// |60 | 4 | The "user version" as read and set by the user_version pragma. |
/// |64 | 4 | True (non-zero) for incremental-vacuum mode. False (zero) otherwise. |
/// |68 | 4 | The "Application ID" set by PRAGMA application_id. |
/// |72 | 20 | Reserved for expansion. Must be zero. |
/// |92 | 4 | The version-valid-for number. |
/// |96 | 4 | SQLITE_VERSION_NUMBER |
#[derive(Debug)]
pub struct SqliteHeader {
/// The header string: "`SQLite format 3\000`".
magic_header_string: MagicHeaderString,
/// The database page size in bytes.
/// Must be a power of two between 512 and 32768 inclusive,
/// or the bytes 1 representing a page size of 65536.
page_size: PageSize,
/// File format version numbers.
file_format_version_numbers: FileFormatVersionNumbers,
/// Bytes of unused "reserved" space at the end of each page. Usually 0.
reserved_bytes_per_page: ReservedBytesPerPage,
/// Payload Fractions.
payload_fractions: PayloadFractions,
/// File change counter.
file_change_counter: FileChangeCounter,
// Size of the database file in pages. The "in-header database size".
db_filesize_in_pages: DatabaseFileSizeInPages,
}
impl SqliteHeader {
pub fn magic_header_string(&self) -> &MagicHeaderString {
&self.magic_header_string
}
pub fn page_size(&self) -> &PageSize {
&self.page_size
}
}
impl TryFrom<&[u8; 100]> for SqliteHeader {
type Error = SQLiteError;
fn try_from(bytes: &[u8; 100]) -> Result<Self, Self::Error> {
let bytes = bytes;
println!("{:x?}", &bytes[0..=15]);
println!("{:x?}", &bytes[16..=17]);
println!("{:x?}", &bytes[18..=19]);
println!("{:x?}", &bytes[20]);
println!("{:x?}", &bytes[21..=23]);
println!("{:x?}", &bytes[24..=27]);
println!("{:x?}", &bytes[28..=31]);
let magic_header_string = MagicHeaderString::parse_bytes(&bytes[0..=15])?;
let page_size = PageSize::parse_bytes(&bytes[16..=17])?;
let file_format_version_numbers =
FileFormatVersionNumbers::parse_bytes(&bytes[18..=19])?;
let reserved_bytes_per_page =
ReservedBytesPerPage::parse_bytes(&[bytes[20]])?;
let payload_fractions = PayloadFractions::parse_bytes(&bytes[21..=23])?;
let file_change_counter = FileChangeCounter::parse_bytes(&bytes[24..=27])?;
let db_filesize_in_pages =
DatabaseFileSizeInPages::parse_bytes(&bytes[28..=31])?;
Ok(Self {
magic_header_string,
page_size,
file_format_version_numbers,
reserved_bytes_per_page,
payload_fractions,
file_change_counter,
db_filesize_in_pages,
})
}
}
trait ParseBytes<T>
where
Self: Sized,
{
fn struct_name() -> &'static str;
fn bytes_length() -> usize;
fn parsing_handler(bytes: &[u8]) -> SQLiteResult<Self>;
fn check_payload_size(bytes: &[u8]) -> SQLiteResult<()> {
if bytes.len() != Self::bytes_length() {
bail!("Invalid size for {}", Self::struct_name());
} else {
Ok(())
}
}
fn parse_bytes(bytes: &[u8]) -> SQLiteResult<Self> {
Self::check_payload_size(bytes)?;
Self::parsing_handler(bytes)
}
}
// TODO
trait ValidateParsed<T>
where
Self: Sized + ParseBytes<T>,
{
fn validate_parsed(&self) -> SQLiteResult<()>;
}