mod application_id;
mod database_text_encoding;
mod db_filesize_in_pages;
mod file_change_counter;
mod file_format_version_numbers;
mod freelist_pages;
mod incremental_vacuum_settings;
mod magic_header_string;
mod page_size;
mod payload_fractions;
mod reserved_bytes_per_page;
mod reserved_for_expansion;
mod schema_cookie;
mod schema_format;
mod suggested_cache_size;
mod user_version;
mod version_valid_for;
mod write_library_version;
use crate::traits::{ParseBytes, ValidateParsed};
use crate::{
impl_name,
result::{SqliteError, SqliteResult},
};
pub use self::{
application_id::ApplicationId,
database_text_encoding::DatabaseTextEncoding,
db_filesize_in_pages::DatabaseFileSizeInPages,
file_change_counter::FileChangeCounter,
file_format_version_numbers::{
FileFormatReadVersion, FileFormatVersionNumbers, FileFormatWriteVersion,
},
freelist_pages::FreeListPages,
incremental_vacuum_settings::IncrementalVacuumSettings,
magic_header_string::MagicHeaderString,
page_size::PageSize,
payload_fractions::{
LeafPayloadFraction, MaximumEmbeddedPayloadFraction,
MinimumEmbeddedPayloadFraction, PayloadFractions,
},
reserved_bytes_per_page::ReservedBytesPerPage,
reserved_for_expansion::ReservedForExpansion,
schema_cookie::SchemaCookie,
schema_format::SchemaFormat,
suggested_cache_size::SuggestedCacheSize,
user_version::UserVersion,
version_valid_for::VersionValidFor,
write_library_version::WriteLibraryVersion,
};
#[derive(Debug, Default)]
pub struct SqliteHeader {
magic_header_string: MagicHeaderString,
page_size: PageSize,
file_format_version_numbers: FileFormatVersionNumbers,
reserved_bytes_per_page: ReservedBytesPerPage,
payload_fractions: PayloadFractions,
file_change_counter: FileChangeCounter,
db_filesize_in_pages: DatabaseFileSizeInPages,
freelist_pages: FreeListPages,
schema_cookie: SchemaCookie,
schema_format: SchemaFormat,
suggested_cache_size: SuggestedCacheSize,
incremental_vacuum_settings: IncrementalVacuumSettings,
database_text_encoding: DatabaseTextEncoding,
user_version: UserVersion,
application_id: ApplicationId,
reserved_for_expansion: ReservedForExpansion,
version_valid_for: VersionValidFor,
write_library_version: WriteLibraryVersion,
}
impl SqliteHeader {
pub const LENGTH_BYTES: usize = 100;
pub fn magic_header_string(&self) -> &MagicHeaderString {
&self.magic_header_string
}
pub fn page_size(&self) -> &PageSize {
&self.page_size
}
pub fn file_format_version_numbers(&self) -> &FileFormatVersionNumbers {
&self.file_format_version_numbers
}
pub fn reserved_bytes_per_page(&self) -> &ReservedBytesPerPage {
&self.reserved_bytes_per_page
}
pub fn payload_fractions(&self) -> &PayloadFractions {
&self.payload_fractions
}
pub fn file_change_counter(&self) -> &FileChangeCounter {
&self.file_change_counter
}
pub fn db_filesize_in_pages(&self) -> &DatabaseFileSizeInPages {
&self.db_filesize_in_pages
}
pub fn freelist_pages(&self) -> &FreeListPages {
&self.freelist_pages
}
pub fn schema_cookie(&self) -> &SchemaCookie {
&self.schema_cookie
}
pub fn schema_format(&self) -> &SchemaFormat {
&self.schema_format
}
pub fn suggested_cache_size(&self) -> &SuggestedCacheSize {
&self.suggested_cache_size
}
pub fn incremental_vacuum_settings(&self) -> &IncrementalVacuumSettings {
&self.incremental_vacuum_settings
}
pub fn database_text_encoding(&self) -> &DatabaseTextEncoding {
&self.database_text_encoding
}
pub fn user_version(&self) -> &UserVersion {
&self.user_version
}
pub fn application_id(&self) -> &ApplicationId {
&self.application_id
}
pub fn reserved_for_expansion(&self) -> &ReservedForExpansion {
&self.reserved_for_expansion
}
pub fn version_valid_for(&self) -> &VersionValidFor {
&self.version_valid_for
}
pub fn write_library_version(&self) -> &WriteLibraryVersion {
&self.write_library_version
}
}
impl_name! {SqliteHeader}
impl ParseBytes for SqliteHeader {
const LENGTH_BYTES: usize = Self::LENGTH_BYTES;
fn parsing_handler(bytes: &[u8]) -> crate::result::SqliteResult<Self> {
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])?;
let freelist_pages = FreeListPages::parse_bytes(&bytes[32..=39])?;
let schema_cookie = SchemaCookie::parse_bytes(&bytes[40..=43])?;
let schema_format = SchemaFormat::parse_bytes(&bytes[44..=47])?;
let suggested_cache_size =
SuggestedCacheSize::parse_bytes(&bytes[48..=51])?;
let largest_root_btree_page =
incremental_vacuum_settings::LargestRootBtreePage::parse_bytes(
&bytes[52..=55],
)?;
let database_text_encoding =
DatabaseTextEncoding::parse_bytes(&bytes[56..=59])?;
let user_version = UserVersion::parse_bytes(&bytes[60..=63])?;
let incremental_vacuum_mode =
incremental_vacuum_settings::IncrementalVacuumMode::parse_bytes(
&bytes[64..=67],
)?;
let application_id = ApplicationId::parse_bytes(&bytes[68..=71])?;
let reserved_for_expansion =
ReservedForExpansion::parse_bytes(&bytes[72..=91])?;
let version_valid_for = VersionValidFor::parse_bytes(&bytes[92..=95])?;
let write_library_version =
WriteLibraryVersion::parse_bytes(&bytes[96..=99])?;
Ok(Self {
magic_header_string,
page_size,
file_format_version_numbers,
reserved_bytes_per_page,
payload_fractions,
file_change_counter,
db_filesize_in_pages,
freelist_pages,
schema_cookie,
schema_format,
suggested_cache_size,
incremental_vacuum_settings: IncrementalVacuumSettings {
largest_root_btree_page,
incremental_vacuum_mode,
},
database_text_encoding,
user_version,
application_id,
reserved_for_expansion,
version_valid_for,
write_library_version,
})
}
}
impl ValidateParsed for SqliteHeader {
fn validate_parsed(&self) -> SqliteResult<()> {
{
const MINIMUM_USABLE_SIZE: u32 = 480;
if (u32::from(self.page_size())
- u32::from(**self.reserved_bytes_per_page()))
< MINIMUM_USABLE_SIZE
{
return Err(SqliteError::HeaderValidationError(
"The usable size is not allowed to be less than 480.".into(),
));
}
}
{
if **self.db_filesize_in_pages() < 1 {
return Err(SqliteError::HeaderValidationError(
"The in-header database size is not valid".into(),
));
}
if **self.file_change_counter() != **self.version_valid_for() {
return Err(SqliteError::HeaderValidationError(
"The change counter must exactly matches the version-valid-for number".into(),
));
}
}
{
if *self.schema_format() != SchemaFormat::Format4 {
return Err(SqliteError::HeaderValidationError(
"Only Schema format 4 is supported".into(),
));
}
}
{
let freelist_pages = self.freelist_pages();
if (**freelist_pages.total() == 0) && (**freelist_pages.first() != 0) {
return Err(SqliteError::HeaderValidationError(
"Free list settings may be corrupted".into(),
));
}
}
{
let incremental_vacuum_mode =
u32::from(self.incremental_vacuum_settings.incremental_vacuum_mode());
let largest_root_btree_page =
**self.incremental_vacuum_settings.largest_root_btree_page();
if incremental_vacuum_mode == 0 && largest_root_btree_page != 0 {
return Err(SqliteError::HeaderValidationError(
"Incremental vacuum settings is zero but corrupted".into(),
));
}
}
{
if **self.file_change_counter() < 1 {
return Err(SqliteError::HeaderValidationError(
"File change counter maybe corrupted".into(),
));
}
if **self.file_change_counter() < **self.version_valid_for() {
return Err(SqliteError::HeaderValidationError(
"The version-valid-for number or the change counter maybe corrupted"
.into(),
));
}
}
Ok(())
}
}
impl TryFrom<&[u8]> for SqliteHeader {
type Error = SqliteError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let parsed = Self::parse_bytes(bytes)?;
parsed.validate_parsed()?;
Ok(parsed)
}
}