permessage_deflate.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. from __future__ import annotations
  2. import zlib
  3. from collections.abc import Sequence
  4. from typing import Any, Literal
  5. from ..exceptions import (
  6. DuplicateParameter,
  7. InvalidParameterName,
  8. InvalidParameterValue,
  9. NegotiationError,
  10. PayloadTooBig,
  11. ProtocolError,
  12. )
  13. from ..frames import CONT, CTRL_OPCODES, Frame
  14. from ..typing import BytesLike, ExtensionName, ExtensionParameter
  15. from .base import ClientExtensionFactory, Extension, ServerExtensionFactory
  16. __all__ = [
  17. "PerMessageDeflate",
  18. "ClientPerMessageDeflateFactory",
  19. "enable_client_permessage_deflate",
  20. "ServerPerMessageDeflateFactory",
  21. "enable_server_permessage_deflate",
  22. ]
  23. _EMPTY_UNCOMPRESSED_BLOCK = b"\x00\x00\xff\xff"
  24. _MAX_WINDOW_BITS_VALUES = [str(bits) for bits in range(8, 16)]
  25. class PerMessageDeflate(Extension):
  26. """
  27. Per-Message Deflate extension.
  28. """
  29. name = ExtensionName("permessage-deflate")
  30. def __init__(
  31. self,
  32. remote_no_context_takeover: bool,
  33. local_no_context_takeover: bool,
  34. remote_max_window_bits: int,
  35. local_max_window_bits: int,
  36. compress_settings: dict[Any, Any] | None = None,
  37. ) -> None:
  38. """
  39. Configure the Per-Message Deflate extension.
  40. """
  41. if compress_settings is None:
  42. compress_settings = {}
  43. assert remote_no_context_takeover in [False, True]
  44. assert local_no_context_takeover in [False, True]
  45. assert 8 <= remote_max_window_bits <= 15
  46. assert 8 <= local_max_window_bits <= 15
  47. assert "wbits" not in compress_settings
  48. self.remote_no_context_takeover = remote_no_context_takeover
  49. self.local_no_context_takeover = local_no_context_takeover
  50. self.remote_max_window_bits = remote_max_window_bits
  51. self.local_max_window_bits = local_max_window_bits
  52. self.compress_settings = compress_settings
  53. if not self.remote_no_context_takeover:
  54. self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits)
  55. if not self.local_no_context_takeover:
  56. self.encoder = zlib.compressobj(
  57. wbits=-self.local_max_window_bits,
  58. **self.compress_settings,
  59. )
  60. # To handle continuation frames properly, we must keep track of
  61. # whether that initial frame was encoded.
  62. self.decode_cont_data = False
  63. # There's no need for self.encode_cont_data because we always encode
  64. # outgoing frames, so it would always be True.
  65. def __repr__(self) -> str:
  66. return (
  67. f"PerMessageDeflate("
  68. f"remote_no_context_takeover={self.remote_no_context_takeover}, "
  69. f"local_no_context_takeover={self.local_no_context_takeover}, "
  70. f"remote_max_window_bits={self.remote_max_window_bits}, "
  71. f"local_max_window_bits={self.local_max_window_bits})"
  72. )
  73. def decode(
  74. self,
  75. frame: Frame,
  76. *,
  77. max_size: int | None = None,
  78. ) -> Frame:
  79. """
  80. Decode an incoming frame.
  81. """
  82. # Skip control frames.
  83. if frame.opcode in CTRL_OPCODES:
  84. return frame
  85. # Handle continuation data frames:
  86. # - skip if the message isn't encoded
  87. # - reset "decode continuation data" flag if it's a final frame
  88. if frame.opcode is CONT:
  89. if not self.decode_cont_data:
  90. return frame
  91. if frame.rsv1:
  92. raise ProtocolError("RSV1 bit set in continuation frame")
  93. if frame.fin:
  94. self.decode_cont_data = False
  95. # Handle text and binary data frames:
  96. # - skip if the message isn't encoded
  97. # - unset the rsv1 flag on the first frame of a compressed message
  98. # - set "decode continuation data" flag if it's a non-final frame
  99. else:
  100. if not frame.rsv1:
  101. return frame
  102. if not frame.fin:
  103. self.decode_cont_data = True
  104. # Re-initialize per-message decoder.
  105. if self.remote_no_context_takeover:
  106. self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits)
  107. # Uncompress data. Protect against zip bombs by preventing zlib from
  108. # decompressing more than max_length bytes (except when the limit is
  109. # disabled with max_size = None).
  110. data: BytesLike
  111. if frame.fin and len(frame.data) < 2044:
  112. # Profiling shows that appending four bytes, which makes a copy, is
  113. # faster than calling decompress() again when data is less than 2kB.
  114. data = bytes(frame.data) + _EMPTY_UNCOMPRESSED_BLOCK
  115. else:
  116. data = frame.data
  117. max_length = 0 if max_size is None else max_size
  118. try:
  119. data = self.decoder.decompress(data, max_length)
  120. if self.decoder.unconsumed_tail:
  121. raise PayloadTooBig(None, max_size)
  122. if frame.fin and len(frame.data) >= 2044:
  123. # In edge cases, flushing may yield data held back by max_size.
  124. if self.decoder.decompress(_EMPTY_UNCOMPRESSED_BLOCK, 1):
  125. raise PayloadTooBig(None, max_size)
  126. except zlib.error as exc:
  127. raise ProtocolError("decompression failed") from exc
  128. # Allow garbage collection of the decoder if it won't be reused.
  129. if frame.fin and self.remote_no_context_takeover:
  130. del self.decoder
  131. return Frame(
  132. frame.opcode,
  133. data,
  134. frame.fin,
  135. # Unset the rsv1 flag on the first frame of a compressed message.
  136. False,
  137. frame.rsv2,
  138. frame.rsv3,
  139. )
  140. def encode(self, frame: Frame) -> Frame:
  141. """
  142. Encode an outgoing frame.
  143. """
  144. # Skip control frames.
  145. if frame.opcode in CTRL_OPCODES:
  146. return frame
  147. # Since we always encode messages, there's no "encode continuation
  148. # data" flag similar to "decode continuation data" at this time.
  149. if frame.opcode is not CONT:
  150. # Re-initialize per-message decoder.
  151. if self.local_no_context_takeover:
  152. self.encoder = zlib.compressobj(
  153. wbits=-self.local_max_window_bits,
  154. **self.compress_settings,
  155. )
  156. # Compress data.
  157. data: BytesLike
  158. data = self.encoder.compress(frame.data) + self.encoder.flush(zlib.Z_SYNC_FLUSH)
  159. if frame.fin:
  160. # Sync flush generates between 5 or 6 bytes, ending with the bytes
  161. # 0x00 0x00 0xff 0xff, which must be removed.
  162. assert data[-4:] == _EMPTY_UNCOMPRESSED_BLOCK
  163. # Making a copy is faster than memoryview(a)[:-4] until 2kB.
  164. if len(data) < 2048:
  165. data = data[:-4]
  166. else:
  167. data = memoryview(data)[:-4]
  168. # Allow garbage collection of the encoder if it won't be reused.
  169. if frame.fin and self.local_no_context_takeover:
  170. del self.encoder
  171. return Frame(
  172. frame.opcode,
  173. data,
  174. frame.fin,
  175. # Set the rsv1 flag on the first frame of a compressed message.
  176. frame.opcode is not CONT,
  177. frame.rsv2,
  178. frame.rsv3,
  179. )
  180. def _build_parameters(
  181. server_no_context_takeover: bool,
  182. client_no_context_takeover: bool,
  183. server_max_window_bits: int | None,
  184. client_max_window_bits: int | Literal[True] | None,
  185. ) -> list[ExtensionParameter]:
  186. """
  187. Build a list of ``(name, value)`` pairs for some compression parameters.
  188. """
  189. params: list[ExtensionParameter] = []
  190. if server_no_context_takeover:
  191. params.append(("server_no_context_takeover", None))
  192. if client_no_context_takeover:
  193. params.append(("client_no_context_takeover", None))
  194. if server_max_window_bits:
  195. params.append(("server_max_window_bits", str(server_max_window_bits)))
  196. if client_max_window_bits is True: # only in handshake requests
  197. params.append(("client_max_window_bits", None))
  198. elif client_max_window_bits:
  199. params.append(("client_max_window_bits", str(client_max_window_bits)))
  200. return params
  201. def _extract_parameters(
  202. params: Sequence[ExtensionParameter], *, is_server: bool
  203. ) -> tuple[bool, bool, int | None, int | Literal[True] | None]:
  204. """
  205. Extract compression parameters from a list of ``(name, value)`` pairs.
  206. If ``is_server`` is :obj:`True`, ``client_max_window_bits`` may be
  207. provided without a value. This is only allowed in handshake requests.
  208. """
  209. server_no_context_takeover: bool = False
  210. client_no_context_takeover: bool = False
  211. server_max_window_bits: int | None = None
  212. client_max_window_bits: int | Literal[True] | None = None
  213. for name, value in params:
  214. if name == "server_no_context_takeover":
  215. if server_no_context_takeover:
  216. raise DuplicateParameter(name)
  217. if value is None:
  218. server_no_context_takeover = True
  219. else:
  220. raise InvalidParameterValue(name, value)
  221. elif name == "client_no_context_takeover":
  222. if client_no_context_takeover:
  223. raise DuplicateParameter(name)
  224. if value is None:
  225. client_no_context_takeover = True
  226. else:
  227. raise InvalidParameterValue(name, value)
  228. elif name == "server_max_window_bits":
  229. if server_max_window_bits is not None:
  230. raise DuplicateParameter(name)
  231. if value in _MAX_WINDOW_BITS_VALUES:
  232. server_max_window_bits = int(value)
  233. else:
  234. raise InvalidParameterValue(name, value)
  235. elif name == "client_max_window_bits":
  236. if client_max_window_bits is not None:
  237. raise DuplicateParameter(name)
  238. if is_server and value is None: # only in handshake requests
  239. client_max_window_bits = True
  240. elif value in _MAX_WINDOW_BITS_VALUES:
  241. client_max_window_bits = int(value)
  242. else:
  243. raise InvalidParameterValue(name, value)
  244. else:
  245. raise InvalidParameterName(name)
  246. return (
  247. server_no_context_takeover,
  248. client_no_context_takeover,
  249. server_max_window_bits,
  250. client_max_window_bits,
  251. )
  252. class ClientPerMessageDeflateFactory(ClientExtensionFactory):
  253. """
  254. Client-side extension factory for the Per-Message Deflate extension.
  255. Parameters behave as described in `section 7.1 of RFC 7692`_.
  256. .. _section 7.1 of RFC 7692: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1
  257. Set them to :obj:`True` to include them in the negotiation offer without a
  258. value or to an integer value to include them with this value.
  259. Args:
  260. server_no_context_takeover: Prevent server from using context takeover.
  261. client_no_context_takeover: Prevent client from using context takeover.
  262. server_max_window_bits: Maximum size of the server's LZ77 sliding window
  263. in bits, between 8 and 15.
  264. client_max_window_bits: Maximum size of the client's LZ77 sliding window
  265. in bits, between 8 and 15, or :obj:`True` to indicate support without
  266. setting a limit.
  267. compress_settings: Additional keyword arguments for :func:`zlib.compressobj`,
  268. excluding ``wbits``.
  269. """
  270. name = ExtensionName("permessage-deflate")
  271. def __init__(
  272. self,
  273. server_no_context_takeover: bool = False,
  274. client_no_context_takeover: bool = False,
  275. server_max_window_bits: int | None = None,
  276. client_max_window_bits: int | Literal[True] | None = True,
  277. compress_settings: dict[str, Any] | None = None,
  278. ) -> None:
  279. """
  280. Configure the Per-Message Deflate extension factory.
  281. """
  282. if not (server_max_window_bits is None or 8 <= server_max_window_bits <= 15):
  283. raise ValueError("server_max_window_bits must be between 8 and 15")
  284. if not (
  285. client_max_window_bits is None
  286. or client_max_window_bits is True
  287. or 8 <= client_max_window_bits <= 15
  288. ):
  289. raise ValueError("client_max_window_bits must be between 8 and 15")
  290. if compress_settings is not None and "wbits" in compress_settings:
  291. raise ValueError(
  292. "compress_settings must not include wbits, "
  293. "set client_max_window_bits instead"
  294. )
  295. self.server_no_context_takeover = server_no_context_takeover
  296. self.client_no_context_takeover = client_no_context_takeover
  297. self.server_max_window_bits = server_max_window_bits
  298. self.client_max_window_bits = client_max_window_bits
  299. self.compress_settings = compress_settings
  300. def get_request_params(self) -> Sequence[ExtensionParameter]:
  301. """
  302. Build request parameters.
  303. """
  304. return _build_parameters(
  305. self.server_no_context_takeover,
  306. self.client_no_context_takeover,
  307. self.server_max_window_bits,
  308. self.client_max_window_bits,
  309. )
  310. def process_response_params(
  311. self,
  312. params: Sequence[ExtensionParameter],
  313. accepted_extensions: Sequence[Extension],
  314. ) -> PerMessageDeflate:
  315. """
  316. Process response parameters.
  317. Return an extension instance.
  318. """
  319. if any(other.name == self.name for other in accepted_extensions):
  320. raise NegotiationError(f"received duplicate {self.name}")
  321. # Request parameters are available in instance variables.
  322. # Load response parameters in local variables.
  323. (
  324. server_no_context_takeover,
  325. client_no_context_takeover,
  326. server_max_window_bits,
  327. client_max_window_bits,
  328. ) = _extract_parameters(params, is_server=False)
  329. # After comparing the request and the response, the final
  330. # configuration must be available in the local variables.
  331. # server_no_context_takeover
  332. #
  333. # Req. Resp. Result
  334. # ------ ------ --------------------------------------------------
  335. # False False False
  336. # False True True
  337. # True False Error!
  338. # True True True
  339. if self.server_no_context_takeover:
  340. if not server_no_context_takeover:
  341. raise NegotiationError("expected server_no_context_takeover")
  342. # client_no_context_takeover
  343. #
  344. # Req. Resp. Result
  345. # ------ ------ --------------------------------------------------
  346. # False False False
  347. # False True True
  348. # True False True - must change value
  349. # True True True
  350. if self.client_no_context_takeover:
  351. if not client_no_context_takeover:
  352. client_no_context_takeover = True
  353. # server_max_window_bits
  354. # Req. Resp. Result
  355. # ------ ------ --------------------------------------------------
  356. # None None None
  357. # None 8≤M≤15 M
  358. # 8≤N≤15 None Error!
  359. # 8≤N≤15 8≤M≤N M
  360. # 8≤N≤15 N<M≤15 Error!
  361. if self.server_max_window_bits is None:
  362. pass
  363. else:
  364. if server_max_window_bits is None:
  365. raise NegotiationError("expected server_max_window_bits")
  366. elif server_max_window_bits > self.server_max_window_bits:
  367. raise NegotiationError("unsupported server_max_window_bits")
  368. # client_max_window_bits
  369. # Req. Resp. Result
  370. # ------ ------ --------------------------------------------------
  371. # None None None
  372. # None 8≤M≤15 Error!
  373. # True None None
  374. # True 8≤M≤15 M
  375. # 8≤N≤15 None N - must change value
  376. # 8≤N≤15 8≤M≤N M
  377. # 8≤N≤15 N<M≤15 Error!
  378. if self.client_max_window_bits is None:
  379. if client_max_window_bits is not None:
  380. raise NegotiationError("unexpected client_max_window_bits")
  381. elif self.client_max_window_bits is True:
  382. pass
  383. else:
  384. if client_max_window_bits is None:
  385. client_max_window_bits = self.client_max_window_bits
  386. elif client_max_window_bits > self.client_max_window_bits:
  387. raise NegotiationError("unsupported client_max_window_bits")
  388. return PerMessageDeflate(
  389. server_no_context_takeover, # remote_no_context_takeover
  390. client_no_context_takeover, # local_no_context_takeover
  391. server_max_window_bits or 15, # remote_max_window_bits
  392. client_max_window_bits or 15, # local_max_window_bits
  393. self.compress_settings,
  394. )
  395. def enable_client_permessage_deflate(
  396. extensions: Sequence[ClientExtensionFactory] | None,
  397. ) -> Sequence[ClientExtensionFactory]:
  398. """
  399. Enable Per-Message Deflate with default settings in client extensions.
  400. If the extension is already present, perhaps with non-default settings,
  401. the configuration isn't changed.
  402. """
  403. if extensions is None:
  404. extensions = []
  405. if not any(
  406. extension_factory.name == ClientPerMessageDeflateFactory.name
  407. for extension_factory in extensions
  408. ):
  409. extensions = list(extensions) + [
  410. ClientPerMessageDeflateFactory(
  411. compress_settings={"memLevel": 5},
  412. )
  413. ]
  414. return extensions
  415. class ServerPerMessageDeflateFactory(ServerExtensionFactory):
  416. """
  417. Server-side extension factory for the Per-Message Deflate extension.
  418. Parameters behave as described in `section 7.1 of RFC 7692`_.
  419. .. _section 7.1 of RFC 7692: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1
  420. Set them to :obj:`True` to include them in the negotiation offer without a
  421. value or to an integer value to include them with this value.
  422. Args:
  423. server_no_context_takeover: Prevent server from using context takeover.
  424. client_no_context_takeover: Prevent client from using context takeover.
  425. server_max_window_bits: Maximum size of the server's LZ77 sliding window
  426. in bits, between 8 and 15.
  427. client_max_window_bits: Maximum size of the client's LZ77 sliding window
  428. in bits, between 8 and 15.
  429. compress_settings: Additional keyword arguments for :func:`zlib.compressobj`,
  430. excluding ``wbits``.
  431. require_client_max_window_bits: Do not enable compression at all if
  432. client doesn't advertise support for ``client_max_window_bits``;
  433. the default behavior is to enable compression without enforcing
  434. ``client_max_window_bits``.
  435. """
  436. name = ExtensionName("permessage-deflate")
  437. def __init__(
  438. self,
  439. server_no_context_takeover: bool = False,
  440. client_no_context_takeover: bool = False,
  441. server_max_window_bits: int | None = None,
  442. client_max_window_bits: int | None = None,
  443. compress_settings: dict[str, Any] | None = None,
  444. require_client_max_window_bits: bool = False,
  445. ) -> None:
  446. """
  447. Configure the Per-Message Deflate extension factory.
  448. """
  449. if not (server_max_window_bits is None or 8 <= server_max_window_bits <= 15):
  450. raise ValueError("server_max_window_bits must be between 8 and 15")
  451. if not (client_max_window_bits is None or 8 <= client_max_window_bits <= 15):
  452. raise ValueError("client_max_window_bits must be between 8 and 15")
  453. if compress_settings is not None and "wbits" in compress_settings:
  454. raise ValueError(
  455. "compress_settings must not include wbits, "
  456. "set server_max_window_bits instead"
  457. )
  458. if client_max_window_bits is None and require_client_max_window_bits:
  459. raise ValueError(
  460. "require_client_max_window_bits is enabled, "
  461. "but client_max_window_bits isn't configured"
  462. )
  463. self.server_no_context_takeover = server_no_context_takeover
  464. self.client_no_context_takeover = client_no_context_takeover
  465. self.server_max_window_bits = server_max_window_bits
  466. self.client_max_window_bits = client_max_window_bits
  467. self.compress_settings = compress_settings
  468. self.require_client_max_window_bits = require_client_max_window_bits
  469. def process_request_params(
  470. self,
  471. params: Sequence[ExtensionParameter],
  472. accepted_extensions: Sequence[Extension],
  473. ) -> tuple[list[ExtensionParameter], PerMessageDeflate]:
  474. """
  475. Process request parameters.
  476. Return response params and an extension instance.
  477. """
  478. if any(other.name == self.name for other in accepted_extensions):
  479. raise NegotiationError(f"skipped duplicate {self.name}")
  480. # Load request parameters in local variables.
  481. (
  482. server_no_context_takeover,
  483. client_no_context_takeover,
  484. server_max_window_bits,
  485. client_max_window_bits,
  486. ) = _extract_parameters(params, is_server=True)
  487. # Configuration parameters are available in instance variables.
  488. # After comparing the request and the configuration, the response must
  489. # be available in the local variables.
  490. # server_no_context_takeover
  491. #
  492. # Config Req. Resp.
  493. # ------ ------ --------------------------------------------------
  494. # False False False
  495. # False True True
  496. # True False True - must change value to True
  497. # True True True
  498. if self.server_no_context_takeover:
  499. if not server_no_context_takeover:
  500. server_no_context_takeover = True
  501. # client_no_context_takeover
  502. #
  503. # Config Req. Resp.
  504. # ------ ------ --------------------------------------------------
  505. # False False False
  506. # False True True (or False)
  507. # True False True - must change value to True
  508. # True True True (or False)
  509. if self.client_no_context_takeover:
  510. if not client_no_context_takeover:
  511. client_no_context_takeover = True
  512. # server_max_window_bits
  513. # Config Req. Resp.
  514. # ------ ------ --------------------------------------------------
  515. # None None None
  516. # None 8≤M≤15 M
  517. # 8≤N≤15 None N - must change value
  518. # 8≤N≤15 8≤M≤N M
  519. # 8≤N≤15 N<M≤15 N - must change value
  520. if self.server_max_window_bits is None:
  521. pass
  522. else:
  523. if server_max_window_bits is None:
  524. server_max_window_bits = self.server_max_window_bits
  525. elif server_max_window_bits > self.server_max_window_bits:
  526. server_max_window_bits = self.server_max_window_bits
  527. # client_max_window_bits
  528. # Config Req. Resp.
  529. # ------ ------ --------------------------------------------------
  530. # None None None
  531. # None True None - must change value
  532. # None 8≤M≤15 M (or None)
  533. # 8≤N≤15 None None or Error!
  534. # 8≤N≤15 True N - must change value
  535. # 8≤N≤15 8≤M≤N M (or None)
  536. # 8≤N≤15 N<M≤15 N
  537. if self.client_max_window_bits is None:
  538. if client_max_window_bits is True:
  539. client_max_window_bits = self.client_max_window_bits
  540. else:
  541. if client_max_window_bits is None:
  542. if self.require_client_max_window_bits:
  543. raise NegotiationError("required client_max_window_bits")
  544. elif client_max_window_bits is True:
  545. client_max_window_bits = self.client_max_window_bits
  546. elif self.client_max_window_bits < client_max_window_bits:
  547. client_max_window_bits = self.client_max_window_bits
  548. return (
  549. _build_parameters(
  550. server_no_context_takeover,
  551. client_no_context_takeover,
  552. server_max_window_bits,
  553. client_max_window_bits,
  554. ),
  555. PerMessageDeflate(
  556. client_no_context_takeover, # remote_no_context_takeover
  557. server_no_context_takeover, # local_no_context_takeover
  558. client_max_window_bits or 15, # remote_max_window_bits
  559. server_max_window_bits or 15, # local_max_window_bits
  560. self.compress_settings,
  561. ),
  562. )
  563. def enable_server_permessage_deflate(
  564. extensions: Sequence[ServerExtensionFactory] | None,
  565. ) -> Sequence[ServerExtensionFactory]:
  566. """
  567. Enable Per-Message Deflate with default settings in server extensions.
  568. If the extension is already present, perhaps with non-default settings,
  569. the configuration isn't changed.
  570. """
  571. if extensions is None:
  572. extensions = []
  573. if not any(
  574. ext_factory.name == ServerPerMessageDeflateFactory.name
  575. for ext_factory in extensions
  576. ):
  577. extensions = list(extensions) + [
  578. ServerPerMessageDeflateFactory(
  579. server_max_window_bits=12,
  580. client_max_window_bits=12,
  581. compress_settings={"memLevel": 5},
  582. )
  583. ]
  584. return extensions