1use crate::compressor;
4use std::ffi::CStr;
5use std::io::Write;
6use std::{fmt, io};
7
8pub const HEADER_LEN: usize = 16;
10pub const MAX_XATTR_SIZE: usize = 3802;
12pub const MAX_XATTR_DATA_SIZE: usize = MAX_XATTR_SIZE - HEADER_LEN;
14pub const MAGIC: [u8; 4] = *b"fpmc";
16
17pub const ZLIB_BLOCK_TABLE_START: u64 = 0x104;
18
19pub const XATTR_NAME: &CStr = c"com.apple.decmpfs";
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq)]
26pub enum Storage {
27 Xattr,
29 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#[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 0x00, 0x1C, 0x00, 0x32,
190 0x00, 0x00,
192 b'c', b'm', b'p', b'f',
194 0x00, 0x00, 0x00, 0x0A,
196 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
198 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}