1use std::io;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub struct ByteRange {
5 pub start: usize,
6 pub end: usize,
7}
8
9impl ByteRange {
10 pub fn len(&self) -> usize {
11 self.end.saturating_sub(self.start)
12 }
13
14 pub fn is_empty(&self) -> bool {
15 self.start == self.end
16 }
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum SectionId {
21 Header,
22 Handler(u8),
23 Tail,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct SectionLayout {
28 pub id: SectionId,
29 pub range: ByteRange,
30}
31
32#[derive(Debug, Clone)]
33pub struct FileLayout {
34 pub file_len: usize,
35 pub sections: Vec<SectionLayout>,
36}
37
38impl FileLayout {
39 pub fn validate(&self) -> io::Result<()> {
40 let Some(first) = self.sections.first() else {
41 return Err(io::Error::new(
42 io::ErrorKind::InvalidData,
43 "file layout must contain at least one section",
44 ));
45 };
46
47 if first.range.start != 0 {
48 return Err(io::Error::new(
49 io::ErrorKind::InvalidData,
50 "layout does not start at byte 0",
51 ));
52 }
53
54 let mut expected = 0usize;
55 for section in &self.sections {
56 if section.range.start != expected {
57 return Err(io::Error::new(
58 io::ErrorKind::InvalidData,
59 format!(
60 "layout gap/overlap around section {:?}: expected start {}, got {}",
61 section.id, expected, section.range.start
62 ),
63 ));
64 }
65 if section.range.end < section.range.start {
66 return Err(io::Error::new(
67 io::ErrorKind::InvalidData,
68 format!(
69 "invalid section range {:?}: {}..{}",
70 section.id, section.range.start, section.range.end
71 ),
72 ));
73 }
74 expected = section.range.end;
75 }
76
77 if expected != self.file_len {
78 return Err(io::Error::new(
79 io::ErrorKind::InvalidData,
80 format!(
81 "layout does not cover file: ended at {}, file length {}",
82 expected, self.file_len
83 ),
84 ));
85 }
86
87 Ok(())
88 }
89}