permessage_deflate.py 26 KB

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