1use crate::cursor::ManifestCursor;
9use crate::error::CoreError;
10
11pub const FEATURE_FLAGS_SECTION_VERSION: u8 = 1;
13
14pub const PREFIX_LEN: usize = 5;
17
18pub const ENTRY_LEN: usize = 3;
20
21#[derive(Copy, Clone, Debug, Eq, PartialEq)]
28pub struct FeatureFlag {
29 pub flag_id: u16,
30 pub required: bool,
31}
32
33#[derive(Clone, Debug, Eq, PartialEq, Default)]
39pub struct FeatureFlags {
40 pub entries: Vec<FeatureFlag>,
41}
42
43impl FeatureFlags {
44 #[must_use]
45 pub fn is_empty(&self) -> bool {
46 self.entries.is_empty()
47 }
48
49 #[must_use]
50 pub fn len(&self) -> usize {
51 self.entries.len()
52 }
53
54 #[must_use]
56 pub fn get(&self, flag_id: u16) -> Option<FeatureFlag> {
57 self.entries
58 .iter()
59 .copied()
60 .find(|entry| entry.flag_id == flag_id)
61 }
62
63 #[must_use]
65 pub fn is_required(&self, flag_id: u16) -> bool {
66 self.get(flag_id).is_some_and(|entry| entry.required)
67 }
68}
69
70pub fn parse_feature_flags_section(
85 cursor: &mut ManifestCursor<'_>,
86) -> Result<FeatureFlags, CoreError> {
87 let section_version = cursor.read_u8()?;
88 if section_version != FEATURE_FLAGS_SECTION_VERSION {
89 return Err(CoreError::UnsupportedFeature {
90 feature: format!(
91 "feature_flags section version {section_version} (supported: {FEATURE_FLAGS_SECTION_VERSION})"
92 ),
93 });
94 }
95 let raw_count = cursor.read_u32_le()?;
96 let entry_count = usize::try_from(raw_count).map_err(|_| CoreError::Corrupt {
97 reason: format!("feature_flags entry count {raw_count} exceeds usize"),
98 })?;
99 let payload_size = entry_count
104 .checked_mul(ENTRY_LEN)
105 .ok_or_else(|| CoreError::Corrupt {
106 reason: format!("feature_flags entry count {entry_count} overflows section size"),
107 })?;
108 if cursor.remaining_len() < payload_size {
109 return Err(CoreError::TooShort {
110 have: cursor.remaining_len(),
111 need: payload_size,
112 });
113 }
114 let mut entries = Vec::with_capacity(entry_count);
115 for index in 0..entry_count {
116 let flag_id = cursor.read_u16_le()?;
117 if flag_id == 0 {
118 return Err(CoreError::Corrupt {
119 reason: format!("feature_flags entry {index}: flag_id 0x0000 is reserved"),
120 });
121 }
122 let required_byte = cursor.read_u8()?;
123 let required = match required_byte {
124 0x00 => false,
125 0x01 => true,
126 other => {
127 return Err(CoreError::Corrupt {
128 reason: format!(
129 "feature_flags entry {index}: required byte must be 0x00 or 0x01, got 0x{other:02X}"
130 ),
131 });
132 }
133 };
134 if entries
135 .iter()
136 .any(|existing: &FeatureFlag| existing.flag_id == flag_id)
137 {
138 return Err(CoreError::Corrupt {
139 reason: format!("feature_flags entry {index}: duplicate flag_id 0x{flag_id:04X}"),
140 });
141 }
142 entries.push(FeatureFlag { flag_id, required });
143 }
144 Ok(FeatureFlags { entries })
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 fn make_flags_bytes(version: u8, entries: &[(u16, u8)]) -> Vec<u8> {
152 let mut bytes = Vec::with_capacity(PREFIX_LEN + entries.len() * ENTRY_LEN);
153 bytes.push(version);
154 let count = u32::try_from(entries.len()).expect("test entries fit in u32");
155 bytes.extend_from_slice(&count.to_le_bytes());
156 for (flag_id, required) in entries {
157 bytes.extend_from_slice(&flag_id.to_le_bytes());
158 bytes.push(*required);
159 }
160 bytes
161 }
162
163 #[test]
164 fn parses_empty_section() {
165 let bytes = make_flags_bytes(FEATURE_FLAGS_SECTION_VERSION, &[]);
166 let mut cursor = ManifestCursor::new(&bytes);
167 let flags = parse_feature_flags_section(&mut cursor).expect("empty section parses");
168 assert!(flags.is_empty());
169 assert_eq!(flags.len(), 0);
170 assert_eq!(cursor.position(), PREFIX_LEN);
171 }
172
173 #[test]
174 fn parses_single_required_ec_flag() {
175 let bytes = make_flags_bytes(FEATURE_FLAGS_SECTION_VERSION, &[(0x0001, 0x01)]);
176 let mut cursor = ManifestCursor::new(&bytes);
177 let flags = parse_feature_flags_section(&mut cursor).expect("single flag parses");
178 assert_eq!(cursor.position(), PREFIX_LEN + ENTRY_LEN);
179 assert_eq!(flags.len(), 1);
180 assert_eq!(
181 flags.entries[0],
182 FeatureFlag {
183 flag_id: 0x0001,
184 required: true,
185 }
186 );
187 assert!(flags.is_required(0x0001));
188 assert!(!flags.is_required(0x0002));
189 }
190
191 #[test]
192 fn parses_mixed_required_and_optional() {
193 let bytes = make_flags_bytes(
194 FEATURE_FLAGS_SECTION_VERSION,
195 &[(0x0001, 0x01), (0x0012, 0x00), (0x0020, 0x01)],
196 );
197 let mut cursor = ManifestCursor::new(&bytes);
198 let flags = parse_feature_flags_section(&mut cursor).expect("mixed flags parse");
199 assert_eq!(cursor.position(), PREFIX_LEN + 3 * ENTRY_LEN);
200 assert_eq!(flags.len(), 3);
201 assert!(flags.is_required(0x0001));
202 assert!(!flags.is_required(0x0012));
203 assert!(flags.is_required(0x0020));
204 assert_eq!(flags.get(0x0012).unwrap().flag_id, 0x0012);
205 }
206
207 #[test]
208 fn parses_after_a_header() {
209 let mut bytes = vec![0u8; 16];
210 bytes[..4].copy_from_slice(b"LMFS");
211 bytes[4..6].copy_from_slice(&1u16.to_le_bytes());
212 bytes[6..8].copy_from_slice(&1u16.to_le_bytes());
213 bytes[8..10].copy_from_slice(&1u16.to_le_bytes());
214 bytes.extend_from_slice(&make_flags_bytes(
215 FEATURE_FLAGS_SECTION_VERSION,
216 &[(0x0001, 0x01)],
217 ));
218 let mut cursor = ManifestCursor::new(&bytes);
219 cursor.skip(16).unwrap();
220 let flags = parse_feature_flags_section(&mut cursor).expect("after header");
221 assert_eq!(flags.len(), 1);
222 assert_eq!(cursor.position(), 16 + PREFIX_LEN + ENTRY_LEN);
223 }
224
225 #[test]
226 fn rejects_unknown_section_version() {
227 let bytes = make_flags_bytes(7, &[]);
228 let mut cursor = ManifestCursor::new(&bytes);
229 match parse_feature_flags_section(&mut cursor) {
230 Err(CoreError::UnsupportedFeature { feature }) => {
231 assert!(feature.contains("version 7"), "got: {feature}");
232 }
233 other => panic!("expected UnsupportedFeature, got {other:?}"),
234 }
235 }
236
237 #[test]
238 fn rejects_short_prefix() {
239 let bytes = [FEATURE_FLAGS_SECTION_VERSION, 0, 0, 0];
242 let mut cursor = ManifestCursor::new(&bytes);
243 match parse_feature_flags_section(&mut cursor) {
244 Err(CoreError::TooShort { have, need }) => {
245 assert_eq!(have, 3);
246 assert_eq!(need, 4);
247 }
248 other => panic!("expected TooShort, got {other:?}"),
249 }
250 }
251
252 #[test]
253 fn rejects_truncated_entries() {
254 let mut bytes = Vec::new();
255 bytes.push(FEATURE_FLAGS_SECTION_VERSION);
256 bytes.extend_from_slice(&10u32.to_le_bytes());
257 bytes.extend_from_slice(&[0x01, 0x00, 0x01]); let mut cursor = ManifestCursor::new(&bytes);
259 match parse_feature_flags_section(&mut cursor) {
260 Err(CoreError::TooShort { have, need }) => {
261 assert_eq!(have, 3);
265 assert_eq!(need, 10 * ENTRY_LEN);
266 }
267 other => panic!("expected TooShort, got {other:?}"),
268 }
269 }
270
271 #[test]
272 fn rejects_zero_flag_id() {
273 let bytes = make_flags_bytes(FEATURE_FLAGS_SECTION_VERSION, &[(0x0000, 0x01)]);
274 let mut cursor = ManifestCursor::new(&bytes);
275 match parse_feature_flags_section(&mut cursor) {
276 Err(CoreError::Corrupt { reason }) => {
277 assert!(reason.contains("0x0000"), "got: {reason}");
278 assert!(reason.contains("reserved"));
279 }
280 other => panic!("expected Corrupt, got {other:?}"),
281 }
282 }
283
284 #[test]
285 fn rejects_bad_required_byte() {
286 let bytes = make_flags_bytes(FEATURE_FLAGS_SECTION_VERSION, &[(0x0001, 0x05)]);
287 let mut cursor = ManifestCursor::new(&bytes);
288 match parse_feature_flags_section(&mut cursor) {
289 Err(CoreError::Corrupt { reason }) => {
290 assert!(reason.contains("required"), "got: {reason}");
291 assert!(reason.contains("0x05"));
292 }
293 other => panic!("expected Corrupt, got {other:?}"),
294 }
295 }
296
297 #[test]
298 fn rejects_duplicate_flag_id() {
299 let bytes = make_flags_bytes(
300 FEATURE_FLAGS_SECTION_VERSION,
301 &[(0x0001, 0x01), (0x0001, 0x00)],
302 );
303 let mut cursor = ManifestCursor::new(&bytes);
304 match parse_feature_flags_section(&mut cursor) {
305 Err(CoreError::Corrupt { reason }) => {
306 assert!(reason.contains("duplicate"), "got: {reason}");
307 assert!(reason.contains("0x0001"));
308 }
309 other => panic!("expected Corrupt, got {other:?}"),
310 }
311 }
312
313 #[test]
314 fn round_trip_all_standard_flags() {
315 let standard: &[(u16, u8)] = &[
316 (0x0001, 0x01),
317 (0x0002, 0x00),
318 (0x0010, 0x00),
319 (0x0011, 0x00),
320 (0x0012, 0x01),
321 (0x0013, 0x00),
322 (0x0014, 0x00),
323 (0x0020, 0x01),
324 (0x0021, 0x00),
325 (0x0022, 0x00),
326 (0x0100, 0x00),
327 (0x0101, 0x00),
328 ];
329 let bytes = make_flags_bytes(FEATURE_FLAGS_SECTION_VERSION, standard);
330 let mut cursor = ManifestCursor::new(&bytes);
331 let flags = parse_feature_flags_section(&mut cursor).expect("all standard parse");
332 assert_eq!(flags.len(), standard.len());
333 assert_eq!(cursor.position(), PREFIX_LEN + standard.len() * ENTRY_LEN);
334 for (entry, (flag_id, required)) in flags.entries.iter().zip(standard.iter()) {
335 assert_eq!(entry.flag_id, *flag_id);
336 assert_eq!(entry.required, *required != 0);
337 }
338 }
339}