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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
//! Reference: https://www.sqlite.org/fileformat2.html

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;
pub mod traits;
mod user_version;
mod version_valid_for;
mod write_library_version;

use self::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,
};

/// # 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,
  /// Unused pages in the database file are stored on a freelist.
  freelist_pages: FreeListPages,
  /// The schema cookie.
  schema_cookie: SchemaCookie,
  /// The schema format number.
  schema_format: SchemaFormat,
  /// Default page cache size.
  suggested_cache_size: SuggestedCacheSize,
  /// Incremental vacuum settings.
  incremental_vacuum_settings: IncrementalVacuumSettings,
  /// 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.
  database_text_encoding: DatabaseTextEncoding,
  /// The "user version" as read and set by the user_version pragma.
  user_version: UserVersion,
  /// The "Application ID" set by PRAGMA application_id.
  application_id: ApplicationId,
  /// Reserved for expansion. Must be zero.
  reserved_for_expansion: ReservedForExpansion,
  /// Version-valid-for number
  version_valid_for: VersionValidFor,
  /// Write library version number
  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<()> {
    {
      //  The usable size is not allowed to be less than 480. In other words, if
      // the page size is 512, then the reserved space size cannot exceed 32.
      const MINIMUM_USABLE_SIZE: u32 = 480;
      if (**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.",
        ));
      }
    }

    {
      //  The in-header database size is only considered to be valid if it is
      // non-zero and if the 4-byte change counter at offset 24 exactly matches
      // the 4-byte version-valid-for number at offset 92. The in-header database
      // size is always valid when the database is only modified using recent
      // versions of SQLite, versions 3.7.0 (2010-07-21) and later.
      if **self.db_filesize_in_pages() < 1 {
        return Err(SQLiteError::HeaderValidationError(
          "The in-header database size is not valid",
        ));
      }
      if **self.file_change_counter() != **self.version_valid_for() {
        return Err(SQLiteError::HeaderValidationError(
        "The change counter must exactly matches the version-valid-for number",
      ));
      }
      //  If a legacy version of SQLite writes to the database, it will not know
      // to update the in-header database size and so the in-header database
      // size could be incorrect. But legacy versions of SQLite will also leave
      // the version-valid-for number at offset 92 unchanged so it will not
      // match the change-counter. Hence, invalid in-header database sizes can
      // be detected (and ignored) by observing when the change-counter does not
      // match the version-valid-for number.}
    }
    // TODO: Free page list

    // TODO: Schema Cookie

    {
      //  New database files created by SQLite use format 4 by default. The
      // legacy_file_format pragma can be used to cause SQLite to create new
      // database files using format 1. The format version number can be made to
      // default to 1 instead of 4 by setting SQLITE_DEFAULT_FILE_FORMAT=1 at
      // compile-time.
      if *self.schema_format() != SchemaFormat::Format4 {
        return Err(SQLiteError::HeaderValidationError(
          "Only Schema format 4 is supported",
        ));
      }
    }

    {
      //  Unused pages in the database file are stored on a freelist. The 4-byte
      // big-endian integer at offset 32 stores the page number of the first
      // page of the freelist, or zero if the freelist is empty. The 4-byte
      // big-endian integer at offset 36 stores the total number of pages on the
      // freelist.
      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",
        ));
      }
    }
    {
      //  If the integer at offset 52 is non-zero then it is the page number of
      // the largest root page in the database file, the database file will
      // contain ptrmap pages, and the mode must be either auto_vacuum or
      // incremental_vacuum. In this latter case, the integer at offset 64 is
      // true for incremental_vacuum and false for auto_vacuum. If the integer
      // at offset 52 is zero then the integer at offset 64 must also be zero.
      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",
        ));
      }
    }
    {
      //  The 4-byte big-endian integer at offset 92 is the value of the change
      // counter when the version number was stored. The integer at offset 92
      // indicates which transaction the version number is valid for and is
      // sometimes called the "version-valid-for number".
      if **self.file_change_counter() < 1 {
        return Err(SQLiteError::HeaderValidationError(
          "File change counter maybe corrupted",
        ));
      }
      if **self.file_change_counter() < **self.version_valid_for() {
        return Err(SQLiteError::HeaderValidationError(
          "The version-valid-for number or the change counter maybe corrupted",
        ));
      }
    }

    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)
  }
}