1pub mod generated;
2#[cfg(not(target_arch = "wasm32"))]
3pub mod pack;
4pub mod versioned;
5
6use std::ops::Range;
7
8use crate::posix::vfs::{VfsError, VfsResult};
9
10pub const AOSPKG_MAGIC: [u8; 4] = [0x89, b'A', b'O', b'S'];
11pub const AOSPKG_FORMAT_VERSION: u16 = 1;
12pub const AOSPKG_HEADER_LEN: usize = 16;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct AospkgHeader {
16 pub manifest_len: u32,
17 pub index_len: u32,
18 pub manifest: Range<usize>,
19 pub index: Range<usize>,
20 pub mount: Range<usize>,
21}
22
23pub fn parse_aospkg_header(bytes: &[u8]) -> VfsResult<AospkgHeader> {
31 parse_aospkg_header_from_prefix(bytes, bytes.len())
32}
33
34pub fn parse_aospkg_header_from_prefix(header: &[u8], file_len: usize) -> VfsResult<AospkgHeader> {
35 if header.len() < AOSPKG_HEADER_LEN {
36 return Err(VfsError::new(
37 "EINVAL",
38 format!(
39 ".aospkg header truncated: {} bytes < {AOSPKG_HEADER_LEN} bytes",
40 header.len()
41 ),
42 ));
43 }
44
45 if file_len < AOSPKG_HEADER_LEN {
46 return Err(VfsError::new(
47 "EINVAL",
48 format!(
49 ".aospkg file truncated: {file_len} bytes < {AOSPKG_HEADER_LEN} bytes"
50 ),
51 ));
52 }
53
54 if header[0..4] != AOSPKG_MAGIC {
55 return Err(VfsError::new("EINVAL", "invalid .aospkg magic"));
56 }
57 let format_version = u16::from_le_bytes([header[4], header[5]]);
58 if format_version != AOSPKG_FORMAT_VERSION {
59 return Err(VfsError::new(
60 "EINVAL",
61 format!("unsupported .aospkg format version: {format_version}"),
62 ));
63 }
64 let flags = u16::from_le_bytes([header[6], header[7]]);
65 if flags != 0 {
66 return Err(VfsError::new(
67 "EINVAL",
68 format!("unsupported .aospkg flags: {flags}"),
69 ));
70 }
71
72 let manifest_len = u32::from_le_bytes([header[8], header[9], header[10], header[11]]);
73 let index_len = u32::from_le_bytes([header[12], header[13], header[14], header[15]]);
74 let manifest_len_usize = usize::try_from(manifest_len).map_err(|_| {
75 VfsError::new(
76 "EOVERFLOW",
77 format!(".aospkg manifest length overflows usize: {manifest_len} bytes"),
78 )
79 })?;
80 let index_len_usize = usize::try_from(index_len).map_err(|_| {
81 VfsError::new(
82 "EOVERFLOW",
83 format!(".aospkg index length overflows usize: {index_len} bytes"),
84 )
85 })?;
86 let index_start = AOSPKG_HEADER_LEN
87 .checked_add(manifest_len_usize)
88 .ok_or_else(|| VfsError::new("EOVERFLOW", ".aospkg manifest range overflows usize"))?;
89 let mount_start = index_start
90 .checked_add(index_len_usize)
91 .ok_or_else(|| VfsError::new("EOVERFLOW", ".aospkg index range overflows usize"))?;
92 if mount_start > file_len {
93 return Err(VfsError::new(
94 "EINVAL",
95 format!(
96 ".aospkg chunks exceed file size: header {AOSPKG_HEADER_LEN} bytes + manifest {manifest_len} bytes + index {index_len} bytes > {file_len} bytes"
97 ),
98 ));
99 }
100
101 Ok(AospkgHeader {
102 manifest_len,
103 index_len,
104 manifest: AOSPKG_HEADER_LEN..index_start,
105 index: index_start..mount_start,
106 mount: mount_start..file_len,
107 })
108}
109
110pub fn encode_aospkg_header(manifest_len: usize, index_len: usize) -> VfsResult<[u8; 16]> {
111 let manifest_len = u32::try_from(manifest_len).map_err(|_| {
112 VfsError::new(
113 "EOVERFLOW",
114 format!(".aospkg manifest chunk too large: {manifest_len} bytes > u32::MAX bytes"),
115 )
116 })?;
117 let index_len = u32::try_from(index_len).map_err(|_| {
118 VfsError::new(
119 "EOVERFLOW",
120 format!(".aospkg index chunk too large: {index_len} bytes > u32::MAX bytes"),
121 )
122 })?;
123 let mut header = [0u8; AOSPKG_HEADER_LEN];
124 header[0..4].copy_from_slice(&AOSPKG_MAGIC);
125 header[4..6].copy_from_slice(&AOSPKG_FORMAT_VERSION.to_le_bytes());
126 header[6..8].copy_from_slice(&0u16.to_le_bytes());
127 header[8..12].copy_from_slice(&manifest_len.to_le_bytes());
128 header[12..16].copy_from_slice(&index_len.to_le_bytes());
129 Ok(header)
130}
131
132#[cfg(not(target_arch = "wasm32"))]
138pub fn read_manifest_chunk_from_file(
139 path: &std::path::Path,
140) -> VfsResult<generated::v1::PackageManifest> {
141 use std::io::{Read, Seek, SeekFrom};
142 let mut file = std::fs::File::open(path)
143 .map_err(|e| VfsError::new("EIO", format!("open {}: {e}", path.display())))?;
144 let file_len = file
145 .metadata()
146 .map_err(|e| VfsError::new("EIO", format!("stat {}: {e}", path.display())))?
147 .len();
148 let file_len = usize::try_from(file_len).map_err(|_| {
149 VfsError::new(
150 "EOVERFLOW",
151 format!("{} is too large to address on this platform", path.display()),
152 )
153 })?;
154 let mut header = [0u8; AOSPKG_HEADER_LEN];
155 file.read_exact(&mut header)
156 .map_err(|e| VfsError::new("EIO", format!("read {} header: {e}", path.display())))?;
157 let parsed = parse_aospkg_header_from_prefix(&header, file_len)?;
158 let mut manifest = vec![0u8; parsed.manifest.len()];
159 file.seek(SeekFrom::Start(parsed.manifest.start as u64))
160 .map_err(|e| VfsError::new("EIO", format!("seek {} manifest: {e}", path.display())))?;
161 file.read_exact(&mut manifest)
162 .map_err(|e| VfsError::new("EIO", format!("read {} manifest: {e}", path.display())))?;
163 versioned::decode_package_manifest(&manifest).map_err(|error| {
164 VfsError::new(
165 "EINVAL",
166 format!("decode package manifest in {}: {error}", path.display()),
167 )
168 })
169}
170
171pub fn validate_mount_range(
172 header: &AospkgHeader,
173 offset: u64,
174 size: u64,
175) -> VfsResult<Range<usize>> {
176 let mount_base = u64::try_from(header.mount.start).map_err(|_| {
177 VfsError::new(
178 "EOVERFLOW",
179 format!(".aospkg mount base overflows u64: {} bytes", header.mount.start),
180 )
181 })?;
182 let absolute_start = mount_base
183 .checked_add(offset)
184 .ok_or_else(|| VfsError::new("EOVERFLOW", ".aospkg member start overflows u64"))?;
185 let absolute_end = absolute_start
186 .checked_add(size)
187 .ok_or_else(|| VfsError::new("EOVERFLOW", ".aospkg member end overflows u64"))?;
188 let file_len = u64::try_from(header.mount.end).map_err(|_| {
189 VfsError::new(
190 "EOVERFLOW",
191 format!(".aospkg file length overflows u64: {} bytes", header.mount.end),
192 )
193 })?;
194 if absolute_end > file_len {
195 return Err(VfsError::new(
196 "EIO",
197 format!(
198 ".aospkg member range exceeds file size: mountBase {mount_base} bytes + offset {offset} bytes + size {size} bytes > {file_len} bytes"
199 ),
200 ));
201 }
202 let start = usize::try_from(absolute_start).map_err(|_| {
203 VfsError::new(
204 "EOVERFLOW",
205 format!(".aospkg member start overflows usize: {absolute_start} bytes"),
206 )
207 })?;
208 let end = usize::try_from(absolute_end).map_err(|_| {
209 VfsError::new(
210 "EOVERFLOW",
211 format!(".aospkg member end overflows usize: {absolute_end} bytes"),
212 )
213 })?;
214 Ok(start..end)
215}