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