zlib_header/lib.rs
1//! Library to work with the 2 Byte zlib header, as defined in
2//! [RFC 1950](https://datatracker.ietf.org/doc/html/rfc1950).
3//! # Examples
4//! ```
5//! use zlib_header::ZlibHeader;
6//! let cm = 8;
7//! let cinfo = 7;
8//! let fdict = false;
9//! let flevel = 2;
10//! let header = ZlibHeader::new(cm, cinfo, fdict, flevel);
11//! match header {
12//! Ok(header) => {
13//! println!("header is valid (strict): {}", header.is_valid_strict()); // header is valid (strict): true
14//! },
15//! Err(err) => eprintln!("Unable to initialize zlib header: {:?}", err)
16//! }
17//! ```
18//! ```
19//! use zlib_header::ZlibHeader;
20//! let header = ZlibHeader::default();
21//! println!("Display: {}", header); // Display: 789C
22//! println!("Debug: {:?}", header); // Debug: ZlibHeader { DEFLATE | 32768 Bytes | default | Dictionary: false | valid }
23//! let bytes = [0x78, 0x9C];
24//! println!("header matches expected bytes: {}", header == bytes); // header matches expected bytes: true
25//! ```
26use hex::FromHexError;
27use std::fmt::{Debug, Display, Formatter};
28
29/// The error type when a value does not fit inside the possible range of a certain number of bits.
30#[derive(Debug)]
31pub enum OutOfRangeError {
32 CompressionMethod(String),
33 CompressionInfo(String),
34 CompressionLevel(String),
35}
36
37/// 2 Byte header at the start of a zlib stream.
38#[repr(C)]
39pub struct ZlibHeader {
40 /// Compression Method and Flags
41 /// 0000_1111 => compression method (`cm`)
42 /// 1111_0000 => compression info (`cinfo`)
43 pub cmf: u8,
44 /// Flags
45 /// 0001_1111 => checksum adjustment (`fcheck`)
46 /// 0010_0000 => preset dictionary used (`fdict`)
47 /// 1100_0000 => compression level (`flevel`)
48 pub flg: u8,
49}
50
51impl ZlibHeader {
52 /// Initializes with the given parameters and calls [`Self::set_fcheck`].
53 pub fn new(cm: u8, cinfo: u8, fdict: bool, flevel: u8) -> Result<Self, OutOfRangeError> {
54 let mut header = Self { cmf: 0, flg: 0 };
55 header.set_cm(cm)?;
56 header.set_cinfo(cinfo)?;
57 header.set_fdict(fdict);
58 header.set_flevel(flevel)?;
59 header.set_fcheck::<false>();
60 Ok(header)
61 }
62
63 /// Parses `input` as 2 Byte hex string to initialize.
64 /// # Errors
65 /// [`FromHexError::InvalidStringLength`] if `input` is not exactly 4 characters long.
66 /// Other [`FromHexError`] variants if [`hex::decode`] fails by other means.
67 pub fn from_hex(input: &str) -> Result<Self, FromHexError> {
68 if input.len() != 4 {
69 return Err(FromHexError::InvalidStringLength);
70 }
71 let bytes = hex::decode(input)?;
72 let header = Self {
73 cmf: bytes[0],
74 flg: bytes[1],
75 };
76 Ok(header)
77 }
78
79 /// Gets the lower 4 bits of `self.cmf`, representing the compression method.
80 pub fn get_cm(&self) -> u8 {
81 self.cmf & 0b0000_1111
82 }
83
84 /// Sets the lower 4 bits of `self.cmf` to `cm`, representing the compression method.
85 /// # Errors
86 /// Returns [`OutOfRangeError::CompressionInfo`] if `cm > 15`
87 pub fn set_cm(&mut self, cm: u8) -> Result<(), OutOfRangeError> {
88 if cm > 15 {
89 let msg = format!("cm was {}, but must be between 0 and 15", cm);
90 return Err(OutOfRangeError::CompressionInfo(msg));
91 }
92 self.cmf = (self.cmf & 0b1111_0000) | cm;
93 Ok(())
94 }
95
96 /// Gets the string representation of `cm`.
97 /// `DEFLATE` if it is 8, `UNDEFINED` in all other cases.
98 pub fn get_cm_str(&self) -> &str {
99 match self.get_cm() {
100 8 => "DEFLATE",
101 _ => "UNDEFINED",
102 }
103 }
104
105 /// Gets the upper 4 bits of `self.cmf`, representing the compression info.
106 /// It is used to determine the sliding window size for de-/compression.
107 /// Read more on [`Self::get_window_size`]
108 pub fn get_cinfo(&self) -> u8 {
109 self.cmf >> 4
110 }
111
112 /// Sets the upper 4 bits of `self.cmf` to `cinfo`, representing the compression info.
113 /// # Errors
114 /// Returns [`OutOfRangeError::CompressionInfo`] if `cinfo > 15`
115 pub fn set_cinfo(&mut self, cinfo: u8) -> Result<(), OutOfRangeError> {
116 if cinfo > 15 {
117 let msg = format!("cinfo was {}, but must be between 0 and 15", cinfo);
118 return Err(OutOfRangeError::CompressionInfo(msg));
119 }
120 self.cmf = (self.cmf & 0b0000_1111) | cinfo << 4;
121 Ok(())
122 }
123
124 /// Gets the size of the sliding window in Bytes.
125 /// Valid window sizes range from 256 to 32768 - means `cinfo` ranges from 0 to 7.
126 /// The formula is: `2.pow(cinfo + 8)`
127 pub fn get_window_size(&self) -> u32 {
128 2u32.pow(self.get_cinfo() as u32 + 8)
129 }
130
131 /// Gets the lowest 5 bits of `self.flg`, representing the checksum adjustment.
132 /// The value is chosen to satisfy the checksum formula over the entire `ZlibHeader`.
133 /// Read more on [`Self::is_valid`]
134 pub fn get_fcheck(&self) -> u8 {
135 self.flg & 0b0001_1111
136 }
137
138 /// Sets the lowest 5 bits of `self.flg`, representing the checksum adjustment.
139 /// The value is chosen to satisfy the checksum formula over the entire `ZlibHeader`.
140 /// The generic constant `CLEAN` dictates if the function has to zero the current `fcheck` bits.
141 /// Read more on [`Self::is_valid`]
142 pub fn set_fcheck<const CLEAN: bool>(&mut self) {
143 let clean_flg = match CLEAN {
144 false => self.flg,
145 true => self.flg & 0b1110_0000,
146 };
147 let clean_header: u16 = ((self.cmf as u16) << 8) + clean_flg as u16;
148 let fcheck = 31 - clean_header % 31;
149 self.flg = clean_flg | fcheck as u8;
150 }
151
152 /// Returns `true` if the checksum formula over the `ZlibHeader` is satisfied.
153 /// The formula is: `(self.cmf * 256 + self.flg) % 31 == 0`
154 pub fn is_valid(&self) -> bool {
155 (self.cmf as usize * 256 + self.flg as usize) % 31 == 0
156 }
157
158 /// In addition to [`Self::is_valid`] it also checks `cm == 8` and `cinfo <= 7`.
159 pub fn is_valid_strict(&self) -> bool {
160 let is_valid = self.is_valid();
161 let is_deflate = self.get_cm() == 8;
162 let cinfo = self.get_cinfo();
163 let is_valid_cinfo = cinfo <= 7;
164 is_valid && is_deflate && is_valid_cinfo
165 }
166
167 /// Gets the bit at index 5 of `self.flg` as `bool`, signaling the usage of a preset dictionary.
168 pub fn get_fdict(&self) -> bool {
169 (self.flg >> 5) & 1 == 1
170 }
171
172 /// Sets the bit at index 5 of `self.flg`, signaling the usage of a preset dictionary.
173 pub fn set_fdict(&mut self, fdict: bool) {
174 let mask: u8 = 0b1101_1111;
175 self.flg = (self.flg & mask) | if fdict { !mask } else { 0 };
176 }
177
178 /// Returns the upper 2 bits of `self.flg`, which represents the compression level.
179 pub fn get_flevel(&self) -> u8 {
180 self.flg >> 6
181 }
182
183 /// Sets the upper 2 bits of `self.flg` to `flevel`, which represents the compression level.
184 /// # Errors
185 /// Returns [`OutOfRangeError::CompressionLevel`] if `flevel > 3`
186 pub fn set_flevel(&mut self, flevel: u8) -> Result<(), OutOfRangeError> {
187 if flevel > 3 {
188 let msg = format!("flevel was {}, but must be between 0 and 3", flevel);
189 return Err(OutOfRangeError::CompressionInfo(msg));
190 }
191 self.flg = (self.flg & 0b0011_1111) | (flevel << 6);
192 Ok(())
193 }
194
195 /// Gets the string representation of `flevel`.
196 /// The values are: `fastest`, `fast`, `default`, `best`
197 pub fn get_flevel_str(&self) -> &str {
198 match self.get_flevel() {
199 0 => "fastest",
200 1 => "fast",
201 2 => "default",
202 3 => "best",
203 _ => unreachable!("only has 2 bits"),
204 }
205 }
206}
207
208impl PartialEq<[u8; 2]> for ZlibHeader {
209 fn eq(&self, slice: &[u8; 2]) -> bool {
210 self.cmf == slice[0] && self.flg == slice[1]
211 }
212}
213
214impl PartialEq<ZlibHeader> for [u8; 2] {
215 fn eq(&self, header: &ZlibHeader) -> bool {
216 self[0] == header.cmf && self[1] == header.flg
217 }
218}
219
220impl PartialEq<&[u8]> for ZlibHeader {
221 fn eq(&self, slice: &&[u8]) -> bool {
222 if slice.len() != 2 {
223 panic!("u8 slice must be exactly 2 Bytes when comparing ZlibHeader");
224 }
225 self.cmf == slice[0] && self.flg == slice[1]
226 }
227}
228
229impl PartialEq<ZlibHeader> for &[u8] {
230 fn eq(&self, header: &ZlibHeader) -> bool {
231 if self.len() != 2 {
232 panic!("u8 slice must be exactly 2 Bytes when comparing ZlibHeader");
233 }
234 self[0] == header.cmf && self[1] == header.flg
235 }
236}
237
238impl From<[u8; 2]> for ZlibHeader {
239 fn from(bytes: [u8; 2]) -> Self {
240 Self {
241 cmf: bytes[0],
242 flg: bytes[1],
243 }
244 }
245}
246
247impl From<ZlibHeader> for [u8; 2] {
248 fn from(header: ZlibHeader) -> Self {
249 [header.cmf, header.flg]
250 }
251}
252
253impl Default for ZlibHeader {
254 /// `789C` - this is DEFLATE with default compression level and a 32 KiB window.
255 fn default() -> Self {
256 Self {
257 cmf: 0x78,
258 flg: 0x9C,
259 }
260 }
261}
262
263impl Display for ZlibHeader {
264 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
265 f.write_str(&format!("{:02X}{:02X}", self.cmf, self.flg))
266 }
267}
268
269impl Debug for ZlibHeader {
270 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
271 let cm = self.get_cm_str();
272 let window_size = self.get_window_size();
273 let flevel = self.get_flevel_str();
274 let fdict = self.get_fdict();
275 let validity = if self.is_valid() { "valid" } else { "invalid" };
276 let str = &format!(
277 "ZlibHeader {{ {} | {} Bytes | {} | Dictionary: {} | {} }}",
278 cm, window_size, flevel, fdict, validity
279 );
280 f.write_str(str)
281 }
282}