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