pklib/
common.rs

1//! Common types and constants for PKWare Data Compression Library
2//!
3//! This module defines the core types, constants, and structures used by both
4//! the compression (implode) and decompression (explode) algorithms.
5
6use thiserror::Error;
7
8/// Compression mode for the PKWare DCL format
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum CompressionMode {
11    /// Binary mode - optimized for binary data
12    Binary = 0,
13    /// ASCII mode - optimized for text data
14    ASCII = 1,
15}
16
17impl CompressionMode {
18    /// Create a CompressionMode from a raw value
19    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/// Dictionary size for compression/decompression
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum DictionarySize {
31    /// 1024 bytes (1KB) dictionary
32    Size1K = 1024,
33    /// 2048 bytes (2KB) dictionary
34    Size2K = 2048,
35    /// 4096 bytes (4KB) dictionary
36    Size4K = 4096,
37}
38
39impl DictionarySize {
40    /// Get the number of bits needed to represent this dictionary size
41    pub fn bits(&self) -> u8 {
42        match self {
43            DictionarySize::Size1K => 4, // 2^10 = 1024, needs 10 bits, 10-6=4
44            DictionarySize::Size2K => 5, // 2^11 = 2048, needs 11 bits, 11-6=5
45            DictionarySize::Size4K => 6, // 2^12 = 4096, needs 12 bits, 12-6=6
46        }
47    }
48
49    /// Get the bit mask for this dictionary size
50    pub fn mask(&self) -> u32 {
51        (*self as u32) - 1
52    }
53
54    /// Create a DictionarySize from the number of bits
55    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    /// Create a DictionarySize from byte size
65    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/// Error type for PKLib operations
76#[derive(Debug, Error)]
77pub enum PkLibError {
78    /// Invalid compression mode value
79    #[error("Invalid compression mode: {0}")]
80    InvalidCompressionMode(u8),
81
82    /// Invalid dictionary size bits
83    #[error("Invalid dictionary bits: {0} (expected 4, 5, or 6)")]
84    InvalidDictionaryBits(u8),
85
86    /// Invalid dictionary size
87    #[error("Invalid dictionary size: {0} (expected 1024, 2048, or 4096)")]
88    InvalidDictionarySize(u32),
89
90    /// Invalid compressed data format
91    #[error("Invalid compressed data format")]
92    InvalidFormat,
93
94    /// Unexpected end of input
95    #[error("Unexpected end of input")]
96    UnexpectedEof,
97
98    /// Output buffer too small
99    #[error("Output buffer too small")]
100    BufferTooSmall,
101
102    /// Invalid length encoding
103    #[error("Invalid length encoding: {0}")]
104    InvalidLength(u32),
105
106    /// Invalid distance encoding
107    #[error("Invalid distance encoding: {0}")]
108    InvalidDistance(u32),
109
110    /// Invalid data format or corruption
111    #[error("Invalid data: {0}")]
112    InvalidData(String),
113
114    /// Decompression error
115    #[error("Decompression error: {0}")]
116    DecompressionError(String),
117
118    /// CRC32 checksum mismatch
119    #[error("CRC32 checksum mismatch: expected {expected:08X}, got {actual:08X}")]
120    CrcMismatch {
121        /// Expected CRC32 value
122        expected: u32,
123        /// Actual CRC32 value
124        actual: u32,
125    },
126
127    /// I/O error
128    #[error("I/O error: {0}")]
129    Io(#[from] std::io::Error),
130}
131
132/// Result type alias for PKLib operations
133pub type Result<T> = std::result::Result<T, PkLibError>;
134
135// PKLib-specific constants
136
137/// Maximum repetition length (distance match length)
138pub const MAX_REP_LENGTH: u32 = 0x204; // 516 bytes
139
140/// Maximum window size for decompression
141pub const MAX_WINDOW_SIZE: usize = 0x3000; // 12KB
142
143/// Size of the compression work buffer
144pub const WORK_BUFF_SIZE: usize = 0x2204;
145
146/// Size of the compression output buffer
147pub const OUT_BUFF_SIZE: usize = 0x802;
148
149/// Size of the decompression input buffer
150pub const IN_BUFF_SIZE: usize = 0x800;
151
152/// Hash table size for compression
153pub const HASH_TABLE_SIZE: usize = 0x900;
154
155/// Minimum match length for compression
156pub const MIN_MATCH_LENGTH: usize = 3;
157
158/// PKLib file signature (if used)
159pub const PKLIB_SIGNATURE: u32 = 0x00088B1F;
160
161/// Compression header structure
162#[derive(Debug, Clone, Copy)]
163pub struct CompressionHeader {
164    /// Compression mode (Binary/ASCII)
165    pub mode: CompressionMode,
166    /// Dictionary size
167    pub dict_size: DictionarySize,
168    /// Original uncompressed size (optional)
169    pub uncompressed_size: Option<u32>,
170    /// CRC32 of uncompressed data (optional)
171    pub crc32: Option<u32>,
172}
173
174/// Statistics for compression/decompression operations
175#[derive(Debug, Default, Clone)]
176pub struct CompressionStats {
177    /// Number of literal bytes encoded/decoded
178    pub literal_count: usize,
179    /// Number of distance matches encoded/decoded
180    pub match_count: usize,
181    /// Total bytes processed
182    pub bytes_processed: usize,
183    /// Longest match found
184    pub longest_match: usize,
185    /// Input bytes (for async operations)
186    pub input_bytes: u64,
187    /// Output bytes (for async operations)
188    pub output_bytes: u64,
189    /// Compression ratio (for async operations)
190    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        // Test bits
210        assert_eq!(DictionarySize::Size1K.bits(), 4);
211        assert_eq!(DictionarySize::Size2K.bits(), 5);
212        assert_eq!(DictionarySize::Size4K.bits(), 6);
213
214        // Test masks
215        assert_eq!(DictionarySize::Size1K.mask(), 0x3FF);
216        assert_eq!(DictionarySize::Size2K.mask(), 0x7FF);
217        assert_eq!(DictionarySize::Size4K.mask(), 0xFFF);
218
219        // Test from_bits
220        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        // Test from_bytes
235        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}