memberlist_proto/compression/
brotli_impl.rs1use core::str::FromStr;
2
3num_to_enum! {
4 #[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 #[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#[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 #[inline]
36 pub const fn new() -> Self {
37 Self {
38 quality: BrotliQuality::Q3,
39 window: BrotliWindow::W16,
40 }
41 }
42
43 #[inline]
45 pub const fn with_quality_and_window(quality: BrotliQuality, window: BrotliWindow) -> Self {
46 Self { quality, window }
47 }
48
49 #[inline]
51 pub const fn quality(&self) -> BrotliQuality {
52 self.quality
53 }
54
55 #[inline]
57 pub const fn window(&self) -> BrotliWindow {
58 self.window
59 }
60
61 #[inline]
65 pub(crate) const fn encode(&self) -> u8 {
66 let quality_bits = (self.quality as u8) << 4;
68 let window_bits = (self.window as u8).saturating_sub(10);
70 quality_bits | window_bits
71 }
72
73 #[inline]
77 pub(crate) const fn decode(byte: u8) -> Self {
78 let quality = BrotliQuality::from_u8(byte >> 4);
80 let window = BrotliWindow::from_u8(10 + (byte & 0x0F));
82 Self { quality, window }
83 }
84}
85
86#[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}