1use std::{
5 collections::BTreeSet,
6 io::{Cursor, Read},
7 ops::Range,
8};
9
10use byteorder::{LittleEndian, ReadBytesExt};
11
12use super::{
13 section::{validate_local_section_name, SectionNode, SectionPath},
14 ParsedFile,
15};
16
17pub const MAGIC: &[u8; 4] = b"VHBC";
18pub const VERSION: u16 = 1;
19pub const FLAGS: u16 = 0;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22struct BytecodeHeader {
23 context_len: usize,
24}
25
26impl BytecodeHeader {
27 const CONTEXT_LEN_SIZE: usize = 8;
28 const ENCODED_LEN: usize = 4 + 2 + 2 + Self::CONTEXT_LEN_SIZE;
29
30 fn read_from<R: Read>(reader: &mut R) -> eyre::Result<Self> {
31 let mut magic = [0; 4];
32 reader.read_exact(&mut magic)?;
33 if &magic != MAGIC {
34 return Err(eyre::eyre!("invalid bytecode magic"));
35 }
36
37 let version = reader.read_u16::<LittleEndian>()?;
38 if version != VERSION {
39 return Err(eyre::eyre!(
40 "unsupported bytecode version {} (expected {})",
41 version,
42 VERSION
43 ));
44 }
45
46 let flags = reader.read_u16::<LittleEndian>()?;
47 if flags != FLAGS {
48 return Err(eyre::eyre!("unsupported bytecode flags 0x{flags:04X}"));
49 }
50
51 Ok(Self {
52 context_len: read_usize_u64(reader, "global context length")?,
53 })
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58struct SectionFrame {
59 section_len: usize,
60 composite_header_len: usize,
61}
62
63impl SectionFrame {
64 const SECTION_LEN_SIZE: usize = 8;
65 const HEADER_LEN_SIZE: usize = 8;
66 const ENCODED_LEN: usize = Self::SECTION_LEN_SIZE + Self::HEADER_LEN_SIZE;
67
68 fn read_from<R: Read>(reader: &mut R) -> eyre::Result<Self> {
69 Ok(Self {
70 section_len: read_usize_u64(reader, "section length")?,
71 composite_header_len: read_usize_u64(reader, "composite header length")?,
72 })
73 }
74
75 fn read_at(bytes: &[u8], section_start: usize, path: &SectionPath) -> eyre::Result<Self> {
76 let frame_end = checked_add(section_start, Self::ENCODED_LEN, "section frame end")?;
77 let frame_bytes = bytes.get(section_start..frame_end).ok_or_else(|| {
78 eyre::eyre!(
79 "section `{}` does not contain a complete section frame",
80 path
81 )
82 })?;
83 let mut cursor = Cursor::new(frame_bytes);
84 Self::read_from(&mut cursor)
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89struct SectionBytecodeHeader {
90 bytecode_len: usize,
91}
92
93impl SectionBytecodeHeader {
94 const BYTECODE_LEN_SIZE: usize = 8;
95 const ENCODED_LEN: usize = Self::BYTECODE_LEN_SIZE;
96
97 fn read_from<R: Read>(reader: &mut R) -> eyre::Result<Self> {
98 Ok(Self {
99 bytecode_len: read_usize_u64(reader, "bytecode length")?,
100 })
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105struct ChildSectionTableHeader {
106 child_count: usize,
107}
108
109impl ChildSectionTableHeader {
110 const CHILD_COUNT_SIZE: usize = 4;
111 const ENCODED_LEN: usize = Self::CHILD_COUNT_SIZE;
112
113 fn read_from<R: Read>(reader: &mut R) -> eyre::Result<Self> {
114 Ok(Self {
115 child_count: reader.read_u32::<LittleEndian>()? as usize,
116 })
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121struct ChildSectionTableEntry {
122 local_name_string: u32,
123 section_offset: usize,
124}
125
126impl ChildSectionTableEntry {
127 const LOCAL_NAME_STRING_SIZE: usize = 4;
128 const SECTION_OFFSET_SIZE: usize = 8;
129 const ENCODED_LEN: usize = Self::LOCAL_NAME_STRING_SIZE + Self::SECTION_OFFSET_SIZE;
130
131 fn read_from<R: Read>(reader: &mut R) -> eyre::Result<Self> {
132 Ok(Self {
133 local_name_string: reader.read_u32::<LittleEndian>()?,
134 section_offset: read_usize_u64(reader, "child section offset")?,
135 })
136 }
137}
138
139pub fn parse_file<F>(bytes: &[u8], mut section_name: F) -> eyre::Result<ParsedFile>
140where
141 F: FnMut(u32) -> Option<String>,
142{
143 let context = context_range(bytes)?;
144
145 let root = parse_section(
146 bytes,
147 &mut section_name,
148 SectionParseInfo {
149 start: context.end,
150 path: SectionPath::root(),
151 },
152 )?;
153 if root.section.end != bytes.len() {
154 return Err(eyre::eyre!(
155 "bytecode length mismatch: root section describes {} bytes, file has {} bytes",
156 root.section.end,
157 bytes.len()
158 ));
159 }
160
161 Ok(ParsedFile { context, root })
162}
163
164pub fn context_range(bytes: &[u8]) -> eyre::Result<Range<usize>> {
165 let mut cursor = Cursor::new(bytes);
166 let header = BytecodeHeader::read_from(&mut cursor)?;
167 let context_start = BytecodeHeader::ENCODED_LEN;
168 let context_end = checked_add(context_start, header.context_len, "global context end")?;
169 if bytes.get(context_start..context_end).is_none() {
170 return Err(eyre::eyre!("global context is out of bounds"));
171 }
172 Ok(context_start..context_end)
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
176struct SectionParseInfo {
177 start: usize,
178 path: SectionPath,
179}
180
181fn parse_section<F>(
182 bytes: &[u8],
183 section_name: &mut F,
184 info: SectionParseInfo,
185) -> eyre::Result<SectionNode>
186where
187 F: FnMut(u32) -> Option<String>,
188{
189 let SectionParseInfo {
190 start: section_start,
191 path,
192 } = info;
193
194 let frame = SectionFrame::read_at(bytes, section_start, &path)?;
195 let section_end = checked_add(section_start, frame.section_len, "section end")?;
196 if section_end > bytes.len() {
197 return Err(eyre::eyre!(
198 "section `{}` extends past end of bytecode",
199 path
200 ));
201 }
202 if frame.composite_header_len > frame.section_len {
203 return Err(eyre::eyre!(
204 "section `{}` composite header length {} exceeds section length {}",
205 path,
206 frame.composite_header_len,
207 frame.section_len
208 ));
209 }
210
211 let composite_header_start = checked_add(
212 section_start,
213 SectionFrame::ENCODED_LEN,
214 "composite header start",
215 )?;
216 let composite_header_end = checked_add(
217 composite_header_start,
218 frame.composite_header_len,
219 "composite header end",
220 )?;
221 let composite_header = composite_header_start..composite_header_end;
222
223 let bytecode_header_start = composite_header.end;
224 if checked_add(
225 bytecode_header_start,
226 SectionBytecodeHeader::ENCODED_LEN,
227 "section bytecode header end",
228 )? > section_end
229 {
230 return Err(eyre::eyre!(
231 "section `{}` does not contain a complete bytecode header",
232 path
233 ));
234 }
235
236 let mut bytecode_header_cursor = Cursor::new(&bytes[bytecode_header_start..section_end]);
237 let bytecode_header = SectionBytecodeHeader::read_from(&mut bytecode_header_cursor)?;
238 let bytecode_start = checked_add(
239 bytecode_header_start,
240 SectionBytecodeHeader::ENCODED_LEN,
241 "bytecode start",
242 )?;
243 let bytecode_end = checked_add(bytecode_start, bytecode_header.bytecode_len, "bytecode end")?;
244 if bytecode_end > section_end {
245 return Err(eyre::eyre!(
246 "section `{}` bytecode extends past section end",
247 path
248 ));
249 }
250
251 let child_table_start = bytecode_end;
252 if checked_add(
253 child_table_start,
254 ChildSectionTableHeader::ENCODED_LEN,
255 "child section table header end",
256 )? > section_end
257 {
258 return Err(eyre::eyre!(
259 "section `{}` does not contain a complete child section table header",
260 path
261 ));
262 }
263
264 let mut child_table_cursor = Cursor::new(&bytes[child_table_start..section_end]);
265 let child_table_header = ChildSectionTableHeader::read_from(&mut child_table_cursor)?;
266 let total_len_of_child_section_entries = child_table_header
267 .child_count
268 .checked_mul(ChildSectionTableEntry::ENCODED_LEN)
269 .ok_or_else(|| eyre::eyre!("section `{}` child table length overflows usize", path))?;
270 let expected_child_table_len = checked_add(
271 ChildSectionTableHeader::ENCODED_LEN,
272 total_len_of_child_section_entries,
273 "child section table length",
274 )?;
275 if checked_add(
276 child_table_start,
277 expected_child_table_len,
278 "child section table end",
279 )? > section_end
280 {
281 return Err(eyre::eyre!(
282 "section `{}` does not contain a complete child section table",
283 path
284 ));
285 }
286
287 let child_section_entries = read_child_section_table(
288 &mut child_table_cursor,
289 child_table_header.child_count,
290 &path,
291 section_name,
292 )?;
293
294 let child_table_len = child_table_cursor.position() as usize;
295 let child_table_end = checked_add(
296 child_table_start,
297 child_table_len,
298 "child section table end",
299 )?;
300
301 let mut claimed_ranges = ClaimedSectionRanges::new(bytecode_start..child_table_end);
302 let mut children = Vec::with_capacity(child_section_entries.len());
303 for child in child_section_entries {
304 children.push(parse_child_section(
305 bytes,
306 section_name,
307 ChildSectionParseInfo {
308 parent_path: &path,
309 parent_start: section_start,
310 parent_end: section_end,
311 parent_child_table_end: child_table_end,
312 claimed_ranges: &mut claimed_ranges,
313 child,
314 },
315 )?);
316 }
317
318 Ok(SectionNode {
319 path,
320 section: section_start..section_end,
321 header: composite_header,
322 bytecode: bytecode_start..bytecode_end,
323 children,
324 })
325}
326
327struct ResolvedChildSectionTableEntry {
328 name: String,
329 entry: ChildSectionTableEntry,
330}
331
332fn read_child_section_table<R, F>(
333 reader: &mut R,
334 child_count: usize,
335 parent_path: &SectionPath,
336 section_name: &mut F,
337) -> eyre::Result<Vec<ResolvedChildSectionTableEntry>>
338where
339 R: Read,
340 F: FnMut(u32) -> Option<String>,
341{
342 let mut children = Vec::with_capacity(child_count);
343 let mut names = BTreeSet::new();
344 for _ in 0..child_count {
345 let entry = ChildSectionTableEntry::read_from(reader)?;
346 let child_name = section_name(entry.local_name_string).ok_or_else(|| {
347 eyre::eyre!(
348 "section `{}` references missing section name string {}",
349 parent_path,
350 entry.local_name_string
351 )
352 })?;
353 validate_local_section_name(parent_path, &child_name)?;
354 if !names.insert(child_name.clone()) {
355 return Err(eyre::eyre!(
356 "section `{}` declares duplicate child `{}`",
357 parent_path,
358 child_name
359 ));
360 }
361
362 children.push(ResolvedChildSectionTableEntry {
363 name: child_name,
364 entry,
365 });
366 }
367 Ok(children)
368}
369
370struct ClaimedSectionRanges {
371 ranges: Vec<(String, Range<usize>)>,
372}
373
374impl ClaimedSectionRanges {
375 fn new(parent_data: Range<usize>) -> Self {
376 Self {
377 ranges: vec![("parent data".to_string(), parent_data)],
378 }
379 }
380
381 fn claim_child(
382 &mut self,
383 parent_path: &SectionPath,
384 child_name: String,
385 range: Range<usize>,
386 ) -> eyre::Result<()> {
387 for (existing_name, existing) in &self.ranges {
388 if ranges_overlap(existing, &range) {
389 return Err(eyre::eyre!(
390 "section `{}` child `{}` overlaps `{}`",
391 parent_path,
392 child_name,
393 existing_name
394 ));
395 }
396 }
397 self.ranges.push((child_name, range));
398 Ok(())
399 }
400}
401
402struct ChildSectionParseInfo<'a> {
403 parent_path: &'a SectionPath,
404 parent_start: usize,
405 parent_end: usize,
406 parent_child_table_end: usize,
407 claimed_ranges: &'a mut ClaimedSectionRanges,
408 child: ResolvedChildSectionTableEntry,
409}
410
411fn parse_child_section<F>(
412 bytes: &[u8],
413 section_name: &mut F,
414 info: ChildSectionParseInfo<'_>,
415) -> eyre::Result<SectionNode>
416where
417 F: FnMut(u32) -> Option<String>,
418{
419 let ChildSectionParseInfo {
420 parent_path,
421 parent_start,
422 parent_end,
423 parent_child_table_end,
424 claimed_ranges,
425 child,
426 } = info;
427 let ResolvedChildSectionTableEntry {
428 name: child_name,
429 entry,
430 } = child;
431
432 let child_start = checked_add(parent_start, entry.section_offset, "child section start")?;
433 let child_path = parent_path.child(child_name.clone());
434 if child_start < parent_child_table_end {
435 return Err(eyre::eyre!(
436 "section `{}` child `{}` begins before parent child table ends",
437 parent_path,
438 child_name
439 ));
440 }
441 if child_start >= parent_end {
442 return Err(eyre::eyre!(
443 "section `{}` child `{}` extends past section end",
444 parent_path,
445 child_name
446 ));
447 }
448 if checked_add(
449 child_start,
450 SectionFrame::ENCODED_LEN,
451 "child section frame end",
452 )? > parent_end
453 {
454 return Err(eyre::eyre!(
455 "section `{}` child `{}` extends past section end",
456 parent_path,
457 child_name
458 ));
459 }
460
461 let child_frame = SectionFrame::read_at(bytes, child_start, &child_path)?;
462 let child_end = checked_add(child_start, child_frame.section_len, "child section end")?;
463 if child_end > parent_end {
464 return Err(eyre::eyre!(
465 "section `{}` child `{}` extends past section end",
466 parent_path,
467 child_name
468 ));
469 }
470
471 let range = child_start..child_end;
472 claimed_ranges.claim_child(parent_path, child_name, range)?;
473
474 parse_section(
475 bytes,
476 section_name,
477 SectionParseInfo {
478 start: child_start,
479 path: child_path,
480 },
481 )
482}
483
484pub fn checked_add(lhs: usize, rhs: usize, label: &str) -> eyre::Result<usize> {
485 lhs.checked_add(rhs)
486 .ok_or_else(|| eyre::eyre!("{label} overflows usize"))
487}
488
489fn ranges_overlap(lhs: &Range<usize>, rhs: &Range<usize>) -> bool {
490 lhs.start < rhs.end && rhs.start < lhs.end
491}
492
493fn read_usize_u64<R: Read>(reader: &mut R, label: &str) -> eyre::Result<usize> {
494 usize::try_from(reader.read_u64::<LittleEndian>()?)
495 .map_err(|_| eyre::eyre!("{label} does not fit in usize"))
496}