1use std::fmt;
9
10#[repr(C, packed)]
12#[derive(Clone, Copy)]
13pub struct DxMachineHeader {
14 pub magic: [u8; 2],
16 pub version: u8,
18 pub flags: u8,
20}
21
22pub const FLAG_HAS_HEAP: u8 = 0b0000_0001;
24
25pub const FLAG_HAS_INTERN: u8 = 0b0000_0010;
27
28pub const FLAG_LITTLE_ENDIAN: u8 = 0b0000_0100;
30
31pub const FLAG_HAS_LENGTH_TABLE: u8 = 0b0000_1000;
33
34const FLAG_RESERVED_MASK: u8 = 0b1111_0000;
36
37impl DxMachineHeader {
38 #[inline]
40 pub const fn new() -> Self {
41 Self {
42 magic: crate::machine::MAGIC,
43 version: crate::machine::VERSION,
44 flags: FLAG_LITTLE_ENDIAN,
45 }
46 }
47
48 #[inline]
50 pub const fn with_flags(flags: u8) -> Self {
51 Self {
52 magic: crate::machine::MAGIC,
53 version: crate::machine::VERSION,
54 flags: flags | FLAG_LITTLE_ENDIAN,
55 }
56 }
57
58 #[inline]
60 pub fn from_bytes(bytes: &[u8]) -> Result<Self, HeaderError> {
61 if bytes.len() < 4 {
62 return Err(HeaderError::BufferTooSmall);
63 }
64
65 let header = Self {
66 magic: [bytes[0], bytes[1]],
67 version: bytes[2],
68 flags: bytes[3],
69 };
70
71 header.validate()?;
72 Ok(header)
73 }
74
75 #[inline]
77 pub fn validate(&self) -> Result<(), HeaderError> {
78 if self.magic != crate::machine::MAGIC {
80 return Err(HeaderError::InvalidMagic {
81 expected: crate::machine::MAGIC,
82 found: self.magic,
83 });
84 }
85
86 if self.version != crate::machine::VERSION {
88 return Err(HeaderError::UnsupportedVersion {
89 supported: crate::machine::VERSION,
90 found: self.version,
91 });
92 }
93
94 if self.flags & FLAG_RESERVED_MASK != 0 {
96 return Err(HeaderError::ReservedFlagsSet);
97 }
98
99 if self.flags & FLAG_LITTLE_ENDIAN == 0 {
101 return Err(HeaderError::UnsupportedEndianness);
102 }
103
104 Ok(())
105 }
106
107 #[inline]
109 pub const fn has_heap(&self) -> bool {
110 self.flags & FLAG_HAS_HEAP != 0
111 }
112
113 #[inline]
115 pub const fn has_intern_table(&self) -> bool {
116 self.flags & FLAG_HAS_INTERN != 0
117 }
118
119 #[inline]
121 pub const fn has_length_table(&self) -> bool {
122 self.flags & FLAG_HAS_LENGTH_TABLE != 0
123 }
124
125 #[inline]
127 pub fn set_has_heap(&mut self, value: bool) {
128 if value {
129 self.flags |= FLAG_HAS_HEAP;
130 } else {
131 self.flags &= !FLAG_HAS_HEAP;
132 }
133 }
134
135 #[inline]
137 pub fn set_has_intern_table(&mut self, value: bool) {
138 if value {
139 self.flags |= FLAG_HAS_INTERN;
140 } else {
141 self.flags &= !FLAG_HAS_INTERN;
142 }
143 }
144
145 #[inline]
147 pub fn set_has_length_table(&mut self, value: bool) {
148 if value {
149 self.flags |= FLAG_HAS_LENGTH_TABLE;
150 } else {
151 self.flags &= !FLAG_HAS_LENGTH_TABLE;
152 }
153 }
154
155 #[inline]
157 pub fn write_to(&self, bytes: &mut [u8]) {
158 bytes[0] = self.magic[0];
159 bytes[1] = self.magic[1];
160 bytes[2] = self.version;
161 bytes[3] = self.flags;
162 }
163
164 #[inline]
166 pub const fn size() -> usize {
167 4
168 }
169}
170
171impl Default for DxMachineHeader {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177impl fmt::Debug for DxMachineHeader {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 f.debug_struct("DxMachineHeader")
180 .field(
181 "magic",
182 &format!("0x{:02X} 0x{:02X}", self.magic[0], self.magic[1]),
183 )
184 .field("version", &self.version)
185 .field("flags", &format!("0b{:08b}", self.flags))
186 .field("has_heap", &self.has_heap())
187 .field("has_intern_table", &self.has_intern_table())
188 .field("has_length_table", &self.has_length_table())
189 .finish()
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
195pub enum HeaderError {
196 BufferTooSmall,
198 InvalidMagic {
200 expected: [u8; 2],
202 found: [u8; 2],
204 },
205 UnsupportedVersion {
207 supported: u8,
209 found: u8,
211 },
212 ReservedFlagsSet,
214 UnsupportedEndianness,
216}
217
218impl fmt::Display for HeaderError {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 match self {
221 Self::BufferTooSmall => write!(f, "Buffer too small to contain header (need 4 bytes)"),
222 Self::InvalidMagic { expected, found } => {
223 write!(
224 f,
225 "Invalid magic bytes: expected {:02X?}, found {:02X?}",
226 expected, found
227 )
228 }
229 Self::UnsupportedVersion { supported, found } => write!(
230 f,
231 "Unsupported format version: this implementation supports v{:02X}, found v{:02X}",
232 supported, found
233 ),
234 Self::ReservedFlagsSet => {
235 write!(f, "Reserved flags are set (file from future version?)")
236 }
237 Self::UnsupportedEndianness => {
238 write!(f, "Unsupported endianness (big-endian not supported in v1)")
239 }
240 }
241 }
242}
243
244impl std::error::Error for HeaderError {}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_header_new() {
252 let header = DxMachineHeader::new();
253 assert_eq!(header.magic, [0x5A, 0x44]);
254 assert_eq!(header.version, 0x01);
255 assert_eq!(header.flags & FLAG_LITTLE_ENDIAN, FLAG_LITTLE_ENDIAN);
256 assert!(!header.has_heap());
257 assert!(!header.has_intern_table());
258 }
259
260 #[test]
261 fn test_header_flags() {
262 let mut header = DxMachineHeader::new();
263
264 assert!(!header.has_heap());
265 header.set_has_heap(true);
266 assert!(header.has_heap());
267
268 assert!(!header.has_intern_table());
269 header.set_has_intern_table(true);
270 assert!(header.has_intern_table());
271 }
272
273 #[test]
274 fn test_header_roundtrip() {
275 let mut bytes = [0u8; 4];
276 let header = DxMachineHeader::with_flags(FLAG_HAS_HEAP | FLAG_LITTLE_ENDIAN);
277
278 header.write_to(&mut bytes);
279 let parsed = DxMachineHeader::from_bytes(&bytes).unwrap();
280
281 assert_eq!(parsed.magic, header.magic);
282 assert_eq!(parsed.version, header.version);
283 assert_eq!(parsed.flags, header.flags);
284 }
285
286 #[test]
287 fn test_header_validation() {
288 let bytes = [0x5A, 0x44, 0x01, FLAG_LITTLE_ENDIAN];
290 assert!(DxMachineHeader::from_bytes(&bytes).is_ok());
291
292 let bytes = [0x00, 0x00, 0x01, FLAG_LITTLE_ENDIAN];
294 assert!(matches!(
295 DxMachineHeader::from_bytes(&bytes),
296 Err(HeaderError::InvalidMagic { .. })
297 ));
298
299 let bytes = [0x5A, 0x44, 0x99, FLAG_LITTLE_ENDIAN];
301 assert!(matches!(
302 DxMachineHeader::from_bytes(&bytes),
303 Err(HeaderError::UnsupportedVersion { .. })
304 ));
305
306 let bytes = [0x5A, 0x44, 0x01, FLAG_LITTLE_ENDIAN | 0b1000_0000];
308 assert!(matches!(
309 DxMachineHeader::from_bytes(&bytes),
310 Err(HeaderError::ReservedFlagsSet)
311 ));
312 }
313
314 #[test]
315 fn test_header_size() {
316 assert_eq!(DxMachineHeader::size(), 4);
317 assert_eq!(std::mem::size_of::<DxMachineHeader>(), 4);
318 }
319}