1use pcf::{FileHeader, ENTRY_SIZE, HEADER_SIZE, TABLE_HEADER_SIZE};
5
6use super::diag::{DiagKind, Diagnostic, Severity};
7use super::walk::{BlockView, Walk};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum RegionKind {
12 FileHeader,
13 TableBlockHeader {
14 block_index: usize,
15 },
16 EntryArray {
17 block_index: usize,
18 entry_count: u8,
19 },
20 PartitionData {
21 uid: [u8; 16],
22 partition_type: u32,
23 used: u64,
24 max: u64,
25 },
26 Slack {
28 uid: [u8; 16],
29 },
30 Gap,
32}
33
34impl RegionKind {
35 pub fn glyph(&self) -> char {
37 match self {
38 RegionKind::FileHeader => 'H',
39 RegionKind::TableBlockHeader { .. } => 'T',
40 RegionKind::EntryArray { .. } => 'E',
41 RegionKind::PartitionData { .. } => 'D',
42 RegionKind::Slack { .. } => '_',
43 RegionKind::Gap => '.',
44 }
45 }
46
47 pub fn short(&self) -> &'static str {
49 match self {
50 RegionKind::FileHeader => "header",
51 RegionKind::TableBlockHeader { .. } => "tableheader",
52 RegionKind::EntryArray { .. } => "entries",
53 RegionKind::PartitionData { .. } => "data",
54 RegionKind::Slack { .. } => "slack",
55 RegionKind::Gap => "gap",
56 }
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct Region {
63 pub start: u64,
64 pub len: u64,
65 pub kind: RegionKind,
66 pub label: String,
67}
68
69impl Region {
70 pub fn end(&self) -> u64 {
71 self.start.saturating_add(self.len)
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct LayoutMap {
78 pub file_len: u64,
79 pub header: Option<FileHeader>,
80 pub blocks: Vec<BlockView>,
81 pub regions: Vec<Region>,
83 pub diagnostics: Vec<Diagnostic>,
84}
85
86pub fn build(walk: &Walk) -> LayoutMap {
88 let mut diagnostics = walk.diagnostics.clone();
89 let mut regions: Vec<Region> = Vec::new();
90
91 if walk.file_len >= HEADER_SIZE {
93 regions.push(Region {
94 start: 0,
95 len: HEADER_SIZE,
96 kind: RegionKind::FileHeader,
97 label: "file header".into(),
98 });
99 }
100
101 for b in &walk.blocks {
103 regions.push(Region {
104 start: b.offset,
105 len: TABLE_HEADER_SIZE,
106 kind: RegionKind::TableBlockHeader {
107 block_index: b.index,
108 },
109 label: format!("block {} header", b.index),
110 });
111 let count = b.header.partition_count;
112 if count > 0 {
113 regions.push(Region {
114 start: b.offset + TABLE_HEADER_SIZE,
115 len: count as u64 * ENTRY_SIZE,
116 kind: RegionKind::EntryArray {
117 block_index: b.index,
118 entry_count: count,
119 },
120 label: format!("block {} entries (x{count})", b.index),
121 });
122 }
123
124 for ev in &b.entries {
125 let e = &ev.entry;
126 let label = e.label_string().unwrap_or_default();
127 if e.used_bytes > 0 {
128 regions.push(Region {
129 start: e.start_offset,
130 len: e.used_bytes,
131 kind: RegionKind::PartitionData {
132 uid: e.uid,
133 partition_type: e.partition_type,
134 used: e.used_bytes,
135 max: e.max_length,
136 },
137 label: format!("data: {label}"),
138 });
139 }
140 let slack = e.max_length.saturating_sub(e.used_bytes);
141 if slack > 0 {
142 regions.push(Region {
143 start: e.start_offset + e.used_bytes,
144 len: slack,
145 kind: RegionKind::Slack { uid: e.uid },
146 label: format!("slack: {label}"),
147 });
148 }
149 }
150 }
151
152 regions.sort_by(|a, b| a.start.cmp(&b.start).then(a.len.cmp(&b.len)));
154
155 let mut out: Vec<Region> = Vec::with_capacity(regions.len());
157 let mut covered_end: u64 = 0;
158 for r in regions.into_iter() {
159 if r.start > covered_end {
160 let gap_len = r.start - covered_end;
161 diagnostics.push(Diagnostic {
162 severity: Severity::Info,
163 kind: DiagKind::Gap {
164 start: covered_end,
165 len: gap_len,
166 },
167 message: format!("{gap_len} dead byte(s) at {covered_end:#x}"),
168 });
169 out.push(Region {
170 start: covered_end,
171 len: gap_len,
172 kind: RegionKind::Gap,
173 label: "gap".into(),
174 });
175 } else if r.start < covered_end && r.len > 0 {
176 let ov = covered_end - r.start;
177 diagnostics.push(Diagnostic {
178 severity: Severity::Warning,
179 kind: DiagKind::Overlap {
180 start: r.start,
181 len: ov.min(r.len),
182 },
183 message: format!(
184 "region '{}' at {:#x} overlaps the preceding region",
185 r.label, r.start
186 ),
187 });
188 }
189 covered_end = covered_end.max(r.end());
190 out.push(r);
191 }
192 if covered_end < walk.file_len {
193 let gap_len = walk.file_len - covered_end;
194 diagnostics.push(Diagnostic {
195 severity: Severity::Info,
196 kind: DiagKind::Gap {
197 start: covered_end,
198 len: gap_len,
199 },
200 message: format!("{gap_len} trailing dead byte(s) at {covered_end:#x}"),
201 });
202 out.push(Region {
203 start: covered_end,
204 len: gap_len,
205 kind: RegionKind::Gap,
206 label: "trailing gap".into(),
207 });
208 }
209
210 LayoutMap {
211 file_len: walk.file_len,
212 header: walk.header,
213 blocks: walk.blocks.clone(),
214 regions: out,
215 diagnostics,
216 }
217}