pub const MAGIC: u32 = 0x636d_7066;
pub const HEADER_LEN: usize = 16;
pub const COMPRESSION_TYPE_OFFSET: usize = 4;
pub const UNCOMPRESSED_SIZE_OFFSET: usize = 8;
pub const CHUNK_SIZE: usize = 65536;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Storage {
Inline,
ResourceFork,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Algorithm {
Uncompressed,
Zlib,
Lzvn,
Lzfse,
LzBitmap,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Compression {
pub algorithm: Algorithm,
pub storage: Storage,
}
#[must_use]
pub fn classify(compression_type: u32) -> Option<Compression> {
use Algorithm::{LzBitmap, Lzfse, Lzvn, Uncompressed, Zlib};
use Storage::{Inline, ResourceFork};
let (algorithm, storage) = match compression_type {
1 | 9 => (Uncompressed, Inline),
10 => (Uncompressed, ResourceFork),
3 => (Zlib, Inline),
4 => (Zlib, ResourceFork),
7 => (Lzvn, Inline),
8 => (Lzvn, ResourceFork),
11 => (Lzfse, Inline),
12 => (Lzfse, ResourceFork),
13 => (LzBitmap, Inline),
14 => (LzBitmap, ResourceFork),
_ => return None,
};
Some(Compression { algorithm, storage })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn magic_is_cmpf_little_endian() {
assert_eq!(MAGIC, 0x636d_7066);
assert_eq!(MAGIC.to_le_bytes(), *b"fpmc");
}
#[test]
fn header_layout_constants() {
assert_eq!(HEADER_LEN, 16);
assert_eq!(COMPRESSION_TYPE_OFFSET, 4);
assert_eq!(UNCOMPRESSED_SIZE_OFFSET, 8);
assert_eq!(CHUNK_SIZE, 65536);
}
#[test]
fn zlib_types_3_inline_4_resource_fork() {
assert_eq!(
classify(3),
Some(Compression {
algorithm: Algorithm::Zlib,
storage: Storage::Inline
})
);
assert_eq!(
classify(4),
Some(Compression {
algorithm: Algorithm::Zlib,
storage: Storage::ResourceFork
})
);
}
#[test]
fn lzvn_types_7_inline_8_resource_fork() {
assert_eq!(
classify(7),
Some(Compression {
algorithm: Algorithm::Lzvn,
storage: Storage::Inline
})
);
assert_eq!(
classify(8),
Some(Compression {
algorithm: Algorithm::Lzvn,
storage: Storage::ResourceFork
})
);
}
#[test]
fn lzfse_types_11_inline_12_resource_fork() {
assert_eq!(
classify(11),
Some(Compression {
algorithm: Algorithm::Lzfse,
storage: Storage::Inline
})
);
assert_eq!(
classify(12),
Some(Compression {
algorithm: Algorithm::Lzfse,
storage: Storage::ResourceFork
})
);
}
#[test]
fn uncompressed_types_1_9_inline_10_resource_fork() {
for inline in [1, 9] {
assert_eq!(
classify(inline),
Some(Compression {
algorithm: Algorithm::Uncompressed,
storage: Storage::Inline
})
);
}
assert_eq!(
classify(10),
Some(Compression {
algorithm: Algorithm::Uncompressed,
storage: Storage::ResourceFork
})
);
}
#[test]
fn lzbitmap_types_13_inline_14_resource_fork() {
assert_eq!(
classify(13),
Some(Compression {
algorithm: Algorithm::LzBitmap,
storage: Storage::Inline
})
);
assert_eq!(
classify(14),
Some(Compression {
algorithm: Algorithm::LzBitmap,
storage: Storage::ResourceFork
})
);
}
#[test]
fn dedup_type_5_and_unknown_types_are_none() {
assert_eq!(classify(5), None); assert_eq!(classify(0), None);
assert_eq!(classify(2), None);
assert_eq!(classify(99), None);
}
#[test]
fn parity_rule_odd_inline_even_resource_fork() {
for t in [3, 4, 7, 8, 11, 12, 13, 14] {
let c = classify(t).expect("documented type");
let expected = if t % 2 == 1 {
Storage::Inline
} else {
Storage::ResourceFork
};
assert_eq!(c.storage, expected, "type {t}");
}
}
}