Skip to main content

vihaco_parser_core/container/
bytecode.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4use 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}