1use bitflags::bitflags;
2
3pub trait BigEndianEncode {
5 type Bytes;
6
7 fn to_be_canonical(self) -> Self::Bytes;
8}
9
10pub trait BigEndianDecode: Sized {
12 type Bytes;
13
14 fn from_be_canonical(bytes: Self::Bytes) -> Self;
15}
16
17pub trait LittleEndianEncode {
19 type Bytes;
20
21 fn to_le_canonical(self) -> Self::Bytes;
22}
23
24pub trait LittleEndianDecode: Sized {
26 type Bytes;
27
28 fn from_le_canonical(bytes: Self::Bytes) -> Self;
29}
30
31macro_rules! impl_endian_codecs {
32 ($ty:ty, $len:expr) => {
33 impl BigEndianEncode for $ty {
34 type Bytes = [u8; $len];
35
36 fn to_be_canonical(self) -> Self::Bytes {
37 self.to_be_bytes()
38 }
39 }
40
41 impl BigEndianDecode for $ty {
42 type Bytes = [u8; $len];
43
44 fn from_be_canonical(bytes: Self::Bytes) -> Self {
45 Self::from_be_bytes(bytes)
46 }
47 }
48
49 impl LittleEndianEncode for $ty {
50 type Bytes = [u8; $len];
51
52 fn to_le_canonical(self) -> Self::Bytes {
53 self.to_le_bytes()
54 }
55 }
56
57 impl LittleEndianDecode for $ty {
58 type Bytes = [u8; $len];
59
60 fn from_le_canonical(bytes: Self::Bytes) -> Self {
61 Self::from_le_bytes(bytes)
62 }
63 }
64 };
65}
66
67impl_endian_codecs!(u16, 2);
68impl_endian_codecs!(u32, 4);
69impl_endian_codecs!(u64, 8);
70impl_endian_codecs!(i32, 4);
71
72fn read_array<const N: usize>(input: &[u8], offset: usize) -> Option<[u8; N]> {
73 let end = offset.checked_add(N)?;
74 input.get(offset..end)?.try_into().ok()
75}
76
77fn write_array<const N: usize>(output: &mut [u8], offset: usize, bytes: [u8; N]) -> bool {
78 let Some(end) = offset.checked_add(N) else {
79 return false;
80 };
81 let Some(dst) = output.get_mut(offset..end) else {
82 return false;
83 };
84 dst.copy_from_slice(&bytes);
85 true
86}
87
88#[must_use]
89pub fn read_u16_be(input: &[u8], offset: usize) -> Option<u16> {
90 read_array::<2>(input, offset).map(u16::from_be_canonical)
91}
92
93#[must_use]
94pub fn read_u32_be(input: &[u8], offset: usize) -> Option<u32> {
95 read_array::<4>(input, offset).map(u32::from_be_canonical)
96}
97
98#[must_use]
99pub fn read_u64_be(input: &[u8], offset: usize) -> Option<u64> {
100 read_array::<8>(input, offset).map(u64::from_be_canonical)
101}
102
103#[must_use]
104pub fn read_i32_be(input: &[u8], offset: usize) -> Option<i32> {
105 read_array::<4>(input, offset).map(i32::from_be_canonical)
106}
107
108#[must_use]
109pub fn read_u16_le(input: &[u8], offset: usize) -> Option<u16> {
110 read_array::<2>(input, offset).map(u16::from_le_canonical)
111}
112
113#[must_use]
114pub fn read_u32_le(input: &[u8], offset: usize) -> Option<u32> {
115 read_array::<4>(input, offset).map(u32::from_le_canonical)
116}
117
118#[must_use]
119pub fn read_u64_le(input: &[u8], offset: usize) -> Option<u64> {
120 read_array::<8>(input, offset).map(u64::from_le_canonical)
121}
122
123#[must_use]
124pub fn write_u16_be(output: &mut [u8], offset: usize, value: u16) -> bool {
125 write_array(output, offset, value.to_be_canonical())
126}
127
128#[must_use]
129pub fn write_u32_be(output: &mut [u8], offset: usize, value: u32) -> bool {
130 write_array(output, offset, value.to_be_canonical())
131}
132
133#[must_use]
134pub fn write_u64_be(output: &mut [u8], offset: usize, value: u64) -> bool {
135 write_array(output, offset, value.to_be_canonical())
136}
137
138#[must_use]
139pub fn write_i32_be(output: &mut [u8], offset: usize, value: i32) -> bool {
140 write_array(output, offset, value.to_be_canonical())
141}
142
143#[must_use]
144pub fn write_u16_le(output: &mut [u8], offset: usize, value: u16) -> bool {
145 write_array(output, offset, value.to_le_canonical())
146}
147
148#[must_use]
149pub fn write_u32_le(output: &mut [u8], offset: usize, value: u32) -> bool {
150 write_array(output, offset, value.to_le_canonical())
151}
152
153#[must_use]
154pub fn write_u64_le(output: &mut [u8], offset: usize, value: u64) -> bool {
155 write_array(output, offset, value.to_le_canonical())
156}
157
158bitflags! {
159 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
161 pub struct OpenFlags: u32 {
162 const READONLY = 0x0000_0001;
164 const READWRITE = 0x0000_0002;
166 const CREATE = 0x0000_0004;
168 const URI = 0x0000_0040;
170 const MEMORY = 0x0000_0080;
172 const NOMUTEX = 0x0000_8000;
174 const FULLMUTEX = 0x0001_0000;
176 const SHAREDCACHE = 0x0002_0000;
178 const PRIVATECACHE = 0x0004_0000;
180 const NOFOLLOW = 0x0100_0000;
182 }
183}
184
185impl Default for OpenFlags {
186 fn default() -> Self {
187 Self::READWRITE | Self::CREATE
188 }
189}
190
191bitflags! {
192 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
194 pub struct SyncFlags: u8 {
195 const NORMAL = 0x02;
197 const FULL = 0x03;
199 const DATAONLY = 0x10;
201 }
202}
203
204bitflags! {
205 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
207 pub struct VfsOpenFlags: u32 {
208 const MAIN_DB = 0x0000_0100;
210 const MAIN_JOURNAL = 0x0000_0800;
212 const TEMP_DB = 0x0000_0200;
214 const TEMP_JOURNAL = 0x0000_1000;
216 const SUBJOURNAL = 0x0000_2000;
218 const SUPER_JOURNAL = 0x0000_4000;
220 const WAL = 0x0008_0000;
222 const EXCLUSIVE = 0x0000_0010;
224 const CREATE = 0x0000_0004;
226 const READWRITE = 0x0000_0002;
228 const READONLY = 0x0000_0001;
230 const DELETEONCLOSE = 0x0000_0008;
232 }
233}
234
235bitflags! {
236 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
238 pub struct AccessFlags: u8 {
239 const EXISTS = 0;
241 const READWRITE = 1;
243 const READ = 2;
245 }
246}
247
248bitflags! {
249 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
251 pub struct PrepareFlags: u32 {
252 const PERSISTENT = 0x01;
254 const NORMALIZE = 0x02;
256 const NO_VTAB = 0x04;
258 }
259}
260
261bitflags! {
262 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
264 pub struct MemFlags: u16 {
265 const NULL = 0x0001;
267 const STR = 0x0002;
269 const INT = 0x0004;
271 const REAL = 0x0008;
273 const BLOB = 0x0010;
275 const INT_REAL = 0x0020;
277 const AFF_MASK = 0x003F;
279 const DYN = 0x0040;
281 const STATIC = 0x0080;
283 const EPHEM = 0x0100;
285 const CLEARED = 0x0200;
287 const TERM = 0x0400;
289 const SUBTYPE = 0x0800;
291 const ZERO = 0x1000;
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use std::fs::File;
300 use std::io::{Read, Seek};
301 use std::process::Command;
302 use std::sync::atomic::{AtomicUsize, Ordering};
303
304 #[test]
305 fn open_flags_default() {
306 let flags = OpenFlags::default();
307 assert!(flags.contains(OpenFlags::READWRITE));
308 assert!(flags.contains(OpenFlags::CREATE));
309 assert!(!flags.contains(OpenFlags::READONLY));
310 }
311
312 #[test]
313 fn open_flags_combinations() {
314 let flags = OpenFlags::READONLY | OpenFlags::URI;
315 assert!(flags.contains(OpenFlags::READONLY));
316 assert!(flags.contains(OpenFlags::URI));
317 assert!(!flags.contains(OpenFlags::CREATE));
318 }
319
320 #[test]
321 fn sync_flags() {
322 let flags = SyncFlags::FULL | SyncFlags::DATAONLY;
323 assert!(flags.contains(SyncFlags::FULL));
324 assert!(flags.contains(SyncFlags::DATAONLY));
325 }
326
327 #[test]
328 fn vfs_open_flags() {
329 let flags = VfsOpenFlags::MAIN_DB | VfsOpenFlags::CREATE | VfsOpenFlags::READWRITE;
330 assert!(flags.contains(VfsOpenFlags::MAIN_DB));
331 assert!(flags.contains(VfsOpenFlags::CREATE));
332 }
333
334 #[test]
335 fn prepare_flags() {
336 let flags = PrepareFlags::PERSISTENT | PrepareFlags::NORMALIZE;
337 assert!(flags.contains(PrepareFlags::PERSISTENT));
338 assert!(flags.contains(PrepareFlags::NORMALIZE));
339 }
340
341 #[test]
342 fn mem_flags() {
343 let flags = MemFlags::INT | MemFlags::STATIC;
344 assert!(flags.contains(MemFlags::INT));
345 assert!(flags.contains(MemFlags::STATIC));
346 assert!(!flags.contains(MemFlags::NULL));
347 }
348
349 #[test]
350 fn test_sqlite_structures_big_endian() {
351 let header = crate::DatabaseHeader {
352 page_size: crate::PageSize::new(4096).expect("valid page size"),
353 change_counter: 0x0102_0304,
354 page_count: 0x0A0B_0C0D,
355 default_cache_size: -2000,
356 ..crate::DatabaseHeader::default()
357 };
358
359 let bytes = header
360 .to_bytes()
361 .expect("header serialization must succeed");
362 assert_eq!(read_u16_be(&bytes, 16), Some(4096));
363 assert_eq!(read_u32_be(&bytes, 24), Some(header.change_counter));
364 assert_eq!(read_u32_be(&bytes, 28), Some(header.page_count));
365 assert_eq!(read_i32_be(&bytes, 48), Some(header.default_cache_size));
366
367 let mut page = vec![0u8; header.page_size.as_usize()];
368 page[0] = crate::BTreePageType::LeafTable as u8;
369 assert!(write_u16_be(&mut page, 1, 0));
370 assert!(write_u16_be(&mut page, 3, 1));
371 assert!(write_u16_be(&mut page, 5, 400));
372 page[7] = 0;
373
374 let parsed = crate::BTreePageHeader::parse(&page, header.page_size, 0, false)
375 .expect("btree header parsing must succeed");
376 assert_eq!(parsed.cell_count, 1);
377 assert_eq!(read_u16_be(&page, 3), Some(parsed.cell_count));
378 assert_eq!(read_u16_be(&page, 5), Some(400));
379 }
380
381 #[test]
382 fn test_mixed_endian_udp_documented() {
383 let mut packet = [0u8; 12];
386 assert!(write_u16_be(&mut packet, 0, 0xBEEF));
387 assert!(write_u16_be(&mut packet, 2, 8)); assert!(write_u64_le(&mut packet, 4, 0x1122_3344_5566_7788));
389
390 assert_eq!(read_u16_be(&packet, 0), Some(0xBEEF));
391 assert_eq!(read_u16_be(&packet, 2), Some(8));
392 assert_eq!(read_u64_le(&packet, 4), Some(0x1122_3344_5566_7788));
393 }
394
395 #[test]
396 fn test_e2e_canonical_bytes_match_sqlite_where_required() {
397 static COUNTER: AtomicUsize = AtomicUsize::new(0);
398
399 if Command::new("sqlite3").arg("--version").output().is_err() {
400 return;
401 }
402
403 let mut path = std::env::temp_dir();
404 path.push(format!(
405 "fsqlite_bd_22n_7_{}_{}.sqlite",
406 std::process::id(),
407 COUNTER.fetch_add(1, Ordering::Relaxed)
408 ));
409
410 let status = Command::new("sqlite3")
411 .arg(&path)
412 .arg("CREATE TABLE t(x); INSERT INTO t VALUES(1);")
413 .status()
414 .expect("sqlite3 execution must succeed");
415 assert!(status.success(), "sqlite3 command failed");
416
417 let mut file = File::open(&path).expect("must open sqlite file");
418 let mut header_bytes = [0u8; crate::DATABASE_HEADER_SIZE];
419 file.read_exact(&mut header_bytes)
420 .expect("must read sqlite header");
421
422 let parsed =
423 crate::DatabaseHeader::from_bytes(&header_bytes).expect("header parse must succeed");
424 let rewritten = parsed.to_bytes().expect("header encode must succeed");
425 assert_eq!(header_bytes, rewritten, "canonical bytes must roundtrip");
426 assert_eq!(
427 parsed
428 .open_mode(crate::MAX_FILE_FORMAT_VERSION)
429 .expect("open mode derivation must succeed"),
430 crate::DatabaseOpenMode::ReadWrite
431 );
432
433 let encoded_page_size = if parsed.page_size.get() == 65_536 {
434 1
435 } else {
436 u16::try_from(parsed.page_size.get()).expect("page size <= u16")
437 };
438 assert_eq!(read_u16_be(&header_bytes, 16), Some(encoded_page_size));
439 assert_eq!(read_u32_be(&header_bytes, 24), Some(parsed.change_counter));
440 assert_eq!(read_u32_be(&header_bytes, 28), Some(parsed.page_count));
441
442 let mut page1 = vec![0u8; parsed.page_size.as_usize()];
443 file.rewind().expect("rewind to file start");
444 file.read_exact(&mut page1).expect("read page 1");
445 let btree =
446 crate::BTreePageHeader::parse(&page1, parsed.page_size, parsed.reserved_per_page, true)
447 .expect("parse page1 btree header");
448
449 assert_eq!(page1[crate::DATABASE_HEADER_SIZE], btree.page_type as u8);
450 assert_eq!(
451 read_u16_be(&page1, crate::DATABASE_HEADER_SIZE + 3),
452 Some(btree.cell_count)
453 );
454 }
455}