1use thiserror::Error;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum CompressionMode {
11 Binary = 0,
13 ASCII = 1,
15}
16
17impl CompressionMode {
18 pub fn from_u8(value: u8) -> Result<Self> {
20 match value {
21 0 => Ok(CompressionMode::Binary),
22 1 => Ok(CompressionMode::ASCII),
23 _ => Err(PkLibError::InvalidCompressionMode(value)),
24 }
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum DictionarySize {
31 Size1K = 1024,
33 Size2K = 2048,
35 Size4K = 4096,
37}
38
39impl DictionarySize {
40 pub fn bits(&self) -> u8 {
42 match self {
43 DictionarySize::Size1K => 4, DictionarySize::Size2K => 5, DictionarySize::Size4K => 6, }
47 }
48
49 pub fn mask(&self) -> u32 {
51 (*self as u32) - 1
52 }
53
54 pub fn from_bits(bits: u8) -> Result<Self> {
56 match bits {
57 4 => Ok(DictionarySize::Size1K),
58 5 => Ok(DictionarySize::Size2K),
59 6 => Ok(DictionarySize::Size4K),
60 _ => Err(PkLibError::InvalidDictionaryBits(bits)),
61 }
62 }
63
64 pub fn from_bytes(bytes: u32) -> Result<Self> {
66 match bytes {
67 1024 => Ok(DictionarySize::Size1K),
68 2048 => Ok(DictionarySize::Size2K),
69 4096 => Ok(DictionarySize::Size4K),
70 _ => Err(PkLibError::InvalidDictionarySize(bytes)),
71 }
72 }
73}
74
75#[derive(Debug, Error)]
77pub enum PkLibError {
78 #[error("Invalid compression mode: {0}")]
80 InvalidCompressionMode(u8),
81
82 #[error("Invalid dictionary bits: {0} (expected 4, 5, or 6)")]
84 InvalidDictionaryBits(u8),
85
86 #[error("Invalid dictionary size: {0} (expected 1024, 2048, or 4096)")]
88 InvalidDictionarySize(u32),
89
90 #[error("Invalid compressed data format")]
92 InvalidFormat,
93
94 #[error("Unexpected end of input")]
96 UnexpectedEof,
97
98 #[error("Output buffer too small")]
100 BufferTooSmall,
101
102 #[error("Invalid length encoding: {0}")]
104 InvalidLength(u32),
105
106 #[error("Invalid distance encoding: {0}")]
108 InvalidDistance(u32),
109
110 #[error("Invalid data: {0}")]
112 InvalidData(String),
113
114 #[error("Decompression error: {0}")]
116 DecompressionError(String),
117
118 #[error("CRC32 checksum mismatch: expected {expected:08X}, got {actual:08X}")]
120 CrcMismatch {
121 expected: u32,
123 actual: u32,
125 },
126
127 #[error("I/O error: {0}")]
129 Io(#[from] std::io::Error),
130}
131
132pub type Result<T> = std::result::Result<T, PkLibError>;
134
135pub const MAX_REP_LENGTH: u32 = 0x204; pub const MAX_WINDOW_SIZE: usize = 0x3000; pub const WORK_BUFF_SIZE: usize = 0x2204;
145
146pub const OUT_BUFF_SIZE: usize = 0x802;
148
149pub const IN_BUFF_SIZE: usize = 0x800;
151
152pub const HASH_TABLE_SIZE: usize = 0x900;
154
155pub const MIN_MATCH_LENGTH: usize = 3;
157
158pub const PKLIB_SIGNATURE: u32 = 0x00088B1F;
160
161#[derive(Debug, Clone, Copy)]
163pub struct CompressionHeader {
164 pub mode: CompressionMode,
166 pub dict_size: DictionarySize,
168 pub uncompressed_size: Option<u32>,
170 pub crc32: Option<u32>,
172}
173
174#[derive(Debug, Default, Clone)]
176pub struct CompressionStats {
177 pub literal_count: usize,
179 pub match_count: usize,
181 pub bytes_processed: usize,
183 pub longest_match: usize,
185 pub input_bytes: u64,
187 pub output_bytes: u64,
189 pub compression_ratio: f64,
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_compression_mode() {
199 assert_eq!(
200 CompressionMode::from_u8(0).unwrap(),
201 CompressionMode::Binary
202 );
203 assert_eq!(CompressionMode::from_u8(1).unwrap(), CompressionMode::ASCII);
204 assert!(CompressionMode::from_u8(2).is_err());
205 }
206
207 #[test]
208 fn test_dictionary_size() {
209 assert_eq!(DictionarySize::Size1K.bits(), 4);
211 assert_eq!(DictionarySize::Size2K.bits(), 5);
212 assert_eq!(DictionarySize::Size4K.bits(), 6);
213
214 assert_eq!(DictionarySize::Size1K.mask(), 0x3FF);
216 assert_eq!(DictionarySize::Size2K.mask(), 0x7FF);
217 assert_eq!(DictionarySize::Size4K.mask(), 0xFFF);
218
219 assert_eq!(
221 DictionarySize::from_bits(4).unwrap(),
222 DictionarySize::Size1K
223 );
224 assert_eq!(
225 DictionarySize::from_bits(5).unwrap(),
226 DictionarySize::Size2K
227 );
228 assert_eq!(
229 DictionarySize::from_bits(6).unwrap(),
230 DictionarySize::Size4K
231 );
232 assert!(DictionarySize::from_bits(7).is_err());
233
234 assert_eq!(
236 DictionarySize::from_bytes(1024).unwrap(),
237 DictionarySize::Size1K
238 );
239 assert_eq!(
240 DictionarySize::from_bytes(2048).unwrap(),
241 DictionarySize::Size2K
242 );
243 assert_eq!(
244 DictionarySize::from_bytes(4096).unwrap(),
245 DictionarySize::Size4K
246 );
247 assert!(DictionarySize::from_bytes(512).is_err());
248 }
249
250 #[test]
251 fn test_constants() {
252 assert_eq!(MAX_REP_LENGTH, 516);
253 assert_eq!(MAX_WINDOW_SIZE, 0x3000);
254 assert_eq!(WORK_BUFF_SIZE, 0x2204);
255 assert_eq!(OUT_BUFF_SIZE, 0x802);
256 assert_eq!(IN_BUFF_SIZE, 0x800);
257 assert_eq!(HASH_TABLE_SIZE, 0x900);
258 }
259}