Skip to main content

applesauce_core/
decmpfs.rs

1//! Helpers for working with decmpfs xattrs
2
3use crate::compressor;
4use std::ffi::CStr;
5use std::io::Write;
6use std::{fmt, io};
7
8/// The length of the decmpfs xattr header
9pub const HEADER_LEN: usize = 16;
10/// The maximum size of a decmpfs xattr
11pub const MAX_XATTR_SIZE: usize = 3802;
12/// The maximum size of the data in a decmpfs xattr (following the header)
13pub const MAX_XATTR_DATA_SIZE: usize = MAX_XATTR_SIZE - HEADER_LEN;
14/// The magic bytes that identify a decmpfs xattr
15pub const MAGIC: [u8; 4] = *b"fpmc";
16
17pub const ZLIB_BLOCK_TABLE_START: u64 = 0x104;
18
19/// The name of the decmpfs xattr
20pub const XATTR_NAME: &CStr = c"com.apple.decmpfs";
21
22/// The location of the compressed data
23///
24/// Compressed data can be stored either in the decmpfs xattr or in the resource fork.
25#[derive(Debug, Copy, Clone, PartialEq, Eq)]
26pub enum Storage {
27    /// The decmpfs header is followed by a single compressed block
28    Xattr,
29    /// The compressed data is stored separately, in the resource fork
30    ResourceFork,
31}
32
33impl fmt::Display for Storage {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        let s = match self {
36            Storage::Xattr => "decmpfs xattr",
37            Storage::ResourceFork => "resource fork",
38        };
39        f.write_str(s)
40    }
41}
42
43/// A combination of the compressor kind, and where the compressed data is stored
44#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45#[repr(transparent)]
46pub struct CompressionType(u32);
47
48impl fmt::Display for CompressionType {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        match self.compression_storage() {
51            Some((compressor, storage)) => {
52                write!(f, "{compressor} in {storage}")
53            }
54            None => write!(f, "unknown compression type: {}", self.0),
55        }
56    }
57}
58
59impl CompressionType {
60    #[must_use]
61    #[inline]
62    pub const fn new(compressor: compressor::Kind, storage: Storage) -> Self {
63        let val = match (compressor, storage) {
64            (compressor::Kind::Zlib, Storage::Xattr) => 3,
65            (compressor::Kind::Zlib, Storage::ResourceFork) => 4,
66            (compressor::Kind::Lzvn, Storage::Xattr) => 7,
67            (compressor::Kind::Lzvn, Storage::ResourceFork) => 8,
68            (compressor::Kind::Lzfse, Storage::Xattr) => 11,
69            (compressor::Kind::Lzfse, Storage::ResourceFork) => 12,
70        };
71        Self(val)
72    }
73
74    #[must_use]
75    #[inline]
76    pub const fn compression_storage(self) -> Option<(compressor::Kind, Storage)> {
77        match self.0 {
78            3 => Some((compressor::Kind::Zlib, Storage::Xattr)),
79            4 => Some((compressor::Kind::Zlib, Storage::ResourceFork)),
80            7 => Some((compressor::Kind::Lzvn, Storage::Xattr)),
81            8 => Some((compressor::Kind::Lzvn, Storage::ResourceFork)),
82            11 => Some((compressor::Kind::Lzfse, Storage::Xattr)),
83            12 => Some((compressor::Kind::Lzfse, Storage::ResourceFork)),
84            _ => None,
85        }
86    }
87
88    #[must_use]
89    #[inline]
90    pub const fn from_raw_type(n: u32) -> Self {
91        Self(n)
92    }
93
94    #[must_use]
95    #[inline]
96    pub const fn raw_type(self) -> u32 {
97        self.0
98    }
99}
100
101#[derive(Debug, Copy, Clone, PartialEq, Eq)]
102pub enum DecodeError {
103    TooSmall,
104    BadMagic,
105}
106
107impl fmt::Display for DecodeError {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        let s = match *self {
110            DecodeError::TooSmall => "decmpfs xattr too small to hold compression header",
111            DecodeError::BadMagic => "decmpfs xattr magic field has incorrect value",
112        };
113        f.write_str(s)
114    }
115}
116
117impl std::error::Error for DecodeError {}
118
119impl From<DecodeError> for io::Error {
120    fn from(err: DecodeError) -> Self {
121        match err {
122            DecodeError::TooSmall => io::Error::new(io::ErrorKind::UnexpectedEof, err),
123            DecodeError::BadMagic => io::Error::new(io::ErrorKind::InvalidData, err),
124        }
125    }
126}
127
128#[derive(Debug, Copy, Clone)]
129pub struct Value<'a> {
130    pub compression_type: CompressionType,
131    pub uncompressed_size: u64,
132    pub extra_data: &'a [u8],
133}
134
135#[allow(clippy::len_without_is_empty)]
136impl<'a> Value<'a> {
137    pub fn from_data(data: &'a [u8]) -> Result<Self, DecodeError> {
138        if data.len() < HEADER_LEN {
139            return Err(DecodeError::TooSmall);
140        }
141        let (header, extra_data) = data.split_at(HEADER_LEN);
142        let magic = &header[0..4];
143        let compression_type = u32::from_le_bytes(header[4..8].try_into().unwrap());
144        let uncompressed_size = u64::from_le_bytes(header[8..16].try_into().unwrap());
145        if magic != MAGIC {
146            return Err(DecodeError::BadMagic);
147        }
148        let compression_type = CompressionType::from_raw_type(compression_type);
149
150        Ok(Self {
151            compression_type,
152            uncompressed_size,
153            extra_data,
154        })
155    }
156
157    pub fn write_to<W: Write>(self, mut writer: W) -> io::Result<()> {
158        writer.write_all(&self.header_bytes())?;
159        writer.write_all(self.extra_data)?;
160
161        Ok(())
162    }
163
164    fn header_bytes(self) -> [u8; HEADER_LEN] {
165        let mut result = [0; HEADER_LEN];
166
167        let mut writer = &mut result[..];
168        writer.write_all(&MAGIC).unwrap();
169        writer
170            .write_all(&self.compression_type.0.to_le_bytes())
171            .unwrap();
172        writer
173            .write_all(&self.uncompressed_size.to_le_bytes())
174            .unwrap();
175        assert!(writer.is_empty());
176
177        result
178    }
179
180    pub fn len(self) -> usize {
181        HEADER_LEN + self.extra_data.len()
182    }
183}
184
185#[rustfmt::skip]
186pub const ZLIB_TRAILER: [u8; 50] = [
187    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
188    // magic 1 + 2
189    0x00, 0x1C, 0x00, 0x32,
190    // spacer1
191    0x00, 0x00,
192    // compression_magic
193    b'c', b'm', b'p', b'f',
194    // magic3
195    0x00, 0x00, 0x00, 0x0A,
196    // magic4
197    0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
198    // spacer2
199    0x00, 0x00, 0x00, 0x00,
200];
201
202#[derive(Debug, Copy, Clone, PartialEq, Eq)]
203pub struct BlockInfo {
204    pub offset: u32,
205    pub compressed_size: u32,
206}
207
208impl BlockInfo {
209    pub const SIZE: usize = 8;
210
211    #[inline]
212    pub fn from_bytes(data: [u8; Self::SIZE]) -> Self {
213        let offset = u32::from_le_bytes(data[..4].try_into().unwrap());
214        let compressed_size = u32::from_le_bytes(data[4..].try_into().unwrap());
215        Self {
216            offset,
217            compressed_size,
218        }
219    }
220
221    #[inline]
222    pub fn to_bytes(self) -> [u8; Self::SIZE] {
223        let mut result = [0; Self::SIZE];
224
225        let Self {
226            offset,
227            compressed_size,
228        } = self;
229
230        let mut writer = &mut result[..];
231        writer.write_all(&offset.to_le_bytes()).unwrap();
232        writer.write_all(&compressed_size.to_le_bytes()).unwrap();
233        assert!(writer.is_empty());
234
235        result
236    }
237}