1use bytes::Bytes;
5use thiserror::Error;
6
7use crate::extensions::compression::{
8 CompressionError, DecompressionError, PerMessageCompressionContext,
9};
10#[cfg(feature = "handshake")]
11use crate::extensions::headers::{SecWebsocketExtensions, WebsocketProtocolExtension};
12use crate::protocol::Role;
13
14pub mod compression;
15#[cfg(feature = "headers")]
16pub(crate) mod headers;
17
18#[derive(Debug, Default)]
20#[allow(missing_copy_implementations)]
21pub struct Extensions {
22 per_message_compression: Option<PerMessageCompressionContext>,
25}
26
27#[derive(Copy, Clone, Default, Debug)]
29#[non_exhaustive]
30pub struct ExtensionsConfig {
31 #[cfg(feature = "deflate")]
35 pub permessage_deflate: Option<compression::deflate::DeflateConfig>,
36}
37
38#[derive(Debug, Error, PartialEq, Eq, Clone)]
40pub enum ExtensionsError {
41 #[error("Extension header had invalid extension: {0}")]
43 InvalidExtension(Box<str>),
44 #[error("Extension negotiation response had conflicting extension: {0}")]
46 ExtensionConflict(Box<str>),
47 #[error("Extension negotiation response had malformed extension: {0}")]
49 MalformedExtension(&'static str),
50}
51
52#[cfg(feature = "handshake")]
53impl ExtensionsConfig {
54 pub(crate) fn generate_offers(&self) -> impl Iterator<Item = WebsocketProtocolExtension> {
55 let Self {
56 #[cfg(feature = "deflate")]
57 permessage_deflate,
58 } = self;
59
60 #[allow(unused_mut, unused_assignments)]
61 let mut permessage_compression_offer = None;
62
63 #[cfg(feature = "deflate")]
64 {
65 permessage_compression_offer = permessage_deflate.as_ref().map(|p| p.as_offer().into());
66 }
67
68 permessage_compression_offer.into_iter()
69 }
70
71 pub(crate) fn verify_agreed_on(
79 &self,
80 agreed: SecWebsocketExtensions,
81 ) -> Result<Extensions, ExtensionsError> {
82 #[cfg_attr(not(feature = "deflate"), allow(unused_mut))]
83 let mut per_message_compression = None;
84
85 for extension in agreed.iter() {
86 match extension.name() {
87 #[cfg(feature = "deflate")]
88 compression::deflate::EXTENSION_NAME => {
89 use compression::deflate::{
90 DeflateContext, DeflateParameterError, PermessageDeflateConfig,
91 EXTENSION_NAME,
92 };
93
94 if per_message_compression.is_some() {
96 return Err(ExtensionsError::ExtensionConflict(EXTENSION_NAME.into()));
97 }
98
99 let deflate = self
100 .permessage_deflate
101 .ok_or_else(|| ExtensionsError::InvalidExtension(EXTENSION_NAME.into()))?;
102
103 let extension: PermessageDeflateConfig = PermessageDeflateConfig::parse_params(
104 extension.params(),
105 )
106 .map_err(|_: DeflateParameterError| {
107 ExtensionsError::MalformedExtension(EXTENSION_NAME)
108 })?;
109
110 let deflate_config = deflate.accept_response(extension).map_err(|e| {
111 ExtensionsError::InvalidExtension(format!("{EXTENSION_NAME}: {e}").into())
112 })?;
113
114 per_message_compression =
115 Some(DeflateContext::new(Role::Client, deflate_config).into());
116 }
117 name => return Err(ExtensionsError::InvalidExtension(name.into())),
118 }
119 }
120
121 Ok(Extensions { per_message_compression })
122 }
123
124 pub(crate) fn accept_offers(
133 &self,
134 extensions: &SecWebsocketExtensions,
135 ) -> Result<(Extensions, Option<SecWebsocketExtensions>), ExtensionsError> {
136 #[cfg_attr(not(feature = "deflate"), allow(unused_mut))]
137 let mut per_message_compression = None;
138
139 for extension in extensions.iter() {
140 match extension.name() {
143 #[cfg(feature = "deflate")]
144 compression::deflate::EXTENSION_NAME => {
145 use compression::deflate::{
146 DeflateContext, PermessageDeflateConfig, EXTENSION_NAME,
147 };
148
149 let deflate = match self.permessage_deflate {
150 Some(deflate) => deflate,
151 None => continue,
152 };
153
154 let extension = match PermessageDeflateConfig::parse_params(extension.params())
155 {
156 Ok(extension) => extension,
157 Err(e) => {
158 log::debug!("{EXTENSION_NAME} extension: {e}");
173 continue;
174 }
175 };
176 if per_message_compression.is_some() {
193 continue;
194 }
195
196 if let Some((config, response)) = deflate.accept_offer(extension) {
197 per_message_compression = Some((
198 DeflateContext::new(Role::Server, config).into(),
199 response.into(),
200 ));
201 }
202 }
203 _ => {}
205 }
206 }
207
208 let (per_message_compression, response) = match per_message_compression {
209 Some((a, b)) => (Some(a), Some(b)),
210 None => (None, None),
211 };
212
213 Ok((
214 Extensions { per_message_compression },
215 response.map(|response| SecWebsocketExtensions::new(std::iter::once(response))),
216 ))
217 }
218}
219
220impl ExtensionsConfig {
221 pub(crate) fn into_unnegotiated_context(self, role: Role) -> Extensions {
227 let Self {
231 #[cfg(feature = "deflate")]
232 permessage_deflate,
233 } = self;
234
235 #[cfg_attr(feature = "deflate", allow(unused_assignments))]
236 #[cfg_attr(not(feature = "deflate"), allow(unused_mut))]
237 let mut per_message_compression = None;
238 #[cfg(feature = "deflate")]
239 {
240 per_message_compression = permessage_deflate
241 .map(|deflate| compression::deflate::DeflateContext::new(role, deflate).into());
242 }
243 let _ = role;
244
245 Extensions { per_message_compression }
246 }
247}
248
249impl Extensions {
250 #[inline]
258 pub(crate) fn per_message_compressor<'s>(
259 &'s mut self,
260 ) -> Option<impl FnOnce(&Bytes) -> Result<Bytes, CompressionError> + 's> {
261 let Self { per_message_compression } = self;
262
263 per_message_compression.as_mut().map(PerMessageCompressionContext::compressor)
264 }
265
266 #[inline]
277 pub(crate) fn per_message_decompressor<'s>(
278 &'s mut self,
279 ) -> Option<impl FnMut(&Bytes, bool, usize) -> Result<Bytes, DecompressionError> + 's> {
280 let Self { per_message_compression } = self;
281 per_message_compression.as_mut().map(PerMessageCompressionContext::decompressor)
282 }
283}
284
285#[cfg(feature = "handshake")]
286#[cfg(test)]
287mod test {
288 use super::*;
289
290 #[test]
291 fn accept_offers_ignores_unknown_extensions() {
292 let (Extensions { per_message_compression }, response) = ExtensionsConfig::default()
293 .accept_offers(&SecWebsocketExtensions::new([
294 "unknown-1".parse().unwrap(),
295 "other-unknown; a=5; b=3".parse().unwrap(),
296 ]))
297 .unwrap();
298
299 assert!(matches!(per_message_compression, None));
300 assert_eq!(response, None);
301 }
302
303 #[test]
304 fn accept_offers_with_deflate_disabled() {
305 let extensions = ExtensionsConfig::default();
306
307 let (Extensions { per_message_compression }, response) =
309 extensions.accept_offers(&SecWebsocketExtensions::new([])).unwrap();
310
311 assert!(matches!(per_message_compression, None));
312 assert_eq!(response, None);
313 }
314
315 #[cfg(feature = "deflate")]
316 #[test]
317 fn accept_offers_with_deflate_enabled() {
318 let extensions = ExtensionsConfig { permessage_deflate: Some(Default::default()) };
319
320 {
321 let (Extensions { per_message_compression }, response) =
324 extensions.accept_offers(&SecWebsocketExtensions::new([])).unwrap();
325 assert!(matches!(per_message_compression, None));
326 assert_eq!(response, None);
327 }
328
329 {
330 let (Extensions { per_message_compression }, response) = extensions
332 .accept_offers(&SecWebsocketExtensions::new([
333 WebsocketProtocolExtension::new(compression::deflate::EXTENSION_NAME, []),
334 WebsocketProtocolExtension::new("some-other-extension", []),
335 ]))
336 .unwrap();
337
338 assert!(matches!(per_message_compression, Some(_)));
339 assert_eq!(
340 response,
341 Some(SecWebsocketExtensions::new([WebsocketProtocolExtension::new(
342 compression::deflate::EXTENSION_NAME,
343 []
344 )]))
345 );
346 }
347 }
348
349 #[cfg(feature = "deflate")]
350 #[test]
351 fn accept_offers_picks_first_acceptable_offer() {
352 use compression::deflate::*;
353 let extensions = ExtensionsConfig {
354 permessage_deflate: Some(
355 DeflateConfig::new().set_max_window_bits(Role::Client, 11).unwrap(),
356 ),
357 };
358
359 let (Extensions { per_message_compression }, response) = extensions
360 .accept_offers(&SecWebsocketExtensions::new([
361 "permessage-deflate".parse().unwrap(),
365 "permessage-deflate; server_max_window_bits=12".parse().unwrap(),
366 "permessage-deflate; client_max_window_bits=11; parameter-from-the-future=3"
368 .parse()
369 .unwrap(),
370 "permessage-deflate; client_no_context_takeover; client_max_window_bits=11"
372 .parse()
373 .unwrap(),
374 "permessage-deflate; client_max_window_bits=10".parse().unwrap(),
376 ]))
377 .unwrap();
378
379 assert!(matches!(per_message_compression, Some(PerMessageCompressionContext::Deflate(_))));
380 assert_eq!(
381 response,
382 Some(SecWebsocketExtensions::new([DeflateConfig::new()
383 .set_no_context_takeover(Role::Client, true)
384 .set_max_window_bits(Role::Client, 11)
385 .unwrap()
386 .as_offer()
387 .into()]))
388 )
389 }
390
391 #[cfg(feature = "deflate")]
392 #[test]
393 fn verify_agreed_on_deflate_then_garbage() {
394 let extensions = ExtensionsConfig { permessage_deflate: Some(Default::default()) };
395
396 let result = extensions.verify_agreed_on(SecWebsocketExtensions::new([
397 WebsocketProtocolExtension::new(compression::deflate::EXTENSION_NAME, []),
398 WebsocketProtocolExtension::new("unrecognized", []),
399 ]));
400
401 assert_eq!(result.unwrap_err(), ExtensionsError::InvalidExtension("unrecognized".into()));
402 }
403
404 #[cfg(feature = "deflate")]
405 #[test]
406 fn verify_agreed_on_deflate_multiple_times() {
407 let extensions = ExtensionsConfig { permessage_deflate: Some(Default::default()) };
408
409 let result = extensions.verify_agreed_on(SecWebsocketExtensions::new([
410 WebsocketProtocolExtension::new(compression::deflate::EXTENSION_NAME, []),
411 WebsocketProtocolExtension::new(
412 compression::deflate::EXTENSION_NAME,
413 ["client_no_context_takeover".parse().unwrap()],
414 ),
415 ]));
416
417 assert_eq!(
418 result.unwrap_err(),
419 ExtensionsError::ExtensionConflict(compression::deflate::EXTENSION_NAME.into())
420 );
421 }
422}