1#![deny(missing_docs)]
2use std::collections::{BTreeMap, BTreeSet};
5use std::ops::Range;
6
7use macho_core::MaterializationLimits;
8use serde::Serialize;
9
10pub use macho_core::format;
11
12pub mod error;
14pub(crate) use error::Error;
15pub use error::{DyldCacheError, DyldCacheErrorKind, Result};
16
17mod completeness;
18mod family;
19mod materialize;
20mod parse;
21mod rewrite;
22
23pub use family::*;
24pub use parse::*;
25
26fn read_u32_le(data: &[u8], offset: usize) -> Result<u32> {
27 let end = offset
28 .checked_add(4)
29 .ok_or_else(|| Error::bounds(offset as u64, 4, data.len() as u64))?;
30 if end > data.len() {
31 return Err(Error::bounds(offset as u64, 4, data.len() as u64));
32 }
33 let bytes: [u8; 4] = data[offset..end]
34 .try_into()
35 .expect("slice length guaranteed by bounds check");
36 Ok(u32::from_le_bytes(bytes))
37}
38
39fn read_u64_le(data: &[u8], offset: usize) -> Result<u64> {
40 let end = offset
41 .checked_add(8)
42 .ok_or_else(|| Error::bounds(offset as u64, 8, data.len() as u64))?;
43 if end > data.len() {
44 return Err(Error::bounds(offset as u64, 8, data.len() as u64));
45 }
46 let bytes: [u8; 8] = data[offset..end]
47 .try_into()
48 .expect("slice length guaranteed by bounds check");
49 Ok(u64::from_le_bytes(bytes))
50}
51
52fn read_uuid(data: &[u8], offset: usize) -> Result<[u8; 16]> {
53 data.get(offset..offset.saturating_add(16))
54 .and_then(|bytes| bytes.try_into().ok())
55 .ok_or_else(|| Error::bounds(offset as u64, 16, data.len() as u64))
56}
57
58fn read_fixed_c_string(data: &[u8], offset: usize, size: usize, subject: &str) -> Result<String> {
59 let bytes = data
60 .get(offset..offset.saturating_add(size))
61 .ok_or_else(|| Error::bounds(offset as u64, size as u64, data.len() as u64))?;
62 let end = bytes
63 .iter()
64 .position(|byte| *byte == 0)
65 .unwrap_or(bytes.len());
66 let value = std::str::from_utf8(&bytes[..end])
67 .map_err(|_| Error::format(format!("{subject} is not UTF-8")))?;
68 Ok(value.to_owned())
69}
70
71fn format_uuid(uuid: [u8; 16]) -> String {
72 format!(
73 "{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
74 uuid[0],
75 uuid[1],
76 uuid[2],
77 uuid[3],
78 uuid[4],
79 uuid[5],
80 uuid[6],
81 uuid[7],
82 uuid[8],
83 uuid[9],
84 uuid[10],
85 uuid[11],
86 uuid[12],
87 uuid[13],
88 uuid[14],
89 uuid[15]
90 )
91}
92
93fn read_c_string(data: &[u8], offset: usize, subject: &str) -> Result<String> {
94 const MAX_CACHE_PATH_BYTES: usize = 4096;
95 let remaining = data
96 .get(offset..)
97 .ok_or_else(|| Error::bounds(offset as u64, 1, data.len() as u64))?;
98 let bounded = &remaining[..remaining.len().min(MAX_CACHE_PATH_BYTES)];
99 let length = bounded.iter().position(|byte| *byte == 0).ok_or_else(|| {
100 Error::format(format!(
101 "{subject} at {offset:#x} is not NUL-terminated within {} bytes",
102 bounded.len()
103 ))
104 })?;
105 let path = std::str::from_utf8(&bounded[..length])
106 .map_err(|_| Error::format(format!("{subject} at {offset:#x} is not UTF-8")))?;
107 if path.is_empty() || !path.starts_with('/') {
108 return Err(Error::format(format!(
109 "{subject} at {offset:#x} is not a nonempty absolute install path"
110 )));
111 }
112 Ok(path.to_owned())
113}
114
115#[cfg(test)]
116mod tests;