modelvault_core/
file_format.rs1use crate::error::{DbError, FormatError};
6
7pub const FILE_MAGIC: [u8; 4] = *b"TDB0";
8
9pub const FORMAT_MAJOR: u16 = 0;
14pub const FORMAT_MINOR_V4: u16 = 4;
16pub const FORMAT_MINOR: u16 = 5;
18pub const FORMAT_MINOR_V6: u16 = 6;
20pub const FORMAT_MINOR_V3: u16 = 3;
22
23pub const FILE_HEADER_SIZE: usize = 32;
24
25pub const MAX_SEGMENT_DECODE_ENTRIES: usize = 1_048_576;
27
28pub fn check_decode_entry_count(n: usize) -> Result<(), DbError> {
30 if n > MAX_SEGMENT_DECODE_ENTRIES {
31 return Err(DbError::Format(FormatError::InvalidCatalogPayload {
32 message: format!("decode entry count {n} exceeds maximum {MAX_SEGMENT_DECODE_ENTRIES}"),
33 }));
34 }
35 Ok(())
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct FileHeader {
41 pub format_major: u16,
42 pub format_minor: u16,
43 pub header_size: u32,
44 pub flags: u64,
45}
46
47impl FileHeader {
48 pub fn new_v0_3() -> Self {
49 Self {
50 format_major: FORMAT_MAJOR,
51 format_minor: FORMAT_MINOR_V3,
52 header_size: FILE_HEADER_SIZE as u32,
53 flags: 0,
54 }
55 }
56
57 pub fn new_v0_4() -> Self {
58 Self {
59 format_major: FORMAT_MAJOR,
60 format_minor: FORMAT_MINOR_V4,
61 header_size: FILE_HEADER_SIZE as u32,
62 flags: 0,
63 }
64 }
65
66 pub fn new_v0_5() -> Self {
67 Self {
68 format_major: FORMAT_MAJOR,
69 format_minor: FORMAT_MINOR,
70 header_size: FILE_HEADER_SIZE as u32,
71 flags: 0,
72 }
73 }
74
75 pub fn new_v0_8() -> Self {
77 Self {
78 format_major: FORMAT_MAJOR,
79 format_minor: FORMAT_MINOR_V6,
80 header_size: FILE_HEADER_SIZE as u32,
81 flags: 0,
82 }
83 }
84
85 pub fn encode(self) -> [u8; FILE_HEADER_SIZE] {
86 let mut buf = [0u8; FILE_HEADER_SIZE];
87 buf[0..4].copy_from_slice(&FILE_MAGIC);
88 buf[4..6].copy_from_slice(&self.format_major.to_le_bytes());
89 buf[6..8].copy_from_slice(&self.format_minor.to_le_bytes());
90 buf[8..12].copy_from_slice(&self.header_size.to_le_bytes());
91 buf[12..20].copy_from_slice(&self.flags.to_le_bytes());
92 buf
93 }
94}
95
96pub fn decode_header(bytes: &[u8]) -> Result<FileHeader, DbError> {
97 if bytes.len() < FILE_HEADER_SIZE {
98 return Err(DbError::Format(FormatError::TruncatedHeader {
99 got: bytes.len(),
100 expected: FILE_HEADER_SIZE,
101 }));
102 }
103
104 if bytes[0..4] != FILE_MAGIC {
105 let mut got = [0u8; 4];
106 got.copy_from_slice(&bytes[0..4]);
107 return Err(DbError::Format(FormatError::BadMagic { got }));
108 }
109
110 let format_major = u16::from_le_bytes([bytes[4], bytes[5]]);
111 let format_minor = u16::from_le_bytes([bytes[6], bytes[7]]);
112 if format_major != FORMAT_MAJOR || !(2..=6).contains(&format_minor) {
113 return Err(DbError::Format(FormatError::UnsupportedVersion {
114 major: format_major,
115 minor: format_minor,
116 }));
117 }
118
119 let header_size = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
120 let flags = u64::from_le_bytes([
121 bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19],
122 ]);
123
124 Ok(FileHeader {
125 format_major,
126 format_minor,
127 header_size,
128 flags,
129 })
130}