1use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
2use std::io::{Read, Write};
3
4use crate::error::{AhuError, Result};
5
6pub const MAGIC: [u8; 4] = [0x41, 0x48, 0x55, 0x31]; pub const HEADER_SIZE: usize = 64;
8pub const FORMAT_MAJOR: u16 = 0;
9pub const FORMAT_MINOR: u16 = 1;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct FileHeader {
13 pub format_major: u16,
14 pub format_minor: u16,
15 pub manifest_offset: u64,
16 pub manifest_length: u32,
17 pub seal_offset: u64,
18 pub seal_length: u32,
19 pub index_offset: u64,
20 pub index_length: u64,
21 pub data_offset: u64,
22 pub data_length: u64,
23}
24
25impl FileHeader {
26 pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
27 let mut magic = [0u8; 4];
28 reader.read_exact(&mut magic)?;
29 if magic != MAGIC {
30 return Err(AhuError::BadMagic { found: magic });
31 }
32
33 let format_major = reader.read_u16::<BigEndian>()?;
34 if format_major != FORMAT_MAJOR {
35 let format_minor = reader.read_u16::<BigEndian>()?;
36 return Err(AhuError::UnsupportedVersion {
37 major: format_major,
38 minor: format_minor,
39 });
40 }
41 let format_minor = reader.read_u16::<BigEndian>()?;
42
43 Ok(FileHeader {
44 format_major,
45 format_minor,
46 manifest_offset: reader.read_u64::<BigEndian>()?,
47 manifest_length: reader.read_u32::<BigEndian>()?,
48 seal_offset: reader.read_u64::<BigEndian>()?,
49 seal_length: reader.read_u32::<BigEndian>()?,
50 index_offset: reader.read_u64::<BigEndian>()?,
51 index_length: reader.read_u64::<BigEndian>()?,
52 data_offset: reader.read_u64::<BigEndian>()?,
53 data_length: reader.read_u64::<BigEndian>()?,
54 })
55 }
56
57 pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
58 writer.write_all(&MAGIC)?;
59 writer.write_u16::<BigEndian>(self.format_major)?;
60 writer.write_u16::<BigEndian>(self.format_minor)?;
61 writer.write_u64::<BigEndian>(self.manifest_offset)?;
62 writer.write_u32::<BigEndian>(self.manifest_length)?;
63 writer.write_u64::<BigEndian>(self.seal_offset)?;
64 writer.write_u32::<BigEndian>(self.seal_length)?;
65 writer.write_u64::<BigEndian>(self.index_offset)?;
66 writer.write_u64::<BigEndian>(self.index_length)?;
67 writer.write_u64::<BigEndian>(self.data_offset)?;
68 writer.write_u64::<BigEndian>(self.data_length)?;
69 Ok(())
70 }
71
72 pub fn validate_bounds(&self, file_size: u64) -> Result<()> {
73 let check = |field: &'static str, offset: u64, length: u64| -> Result<()> {
74 if offset.checked_add(length).is_none_or(|end| end > file_size) {
75 return Err(AhuError::HeaderOutOfBounds {
76 field,
77 offset,
78 length,
79 file_size,
80 });
81 }
82 Ok(())
83 };
84
85 check(
86 "manifest",
87 self.manifest_offset,
88 self.manifest_length as u64,
89 )?;
90 check("seal", self.seal_offset, self.seal_length as u64)?;
91 check("index", self.index_offset, self.index_length)?;
92 check("data", self.data_offset, self.data_length)?;
93 Ok(())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use std::io::Cursor;
101
102 #[test]
103 fn round_trip() {
104 let header = FileHeader {
105 format_major: FORMAT_MAJOR,
106 format_minor: FORMAT_MINOR,
107 manifest_offset: 64,
108 manifest_length: 256,
109 seal_offset: 320,
110 seal_length: 512,
111 index_offset: 832,
112 index_length: 4800,
113 data_offset: 5632,
114 data_length: 102400,
115 };
116
117 let mut buf = Vec::new();
118 header.write_to(&mut buf).unwrap();
119 assert_eq!(buf.len(), HEADER_SIZE);
120
121 let mut cursor = Cursor::new(&buf);
122 let read_back = FileHeader::read_from(&mut cursor).unwrap();
123 assert_eq!(header, read_back);
124 }
125
126 #[test]
127 fn rejects_bad_magic() {
128 let buf = [0x00u8; HEADER_SIZE];
129 let mut cursor = Cursor::new(&buf);
130 let err = FileHeader::read_from(&mut cursor).unwrap_err();
131 assert!(matches!(err, AhuError::BadMagic { .. }));
132 }
133}