silpkg/base/
parse.rs

1use alloc::{string::String, vec::Vec};
2
3use hashbrown::HashMap;
4use macros::generator;
5
6use crate::{
7    base::{ENTRY_SIZE, HEADER_SIZE, MAGIC},
8    util::ByteSliceExt,
9};
10
11use super::{Entry, ParseError, PkgState, RawFlags, ReadSeekRequest, Response};
12
13#[generator(static, yield ReadSeekRequest -> Response)]
14pub fn check_magic() -> bool {
15    for expected in MAGIC {
16        let byte = match request!(read 1)[..] {
17            [byte] => byte,
18            [] => return false,
19            _ => unreachable!(),
20        };
21
22        if byte != *expected {
23            return false;
24        }
25    }
26
27    true
28}
29
30#[generator(static, yield ReadSeekRequest -> Response)]
31pub fn parse(expect_magic: bool) -> Result<PkgState, ParseError> {
32    request!(rewind);
33
34    if expect_magic && !check_magic().await {
35        return Err(ParseError::MismatchedMagic);
36    }
37
38    let read = request!(read exact 4);
39
40    {
41        let header_size = read[0..2].as_u16_be();
42        if header_size as u64 != HEADER_SIZE {
43            return Err(ParseError::MismatchedHeaderSize { size: header_size });
44        }
45    }
46
47    {
48        let entry_size = read[2..4].as_u16_be();
49        if entry_size as u64 != ENTRY_SIZE {
50            return Err(ParseError::MismatchedEntrySize { size: entry_size });
51        }
52    }
53
54    let storage_len = request!(stream len);
55    let read = request!(read exact 8);
56    let entry_count = read[0..4].as_u32_be();
57    if (HEADER_SIZE + entry_count as u64 * ENTRY_SIZE) > storage_len {
58        return Err(ParseError::EntryOverflow);
59    }
60
61    let path_region_size = read[4..8].as_u32_be();
62    if (HEADER_SIZE + entry_count as u64 * ENTRY_SIZE) + path_region_size as u64 > storage_len {
63        return Err(ParseError::PathOverflow);
64    }
65
66    let mut entries = Vec::with_capacity(entry_count as usize);
67    let mut path_to_entry_index_map = HashMap::new();
68
69    for _ in 0..entry_count {
70        let read = request!(read exact 20);
71        let path_hash = read[0..4].as_u32_be();
72
73        let path_offset_and_flags = read[4..8].as_u32_be();
74        let path_offset = path_offset_and_flags & 0x00FFFFFF;
75        let flag_bits = path_offset_and_flags & 0xFF000000;
76        let flags =
77            RawFlags::from_bits(flag_bits).ok_or(ParseError::UnrecognisedEntryFlags(flag_bits))?;
78
79        let data_offset = read[8..12].as_u32_be();
80        let data_size = read[12..16].as_u32_be();
81        let unpacked_size = read[16..20].as_u32_be();
82
83        if data_offset == 0 {
84            entries.push(None)
85        } else {
86            entries.push(Some(Entry {
87                path_hash,
88                relative_path_offset: path_offset,
89                path: String::new(),
90                data_offset,
91                data_size,
92                unpacked_size,
93                flags,
94            }))
95        }
96    }
97
98    let read = request!(read exact path_region_size.into());
99    for (i, maybe_entry) in entries.iter_mut().enumerate() {
100        if let Some(entry) = maybe_entry {
101            let path = read[entry.relative_path_offset as usize..]
102                .iter()
103                // TODO: Fail if null terminator is not present
104                .take_while(|b| **b != 0)
105                .map(|b| {
106                    if !b.is_ascii() {
107                        Err(ParseError::NonAsciiPath)
108                    } else {
109                        Ok(*b as char)
110                    }
111                })
112                .try_collect::<String>()?;
113
114            entry.path = path.clone();
115            path_to_entry_index_map
116                .try_insert(path, i)
117                .map_err(|e| ParseError::SamePath(e.entry.key().clone()))?;
118        }
119    }
120
121    Ok(PkgState {
122        path_region_size,
123        path_region_empty_offset: path_region_size
124            .checked_add_signed(-((read.iter().rev().take_while(|b| **b == 0).count() as i32) - 1))
125            .unwrap(),
126        entries,
127        path_to_entry_index_map,
128    })
129}