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.

serializer.py 36KB

1 year ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  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 os
  27. import re
  28. import struct
  29. import platform
  30. import math
  31. import decimal
  32. from binascii import b2a_hex, a2b_hex
  33. from typing import Optional, List, Tuple
  34. from txaio import time_ns
  35. from autobahn.wamp.interfaces import IObjectSerializer, ISerializer, IMessage
  36. from autobahn.wamp.exception import ProtocolError
  37. from autobahn.wamp import message
  38. # note: __all__ must be a list here, since we dynamically
  39. # extend it depending on availability of more serializers
  40. __all__ = ['Serializer',
  41. 'JsonObjectSerializer',
  42. 'JsonSerializer']
  43. SERID_TO_OBJSER = {}
  44. SERID_TO_SER = {}
  45. class Serializer(object):
  46. """
  47. Base class for WAMP serializers. A WAMP serializer is the core glue between
  48. parsed WAMP message objects and the bytes on wire (the transport).
  49. """
  50. RATED_MESSAGE_SIZE = 512
  51. """
  52. Serialized WAMP message payload size per rated WAMP message.
  53. """
  54. # WAMP defines the following 24 message types
  55. MESSAGE_TYPE_MAP = {
  56. message.Hello.MESSAGE_TYPE: message.Hello,
  57. message.Welcome.MESSAGE_TYPE: message.Welcome,
  58. message.Abort.MESSAGE_TYPE: message.Abort,
  59. message.Challenge.MESSAGE_TYPE: message.Challenge,
  60. message.Authenticate.MESSAGE_TYPE: message.Authenticate,
  61. message.Goodbye.MESSAGE_TYPE: message.Goodbye,
  62. message.Error.MESSAGE_TYPE: message.Error,
  63. message.Publish.MESSAGE_TYPE: message.Publish,
  64. message.Published.MESSAGE_TYPE: message.Published,
  65. message.Subscribe.MESSAGE_TYPE: message.Subscribe,
  66. message.Subscribed.MESSAGE_TYPE: message.Subscribed,
  67. message.Unsubscribe.MESSAGE_TYPE: message.Unsubscribe,
  68. message.Unsubscribed.MESSAGE_TYPE: message.Unsubscribed,
  69. message.Event.MESSAGE_TYPE: message.Event,
  70. message.EventReceived.MESSAGE_TYPE: message.EventReceived,
  71. message.Call.MESSAGE_TYPE: message.Call,
  72. message.Cancel.MESSAGE_TYPE: message.Cancel,
  73. message.Result.MESSAGE_TYPE: message.Result,
  74. message.Register.MESSAGE_TYPE: message.Register,
  75. message.Registered.MESSAGE_TYPE: message.Registered,
  76. message.Unregister.MESSAGE_TYPE: message.Unregister,
  77. message.Unregistered.MESSAGE_TYPE: message.Unregistered,
  78. message.Invocation.MESSAGE_TYPE: message.Invocation,
  79. message.Interrupt.MESSAGE_TYPE: message.Interrupt,
  80. message.Yield.MESSAGE_TYPE: message.Yield
  81. }
  82. """
  83. Mapping of WAMP message type codes to WAMP message classes.
  84. """
  85. def __init__(self, serializer):
  86. """
  87. :param serializer: The object serializer to use for WAMP wire-level serialization.
  88. :type serializer: An object that implements :class:`autobahn.interfaces.IObjectSerializer`.
  89. """
  90. self._serializer = serializer
  91. self._stats_reset = time_ns()
  92. self._stats_cycle = 0
  93. self._serialized_bytes = 0
  94. self._serialized_messages = 0
  95. self._serialized_rated_messages = 0
  96. self._unserialized_bytes = 0
  97. self._unserialized_messages = 0
  98. self._unserialized_rated_messages = 0
  99. self._autoreset_rated_messages = None
  100. self._autoreset_duration = None
  101. self._autoreset_callback = None
  102. def stats_reset(self):
  103. """
  104. Get serializer statistics: timestamp when statistics were last reset.
  105. :return: Last reset time of statistics (UTC, ns since Unix epoch)
  106. :rtype: int
  107. """
  108. return self._stats_reset
  109. def stats_bytes(self):
  110. """
  111. Get serializer statistics: bytes (serialized + unserialized).
  112. :return: Number of bytes.
  113. :rtype: int
  114. """
  115. return self._serialized_bytes + self._unserialized_bytes
  116. def stats_messages(self):
  117. """
  118. Get serializer statistics: messages (serialized + unserialized).
  119. :return: Number of messages.
  120. :rtype: int
  121. """
  122. return self._serialized_messages + self._unserialized_messages
  123. def stats_rated_messages(self):
  124. """
  125. Get serializer statistics: rated messages (serialized + unserialized).
  126. :return: Number of rated messages.
  127. :rtype: int
  128. """
  129. return self._serialized_rated_messages + self._unserialized_rated_messages
  130. def set_stats_autoreset(self, rated_messages, duration, callback, reset_now=False):
  131. """
  132. Configure a user callback invoked when accumulated stats hit specified threshold.
  133. When the specified number of rated messages have been processed or the specified duration
  134. has passed, statistics are automatically reset, and the last statistics is provided to
  135. the user callback.
  136. :param rated_messages: Number of rated messages that should trigger an auto-reset.
  137. :type rated_messages: int
  138. :param duration: Duration in ns that when passed will trigger an auto-reset.
  139. :type duration: int
  140. :param callback: User callback to be invoked when statistics are auto-reset. The function
  141. will be invoked with a single positional argument: the accumulated statistics before the reset.
  142. :type callback: callable
  143. """
  144. assert(rated_messages is None or type(rated_messages) == int)
  145. assert(duration is None or type(duration) == int)
  146. assert(rated_messages or duration)
  147. assert(callable(callback))
  148. self._autoreset_rated_messages = rated_messages
  149. self._autoreset_duration = duration
  150. self._autoreset_callback = callback
  151. # maybe auto-reset and trigger user callback ..
  152. if self._autoreset_callback and reset_now:
  153. stats = self.stats(reset=True)
  154. self._autoreset_callback(stats)
  155. return stats
  156. def stats(self, reset=True, details=False):
  157. """
  158. Get (and reset) serializer statistics.
  159. :param reset: If ``True``, reset the serializer statistics.
  160. :type reset: bool
  161. :param details: If ``True``, return detailed statistics split up by serialization/unserialization.
  162. :type details: bool
  163. :return: Serializer statistics, eg:
  164. .. code-block:: json
  165. {
  166. "timestamp": 1574156576688704693,
  167. "duration": 34000000000,
  168. "bytes": 0,
  169. "messages": 0,
  170. "rated_messages": 0
  171. }
  172. :rtype: dict
  173. """
  174. assert(type(reset) == bool)
  175. assert(type(details) == bool)
  176. self._stats_cycle += 1
  177. if details:
  178. data = {
  179. 'cycle': self._stats_cycle,
  180. 'serializer': self.SERIALIZER_ID,
  181. 'timestamp': self._stats_reset,
  182. 'duration': time_ns() - self._stats_reset,
  183. 'serialized': {
  184. 'bytes': self._serialized_bytes,
  185. 'messages': self._serialized_messages,
  186. 'rated_messages': self._serialized_rated_messages,
  187. },
  188. 'unserialized': {
  189. 'bytes': self._unserialized_bytes,
  190. 'messages': self._unserialized_messages,
  191. 'rated_messages': self._unserialized_rated_messages,
  192. }
  193. }
  194. else:
  195. data = {
  196. 'cycle': self._stats_cycle,
  197. 'serializer': self.SERIALIZER_ID,
  198. 'timestamp': self._stats_reset,
  199. 'duration': time_ns() - self._stats_reset,
  200. 'bytes': self._serialized_bytes + self._unserialized_bytes,
  201. 'messages': self._serialized_messages + self._unserialized_messages,
  202. 'rated_messages': self._serialized_rated_messages + self._unserialized_rated_messages,
  203. }
  204. if reset:
  205. self._serialized_bytes = 0
  206. self._serialized_messages = 0
  207. self._serialized_rated_messages = 0
  208. self._unserialized_bytes = 0
  209. self._unserialized_messages = 0
  210. self._unserialized_rated_messages = 0
  211. self._stats_reset = time_ns()
  212. return data
  213. def serialize(self, msg: IMessage) -> Tuple[bytes, bool]:
  214. """
  215. Implements :func:`autobahn.wamp.interfaces.ISerializer.serialize`
  216. """
  217. data, is_binary = msg.serialize(self._serializer), self._serializer.BINARY
  218. # maintain statistics for serialized WAMP message data
  219. self._serialized_bytes += len(data)
  220. self._serialized_messages += 1
  221. self._serialized_rated_messages += int(math.ceil(float(len(data)) / self.RATED_MESSAGE_SIZE))
  222. # maybe auto-reset and trigger user callback ..
  223. if self._autoreset_callback and ((self._autoreset_duration and (time_ns() - self._stats_reset) >= self._autoreset_duration) or (self._autoreset_rated_messages and self.stats_rated_messages() >= self._autoreset_rated_messages)):
  224. stats = self.stats(reset=True)
  225. self._autoreset_callback(stats)
  226. return data, is_binary
  227. def unserialize(self, payload: bytes, isBinary: Optional[bool] = None) -> List[IMessage]:
  228. """
  229. Implements :func:`autobahn.wamp.interfaces.ISerializer.unserialize`
  230. """
  231. if isBinary is not None:
  232. if isBinary != self._serializer.BINARY:
  233. raise ProtocolError(
  234. "invalid serialization of WAMP message (binary {0}, but expected {1})".format(isBinary,
  235. self._serializer.BINARY))
  236. try:
  237. raw_msgs = self._serializer.unserialize(payload)
  238. except Exception as e:
  239. raise ProtocolError("invalid serialization of WAMP message: {0} {1}".format(type(e).__name__, e))
  240. if self._serializer.NAME == 'flatbuffers':
  241. msgs = raw_msgs
  242. else:
  243. msgs = []
  244. for raw_msg in raw_msgs:
  245. if type(raw_msg) != list:
  246. raise ProtocolError("invalid type {0} for WAMP message".format(type(raw_msg)))
  247. if len(raw_msg) == 0:
  248. raise ProtocolError("missing message type in WAMP message")
  249. message_type = raw_msg[0]
  250. if type(message_type) != int:
  251. raise ProtocolError("invalid type {0} for WAMP message type".format(type(message_type)))
  252. Klass = self.MESSAGE_TYPE_MAP.get(message_type)
  253. if Klass is None:
  254. raise ProtocolError("invalid WAMP message type {0}".format(message_type))
  255. # this might again raise `ProtocolError` ..
  256. msg = Klass.parse(raw_msg)
  257. msgs.append(msg)
  258. # maintain statistics for unserialized WAMP message data
  259. self._unserialized_bytes += len(payload)
  260. self._unserialized_messages += len(msgs)
  261. self._unserialized_rated_messages += int(math.ceil(float(len(payload)) / self.RATED_MESSAGE_SIZE))
  262. # maybe auto-reset and trigger user callback ..
  263. if self._autoreset_callback and ((self._autoreset_duration and (time_ns() - self._stats_reset) >= self._autoreset_duration) or (self._autoreset_rated_messages and self.stats_rated_messages() >= self._autoreset_rated_messages)):
  264. stats = self.stats(reset=True)
  265. self._autoreset_callback(stats)
  266. return msgs
  267. # JSON serialization is always supported
  268. _USE_UJSON = 'AUTOBAHN_USE_UJSON' in os.environ
  269. if _USE_UJSON:
  270. try:
  271. import ujson
  272. _USE_UJSON = True
  273. except ImportError:
  274. import json
  275. _USE_UJSON = False
  276. else:
  277. import json
  278. if _USE_UJSON:
  279. # ujson doesn't support plugging into the JSON string parsing machinery ..
  280. print('WARNING: Autobahn is using ujson accelerated JSON module - will run faster,\nbut only on CPython and will loose ability to transport binary payload transparently!')
  281. _loads = ujson.loads
  282. _dumps = ujson.dumps
  283. _json = ujson
  284. else:
  285. # print('Notice: Autobahn is using json built-in standard library module for JSON serialization')
  286. import base64
  287. class _WAMPJsonEncoder(json.JSONEncoder):
  288. def __init__(self, *args, **kwargs):
  289. if 'use_binary_hex_encoding' in kwargs:
  290. self._use_binary_hex_encoding = kwargs['use_binary_hex_encoding']
  291. del kwargs['use_binary_hex_encoding']
  292. else:
  293. self._use_binary_hex_encoding = False
  294. json.JSONEncoder.__init__(self, *args, **kwargs)
  295. def default(self, obj):
  296. if isinstance(obj, bytes):
  297. if self._use_binary_hex_encoding:
  298. return '0x' + b2a_hex(obj).decode('ascii')
  299. else:
  300. return '\x00' + base64.b64encode(obj).decode('ascii')
  301. elif isinstance(obj, decimal.Decimal):
  302. return str(obj)
  303. else:
  304. return json.JSONEncoder.default(self, obj)
  305. #
  306. # the following is a hack. see http://bugs.python.org/issue29992
  307. #
  308. from json import scanner
  309. from json.decoder import scanstring
  310. _DEC_MATCH = re.compile(r'^[\+\-E\.0-9]+$')
  311. class _WAMPJsonDecoder(json.JSONDecoder):
  312. def __init__(self, *args, **kwargs):
  313. if 'use_binary_hex_encoding' in kwargs:
  314. self._use_binary_hex_encoding = kwargs['use_binary_hex_encoding']
  315. del kwargs['use_binary_hex_encoding']
  316. else:
  317. self._use_binary_hex_encoding = False
  318. if 'use_decimal_from_str' in kwargs:
  319. self._use_decimal_from_str = kwargs['use_decimal_from_str']
  320. del kwargs['use_decimal_from_str']
  321. else:
  322. self._use_decimal_from_str = False
  323. if 'use_decimal_from_float' in kwargs:
  324. self._use_decimal_from_float = kwargs['use_decimal_from_float']
  325. del kwargs['use_decimal_from_float']
  326. if self._use_decimal_from_float:
  327. kwargs['parse_float'] = decimal.Decimal
  328. else:
  329. self._use_decimal_from_str = False
  330. json.JSONDecoder.__init__(self, *args, **kwargs)
  331. def _parse_string(*args, **kwargs):
  332. s, idx = scanstring(*args, **kwargs)
  333. if self._use_binary_hex_encoding:
  334. if s and s[0:2] == '0x':
  335. s = a2b_hex(s[2:])
  336. return s, idx
  337. else:
  338. if s and s[0] == '\x00':
  339. s = base64.b64decode(s[1:])
  340. return s, idx
  341. if self._use_decimal_from_str and _DEC_MATCH.match(s):
  342. try:
  343. s = decimal.Decimal(s)
  344. return s, idx
  345. except decimal.InvalidOperation:
  346. pass
  347. return s, idx
  348. self.parse_string = _parse_string
  349. # we need to recreate the internal scan function ..
  350. self.scan_once = scanner.py_make_scanner(self)
  351. # .. and we have to explicitly use the Py version,
  352. # not the C version, as the latter won't work
  353. # self.scan_once = scanner.make_scanner(self)
  354. def _loads(s, use_binary_hex_encoding=False, use_decimal_from_str=False, use_decimal_from_float=False):
  355. return json.loads(s,
  356. use_binary_hex_encoding=use_binary_hex_encoding,
  357. use_decimal_from_str=use_decimal_from_str,
  358. use_decimal_from_float=use_decimal_from_float,
  359. cls=_WAMPJsonDecoder)
  360. def _dumps(obj, use_binary_hex_encoding=False):
  361. return json.dumps(obj,
  362. separators=(',', ':'),
  363. ensure_ascii=False,
  364. sort_keys=False,
  365. use_binary_hex_encoding=use_binary_hex_encoding,
  366. cls=_WAMPJsonEncoder)
  367. _json = json
  368. class JsonObjectSerializer(object):
  369. JSON_MODULE = _json
  370. """
  371. The JSON module used (now only stdlib).
  372. """
  373. NAME = 'json'
  374. BINARY = False
  375. def __init__(self, batched=False, use_binary_hex_encoding=False, use_decimal_from_str=False, use_decimal_from_float=False):
  376. """
  377. :param batched: Flag that controls whether serializer operates in batched mode.
  378. :type batched: bool
  379. :param use_binary_hex_encoding: Flag to enable HEX encoding prefixed with ``"0x"``,
  380. otherwise prefix binaries with a ``\0`` byte.
  381. :type use_binary_hex_encoding: bool
  382. :param use_decimal_from_str: Flag to automatically encode Decimals as strings, and
  383. to try to parse strings as Decimals.
  384. :type use_decimal_from_str: bool
  385. """
  386. self._batched = batched
  387. self._use_binary_hex_encoding = use_binary_hex_encoding
  388. self._use_decimal_from_str = use_decimal_from_str
  389. self._use_decimal_from_float = use_decimal_from_float
  390. def serialize(self, obj):
  391. """
  392. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.serialize`
  393. """
  394. s = _dumps(obj, use_binary_hex_encoding=self._use_binary_hex_encoding)
  395. if isinstance(s, str):
  396. s = s.encode('utf8')
  397. if self._batched:
  398. return s + b'\30'
  399. else:
  400. return s
  401. def unserialize(self, payload):
  402. """
  403. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.unserialize`
  404. """
  405. if self._batched:
  406. chunks = payload.split(b'\30')[:-1]
  407. else:
  408. chunks = [payload]
  409. if len(chunks) == 0:
  410. raise Exception("batch format error")
  411. return [_loads(data.decode('utf8'),
  412. use_binary_hex_encoding=self._use_binary_hex_encoding,
  413. use_decimal_from_str=self._use_decimal_from_str,
  414. use_decimal_from_float=self._use_decimal_from_float) for data in chunks]
  415. IObjectSerializer.register(JsonObjectSerializer)
  416. SERID_TO_OBJSER[JsonObjectSerializer.NAME] = JsonObjectSerializer
  417. class JsonSerializer(Serializer):
  418. SERIALIZER_ID = "json"
  419. """
  420. ID used as part of the WebSocket subprotocol name to identify the
  421. serializer with WAMP-over-WebSocket.
  422. """
  423. RAWSOCKET_SERIALIZER_ID = 1
  424. """
  425. ID used in lower four bits of second octet in RawSocket opening
  426. handshake identify the serializer with WAMP-over-RawSocket.
  427. """
  428. MIME_TYPE = "application/json"
  429. """
  430. MIME type announced in HTTP request/response headers when running
  431. WAMP-over-Longpoll HTTP fallback.
  432. """
  433. def __init__(self, batched=False, use_binary_hex_encoding=False, use_decimal_from_str=False):
  434. """
  435. Ctor.
  436. :param batched: Flag to control whether to put this serialized into batched mode.
  437. :type batched: bool
  438. """
  439. Serializer.__init__(self, JsonObjectSerializer(batched=batched,
  440. use_binary_hex_encoding=use_binary_hex_encoding,
  441. use_decimal_from_str=use_decimal_from_str))
  442. if batched:
  443. self.SERIALIZER_ID = "json.batched"
  444. ISerializer.register(JsonSerializer)
  445. SERID_TO_SER[JsonSerializer.SERIALIZER_ID] = JsonSerializer
  446. _HAS_MSGPACK = False
  447. _USE_UMSGPACK = platform.python_implementation() == 'PyPy' or 'AUTOBAHN_USE_UMSGPACK' in os.environ
  448. if not _USE_UMSGPACK:
  449. try:
  450. # on CPython, use an impl. with native extension:
  451. # https://pypi.org/project/msgpack/
  452. # https://github.com/msgpack/msgpack-python
  453. import msgpack
  454. except ImportError:
  455. pass
  456. else:
  457. _HAS_MSGPACK = True
  458. _packb = lambda obj: msgpack.packb(obj, use_bin_type=True) # noqa
  459. _unpackb = lambda data: msgpack.unpackb(data, raw=False) # noqa
  460. _msgpack = msgpack
  461. # print('Notice: Autobahn is using msgpack library (with native extension, best on CPython) for MessagePack serialization')
  462. else:
  463. try:
  464. # on PyPy in particular, use a pure python impl.:
  465. # https://pypi.python.org/pypi/u-msgpack-python
  466. # https://github.com/vsergeev/u-msgpack-python
  467. import umsgpack
  468. except ImportError:
  469. pass
  470. else:
  471. _HAS_MSGPACK = True
  472. _packb = umsgpack.packb
  473. _unpackb = umsgpack.unpackb
  474. _msgpack = umsgpack
  475. # print('Notice: Autobahn is using umsgpack library (pure Python, best on PyPy) for MessagePack serialization')
  476. if _HAS_MSGPACK:
  477. class MsgPackObjectSerializer(object):
  478. NAME = 'msgpack'
  479. MSGPACK_MODULE = _msgpack
  480. BINARY = True
  481. """
  482. Flag that indicates whether this serializer needs a binary clean transport.
  483. """
  484. def __init__(self, batched=False):
  485. """
  486. :param batched: Flag that controls whether serializer operates in batched mode.
  487. :type batched: bool
  488. """
  489. self._batched = batched
  490. def serialize(self, obj):
  491. """
  492. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.serialize`
  493. """
  494. data = _packb(obj)
  495. if self._batched:
  496. return struct.pack("!L", len(data)) + data
  497. else:
  498. return data
  499. def unserialize(self, payload):
  500. """
  501. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.unserialize`
  502. """
  503. if self._batched:
  504. msgs = []
  505. N = len(payload)
  506. i = 0
  507. while i < N:
  508. # read message length prefix
  509. if i + 4 > N:
  510. raise Exception("batch format error [1]")
  511. l = struct.unpack("!L", payload[i:i + 4])[0]
  512. # read message data
  513. if i + 4 + l > N:
  514. raise Exception("batch format error [2]")
  515. data = payload[i + 4:i + 4 + l]
  516. # append parsed raw message
  517. msgs.append(_unpackb(data))
  518. # advance until everything consumed
  519. i = i + 4 + l
  520. if i != N:
  521. raise Exception("batch format error [3]")
  522. return msgs
  523. else:
  524. unpacked = _unpackb(payload)
  525. return [unpacked]
  526. IObjectSerializer.register(MsgPackObjectSerializer)
  527. __all__.append('MsgPackObjectSerializer')
  528. SERID_TO_OBJSER[MsgPackObjectSerializer.NAME] = MsgPackObjectSerializer
  529. class MsgPackSerializer(Serializer):
  530. SERIALIZER_ID = "msgpack"
  531. """
  532. ID used as part of the WebSocket subprotocol name to identify the
  533. serializer with WAMP-over-WebSocket.
  534. """
  535. RAWSOCKET_SERIALIZER_ID = 2
  536. """
  537. ID used in lower four bits of second octet in RawSocket opening
  538. handshake identify the serializer with WAMP-over-RawSocket.
  539. """
  540. MIME_TYPE = "application/x-msgpack"
  541. """
  542. MIME type announced in HTTP request/response headers when running
  543. WAMP-over-Longpoll HTTP fallback.
  544. """
  545. def __init__(self, batched=False):
  546. """
  547. Ctor.
  548. :param batched: Flag to control whether to put this serialized into batched mode.
  549. :type batched: bool
  550. """
  551. Serializer.__init__(self, MsgPackObjectSerializer(batched=batched))
  552. if batched:
  553. self.SERIALIZER_ID = "msgpack.batched"
  554. ISerializer.register(MsgPackSerializer)
  555. SERID_TO_SER[MsgPackSerializer.SERIALIZER_ID] = MsgPackSerializer
  556. __all__.append('MsgPackSerializer')
  557. _HAS_CBOR = False
  558. try:
  559. import cbor2
  560. except ImportError:
  561. pass
  562. else:
  563. _HAS_CBOR = True
  564. _cbor_loads = cbor2.loads
  565. _cbor_dumps = cbor2.dumps
  566. _cbor = cbor2
  567. if _HAS_CBOR:
  568. class CBORObjectSerializer(object):
  569. """
  570. CBOR serializer based on `cbor2 <https://github.com/agronholm/cbor2>`_.
  571. This CBOR serializer has proper support for arbitrary precision decimals,
  572. via tagged decimal fraction encoding, as described in
  573. `RFC7049 section 2.4.3 <https://datatracker.ietf.org/doc/html/rfc7049#section-2.4.3>`_.
  574. """
  575. NAME = 'cbor'
  576. CBOR_MODULE = _cbor
  577. BINARY = True
  578. """
  579. Flag that indicates whether this serializer needs a binary clean transport.
  580. """
  581. def __init__(self, batched=False):
  582. """
  583. :param batched: Flag that controls whether serializer operates in batched mode.
  584. :type batched: bool
  585. """
  586. self._batched = batched
  587. def serialize(self, obj):
  588. """
  589. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.serialize`
  590. """
  591. data = _cbor_dumps(obj)
  592. if self._batched:
  593. return struct.pack("!L", len(data)) + data
  594. else:
  595. return data
  596. def unserialize(self, payload):
  597. """
  598. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.unserialize`
  599. """
  600. if self._batched:
  601. msgs = []
  602. N = len(payload)
  603. i = 0
  604. while i < N:
  605. # read message length prefix
  606. if i + 4 > N:
  607. raise Exception("batch format error [1]")
  608. l = struct.unpack("!L", payload[i:i + 4])[0]
  609. # read message data
  610. if i + 4 + l > N:
  611. raise Exception("batch format error [2]")
  612. data = payload[i + 4:i + 4 + l]
  613. # append parsed raw message
  614. msgs.append(_cbor_loads(data))
  615. # advance until everything consumed
  616. i = i + 4 + l
  617. if i != N:
  618. raise Exception("batch format error [3]")
  619. return msgs
  620. else:
  621. unpacked = _cbor_loads(payload)
  622. return [unpacked]
  623. IObjectSerializer.register(CBORObjectSerializer)
  624. SERID_TO_OBJSER[CBORObjectSerializer.NAME] = CBORObjectSerializer
  625. __all__.append('CBORObjectSerializer')
  626. class CBORSerializer(Serializer):
  627. SERIALIZER_ID = "cbor"
  628. """
  629. ID used as part of the WebSocket subprotocol name to identify the
  630. serializer with WAMP-over-WebSocket.
  631. """
  632. RAWSOCKET_SERIALIZER_ID = 3
  633. """
  634. ID used in lower four bits of second octet in RawSocket opening
  635. handshake identify the serializer with WAMP-over-RawSocket.
  636. """
  637. MIME_TYPE = "application/cbor"
  638. """
  639. MIME type announced in HTTP request/response headers when running
  640. WAMP-over-Longpoll HTTP fallback.
  641. """
  642. def __init__(self, batched=False):
  643. """
  644. Ctor.
  645. :param batched: Flag to control whether to put this serialized into batched mode.
  646. :type batched: bool
  647. """
  648. Serializer.__init__(self, CBORObjectSerializer(batched=batched))
  649. if batched:
  650. self.SERIALIZER_ID = "cbor.batched"
  651. ISerializer.register(CBORSerializer)
  652. SERID_TO_SER[CBORSerializer.SERIALIZER_ID] = CBORSerializer
  653. __all__.append('CBORSerializer')
  654. # UBJSON serialization depends on the `py-ubjson` package being available
  655. # https://pypi.python.org/pypi/py-ubjson
  656. # https://github.com/Iotic-Labs/py-ubjson
  657. try:
  658. import ubjson
  659. except ImportError:
  660. pass
  661. else:
  662. # print('Notice: Autobahn is using ubjson module for UBJSON serialization')
  663. class UBJSONObjectSerializer(object):
  664. NAME = 'ubjson'
  665. UBJSON_MODULE = ubjson
  666. BINARY = True
  667. """
  668. Flag that indicates whether this serializer needs a binary clean transport.
  669. """
  670. def __init__(self, batched=False):
  671. """
  672. :param batched: Flag that controls whether serializer operates in batched mode.
  673. :type batched: bool
  674. """
  675. self._batched = batched
  676. def serialize(self, obj):
  677. """
  678. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.serialize`
  679. """
  680. data = ubjson.dumpb(obj)
  681. if self._batched:
  682. return struct.pack("!L", len(data)) + data
  683. else:
  684. return data
  685. def unserialize(self, payload):
  686. """
  687. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.unserialize`
  688. """
  689. if self._batched:
  690. msgs = []
  691. N = len(payload)
  692. i = 0
  693. while i < N:
  694. # read message length prefix
  695. if i + 4 > N:
  696. raise Exception("batch format error [1]")
  697. l = struct.unpack("!L", payload[i:i + 4])[0]
  698. # read message data
  699. if i + 4 + l > N:
  700. raise Exception("batch format error [2]")
  701. data = payload[i + 4:i + 4 + l]
  702. # append parsed raw message
  703. msgs.append(ubjson.loadb(data))
  704. # advance until everything consumed
  705. i = i + 4 + l
  706. if i != N:
  707. raise Exception("batch format error [3]")
  708. return msgs
  709. else:
  710. unpacked = ubjson.loadb(payload)
  711. return [unpacked]
  712. IObjectSerializer.register(UBJSONObjectSerializer)
  713. SERID_TO_OBJSER[UBJSONObjectSerializer.NAME] = UBJSONObjectSerializer
  714. __all__.append('UBJSONObjectSerializer')
  715. class UBJSONSerializer(Serializer):
  716. SERIALIZER_ID = "ubjson"
  717. """
  718. ID used as part of the WebSocket subprotocol name to identify the
  719. serializer with WAMP-over-WebSocket.
  720. """
  721. RAWSOCKET_SERIALIZER_ID = 4
  722. """
  723. ID used in lower four bits of second octet in RawSocket opening
  724. handshake identify the serializer with WAMP-over-RawSocket.
  725. """
  726. MIME_TYPE = "application/ubjson"
  727. """
  728. MIME type announced in HTTP request/response headers when running
  729. WAMP-over-Longpoll HTTP fallback.
  730. """
  731. def __init__(self, batched=False):
  732. """
  733. Ctor.
  734. :param batched: Flag to control whether to put this serialized into batched mode.
  735. :type batched: bool
  736. """
  737. Serializer.__init__(self, UBJSONObjectSerializer(batched=batched))
  738. if batched:
  739. self.SERIALIZER_ID = "ubjson.batched"
  740. ISerializer.register(UBJSONSerializer)
  741. SERID_TO_SER[UBJSONSerializer.SERIALIZER_ID] = UBJSONSerializer
  742. __all__.append('UBJSONSerializer')
  743. _HAS_FLATBUFFERS = False
  744. try:
  745. import flatbuffers # noqa
  746. from autobahn.wamp import message_fbs
  747. except ImportError:
  748. pass
  749. else:
  750. _HAS_FLATBUFFERS = True
  751. if _HAS_FLATBUFFERS:
  752. class FlatBuffersObjectSerializer(object):
  753. NAME = 'flatbuffers'
  754. FLATBUFFERS_MODULE = flatbuffers
  755. BINARY = True
  756. """
  757. Flag that indicates whether this serializer needs a binary clean transport.
  758. """
  759. MESSAGE_TYPE_MAP = {
  760. message_fbs.MessageType.EVENT: (message_fbs.Event, message.Event),
  761. message_fbs.MessageType.PUBLISH: (message_fbs.Publish, message.Publish),
  762. }
  763. def __init__(self, batched=False):
  764. """
  765. :param batched: Flag that controls whether serializer operates in batched mode.
  766. :type batched: bool
  767. """
  768. assert not batched, 'WAMP-FlatBuffers serialization does not support message batching currently'
  769. self._batched = batched
  770. def serialize(self, obj):
  771. """
  772. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.serialize`
  773. """
  774. raise NotImplementedError()
  775. def unserialize(self, payload):
  776. """
  777. Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.unserialize`
  778. """
  779. union_msg = message_fbs.Message.Message.GetRootAsMessage(payload, 0)
  780. msg_type = union_msg.MsgType()
  781. if msg_type in self.MESSAGE_TYPE_MAP:
  782. fbs_klass, wamp_klass = self.MESSAGE_TYPE_MAP[msg_type]
  783. fbs_msg = fbs_klass()
  784. _tab = union_msg.Msg()
  785. fbs_msg.Init(_tab.Bytes, _tab.Pos)
  786. msg = wamp_klass(from_fbs=fbs_msg)
  787. return [msg]
  788. else:
  789. raise NotImplementedError('message type {} not yet implemented for WAMP-FlatBuffers'.format(msg_type))
  790. IObjectSerializer.register(FlatBuffersObjectSerializer)
  791. __all__.append('FlatBuffersObjectSerializer')
  792. SERID_TO_OBJSER[FlatBuffersObjectSerializer.NAME] = FlatBuffersObjectSerializer
  793. class FlatBuffersSerializer(Serializer):
  794. SERIALIZER_ID = "flatbuffers"
  795. """
  796. ID used as part of the WebSocket subprotocol name to identify the
  797. serializer with WAMP-over-WebSocket.
  798. """
  799. RAWSOCKET_SERIALIZER_ID = 5
  800. """
  801. ID used in lower four bits of second octet in RawSocket opening
  802. handshake identify the serializer with WAMP-over-RawSocket.
  803. """
  804. MIME_TYPE = "application/x-flatbuffers"
  805. """
  806. MIME type announced in HTTP request/response headers when running
  807. WAMP-over-Longpoll HTTP fallback.
  808. """
  809. def __init__(self, batched=False):
  810. """
  811. :param batched: Flag to control whether to put this serialized into batched mode.
  812. :type batched: bool
  813. """
  814. Serializer.__init__(self, FlatBuffersObjectSerializer(batched=batched))
  815. if batched:
  816. self.SERIALIZER_ID = "flatbuffers.batched"
  817. ISerializer.register(FlatBuffersSerializer)
  818. SERID_TO_SER[FlatBuffersSerializer.SERIALIZER_ID] = FlatBuffersSerializer
  819. __all__.append('FlatBuffersSerializer')
  820. def create_transport_serializer(serializer_id):
  821. batched = False
  822. if '.' in serializer_id:
  823. l = serializer_id.split('.')
  824. serializer_id = l[0]
  825. if len(l) > 1 and l[1] == 'batched':
  826. batched = True
  827. if serializer_id in SERID_TO_SER:
  828. return SERID_TO_SER[serializer_id](batched=batched)
  829. else:
  830. raise RuntimeError('could not create serializer for "{}" (available: {})'.format(serializer_id, sorted(SERID_TO_SER.keys())))
  831. def create_transport_serializers(transport):
  832. """
  833. Create a list of serializers to use with a WAMP protocol factory.
  834. """
  835. serializers = []
  836. for serializer_id in transport.serializers:
  837. serializers.append(create_transport_serializer(serializer_id))
  838. return serializers