1mod 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#[derive(Debug, Default, Name)]
89pub struct SqliteHeader {
90 magic_header_string: MagicHeaderString,
92 page_size: PageSize,
96 file_format_version_numbers: FileFormatVersionNumbers,
98 reserved_bytes_per_page: ReservedBytesPerPage,
100 payload_fractions: PayloadFractions,
102 file_change_counter: FileChangeCounter,
104 db_filesize_in_pages: DatabaseFileSizeInPages,
106 freelist_pages: FreeListPages,
108 schema_cookie: SchemaCookie,
110 schema_format: SchemaFormat,
112 suggested_cache_size: SuggestedCacheSize,
114 incremental_vacuum_settings: IncrementalVacuumSettings,
116 database_text_encoding: DatabaseTextEncoding,
118 user_version: UserVersion,
120 application_id: ApplicationId,
122 reserved_for_expansion: ReservedForExpansion,
124 version_valid_for: VersionValidFor,
126 write_library_version: WriteLibraryVersion,
128}
129impl 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 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 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 }
327 {
332 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 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 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 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}