Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

compress_deflate.py 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. ###############################################################################
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (c) typedef int GmbH
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy
  8. # of this software and associated documentation files (the "Software"), to deal
  9. # in the Software without restriction, including without limitation the rights
  10. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. # copies of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in
  15. # all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. # THE SOFTWARE.
  24. #
  25. ###############################################################################
  26. import zlib
  27. from autobahn.util import public
  28. from autobahn.websocket.compress_base import PerMessageCompressOffer, \
  29. PerMessageCompressOfferAccept, \
  30. PerMessageCompressResponse, \
  31. PerMessageCompressResponseAccept, \
  32. PerMessageCompress
  33. __all__ = (
  34. 'PerMessageDeflateMixin',
  35. 'PerMessageDeflateOffer',
  36. 'PerMessageDeflateOfferAccept',
  37. 'PerMessageDeflateResponse',
  38. 'PerMessageDeflateResponseAccept',
  39. 'PerMessageDeflate',
  40. )
  41. class PerMessageDeflateMixin(object):
  42. """
  43. Mixin class for this extension.
  44. """
  45. EXTENSION_NAME = "permessage-deflate"
  46. """
  47. Name of this WebSocket extension.
  48. """
  49. WINDOW_SIZE_PERMISSIBLE_VALUES = [9, 10, 11, 12, 13, 14, 15]
  50. """
  51. Permissible value for window size parameter.
  52. Higher values use more memory, but produce smaller output. The default is 15.
  53. """
  54. MEM_LEVEL_PERMISSIBLE_VALUES = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  55. """
  56. Permissible value for memory level parameter.
  57. Higher values use more memory, but are faster and produce smaller output. The default is 8.
  58. """
  59. @public
  60. class PerMessageDeflateOffer(PerMessageCompressOffer, PerMessageDeflateMixin):
  61. """
  62. Set of extension parameters for `permessage-deflate` WebSocket extension
  63. offered by a client to a server.
  64. """
  65. @classmethod
  66. def parse(cls, params):
  67. """
  68. Parses a WebSocket extension offer for `permessage-deflate` provided by a client to a server.
  69. :param params: Output from :func:`autobahn.websocket.WebSocketProtocol._parseExtensionsHeader`.
  70. :type params: list
  71. :returns: A new instance of :class:`autobahn.compress.PerMessageDeflateOffer`.
  72. :rtype: obj
  73. """
  74. # extension parameter defaults
  75. accept_max_window_bits = False
  76. accept_no_context_takeover = True
  77. # accept_no_context_takeover = False # FIXME: this may change in draft
  78. request_max_window_bits = 0
  79. request_no_context_takeover = False
  80. # verify/parse client ("client-to-server direction") parameters of permessage-deflate offer
  81. for p in params:
  82. if len(params[p]) > 1:
  83. raise Exception("multiple occurrence of extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
  84. val = params[p][0]
  85. if p == 'client_max_window_bits':
  86. #
  87. # see: https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18
  88. # 8.1.2.2. client_max_window_bits
  89. # ".. This parameter has no value or a decimal integer value without
  90. # leading zeroes between 9 to 15 inclusive ..""
  91. # noinspection PySimplifyBooleanCheck
  92. if val is not True:
  93. try:
  94. val = int(val)
  95. except:
  96. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  97. else:
  98. if val not in PerMessageDeflateMixin.WINDOW_SIZE_PERMISSIBLE_VALUES:
  99. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  100. else:
  101. # FIXME (maybe): possibly forward/process the client hint!
  102. # accept_max_window_bits = val
  103. accept_max_window_bits = True
  104. else:
  105. accept_max_window_bits = True
  106. elif p == 'client_no_context_takeover':
  107. # noinspection PySimplifyBooleanCheck
  108. if val is not True:
  109. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  110. else:
  111. accept_no_context_takeover = True
  112. elif p == 'server_max_window_bits':
  113. try:
  114. val = int(val)
  115. except:
  116. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  117. else:
  118. if val not in PerMessageDeflateMixin.WINDOW_SIZE_PERMISSIBLE_VALUES:
  119. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  120. else:
  121. request_max_window_bits = val
  122. elif p == 'server_no_context_takeover':
  123. # noinspection PySimplifyBooleanCheck
  124. if val is not True:
  125. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  126. else:
  127. request_no_context_takeover = True
  128. else:
  129. raise Exception("illegal extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
  130. offer = cls(accept_no_context_takeover,
  131. accept_max_window_bits,
  132. request_no_context_takeover,
  133. request_max_window_bits)
  134. return offer
  135. def __init__(self,
  136. accept_no_context_takeover=True,
  137. accept_max_window_bits=True,
  138. request_no_context_takeover=False,
  139. request_max_window_bits=0):
  140. """
  141. :param accept_no_context_takeover: When ``True``, the client accepts the "no context takeover" feature.
  142. :type accept_no_context_takeover: bool
  143. :param accept_max_window_bits: When ``True``, the client accepts setting "max window size".
  144. :type accept_max_window_bits: bool
  145. :param request_no_context_takeover: When ``True``, the client request the "no context takeover" feature.
  146. :type request_no_context_takeover: bool
  147. :param request_max_window_bits: When non-zero, the client requests the given "max window size" (must be
  148. and integer from the interval ``[9..15]``).
  149. :type request_max_window_bits: int
  150. """
  151. if type(accept_no_context_takeover) != bool:
  152. raise Exception("invalid type %s for accept_no_context_takeover" % type(accept_no_context_takeover))
  153. self.accept_no_context_takeover = accept_no_context_takeover
  154. if type(accept_max_window_bits) != bool:
  155. raise Exception("invalid type %s for accept_max_window_bits" % type(accept_max_window_bits))
  156. self.accept_max_window_bits = accept_max_window_bits
  157. if type(request_no_context_takeover) != bool:
  158. raise Exception("invalid type %s for request_no_context_takeover" % type(request_no_context_takeover))
  159. self.request_no_context_takeover = request_no_context_takeover
  160. if request_max_window_bits != 0 and request_max_window_bits not in self.WINDOW_SIZE_PERMISSIBLE_VALUES:
  161. raise Exception("invalid value %s for request_max_window_bits - permissible values %s" % (request_max_window_bits, self.WINDOW_SIZE_PERMISSIBLE_VALUES))
  162. self.request_max_window_bits = request_max_window_bits
  163. def get_extension_string(self):
  164. """
  165. Returns the WebSocket extension configuration string as sent to the server.
  166. :returns: PMCE configuration string.
  167. :rtype: str
  168. """
  169. pmce_string = self.EXTENSION_NAME
  170. if self.accept_no_context_takeover:
  171. pmce_string += "; client_no_context_takeover"
  172. if self.accept_max_window_bits:
  173. pmce_string += "; client_max_window_bits"
  174. if self.request_no_context_takeover:
  175. pmce_string += "; server_no_context_takeover"
  176. if self.request_max_window_bits != 0:
  177. pmce_string += "; server_max_window_bits=%d" % self.request_max_window_bits
  178. return pmce_string
  179. def __json__(self):
  180. """
  181. Returns a JSON serializable object representation.
  182. :returns: JSON serializable representation.
  183. :rtype: dict
  184. """
  185. return {'extension': self.EXTENSION_NAME,
  186. 'accept_no_context_takeover': self.accept_no_context_takeover,
  187. 'accept_max_window_bits': self.accept_max_window_bits,
  188. 'request_no_context_takeover': self.request_no_context_takeover,
  189. 'request_max_window_bits': self.request_max_window_bits}
  190. def __repr__(self):
  191. """
  192. Returns Python object representation that can be eval'ed to reconstruct the object.
  193. :returns: Python string representation.
  194. :rtype: str
  195. """
  196. return "PerMessageDeflateOffer(accept_no_context_takeover = %s, accept_max_window_bits = %s, request_no_context_takeover = %s, request_max_window_bits = %s)" % (self.accept_no_context_takeover, self.accept_max_window_bits, self.request_no_context_takeover, self.request_max_window_bits)
  197. @public
  198. class PerMessageDeflateOfferAccept(PerMessageCompressOfferAccept, PerMessageDeflateMixin):
  199. """
  200. Set of parameters with which to accept an `permessage-deflate` offer
  201. from a client by a server.
  202. """
  203. def __init__(self,
  204. offer,
  205. request_no_context_takeover=False,
  206. request_max_window_bits=0,
  207. no_context_takeover=None,
  208. window_bits=None,
  209. mem_level=None,
  210. max_message_size=None):
  211. """
  212. :param offer: The offer being accepted.
  213. :type offer: Instance of :class:`autobahn.compress.PerMessageDeflateOffer`.
  214. :param request_no_context_takeover: When ``True``, the server requests the "no context takeover" feature.
  215. :type request_no_context_takeover: bool
  216. :param request_max_window_bits: When non-zero, the server requests the given "max window size" (must be
  217. and integer from the interval ``[9..15]``).
  218. :param request_max_window_bits: int
  219. :param no_context_takeover: Override server ("server-to-client direction") context takeover (this must
  220. be compatible with the offer).
  221. :type no_context_takeover: bool
  222. :param window_bits: Override server ("server-to-client direction") window size (this must be
  223. compatible with the offer).
  224. :type window_bits: int
  225. :param mem_level: Set server ("server-to-client direction") memory level.
  226. :type mem_level: int
  227. """
  228. if not isinstance(offer, PerMessageDeflateOffer):
  229. raise Exception("invalid type %s for offer" % type(offer))
  230. self.offer = offer
  231. if type(request_no_context_takeover) != bool:
  232. raise Exception("invalid type %s for request_no_context_takeover" % type(request_no_context_takeover))
  233. if request_no_context_takeover and not offer.accept_no_context_takeover:
  234. raise Exception("invalid value %s for request_no_context_takeover - feature unsupported by client" % request_no_context_takeover)
  235. self.request_no_context_takeover = request_no_context_takeover
  236. if request_max_window_bits != 0 and request_max_window_bits not in self.WINDOW_SIZE_PERMISSIBLE_VALUES:
  237. raise Exception("invalid value %s for request_max_window_bits - permissible values %s" % (request_max_window_bits, self.WINDOW_SIZE_PERMISSIBLE_VALUES))
  238. if request_max_window_bits != 0 and not offer.accept_max_window_bits:
  239. raise Exception("invalid value %s for request_max_window_bits - feature unsupported by client" % request_max_window_bits)
  240. self.request_max_window_bits = request_max_window_bits
  241. if no_context_takeover is not None:
  242. if type(no_context_takeover) != bool:
  243. raise Exception("invalid type %s for no_context_takeover" % type(no_context_takeover))
  244. if offer.request_no_context_takeover and not no_context_takeover:
  245. raise Exception("invalid value %s for no_context_takeover - client requested feature" % no_context_takeover)
  246. self.no_context_takeover = no_context_takeover
  247. if window_bits is not None:
  248. if window_bits not in self.WINDOW_SIZE_PERMISSIBLE_VALUES:
  249. raise Exception("invalid value %s for window_bits - permissible values %s" % (window_bits, self.WINDOW_SIZE_PERMISSIBLE_VALUES))
  250. if offer.request_max_window_bits != 0 and window_bits > offer.request_max_window_bits:
  251. raise Exception("invalid value %s for window_bits - client requested lower maximum value" % window_bits)
  252. self.window_bits = window_bits
  253. if mem_level is not None:
  254. if mem_level not in self.MEM_LEVEL_PERMISSIBLE_VALUES:
  255. raise Exception("invalid value %s for mem_level - permissible values %s" % (mem_level, self.MEM_LEVEL_PERMISSIBLE_VALUES))
  256. self.mem_level = mem_level
  257. self.max_message_size = max_message_size # clamp/check values..?
  258. def get_extension_string(self):
  259. """
  260. Returns the WebSocket extension configuration string as sent to the server.
  261. :returns: PMCE configuration string.
  262. :rtype: str
  263. """
  264. pmce_string = self.EXTENSION_NAME
  265. if self.offer.request_no_context_takeover:
  266. pmce_string += "; server_no_context_takeover"
  267. if self.offer.request_max_window_bits != 0:
  268. pmce_string += "; server_max_window_bits=%d" % self.offer.request_max_window_bits
  269. if self.request_no_context_takeover:
  270. pmce_string += "; client_no_context_takeover"
  271. if self.request_max_window_bits != 0:
  272. pmce_string += "; client_max_window_bits=%d" % self.request_max_window_bits
  273. return pmce_string
  274. def __json__(self):
  275. """
  276. Returns a JSON serializable object representation.
  277. :returns: JSON serializable representation.
  278. :rtype: dict
  279. """
  280. return {
  281. 'extension': self.EXTENSION_NAME,
  282. 'offer': self.offer.__json__(),
  283. 'request_no_context_takeover': self.request_no_context_takeover,
  284. 'request_max_window_bits': self.request_max_window_bits,
  285. 'no_context_takeover': self.no_context_takeover,
  286. 'window_bits': self.window_bits,
  287. 'mem_level': self.mem_level,
  288. 'max_message_size': self.max_message_size,
  289. }
  290. def __repr__(self):
  291. """
  292. Returns Python object representation that can be eval'ed to reconstruct the object.
  293. :returns: Python string representation.
  294. :rtype: str
  295. """
  296. return "PerMessageDeflateOfferAccept(offer = %s, request_no_context_takeover = %s, request_max_window_bits = %s, no_context_takeover = %s, window_bits = %s, mem_level = %s, max_message_size = %s)" % (self.offer.__repr__(), self.request_no_context_takeover, self.request_max_window_bits, self.no_context_takeover, self.window_bits, self.mem_level, self.max_message_size)
  297. @public
  298. class PerMessageDeflateResponse(PerMessageCompressResponse, PerMessageDeflateMixin):
  299. """
  300. Set of parameters for `permessage-deflate` responded by server.
  301. """
  302. @classmethod
  303. def parse(cls, params):
  304. """
  305. Parses a WebSocket extension response for `permessage-deflate` provided by a server to a client.
  306. :param params: Output from :func:`autobahn.websocket.WebSocketProtocol._parseExtensionsHeader`.
  307. :type params: list
  308. :returns: A new instance of :class:`autobahn.compress.PerMessageDeflateResponse`.
  309. :rtype: obj
  310. """
  311. client_max_window_bits = 0
  312. client_no_context_takeover = False
  313. server_max_window_bits = 0
  314. server_no_context_takeover = False
  315. for p in params:
  316. if len(params[p]) > 1:
  317. raise Exception("multiple occurrence of extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
  318. val = params[p][0]
  319. if p == 'client_max_window_bits':
  320. try:
  321. val = int(val)
  322. except:
  323. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  324. else:
  325. if val not in PerMessageDeflateMixin.WINDOW_SIZE_PERMISSIBLE_VALUES:
  326. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  327. else:
  328. client_max_window_bits = val
  329. elif p == 'client_no_context_takeover':
  330. # noinspection PySimplifyBooleanCheck
  331. if val is not True:
  332. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  333. else:
  334. client_no_context_takeover = True
  335. elif p == 'server_max_window_bits':
  336. try:
  337. val = int(val)
  338. except:
  339. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  340. else:
  341. if val not in PerMessageDeflateMixin.WINDOW_SIZE_PERMISSIBLE_VALUES:
  342. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  343. else:
  344. server_max_window_bits = val
  345. elif p == 'server_no_context_takeover':
  346. # noinspection PySimplifyBooleanCheck
  347. if val is not True:
  348. raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
  349. else:
  350. server_no_context_takeover = True
  351. else:
  352. raise Exception("illegal extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
  353. response = cls(client_max_window_bits,
  354. client_no_context_takeover,
  355. server_max_window_bits,
  356. server_no_context_takeover)
  357. return response
  358. def __init__(self,
  359. client_max_window_bits,
  360. client_no_context_takeover,
  361. server_max_window_bits,
  362. server_no_context_takeover):
  363. """
  364. :param client_max_window_bits: FIXME
  365. :type client_max_window_bits: int
  366. :param client_no_context_takeover: FIXME
  367. :type client_no_context_takeover: bool
  368. :param server_max_window_bits: FIXME
  369. :type server_max_window_bits: int
  370. :param server_no_context_takeover: FIXME
  371. :type server_no_context_takeover: bool
  372. """
  373. self.client_max_window_bits = client_max_window_bits
  374. self.client_no_context_takeover = client_no_context_takeover
  375. self.server_max_window_bits = server_max_window_bits
  376. self.server_no_context_takeover = server_no_context_takeover
  377. def __json__(self):
  378. """
  379. Returns a JSON serializable object representation.
  380. :returns: JSON serializable representation.
  381. :rtype: dict
  382. """
  383. return {'extension': self.EXTENSION_NAME,
  384. 'client_max_window_bits': self.client_max_window_bits,
  385. 'client_no_context_takeover': self.client_no_context_takeover,
  386. 'server_max_window_bits': self.server_max_window_bits,
  387. 'server_no_context_takeover': self.server_no_context_takeover}
  388. def __repr__(self):
  389. """
  390. Returns Python object representation that can be eval'ed to reconstruct the object.
  391. :returns: Python string representation.
  392. :rtype: str
  393. """
  394. return "PerMessageDeflateResponse(client_max_window_bits = %s, client_no_context_takeover = %s, server_max_window_bits = %s, server_no_context_takeover = %s)" % (self.client_max_window_bits, self.client_no_context_takeover, self.server_max_window_bits, self.server_no_context_takeover)
  395. @public
  396. class PerMessageDeflateResponseAccept(PerMessageCompressResponseAccept, PerMessageDeflateMixin):
  397. """
  398. Set of parameters with which to accept an `permessage-deflate` response
  399. from a server by a client.
  400. """
  401. def __init__(self,
  402. response,
  403. no_context_takeover=None,
  404. window_bits=None,
  405. mem_level=None,
  406. max_message_size=None):
  407. """
  408. :param response: The response being accepted.
  409. :type response: Instance of :class:`autobahn.compress.PerMessageDeflateResponse`.
  410. :param no_context_takeover: Override client ("client-to-server direction") context takeover (this must be compatible with response).
  411. :type no_context_takeover: bool
  412. :param window_bits: Override client ("client-to-server direction") window size (this must be compatible with response).
  413. :type window_bits: int
  414. :param mem_level: Set client ("client-to-server direction") memory level.
  415. :type mem_level: int
  416. """
  417. if not isinstance(response, PerMessageDeflateResponse):
  418. raise Exception("invalid type %s for response" % type(response))
  419. self.response = response
  420. if no_context_takeover is not None:
  421. if type(no_context_takeover) != bool:
  422. raise Exception("invalid type %s for no_context_takeover" % type(no_context_takeover))
  423. if response.client_no_context_takeover and not no_context_takeover:
  424. raise Exception("invalid value %s for no_context_takeover - server requested feature" % no_context_takeover)
  425. self.no_context_takeover = no_context_takeover
  426. if window_bits is not None:
  427. if window_bits not in self.WINDOW_SIZE_PERMISSIBLE_VALUES:
  428. raise Exception("invalid value %s for window_bits - permissible values %s" % (window_bits, self.WINDOW_SIZE_PERMISSIBLE_VALUES))
  429. if response.client_max_window_bits != 0 and window_bits > response.client_max_window_bits:
  430. raise Exception("invalid value %s for window_bits - server requested lower maximum value" % window_bits)
  431. self.window_bits = window_bits
  432. if mem_level is not None:
  433. if mem_level not in self.MEM_LEVEL_PERMISSIBLE_VALUES:
  434. raise Exception("invalid value %s for mem_level - permissible values %s" % (mem_level, self.MEM_LEVEL_PERMISSIBLE_VALUES))
  435. self.mem_level = mem_level
  436. self.max_message_size = max_message_size
  437. def __json__(self):
  438. """
  439. Returns a JSON serializable object representation.
  440. :returns: JSON serializable representation.
  441. :rtype: dict
  442. """
  443. return {'extension': self.EXTENSION_NAME,
  444. 'response': self.response.__json__(),
  445. 'no_context_takeover': self.no_context_takeover,
  446. 'window_bits': self.window_bits,
  447. 'mem_level': self.mem_level}
  448. def __repr__(self):
  449. """
  450. Returns Python object representation that can be eval'ed to reconstruct the object.
  451. :returns: Python string representation.
  452. :rtype: str
  453. """
  454. return "PerMessageDeflateResponseAccept(response = %s, no_context_takeover = %s, window_bits = %s, mem_level = %s)" % (self.response.__repr__(), self.no_context_takeover, self.window_bits, self.mem_level)
  455. # noinspection PyArgumentList
  456. class PerMessageDeflate(PerMessageCompress, PerMessageDeflateMixin):
  457. """
  458. `permessage-deflate` WebSocket extension processor.
  459. """
  460. DEFAULT_WINDOW_BITS = zlib.MAX_WBITS
  461. DEFAULT_MEM_LEVEL = 8
  462. @classmethod
  463. def create_from_response_accept(cls, is_server, accept):
  464. # accept: instance of PerMessageDeflateResponseAccept
  465. pmce = cls(
  466. is_server,
  467. accept.response.server_no_context_takeover,
  468. accept.no_context_takeover if accept.no_context_takeover is not None else accept.response.client_no_context_takeover,
  469. accept.response.server_max_window_bits,
  470. accept.window_bits if accept.window_bits is not None else accept.response.client_max_window_bits,
  471. accept.mem_level,
  472. accept.max_message_size,
  473. )
  474. return pmce
  475. @classmethod
  476. def create_from_offer_accept(cls, is_server, accept):
  477. # accept: instance of PerMessageDeflateOfferAccept
  478. pmce = cls(is_server,
  479. accept.no_context_takeover if accept.no_context_takeover is not None else accept.offer.request_no_context_takeover,
  480. accept.request_no_context_takeover,
  481. accept.window_bits if accept.window_bits is not None else accept.offer.request_max_window_bits,
  482. accept.request_max_window_bits,
  483. accept.mem_level,
  484. accept.max_message_size,)
  485. return pmce
  486. def __init__(self,
  487. is_server,
  488. server_no_context_takeover,
  489. client_no_context_takeover,
  490. server_max_window_bits,
  491. client_max_window_bits,
  492. mem_level,
  493. max_message_size=None):
  494. self._is_server = is_server
  495. self.server_no_context_takeover = server_no_context_takeover
  496. self.client_no_context_takeover = client_no_context_takeover
  497. self.server_max_window_bits = server_max_window_bits if server_max_window_bits != 0 else self.DEFAULT_WINDOW_BITS
  498. self.client_max_window_bits = client_max_window_bits if client_max_window_bits != 0 else self.DEFAULT_WINDOW_BITS
  499. self.mem_level = mem_level if mem_level else self.DEFAULT_MEM_LEVEL
  500. self.max_message_size = max_message_size # None means "no limit"
  501. self._compressor = None
  502. self._decompressor = None
  503. def __json__(self):
  504. return {'extension': self.EXTENSION_NAME,
  505. 'is_server': self._is_server,
  506. 'server_no_context_takeover': self.server_no_context_takeover,
  507. 'client_no_context_takeover': self.client_no_context_takeover,
  508. 'server_max_window_bits': self.server_max_window_bits,
  509. 'client_max_window_bits': self.client_max_window_bits,
  510. 'mem_level': self.mem_level}
  511. def __repr__(self):
  512. return "PerMessageDeflate(is_server = %s, server_no_context_takeover = %s, client_no_context_takeover = %s, server_max_window_bits = %s, client_max_window_bits = %s, mem_level = %s)" % (self._is_server, self.server_no_context_takeover, self.client_no_context_takeover, self.server_max_window_bits, self.client_max_window_bits, self.mem_level)
  513. def start_compress_message(self):
  514. # compressobj([level[, method[, wbits[, mem_level[, strategy]]]]])
  515. # http://bugs.python.org/issue19278
  516. # http://hg.python.org/cpython/rev/c54c8e71b79a
  517. if self._is_server:
  518. if self._compressor is None or self.server_no_context_takeover:
  519. self._compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -self.server_max_window_bits, self.mem_level)
  520. else:
  521. if self._compressor is None or self.client_no_context_takeover:
  522. self._compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -self.client_max_window_bits, self.mem_level)
  523. def compress_message_data(self, data):
  524. return self._compressor.compress(data)
  525. def end_compress_message(self):
  526. data = self._compressor.flush(zlib.Z_SYNC_FLUSH)
  527. return data[:-4]
  528. def start_decompress_message(self):
  529. if self._is_server:
  530. if self._decompressor is None or self.client_no_context_takeover:
  531. self._decompressor = zlib.decompressobj(-self.client_max_window_bits)
  532. else:
  533. if self._decompressor is None or self.server_no_context_takeover:
  534. self._decompressor = zlib.decompressobj(-self.server_max_window_bits)
  535. def decompress_message_data(self, data):
  536. if self.max_message_size is not None:
  537. return self._decompressor.decompress(data, self.max_message_size)
  538. return self._decompressor.decompress(data)
  539. def end_decompress_message(self):
  540. # Eat stripped LEN and NLEN field of a non-compressed block added
  541. # for Z_SYNC_FLUSH.
  542. self._decompressor.decompress(b'\x00\x00\xff\xff')