1use crate::cursor::ManifestCursor;
8use crate::error::CoreError;
9use limnifs_format::{MANIFEST_HEADER_LEN, MANIFEST_MAGIC};
10
11#[derive(Copy, Clone, Debug, Eq, PartialEq)]
19pub struct ManifestHeader {
20 pub drop_store_version: u16,
21 pub metadata_version: u16,
22 pub manifest_version: u16,
23}
24
25impl ManifestHeader {
26 pub const CURRENT_DROP_STORE_VERSION: u16 = 1;
30 pub const CURRENT_METADATA_VERSION: u16 = 1;
31 pub const CURRENT_MANIFEST_VERSION: u16 = 1;
32
33 #[must_use]
34 pub const fn new(
35 drop_store_version: u16,
36 metadata_version: u16,
37 manifest_version: u16,
38 ) -> Self {
39 Self {
40 drop_store_version,
41 metadata_version,
42 manifest_version,
43 }
44 }
45
46 #[must_use]
47 pub fn current() -> Self {
48 Self::new(
49 Self::CURRENT_DROP_STORE_VERSION,
50 Self::CURRENT_METADATA_VERSION,
51 Self::CURRENT_MANIFEST_VERSION,
52 )
53 }
54
55 #[must_use]
57 pub fn to_bytes(self) -> [u8; MANIFEST_HEADER_LEN] {
58 let mut out = [0u8; MANIFEST_HEADER_LEN];
59 out[..4].copy_from_slice(&MANIFEST_MAGIC);
60 out[4..6].copy_from_slice(&self.drop_store_version.to_le_bytes());
61 out[6..8].copy_from_slice(&self.metadata_version.to_le_bytes());
62 out[8..10].copy_from_slice(&self.manifest_version.to_le_bytes());
63 out
65 }
66}
67
68pub fn parse_manifest_header(cursor: &mut ManifestCursor<'_>) -> Result<ManifestHeader, CoreError> {
83 let magic = cursor.read_magic()?;
84 if magic != MANIFEST_MAGIC {
85 return Err(CoreError::BadMagic { found: magic });
86 }
87 let drop_store_version = cursor.read_u16_le()?;
88 let metadata_version = cursor.read_u16_le()?;
89 let manifest_version = cursor.read_u16_le()?;
90 let reserved = cursor.read_n(6)?;
91 if reserved.iter().any(|&b| b != 0) {
92 return Err(CoreError::Corrupt {
93 reason: format!(
94 "reserved bytes 10..{MANIFEST_HEADER_LEN} must be zero, found {reserved:?}"
95 ),
96 });
97 }
98 Ok(ManifestHeader {
99 drop_store_version,
100 metadata_version,
101 manifest_version,
102 })
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 fn current_header_bytes() -> [u8; MANIFEST_HEADER_LEN] {
110 let mut bytes = [0u8; MANIFEST_HEADER_LEN];
111 bytes[..4].copy_from_slice(b"LMFS");
112 bytes[4..6].copy_from_slice(&1u16.to_le_bytes());
113 bytes[6..8].copy_from_slice(&1u16.to_le_bytes());
114 bytes[8..10].copy_from_slice(&1u16.to_le_bytes());
115 bytes
116 }
117
118 #[test]
119 fn parses_current_header() {
120 let bytes = current_header_bytes();
121 let mut cursor = ManifestCursor::new(&bytes);
122 let header = parse_manifest_header(&mut cursor).expect("current header parses");
123 assert_eq!(header, ManifestHeader::current());
124 assert_eq!(cursor.position(), MANIFEST_HEADER_LEN);
125 }
126
127 #[test]
128 fn parses_zero_versions() {
129 let mut bytes = current_header_bytes();
130 bytes[4..10].fill(0);
131 let mut cursor = ManifestCursor::new(&bytes);
132 let header = parse_manifest_header(&mut cursor).expect("zero versions parse");
133 assert_eq!(header.drop_store_version, 0);
134 assert_eq!(header.metadata_version, 0);
135 assert_eq!(header.manifest_version, 0);
136 }
137
138 #[test]
139 fn rejects_short_buffer() {
140 let mut short = [0u8; 12];
144 short[..4].copy_from_slice(b"LMFS");
145 short[4..6].copy_from_slice(&1u16.to_le_bytes());
146 short[6..8].copy_from_slice(&1u16.to_le_bytes());
147 short[8..10].copy_from_slice(&1u16.to_le_bytes());
148 let mut cursor = ManifestCursor::new(&short);
149 match parse_manifest_header(&mut cursor) {
150 Err(CoreError::TooShort { have, need }) => {
151 assert_eq!(have, 2);
152 assert_eq!(need, 6);
153 }
154 other => panic!("expected TooShort, got {other:?}"),
155 }
156 }
157
158 #[test]
159 fn rejects_empty_buffer() {
160 let empty: [u8; 0] = [];
161 let mut cursor = ManifestCursor::new(&empty);
162 assert!(matches!(
163 parse_manifest_header(&mut cursor),
164 Err(CoreError::TooShort { .. })
165 ));
166 }
167
168 #[test]
169 fn rejects_bad_magic() {
170 let mut bytes = current_header_bytes();
171 bytes[0] = b'X';
172 let mut cursor = ManifestCursor::new(&bytes);
173 match parse_manifest_header(&mut cursor) {
174 Err(CoreError::BadMagic { found }) => {
175 assert_eq!(found, *b"XMFS");
176 }
177 other => panic!("expected BadMagic, got {other:?}"),
178 }
179 }
180
181 #[test]
182 fn rejects_nonzero_reserved() {
183 let mut bytes = current_header_bytes();
184 bytes[13] = 0x01;
185 let mut cursor = ManifestCursor::new(&bytes);
186 match parse_manifest_header(&mut cursor) {
187 Err(CoreError::Corrupt { reason }) => {
188 assert!(reason.contains("reserved"), "reason was: {reason}");
189 }
190 other => panic!("expected Corrupt, got {other:?}"),
191 }
192 }
193
194 #[test]
195 fn accepts_extra_bytes_after_header() {
196 let mut bytes = vec![0u8; MANIFEST_HEADER_LEN + 32];
197 bytes[..MANIFEST_HEADER_LEN].copy_from_slice(¤t_header_bytes());
198 let mut cursor = ManifestCursor::new(&bytes);
199 let header = parse_manifest_header(&mut cursor).expect("extra bytes are ignored");
200 assert_eq!(header, ManifestHeader::current());
201 assert_eq!(cursor.position(), MANIFEST_HEADER_LEN);
202 assert_eq!(cursor.remaining_len(), 32);
203 }
204
205 #[test]
206 fn round_trip_to_bytes() {
207 let header = ManifestHeader::new(7, 11, 13);
208 let bytes = header.to_bytes();
209 assert_eq!(&bytes[..4], b"LMFS");
210 assert_eq!(u16::from_le_bytes([bytes[4], bytes[5]]), 7);
211 assert_eq!(u16::from_le_bytes([bytes[6], bytes[7]]), 11);
212 assert_eq!(u16::from_le_bytes([bytes[8], bytes[9]]), 13);
213 assert_eq!(&bytes[10..MANIFEST_HEADER_LEN], &[0, 0, 0, 0, 0, 0]);
214
215 let mut cursor = ManifestCursor::new(&bytes);
216 let reparsed = parse_manifest_header(&mut cursor).expect("roundtrip");
217 assert_eq!(reparsed, header);
218 }
219}