memberlist_proto/compression/
brotli_impl.rs

1use core::str::FromStr;
2
3num_to_enum! {
4  /// The brotli quality
5  #[derive(Default)]
6  BrotliQuality(u8 in [0, 11]):"quality":"Q" {
7    0, 1, 2, #[default] 3, 4, 5, 6, 7, 8, 9, 10, 11,
8  }
9}
10
11num_to_enum! {
12  /// The brotli window
13  #[derive(Default)]
14  BrotliWindow(u8 in [10, 24]):"window":"W" {
15    10, 11, 12, 13, 14, 15, #[default] 16, 17, 18, 19, 20, 21, 22, 23, 24,
16  }
17}
18
19/// The brotli algorithm
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
21#[display("({quality}, {window})")]
22pub struct BrotliAlgorithm {
23  quality: BrotliQuality,
24  window: BrotliWindow,
25}
26
27impl Default for BrotliAlgorithm {
28  fn default() -> Self {
29    Self::new()
30  }
31}
32
33impl BrotliAlgorithm {
34  /// Creates a new `BrotliAlgorithm` with the default quality and window.
35  #[inline]
36  pub const fn new() -> Self {
37    Self {
38      quality: BrotliQuality::Q3,
39      window: BrotliWindow::W16,
40    }
41  }
42
43  /// Creates a new `BrotliAlgorithm` with the quality and window.
44  #[inline]
45  pub const fn with_quality_and_window(quality: BrotliQuality, window: BrotliWindow) -> Self {
46    Self { quality, window }
47  }
48
49  /// Returns the quality of the brotli algorithm.
50  #[inline]
51  pub const fn quality(&self) -> BrotliQuality {
52    self.quality
53  }
54
55  /// Returns the window of the brotli algorithm.
56  #[inline]
57  pub const fn window(&self) -> BrotliWindow {
58    self.window
59  }
60
61  /// Encodes the algorithm settings into a single byte.
62  /// Quality (0-11) is stored in the high 4 bits.
63  /// Window (10-24) is stored in the low 4 bits, mapped to 0-15.
64  #[inline]
65  pub(crate) const fn encode(&self) -> u8 {
66    // Quality goes in high 4 bits
67    let quality_bits = (self.quality as u8) << 4;
68    // Window needs to be mapped from 10-24 to 0-15
69    let window_bits = (self.window as u8).saturating_sub(10);
70    quality_bits | window_bits
71  }
72
73  /// Decodes a single byte into algorithm settings.
74  /// Quality is extracted from high 4 bits.
75  /// Window is extracted from low 4 bits and mapped back to 10-24 range.
76  #[inline]
77  pub(crate) const fn decode(byte: u8) -> Self {
78    // Extract quality from high 4 bits
79    let quality = BrotliQuality::from_u8(byte >> 4);
80    // Extract window from low 4 bits and map back to 10-24 range
81    let window = BrotliWindow::from_u8(10 + (byte & 0x0F));
82    Self { quality, window }
83  }
84}
85
86/// An error that occurs when parsing a brotli quality.
87#[derive(Debug, PartialEq, Eq, Hash, Clone, thiserror::Error)]
88#[error("invalid brotli: {0}")]
89pub struct ParseBrotliAlgorithmError(String);
90
91impl FromStr for BrotliAlgorithm {
92  type Err = ParseBrotliAlgorithmError;
93
94  fn from_str(s: &str) -> Result<Self, Self::Err> {
95    let parts = s.trim_matches(|c| c == '(' || c == ')');
96
97    if parts.is_empty() {
98      return Ok(Self::default());
99    }
100
101    let mut parts = parts.split(',');
102    let quality = parts
103      .next()
104      .ok_or_else(|| ParseBrotliAlgorithmError(s.to_string()))?
105      .trim();
106
107    let quality = super::parse_or_default::<u8, _>(quality)
108      .map_err(|_| ParseBrotliAlgorithmError(s.to_string()))?;
109
110    let window = parts
111      .next()
112      .ok_or_else(|| ParseBrotliAlgorithmError(s.to_string()))?
113      .trim();
114
115    let window = super::parse_or_default::<u8, _>(window)
116      .map_err(|_| ParseBrotliAlgorithmError(s.to_string()))?;
117
118    Ok(Self::with_quality_and_window(quality, window))
119  }
120}
121
122#[cfg(feature = "serde")]
123const _: () = {
124  use serde::{Deserialize, Serialize};
125
126  #[derive(serde::Serialize, serde::Deserialize)]
127  struct BrotliAlgorithmHelper {
128    quality: BrotliQuality,
129    window: BrotliWindow,
130  }
131
132  impl From<BrotliAlgorithm> for BrotliAlgorithmHelper {
133    fn from(algo: BrotliAlgorithm) -> Self {
134      Self {
135        quality: algo.quality(),
136        window: algo.window(),
137      }
138    }
139  }
140
141  impl From<BrotliAlgorithmHelper> for BrotliAlgorithm {
142    fn from(helper: BrotliAlgorithmHelper) -> Self {
143      Self::with_quality_and_window(helper.quality, helper.window)
144    }
145  }
146
147  impl Serialize for BrotliAlgorithm {
148    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149    where
150      S: serde::Serializer,
151    {
152      if serializer.is_human_readable() {
153        BrotliAlgorithmHelper::from(*self).serialize(serializer)
154      } else {
155        serializer.serialize_u8(self.encode())
156      }
157    }
158  }
159
160  impl<'de> Deserialize<'de> for BrotliAlgorithm {
161    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162    where
163      D: serde::Deserializer<'de>,
164    {
165      if deserializer.is_human_readable() {
166        BrotliAlgorithmHelper::deserialize(deserializer).map(Into::into)
167      } else {
168        u8::deserialize(deserializer).map(Self::decode)
169      }
170    }
171  }
172};
173
174#[test]
175fn parse_str() {
176  assert_eq!("".parse::<BrotliAlgorithm>(), Ok(BrotliAlgorithm::new()));
177  assert_eq!("()".parse::<BrotliAlgorithm>(), Ok(BrotliAlgorithm::new()));
178  assert_eq!(
179    "(1, 11)".parse::<BrotliAlgorithm>(),
180    Ok(BrotliAlgorithm::with_quality_and_window(
181      BrotliQuality::Q1,
182      BrotliWindow::W11
183    ))
184  );
185  assert_eq!(
186    "(11, 24)".parse::<BrotliAlgorithm>(),
187    Ok(BrotliAlgorithm::with_quality_and_window(
188      BrotliQuality::Q11,
189      BrotliWindow::W24
190    ))
191  );
192  assert_eq!(
193    "(5, 15)".parse::<BrotliAlgorithm>(),
194    Ok(BrotliAlgorithm::with_quality_and_window(
195      BrotliQuality::Q5,
196      BrotliWindow::W15
197    ))
198  );
199  assert_eq!(
200    "(10, 20)".parse::<BrotliAlgorithm>(),
201    Ok(BrotliAlgorithm::with_quality_and_window(
202      BrotliQuality::Q10,
203      BrotliWindow::W20
204    ))
205  );
206  assert_eq!(
207    "(1, 11)".parse::<BrotliAlgorithm>(),
208    Ok(BrotliAlgorithm::with_quality_and_window(
209      BrotliQuality::Q1,
210      BrotliWindow::W11
211    ))
212  );
213  assert_eq!(
214    "(2, 12)".parse::<BrotliAlgorithm>(),
215    Ok(BrotliAlgorithm::with_quality_and_window(
216      BrotliQuality::Q2,
217      BrotliWindow::W12
218    ))
219  );
220  assert_eq!(
221    "(3, 13)".parse::<BrotliAlgorithm>(),
222    Ok(BrotliAlgorithm::with_quality_and_window(
223      BrotliQuality::Q3,
224      BrotliWindow::W13
225    ))
226  );
227  assert_eq!(
228    "(4, 14)".parse::<BrotliAlgorithm>(),
229    Ok(BrotliAlgorithm::with_quality_and_window(
230      BrotliQuality::Q4,
231      BrotliWindow::W14
232    ))
233  );
234  assert_eq!(
235    "(255, 255)".parse::<BrotliAlgorithm>(),
236    Ok(BrotliAlgorithm::with_quality_and_window(
237      BrotliQuality::max(),
238      BrotliWindow::max()
239    ))
240  );
241  assert_eq!(
242    "(-, -)".parse::<BrotliAlgorithm>(),
243    Ok(BrotliAlgorithm::new())
244  );
245}