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.

rawsocket.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  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 copy
  27. import math
  28. from typing import Optional
  29. import txaio
  30. from twisted.internet.protocol import Factory
  31. from twisted.protocols.basic import Int32StringReceiver
  32. from twisted.internet.error import ConnectionDone
  33. from twisted.internet.defer import CancelledError
  34. from autobahn.util import public, _LazyHexFormatter
  35. from autobahn.twisted.util import create_transport_details, transport_channel_id
  36. from autobahn.wamp.types import TransportDetails
  37. from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost, InvalidUriError
  38. from autobahn.exception import PayloadExceededError
  39. __all__ = (
  40. 'WampRawSocketServerProtocol',
  41. 'WampRawSocketClientProtocol',
  42. 'WampRawSocketServerFactory',
  43. 'WampRawSocketClientFactory'
  44. )
  45. class WampRawSocketProtocol(Int32StringReceiver):
  46. """
  47. Base class for Twisted-based WAMP-over-RawSocket protocols.
  48. """
  49. log = txaio.make_logger()
  50. peer: Optional[str] = None
  51. is_server: Optional[bool] = None
  52. def __init__(self):
  53. # set the RawSocket maximum message size by default
  54. self._max_message_size = 2**24
  55. self._transport_details = None
  56. @property
  57. def transport_details(self) -> Optional[TransportDetails]:
  58. """
  59. Implements :func:`autobahn.wamp.interfaces.ITransport.transport_details`
  60. """
  61. return self._transport_details
  62. def lengthLimitExceeded(self, length):
  63. # override hook in Int32StringReceiver base class that is fired when a message is (to be) received
  64. # that is larger than what we agreed to handle (by negotiation in the RawSocket opening handshake)
  65. emsg = 'RawSocket connection: length of received message exceeded (message was {} bytes, but current maximum is {} bytes)'.format(length, self.MAX_LENGTH)
  66. raise PayloadExceededError(emsg)
  67. def connectionMade(self):
  68. # Twisted networking framework entry point, called by Twisted
  69. # when the connection is established (either a client or a server)
  70. # determine preliminary transport details (what is know at this point)
  71. self._transport_details = create_transport_details(self.transport, self.is_server)
  72. self._transport_details.channel_framing = TransportDetails.CHANNEL_FRAMING_RAWSOCKET
  73. # backward compatibility
  74. self.peer = self._transport_details.peer
  75. # a Future/Deferred that fires when we hit STATE_CLOSED
  76. self.is_closed = txaio.create_future()
  77. # this will hold an ApplicationSession object
  78. # once the RawSocket opening handshake has been
  79. # completed
  80. #
  81. self._session = None
  82. # Will hold the negotiated serializer once the opening handshake is complete
  83. #
  84. self._serializer = None
  85. # Will be set to True once the opening handshake is complete
  86. #
  87. self._handshake_complete = False
  88. # Buffer for opening handshake received bytes.
  89. #
  90. self._handshake_bytes = b''
  91. # Peer requested to _receive_ this maximum length of serialized messages - hence we must not send larger msgs!
  92. #
  93. self._max_len_send = None
  94. def _on_handshake_complete(self):
  95. # RawSocket connection established. Now let the user WAMP session factory
  96. # create a new WAMP session and fire off session open callback.
  97. try:
  98. if self._transport_details.is_secure:
  99. # now that the TLS opening handshake is complete, the actual TLS channel ID
  100. # will be available. make sure to set it!
  101. channel_id = {
  102. 'tls-unique': transport_channel_id(self.transport, self._transport_details.is_server, 'tls-unique'),
  103. }
  104. self._transport_details.channel_id = channel_id
  105. self._session = self.factory._factory()
  106. self.log.debug('{klass}._on_handshake_complete(): calling {method}', session=self._session,
  107. klass=self.__class__.__name__, method=self._session.onOpen)
  108. res = self._session.onOpen(self)
  109. except Exception as e:
  110. # Exceptions raised in onOpen are fatal ..
  111. self.log.warn("{klass}._on_handshake_complete(): ApplicationSession constructor / onOpen raised ({err})",
  112. klass=self.__class__.__name__, err=e)
  113. self.abort()
  114. else:
  115. self.log.debug('{klass}._on_handshake_complete(): {session} started (res={res}).', klass=self.__class__.__name__,
  116. session=self._session, res=res)
  117. def connectionLost(self, reason):
  118. self.log.debug('{klass}.connectionLost(reason="{reason}"', klass=self.__class__.__name__, reason=reason)
  119. txaio.resolve(self.is_closed, self)
  120. try:
  121. wasClean = isinstance(reason.value, ConnectionDone)
  122. if self._session:
  123. self._session.onClose(wasClean)
  124. except Exception as e:
  125. # silently ignore exceptions raised here ..
  126. self.log.warn('{klass}.connectionLost(): ApplicationSession.onClose raised "{err}"',
  127. klass=self.__class__.__name__, err=e)
  128. self._session = None
  129. def stringReceived(self, payload):
  130. self.log.trace('{klass}.stringReceived(): RX {octets} octets',
  131. klass=self.__class__.__name__, octets=_LazyHexFormatter(payload))
  132. try:
  133. for msg in self._serializer.unserialize(payload):
  134. self.log.trace("{klass}.stringReceived: RX WAMP message: {msg}",
  135. klass=self.__class__.__name__, msg=msg)
  136. self._session.onMessage(msg)
  137. except CancelledError as e:
  138. self.log.debug("{klass}.stringReceived: WAMP CancelledError - connection will continue!\n{err}",
  139. klass=self.__class__.__name__,
  140. err=e)
  141. except InvalidUriError as e:
  142. self.log.warn("{klass}.stringReceived: WAMP InvalidUriError - aborting connection!\n{err}",
  143. klass=self.__class__.__name__,
  144. err=e)
  145. self.abort()
  146. except ProtocolError as e:
  147. self.log.warn("{klass}.stringReceived: WAMP ProtocolError - aborting connection!\n{err}",
  148. klass=self.__class__.__name__,
  149. err=e)
  150. self.abort()
  151. except PayloadExceededError as e:
  152. self.log.warn("{klass}.stringReceived: WAMP PayloadExceededError - aborting connection!\n{err}",
  153. klass=self.__class__.__name__,
  154. err=e)
  155. self.abort()
  156. except SerializationError as e:
  157. self.log.warn("{klass}.stringReceived: WAMP SerializationError - aborting connection!\n{err}",
  158. klass=self.__class__.__name__,
  159. err=e)
  160. self.abort()
  161. except Exception as e:
  162. self.log.failure()
  163. self.log.warn("{klass}.stringReceived: WAMP Exception - aborting connection!\n{err}",
  164. klass=self.__class__.__name__,
  165. err=e)
  166. self.abort()
  167. def send(self, msg):
  168. """
  169. Implements :func:`autobahn.wamp.interfaces.ITransport.send`
  170. """
  171. if self.isOpen():
  172. self.log.trace('{klass}.send() (serializer={serializer}): TX WAMP message: "{msg}"',
  173. klass=self.__class__.__name__, msg=msg, serializer=self._serializer)
  174. try:
  175. payload, _ = self._serializer.serialize(msg)
  176. except SerializationError as e:
  177. # all exceptions raised from above should be serialization errors ..
  178. raise SerializationError("WampRawSocketProtocol: unable to serialize WAMP application payload ({0})".format(e))
  179. else:
  180. payload_len = len(payload)
  181. if 0 < self._max_len_send < payload_len:
  182. emsg = 'tried to send RawSocket message with size {} exceeding payload limit of {} octets'.format(
  183. payload_len, self._max_len_send)
  184. self.log.warn(emsg)
  185. raise PayloadExceededError(emsg)
  186. else:
  187. self.sendString(payload)
  188. self.log.trace('{klass}.send(): TX {octets} octets',
  189. klass=self.__class__.__name__, octets=_LazyHexFormatter(payload))
  190. else:
  191. raise TransportLost()
  192. def isOpen(self):
  193. """
  194. Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen`
  195. """
  196. return self._session is not None
  197. def close(self):
  198. """
  199. Implements :func:`autobahn.wamp.interfaces.ITransport.close`
  200. """
  201. if self.isOpen():
  202. self.transport.loseConnection()
  203. else:
  204. raise TransportLost()
  205. def abort(self):
  206. """
  207. Implements :func:`autobahn.wamp.interfaces.ITransport.abort`
  208. """
  209. if self.isOpen():
  210. if hasattr(self.transport, 'abortConnection'):
  211. # ProcessProtocol lacks abortConnection()
  212. self.transport.abortConnection()
  213. else:
  214. self.transport.loseConnection()
  215. else:
  216. raise TransportLost()
  217. @public
  218. class WampRawSocketServerProtocol(WampRawSocketProtocol):
  219. """
  220. Twisted-based WAMP-over-RawSocket server protocol.
  221. Implements:
  222. * :class:`autobahn.wamp.interfaces.ITransport`
  223. """
  224. def dataReceived(self, data):
  225. if self._handshake_complete:
  226. WampRawSocketProtocol.dataReceived(self, data)
  227. else:
  228. remaining = 4 - len(self._handshake_bytes)
  229. self._handshake_bytes += data[:remaining]
  230. if len(self._handshake_bytes) == 4:
  231. self.log.debug(
  232. "WampRawSocketServerProtocol: opening handshake received - 0x{octets}",
  233. octets=_LazyHexFormatter(self._handshake_bytes),
  234. )
  235. # first octet must be magic octet 0x7f
  236. #
  237. _magic = ord(self._handshake_bytes[0:1])
  238. if _magic != 127:
  239. self.log.warn(
  240. "WampRawSocketServerProtocol: invalid magic byte (octet 1) in"
  241. " opening handshake: was {magic}, but expected 127",
  242. magic=_magic,
  243. )
  244. self.abort()
  245. else:
  246. self.log.debug('WampRawSocketServerProtocol: correct magic byte received')
  247. # peer requests us to send messages of maximum length 2**max_len_exp
  248. #
  249. self._max_len_send = 2 ** (9 + (ord(self._handshake_bytes[1:2]) >> 4))
  250. self.log.debug(
  251. "WampRawSocketServerProtocol: client requests us to send out most {max_bytes} bytes per message",
  252. max_bytes=self._max_len_send,
  253. )
  254. # client wants to speak this serialization format
  255. #
  256. ser_id = ord(self._handshake_bytes[1:2]) & 0x0F
  257. if ser_id in self.factory._serializers:
  258. self._serializer = copy.copy(self.factory._serializers[ser_id])
  259. self.log.debug(
  260. "WampRawSocketServerProtocol: client wants to use serializer '{serializer}'",
  261. serializer=ser_id,
  262. )
  263. else:
  264. self.log.warn(
  265. "WampRawSocketServerProtocol: opening handshake - no suitable serializer found (client requested {serializer}, and we have {serializers}",
  266. serializer=ser_id,
  267. serializers=self.factory._serializers.keys(),
  268. )
  269. self.abort()
  270. # we request the client to send message of maximum length 2**reply_max_len_exp
  271. #
  272. reply_max_len_exp = int(math.ceil(math.log(self._max_message_size, 2)))
  273. # this is an instance attribute on the Twisted base class for maximum size
  274. # of _received_ messages
  275. self.MAX_LENGTH = 2**reply_max_len_exp
  276. # send out handshake reply
  277. #
  278. reply_octet2 = bytes(bytearray([
  279. ((reply_max_len_exp - 9) << 4) | self._serializer.RAWSOCKET_SERIALIZER_ID]))
  280. self.transport.write(b'\x7F') # magic byte
  281. self.transport.write(reply_octet2) # max length / serializer
  282. self.transport.write(b'\x00\x00') # reserved octets
  283. self._handshake_complete = True
  284. self._on_handshake_complete()
  285. self.log.debug(
  286. "WampRawSocketServerProtocol: opening handshake completed: {serializer}",
  287. serializer=self._serializer,
  288. )
  289. # consume any remaining data received already ..
  290. #
  291. data = data[remaining:]
  292. if data:
  293. self.dataReceived(data)
  294. @public
  295. class WampRawSocketClientProtocol(WampRawSocketProtocol):
  296. """
  297. Twisted-based WAMP-over-RawSocket client protocol.
  298. Implements:
  299. * :class:`autobahn.wamp.interfaces.ITransport`
  300. """
  301. def connectionMade(self):
  302. WampRawSocketProtocol.connectionMade(self)
  303. self._serializer = copy.copy(self.factory._serializer)
  304. # we request the peer to send messages of maximum length 2**reply_max_len_exp
  305. request_max_len_exp = int(math.ceil(math.log(self._max_message_size, 2)))
  306. # this is an instance attribute on the Twisted base class for maximum size
  307. # of _received_ messages
  308. self.MAX_LENGTH = 2**request_max_len_exp
  309. # send out handshake request
  310. #
  311. request_octet2 = bytes(bytearray([
  312. ((request_max_len_exp - 9) << 4) | self._serializer.RAWSOCKET_SERIALIZER_ID]))
  313. self.transport.write(b'\x7F') # magic byte
  314. self.transport.write(request_octet2) # max length / serializer
  315. self.transport.write(b'\x00\x00') # reserved octets
  316. def dataReceived(self, data):
  317. if self._handshake_complete:
  318. WampRawSocketProtocol.dataReceived(self, data)
  319. else:
  320. remaining = 4 - len(self._handshake_bytes)
  321. self._handshake_bytes += data[:remaining]
  322. if len(self._handshake_bytes) == 4:
  323. self.log.debug(
  324. "WampRawSocketClientProtocol: opening handshake received - {handshake}",
  325. handshake=_LazyHexFormatter(self._handshake_bytes),
  326. )
  327. if ord(self._handshake_bytes[0:1]) != 0x7f:
  328. self.log.debug(
  329. "WampRawSocketClientProtocol: invalid magic byte (octet 1) in opening handshake: was 0x{magic}, but expected 0x7f",
  330. magic=_LazyHexFormatter(self._handshake_bytes[0]),
  331. )
  332. self.abort()
  333. # peer requests us to _send_ messages of maximum length 2**max_len_exp
  334. #
  335. self._max_len_send = 2 ** (9 + (ord(self._handshake_bytes[1:2]) >> 4))
  336. self.log.debug(
  337. "WampRawSocketClientProtocol: server requests us to send out most {max} bytes per message",
  338. max=self._max_len_send,
  339. )
  340. # client wants to speak this serialization format
  341. #
  342. ser_id = ord(self._handshake_bytes[1:2]) & 0x0F
  343. if ser_id != self._serializer.RAWSOCKET_SERIALIZER_ID:
  344. self.log.error(
  345. "WampRawSocketClientProtocol: opening handshake - no suitable serializer found (server replied {serializer}, and we requested {serializers})",
  346. serializer=ser_id,
  347. serializers=self._serializer.RAWSOCKET_SERIALIZER_ID,
  348. )
  349. self.abort()
  350. self._handshake_complete = True
  351. self._on_handshake_complete()
  352. self.log.debug(
  353. "WampRawSocketClientProtocol: opening handshake completed (using serializer {serializer})",
  354. serializer=self._serializer,
  355. )
  356. # consume any remaining data received already ..
  357. #
  358. data = data[remaining:]
  359. if data:
  360. self.dataReceived(data)
  361. class WampRawSocketFactory(Factory):
  362. """
  363. Base class for Twisted-based WAMP-over-RawSocket factories.
  364. """
  365. log = txaio.make_logger()
  366. def __init__(self, factory):
  367. """
  368. :param factory: A callable that produces instances that implement
  369. :class:`autobahn.wamp.interfaces.ITransportHandler`
  370. :type factory: callable
  371. """
  372. if callable(factory):
  373. self._factory = factory
  374. else:
  375. self._factory = lambda: factory
  376. # RawSocket max payload size is 16M (https://wamp-proto.org/_static/gen/wamp_latest_ietf.html#handshake)
  377. self._max_message_size = 2**24
  378. def resetProtocolOptions(self):
  379. self._max_message_size = 2**24
  380. def setProtocolOptions(self, maxMessagePayloadSize=None):
  381. self.log.debug('{klass}.setProtocolOptions(maxMessagePayloadSize={maxMessagePayloadSize})',
  382. klass=self.__class__.__name__, maxMessagePayloadSize=maxMessagePayloadSize)
  383. assert maxMessagePayloadSize is None or (type(maxMessagePayloadSize) == int and maxMessagePayloadSize >= 512 and maxMessagePayloadSize <= 2**24)
  384. if maxMessagePayloadSize is not None and maxMessagePayloadSize != self._max_message_size:
  385. self._max_message_size = maxMessagePayloadSize
  386. def buildProtocol(self, addr):
  387. self.log.debug('{klass}.buildProtocol(addr={addr})', klass=self.__class__.__name__, addr=addr)
  388. p = self.protocol()
  389. p.factory = self
  390. p.MAX_LENGTH = self._max_message_size
  391. p._max_message_size = self._max_message_size
  392. self.log.debug('{klass}.buildProtocol() -> proto={proto}, max_message_size={max_message_size}, MAX_LENGTH={MAX_LENGTH}',
  393. klass=self.__class__.__name__, proto=p, max_message_size=p._max_message_size, MAX_LENGTH=p.MAX_LENGTH)
  394. return p
  395. @public
  396. class WampRawSocketServerFactory(WampRawSocketFactory):
  397. """
  398. Twisted-based WAMP-over-RawSocket server protocol factory.
  399. """
  400. protocol = WampRawSocketServerProtocol
  401. def __init__(self, factory, serializers=None):
  402. """
  403. :param factory: A callable that produces instances that implement
  404. :class:`autobahn.wamp.interfaces.ITransportHandler`
  405. :type factory: callable
  406. :param serializers: A list of WAMP serializers to use (or ``None``
  407. for all available serializers).
  408. :type serializers: list of objects implementing
  409. :class:`autobahn.wamp.interfaces.ISerializer`
  410. """
  411. WampRawSocketFactory.__init__(self, factory)
  412. if serializers is None:
  413. serializers = []
  414. # try CBOR WAMP serializer
  415. try:
  416. from autobahn.wamp.serializer import CBORSerializer
  417. serializers.append(CBORSerializer(batched=True))
  418. serializers.append(CBORSerializer())
  419. except ImportError:
  420. pass
  421. # try MsgPack WAMP serializer
  422. try:
  423. from autobahn.wamp.serializer import MsgPackSerializer
  424. serializers.append(MsgPackSerializer(batched=True))
  425. serializers.append(MsgPackSerializer())
  426. except ImportError:
  427. pass
  428. # try UBJSON WAMP serializer
  429. try:
  430. from autobahn.wamp.serializer import UBJSONSerializer
  431. serializers.append(UBJSONSerializer(batched=True))
  432. serializers.append(UBJSONSerializer())
  433. except ImportError:
  434. pass
  435. # try JSON WAMP serializer
  436. try:
  437. from autobahn.wamp.serializer import JsonSerializer
  438. serializers.append(JsonSerializer(batched=True))
  439. serializers.append(JsonSerializer())
  440. except ImportError:
  441. pass
  442. if not serializers:
  443. raise Exception("could not import any WAMP serializers")
  444. self._serializers = {}
  445. for ser in serializers:
  446. self._serializers[ser.RAWSOCKET_SERIALIZER_ID] = ser
  447. @public
  448. class WampRawSocketClientFactory(WampRawSocketFactory):
  449. """
  450. Twisted-based WAMP-over-RawSocket client protocol factory.
  451. """
  452. protocol = WampRawSocketClientProtocol
  453. def __init__(self, factory, serializer=None):
  454. """
  455. :param factory: A callable that produces instances that implement
  456. :class:`autobahn.wamp.interfaces.ITransportHandler`
  457. :type factory: callable
  458. :param serializer: The WAMP serializer to use (or ``None`` for
  459. "best" serializer, chosen as the first serializer available from
  460. this list: CBOR, MessagePack, UBJSON, JSON).
  461. :type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
  462. """
  463. WampRawSocketFactory.__init__(self, factory)
  464. # Reduce the factory logs noise
  465. self.noisy = False
  466. if serializer is None:
  467. # try CBOR WAMP serializer
  468. try:
  469. from autobahn.wamp.serializer import CBORSerializer
  470. serializer = CBORSerializer()
  471. except ImportError:
  472. pass
  473. if serializer is None:
  474. # try MsgPack WAMP serializer
  475. try:
  476. from autobahn.wamp.serializer import MsgPackSerializer
  477. serializer = MsgPackSerializer()
  478. except ImportError:
  479. pass
  480. if serializer is None:
  481. # try UBJSON WAMP serializer
  482. try:
  483. from autobahn.wamp.serializer import UBJSONSerializer
  484. serializer = UBJSONSerializer()
  485. except ImportError:
  486. pass
  487. if serializer is None:
  488. # try JSON WAMP serializer
  489. try:
  490. from autobahn.wamp.serializer import JsonSerializer
  491. serializer = JsonSerializer()
  492. except ImportError:
  493. pass
  494. if serializer is None:
  495. raise Exception("could not import any WAMP serializer")
  496. self._serializer = serializer