sq3_rs/
file_header.rs

1//! Reference: https://www.sqlite.org/fileformat2.html
2
3mod application_id;
4mod database_text_encoding;
5mod db_filesize_in_pages;
6mod file_change_counter;
7mod file_format_version_numbers;
8mod freelist_pages;
9mod incremental_vacuum_settings;
10mod magic_header_string;
11mod page_size;
12mod payload_fractions;
13mod reserved_bytes_per_page;
14mod reserved_for_expansion;
15mod schema_cookie;
16mod schema_format;
17mod suggested_cache_size;
18
19mod user_version;
20mod version_valid_for;
21mod write_library_version;
22
23use sq3_derive::Name;
24
25use sq3_parser::TypeName;
26
27use crate::{
28    result::SqliteError,
29    traits::{ParseBytes, ValidateParsed},
30    SqliteResult,
31};
32
33pub use self::{
34    application_id::ApplicationId,
35    database_text_encoding::DatabaseTextEncoding,
36    db_filesize_in_pages::DatabaseFileSizeInPages,
37    file_change_counter::FileChangeCounter,
38    file_format_version_numbers::{
39        FileFormatReadVersion, FileFormatVersionNumbers, FileFormatWriteVersion,
40    },
41    freelist_pages::{FreeListPages, FreeListPagesFirstTrunkPage, FreeListPagesTotalPages},
42    incremental_vacuum_settings::{
43        IncrementalVacuumMode, IncrementalVacuumSettings, LargestRootBtreePage,
44    },
45    magic_header_string::MagicHeaderString,
46    page_size::{PageSize, PageSizeIterator},
47    payload_fractions::{
48        LeafPayloadFraction, MaximumEmbeddedPayloadFraction, MinimumEmbeddedPayloadFraction,
49        PayloadFractions,
50    },
51    reserved_bytes_per_page::ReservedBytesPerPage,
52    reserved_for_expansion::ReservedForExpansion,
53    schema_cookie::SchemaCookie,
54    schema_format::SchemaFormat,
55    suggested_cache_size::SuggestedCacheSize,
56    user_version::UserVersion,
57    version_valid_for::VersionValidFor,
58    write_library_version::WriteLibraryVersion,
59};
60
61/// # Database File Format
62///
63/// |Offset | Size  | Description|
64/// |-------|-------|------------|
65/// |  0    | 16    | The header string: "Sqlite format 3\000" |
66/// | 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. |
67/// | 18    |  1    | File format write version. 1 for legacy; 2 for WAL. |
68/// | 19    |  1    | File format read version. 1 for legacy; 2 for WAL. |
69/// | 20    |  1    | Bytes of unused "reserved" space at the end of each page. Usually 0. |
70/// | 21    |  1    | Maximum embedded payload fraction. Must be 64. |
71/// | 22    |  1    | Minimum embedded payload fraction. Must be 32. |
72/// | 23    |  1    | Leaf payload fraction. Must be 32. |
73/// | 24    |  4    | File change counter. |
74/// | 28    |  4    | Size of the database file in pages. The "in-header database size". |
75/// | 32    |  4    | Page number of the first freelist trunk page. |
76/// | 36    |  4    | Total number of freelist pages. |
77/// | 40    |  4    | The schema cookie. |
78/// | 44    |  4    | The schema format number. Supported schema formats are 1, 2, 3, and 4. |
79/// | 48    |  4    | Default page cache size. |
80/// | 52    |  4    | The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise. |
81/// | 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. |
82/// | 60    |  4    | The "user version" as read and set by the user_version pragma. |
83/// | 64    |  4    | True (non-zero) for incremental-vacuum mode. False (zero) otherwise. |
84/// | 68    |  4    | The "Application ID" set by PRAGMA application_id. |
85/// | 72    | 20    | Reserved for expansion. Must be zero. |
86/// | 92    |  4    | The version-valid-for number. |
87/// | 96    |  4    | SQLITE_VERSION_NUMBER |
88#[derive(Debug, Default, Name)]
89pub struct SqliteHeader {
90    /// The header string: "`Sqlite format 3\000`".
91    magic_header_string: MagicHeaderString,
92    /// The database page size in bytes.
93    ///  Must be a power of two between 512 and 32768 inclusive,
94    /// or the bytes 1 representing a page size of 65536.
95    page_size: PageSize,
96    /// File format version numbers.
97    file_format_version_numbers: FileFormatVersionNumbers,
98    /// Bytes of unused "reserved" space at the end of each page. Usually 0.
99    reserved_bytes_per_page: ReservedBytesPerPage,
100    /// Payload Fractions.
101    payload_fractions: PayloadFractions,
102    /// File change counter.
103    file_change_counter: FileChangeCounter,
104    /// Size of the database file in pages. The "in-header database size".
105    db_filesize_in_pages: DatabaseFileSizeInPages,
106    /// Unused pages in the database file are stored on a freelist.
107    freelist_pages: FreeListPages,
108    /// The schema cookie.
109    schema_cookie: SchemaCookie,
110    /// The schema format number.
111    schema_format: SchemaFormat,
112    /// Default page cache size.
113    suggested_cache_size: SuggestedCacheSize,
114    /// Incremental vacuum settings.
115    incremental_vacuum_settings: IncrementalVacuumSettings,
116    /// The database text encoding. A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be.
117    database_text_encoding: DatabaseTextEncoding,
118    /// The "user version" as read and set by the user_version pragma.
119    user_version: UserVersion,
120    /// The "Application ID" set by PRAGMA application_id.
121    application_id: ApplicationId,
122    /// Reserved for expansion. Must be zero.
123    reserved_for_expansion: ReservedForExpansion,
124    /// Version-valid-for number
125    version_valid_for: VersionValidFor,
126    /// Write library version number
127    write_library_version: WriteLibraryVersion,
128}
129/*
130
131[./data/mydatabase.db]:
132database page size:  4096
133write format:        1
134read format:         1
135reserved bytes:      0
136file change counter: 5
137database page count: 6
138freelist page count: 0
139schema cookie:       3
140schema format:       4
141default cache size:  0
142autovacuum top root: 0
143incremental vacuum:  0
144text encoding:       1 (utf8)
145user version:        0
146application id:      0
147software version:    3041002
148
149*/
150impl SqliteHeader {
151    pub const LENGTH_BYTES: usize = 100;
152    pub fn magic_header_string(&self) -> &MagicHeaderString {
153        &self.magic_header_string
154    }
155
156    pub fn page_size(&self) -> &PageSize {
157        &self.page_size
158    }
159
160    pub fn file_format_version_numbers(&self) -> &FileFormatVersionNumbers {
161        &self.file_format_version_numbers
162    }
163
164    pub fn reserved_bytes_per_page(&self) -> &ReservedBytesPerPage {
165        &self.reserved_bytes_per_page
166    }
167
168    pub fn payload_fractions(&self) -> &PayloadFractions {
169        &self.payload_fractions
170    }
171
172    pub fn file_change_counter(&self) -> &FileChangeCounter {
173        &self.file_change_counter
174    }
175
176    pub fn db_filesize_in_pages(&self) -> &DatabaseFileSizeInPages {
177        &self.db_filesize_in_pages
178    }
179
180    pub fn freelist_pages(&self) -> &FreeListPages {
181        &self.freelist_pages
182    }
183
184    pub fn schema_cookie(&self) -> &SchemaCookie {
185        &self.schema_cookie
186    }
187
188    pub fn schema_format(&self) -> &SchemaFormat {
189        &self.schema_format
190    }
191
192    pub fn suggested_cache_size(&self) -> &SuggestedCacheSize {
193        &self.suggested_cache_size
194    }
195
196    pub fn incremental_vacuum_settings(&self) -> &IncrementalVacuumSettings {
197        &self.incremental_vacuum_settings
198    }
199
200    pub fn database_text_encoding(&self) -> &DatabaseTextEncoding {
201        &self.database_text_encoding
202    }
203
204    pub fn user_version(&self) -> &UserVersion {
205        &self.user_version
206    }
207
208    pub fn application_id(&self) -> &ApplicationId {
209        &self.application_id
210    }
211
212    pub fn reserved_for_expansion(&self) -> &ReservedForExpansion {
213        &self.reserved_for_expansion
214    }
215
216    pub fn version_valid_for(&self) -> &VersionValidFor {
217        &self.version_valid_for
218    }
219
220    pub fn write_library_version(&self) -> &WriteLibraryVersion {
221        &self.write_library_version
222    }
223}
224
225impl ParseBytes for SqliteHeader {
226    const LENGTH_BYTES: usize = Self::LENGTH_BYTES;
227
228    fn parsing_handler(bytes: &[u8]) -> SqliteResult<Self> {
229        let magic_header_string = MagicHeaderString::parse_bytes(&bytes[0..=15])?;
230        let page_size = PageSize::parse_bytes(&bytes[16..=17])?;
231        let file_format_version_numbers = FileFormatVersionNumbers::parse_bytes(&bytes[18..=19])?;
232        let reserved_bytes_per_page = ReservedBytesPerPage::parse_bytes(&[bytes[20]])?;
233        let payload_fractions = PayloadFractions::parse_bytes(&bytes[21..=23])?;
234
235        let file_change_counter = FileChangeCounter::parse_bytes(&bytes[24..=27])?;
236        let db_filesize_in_pages = DatabaseFileSizeInPages::parse_bytes(&bytes[28..=31])?;
237
238        let freelist_pages = FreeListPages::parse_bytes(&bytes[32..=39])?;
239
240        let schema_cookie = SchemaCookie::parse_bytes(&bytes[40..=43])?;
241
242        let schema_format = SchemaFormat::parse_bytes(&bytes[44..=47])?;
243
244        let suggested_cache_size = SuggestedCacheSize::parse_bytes(&bytes[48..=51])?;
245
246        let largest_root_btree_page = LargestRootBtreePage::parse_bytes(&bytes[52..=55])?;
247
248        let database_text_encoding = DatabaseTextEncoding::parse_bytes(&bytes[56..=59])?;
249
250        let user_version = UserVersion::parse_bytes(&bytes[60..=63])?;
251
252        let incremental_vacuum_mode = IncrementalVacuumMode::parse_bytes(&bytes[64..=67])?;
253
254        let application_id = ApplicationId::parse_bytes(&bytes[68..=71])?;
255
256        let reserved_for_expansion = ReservedForExpansion::parse_bytes(&bytes[72..=91])?;
257
258        let version_valid_for = VersionValidFor::parse_bytes(&bytes[92..=95])?;
259
260        let write_library_version = WriteLibraryVersion::parse_bytes(&bytes[96..=99])?;
261
262        Ok(Self {
263            magic_header_string,
264            page_size,
265            file_format_version_numbers,
266            reserved_bytes_per_page,
267            payload_fractions,
268            file_change_counter,
269            db_filesize_in_pages,
270            freelist_pages,
271            schema_cookie,
272            schema_format,
273            suggested_cache_size,
274            incremental_vacuum_settings: IncrementalVacuumSettings {
275                largest_root_btree_page,
276                incremental_vacuum_mode,
277            },
278            database_text_encoding,
279            user_version,
280            application_id,
281            reserved_for_expansion,
282            version_valid_for,
283            write_library_version,
284        })
285    }
286}
287
288impl ValidateParsed for SqliteHeader {
289    fn validate_parsed(&self) -> SqliteResult<()> {
290        {
291            //  The usable size is not allowed to be less than 480. In other words, if
292            // the page size is 512, then the reserved space size cannot exceed 32.
293            const MINIMUM_USABLE_SIZE: u32 = 480;
294            if (u32::from(self.page_size()) - u32::from(**self.reserved_bytes_per_page()))
295                < MINIMUM_USABLE_SIZE
296            {
297                return Err(SqliteError::HeaderValidationError(
298                    "The usable size is not allowed to be less than 480.".into(),
299                ));
300            }
301        }
302
303        {
304            //  The in-header database size is only considered to be valid if it is
305            // non-zero and if the 4-byte change counter at offset 24 exactly matches
306            // the 4-byte version-valid-for number at offset 92. The in-header database
307            // size is always valid when the database is only modified using recent
308            // versions of Sqlite, versions 3.7.0 (2010-07-21) and later.
309            if **self.db_filesize_in_pages() < 1 {
310                return Err(SqliteError::HeaderValidationError(
311                    "The in-header database size is not valid".into(),
312                ));
313            }
314            if **self.file_change_counter() != **self.version_valid_for() {
315                return Err(SqliteError::HeaderValidationError(
316                    "The change counter must exactly matches the version-valid-for number".into(),
317                ));
318            }
319            //  If a legacy version of Sqlite writes to the database, it will not know
320            // to update the in-header database size and so the in-header database
321            // size could be incorrect. But legacy versions of Sqlite will also leave
322            // the version-valid-for number at offset 92 unchanged so it will not
323            // match the change-counter. Hence, invalid in-header database sizes can
324            // be detected (and ignored) by observing when the change-counter does not
325            // match the version-valid-for number.}
326        }
327        // TODO: Free page list
328
329        // TODO: Schema Cookie
330
331        {
332            //  New database files created by Sqlite use format 4 by default. The
333            // legacy_file_format pragma can be used to cause Sqlite to create new
334            // database files using format 1. The format version number can be made to
335            // default to 1 instead of 4 by setting SQLITE_DEFAULT_FILE_FORMAT=1 at
336            // compile-time.
337            if *self.schema_format() != SchemaFormat::Format4 {
338                return Err(SqliteError::HeaderValidationError(
339                    "Only Schema format 4 is supported".into(),
340                ));
341            }
342        }
343
344        {
345            //  Unused pages in the database file are stored on a freelist. The 4-byte
346            // big-endian integer at offset 32 stores the page number of the first
347            // page of the freelist, or zero if the freelist is empty. The 4-byte
348            // big-endian integer at offset 36 stores the total number of pages on the
349            // freelist.
350            let freelist_pages = self.freelist_pages();
351            if (**freelist_pages.total() == 0) && (**freelist_pages.first() != 0) {
352                return Err(SqliteError::HeaderValidationError(
353                    "Free list settings may be corrupted".into(),
354                ));
355            }
356        }
357        {
358            //  If the integer at offset 52 is non-zero then it is the page number of
359            // the largest root page in the database file, the database file will
360            // contain ptrmap pages, and the mode must be either auto_vacuum or
361            // incremental_vacuum. In this latter case, the integer at offset 64 is
362            // true for incremental_vacuum and false for auto_vacuum. If the integer
363            // at offset 52 is zero then the integer at offset 64 must also be zero.
364            let incremental_vacuum_mode =
365                u32::from(self.incremental_vacuum_settings.incremental_vacuum_mode());
366            let largest_root_btree_page =
367                **self.incremental_vacuum_settings.largest_root_btree_page();
368            if incremental_vacuum_mode == 0 && largest_root_btree_page != 0 {
369                return Err(SqliteError::HeaderValidationError(
370                    "Incremental vacuum settings is zero but corrupted".into(),
371                ));
372            }
373        }
374        {
375            //  The 4-byte big-endian integer at offset 92 is the value of the change
376            // counter when the version number was stored. The integer at offset 92
377            // indicates which transaction the version number is valid for and is
378            // sometimes called the "version-valid-for number".
379            if **self.file_change_counter() < 1 {
380                return Err(SqliteError::HeaderValidationError(
381                    "File change counter maybe corrupted".into(),
382                ));
383            }
384            if **self.file_change_counter() < **self.version_valid_for() {
385                return Err(SqliteError::HeaderValidationError(
386                    "The version-valid-for number or the change counter maybe corrupted".into(),
387                ));
388            }
389        }
390
391        Ok(())
392    }
393}
394
395impl TryFrom<&[u8]> for SqliteHeader {
396    type Error = SqliteError;
397
398    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
399        let parsed = Self::parse_bytes(bytes)?;
400        parsed.validate_parsed()?;
401        Ok(parsed)
402    }
403}