This commit is contained in:
Mai Gillmann
2019-12-17 14:09:10 +01:00
parent 66e908fc8a
commit 4791d00a43
2122 changed files with 423791 additions and 0 deletions
@@ -0,0 +1,27 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
__version__ = u'19.11.1'
@@ -0,0 +1,505 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
try:
import asyncio
except ImportError:
# trollious for py2 support - however it has been deprecated
import trollius as asyncio
import struct
import math
from autobahn.util import public, _LazyHexFormatter
from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost
from autobahn.asyncio.util import peer2str, get_serializers
import txaio
__all__ = (
'WampRawSocketServerProtocol',
'WampRawSocketClientProtocol',
'WampRawSocketServerFactory',
'WampRawSocketClientFactory'
)
txaio.use_asyncio()
FRAME_TYPE_DATA = 0
FRAME_TYPE_PING = 1
FRAME_TYPE_PONG = 2
MAGIC_BYTE = 0x7F
class PrefixProtocol(asyncio.Protocol):
prefix_format = '!L'
prefix_length = struct.calcsize(prefix_format)
max_length = 16 * 1024 * 1024
max_length_send = max_length
log = txaio.make_logger() # @UndefinedVariable
def connection_made(self, transport):
self.transport = transport
peer = transport.get_extra_info('peername')
self.peer = peer2str(peer)
self.log.debug('RawSocker Asyncio: Connection made with peer {peer}', peer=self.peer)
self._buffer = b''
self._header = None
self._wait_closed = txaio.create_future()
@property
def is_closed(self):
if hasattr(self, '_wait_closed'):
return self._wait_closed
else:
f = txaio.create_future()
f.set_result(True)
return f
def connection_lost(self, exc):
self.log.debug('RawSocker Asyncio: Connection lost')
self.transport = None
self._wait_closed.set_result(True)
self._on_connection_lost(exc)
def _on_connection_lost(self, exc):
pass
def protocol_error(self, msg):
self.log.error(msg)
self.transport.close()
def sendString(self, data):
l = len(data)
if l > self.max_length_send:
raise ValueError('Data too big')
header = struct.pack(self.prefix_format, len(data))
self.transport.write(header)
self.transport.write(data)
def ping(self, data):
raise NotImplementedError()
def pong(self, data):
raise NotImplementedError()
def data_received(self, data):
self._buffer += data
pos = 0
remaining = len(self._buffer)
while remaining >= self.prefix_length:
# do not recalculate header if available from previous call
if self._header:
frame_type, frame_length = self._header
else:
header = self._buffer[pos:pos + self.prefix_length]
frame_type = ord(header[0:1]) & 0b00000111
if frame_type > FRAME_TYPE_PONG:
self.protocol_error('Invalid frame type')
return
frame_length = struct.unpack(self.prefix_format, b'\0' + header[1:])[0]
if frame_length > self.max_length:
self.protocol_error('Frame too big')
return
if remaining - self.prefix_length >= frame_length:
self._header = None
pos += self.prefix_length
remaining -= self.prefix_length
data = self._buffer[pos:pos + frame_length]
pos += frame_length
remaining -= frame_length
if frame_type == FRAME_TYPE_DATA:
self.stringReceived(data)
elif frame_type == FRAME_TYPE_PING:
self.ping(data)
elif frame_type == FRAME_TYPE_PONG:
self.pong(data)
else:
# save heaader
self._header = frame_type, frame_length
break
self._buffer = self._buffer[pos:]
def stringReceived(self, data):
raise NotImplementedError()
class RawSocketProtocol(PrefixProtocol):
def __init__(self):
max_size = None
if max_size:
exp = int(math.ceil(math.log(max_size, 2))) - 9
if exp > 15:
raise ValueError('Maximum length is 16M')
self.max_length = 2**(exp + 9)
self._length_exp = exp
else:
self._length_exp = 15
self.max_length = 2**24
def connection_made(self, transport):
PrefixProtocol.connection_made(self, transport)
self._handshake_done = False
def _on_handshake_complete(self):
raise NotImplementedError()
def parse_handshake(self):
buf = bytearray(self._buffer[:4])
if buf[0] != MAGIC_BYTE:
raise HandshakeError('Invalid magic byte in handshake')
return
ser = buf[1] & 0x0F
lexp = buf[1] >> 4
self.max_length_send = 2**(lexp + 9)
if buf[2] != 0 or buf[3] != 0:
raise HandshakeError('Reserved bytes must be zero')
return ser, lexp
def process_handshake(self):
raise NotImplementedError()
def data_received(self, data):
self.log.debug('RawSocker Asyncio: data received {data}', data=_LazyHexFormatter(data))
if self._handshake_done:
return PrefixProtocol.data_received(self, data)
else:
self._buffer += data
if len(self._buffer) >= 4:
try:
self.process_handshake()
except HandshakeError as e:
self.protocol_error('Handshake error : {err}'.format(err=e))
return
self._handshake_done = True
self._on_handshake_complete()
data = self._buffer[4:]
self._buffer = b''
if data:
PrefixProtocol.data_received(self, data)
ERR_SERIALIZER_UNSUPPORTED = 1
ERRMAP = {
0: "illegal (must not be used)",
1: "serializer unsupported",
2: "maximum message length unacceptable",
3: "use of reserved bits (unsupported feature)",
4: "maximum connection count reached"
}
class HandshakeError(Exception):
def __init__(self, msg, code=0):
Exception.__init__(self, msg if not code else msg + ' : %s' % ERRMAP.get(code))
class RawSocketClientProtocol(RawSocketProtocol):
def check_serializer(self, ser_id):
return True
def process_handshake(self):
ser_id, err = self.parse_handshake()
if ser_id == 0:
raise HandshakeError('Server returned handshake error', err)
if self.serializer_id != ser_id:
raise HandshakeError('Server returned different serializer {0} then requested {1}'
.format(ser_id, self.serializer_id))
@property
def serializer_id(self):
raise NotImplementedError()
def connection_made(self, transport):
RawSocketProtocol.connection_made(self, transport)
# start handshake
hs = bytes(bytearray([MAGIC_BYTE,
self._length_exp << 4 | self.serializer_id,
0, 0]))
transport.write(hs)
self.log.debug('RawSocket Asyncio: Client handshake sent')
class RawSocketServerProtocol(RawSocketProtocol):
def supports_serializer(self, ser_id):
raise NotImplementedError()
def process_handshake(self):
def send_response(lexp, ser_id):
b2 = lexp << 4 | (ser_id & 0x0f)
self.transport.write(bytes(bytearray([MAGIC_BYTE, b2, 0, 0])))
ser_id, _lexp = self.parse_handshake()
if not self.supports_serializer(ser_id):
send_response(ERR_SERIALIZER_UNSUPPORTED, 0)
raise HandshakeError('Serializer unsupported : {ser_id}'.format(ser_id=ser_id))
send_response(self._length_exp, ser_id)
# this is transport independent part of WAMP protocol
class WampRawSocketMixinGeneral(object):
def _on_handshake_complete(self):
self.log.debug("WampRawSocketProtocol: Handshake complete")
try:
self._session = self.factory._factory()
self._session.onOpen(self)
except Exception as e:
# Exceptions raised in onOpen are fatal ..
self.log.warn("WampRawSocketProtocol: ApplicationSession constructor / onOpen raised ({err})", err=e)
self.abort()
else:
self.log.info("ApplicationSession started.")
def stringReceived(self, payload):
self.log.debug("WampRawSocketProtocol: RX octets: {octets}", octets=_LazyHexFormatter(payload))
try:
for msg in self._serializer.unserialize(payload):
self.log.debug("WampRawSocketProtocol: RX WAMP message: {msg}", msg=msg)
self._session.onMessage(msg)
except ProtocolError as e:
self.log.warn("WampRawSocketProtocol: WAMP Protocol Error ({err}) - aborting connection", err=e)
self.abort()
except Exception as e:
self.log.warn("WampRawSocketProtocol: WAMP Internal Error ({err}) - aborting connection", err=e)
self.abort()
def send(self, msg):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.send`
"""
if self.isOpen():
self.log.debug("WampRawSocketProtocol: TX WAMP message: {msg}", msg=msg)
try:
payload, _ = self._serializer.serialize(msg)
except Exception as e:
# all exceptions raised from above should be serialization errors ..
raise SerializationError("WampRawSocketProtocol: unable to serialize WAMP application payload ({0})"
.format(e))
else:
self.sendString(payload)
self.log.debug("WampRawSocketProtocol: TX octets: {octets}", octets=_LazyHexFormatter(payload))
else:
raise TransportLost()
def isOpen(self):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen`
"""
return hasattr(self, '_session') and self._session is not None
# this is asyncio dependent part of WAMP protocol
class WampRawSocketMixinAsyncio(object):
"""
Base class for asyncio-based WAMP-over-RawSocket protocols.
"""
def _on_connection_lost(self, exc):
try:
wasClean = exc is None
self._session.onClose(wasClean)
except Exception as e:
# silently ignore exceptions raised here ..
self.log.warn("WampRawSocketProtocol: ApplicationSession.onClose raised ({err})", err=e)
self._session = None
def close(self):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.close`
"""
if self.isOpen():
self.transport.close()
else:
raise TransportLost()
def abort(self):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.abort`
"""
if self.isOpen():
if hasattr(self.transport, 'abort'):
# ProcessProtocol lacks abortConnection()
self.transport.abort()
else:
self.transport.close()
else:
raise TransportLost()
@public
class WampRawSocketServerProtocol(WampRawSocketMixinGeneral, WampRawSocketMixinAsyncio, RawSocketServerProtocol):
"""
asyncio-based WAMP-over-RawSocket server protocol.
Implements:
* :class:`autobahn.wamp.interfaces.ITransport`
"""
def supports_serializer(self, ser_id):
if ser_id in self.factory._serializers:
self._serializer = self.factory._serializers[ser_id]()
self.log.debug(
"WampRawSocketProtocol: client wants to use serializer '{serializer}'",
serializer=ser_id,
)
return True
else:
self.log.debug(
"WampRawSocketProtocol: opening handshake - no suitable serializer found (client requested {serializer}, and we have {serializers}",
serializer=ser_id,
serializers=self.factory._serializers.keys(),
)
self.abort()
return False
def get_channel_id(self, channel_id_type=u'tls-unique'):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.get_channel_id`
"""
return None
# return transport_channel_id(self.transport, is_server=True, channel_id_type=channel_id_type)
@public
class WampRawSocketClientProtocol(WampRawSocketMixinGeneral, WampRawSocketMixinAsyncio, RawSocketClientProtocol):
"""
asyncio-based WAMP-over-RawSocket client protocol.
Implements:
* :class:`autobahn.wamp.interfaces.ITransport`
"""
@property
def serializer_id(self):
if not hasattr(self, '_serializer'):
self._serializer = self.factory._serializer
return self._serializer.RAWSOCKET_SERIALIZER_ID
def get_channel_id(self, channel_id_type=u'tls-unique'):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.get_channel_id`
"""
return None
# return transport_channel_id(self.transport, is_server=False, channel_id_type=channel_id_type)
class WampRawSocketFactory(object):
"""
Adapter class for asyncio-based WebSocket client and server factories.def dataReceived(self, data):
"""
log = txaio.make_logger()
@public
def __call__(self):
proto = self.protocol()
proto.factory = self
return proto
@public
class WampRawSocketServerFactory(WampRawSocketFactory):
"""
asyncio-based WAMP-over-RawSocket server protocol factory.
"""
protocol = WampRawSocketServerProtocol
def __init__(self, factory, serializers=None):
"""
:param factory: A callable that produces instances that implement
:class:`autobahn.wamp.interfaces.ITransportHandler`
:type factory: callable
:param serializers: A list of WAMP serializers to use (or ``None``
for all available serializers).
:type serializers: list of objects implementing
:class:`autobahn.wamp.interfaces.ISerializer`
"""
if callable(factory):
self._factory = factory
else:
self._factory = lambda: factory
# when no serializers were requested specifically, then support
# all that are available
if serializers is None:
serializers = get_serializers()
if not serializers:
raise Exception("could not import any WAMP serializers")
self._serializers = {ser.RAWSOCKET_SERIALIZER_ID: ser for ser in serializers}
@public
class WampRawSocketClientFactory(WampRawSocketFactory):
"""
asyncio-based WAMP-over-RawSocket client factory.
"""
protocol = WampRawSocketClientProtocol
def __init__(self, factory, serializer=None):
"""
:param factory: A callable that produces instances that implement
:class:`autobahn.wamp.interfaces.ITransportHandler`
:type factory: callable
:param serializer: The WAMP serializer to use (or ``None`` for
"best" serializer, chosen as the first serializer available from
this list: CBOR, MessagePack, UBJSON, JSON).
:type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
"""
if callable(factory):
self._factory = factory
else:
self._factory = lambda: factory
# when no serializer was requested specifically, use the first
# one available
if serializer is None:
serializers = get_serializers()
if serializers:
serializer = serializers[0]()
if serializer is None:
raise Exception("could not import any WAMP serializer")
self._serializer = serializer
@@ -0,0 +1,396 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
from collections import deque
import txaio
txaio.use_asyncio()
from autobahn.util import public
from autobahn.asyncio.util import transport_channel_id, peer2str
from autobahn.wamp import websocket
from autobahn.websocket import protocol
from autobahn.websocket.types import TransportDetails
try:
import asyncio
from asyncio import iscoroutine
from asyncio import Future
except ImportError:
# Trollius >= 0.3 was renamed
# noinspection PyUnresolvedReferences
import trollius as asyncio
from trollius import iscoroutine
from trollius import Future
if hasattr(asyncio, 'ensure_future'):
ensure_future = asyncio.ensure_future
else: # Deprecated since Python 3.4.4
ensure_future = getattr(asyncio, 'async')
__all__ = (
'WebSocketServerProtocol',
'WebSocketClientProtocol',
'WebSocketServerFactory',
'WebSocketClientFactory',
'WampWebSocketServerProtocol',
'WampWebSocketClientProtocol',
'WampWebSocketServerFactory',
'WampWebSocketClientFactory',
)
def yields(value):
"""
Returns ``True`` iff the value yields.
.. seealso:: http://stackoverflow.com/questions/20730248/maybedeferred-analog-with-asyncio
"""
return isinstance(value, Future) or iscoroutine(value)
class WebSocketAdapterProtocol(asyncio.Protocol):
"""
Adapter class for asyncio-based WebSocket client and server protocols.
"""
def connection_made(self, transport):
self.transport = transport
self.receive_queue = deque()
self._consume()
try:
self.peer = peer2str(transport.get_extra_info('peername'))
except:
self.peer = u"?"
self._connectionMade()
def connection_lost(self, exc):
self._connectionLost(exc)
# according to asyncio docs, connection_lost(None) is called
# if something else called transport.close()
if exc is not None:
self.transport.close()
self.transport = None
def _consume(self):
self.waiter = Future(loop=self.factory.loop or txaio.config.loop)
def process(_):
while len(self.receive_queue):
data = self.receive_queue.popleft()
if self.transport:
self._dataReceived(data)
self._consume()
self.waiter.add_done_callback(process)
def data_received(self, data):
self.receive_queue.append(data)
if not self.waiter.done():
self.waiter.set_result(None)
def _closeConnection(self, abort=False):
if abort and hasattr(self.transport, 'abort'):
self.transport.abort()
else:
self.transport.close()
def _onOpen(self):
res = self.onOpen()
if yields(res):
ensure_future(res)
def _onMessageBegin(self, isBinary):
res = self.onMessageBegin(isBinary)
if yields(res):
ensure_future(res)
def _onMessageFrameBegin(self, length):
res = self.onMessageFrameBegin(length)
if yields(res):
ensure_future(res)
def _onMessageFrameData(self, payload):
res = self.onMessageFrameData(payload)
if yields(res):
ensure_future(res)
def _onMessageFrameEnd(self):
res = self.onMessageFrameEnd()
if yields(res):
ensure_future(res)
def _onMessageFrame(self, payload):
res = self.onMessageFrame(payload)
if yields(res):
ensure_future(res)
def _onMessageEnd(self):
res = self.onMessageEnd()
if yields(res):
ensure_future(res)
def _onMessage(self, payload, isBinary):
res = self.onMessage(payload, isBinary)
if yields(res):
ensure_future(res)
def _onPing(self, payload):
res = self.onPing(payload)
if yields(res):
ensure_future(res)
def _onPong(self, payload):
res = self.onPong(payload)
if yields(res):
ensure_future(res)
def _onClose(self, wasClean, code, reason):
res = self.onClose(wasClean, code, reason)
if yields(res):
ensure_future(res)
def registerProducer(self, producer, streaming):
raise Exception("not implemented")
def unregisterProducer(self):
# note that generic websocket/protocol.py code calls
# .unregisterProducer whenever we dropConnection -- that's
# correct behavior on Twisted so either we'd have to
# try/except there, or special-case Twisted, ..or just make
# this "not an error"
pass
@public
class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServerProtocol):
"""
Base class for asyncio-based WebSocket server protocols.
Implements:
* :class:`autobahn.websocket.interfaces.IWebSocketChannel`
"""
log = txaio.make_logger()
def get_channel_id(self, channel_id_type=u'tls-unique'):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.get_channel_id`
"""
return transport_channel_id(self.transport, True, channel_id_type)
@public
class WebSocketClientProtocol(WebSocketAdapterProtocol, protocol.WebSocketClientProtocol):
"""
Base class for asyncio-based WebSocket client protocols.
Implements:
* :class:`autobahn.websocket.interfaces.IWebSocketChannel`
"""
log = txaio.make_logger()
def _onConnect(self, response):
res = self.onConnect(response)
if yields(res):
ensure_future(res)
def startTLS(self):
raise Exception("WSS over explicit proxies not implemented")
def get_channel_id(self, channel_id_type=u'tls-unique'):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.get_channel_id`
"""
return transport_channel_id(self.transport, False, channel_id_type)
def _create_transport_details(self):
"""
Internal helper.
Base class calls this to create a TransportDetails
"""
is_secure = self.transport.get_extra_info('peercert', None) is not None
if is_secure:
secure_channel_id = {
u'tls-unique': transport_channel_id(self.transport, False, 'tls-unique'),
}
else:
secure_channel_id = {}
return TransportDetails(peer=self.peer, is_secure=is_secure, secure_channel_id=secure_channel_id)
class WebSocketAdapterFactory(object):
"""
Adapter class for asyncio-based WebSocket client and server factories.
"""
log = txaio.make_logger()
def __call__(self):
proto = self.protocol()
proto.factory = self
return proto
@public
class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory):
"""
Base class for asyncio-based WebSocket server factories.
Implements:
* :class:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`
"""
protocol = WebSocketServerProtocol
def __init__(self, *args, **kwargs):
"""
.. note::
In addition to all arguments to the constructor of
:meth:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`,
you can supply a ``loop`` keyword argument to specify the
asyncio event loop to be used.
"""
loop = kwargs.pop('loop', None)
self.loop = loop or asyncio.get_event_loop()
protocol.WebSocketServerFactory.__init__(self, *args, **kwargs)
@public
class WebSocketClientFactory(WebSocketAdapterFactory, protocol.WebSocketClientFactory):
"""
Base class for asyncio-based WebSocket client factories.
Implements:
* :class:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`
"""
def __init__(self, *args, **kwargs):
"""
.. note::
In addition to all arguments to the constructor of
:meth:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`,
you can supply a ``loop`` keyword argument to specify the
asyncio event loop to be used.
"""
loop = kwargs.pop('loop', None)
self.loop = loop or asyncio.get_event_loop()
protocol.WebSocketClientFactory.__init__(self, *args, **kwargs)
@public
class WampWebSocketServerProtocol(websocket.WampWebSocketServerProtocol, WebSocketServerProtocol):
"""
asyncio-based WAMP-over-WebSocket server protocol.
Implements:
* :class:`autobahn.wamp.interfaces.ITransport`
"""
@public
class WampWebSocketServerFactory(websocket.WampWebSocketServerFactory, WebSocketServerFactory):
"""
asyncio-based WAMP-over-WebSocket server factory.
"""
protocol = WampWebSocketServerProtocol
def __init__(self, factory, *args, **kwargs):
"""
:param factory: A callable that produces instances that implement
:class:`autobahn.wamp.interfaces.ITransportHandler`
:type factory: callable
:param serializers: A list of WAMP serializers to use (or ``None``
for all available serializers).
:type serializers: list of objects implementing
:class:`autobahn.wamp.interfaces.ISerializer`
"""
serializers = kwargs.pop('serializers', None)
websocket.WampWebSocketServerFactory.__init__(self, factory, serializers)
kwargs['protocols'] = self._protocols
# noinspection PyCallByClass
WebSocketServerFactory.__init__(self, *args, **kwargs)
@public
class WampWebSocketClientProtocol(websocket.WampWebSocketClientProtocol, WebSocketClientProtocol):
"""
asyncio-based WAMP-over-WebSocket client protocols.
Implements:
* :class:`autobahn.wamp.interfaces.ITransport`
"""
@public
class WampWebSocketClientFactory(websocket.WampWebSocketClientFactory, WebSocketClientFactory):
"""
asyncio-based WAMP-over-WebSocket client factory.
"""
protocol = WampWebSocketClientProtocol
def __init__(self, factory, *args, **kwargs):
"""
:param factory: A callable that produces instances that implement
:class:`autobahn.wamp.interfaces.ITransportHandler`
:type factory: callable
:param serializer: The WAMP serializer to use (or ``None`` for
"best" serializer, chosen as the first serializer available from
this list: CBOR, MessagePack, UBJSON, JSON).
:type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
"""
serializers = kwargs.pop('serializers', None)
websocket.WampWebSocketClientFactory.__init__(self, factory, serializers)
kwargs['protocols'] = self._protocols
WebSocketClientFactory.__init__(self, *args, **kwargs)
@@ -0,0 +1,41 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
from autobahn.util import public
__all__ = (
'PayloadExceededError',
)
@public
class PayloadExceededError(RuntimeError):
"""
Exception raised when the serialized and framed (eg WebSocket/RawSocket) WAMP payload
exceeds the transport message size limit.
"""
@@ -0,0 +1,646 @@
///////////////////////////////////////////////////////////////////////////////
//
// The MIT License (MIT)
//
// Copyright (c) Crossbar.io Technologies GmbH
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
///////////////////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <stdint.h>
// http://stackoverflow.com/questions/11228855/header-files-for-simd-intrinsics
#include <x86intrin.h>
#define UTF8_ACCEPT 0
#define UTF8_REJECT 1
typedef struct {
size_t current_index;
size_t total_index;
int state;
int impl;
} utf8_validator_t;
#define UTF8_VALIDATOR_OPTIMAL 0
#define UTF8_VALIDATOR_TABLE_DFA 1
#define UTF8_VALIDATOR_UNROLLED_DFA 2
#define UTF8_VALIDATOR_SSE2_DFA 3
#define UTF8_VALIDATOR_SSE41_DFA 4
int nvx_utf8vld_get_impl (void* utf8vld) {
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
return vld->impl;
}
int nvx_utf8vld_set_impl (void* utf8vld, int impl) {
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
if (impl) {
// set requested implementation
//
#ifndef __SSE4_1__
# ifdef __SSE2__
if (impl <= UTF8_VALIDATOR_SSE2_DFA) {
vld->impl = impl;
}
# else
if (impl <= UTF8_VALIDATOR_UNROLLED_DFA) {
vld->impl = impl;
}
# endif
#else
if (impl <= UTF8_VALIDATOR_SSE41_DFA) {
vld->impl = impl;
}
#endif
} else {
// set optimal implementation
//
#ifndef __SSE4_1__
# ifdef __SSE2__
vld->impl = UTF8_VALIDATOR_SSE2_DFA;
# else
vld->impl = UTF8_VALIDATOR_UNROLLED_DFA;
# endif
#else
vld->impl = UTF8_VALIDATOR_SSE41_DFA;
#endif
}
return vld->impl;
}
void nvx_utf8vld_reset (void* utf8vld) {
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
vld->state = 0;
vld->current_index = -1;
vld->total_index = -1;
}
void* nvx_utf8vld_new () {
void* p = malloc(sizeof(utf8_validator_t));
nvx_utf8vld_reset(p);
nvx_utf8vld_set_impl(p, 0);
return p;
}
void nvx_utf8vld_free (void* utf8vld) {
free (utf8vld);
}
// unrolled DFA from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
//
static const uint8_t UTF8VALIDATOR_DFA[] __attribute__((aligned(64))) =
{
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // s7..s8
};
int _nvx_utf8vld_validate_table (void* utf8vld, const uint8_t* data, size_t length) {
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
int state = vld->state;
const uint8_t* end = data + length;
while (data < end && state != 1) {
state = UTF8VALIDATOR_DFA[256 + state * 16 + UTF8VALIDATOR_DFA[*data++]];
}
vld->state = state;
if (state == 0) {
// UTF8 is valid and ends on codepoint
return 0;
} else {
if (state == 1) {
// UTF8 is invalid
return -1;
} else {
// UTF8 is valid, but does not end on codepoint (needs more data)
return 1;
}
}
}
// unrolled DFA from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
//
#define DFA_TRANSITION(state, octet) \
if (state == 0) { \
if (octet >= 0x00 && octet <= 0x7f) { \
/* reflective state 0 */ \
} else if (octet >= 0xc2 && octet <= 0xdf) { \
state = 2; \
} else if ((octet >= 0xe1 && octet <= 0xec) || octet == 0xee || octet == 0xef) { \
state = 3; \
} else if (octet == 0xe0) { \
state = 4; \
} else if (octet == 0xed) { \
state = 5; \
} else if (octet == 0xf4) { \
state = 8; \
} else if (octet == 0xf1 || octet == 0xf2 || octet == 0xf3) { \
state = 7; \
} else if (octet == 0xf0) { \
state = 6; \
} else { \
state = 1; \
} \
} else if (state == 2) { \
if (octet >= 0x80 && octet <= 0xbf) { \
state = 0; \
} else { \
state = 1; \
} \
} else if (state == 3) { \
if (octet >= 0x80 && octet <= 0xbf) { \
state = 2; \
} else { \
state = 1; \
} \
} else if (state == 4) { \
if (octet >= 0xa0 && octet <= 0xbf) { \
state = 2; \
} else { \
state = 1; \
} \
} else if (state == 5) { \
if (octet >= 0x80 && octet <= 0x9f) { \
state = 2; \
} else { \
state = 1; \
} \
} else if (state == 6) { \
if (octet >= 0x90 && octet <= 0xbf) { \
state = 3; \
} else { \
state = 1; \
} \
} else if (state == 7) { \
if (octet >= 0x80 && octet <= 0xbf) { \
state = 3; \
} else { \
state = 1; \
} \
} else if (state == 8) { \
if (octet >= 0x80 && octet <= 0x8f) { \
state = 3; \
} else { \
state = 1; \
} \
} else if (state == 1) { \
/* refective state 1 */ \
} else { \
/* should not arrive here */ \
}
int _nvx_utf8vld_validate_unrolled (void* utf8vld, const uint8_t* data, size_t length) {
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
int state = vld->state;
const uint8_t* tail_end = data + length;
while (data < tail_end && state != 1) {
// get tail octet
int octet = *data;
// do the DFA
DFA_TRANSITION(state, octet);
++data;
}
vld->state = state;
if (state == 0) {
// UTF8 is valid and ends on codepoint
return 0;
} else {
if (state == 1) {
// UTF8 is invalid
return -1;
} else {
// UTF8 is valid, but does not end on codepoint (needs more data)
return 1;
}
}
}
/*
__m128i _mm_load_si128 (__m128i const* mem_addr)
#include "emmintrin.h"
Instruction: movdqa
CPUID Feature Flag: SSE2
int _mm_movemask_epi8 (__m128i a)
#include "emmintrin.h"
Instruction: pmovmskb
CPUID Feature Flag: SSE2
__m128i _mm_srli_si128 (__m128i a, int imm)
#include "emmintrin.h"
Instruction: psrldq
CPUID Feature Flag: SSE2
int _mm_cvtsi128_si32 (__m128i a)
#include "emmintrin.h"
Instruction: movd
CPUID Feature Flag: SSE2
int _mm_extract_epi16 (__m128i a, int imm)
#include "emmintrin.h"
Instruction: pextrw
CPUID Feature Flag: SSE2
int _mm_extract_epi8 (__m128i a, const int imm)
#include "smmintrin.h"
Instruction: pextrb
CPUID Feature Flag: SSE4.1
*/
#ifdef __SSE2__
int _nvx_utf8vld_validate_sse2 (void* utf8vld, const uint8_t* data, size_t length) {
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
int state = vld->state;
const uint8_t* tail_end = data + length;
// process unaligned head (sub 16 octets)
//
size_t head_len = ((size_t) data) % sizeof(__m128i);
if (head_len) {
const uint8_t* head_end = data + head_len;
while (data < head_end && state != UTF8_REJECT) {
// get head octet
int octet = *data;
// do the DFA
DFA_TRANSITION(state, octet);
++data;
}
}
// process aligned middle (16 octet chunks)
//
const __m128i* ptr = ((const __m128i*) data);
const __m128i* end = ((const __m128i*) data) + ((length - head_len) / sizeof(__m128i));
while (ptr < end && state != UTF8_REJECT) {
__builtin_prefetch(ptr + 1, 0, 3);
//__builtin_prefetch(ptr + 4, 0, 3); // 16*4=64: cache-line prefetch
__m128i xmm1 = _mm_load_si128(ptr);
if (__builtin_expect(state || _mm_movemask_epi8(xmm1), 0)) {
// copy to different reg - this allows the prefetching to
// do its job in the meantime (I guess ..)
// SSE2 variant
//
int octet;
// octet 0
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 1
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 2
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 3
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 4
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 5
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 6
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 7
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 8
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 9
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 10
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 11
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 12
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 13
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 14
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
// octet 15
xmm1 = _mm_srli_si128(xmm1, 1);
octet = 0xff & _mm_cvtsi128_si32(xmm1);
DFA_TRANSITION(state, octet);
}
++ptr;
}
// process unaligned tail (sub 16 octets)
//
const uint8_t* tail_ptr = (const uint8_t*) ptr;
while (tail_ptr < tail_end && state != UTF8_REJECT) {
// get tail octet
int octet = *tail_ptr;
// do the DFA
DFA_TRANSITION(state, octet);
++tail_ptr;
}
vld->state = state;
if (state == UTF8_ACCEPT) {
// UTF8 is valid and ends on codepoint
return 0;
} else {
if (state == UTF8_REJECT) {
// UTF8 is invalid
return -1;
} else {
// UTF8 is valid, but does not end on codepoint (needs more data)
return 1;
}
}
}
#endif
#ifdef __SSE4_1__
int _nvx_utf8vld_validate_sse4 (void* utf8vld, const uint8_t* data, size_t length) {
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
int state = vld->state;
const uint8_t* tail_end = data + length;
// process unaligned head (sub 16 octets)
//
size_t head_len = ((size_t) data) % sizeof(__m128i);
if (head_len) {
const uint8_t* head_end = data + head_len;
while (data < head_end && state != UTF8_REJECT) {
// get head octet
int octet = *data;
// do the DFA
DFA_TRANSITION(state, octet);
++data;
}
}
// process aligned middle (16 octet chunks)
//
const __m128i* ptr = ((const __m128i*) data);
const __m128i* end = ((const __m128i*) data) + ((length - head_len) / sizeof(__m128i));
while (ptr < end && state != UTF8_REJECT) {
__builtin_prefetch(ptr + 1, 0, 3);
//__builtin_prefetch(ptr + 4, 0, 3); // 16*4=64: cache-line prefetch
__m128i xmm1 = _mm_load_si128(ptr);
if (__builtin_expect(state || _mm_movemask_epi8(xmm1), 0)) {
// copy to different reg - this allows the prefetching to
// do its job in the meantime (I guess ..)
// SSE4.1 variant
//
int octet;
// octet 0
octet = _mm_extract_epi8(xmm1, 0);
DFA_TRANSITION(state, octet);
// octet 1
octet = _mm_extract_epi8(xmm1, 1);
DFA_TRANSITION(state, octet);
// octet 2
octet = _mm_extract_epi8(xmm1, 2);
DFA_TRANSITION(state, octet);
// octet 3
octet = _mm_extract_epi8(xmm1, 3);
DFA_TRANSITION(state, octet);
// octet 4
octet = _mm_extract_epi8(xmm1, 4);
DFA_TRANSITION(state, octet);
// octet 5
octet = _mm_extract_epi8(xmm1, 5);
DFA_TRANSITION(state, octet);
// octet 6
octet = _mm_extract_epi8(xmm1, 6);
DFA_TRANSITION(state, octet);
// octet 7
octet = _mm_extract_epi8(xmm1, 7);
DFA_TRANSITION(state, octet);
// octet 8
octet = _mm_extract_epi8(xmm1, 8);
DFA_TRANSITION(state, octet);
// octet 9
octet = _mm_extract_epi8(xmm1, 9);
DFA_TRANSITION(state, octet);
// octet 10
octet = _mm_extract_epi8(xmm1, 10);
DFA_TRANSITION(state, octet);
// octet 11
octet = _mm_extract_epi8(xmm1, 11);
DFA_TRANSITION(state, octet);
// octet 12
octet = _mm_extract_epi8(xmm1, 12);
DFA_TRANSITION(state, octet);
// octet 13
octet = _mm_extract_epi8(xmm1, 13);
DFA_TRANSITION(state, octet);
// octet 14
octet = _mm_extract_epi8(xmm1, 14);
DFA_TRANSITION(state, octet);
// octet 15
octet = _mm_extract_epi8(xmm1, 15);
DFA_TRANSITION(state, octet);
}
++ptr;
}
// process unaligned tail (sub 16 octets)
//
const uint8_t* tail_ptr = (const uint8_t*) ptr;
while (tail_ptr < tail_end && state != UTF8_REJECT) {
// get tail octet
int octet = *tail_ptr;
// do the DFA
DFA_TRANSITION(state, octet);
++tail_ptr;
}
vld->state = state;
if (state == UTF8_ACCEPT) {
// UTF8 is valid and ends on codepoint
return 0;
} else {
if (state == UTF8_REJECT) {
// UTF8 is invalid
return -1;
} else {
// UTF8 is valid, but does not end on codepoint (needs more data)
return 1;
}
}
}
#endif
int nvx_utf8vld_validate (void* utf8vld, const uint8_t* data, size_t length) {
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
switch (vld->impl) {
case UTF8_VALIDATOR_TABLE_DFA:
return _nvx_utf8vld_validate_table(utf8vld, data, length);
case UTF8_VALIDATOR_UNROLLED_DFA:
return _nvx_utf8vld_validate_unrolled(utf8vld, data, length);
#ifdef __SSE2__
case UTF8_VALIDATOR_SSE2_DFA:
return _nvx_utf8vld_validate_table(utf8vld, data, length);
#endif
#ifdef __SSE4_1__
case UTF8_VALIDATOR_SSE41_DFA:
return _nvx_utf8vld_validate_table(utf8vld, data, length);
#endif
default:
return _nvx_utf8vld_validate_table(utf8vld, data, length);
}
}
@@ -0,0 +1,357 @@
# coding=utf-8
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import six
import struct
import unittest
from autobahn.websocket.utf8validator import Utf8Validator as StandardUtf8Validator
try:
from _nvx_utf8validator import lib # noqa
from autobahn.nvx import Utf8Validator as NvxUtf8Validator
except ImportError:
HAS_NVX = False
else:
HAS_NVX = True
def _create_utf8_test_sequences():
"""
Create test sequences for UTF-8 decoder tests from
http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
"""
UTF8_TEST_SEQUENCES = []
# 1 Some correct UTF-8 text
vss = b'\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5'
vs = [b"Some valid UTF-8 sequences", []]
vs[1].append((True, b'hello\x24world')) # U+0024
vs[1].append((True, b'hello\xC2\xA2world')) # U+00A2
vs[1].append((True, b'hello\xE2\x82\xACworld')) # U+20AC
vs[1].append((True, b'hello\xF0\xA4\xAD\xA2world')) # U+24B62
vs[1].append((True, vss))
UTF8_TEST_SEQUENCES.append(vs)
# All prefixes of correct UTF-8 text
vs = [
b"All prefixes of a valid UTF-8 string that contains multi-byte code points",
[]]
v = StandardUtf8Validator()
for i in range(1, len(vss) + 1):
v.reset()
res = v.validate(vss[:i])
vs[1].append((res[0] and res[1], vss[:i]))
UTF8_TEST_SEQUENCES.append(vs)
# 2.1 First possible sequence of a certain length
vs = [b"First possible sequence of a certain length", []]
vs[1].append((True, b'\x00'))
vs[1].append((True, b'\xc2\x80'))
vs[1].append((True, b'\xe0\xa0\x80'))
vs[1].append((True, b'\xf0\x90\x80\x80'))
UTF8_TEST_SEQUENCES.append(vs)
# the following conform to the UTF-8 integer encoding scheme, but
# valid UTF-8 only allows for Unicode code points up to U+10FFFF
vs = [b"First possible sequence length 5/6 (invalid codepoints)", []]
vs[1].append((False, b'\xf8\x88\x80\x80\x80'))
vs[1].append((False, b'\xfc\x84\x80\x80\x80\x80'))
UTF8_TEST_SEQUENCES.append(vs)
# 2.2 Last possible sequence of a certain length
vs = [b"Last possible sequence of a certain length", []]
vs[1].append((True, b'\x7f'))
vs[1].append((True, b'\xdf\xbf'))
vs[1].append((True, b'\xef\xbf\xbf'))
vs[1].append((True, b'\xf4\x8f\xbf\xbf'))
UTF8_TEST_SEQUENCES.append(vs)
# the following conform to the UTF-8 integer encoding scheme, but
# valid UTF-8 only allows for Unicode code points up to U+10FFFF
vs = [b"Last possible sequence length 4/5/6 (invalid codepoints)", []]
vs[1].append((False, b'\xf7\xbf\xbf\xbf'))
vs[1].append((False, b'\xfb\xbf\xbf\xbf\xbf'))
vs[1].append((False, b'\xfd\xbf\xbf\xbf\xbf\xbf'))
UTF8_TEST_SEQUENCES.append(vs)
# 2.3 Other boundary conditions
vs = [b"Other boundary conditions", []]
vs[1].append((True, b'\xed\x9f\xbf'))
vs[1].append((True, b'\xee\x80\x80'))
vs[1].append((True, b'\xef\xbf\xbd'))
vs[1].append((True, b'\xf4\x8f\xbf\xbf'))
vs[1].append((False, b'\xf4\x90\x80\x80'))
UTF8_TEST_SEQUENCES.append(vs)
# 3.1 Unexpected continuation bytes
vs = [b"Unexpected continuation bytes", []]
vs[1].append((False, b'\x80'))
vs[1].append((False, b'\xbf'))
vs[1].append((False, b'\x80\xbf'))
vs[1].append((False, b'\x80\xbf\x80'))
vs[1].append((False, b'\x80\xbf\x80\xbf'))
vs[1].append((False, b'\x80\xbf\x80\xbf\x80'))
vs[1].append((False, b'\x80\xbf\x80\xbf\x80\xbf'))
s = b''
# 3.2 Lonely start characters
vs = [b"Lonely start characters", []]
m = [(0xc0, 0xdf), (0xe0, 0xef), (0xf0, 0xf7), (0xf8, 0xfb), (0xfc, 0xfd)]
for mm in m:
s = b''
for i in range(mm[0], mm[1]):
s += struct.pack('BB', i, 0x20)
# s += chr(i)
# s += chr(0x20)
vs[1].append((False, s))
UTF8_TEST_SEQUENCES.append(vs)
# 3.3 Sequences with last continuation byte missing
vs = [b"Sequences with last continuation byte missing", []]
k = [b'\xc0', b'\xe0\x80', b'\xf0\x80\x80', b'\xf8\x80\x80\x80', b'\xfc\x80\x80\x80\x80',
b'\xdf', b'\xef\xbf', b'\xf7\xbf\xbf', b'\xfb\xbf\xbf\xbf', b'\xfd\xbf\xbf\xbf\xbf']
for kk in k:
vs[1].append((False, kk))
UTF8_TEST_SEQUENCES.append(vs)
# 3.4 Concatenation of incomplete sequences
vs = [b"Concatenation of incomplete sequences", []]
vs[1].append((False, b''.join(k)))
UTF8_TEST_SEQUENCES.append(vs)
# 3.5 Impossible bytes
vs = [b"Impossible bytes", []]
vs[1].append((False, b'\xfe'))
vs[1].append((False, b'\xff'))
vs[1].append((False, b'\xfe\xfe\xff\xff'))
UTF8_TEST_SEQUENCES.append(vs)
# 4.1 Examples of an overlong ASCII character
vs = [b"Examples of an overlong ASCII character", []]
vs[1].append((False, b'\xc0\xaf'))
vs[1].append((False, b'\xe0\x80\xaf'))
vs[1].append((False, b'\xf0\x80\x80\xaf'))
vs[1].append((False, b'\xf8\x80\x80\x80\xaf'))
vs[1].append((False, b'\xfc\x80\x80\x80\x80\xaf'))
UTF8_TEST_SEQUENCES.append(vs)
# 4.2 Maximum overlong sequences
vs = [b"Maximum overlong sequences", []]
vs[1].append((False, b'\xc1\xbf'))
vs[1].append((False, b'\xe0\x9f\xbf'))
vs[1].append((False, b'\xf0\x8f\xbf\xbf'))
vs[1].append((False, b'\xf8\x87\xbf\xbf\xbf'))
vs[1].append((False, b'\xfc\x83\xbf\xbf\xbf\xbf'))
UTF8_TEST_SEQUENCES.append(vs)
# 4.3 Overlong representation of the NUL character
vs = [b"Overlong representation of the NUL character", []]
vs[1].append((False, b'\xc0\x80'))
vs[1].append((False, b'\xe0\x80\x80'))
vs[1].append((False, b'\xf0\x80\x80\x80'))
vs[1].append((False, b'\xf8\x80\x80\x80\x80'))
vs[1].append((False, b'\xfc\x80\x80\x80\x80\x80'))
UTF8_TEST_SEQUENCES.append(vs)
# 5.1 Single UTF-16 surrogates
vs = [b"Single UTF-16 surrogates", []]
vs[1].append((False, b'\xed\xa0\x80'))
vs[1].append((False, b'\xed\xad\xbf'))
vs[1].append((False, b'\xed\xae\x80'))
vs[1].append((False, b'\xed\xaf\xbf'))
vs[1].append((False, b'\xed\xb0\x80'))
vs[1].append((False, b'\xed\xbe\x80'))
vs[1].append((False, b'\xed\xbf\xbf'))
UTF8_TEST_SEQUENCES.append(vs)
# 5.2 Paired UTF-16 surrogates
vs = [b"Paired UTF-16 surrogates", []]
vs[1].append((False, b'\xed\xa0\x80\xed\xb0\x80'))
vs[1].append((False, b'\xed\xa0\x80\xed\xbf\xbf'))
vs[1].append((False, b'\xed\xad\xbf\xed\xb0\x80'))
vs[1].append((False, b'\xed\xad\xbf\xed\xbf\xbf'))
vs[1].append((False, b'\xed\xae\x80\xed\xb0\x80'))
vs[1].append((False, b'\xed\xae\x80\xed\xbf\xbf'))
vs[1].append((False, b'\xed\xaf\xbf\xed\xb0\x80'))
vs[1].append((False, b'\xed\xaf\xbf\xed\xbf\xbf'))
UTF8_TEST_SEQUENCES.append(vs)
# 5.3 Other illegal code positions
# Those are non-character code points and valid UTF-8 by RFC 3629
vs = [b"Non-character code points (valid UTF-8)", []]
# https://bug686312.bugzilla.mozilla.org/attachment.cgi?id=561257
# non-characters: EF BF [BE-BF]
vs[1].append((True, b'\xef\xbf\xbe'))
vs[1].append((True, b'\xef\xbf\xbf'))
# non-characters: F[0-7] [89AB]F BF [BE-BF]
for z1 in [b'\xf0', b'\xf1', b'\xf2', b'\xf3', b'\xf4']:
for z2 in [b'\x8f', b'\x9f', b'\xaf', b'\xbf']:
# those encode codepoints >U+10FFFF
if not (z1 == b'\xf4' and z2 != b'\x8f'):
for z3 in [b'\xbe', b'\xbf']:
zz = z1 + z2 + b'\xbf' + z3
if zz not in [b'\xf0\x8f\xbf\xbe',
b'\xf0\x8f\xbf\xbf']: # filter overlong sequences
vs[1].append((True, zz))
UTF8_TEST_SEQUENCES.append(vs)
# Unicode "specials", such as replacement char etc
# http://en.wikipedia.org/wiki/Specials_%28Unicode_block%29
vs = [b"Unicode specials (i.e. replacement char)", []]
vs[1].append((True, b'\xef\xbf\xb9'))
vs[1].append((True, b'\xef\xbf\xba'))
vs[1].append((True, b'\xef\xbf\xbb'))
vs[1].append((True, b'\xef\xbf\xbc'))
vs[1].append((True, b'\xef\xbf\xbd')) # replacement char
vs[1].append((True, b'\xef\xbf\xbe'))
vs[1].append((True, b'\xef\xbf\xbf'))
UTF8_TEST_SEQUENCES.append(vs)
return UTF8_TEST_SEQUENCES
def _create_valid_utf8_test_sequences():
"""
Generate some exotic, but valid UTF8 test strings.
"""
VALID_UTF8_TEST_SEQUENCES = []
for test in _create_utf8_test_sequences():
valids = [x[1] for x in test[1] if x[0]]
if len(valids) > 0:
VALID_UTF8_TEST_SEQUENCES.append([test[0], valids])
return VALID_UTF8_TEST_SEQUENCES
@unittest.skipIf(not HAS_NVX, 'NVX native extensions not present')
class TestNvxUtf8Validator(unittest.TestCase):
def setUp(self):
# These tests verify the UTF-8 decoder/validator on the various test cases from
# http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
vs = []
for k in _create_utf8_test_sequences():
vs.extend(k[1])
# All Unicode code points
for i in range(
0, 0xffff): # should by 0x10ffff, but non-wide Python build is limited to 16-bits
if i < 0xD800 or i > 0xDFFF: # filter surrogate code points, which are disallowed to encode in UTF-8
vs.append((True, six.unichr(i).encode("utf-8")))
# FIXME: UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800'
# in position 0: surrogates not allowed
if False:
# 5.1 Single UTF-16 surrogates
for i in range(0xD800, 0xDBFF): # high-surrogate
ss = six.unichr(i).encode("utf-8")
vs.append((False, ss))
for i in range(0xDC00, 0xDFFF): # low-surrogate
ss = six.unichr(i).encode("utf-8")
vs.append((False, ss))
# 5.2 Paired UTF-16 surrogates
for i in range(0xD800, 0xDBFF): # high-surrogate
for j in range(0xDC00, 0xDFFF): # low-surrogate
ss1 = six.unichr(i).encode("utf-8")
ss2 = six.unichr(j).encode("utf-8")
vs.append((False, ss1 + ss2))
vs.append((False, ss2 + ss1))
self._TEST_SEQUENCES = vs
def test_standard_utf8validator(self):
"""
Test standard implementation of UTF8 validator.
"""
validator = StandardUtf8Validator()
return self._test_utf8(validator)
def test_nvx_utf8validator(self):
"""
Test NVX implementation of UTF8 validator.
"""
validator = NvxUtf8Validator()
return self._test_utf8(validator)
def test_standard_utf8validator_incremental(self):
"""
Test standard implementation of UTF8 validator in incremental mode.
"""
validator = StandardUtf8Validator()
return self._test_utf8_incremental(validator)
# NVX UTF8 validator lack incremental mode implementation
@unittest.expectedFailure
def test_nvx_utf8validator_incremental(self):
"""
Test NVX implementation of UTF8 validator in incremental mode.
"""
validator = NvxUtf8Validator()
return self._test_utf8_incremental(validator)
def _test_utf8(self, validator):
for s in self._TEST_SEQUENCES:
validator.reset()
r = validator.validate(s[1])
# no UTF-8 decode error _and_ everything consumed
res = r[0] and r[1]
self.assertEqual(res, s[0])
def _test_utf8_incremental(self, validator, withPositions=True):
# These tests verify that the UTF-8 decoder/validator can operate incrementally.
if withPositions:
# testing validator 4 on incremental detection with positions
k = 4
else:
# testing validator 2 on incremental detection without positions
k = 2
validator.reset()
self.assertEqual((True, True, 15, 15)[:k], validator.validate(u'µ@ßöäüàá'.encode('utf8'))[:k])
validator.reset()
self.assertEqual((False, False, 0, 0)[:k], validator.validate(b"\xF5")[:k])
# the following 3 all fail on eating byte 7 (0xA0)
validator.reset()
self.assertEqual((True, True, 6, 6)[:k], validator.validate(b"\x65\x64\x69\x74\x65\x64")[:k])
self.assertEqual((False, False, 1, 7)[:k], validator.validate(b"\xED\xA0\x80")[:k])
validator.reset()
self.assertEqual((True, True, 4, 4)[:k], validator.validate(b"\x65\x64\x69\x74")[:k])
self.assertEqual((False, False, 3, 7)[:k], validator.validate(b"\x65\x64\xED\xA0\x80")[:k])
validator.reset()
self.assertEqual((True, False, 7, 7)[:k], validator.validate(b"\x65\x64\x69\x74\x65\x64\xED")[:k])
self.assertEqual((False, False, 0, 7)[:k], validator.validate(b"\xA0\x80")[:k])
@@ -0,0 +1,126 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import unittest
from autobahn.rawsocket.util import create_url, parse_url
class TestCreateRsUrl(unittest.TestCase):
def test_create_url01(self):
self.assertEqual(create_url("localhost"), "rs://localhost:80")
def test_create_url02(self):
self.assertEqual(create_url("localhost", port=8090), "rs://localhost:8090")
def test_create_url03(self):
self.assertEqual(create_url("localhost", isSecure=True), "rss://localhost:443")
def test_create_url04(self):
self.assertEqual(create_url("localhost", isSecure=True, port=443), "rss://localhost:443")
def test_create_url05(self):
self.assertEqual(create_url("localhost", isSecure=True, port=80), "rss://localhost:80")
def test_create_url06(self):
self.assertEqual(create_url("unix", port="file.sock"), "rs://unix:file.sock")
def test_create_url07(self):
self.assertEqual(create_url("unix", port="/tmp/file.sock"), "rs://unix:/tmp/file.sock")
def test_create_url08(self):
self.assertEqual(create_url("unix", port="../file.sock"), "rs://unix:../file.sock")
def test_create_url09(self):
self.assertEqual(create_url("unix", isSecure=True, port="file.sock"), "rss://unix:file.sock")
def test_create_url10(self):
self.assertEqual(create_url("unix", isSecure=True, port="/tmp/file.sock"), "rss://unix:/tmp/file.sock")
def test_create_url11(self):
self.assertEqual(create_url("unix", isSecure=True, port="../file.sock"), "rss://unix:../file.sock")
class TestParseWsUrl(unittest.TestCase):
# parse_url -> (isSecure, host, port)
def test_parse_url01(self):
self.assertEqual(parse_url("rs://localhost"), (False, 'localhost', 80))
def test_parse_url02(self):
self.assertEqual(parse_url("rss://localhost"), (True, 'localhost', 443))
def test_parse_url03(self):
self.assertEqual(parse_url("rs://localhost:9000"), (False, 'localhost', 9000))
def test_parse_url04(self):
self.assertEqual(parse_url("rss://localhost:9000"), (True, 'localhost', 9000))
def test_parse_url05(self):
self.assertRaises(Exception, parse_url, "ws://localhost")
def test_parse_url06(self):
self.assertRaises(Exception, parse_url, "wss://localhost")
def test_parse_url07(self):
self.assertRaises(Exception, parse_url, "ws://localhost:80")
def test_parse_url08(self):
self.assertRaises(Exception, parse_url, "rs://localhost/somepath")
def test_parse_url09(self):
self.assertRaises(Exception, parse_url, "rs://localhost#somefrag")
def test_parse_url10(self):
self.assertRaises(Exception, parse_url, "rs://localhost?foo=bar")
def test_parse_url11(self):
self.assertRaises(Exception, parse_url, "rss://")
def test_parse_url12(self):
self.assertRaises(Exception, parse_url, "rs://")
def test_parse_url13(self):
self.assertEqual(parse_url("rs://unix:file.sock"), (False, 'unix', 'file.sock'))
def test_parse_url14(self):
self.assertEqual(parse_url("rs://unix:/tmp/file.sock"), (False, 'unix', '/tmp/file.sock'))
def test_parse_url15(self):
self.assertEqual(parse_url("rs://unix:../file.sock"), (False, 'unix', '../file.sock'))
def test_parse_url16(self):
self.assertEqual(parse_url("rss://unix:file.sock"), (True, 'unix', 'file.sock'))
def test_parse_url17(self):
self.assertEqual(parse_url("rss://unix:/tmp/file.sock"), (True, 'unix', '/tmp/file.sock'))
def test_parse_url18(self):
self.assertEqual(parse_url("rss://unix:../file.sock"), (True, 'unix', '../file.sock'))
@@ -0,0 +1,111 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import sys
import unittest
import uuid
import random
from nacl import utils, public
from autobahn import util
@unittest.skipIf(not sys.platform.startswith('linux'), 'entropy depletion tests only available on Linux')
class TestEntropy(unittest.TestCase):
def test_non_depleting(self):
res = {}
with open('/dev/urandom', 'rb') as rng:
for i in range(1000):
for j in range(100):
# "reseed" (seems pointless, but ..)
random.seed()
# random UUIDs
v1 = uuid.uuid4() # noqa
# stdlib random
v2 = random.random() # noqa
v3 = random.getrandbits(32) # noqa
v4 = random.randint(0, 9007199254740992) # noqa
v5 = random.normalvariate(10, 100) # noqa
v6 = random.choice(range(100)) # noqa
# PyNaCl
v7 = utils.random(public.Box.NONCE_SIZE) # noqa
# Autobahn utils
v8 = util.generate_token(4, 4) # noqa
v9 = util.id() # noqa
v10 = util.rid() # noqa
v11 = util.newid() # noqa
# direct procfs access to PRNG
d = rng.read(1000) # noqa
# check available entropy
with open('/proc/sys/kernel/random/entropy_avail', 'r') as ent:
ea = int(ent.read()) // 100
if ea not in res:
res[ea] = 0
res[ea] += 1
skeys = sorted(res.keys())
print('\nsystem entropy depletion stats:')
for k in skeys:
print('{}: {}'.format(k, res[k]))
self.assertTrue(skeys[0] > 10)
def test_depleting(self):
res = {}
with open('/dev/random', 'rb') as rng:
for i in range(10000):
# direct procfs access to "real" RNG
d = rng.read(1000) # noqa
# check available entropy
with open('/proc/sys/kernel/random/entropy_avail', 'r') as ent:
ea = int(ent.read()) // 100
if ea not in res:
res[ea] = 0
res[ea] += 1
skeys = sorted(res.keys())
print('\nsystem entropy depletion stats:')
for k in skeys:
print('{}: {}'.format(k, res[k]))
self.assertTrue(skeys[0] == 0)
@@ -0,0 +1,48 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import unittest
from autobahn.util import IdGenerator
class TestIdGenerator(unittest.TestCase):
def test_idgenerator_is_generator(self):
"IdGenerator follows the generator protocol"
g = IdGenerator()
self.assertEqual(1, next(g))
self.assertEqual(2, next(g))
def test_generator_wrap(self):
g = IdGenerator()
g._next = 2 ** 53 - 1 # cheat a little
v = next(g)
self.assertEqual(v, 2 ** 53)
v = next(g)
self.assertEqual(v, 1)
@@ -0,0 +1,89 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import sys
import platform
import twisted
import autobahn
# Twisted specific utilities (these should really be in Twisted, but
# they aren't, and we use these in example code, so it must be part of
# the public API)
from autobahn.twisted.util import sleep
from autobahn.twisted.choosereactor import install_reactor
# WebSocket protocol support
from autobahn.twisted.websocket import \
WebSocketServerProtocol, \
WebSocketClientProtocol, \
WebSocketServerFactory, \
WebSocketClientFactory
# support for running Twisted stream protocols over WebSocket
from autobahn.twisted.websocket import WrappingWebSocketServerFactory, \
WrappingWebSocketClientFactory
# Twisted Web support - FIXME: these imports trigger import of Twisted reactor!
# from autobahn.twisted.resource import WebSocketResource, WSGIRootResource
# WAMP support
from autobahn.twisted.wamp import ApplicationSession
__all__ = (
# this should really be in Twisted
'sleep',
'install_reactor',
# WebSocket
'WebSocketServerProtocol',
'WebSocketClientProtocol',
'WebSocketServerFactory',
'WebSocketClientFactory',
# wrapping stream protocols in WebSocket
'WrappingWebSocketServerFactory',
'WrappingWebSocketClientFactory',
# Twisted Web - FIXME: see comment for import above
# 'WebSocketResource',
# this should really be in Twisted - FIXME: see comment for import above
# 'WSGIRootResource',
# WAMP support
'ApplicationSession',
)
__ident__ = u'Autobahn/{}-Twisted/{}-{}/{}'.format(autobahn.__version__, twisted.__version__, platform.python_implementation(), '.'.join([str(x) for x in list(sys.version_info[:3])]))
"""
AutobahnPython library implementation (eg. "Autobahn/0.13.0-Twisted/15.5.0-CPython/3.5.1")
"""
@@ -0,0 +1,870 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", fWITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import os
import time
import struct
import sys
import re
import base64
import math
import random
import binascii
from datetime import datetime, timedelta
from pprint import pformat
from array import array
import six
import txaio
try:
_TLS = True
from OpenSSL import SSL
except ImportError:
_TLS = False
__all__ = ("public",
"encode_truncate",
"xor",
"utcnow",
"utcstr",
"id",
"rid",
"newid",
"rtime",
"Stopwatch",
"Tracker",
"EqualityMixin",
"ObservableMixin",
"IdGenerator",
"generate_token",
"generate_activation_code",
"generate_serial_number",
"generate_user_password")
def public(obj):
"""
The public user API of Autobahn is marked using this decorator.
Everything that is not decorated @public is library internal, can
change at any time and should not be used in user program code.
"""
try:
obj._is_public = True
except AttributeError:
# FIXME: exceptions.AttributeError: 'staticmethod' object has no attribute '_is_public'
pass
return obj
@public
def encode_truncate(text, limit, encoding='utf8', return_encoded=True):
"""
Given a string, return a truncated version of the string such that
the UTF8 encoding of the string is smaller than the given limit.
This function correctly truncates even in the presence of Unicode code
points that encode to multi-byte encodings which must not be truncated
in the middle.
:param text: The (Unicode) string to truncate.
:type text: str
:param limit: The number of bytes to limit the UTF8 encoding to.
:type limit: int
:param encoding: Truncate the string in this encoding (default is ``utf-8``).
:type encoding: str
:param return_encoded: If ``True``, return the string encoded into bytes
according to the specified encoding, else return the string as a string.
:type return_encoded: bool
:returns: The truncated string.
:rtype: str or bytes
"""
assert(text is None or type(text) == six.text_type)
assert(type(limit) in six.integer_types)
assert(limit >= 0)
if text is None:
return
# encode the given string in the specified encoding
s = text.encode(encoding)
# when the resulting byte string is longer than the given limit ..
if len(s) > limit:
# .. truncate, and
s = s[:limit]
# decode back, ignoring errors that result from truncation
# in the middle of multi-byte encodings
text = s.decode(encoding, 'ignore')
if return_encoded:
s = text.encode(encoding)
if return_encoded:
return s
else:
return text
@public
def xor(d1, d2):
"""
XOR two binary strings of arbitrary (equal) length.
:param d1: The first binary string.
:type d1: binary
:param d2: The second binary string.
:type d2: binary
:returns: XOR of the binary strings (``XOR(d1, d2)``)
:rtype: bytes
"""
if type(d1) != six.binary_type:
raise Exception("invalid type {} for d1 - must be binary".format(type(d1)))
if type(d2) != six.binary_type:
raise Exception("invalid type {} for d2 - must be binary".format(type(d2)))
if len(d1) != len(d2):
raise Exception("cannot XOR binary string of differing length ({} != {})".format(len(d1), len(d2)))
d1 = array('B', d1)
d2 = array('B', d2)
for i in range(len(d1)):
d1[i] ^= d2[i]
if six.PY3:
return d1.tobytes()
else:
return d1.tostring()
@public
def utcstr(ts=None):
"""
Format UTC timestamp in ISO 8601 format.
Note: to parse an ISO 8601 formatted string, use the **iso8601**
module instead (e.g. ``iso8601.parse_date("2014-05-23T13:03:44.123Z")``).
:param ts: The timestamp to format.
:type ts: instance of :py:class:`datetime.datetime` or ``None``
:returns: Timestamp formatted in ISO 8601 format.
:rtype: str
"""
assert(ts is None or isinstance(ts, datetime))
if ts is None:
ts = datetime.utcnow()
return u"{0}Z".format(ts.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3])
@public
def utcnow():
"""
Get current time in UTC as ISO 8601 string.
:returns: Current time as string in ISO 8601 format.
:rtype: str
"""
return utcstr()
class IdGenerator(object):
"""
ID generator for WAMP request IDs.
WAMP request IDs are sequential per WAMP session, starting at 1 and
wrapping around at 2**53 (both value are inclusive [1, 2**53]).
The upper bound **2**53** is chosen since it is the maximum integer that can be
represented as a IEEE double such that all smaller integers are representable as well.
Hence, IDs can be safely used with languages that use IEEE double as their
main (or only) number type (JavaScript, Lua, etc).
See https://github.com/wamp-proto/wamp-proto/blob/master/spec/basic.md#ids
"""
def __init__(self):
self._next = 0 # starts at 1; next() pre-increments
def next(self):
"""
Returns next ID.
:returns: The next ID.
:rtype: int
"""
self._next += 1
if self._next > 9007199254740992:
self._next = 1
return self._next
# generator protocol
def __next__(self):
return self.next()
#
# Performance comparison of IdGenerator.next(), id() and rid().
#
# All tests were performed on:
#
# - Ubuntu 14.04 LTS x86-64
# - Intel Core i7 920 @ 3.3GHz
#
# The tests generated 100 mio. IDs and run-time was measured
# as wallclock from Unix "time" command. In each run, a single CPU
# core was essentially at 100% load all the time (though the sys/usr
# ratio was different).
#
# PyPy 2.6.1:
#
# IdGenerator.next() 0.5s
# id() 29.4s
# rid() 106.1s
#
# CPython 2.7.10:
#
# IdGenerator.next() 49.0s
# id() 370.5s
# rid() 196.4s
#
#
# Note on the ID range [0, 2**53]. We once reduced the range to [0, 2**31].
# This lead to extremely hard to track down issues due to ID collisions!
# Here: https://github.com/crossbario/autobahn-python/issues/419#issue-90483337
#
# 8 byte mask with 53 LSBs set (WAMP requires IDs from [0, 2**53]
_WAMP_ID_MASK = struct.unpack(">Q", b"\x00\x1f\xff\xff\xff\xff\xff\xff")[0]
def rid():
"""
Generate a new random integer ID from range **[0, 2**53]**.
The generated ID is uniformly distributed over the whole range, doesn't have
a period (no pseudo-random generator is used) and cryptographically strong.
The upper bound **2**53** is chosen since it is the maximum integer that can be
represented as a IEEE double such that all smaller integers are representable as well.
Hence, IDs can be safely used with languages that use IEEE double as their
main (or only) number type (JavaScript, Lua, etc).
:returns: A random integer ID.
:rtype: int
"""
return struct.unpack("@Q", os.urandom(8))[0] & _WAMP_ID_MASK
# noinspection PyShadowingBuiltins
def id():
"""
Generate a new random integer ID from range **[0, 2**53]**.
The generated ID is based on a pseudo-random number generator (Mersenne Twister,
which has a period of 2**19937-1). It is NOT cryptographically strong, and
hence NOT suitable to generate e.g. secret keys or access tokens.
The upper bound **2**53** is chosen since it is the maximum integer that can be
represented as a IEEE double such that all smaller integers are representable as well.
Hence, IDs can be safely used with languages that use IEEE double as their
main (or only) number type (JavaScript, Lua, etc).
:returns: A random integer ID.
:rtype: int
"""
return random.randint(0, 9007199254740992)
def newid(length=16):
"""
Generate a new random string ID.
The generated ID is uniformly distributed and cryptographically strong. It is
hence usable for things like secret keys and access tokens.
:param length: The length (in chars) of the ID to generate.
:type length: int
:returns: A random string ID.
:rtype: str
"""
l = int(math.ceil(float(length) * 6. / 8.))
return base64.b64encode(os.urandom(l))[:length].decode('ascii')
# a standard base36 character set
# DEFAULT_TOKEN_CHARS = string.digits + string.ascii_uppercase
# we take out the following 9 chars (leaving 27), because there
# is visual ambiguity: 0/O/D, 1/I, 8/B, 2/Z
DEFAULT_TOKEN_CHARS = u'345679ACEFGHJKLMNPQRSTUVWXY'
"""
Default set of characters to create rtokens from.
"""
DEFAULT_ZBASE32_CHARS = u'13456789abcdefghijkmnopqrstuwxyz'
"""
Our choice of confusing characters to eliminate is: `0', `l', `v', and `2'. Our
reasoning is that `0' is potentially mistaken for `o', that `l' is potentially
mistaken for `1' or `i', that `v' is potentially mistaken for `u' or `r'
(especially in handwriting) and that `2' is potentially mistaken for `z'
(especially in handwriting).
Note that we choose to focus on typed and written transcription more than on
vocal, since humans already have a well-established system of disambiguating
spoken alphanumerics, such as the United States military's "Alpha Bravo Charlie
Delta" and telephone operators' "Is that 'd' as in 'dog'?".
* http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
"""
@public
def generate_token(char_groups, chars_per_group, chars=None, sep=None, lower_case=False):
"""
Generate cryptographically strong tokens, which are strings like `M6X5-YO5W-T5IK`.
These can be used e.g. for used-only-once activation tokens or the like.
The returned token has an entropy of
``math.log(len(chars), 2.) * chars_per_group * char_groups``
bits.
With the default charset and 4 characters per group, ``generate_token()`` produces
strings with the following entropy:
================ =================== ========================================
character groups entropy (at least) recommended use
================ =================== ========================================
2 38 bits
3 57 bits one-time activation or pairing code
4 76 bits secure user password
5 95 bits
6 114 bits globally unique serial / product code
7 133 bits
================ =================== ========================================
Here are some examples:
* token(3): ``9QXT-UXJW-7R4H``
* token(4): ``LPNN-JMET-KWEP-YK45``
* token(6): ``NXW9-74LU-6NUH-VLPV-X6AG-QUE3``
:param char_groups: Number of character groups (or characters if chars_per_group == 1).
:type char_groups: int
:param chars_per_group: Number of characters per character group (or 1 to return a token with no grouping).
:type chars_per_group: int
:param chars: Characters to choose from. Default is 27 character subset
of the ISO basic Latin alphabet (see: ``DEFAULT_TOKEN_CHARS``).
:type chars: str or None
:param sep: When separating groups in the token, the separater string.
:type sep: str
:param lower_case: If ``True``, generate token in lower-case.
:type lower_case: bool
:returns: The generated token.
:rtype: str
"""
assert(type(char_groups) in six.integer_types)
assert(type(chars_per_group) in six.integer_types)
assert(chars is None or type(chars) == six.text_type)
chars = chars or DEFAULT_TOKEN_CHARS
if lower_case:
chars = chars.lower()
sep = sep or u'-'
rng = random.SystemRandom()
token_value = u''.join(rng.choice(chars) for _ in range(char_groups * chars_per_group))
if chars_per_group > 1:
return sep.join(map(u''.join, zip(*[iter(token_value)] * chars_per_group)))
else:
return token_value
@public
def generate_activation_code():
"""
Generate a one-time activation code or token of the form ``u'W97F-96MJ-YGJL'``.
The generated value is cryptographically strong and has (at least) 57 bits of entropy.
:returns: The generated activation code.
:rtype: str
"""
return generate_token(char_groups=3, chars_per_group=4, chars=DEFAULT_TOKEN_CHARS, sep=u'-', lower_case=False)
@public
def generate_user_password():
"""
Generate a secure, random user password of the form ``u'kgojzi61dn5dtb6d'``.
The generated value is cryptographically strong and has (at least) 76 bits of entropy.
:returns: The generated password.
:rtype: str
"""
return generate_token(char_groups=16, chars_per_group=1, chars=DEFAULT_ZBASE32_CHARS, sep=u'-', lower_case=True)
@public
def generate_serial_number():
"""
Generate a globally unique serial / product code of the form ``u'YRAC-EL4X-FQQE-AW4T-WNUV-VN6T'``.
The generated value is cryptographically strong and has (at least) 114 bits of entropy.
:returns: The generated serial number / product code.
:rtype: str
"""
return generate_token(char_groups=6, chars_per_group=4, chars=DEFAULT_TOKEN_CHARS, sep=u'-', lower_case=False)
# Select the most precise walltime measurement function available
# on the platform
#
if sys.platform.startswith('win'):
# On Windows, this function returns wall-clock seconds elapsed since the
# first call to this function, as a floating point number, based on the
# Win32 function QueryPerformanceCounter(). The resolution is typically
# better than one microsecond
if sys.version_info >= (3, 8):
_rtime = time.perf_counter
else:
_rtime = time.clock
_ = _rtime() # this starts wallclock
else:
# On Unix-like platforms, this used the first available from this list:
# (1) gettimeofday() -- resolution in microseconds
# (2) ftime() -- resolution in milliseconds
# (3) time() -- resolution in seconds
_rtime = time.time
@public
def rtime():
"""
Precise, fast wallclock time.
:returns: The current wallclock in seconds. Returned values are only guaranteed
to be meaningful relative to each other.
:rtype: float
"""
return _rtime()
class Stopwatch(object):
"""
Stopwatch based on walltime.
This can be used to do code timing and uses the most precise walltime measurement
available on the platform. This is a very light-weight object,
so create/dispose is very cheap.
"""
def __init__(self, start=True):
"""
:param start: If ``True``, immediately start the stopwatch.
:type start: bool
"""
self._elapsed = 0
if start:
self._started = rtime()
self._running = True
else:
self._started = None
self._running = False
def elapsed(self):
"""
Return total time elapsed in seconds during which the stopwatch was running.
:returns: The elapsed time in seconds.
:rtype: float
"""
if self._running:
now = rtime()
return self._elapsed + (now - self._started)
else:
return self._elapsed
def pause(self):
"""
Pauses the stopwatch and returns total time elapsed in seconds during which
the stopwatch was running.
:returns: The elapsed time in seconds.
:rtype: float
"""
if self._running:
now = rtime()
self._elapsed += now - self._started
self._running = False
return self._elapsed
else:
return self._elapsed
def resume(self):
"""
Resumes a paused stopwatch and returns total elapsed time in seconds
during which the stopwatch was running.
:returns: The elapsed time in seconds.
:rtype: float
"""
if not self._running:
self._started = rtime()
self._running = True
return self._elapsed
else:
now = rtime()
return self._elapsed + (now - self._started)
def stop(self):
"""
Stops the stopwatch and returns total time elapsed in seconds during which
the stopwatch was (previously) running.
:returns: The elapsed time in seconds.
:rtype: float
"""
elapsed = self.pause()
self._elapsed = 0
self._started = None
self._running = False
return elapsed
class Tracker(object):
"""
A key-based statistics tracker.
"""
def __init__(self, tracker, tracked):
"""
"""
self.tracker = tracker
self.tracked = tracked
self._timings = {}
self._offset = rtime()
self._dt_offset = datetime.utcnow()
def track(self, key):
"""
Track elapsed for key.
:param key: Key under which to track the timing.
:type key: str
"""
self._timings[key] = rtime()
def diff(self, start_key, end_key, formatted=True):
"""
Get elapsed difference between two previously tracked keys.
:param start_key: First key for interval (older timestamp).
:type start_key: str
:param end_key: Second key for interval (younger timestamp).
:type end_key: str
:param formatted: If ``True``, format computed time period and return string.
:type formatted: bool
:returns: Computed time period in seconds (or formatted string).
:rtype: float or str
"""
if end_key in self._timings and start_key in self._timings:
d = self._timings[end_key] - self._timings[start_key]
if formatted:
if d < 0.00001: # 10us
s = "%d ns" % round(d * 1000000000.)
elif d < 0.01: # 10ms
s = "%d us" % round(d * 1000000.)
elif d < 10: # 10s
s = "%d ms" % round(d * 1000.)
else:
s = "%d s" % round(d)
return s.rjust(8)
else:
return d
else:
if formatted:
return "n.a.".rjust(8)
else:
return None
def absolute(self, key):
"""
Return the UTC wall-clock time at which a tracked event occurred.
:param key: The key
:type key: str
:returns: Timezone-naive datetime.
:rtype: instance of :py:class:`datetime.datetime`
"""
elapsed = self[key]
if elapsed is None:
raise KeyError("No such key \"%s\"." % elapsed)
return self._dt_offset + timedelta(seconds=elapsed)
def __getitem__(self, key):
if key in self._timings:
return self._timings[key] - self._offset
else:
return None
def __iter__(self):
return self._timings.__iter__()
def __str__(self):
return pformat(self._timings)
class EqualityMixin(object):
"""
Mixing to add equality comparison operators to a class.
Two objects are identical under this mixin, if and only if:
1. both object have the same class
2. all non-private object attributes are equal
"""
def __eq__(self, other):
"""
Compare this object to another object for equality.
:param other: The other object to compare with.
:type other: obj
:returns: ``True`` iff the objects are equal.
:rtype: bool
"""
if not isinstance(other, self.__class__):
return False
# we only want the actual message data attributes (not eg _serialize)
for k in self.__dict__:
if not k.startswith('_'):
if not self.__dict__[k] == other.__dict__[k]:
return False
return True
# return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__)
def __ne__(self, other):
"""
Compare this object to another object for inequality.
:param other: The other object to compare with.
:type other: obj
:returns: ``True`` iff the objects are not equal.
:rtype: bool
"""
return not self.__eq__(other)
def wildcards2patterns(wildcards):
"""
Compute a list of regular expression patterns from a list of
wildcard strings. A wildcard string uses '*' as a wildcard character
matching anything.
:param wildcards: List of wildcard strings to compute regular expression patterns for.
:type wildcards: list of str
:returns: Computed regular expressions.
:rtype: list of obj
"""
# note that we add the ^ and $ so that the *entire* string must
# match. Without this, e.g. a prefix will match:
# re.match('.*good\\.com', 'good.com.evil.com') # match!
# re.match('.*good\\.com$', 'good.com.evil.com') # no match!
return [re.compile('^' + wc.replace('.', r'\.').replace('*', '.*') + '$') for wc in wildcards]
class ObservableMixin(object):
"""
Internal utility for enabling event-listeners on particular objects
"""
# A "helper" style composable class (as opposed to a mix-in) might
# be a lot easier to deal with here. Having an __init__ method
# with a "mix in" style class can be fragile and error-prone,
# especially if it takes arguments. Since we don't use the
# "parent" beavior anywhere, I didn't add a .set_parent() (yet?)
# these are class-level globals; individual instances are
# initialized as-needed (e.g. the first .on() call adds a
# _listeners dict). Thus, subclasses don't have to call super()
# properly etc.
_parent = None
_valid_events = None
_listeners = None
def set_valid_events(self, valid_events=None):
"""
:param valid_events: if non-None, .on() or .fire() with an event
not listed in valid_events raises an exception.
"""
self._valid_events = list(valid_events)
def _check_event(self, event):
"""
Internal helper. Throws RuntimeError if we have a valid_events
list, and the given event isnt' in it. Does nothing otherwise.
"""
if self._valid_events and event not in self._valid_events:
raise RuntimeError(
"Invalid event '{event}'. Expected one of: {events}".format(
event=event,
events=', '.join(self._valid_events),
)
)
def on(self, event, handler):
"""
Add a handler for an event.
:param event: the name of the event
:param handler: a callable thats invoked when .fire() is
called for this events. Arguments will be whatever are given
to .fire()
"""
# print("adding '{}' to '{}': {}".format(event, hash(self), handler))
self._check_event(event)
if self._listeners is None:
self._listeners = dict()
if event not in self._listeners:
self._listeners[event] = []
self._listeners[event].append(handler)
def off(self, event=None, handler=None):
"""
Stop listening for a single event, or all events.
:param event: if None, remove all listeners. Otherwise, remove
listeners for the single named event.
:param handler: if None, remove all handlers for the named
event; otherwise remove just the given handler.
"""
if event is None:
if handler is not None:
# maybe this should mean "remove the given handler
# from any event at all that contains it"...?
raise RuntimeError(
"Can't specificy a specific handler without an event"
)
self._listeners = dict()
else:
if self._listeners is None:
return
self._check_event(event)
if event in self._listeners:
if handler is None:
del self._listeners[event]
else:
self._listeners[event].discard(handler)
def fire(self, event, *args, **kwargs):
"""
Fire a particular event.
:param event: the event to fire. All other args and kwargs are
passed on to the handler(s) for the event.
:return: a Deferred/Future gathering all async results from
all handlers and/or parent handlers.
"""
# print("firing '{}' from '{}'".format(event, hash(self)))
if self._listeners is None:
return txaio.create_future(result=[])
self._check_event(event)
res = []
for handler in self._listeners.get(event, []):
future = txaio.as_future(handler, *args, **kwargs)
res.append(future)
if self._parent is not None:
res.append(self._parent.fire(event, *args, **kwargs))
return txaio.gather(res, consume_exceptions=False)
class _LazyHexFormatter(object):
"""
This is used to avoid calling binascii.hexlify() on data given to
log.debug() calls unless debug is active (for example). Like::
self.log.debug(
"Some data: {octets}",
octets=_LazyHexFormatter(os.urandom(32)),
)
"""
__slots__ = ('obj',)
def __init__(self, obj):
self.obj = obj
def __str__(self):
return binascii.hexlify(self.obj).decode('ascii')
def _is_tls_error(instance):
"""
:returns: True if we have TLS support and 'instance' is an
instance of :class:`OpenSSL.SSL.Error` otherwise False
"""
if _TLS:
return isinstance(instance, SSL.Error)
return False
def _maybe_tls_reason(instance):
"""
:returns: a TLS error-message, or empty-string if 'instance' is
not a TLS error.
"""
if _is_tls_error(instance):
ssl_error = instance.args[0][0]
return u"SSL error: {msg} (in {func})".format(
func=ssl_error[1],
msg=ssl_error[2],
)
return u""
@@ -0,0 +1,83 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
from autobahn.wamp.types import \
ComponentConfig, \
SessionDetails, \
CloseDetails, \
RegisterOptions, \
CallOptions, \
CallDetails, \
CallResult, \
SubscribeOptions, \
PublishOptions, \
EventDetails
from autobahn.wamp.exception import \
Error, \
SessionNotReady, \
SerializationError, \
ProtocolError, \
TransportLost, \
ApplicationError, \
InvalidUri
from autobahn.wamp.interfaces import ISession
from autobahn.wamp.uri import \
error, \
register, \
subscribe
__all__ = (
'ComponentConfig',
'SessionDetails',
'CloseDetails',
'RegisterOptions',
'CallOptions',
'CallDetails',
'CallResult',
'SubscribeOptions',
'PublishOptions',
'EventDetails',
'Error',
'SessionNotReady',
'SerializationError',
'ProtocolError',
'TransportLost',
'ApplicationError',
'InvalidUri',
'ISession',
'error',
'register',
'subscribe',
)
@@ -0,0 +1,314 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import six
from autobahn.util import public
from autobahn.wamp.uri import error
__all__ = (
'Error',
'SessionNotReady',
'SerializationError',
'ProtocolError',
'TransportLost',
'ApplicationError',
'NotAuthorized',
'InvalidUri',
)
@public
class Error(RuntimeError):
"""
Base class for all exceptions related to WAMP.
"""
@public
class SessionNotReady(Error):
"""
The application tried to perform a WAMP interaction, but the
session is not yet fully established.
"""
@public
class SerializationError(Error):
"""
Exception raised when the WAMP serializer could not serialize the
application payload (``args`` or ``kwargs`` for ``CALL``, ``PUBLISH``, etc).
"""
@public
class InvalidUriError(Error):
"""
Exception raised when an invalid WAMP URI was used.
"""
@public
class ProtocolError(Error):
"""
Exception raised when WAMP protocol was violated. Protocol errors
are fatal and are handled by the WAMP implementation. They are
not supposed to be handled at the application level.
"""
@public
class TransportLost(Error):
"""
Exception raised when the transport underlying the WAMP session
was lost or is not connected.
"""
@public
class ApplicationError(Error):
"""
Base class for all exceptions that can/may be handled
at the application level.
"""
INVALID_URI = u"wamp.error.invalid_uri"
"""
Peer provided an incorrect URI for a URI-based attribute of a WAMP message
such as a realm, topic or procedure.
"""
INVALID_PAYLOAD = u"wamp.error.invalid_payload"
"""
The application payload could not be serialized.
"""
PAYLOAD_SIZE_EXCEEDED = u"wamp.error.payload_size_exceeded"
"""
The application payload could not be transported becuase the serialized/framed payload
exceeds the transport limits.
"""
NO_SUCH_PROCEDURE = u"wamp.error.no_such_procedure"
"""
A Dealer could not perform a call, since not procedure is currently registered
under the given URI.
"""
PROCEDURE_ALREADY_EXISTS = u"wamp.error.procedure_already_exists"
"""
A procedure could not be registered, since a procedure with the given URI is
already registered.
"""
PROCEDURE_EXISTS_INVOCATION_POLICY_CONFLICT = u"wamp.error.procedure_exists_with_different_invocation_policy"
"""
A procedure could not be registered, since a procedure with the given URI is
already registered, and the registration has a conflicting invocation policy.
"""
NO_SUCH_REGISTRATION = u"wamp.error.no_such_registration"
"""
A Dealer could not perform a unregister, since the given registration is not active.
"""
NO_SUCH_SUBSCRIPTION = u"wamp.error.no_such_subscription"
"""
A Broker could not perform a unsubscribe, since the given subscription is not active.
"""
NO_SUCH_SESSION = u"wamp.error.no_such_session"
"""
A router could not perform an operation, since a session ID specified was non-existant.
"""
INVALID_ARGUMENT = u"wamp.error.invalid_argument"
"""
A call failed, since the given argument types or values are not acceptable to the
called procedure - in which case the *Callee* may throw this error. Or a Router
performing *payload validation* checked the payload (``args`` / ``kwargs``) of a call,
call result, call error or publish, and the payload did not conform.
"""
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
SYSTEM_SHUTDOWN = u"wamp.error.system_shutdown"
"""
The *Peer* is shutting down completely - used as a ``GOODBYE`` (or ``ABORT``) reason.
"""
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
CLOSE_REALM = u"wamp.error.close_realm"
"""
The *Peer* want to leave the realm - used as a ``GOODBYE`` reason.
"""
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
GOODBYE_AND_OUT = u"wamp.error.goodbye_and_out"
"""
A *Peer* acknowledges ending of a session - used as a ``GOOBYE`` reply reason.
"""
NOT_AUTHORIZED = u"wamp.error.not_authorized"
"""
A call, register, publish or subscribe failed, since the session is not authorized
to perform the operation.
"""
AUTHORIZATION_FAILED = u"wamp.error.authorization_failed"
"""
A Dealer or Broker could not determine if the *Peer* is authorized to perform
a join, call, register, publish or subscribe, since the authorization operation
*itself* failed. E.g. a custom authorizer did run into an error.
"""
AUTHENTICATION_FAILED = u"wamp.error.authentication_failed"
"""
Something failed with the authentication itself, that is, authentication could
not run to end.
"""
NO_AUTH_METHOD = u"wamp.error.no_auth_method"
"""
No authentication method the peer offered is available or active.
"""
NO_SUCH_REALM = u"wamp.error.no_such_realm"
"""
Peer wanted to join a non-existing realm (and the *Router* did not allow to auto-create
the realm).
"""
NO_SUCH_ROLE = u"wamp.error.no_such_role"
"""
A *Peer* was to be authenticated under a Role that does not (or no longer) exists on the Router.
For example, the *Peer* was successfully authenticated, but the Role configured does not
exists - hence there is some misconfiguration in the Router.
"""
NO_SUCH_PRINCIPAL = u"wamp.error.no_such_principal"
"""
A *Peer* was authenticated for an authid that does not or longer exists.
"""
CANCELED = u"wamp.error.canceled"
"""
A Dealer or Callee canceled a call previously issued (WAMP AP).
"""
TIMEOUT = u"wamp.error.timeout"
"""
A pending (in-flight) call was timed out.
"""
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
NO_ELIGIBLE_CALLEE = u"wamp.error.no_eligible_callee"
"""
A *Dealer* could not perform a call, since a procedure with the given URI is registered,
but *Callee Black- and Whitelisting* and/or *Caller Exclusion* lead to the
exclusion of (any) *Callee* providing the procedure (WAMP AP).
"""
ENC_NO_PAYLOAD_CODEC = u"wamp.error.no_payload_codec"
"""
WAMP message in payload transparency mode received, but no codec set
or codec did not decode the payload.
"""
ENC_TRUSTED_URI_MISMATCH = u"wamp.error.encryption.trusted_uri_mismatch"
"""
WAMP-cryptobox application payload end-to-end encryption error.
"""
ENC_DECRYPT_ERROR = u"wamp.error.encryption.decrypt_error"
"""
WAMP-cryptobox application payload end-to-end encryption error.
"""
def __init__(self, error, *args, **kwargs):
"""
:param error: The URI of the error that occurred, e.g. ``wamp.error.not_authorized``.
:type error: str
"""
Exception.__init__(self, *args)
self.kwargs = kwargs
self.error = error
self.enc_algo = kwargs.pop('enc_algo', None)
self.callee = kwargs.pop('callee', None)
self.callee_authid = kwargs.pop('callee_authid', None)
self.callee_authrole = kwargs.pop('callee_authrole', None)
self.forward_for = kwargs.pop('forward_for', None)
@public
def error_message(self):
"""
Get the error message of this exception.
:returns: The error message.
:rtype: str
"""
return u'{0}: {1}'.format(
self.error,
u' '.join([six.text_type(a) for a in self.args]),
)
def __unicode__(self):
if self.kwargs and 'traceback' in self.kwargs:
tb = u':\n' + u'\n'.join(self.kwargs.pop('traceback')) + u'\n'
self.kwargs['traceback'] = u'...'
else:
tb = u''
return u"ApplicationError(error=<{0}>, args={1}, kwargs={2}, enc_algo={3}, callee={4}, callee_authid={5}, callee_authrole={6}, forward_for={7}){8}".format(
self.error, list(self.args), self.kwargs, self.enc_algo, self.callee, self.callee_authid, self.callee_authrole, self.forward_for, tb)
def __str__(self):
if six.PY3:
return self.__unicode__()
else:
return self.__unicode__().encode('utf8')
@error(ApplicationError.NOT_AUTHORIZED)
class NotAuthorized(Exception):
"""
Not authorized to perform the respective action.
"""
@error(ApplicationError.INVALID_URI)
class InvalidUri(Exception):
"""
The URI for a topic, procedure or error is not a valid WAMP URI.
"""
@error(ApplicationError.INVALID_PAYLOAD)
class InvalidPayload(Exception):
"""
The URI for a topic, procedure or error is not a valid WAMP URI.
"""
@@ -0,0 +1,38 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: wamp
import flatbuffers
class Map(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsMap(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Map()
x.Init(buf, n + offset)
return x
# Map
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Map
def Key(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
# Map
def Value(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
def MapStart(builder): builder.StartObject(2)
def MapAddKey(builder, key): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(key), 0)
def MapAddValue(builder, value): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(value), 0)
def MapEnd(builder): return builder.EndObject()
@@ -0,0 +1,22 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: wamp
import flatbuffers
class Void(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsVoid(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Void()
x.Init(buf, n + offset)
return x
# Void
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
def VoidStart(builder): builder.StartObject(0)
def VoidEnd(builder): return builder.EndObject()
@@ -0,0 +1,11 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
class AuthFactor(object):
NONE = 0
AuthTicketRequest = 1
AuthCraRequest = 2
AuthScramRequest = 3
AuthCryptosignRequest = 4
@@ -0,0 +1,8 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
class AuthMode(object):
FIRST = 0
MULTIFACTOR = 1
@@ -0,0 +1,22 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class AuthTicketChallenge(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsAuthTicketChallenge(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = AuthTicketChallenge()
x.Init(buf, n + offset)
return x
# AuthTicketChallenge
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
def AuthTicketChallengeStart(builder): builder.StartObject(0)
def AuthTicketChallengeEnd(builder): return builder.EndObject()
@@ -0,0 +1,22 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class AuthTicketRequest(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsAuthTicketRequest(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = AuthTicketRequest()
x.Init(buf, n + offset)
return x
# AuthTicketRequest
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
def AuthTicketRequestStart(builder): builder.StartObject(0)
def AuthTicketRequestEnd(builder): return builder.EndObject()
@@ -0,0 +1,102 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class CalleeFeatures(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsCalleeFeatures(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = CalleeFeatures()
x.Init(buf, n + offset)
return x
# CalleeFeatures
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# CalleeFeatures
def CallerIdentification(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def CallTrustlevels(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def CallTimeout(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def CallCanceling(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def ProgressiveCallResults(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def RegistrationRevocation(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def PatternBasedRegistration(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def SharedRegistration(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def PayloadTransparency(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CalleeFeatures
def PayloadEncryptionCryptobox(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
def CalleeFeaturesStart(builder): builder.StartObject(10)
def CalleeFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
def CalleeFeaturesAddCallTrustlevels(builder, callTrustlevels): builder.PrependBoolSlot(1, callTrustlevels, 0)
def CalleeFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(2, callTimeout, 0)
def CalleeFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(3, callCanceling, 0)
def CalleeFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(4, progressiveCallResults, 0)
def CalleeFeaturesAddRegistrationRevocation(builder, registrationRevocation): builder.PrependBoolSlot(5, registrationRevocation, 0)
def CalleeFeaturesAddPatternBasedRegistration(builder, patternBasedRegistration): builder.PrependBoolSlot(6, patternBasedRegistration, 0)
def CalleeFeaturesAddSharedRegistration(builder, sharedRegistration): builder.PrependBoolSlot(7, sharedRegistration, 0)
def CalleeFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(8, payloadTransparency, 0)
def CalleeFeaturesAddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(9, payloadEncryptionCryptobox, 0)
def CalleeFeaturesEnd(builder): return builder.EndObject()
@@ -0,0 +1,70 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class CallerFeatures(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsCallerFeatures(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = CallerFeatures()
x.Init(buf, n + offset)
return x
# CallerFeatures
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# CallerFeatures
def CallerIdentification(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CallerFeatures
def CallTimeout(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CallerFeatures
def CallCanceling(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CallerFeatures
def ProgressiveCallResults(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CallerFeatures
def PayloadTransparency(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# CallerFeatures
def PayloadEncryptionCryptobox(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
def CallerFeaturesStart(builder): builder.StartObject(6)
def CallerFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
def CallerFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(1, callTimeout, 0)
def CallerFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(2, callCanceling, 0)
def CallerFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(3, progressiveCallResults, 0)
def CallerFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(4, payloadTransparency, 0)
def CallerFeaturesAddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(5, payloadEncryptionCryptobox, 0)
def CallerFeaturesEnd(builder): return builder.EndObject()
@@ -0,0 +1,9 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
class CancelMode(object):
SKIP = 0
ABORT = 1
KILL = 2
@@ -0,0 +1,42 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class Challenge(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsChallenge(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Challenge()
x.Init(buf, n + offset)
return x
# Challenge
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Challenge
def Method(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# Challenge
def Extra(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
x = self._tab.Indirect(o + self._tab.Pos)
from .Map import Map
obj = Map()
obj.Init(self._tab.Bytes, x)
return obj
return None
def ChallengeStart(builder): builder.StartObject(2)
def ChallengeAddMethod(builder, method): builder.PrependUint8Slot(0, method, 0)
def ChallengeAddExtra(builder, extra): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(extra), 0)
def ChallengeEnd(builder): return builder.EndObject()
@@ -0,0 +1,8 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
class ChannelBinding(object):
NONE = 0
TLS_UNIQUE = 1
@@ -0,0 +1,126 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class DealerFeatures(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsDealerFeatures(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = DealerFeatures()
x.Init(buf, n + offset)
return x
# DealerFeatures
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# DealerFeatures
def CallerIdentification(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def CallTrustlevels(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def CallTimeout(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def CallCanceling(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def ProgressiveCallResults(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def RegistrationRevocation(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def PatternBasedRegistration(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def SharedRegistration(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def SessionMetaApi(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def RegistrationMetaApi(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def TestamentMetaApi(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def PayloadTransparency(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# DealerFeatures
def PayloadEncryptionCryptobox(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
def DealerFeaturesStart(builder): builder.StartObject(13)
def DealerFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
def DealerFeaturesAddCallTrustlevels(builder, callTrustlevels): builder.PrependBoolSlot(1, callTrustlevels, 0)
def DealerFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(2, callTimeout, 0)
def DealerFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(3, callCanceling, 0)
def DealerFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(4, progressiveCallResults, 0)
def DealerFeaturesAddRegistrationRevocation(builder, registrationRevocation): builder.PrependBoolSlot(5, registrationRevocation, 0)
def DealerFeaturesAddPatternBasedRegistration(builder, patternBasedRegistration): builder.PrependBoolSlot(6, patternBasedRegistration, 0)
def DealerFeaturesAddSharedRegistration(builder, sharedRegistration): builder.PrependBoolSlot(7, sharedRegistration, 0)
def DealerFeaturesAddSessionMetaApi(builder, sessionMetaApi): builder.PrependBoolSlot(8, sessionMetaApi, 0)
def DealerFeaturesAddRegistrationMetaApi(builder, registrationMetaApi): builder.PrependBoolSlot(9, registrationMetaApi, 0)
def DealerFeaturesAddTestamentMetaApi(builder, testamentMetaApi): builder.PrependBoolSlot(10, testamentMetaApi, 0)
def DealerFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(11, payloadTransparency, 0)
def DealerFeaturesAddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(12, payloadEncryptionCryptobox, 0)
def DealerFeaturesEnd(builder): return builder.EndObject()
@@ -0,0 +1,110 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class Error(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsError(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Error()
x.Init(buf, n + offset)
return x
# Error
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Error
def RequestType(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos)
return 0
# Error
def Request(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
return 0
# Error
def Error(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
# Error
def Payload(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Error
def PayloadAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Error
def PayloadLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return self._tab.VectorLen(o)
return 0
# Error
def EncAlgo(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# Error
def EncSerializer(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# Error
def EncKey(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Error
def EncKeyAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Error
def EncKeyLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
if o != 0:
return self._tab.VectorLen(o)
return 0
def ErrorStart(builder): builder.StartObject(7)
def ErrorAddRequestType(builder, requestType): builder.PrependUint16Slot(0, requestType, 0)
def ErrorAddRequest(builder, request): builder.PrependUint64Slot(1, request, 0)
def ErrorAddError(builder, error): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(error), 0)
def ErrorAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
def ErrorStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def ErrorAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(4, encAlgo, 0)
def ErrorAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(5, encSerializer, 0)
def ErrorAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(6, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
def ErrorStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def ErrorEnd(builder): return builder.EndObject()
@@ -0,0 +1,223 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class Event(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsEvent(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Event()
x.Init(buf, n + offset)
return x
# Event
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Event
def Subscription(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
return 0
# Event
def Publication(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
return 0
# /// Positional values for application-defined event payload.
# Event
def Args(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Event
def ArgsAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Event
def ArgsLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return self._tab.VectorLen(o)
return 0
# /// Keyword values for application-defined event payload.
# Event
def Kwargs(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Event
def KwargsAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Event
def KwargsLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return self._tab.VectorLen(o)
return 0
# /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
# Event
def Payload(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Event
def PayloadAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Event
def PayloadLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return self._tab.VectorLen(o)
return 0
# Event
def EncAlgo(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# Event
def EncSerializer(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# Event
def EncKey(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Event
def EncKeyAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Event
def EncKeyLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
if o != 0:
return self._tab.VectorLen(o)
return 0
# Event
def Publisher(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
return 0
# Event
def PublisherAuthid(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
# Event
def PublisherAuthrole(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
# Event
def Topic(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
# Event
def Retained(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# Event
def Acknowledge(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# Event
def ForwardFor(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32))
if o != 0:
x = self._tab.Vector(o)
x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4
x = self._tab.Indirect(x)
from .Principal import Principal
obj = Principal()
obj.Init(self._tab.Bytes, x)
return obj
return None
# Event
def ForwardForLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32))
if o != 0:
return self._tab.VectorLen(o)
return 0
def EventStart(builder): builder.StartObject(15)
def EventAddSubscription(builder, subscription): builder.PrependUint64Slot(0, subscription, 0)
def EventAddPublication(builder, publication): builder.PrependUint64Slot(1, publication, 0)
def EventAddArgs(builder, args): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(args), 0)
def EventStartArgsVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def EventAddKwargs(builder, kwargs): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(kwargs), 0)
def EventStartKwargsVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def EventAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
def EventStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def EventAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(5, encAlgo, 0)
def EventAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(6, encSerializer, 0)
def EventAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
def EventStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def EventAddPublisher(builder, publisher): builder.PrependUint64Slot(8, publisher, 0)
def EventAddPublisherAuthid(builder, publisherAuthid): builder.PrependUOffsetTRelativeSlot(9, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthid), 0)
def EventAddPublisherAuthrole(builder, publisherAuthrole): builder.PrependUOffsetTRelativeSlot(10, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthrole), 0)
def EventAddTopic(builder, topic): builder.PrependUOffsetTRelativeSlot(11, flatbuffers.number_types.UOffsetTFlags.py_type(topic), 0)
def EventAddRetained(builder, retained): builder.PrependBoolSlot(12, retained, 0)
def EventAddAcknowledge(builder, acknowledge): builder.PrependBoolSlot(13, acknowledge, 0)
def EventAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot(14, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0)
def EventStartForwardForVector(builder, numElems): return builder.StartVector(4, numElems, 4)
def EventEnd(builder): return builder.EndObject()
@@ -0,0 +1,94 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class EventReceived(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsEventReceived(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = EventReceived()
x.Init(buf, n + offset)
return x
# EventReceived
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# EventReceived
def Publication(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
return 0
# EventReceived
def Payload(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# EventReceived
def PayloadAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# EventReceived
def PayloadLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.VectorLen(o)
return 0
# EventReceived
def EncAlgo(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# EventReceived
def EncSerializer(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# EventReceived
def EncKey(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# EventReceived
def EncKeyAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# EventReceived
def EncKeyLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return self._tab.VectorLen(o)
return 0
def EventReceivedStart(builder): builder.StartObject(5)
def EventReceivedAddPublication(builder, publication): builder.PrependUint64Slot(0, publication, 0)
def EventReceivedAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
def EventReceivedStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def EventReceivedAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(2, encAlgo, 0)
def EventReceivedAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(3, encSerializer, 0)
def EventReceivedAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
def EventReceivedStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def EventReceivedEnd(builder): return builder.EndObject()
@@ -0,0 +1,118 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class Hello(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsHello(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Hello()
x.Init(buf, n + offset)
return x
# Hello
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Hello
def Roles(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
x = self._tab.Indirect(o + self._tab.Pos)
from .ClientRoles import ClientRoles
obj = ClientRoles()
obj.Init(self._tab.Bytes, x)
return obj
return None
# Hello
def Realm(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
# Hello
def Authmethods(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Hello
def AuthmethodsAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Hello
def AuthmethodsLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return self._tab.VectorLen(o)
return 0
# Hello
def Authid(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
# Hello
def Authrole(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
# Hello
def Authextra(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
if o != 0:
x = self._tab.Indirect(o + self._tab.Pos)
from .Map import Map
obj = Map()
obj.Init(self._tab.Bytes, x)
return obj
return None
# Hello
def Resumable(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# Hello
def ResumeSession(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
return 0
# Hello
def ResumeToken(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
def HelloStart(builder): builder.StartObject(9)
def HelloAddRoles(builder, roles): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(roles), 0)
def HelloAddRealm(builder, realm): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(realm), 0)
def HelloAddAuthmethods(builder, authmethods): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(authmethods), 0)
def HelloStartAuthmethodsVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def HelloAddAuthid(builder, authid): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(authid), 0)
def HelloAddAuthrole(builder, authrole): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(authrole), 0)
def HelloAddAuthextra(builder, authextra): builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(authextra), 0)
def HelloAddResumable(builder, resumable): builder.PrependBoolSlot(6, resumable, 0)
def HelloAddResumeSession(builder, resumeSession): builder.PrependUint64Slot(7, resumeSession, 0)
def HelloAddResumeToken(builder, resumeToken): builder.PrependUOffsetTRelativeSlot(8, flatbuffers.number_types.UOffsetTFlags.py_type(resumeToken), 0)
def HelloEnd(builder): return builder.EndObject()
@@ -0,0 +1,38 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class Interrupt(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsInterrupt(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Interrupt()
x.Init(buf, n + offset)
return x
# Interrupt
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Interrupt
def Request(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
return 0
# Interrupt
def Mode(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 1
def InterruptStart(builder): builder.StartObject(2)
def InterruptAddRequest(builder, request): builder.PrependUint64Slot(0, request, 0)
def InterruptAddMode(builder, mode): builder.PrependUint8Slot(1, mode, 1)
def InterruptEnd(builder): return builder.EndObject()
@@ -0,0 +1,9 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
class Kdf(object):
NONE = 0
PBKDF2 = 1
ARGON2 = 2
@@ -0,0 +1,9 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
class Match(object):
EXACT = 0
PREFIX = 1
WILDCARD = 2
@@ -0,0 +1,70 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class PublisherFeatures(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsPublisherFeatures(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = PublisherFeatures()
x.Init(buf, n + offset)
return x
# PublisherFeatures
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# PublisherFeatures
def PublisherIdentification(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# PublisherFeatures
def PublisherExclusion(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# PublisherFeatures
def SubscriberBlackwhiteListing(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# PublisherFeatures
def AcknowledgeEventReceived(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# PublisherFeatures
def PayloadTransparency(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
# PublisherFeatures
def PayloadEncryptionCryptobox(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
if o != 0:
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
return False
def PublisherFeaturesStart(builder): builder.StartObject(6)
def PublisherFeaturesAddPublisherIdentification(builder, publisherIdentification): builder.PrependBoolSlot(0, publisherIdentification, 0)
def PublisherFeaturesAddPublisherExclusion(builder, publisherExclusion): builder.PrependBoolSlot(1, publisherExclusion, 0)
def PublisherFeaturesAddSubscriberBlackwhiteListing(builder, subscriberBlackwhiteListing): builder.PrependBoolSlot(2, subscriberBlackwhiteListing, 0)
def PublisherFeaturesAddAcknowledgeEventReceived(builder, acknowledgeEventReceived): builder.PrependBoolSlot(3, acknowledgeEventReceived, 0)
def PublisherFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(4, payloadTransparency, 0)
def PublisherFeaturesAddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(5, payloadEncryptionCryptobox, 0)
def PublisherFeaturesEnd(builder): return builder.EndObject()
@@ -0,0 +1,13 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
class Serializer(object):
TRANSPORT = 0
JSON = 1
MSGPACK = 2
CBOR = 3
UBJSON = 4
OPAQUE = 5
FLATBUFFERS = 6
@@ -0,0 +1,94 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: proto
import flatbuffers
class Yield(object):
__slots__ = ['_tab']
@classmethod
def GetRootAsYield(cls, buf, offset):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Yield()
x.Init(buf, n + offset)
return x
# Yield
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Yield
def Request(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
return 0
# Yield
def Payload(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Yield
def PayloadAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Yield
def PayloadLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
return self._tab.VectorLen(o)
return 0
# Yield
def EncAlgo(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# Yield
def EncSerializer(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# Yield
def EncKey(self, j):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
a = self._tab.Vector(o)
return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
return 0
# Yield
def EncKeyAsNumpy(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
return 0
# Yield
def EncKeyLength(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
if o != 0:
return self._tab.VectorLen(o)
return 0
def YieldStart(builder): builder.StartObject(5)
def YieldAddRequest(builder, request): builder.PrependUint64Slot(0, request, 0)
def YieldAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
def YieldStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def YieldAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(2, encAlgo, 0)
def YieldAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(3, encSerializer, 0)
def YieldAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
def YieldStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
def YieldEnd(builder): return builder.EndObject()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
from autobahn.wamp import cryptobox
import unittest
@unittest.skipIf(not cryptobox.HAS_CRYPTOBOX, 'no cryptobox support present')
class TestCryptoBox(unittest.TestCase):
def test_create_keyring(self):
kr = cryptobox.KeyRing()
assert kr
@@ -0,0 +1,127 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import hashlib
import os
from mock import Mock
import txaio
if os.environ.get('USE_TWISTED', False):
txaio.use_twisted()
elif os.environ.get('USE_ASYNCIO', False):
txaio.use_asyncio()
else:
raise Exception('no networking framework selected')
from autobahn.wamp.cryptosign import _makepad, HAS_CRYPTOSIGN
from autobahn.wamp import types
from autobahn.wamp.auth import create_authenticator
if HAS_CRYPTOSIGN:
from autobahn.wamp.cryptosign import SigningKey
from nacl.encoding import HexEncoder
import tempfile
import unittest
keybody = '''-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAa38i/4dNWFuZN/72QAJbyOwZvkUyML/u2b2B1uW4RbQAAAJj4FLyB+BS8
gQAAAAtzc2gtZWQyNTUxOQAAACAa38i/4dNWFuZN/72QAJbyOwZvkUyML/u2b2B1uW4RbQ
AAAEBNV9l6aPVVaWYgpthJwM5YJWhRjXKet1PcfHMt4oBFEBrfyL/h01YW5k3/vZAAlvI7
Bm+RTIwv+7ZvYHW5bhFtAAAAFXNvbWV1c2VyQGZ1bmt0aGF0LmNvbQ==
-----END OPENSSH PRIVATE KEY-----'''
pubkey = '''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJVp3hjHwIQyEladzd8mFcf0YSXcmyKS3qMLB7VqTQKm someuser@example.com
'''
@unittest.skipIf(not HAS_CRYPTOSIGN, 'nacl library not present')
class TestAuth(unittest.TestCase):
def setUp(self):
self.key = SigningKey.from_ssh_data(keybody)
self.privkey_hex = self.key._key.encode(encoder=HexEncoder)
m = hashlib.sha256()
m.update("some TLS message".encode())
self.channel_id = m.digest()
def test_valid(self):
session = Mock()
session._transport.get_channel_id = Mock(return_value=self.channel_id)
challenge = types.Challenge(u"ticket", dict(challenge="ff" * 32))
signed = yield self.key.sign_challenge(session, challenge)
self.assertEqual(
u'9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00',
signed.result,
)
def test_authenticator(self):
authenticator = create_authenticator(
u"cryptosign",
authid="someone",
privkey=self.privkey_hex,
)
session = Mock()
session._transport.get_channel_id = Mock(return_value=self.channel_id)
challenge = types.Challenge(u"cryptosign", dict(challenge="ff" * 32))
reply = yield authenticator.on_challenge(session, challenge)
self.assertEqual(
reply.result,
u'9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00',
)
class TestKey(unittest.TestCase):
def test_pad(self):
self.assertEqual(_makepad(0), '')
self.assertEqual(_makepad(2), '\x01\x02')
self.assertEqual(_makepad(3), '\x01\x02\x03')
@unittest.skipIf(not HAS_CRYPTOSIGN, 'nacl library not present')
def test_key(self):
with tempfile.NamedTemporaryFile('w+t') as fp:
fp.write(keybody)
fp.seek(0)
key = SigningKey.from_ssh_key(fp.name)
self.assertEqual(key.public_key(), '1adfc8bfe1d35616e64dffbd900096f23b066f914c8c2ffbb66f6075b96e116d')
@unittest.skipIf(not HAS_CRYPTOSIGN, 'nacl library not present')
def test_pubkey(self):
with tempfile.NamedTemporaryFile('w+t') as fp:
fp.write(pubkey)
fp.seek(0)
key = SigningKey.from_ssh_key(fp.name)
self.assertEqual(key.public_key(), '9569de18c7c0843212569dcddf2615c7f46125dc9b2292dea30b07b56a4d02a6')
self.assertEqual(key.comment(), 'someuser@example.com')
@@ -0,0 +1,580 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
from autobahn import wamp
from autobahn.wamp.uri import Pattern, RegisterOptions, SubscribeOptions
import unittest
class TestUris(unittest.TestCase):
def test_invalid_uris(self):
for u in [u"",
u"com.myapp.<product:foo>.update",
u"com.myapp.<123:int>.update",
u"com.myapp.<:product>.update",
u"com.myapp.<product:>.update",
u"com.myapp.<int:>.update",
]:
self.assertRaises(Exception, Pattern, u, Pattern.URI_TARGET_ENDPOINT)
def test_valid_uris(self):
for u in [u"com.myapp.proc1",
u"123",
u"com.myapp.<product:int>.update",
u"com.myapp.<category:string>.<subcategory>.list"
u"com.myapp.something..update"
]:
p = Pattern(u, Pattern.URI_TARGET_ENDPOINT)
self.assertIsInstance(p, Pattern)
def test_parse_uris(self):
tests = [
(u"com.myapp.<product:int>.update", [
(u"com.myapp.0.update", {u'product': 0}),
(u"com.myapp.123456.update", {u'product': 123456}),
(u"com.myapp.aaa.update", None),
(u"com.myapp..update", None),
(u"com.myapp.0.delete", None),
]
),
(u"com.myapp.<product:string>.update", [
(u"com.myapp.box.update", {u'product': u'box'}),
(u"com.myapp.123456.update", {u'product': u'123456'}),
(u"com.myapp..update", None),
]
),
(u"com.myapp.<product>.update", [
(u"com.myapp.0.update", {u'product': u'0'}),
(u"com.myapp.abc.update", {u'product': u'abc'}),
(u"com.myapp..update", None),
]
),
(u"com.myapp.<category:string>.<subcategory:string>.list", [
(u"com.myapp.cosmetic.shampoo.list", {u'category': u'cosmetic', u'subcategory': u'shampoo'}),
(u"com.myapp...list", None),
(u"com.myapp.cosmetic..list", None),
(u"com.myapp..shampoo.list", None),
]
)
]
for test in tests:
pat = Pattern(test[0], Pattern.URI_TARGET_ENDPOINT)
for ptest in test[1]:
uri = ptest[0]
kwargs_should = ptest[1]
if kwargs_should is not None:
args_is, kwargs_is = pat.match(uri)
self.assertEqual(kwargs_is, kwargs_should)
else:
self.assertRaises(Exception, pat.match, uri)
class TestDecorators(unittest.TestCase):
def test_decorate_endpoint(self):
@wamp.register(u"com.calculator.square")
def square(_):
"""Do nothing."""
self.assertTrue(hasattr(square, '_wampuris'))
self.assertTrue(type(square._wampuris) == list)
self.assertEqual(len(square._wampuris), 1)
self.assertIsInstance(square._wampuris[0], Pattern)
self.assertTrue(square._wampuris[0].is_endpoint())
self.assertFalse(square._wampuris[0].is_handler())
self.assertFalse(square._wampuris[0].is_exception())
self.assertEqual(square._wampuris[0].uri(), u"com.calculator.square")
self.assertEqual(square._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
@wamp.register(u"com.myapp.product.<product:int>.update")
def update_product(product=None, label=None):
"""Do nothing."""
self.assertTrue(hasattr(update_product, '_wampuris'))
self.assertTrue(type(update_product._wampuris) == list)
self.assertEqual(len(update_product._wampuris), 1)
self.assertIsInstance(update_product._wampuris[0], Pattern)
self.assertTrue(update_product._wampuris[0].is_endpoint())
self.assertFalse(update_product._wampuris[0].is_handler())
self.assertFalse(update_product._wampuris[0].is_exception())
self.assertEqual(update_product._wampuris[0].uri(), u"com.myapp.product.<product:int>.update")
self.assertEqual(update_product._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
@wamp.register(u"com.myapp.<category:string>.<cid:int>.update")
def update(category=None, cid=None):
"""Do nothing."""
self.assertTrue(hasattr(update, '_wampuris'))
self.assertTrue(type(update._wampuris) == list)
self.assertEqual(len(update._wampuris), 1)
self.assertIsInstance(update._wampuris[0], Pattern)
self.assertTrue(update._wampuris[0].is_endpoint())
self.assertFalse(update._wampuris[0].is_handler())
self.assertFalse(update._wampuris[0].is_exception())
self.assertEqual(update._wampuris[0].uri(), u"com.myapp.<category:string>.<cid:int>.update")
self.assertEqual(update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
@wamp.register(u"com.myapp.circle.<name:string>",
RegisterOptions(match=u"wildcard", details_arg="details"))
def circle(name=None, details=None):
""" Do nothing. """
self.assertTrue(hasattr(circle, '_wampuris'))
self.assertTrue(type(circle._wampuris) == list)
self.assertEqual(len(circle._wampuris), 1)
self.assertIsInstance(circle._wampuris[0], Pattern)
self.assertIsInstance(circle._wampuris[0].options, RegisterOptions)
self.assertEqual(circle._wampuris[0].options.match, u"wildcard")
self.assertEqual(circle._wampuris[0].options.details_arg, "details")
self.assertTrue(circle._wampuris[0].is_endpoint())
self.assertFalse(circle._wampuris[0].is_handler())
self.assertFalse(circle._wampuris[0].is_exception())
self.assertEqual(circle._wampuris[0].uri(), u"com.myapp.circle.<name:string>")
self.assertEqual(circle._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
@wamp.register(u"com.myapp.something..update",
RegisterOptions(match=u"wildcard", details_arg="details"))
def something(dynamic=None, details=None):
""" Do nothing. """
self.assertTrue(hasattr(something, '_wampuris'))
self.assertTrue(type(something._wampuris) == list)
self.assertEqual(len(something._wampuris), 1)
self.assertIsInstance(something._wampuris[0], Pattern)
self.assertIsInstance(something._wampuris[0].options, RegisterOptions)
self.assertEqual(something._wampuris[0].options.match, u"wildcard")
self.assertEqual(something._wampuris[0].options.details_arg, "details")
self.assertTrue(something._wampuris[0].is_endpoint())
self.assertFalse(something._wampuris[0].is_handler())
self.assertFalse(something._wampuris[0].is_exception())
self.assertEqual(something._wampuris[0].uri(), u"com.myapp.something..update")
self.assertEqual(something._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
def test_decorate_handler(self):
@wamp.subscribe(u"com.myapp.on_shutdown")
def on_shutdown():
"""Do nothing."""
self.assertTrue(hasattr(on_shutdown, '_wampuris'))
self.assertTrue(type(on_shutdown._wampuris) == list)
self.assertEqual(len(on_shutdown._wampuris), 1)
self.assertIsInstance(on_shutdown._wampuris[0], Pattern)
self.assertFalse(on_shutdown._wampuris[0].is_endpoint())
self.assertTrue(on_shutdown._wampuris[0].is_handler())
self.assertFalse(on_shutdown._wampuris[0].is_exception())
self.assertEqual(on_shutdown._wampuris[0].uri(), u"com.myapp.on_shutdown")
self.assertEqual(on_shutdown._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
@wamp.subscribe(u"com.myapp.product.<product:int>.on_update")
def on_product_update(product=None, label=None):
"""Do nothing."""
self.assertTrue(hasattr(on_product_update, '_wampuris'))
self.assertTrue(type(on_product_update._wampuris) == list)
self.assertEqual(len(on_product_update._wampuris), 1)
self.assertIsInstance(on_product_update._wampuris[0], Pattern)
self.assertFalse(on_product_update._wampuris[0].is_endpoint())
self.assertTrue(on_product_update._wampuris[0].is_handler())
self.assertFalse(on_product_update._wampuris[0].is_exception())
self.assertEqual(on_product_update._wampuris[0].uri(), u"com.myapp.product.<product:int>.on_update")
self.assertEqual(on_product_update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
@wamp.subscribe(u"com.myapp.<category:string>.<cid:int>.on_update")
def on_update(category=None, cid=None, label=None):
"""Do nothing."""
self.assertTrue(hasattr(on_update, '_wampuris'))
self.assertTrue(type(on_update._wampuris) == list)
self.assertEqual(len(on_update._wampuris), 1)
self.assertIsInstance(on_update._wampuris[0], Pattern)
self.assertFalse(on_update._wampuris[0].is_endpoint())
self.assertTrue(on_update._wampuris[0].is_handler())
self.assertFalse(on_update._wampuris[0].is_exception())
self.assertEqual(on_update._wampuris[0].uri(), u"com.myapp.<category:string>.<cid:int>.on_update")
self.assertEqual(on_update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
@wamp.subscribe(u"com.myapp.on.<event:string>",
SubscribeOptions(match=u"wildcard", details_arg="details"))
def on_event(event=None, details=None):
""" Do nothing. """
self.assertTrue(hasattr(on_event, '_wampuris'))
self.assertTrue(type(on_event._wampuris) == list)
self.assertEqual(len(on_event._wampuris), 1)
self.assertIsInstance(on_event._wampuris[0], Pattern)
self.assertIsInstance(on_event._wampuris[0].options, SubscribeOptions)
self.assertEqual(on_event._wampuris[0].options.match, u"wildcard")
self.assertEqual(on_event._wampuris[0].options.details_arg, "details")
self.assertFalse(on_event._wampuris[0].is_endpoint())
self.assertTrue(on_event._wampuris[0].is_handler())
self.assertFalse(on_event._wampuris[0].is_exception())
self.assertEqual(on_event._wampuris[0].uri(), u"com.myapp.on.<event:string>")
self.assertEqual(on_event._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
def test_decorate_exception(self):
@wamp.error(u"com.myapp.error")
class AppError(Exception):
"""Do nothing."""
self.assertTrue(hasattr(AppError, '_wampuris'))
self.assertTrue(type(AppError._wampuris) == list)
self.assertEqual(len(AppError._wampuris), 1)
self.assertIsInstance(AppError._wampuris[0], Pattern)
self.assertFalse(AppError._wampuris[0].is_endpoint())
self.assertFalse(AppError._wampuris[0].is_handler())
self.assertTrue(AppError._wampuris[0].is_exception())
self.assertEqual(AppError._wampuris[0].uri(), u"com.myapp.error")
self.assertEqual(AppError._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
@wamp.error(u"com.myapp.product.<product:int>.product_inactive")
class ProductInactiveError(Exception):
"""Do nothing."""
self.assertTrue(hasattr(ProductInactiveError, '_wampuris'))
self.assertTrue(type(ProductInactiveError._wampuris) == list)
self.assertEqual(len(ProductInactiveError._wampuris), 1)
self.assertIsInstance(ProductInactiveError._wampuris[0], Pattern)
self.assertFalse(ProductInactiveError._wampuris[0].is_endpoint())
self.assertFalse(ProductInactiveError._wampuris[0].is_handler())
self.assertTrue(ProductInactiveError._wampuris[0].is_exception())
self.assertEqual(ProductInactiveError._wampuris[0].uri(), u"com.myapp.product.<product:int>.product_inactive")
self.assertEqual(ProductInactiveError._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
@wamp.error(u"com.myapp.<category:string>.<product:int>.inactive")
class ObjectInactiveError(Exception):
"""Do nothing."""
self.assertTrue(hasattr(ObjectInactiveError, '_wampuris'))
self.assertTrue(type(ObjectInactiveError._wampuris) == list)
self.assertEqual(len(ObjectInactiveError._wampuris), 1)
self.assertIsInstance(ObjectInactiveError._wampuris[0], Pattern)
self.assertFalse(ObjectInactiveError._wampuris[0].is_endpoint())
self.assertFalse(ObjectInactiveError._wampuris[0].is_handler())
self.assertTrue(ObjectInactiveError._wampuris[0].is_exception())
self.assertEqual(ObjectInactiveError._wampuris[0].uri(), u"com.myapp.<category:string>.<product:int>.inactive")
self.assertEqual(ObjectInactiveError._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
def test_match_decorated_endpoint(self):
@wamp.register(u"com.calculator.square")
def square(x):
return x
args, kwargs = square._wampuris[0].match(u"com.calculator.square")
self.assertEqual(square(666, **kwargs), 666)
@wamp.register(u"com.myapp.product.<product:int>.update")
def update_product(product=None, label=None):
return product, label
args, kwargs = update_product._wampuris[0].match(u"com.myapp.product.123456.update")
kwargs['label'] = "foobar"
self.assertEqual(update_product(**kwargs), (123456, "foobar"))
@wamp.register(u"com.myapp.<category:string>.<cid:int>.update")
def update(category=None, cid=None, label=None):
return category, cid, label
args, kwargs = update._wampuris[0].match(u"com.myapp.product.123456.update")
kwargs['label'] = "foobar"
self.assertEqual(update(**kwargs), ("product", 123456, "foobar"))
def test_match_decorated_handler(self):
@wamp.subscribe(u"com.myapp.on_shutdown")
def on_shutdown():
pass
args, kwargs = on_shutdown._wampuris[0].match(u"com.myapp.on_shutdown")
self.assertEqual(on_shutdown(**kwargs), None)
@wamp.subscribe(u"com.myapp.product.<product:int>.on_update")
def on_product_update(product=None, label=None):
return product, label
args, kwargs = on_product_update._wampuris[0].match(u"com.myapp.product.123456.on_update")
kwargs['label'] = "foobar"
self.assertEqual(on_product_update(**kwargs), (123456, "foobar"))
@wamp.subscribe(u"com.myapp.<category:string>.<cid:int>.on_update")
def on_update(category=None, cid=None, label=None):
return category, cid, label
args, kwargs = on_update._wampuris[0].match(u"com.myapp.product.123456.on_update")
kwargs['label'] = "foobar"
self.assertEqual(on_update(**kwargs), ("product", 123456, "foobar"))
def test_match_decorated_exception(self):
@wamp.error(u"com.myapp.error")
class AppError(Exception):
def __init__(self, msg):
Exception.__init__(self, msg)
def __eq__(self, other):
return self.__class__ == other.__class__ and \
self.args == other.args
args, kwargs = AppError._wampuris[0].match(u"com.myapp.error")
# noinspection PyArgumentList
self.assertEqual(AppError(u"fuck", **kwargs), AppError(u"fuck"))
@wamp.error(u"com.myapp.product.<product:int>.product_inactive")
class ProductInactiveError(Exception):
def __init__(self, msg, product=None):
Exception.__init__(self, msg)
self.product = product
def __eq__(self, other):
return self.__class__ == other.__class__ and \
self.args == other.args and \
self.product == other.product
args, kwargs = ProductInactiveError._wampuris[0].match(u"com.myapp.product.123456.product_inactive")
self.assertEqual(ProductInactiveError("fuck", **kwargs), ProductInactiveError("fuck", 123456))
@wamp.error(u"com.myapp.<category:string>.<product:int>.inactive")
class ObjectInactiveError(Exception):
def __init__(self, msg, category=None, product=None):
Exception.__init__(self, msg)
self.category = category
self.product = product
def __eq__(self, other):
return self.__class__ == other.__class__ and \
self.args == other.args and \
self.category == other.category and \
self.product == other.product
args, kwargs = ObjectInactiveError._wampuris[0].match(u"com.myapp.product.123456.inactive")
self.assertEqual(ObjectInactiveError("fuck", **kwargs), ObjectInactiveError("fuck", "product", 123456))
class KwException(Exception):
def __init__(self, *args, **kwargs):
Exception.__init__(self, *args)
self.kwargs = kwargs
# what if the WAMP error message received
# contains args/kwargs that cannot be
# consumed by the constructor of the exception
# class defined for the WAMP error URI?
# 1. we can bail out (but we are already signaling an error)
# 2. we can require a generic constructor
# 3. we can map only unconsumed args/kwargs to generic attributes
# 4. we can silently drop unconsumed args/kwargs
class MockSession(object):
def __init__(self):
self._ecls_to_uri_pat = {}
self._uri_to_ecls = {}
def define(self, exception, error=None):
if error is None:
assert(hasattr(exception, '_wampuris'))
self._ecls_to_uri_pat[exception] = exception._wampuris
self._uri_to_ecls[exception._wampuris[0].uri()] = exception
else:
assert(not hasattr(exception, '_wampuris'))
self._ecls_to_uri_pat[exception] = [Pattern(error, Pattern.URI_TARGET_HANDLER)]
self._uri_to_ecls[error] = exception
def map_error(self, error, args=None, kwargs=None):
# FIXME:
# 1. map to ecls based on error URI wildcard/prefix
# 2. extract additional args/kwargs from error URI
if error in self._uri_to_ecls:
ecls = self._uri_to_ecls[error]
try:
# the following might fail, eg. TypeError when
# signature of exception constructor is incompatible
# with args/kwargs or when the exception constructor raises
if kwargs:
if args:
exc = ecls(*args, **kwargs)
else:
exc = ecls(**kwargs)
else:
if args:
exc = ecls(*args)
else:
exc = ecls()
except Exception:
# FIXME: log e
exc = KwException(error, *args, **kwargs)
else:
# this never fails
args = args or []
kwargs = kwargs or {}
exc = KwException(error, *args, **kwargs)
return exc
class TestDecoratorsAdvanced(unittest.TestCase):
def test_decorate_exception_non_exception(self):
def test():
# noinspection PyUnusedLocal
@wamp.error(u"com.test.error")
class Foo(object):
pass
self.assertRaises(Exception, test)
def test_decorate_endpoint_multiple(self):
# noinspection PyUnusedLocal
@wamp.register(u"com.oldapp.oldproc")
@wamp.register(u"com.calculator.square")
def square(x):
"""Do nothing."""
self.assertTrue(hasattr(square, '_wampuris'))
self.assertTrue(type(square._wampuris) == list)
self.assertEqual(len(square._wampuris), 2)
for i in range(2):
self.assertIsInstance(square._wampuris[i], Pattern)
self.assertTrue(square._wampuris[i].is_endpoint())
self.assertFalse(square._wampuris[i].is_handler())
self.assertFalse(square._wampuris[i].is_exception())
self.assertEqual(square._wampuris[i]._type, Pattern.URI_TYPE_EXACT)
self.assertEqual(square._wampuris[0].uri(), u"com.calculator.square")
self.assertEqual(square._wampuris[1].uri(), u"com.oldapp.oldproc")
def test_marshal_decorated_exception(self):
@wamp.error(u"com.myapp.error")
class AppError(Exception):
pass
try:
raise AppError("fuck")
except Exception as e:
self.assertEqual(e._wampuris[0].uri(), u"com.myapp.error")
@wamp.error(u"com.myapp.product.<product:int>.product_inactive")
class ProductInactiveError(Exception):
def __init__(self, msg, product=None):
Exception.__init__(self, msg)
self.product = product
try:
raise ProductInactiveError("fuck", 123456)
except Exception as e:
self.assertEqual(e._wampuris[0].uri(), u"com.myapp.product.<product:int>.product_inactive")
session = MockSession()
session.define(AppError)
def test_define_exception_undecorated(self):
session = MockSession()
class AppError(Exception):
pass
# defining an undecorated exception requires
# an URI to be provided
self.assertRaises(Exception, session.define, AppError)
session.define(AppError, u"com.myapp.error")
exc = session.map_error(u"com.myapp.error")
self.assertIsInstance(exc, AppError)
def test_define_exception_decorated(self):
session = MockSession()
@wamp.error(u"com.myapp.error")
class AppError(Exception):
pass
# when defining a decorated exception
# an URI must not be provided
self.assertRaises(Exception, session.define, AppError, u"com.myapp.error")
session.define(AppError)
exc = session.map_error(u"com.myapp.error")
self.assertIsInstance(exc, AppError)
def test_map_exception_undefined(self):
session = MockSession()
exc = session.map_error(u"com.myapp.error")
self.assertIsInstance(exc, Exception)
def test_map_exception_args(self):
session = MockSession()
@wamp.error(u"com.myapp.error")
class AppError(Exception):
pass
@wamp.error(u"com.myapp.error.product_inactive")
class ProductInactiveError(Exception):
def __init__(self, product=None):
self.product = product
# define exceptions in mock session
session.define(AppError)
session.define(ProductInactiveError)
for test in [
# (u"com.myapp.foo.error", [], {}, KwException),
(u"com.myapp.error", [], {}, AppError),
(u"com.myapp.error", ["you are doing it wrong"], {}, AppError),
(u"com.myapp.error", ["you are doing it wrong", 1, 2, 3], {}, AppError),
(u"com.myapp.error.product_inactive", [], {}, ProductInactiveError),
(u"com.myapp.error.product_inactive", [], {"product": 123456}, ProductInactiveError),
]:
error, args, kwargs, ecls = test
exc = session.map_error(error, args, kwargs)
self.assertIsInstance(exc, ecls)
self.assertEqual(list(exc.args), args)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,296 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import traceback
from autobahn.websocket import protocol
from autobahn.websocket.types import ConnectionDeny
from autobahn.wamp.interfaces import ITransport
from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost
__all__ = ('WampWebSocketServerProtocol',
'WampWebSocketClientProtocol',
'WampWebSocketServerFactory',
'WampWebSocketClientFactory')
class WampWebSocketProtocol(object):
"""
Base class for WAMP-over-WebSocket transport mixins.
"""
_session = None # default; self.session is set in onOpen
def _bailout(self, code, reason=None):
self.log.debug('Failing WAMP-over-WebSocket transport: code={code}, reason="{reason}"', code=code, reason=reason)
self._fail_connection(code, reason)
def onOpen(self):
"""
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen`
"""
# WebSocket connection established. Now let the user WAMP session factory
# create a new WAMP session and fire off session open callback.
try:
self._session = self.factory._factory()
self._session.onOpen(self)
except Exception as e:
self.log.critical("{tb}", tb=traceback.format_exc())
reason = u'WAMP Internal Error ({0})'.format(e)
self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INTERNAL_ERROR, reason=reason)
def onClose(self, wasClean, code, reason):
"""
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onClose`
"""
# WAMP session might never have been established in the first place .. guard this!
self._onclose_reason = reason
if self._session is not None:
# WebSocket connection lost - fire off the WAMP
# session close callback
# noinspection PyBroadException
try:
self.log.debug('WAMP-over-WebSocket transport lost: wasClean={wasClean}, code={code}, reason="{reason}"', wasClean=wasClean, code=code, reason=reason)
self._session.onClose(wasClean)
except Exception:
self.log.critical("{tb}", tb=traceback.format_exc())
self._session = None
def onMessage(self, payload, isBinary):
"""
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage`
"""
try:
for msg in self._serializer.unserialize(payload, isBinary):
self.log.trace(
"WAMP RECV: message={message}, session={session}, authid={authid}",
authid=self._session._authid,
session=self._session._session_id,
message=msg,
)
self._session.onMessage(msg)
except ProtocolError as e:
self.log.critical("{tb}", tb=traceback.format_exc())
reason = u'WAMP Protocol Error ({0})'.format(e)
self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, reason=reason)
except Exception as e:
self.log.critical("{tb}", tb=traceback.format_exc())
reason = u'WAMP Internal Error ({0})'.format(e)
self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INTERNAL_ERROR, reason=reason)
def send(self, msg):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.send`
"""
if self.isOpen():
try:
self.log.trace(
"WAMP SEND: message={message}, session={session}, authid={authid}",
authid=self._session._authid,
session=self._session._session_id,
message=msg,
)
payload, isBinary = self._serializer.serialize(msg)
except Exception as e:
self.log.error("WAMP message serialization error: {}".format(e))
# all exceptions raised from above should be serialization errors ..
raise SerializationError(u"WAMP message serialization error: {0}".format(e))
else:
self.sendMessage(payload, isBinary)
else:
raise TransportLost()
def isOpen(self):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen`
"""
return self._session is not None
def close(self):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.close`
"""
if self.isOpen():
self.sendClose(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
else:
raise TransportLost()
def abort(self):
"""
Implements :func:`autobahn.wamp.interfaces.ITransport.abort`
"""
if self.isOpen():
self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_GOING_AWAY)
else:
raise TransportLost()
ITransport.register(WampWebSocketProtocol)
def parseSubprotocolIdentifier(subprotocol):
try:
s = subprotocol.split(u'.')
if s[0] != u'wamp':
raise Exception(u'WAMP WebSocket subprotocol identifier must start with "wamp", not "{}"'.format(s[0]))
version = int(s[1])
serializerId = u'.'.join(s[2:])
return version, serializerId
except:
return None, None
class WampWebSocketServerProtocol(WampWebSocketProtocol):
"""
Mixin for WAMP-over-WebSocket server transports.
"""
STRICT_PROTOCOL_NEGOTIATION = True
def onConnect(self, request):
"""
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onConnect`
"""
headers = {}
for subprotocol in request.protocols:
version, serializerId = parseSubprotocolIdentifier(subprotocol)
if version == 2 and serializerId in self.factory._serializers.keys():
self._serializer = self.factory._serializers[serializerId]
return subprotocol, headers
if self.STRICT_PROTOCOL_NEGOTIATION:
raise ConnectionDeny(ConnectionDeny.BAD_REQUEST, u'This server only speaks WebSocket subprotocols {}'.format(u', '.join(self.factory.protocols)))
else:
# assume wamp.2.json
self._serializer = self.factory._serializers[u'json']
return None, headers
class WampWebSocketClientProtocol(WampWebSocketProtocol):
"""
Mixin for WAMP-over-WebSocket client transports.
"""
STRICT_PROTOCOL_NEGOTIATION = True
def onConnect(self, response):
"""
Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onConnect`
"""
if response.protocol not in self.factory.protocols:
if self.STRICT_PROTOCOL_NEGOTIATION:
raise Exception(u'The server does not speak any of the WebSocket subprotocols {} we requested.'.format(u', '.join(self.factory.protocols)))
else:
# assume wamp.2.json
serializerId = u'json'
else:
version, serializerId = parseSubprotocolIdentifier(response.protocol)
self._serializer = self.factory._serializers[serializerId]
class WampWebSocketFactory(object):
"""
Base class for WAMP-over-WebSocket transport factory mixins.
"""
def __init__(self, factory, serializers=None):
"""
Ctor.
:param factory: A callable that produces instances that implement
:class:`autobahn.wamp.interfaces.ITransportHandler`
:type factory: callable
:param serializers: A list of WAMP serializers to use (or None for default
serializers). Serializers must implement
:class:`autobahn.wamp.interfaces.ISerializer`.
:type serializers: list
"""
if callable(factory):
self._factory = factory
else:
self._factory = lambda: factory
if serializers is None:
serializers = []
# try CBOR WAMP serializer
try:
from autobahn.wamp.serializer import CBORSerializer
serializers.append(CBORSerializer(batched=True))
serializers.append(CBORSerializer())
except ImportError:
pass
# try MsgPack WAMP serializer
try:
from autobahn.wamp.serializer import MsgPackSerializer
serializers.append(MsgPackSerializer(batched=True))
serializers.append(MsgPackSerializer())
except ImportError:
pass
# try UBJSON WAMP serializer
try:
from autobahn.wamp.serializer import UBJSONSerializer
serializers.append(UBJSONSerializer(batched=True))
serializers.append(UBJSONSerializer())
except ImportError:
pass
# try JSON WAMP serializer
try:
from autobahn.wamp.serializer import JsonSerializer
serializers.append(JsonSerializer(batched=True))
serializers.append(JsonSerializer())
except ImportError:
pass
if not serializers:
raise Exception(u'Could not import any WAMP serializer')
self._serializers = {}
for ser in serializers:
self._serializers[ser.SERIALIZER_ID] = ser
self._protocols = [u'wamp.2.{}'.format(ser.SERIALIZER_ID) for ser in serializers]
class WampWebSocketServerFactory(WampWebSocketFactory):
"""
Mixin for WAMP-over-WebSocket server transport factories.
"""
class WampWebSocketClientFactory(WampWebSocketFactory):
"""
Mixin for WAMP-over-WebSocket client transport factories.
"""
@@ -0,0 +1,448 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import bz2
from autobahn.websocket.compress_base import PerMessageCompressOffer, \
PerMessageCompressOfferAccept, \
PerMessageCompressResponse, \
PerMessageCompressResponseAccept, \
PerMessageCompress
__all__ = (
'PerMessageBzip2Mixin',
'PerMessageBzip2Offer',
'PerMessageBzip2OfferAccept',
'PerMessageBzip2Response',
'PerMessageBzip2ResponseAccept',
'PerMessageBzip2',
)
class PerMessageBzip2Mixin(object):
"""
Mixin class for this extension.
"""
EXTENSION_NAME = "permessage-bzip2"
"""
Name of this WebSocket extension.
"""
COMPRESS_LEVEL_PERMISSIBLE_VALUES = [1, 2, 3, 4, 5, 6, 7, 8, 9]
"""
Permissible value for compression level parameter.
"""
class PerMessageBzip2Offer(PerMessageCompressOffer, PerMessageBzip2Mixin):
"""
Set of extension parameters for `permessage-bzip2` WebSocket extension
offered by a client to a server.
"""
@classmethod
def parse(cls, params):
"""
Parses a WebSocket extension offer for `permessage-bzip2` provided by a client to a server.
:param params: Output from :func:`autobahn.websocket.WebSocketProtocol._parseExtensionsHeader`.
:type params: list
:returns: object -- A new instance of :class:`autobahn.compress.PerMessageBzip2Offer`.
"""
# extension parameter defaults
accept_max_compress_level = False
request_max_compress_level = 0
# verify/parse client ("client-to-server direction") parameters of permessage-bzip2 offer
for p in params:
if len(params[p]) > 1:
raise Exception("multiple occurrence of extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
val = params[p][0]
if p == 'client_max_compress_level':
# noinspection PySimplifyBooleanCheck
if val is not True:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
else:
accept_max_compress_level = True
elif p == 'server_max_compress_level':
try:
val = int(val)
except:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
if val not in PerMessageBzip2Mixin.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
else:
request_max_compress_level = val
else:
raise Exception("illegal extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
offer = cls(accept_max_compress_level,
request_max_compress_level)
return offer
def __init__(self,
accept_max_compress_level=True,
request_max_compress_level=0):
"""
Constructor.
:param accept_max_compress_level: Iff true, client accepts "maximum compression level" parameter.
:type accept_max_compress_level: bool
:param request_max_compress_level: Iff non-zero, client requests given "maximum compression level" - must be 1-9.
:type request_max_compress_level: int
"""
if type(accept_max_compress_level) != bool:
raise Exception("invalid type %s for accept_max_compress_level" % type(accept_max_compress_level))
self.accept_max_compress_level = accept_max_compress_level
if request_max_compress_level != 0 and request_max_compress_level not in self.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
raise Exception("invalid value %s for request_max_compress_level - permissible values %s" % (request_max_compress_level, self.COMPRESS_LEVEL_PERMISSIBLE_VALUES))
self.request_max_compress_level = request_max_compress_level
def get_extension_string(self):
"""
Returns the WebSocket extension configuration string as sent to the server.
:returns: PMCE configuration string.
:rtype: str
"""
pmce_string = self.EXTENSION_NAME
if self.accept_max_compress_level:
pmce_string += "; client_max_compress_level"
if self.request_max_compress_level != 0:
pmce_string += "; server_max_compress_level=%d" % self.request_max_compress_level
return pmce_string
def __json__(self):
"""
Returns a JSON serializable object representation.
:returns: JSON serializable representation.
:rtype: dict
"""
return {'extension': self.EXTENSION_NAME,
'accept_max_compress_level': self.accept_max_compress_level,
'request_max_compress_level': self.request_max_compress_level}
def __repr__(self):
"""
Returns Python object representation that can be eval'ed to reconstruct the object.
:returns: Python string representation.
:rtype: str
"""
return "PerMessageBzip2Offer(accept_max_compress_level = %s, request_max_compress_level = %s)" % (self.accept_max_compress_level, self.request_max_compress_level)
class PerMessageBzip2OfferAccept(PerMessageCompressOfferAccept, PerMessageBzip2Mixin):
"""
Set of parameters with which to accept an `permessage-bzip2` offer
from a client by a server.
"""
def __init__(self,
offer,
request_max_compress_level=0,
compress_level=None):
"""
Constructor.
:param offer: The offer being accepted.
:type offer: Instance of :class:`autobahn.compress.PerMessageBzip2Offer`.
:param request_max_compress_level: Iff non-zero, server requests given "maximum compression level" - must be 1-9.
:type request_max_compress_level: int
:param compress_level: Override server ("server-to-client direction") compress level (this must be compatible with offer).
:type compress_level: int
"""
if not isinstance(offer, PerMessageBzip2Offer):
raise Exception("invalid type %s for offer" % type(offer))
self.offer = offer
if request_max_compress_level != 0 and request_max_compress_level not in self.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
raise Exception("invalid value %s for request_max_compress_level - permissible values %s" % (request_max_compress_level, self.COMPRESS_LEVEL_PERMISSIBLE_VALUES))
if request_max_compress_level != 0 and not offer.accept_max_compress_level:
raise Exception("invalid value %s for request_max_compress_level - feature unsupported by client" % request_max_compress_level)
self.request_max_compress_level = request_max_compress_level
if compress_level is not None:
if compress_level not in self.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
raise Exception("invalid value %s for compress_level - permissible values %s" % (compress_level, self.COMPRESS_LEVEL_PERMISSIBLE_VALUES))
if offer.request_max_compress_level != 0 and compress_level > offer.request_max_compress_level:
raise Exception("invalid value %s for compress_level - client requested lower maximum value" % compress_level)
self.compress_level = compress_level
def get_extension_string(self):
"""
Returns the WebSocket extension configuration string as sent to the server.
:returns: PMCE configuration string.
:rtype: str
"""
pmce_string = self.EXTENSION_NAME
if self.offer.request_max_compress_level != 0:
pmce_string += "; server_max_compress_level=%d" % self.offer.request_max_compress_level
if self.request_max_compress_level != 0:
pmce_string += "; client_max_compress_level=%d" % self.request_max_compress_level
return pmce_string
def __json__(self):
"""
Returns a JSON serializable object representation.
:returns: JSON serializable representation.
:rtype: dict
"""
return {'extension': self.EXTENSION_NAME,
'offer': self.offer.__json__(),
'request_max_compress_level': self.request_max_compress_level,
'compress_level': self.compress_level}
def __repr__(self):
"""
Returns Python object representation that can be eval'ed to reconstruct the object.
:returns: Python string representation.
:rtype: str
"""
return "PerMessageBzip2Accept(offer = %s, request_max_compress_level = %s, compress_level = %s)" % (self.offer.__repr__(), self.request_max_compress_level, self.compress_level)
class PerMessageBzip2Response(PerMessageCompressResponse, PerMessageBzip2Mixin):
"""
Set of parameters for `permessage-bzip2` responded by server.
"""
@classmethod
def parse(cls, params):
"""
Parses a WebSocket extension response for `permessage-bzip2` provided by a server to a client.
:param params: Output from :func:`autobahn.websocket.WebSocketProtocol._parseExtensionsHeader`.
:type params: list
:returns: A new instance of :class:`autobahn.compress.PerMessageBzip2Response`.
:rtype: obj
"""
client_max_compress_level = 0
server_max_compress_level = 0
for p in params:
if len(params[p]) > 1:
raise Exception("multiple occurrence of extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
val = params[p][0]
if p == 'client_max_compress_level':
try:
val = int(val)
except:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
if val not in PerMessageBzip2Mixin.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
else:
client_max_compress_level = val
elif p == 'server_max_compress_level':
try:
val = int(val)
except:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
if val not in PerMessageBzip2Mixin.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
else:
server_max_compress_level = val
else:
raise Exception("illegal extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
response = cls(client_max_compress_level,
server_max_compress_level)
return response
def __init__(self,
client_max_compress_level,
server_max_compress_level):
self.client_max_compress_level = client_max_compress_level
self.server_max_compress_level = server_max_compress_level
def __json__(self):
"""
Returns a JSON serializable object representation.
:returns: JSON serializable representation.
:rtype: dict
"""
return {'extension': self.EXTENSION_NAME,
'client_max_compress_level': self.client_max_compress_level,
'server_max_compress_level': self.server_max_compress_level}
def __repr__(self):
"""
Returns Python object representation that can be eval'ed to reconstruct the object.
:returns: Python string representation.
:rtype: str
"""
return "PerMessageBzip2Response(client_max_compress_level = %s, server_max_compress_level = %s)" % (self.client_max_compress_level, self.server_max_compress_level)
class PerMessageBzip2ResponseAccept(PerMessageCompressResponseAccept, PerMessageBzip2Mixin):
"""
Set of parameters with which to accept an `permessage-bzip2` response
from a server by a client.
"""
def __init__(self,
response,
compress_level=None):
"""
:param response: The response being accepted.
:type response: Instance of :class:`autobahn.compress.PerMessageBzip2Response`.
:param compress_level: Override client ("client-to-server direction") compress level (this must be compatible with response).
:type compress_level: int
"""
if not isinstance(response, PerMessageBzip2Response):
raise Exception("invalid type %s for response" % type(response))
self.response = response
if compress_level is not None:
if compress_level not in self.COMPRESS_LEVEL_PERMISSIBLE_VALUES:
raise Exception("invalid value %s for compress_level - permissible values %s" % (compress_level, self.COMPRESS_LEVEL_PERMISSIBLE_VALUES))
if response.client_max_compress_level != 0 and compress_level > response.client_max_compress_level:
raise Exception("invalid value %s for compress_level - server requested lower maximum value" % compress_level)
self.compress_level = compress_level
def __json__(self):
"""
Returns a JSON serializable object representation.
:returns: JSON serializable representation.
:rtype: dict
"""
return {'extension': self.EXTENSION_NAME,
'response': self.response.__json__(),
'compress_level': self.compress_level}
def __repr__(self):
"""
Returns Python object representation that can be eval'ed to reconstruct the object.
:returns: Python string representation.
:rtype: str
"""
return "PerMessageBzip2ResponseAccept(response = %s, compress_level = %s)" % (self.response.__repr__(), self.compress_level)
class PerMessageBzip2(PerMessageCompress, PerMessageBzip2Mixin):
"""
`permessage-bzip2` WebSocket extension processor.
"""
DEFAULT_COMPRESS_LEVEL = 9
@classmethod
def create_from_response_accept(cls, is_server, accept):
pmce = cls(is_server,
accept.response.server_max_compress_level,
accept.compress_level if accept.compress_level is not None else accept.response.client_max_compress_level)
return pmce
@classmethod
def create_from_offer_accept(cls, is_server, accept):
pmce = cls(is_server,
accept.compress_level if accept.compress_level is not None else accept.offer.request_max_compress_level,
accept.request_max_compress_level)
return pmce
def __init__(self,
is_server,
server_max_compress_level,
client_max_compress_level):
self._isServer = is_server
self._compressor = None
self._decompressor = None
self.server_max_compress_level = server_max_compress_level if server_max_compress_level != 0 else self.DEFAULT_COMPRESS_LEVEL
self.client_max_compress_level = client_max_compress_level if client_max_compress_level != 0 else self.DEFAULT_COMPRESS_LEVEL
def __json__(self):
return {'extension': self.EXTENSION_NAME,
'isServer': self._isServer,
'server_max_compress_level': self.server_max_compress_level,
'client_max_compress_level': self.client_max_compress_level}
def __repr__(self):
return "PerMessageBzip2(isServer = %s, server_max_compress_level = %s, client_max_compress_level = %s)" % (self._isServer, self.server_max_compress_level, self.client_max_compress_level)
def start_compress_message(self):
if self._isServer:
if self._compressor is None:
self._compressor = bz2.BZ2Compressor(self.server_max_compress_level)
else:
if self._compressor is None:
self._compressor = bz2.BZ2Compressor(self.client_max_compress_level)
def compress_message_data(self, data):
return self._compressor.compress(data)
def end_compress_message(self):
data = self._compressor.flush()
# there seems to be no "flush without close stream", and after
# full flush, compressor must not be reused
self._compressor = None
return data
def start_decompress_message(self):
if self._decompressor is None:
self._decompressor = bz2.BZ2Decompressor()
def decompress_message_data(self, data):
return self._decompressor.decompress(data)
def end_decompress_message(self):
self._decompressor = None
@@ -0,0 +1,429 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from __future__ import absolute_import
import snappy
from autobahn.websocket.compress_base import PerMessageCompressOffer, \
PerMessageCompressOfferAccept, \
PerMessageCompressResponse, \
PerMessageCompressResponseAccept, \
PerMessageCompress
__all__ = (
'PerMessageSnappyMixin',
'PerMessageSnappyOffer',
'PerMessageSnappyOfferAccept',
'PerMessageSnappyResponse',
'PerMessageSnappyResponseAccept',
'PerMessageSnappy',
)
class PerMessageSnappyMixin(object):
"""
Mixin class for this extension.
"""
EXTENSION_NAME = "permessage-snappy"
"""
Name of this WebSocket extension.
"""
class PerMessageSnappyOffer(PerMessageCompressOffer, PerMessageSnappyMixin):
"""
Set of extension parameters for `permessage-snappy` WebSocket extension
offered by a client to a server.
"""
@classmethod
def parse(cls, params):
"""
Parses a WebSocket extension offer for `permessage-snappy` provided by a client to a server.
:param params: Output from :func:`autobahn.websocket.WebSocketProtocol._parseExtensionsHeader`.
:type params: list
:returns: A new instance of :class:`autobahn.compress.PerMessageSnappyOffer`.
:rtype: obj
"""
# extension parameter defaults
accept_no_context_takeover = False
request_no_context_takeover = False
# verify/parse client ("client-to-server direction") parameters of permessage-snappy offer
for p in params:
if len(params[p]) > 1:
raise Exception("multiple occurrence of extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
val = params[p][0]
if p == 'client_no_context_takeover':
# noinspection PySimplifyBooleanCheck
if val is not True:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
else:
accept_no_context_takeover = True
elif p == 'server_no_context_takeover':
# noinspection PySimplifyBooleanCheck
if val is not True:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
else:
request_no_context_takeover = True
else:
raise Exception("illegal extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
offer = cls(accept_no_context_takeover,
request_no_context_takeover)
return offer
def __init__(self,
accept_no_context_takeover=True,
request_no_context_takeover=False):
"""
:param accept_no_context_takeover: Iff true, client accepts "no context takeover" feature.
:type accept_no_context_takeover: bool
:param request_no_context_takeover: Iff true, client request "no context takeover" feature.
:type request_no_context_takeover: bool
"""
if type(accept_no_context_takeover) != bool:
raise Exception("invalid type %s for accept_no_context_takeover" % type(accept_no_context_takeover))
self.accept_no_context_takeover = accept_no_context_takeover
if type(request_no_context_takeover) != bool:
raise Exception("invalid type %s for request_no_context_takeover" % type(request_no_context_takeover))
self.request_no_context_takeover = request_no_context_takeover
def get_extension_string(self):
"""
Returns the WebSocket extension configuration string as sent to the server.
:returns: PMCE configuration string.
:rtype: str
"""
pmce_string = self.EXTENSION_NAME
if self.accept_no_context_takeover:
pmce_string += "; client_no_context_takeover"
if self.request_no_context_takeover:
pmce_string += "; server_no_context_takeover"
return pmce_string
def __json__(self):
"""
Returns a JSON serializable object representation.
:returns: JSON serializable representation.
:rtype: dict
"""
return {'extension': self.EXTENSION_NAME,
'accept_no_context_takeover': self.accept_no_context_takeover,
'request_no_context_takeover': self.request_no_context_takeover}
def __repr__(self):
"""
Returns Python object representation that can be eval'ed to reconstruct the object.
:returns: Python string representation.
:rtype: str
"""
return "PerMessageSnappyOffer(accept_no_context_takeover = %s, request_no_context_takeover = %s)" % (self.accept_no_context_takeover, self.request_no_context_takeover)
class PerMessageSnappyOfferAccept(PerMessageCompressOfferAccept, PerMessageSnappyMixin):
"""
Set of parameters with which to accept an `permessage-snappy` offer
from a client by a server.
"""
def __init__(self,
offer,
request_no_context_takeover=False,
no_context_takeover=None):
"""
:param offer: The offer being accepted.
:type offer: Instance of :class:`autobahn.compress.PerMessageSnappyOffer`.
:param request_no_context_takeover: Iff true, server request "no context takeover" feature.
:type request_no_context_takeover: bool
:param no_context_takeover: Override server ("server-to-client direction") context takeover (this must be compatible with offer).
:type no_context_takeover: bool
"""
if not isinstance(offer, PerMessageSnappyOffer):
raise Exception("invalid type %s for offer" % type(offer))
self.offer = offer
if type(request_no_context_takeover) != bool:
raise Exception("invalid type %s for request_no_context_takeover" % type(request_no_context_takeover))
if request_no_context_takeover and not offer.accept_no_context_takeover:
raise Exception("invalid value %s for request_no_context_takeover - feature unsupported by client" % request_no_context_takeover)
self.request_no_context_takeover = request_no_context_takeover
if no_context_takeover is not None:
if type(no_context_takeover) != bool:
raise Exception("invalid type %s for no_context_takeover" % type(no_context_takeover))
if offer.request_no_context_takeover and not no_context_takeover:
raise Exception("invalid value %s for no_context_takeover - client requested feature" % no_context_takeover)
self.no_context_takeover = no_context_takeover
def get_extension_string(self):
"""
Returns the WebSocket extension configuration string as sent to the server.
:returns: PMCE configuration string.
:rtype: str
"""
pmce_string = self.EXTENSION_NAME
if self.offer.request_no_context_takeover:
pmce_string += "; server_no_context_takeover"
if self.request_no_context_takeover:
pmce_string += "; client_no_context_takeover"
return pmce_string
def __json__(self):
"""
Returns a JSON serializable object representation.
:returns: JSON serializable representation.
:rtype: dict
"""
return {'extension': self.EXTENSION_NAME,
'offer': self.offer.__json__(),
'request_no_context_takeover': self.request_no_context_takeover,
'no_context_takeover': self.no_context_takeover}
def __repr__(self):
"""
Returns Python object representation that can be eval'ed to reconstruct the object.
:returns: Python string representation.
:rtype: str
"""
return "PerMessageSnappyAccept(offer = %s, request_no_context_takeover = %s, no_context_takeover = %s)" % (self.offer.__repr__(), self.request_no_context_takeover, self.no_context_takeover)
class PerMessageSnappyResponse(PerMessageCompressResponse, PerMessageSnappyMixin):
"""
Set of parameters for `permessage-snappy` responded by server.
"""
@classmethod
def parse(cls, params):
"""
Parses a WebSocket extension response for `permessage-snappy` provided by a server to a client.
:param params: Output from :func:`autobahn.websocket.WebSocketProtocol._parseExtensionsHeader`.
:type params: list
:returns: A new instance of :class:`autobahn.compress.PerMessageSnappyResponse`.
:rtype: obj
"""
client_no_context_takeover = False
server_no_context_takeover = False
for p in params:
if len(params[p]) > 1:
raise Exception("multiple occurrence of extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
val = params[p][0]
if p == 'client_no_context_takeover':
# noinspection PySimplifyBooleanCheck
if val is not True:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
else:
client_no_context_takeover = True
elif p == 'server_no_context_takeover':
# noinspection PySimplifyBooleanCheck
if val is not True:
raise Exception("illegal extension parameter value '%s' for parameter '%s' of extension '%s'" % (val, p, cls.EXTENSION_NAME))
else:
server_no_context_takeover = True
else:
raise Exception("illegal extension parameter '%s' for extension '%s'" % (p, cls.EXTENSION_NAME))
response = cls(client_no_context_takeover,
server_no_context_takeover)
return response
def __init__(self,
client_no_context_takeover,
server_no_context_takeover):
self.client_no_context_takeover = client_no_context_takeover
self.server_no_context_takeover = server_no_context_takeover
def __json__(self):
"""
Returns a JSON serializable object representation.
:returns: JSON serializable representation.
:rtype: dict
"""
return {'extension': self.EXTENSION_NAME,
'client_no_context_takeover': self.client_no_context_takeover,
'server_no_context_takeover': self.server_no_context_takeover}
def __repr__(self):
"""
Returns Python object representation that can be eval'ed to reconstruct the object.
:returns: Python string representation.
:rtype: str
"""
return "PerMessageSnappyResponse(client_no_context_takeover = %s, server_no_context_takeover = %s)" % (self.client_no_context_takeover, self.server_no_context_takeover)
class PerMessageSnappyResponseAccept(PerMessageCompressResponseAccept, PerMessageSnappyMixin):
"""
Set of parameters with which to accept an `permessage-snappy` response
from a server by a client.
"""
def __init__(self,
response,
no_context_takeover=None):
"""
:param response: The response being accepted.
:type response: Instance of :class:`autobahn.compress.PerMessageSnappyResponse`.
:param no_context_takeover: Override client ("client-to-server direction") context takeover (this must be compatible with response).
:type no_context_takeover: bool
"""
if not isinstance(response, PerMessageSnappyResponse):
raise Exception("invalid type %s for response" % type(response))
self.response = response
if no_context_takeover is not None:
if type(no_context_takeover) != bool:
raise Exception("invalid type %s for no_context_takeover" % type(no_context_takeover))
if response.client_no_context_takeover and not no_context_takeover:
raise Exception("invalid value %s for no_context_takeover - server requested feature" % no_context_takeover)
self.no_context_takeover = no_context_takeover
def __json__(self):
"""
Returns a JSON serializable object representation.
:returns: JSON serializable representation.
:rtype: dict
"""
return {'extension': self.EXTENSION_NAME,
'response': self.response.__json__(),
'no_context_takeover': self.no_context_takeover}
def __repr__(self):
"""
Returns Python object representation that can be eval'ed to reconstruct the object.
:returns: Python string representation.
:rtype: str
"""
return "PerMessageSnappyResponseAccept(response = %s, no_context_takeover = %s)" % (self.response.__repr__(), self.no_context_takeover)
class PerMessageSnappy(PerMessageCompress, PerMessageSnappyMixin):
"""
`permessage-snappy` WebSocket extension processor.
"""
@classmethod
def create_from_response_accept(cls, is_server, accept):
pmce = cls(is_server,
accept.response.server_no_context_takeover,
accept.no_context_takeover if accept.no_context_takeover is not None else accept.response.client_no_context_takeover)
return pmce
@classmethod
def create_from_offer_accept(cls, is_server, accept):
pmce = cls(is_server,
accept.no_context_takeover if accept.no_context_takeover is not None else accept.offer.request_no_context_takeover,
accept.request_no_context_takeover)
return pmce
def __init__(self,
is_server,
server_no_context_takeover,
client_no_context_takeover):
self._is_server = is_server
self.server_no_context_takeover = server_no_context_takeover
self.client_no_context_takeover = client_no_context_takeover
self._compressor = None
self._decompressor = None
def __json__(self):
return {'extension': self.EXTENSION_NAME,
'server_no_context_takeover': self.server_no_context_takeover,
'client_no_context_takeover': self.client_no_context_takeover}
def __repr__(self):
return "PerMessageSnappy(is_server = %s, server_no_context_takeover = %s, client_no_context_takeover = %s)" % (self._is_server, self.server_no_context_takeover, self.client_no_context_takeover)
def start_compress_message(self):
if self._is_server:
if self._compressor is None or self.server_no_context_takeover:
self._compressor = snappy.StreamCompressor()
else:
if self._compressor is None or self.client_no_context_takeover:
self._compressor = snappy.StreamCompressor()
def compress_message_data(self, data):
return self._compressor.add_chunk(data)
def end_compress_message(self):
return b""
def start_decompress_message(self):
if self._is_server:
if self._decompressor is None or self.client_no_context_takeover:
self._decompressor = snappy.StreamDecompressor()
else:
if self._decompressor is None or self.server_no_context_takeover:
self._decompressor = snappy.StreamDecompressor()
def decompress_message_data(self, data):
return self._decompressor.decompress(data)
def end_decompress_message(self):
pass
@@ -0,0 +1,188 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class IMarketMaker(object):
"""
XBR Market Maker interface.
"""
@abc.abstractmethod
def status(self, details):
"""
:param details:
:return:
"""
@abc.abstractmethod
def offer(self, key_id, price, details):
"""
:param key_id:
:param price:
:param details:
:return:
"""
@abc.abstractmethod
def revoke(self, key_id, details):
"""
:param key_id:
:param details:
:return:
"""
@abc.abstractmethod
def quote(self, key_id, details):
"""
:param key_id:
:param details:
:return:
"""
@abc.abstractmethod
def buy(self, channel_id, channel_seq, buyer_pubkey, datakey_id, amount, balance, signature, details):
"""
:param channel_id:
:param channel_seq:
:param buyer_pubkey:
:param datakey_id:
:param amount:
:param balance:
:param signature:
:param details:
:return:
"""
@abc.abstractmethod
def get_payment_channels(self, address, details):
"""
:param address:
:param details:
:return:
"""
@abc.abstractmethod
def get_payment_channel(self, channel_id, details):
"""
:param channel_id:
:param details:
:return:
"""
@six.add_metaclass(abc.ABCMeta)
class IProvider(object):
"""
XBR Provider interface.
"""
@abc.abstractmethod
def sell(self, key_id, buyer_pubkey, amount_paid, post_balance, signature, details):
"""
:param key_id:
:param buyer_pubkey:
:param amount_paid:
:param post_balance:
:param signature:
:param details:
:return:
"""
@six.add_metaclass(abc.ABCMeta)
class IConsumer(object):
"""
XBR Consumer interface.
"""
@six.add_metaclass(abc.ABCMeta)
class ISeller(object):
"""
XBR Seller interface.
"""
@abc.abstractmethod
async def start(self, session):
"""
:param session:
:return:
"""
@abc.abstractmethod
async def wrap(self, uri, payload):
"""
:param uri:
:param payload:
:return:
"""
@six.add_metaclass(abc.ABCMeta)
class IBuyer(object):
"""
XBR Buyer interface.
"""
@abc.abstractmethod
async def start(self, session):
"""
Start buying keys over the provided session.
:param session: WAMP session that allows to talk to the XBR Market Maker.
"""
@abc.abstractmethod
async def unwrap(self, key_id, enc_ser, ciphertext):
"""
Decrypt and deserialize received XBR payload.
:param key_id: The ID of the datakey the payload is encrypted with.
:type key_id: bytes
:param enc_ser: The serializer that was used for serializing the payload. One of ``cbor``, ``json``, ``msgpack``, ``ubjson``.
:type enc_ser: str
:param ciphertext: The encrypted payload to unwrap.
:type ciphertext: bytes
:returns: The unwrapped application payload.
:rtype: object
"""
@@ -0,0 +1,162 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) 2018 Luis Teixeira
# - copied & modified from https://github.com/vergl4s/ethereum-mnemonic-utils
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
import hashlib
import hmac
import struct
from base58 import b58encode_check
from ecdsa.curves import SECP256k1
BIP39_PBKDF2_ROUNDS = 2048
BIP39_SALT_MODIFIER = "mnemonic"
BIP32_PRIVDEV = 0x80000000
BIP32_CURVE = SECP256k1
BIP32_SEED_MODIFIER = b'Bitcoin seed'
# https://github.com/ethereum/EIPs/issues/84#issuecomment-528213145
LEDGER_ETH_DERIVATION_PATH = "m/44'/60'/0'/0"
def mnemonic_to_bip39seed(mnemonic, passphrase):
""" BIP39 seed from a mnemonic key.
Logic adapted from https://github.com/trezor/python-mnemonic. """
mnemonic = bytes(mnemonic, 'utf8')
salt = bytes(BIP39_SALT_MODIFIER + passphrase, 'utf8')
return hashlib.pbkdf2_hmac('sha512', mnemonic, salt, BIP39_PBKDF2_ROUNDS)
def bip39seed_to_bip32masternode(seed):
""" BIP32 master node derivation from a bip39 seed.
Logic adapted from https://github.com/satoshilabs/slips/blob/master/slip-0010/testvectors.py. """
h = hmac.new(BIP32_SEED_MODIFIER, seed, hashlib.sha512).digest()
key, chain_code = h[:32], h[32:]
return key, chain_code
def derive_public_key(private_key):
""" Public key from a private key.
Logic adapted from https://github.com/satoshilabs/slips/blob/master/slip-0010/testvectors.py. """
Q = int.from_bytes(private_key, byteorder='big') * BIP32_CURVE.generator
xstr = Q.x().to_bytes(32, byteorder='big')
parity = Q.y() & 1
return (2 + parity).to_bytes(1, byteorder='big') + xstr
def derive_bip32childkey(parent_key, parent_chain_code, i):
""" Derives a child key from an existing key, i is current derivation parameter.
Logic adapted from https://github.com/satoshilabs/slips/blob/master/slip-0010/testvectors.py. """
assert len(parent_key) == 32
assert len(parent_chain_code) == 32
k = parent_chain_code
if (i & BIP32_PRIVDEV) != 0:
key = b'\x00' + parent_key
else:
key = derive_public_key(parent_key)
d = key + struct.pack('>L', i)
while True:
h = hmac.new(k, d, hashlib.sha512).digest()
key, chain_code = h[:32], h[32:]
a = int.from_bytes(key, byteorder='big')
b = int.from_bytes(parent_key, byteorder='big')
key = (a + b) % BIP32_CURVE.order
if a < BIP32_CURVE.order and key != 0:
key = key.to_bytes(32, byteorder='big')
break
d = b'\x01' + h[32:] + struct.pack('>L', i)
return key, chain_code
def fingerprint(public_key):
""" BIP32 fingerprint formula, used to get b58 serialized key. """
return hashlib.new('ripemd160', hashlib.sha256(public_key).digest()).digest()[:4]
def b58xprv(parent_fingerprint, private_key, chain, depth, childnr):
""" Private key b58 serialization format. """
raw = (b'\x04\x88\xad\xe4' + bytes(chr(depth), 'utf-8') + parent_fingerprint + childnr.to_bytes(
4, byteorder='big') + chain + b'\x00' + private_key)
return b58encode_check(raw)
def b58xpub(parent_fingerprint, public_key, chain, depth, childnr):
""" Public key b58 serialization format. """
raw = (b'\x04\x88\xb2\x1e' + bytes(chr(depth), 'utf-8') + parent_fingerprint + childnr.to_bytes(
4, byteorder='big') + chain + public_key)
return b58encode_check(raw)
def parse_derivation_path(str_derivation_path):
""" Parses a derivation path such as "m/44'/60/0'/0" and returns
list of integers for each element in path. """
path = []
if str_derivation_path[0:2] != 'm/':
raise ValueError("Can't recognize derivation path. It should look like \"m/44'/60/0'/0\".")
for i in str_derivation_path.lstrip('m/').split('/'):
if "'" in i:
path.append(BIP32_PRIVDEV + int(i[:-1]))
else:
path.append(int(i))
return path
def mnemonic_to_private_key(mnemonic, str_derivation_path=LEDGER_ETH_DERIVATION_PATH, passphrase=""):
""" Performs all convertions to get a private key from a mnemonic sentence, including:
BIP39 mnemonic to seed
BIP32 seed to master key
BIP32 child derivation of a path provided
Parameters:
mnemonic -- seed wordlist, usually with 24 words, that is used for ledger wallet backup
str_derivation_path -- string that directs BIP32 key derivation, defaults to path
used by ledger ETH wallet
"""
derivation_path = parse_derivation_path(str_derivation_path)
bip39seed = mnemonic_to_bip39seed(mnemonic, passphrase)
master_private_key, master_chain_code = bip39seed_to_bip32masternode(bip39seed)
private_key, chain_code = master_private_key, master_chain_code
for i in derivation_path:
private_key, chain_code = derive_bip32childkey(private_key, chain_code, i)
return private_key
@@ -0,0 +1,218 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################
from binascii import a2b_hex
import click
import web3
from py_eth_sig_utils import signing
_EIP712_SIG_LEN = 32 + 32 + 1
def unpack_uint128(data):
assert data is None or type(data) == bytes, 'data must by bytes, was {}'.format(type(data))
if data and type(data) == bytes:
assert len(data) == 16, 'data must be bytes[16], but was bytes[{}]'.format(len(data))
if data:
return web3.Web3.toInt(data)
else:
return 0
def pack_uint128(value):
assert value is None or (type(value) == int and value >= 0 and value < 2**128)
if value:
data = web3.Web3.toBytes(value)
return b'\x00' * (16 - len(data)) + data
else:
return b'\x00' * 16
# FIXME: possibly use https://eth-abi.readthedocs.io/en/stable/decoding.html
def unpack_uint256(data):
assert data is None or type(data) == bytes, 'data must by bytes, was {}'.format(type(data))
if data and type(data) == bytes:
assert len(data) == 32, 'data must be bytes[32], but was bytes[{}]'.format(len(data))
if data:
return int(web3.Web3.toInt(data))
else:
return 0
def pack_uint256(value):
assert value is None or (type(value) == int and value >= 0 and value < 2**256), 'value must be uint256, but was {}'.format(value)
if value:
data = web3.Web3.toBytes(value)
return b'\x00' * (32 - len(data)) + data
else:
return b'\x00' * 32
def hl(text, bold=True, color='yellow'):
if not isinstance(text, str):
text = '{}'.format(text)
return click.style(text, fg=color, bold=bold)
def _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final):
assert type(verifying_adr) == bytes and len(verifying_adr) == 20
assert type(channel_adr) == bytes and len(channel_adr) == 20
assert type(channel_seq) == int
assert type(balance) == int
assert type(is_final) == bool
data = {
'types': {
'EIP712Domain': [
{'name': 'name', 'type': 'string'},
{'name': 'version', 'type': 'string'},
{'name': 'chainId', 'type': 'uint256'},
{'name': 'verifyingContract', 'type': 'address'},
],
'ChannelClose': [
# The channel contract address.
{'name': 'channel_adr', 'type': 'address'},
# Channel off-chain transaction sequence number.
{'name': 'channel_seq', 'type': 'uint32'},
# Balance remaining in after the transaction.
{'name': 'balance', 'type': 'uint256'},
# Transaction is marked as final.
{'name': 'is_final', 'type': 'bool'},
],
},
'primaryType': 'ChannelClose',
'domain': {
'name': 'XBR',
'version': '1',
'chainId': 1,
'verifyingContract': verifying_adr,
},
'message': {
'channel_adr': channel_adr,
'channel_seq': channel_seq,
'balance': balance,
'is_final': is_final
},
}
return data
def sign_eip712_data(eth_privkey, channel_adr, channel_seq, balance, is_final=False):
"""
:param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address).
:type eth_privkey: bytes
:param channel_adr: Channel contract address.
:type channel_adr: bytes
:param channel_seq: Payment channel off-chain transaction sequence number.
:type channel_seq: int
:param balance: Balance remaining in the payment/paying channel after buying/selling the key.
:type balance: int
:param is_final: Flag to indicate the transaction is considered final.
:type is_final: bool
:return: The signature according to EIP712 (32+32+1 raw bytes).
:rtype: bytes
"""
assert type(eth_privkey) == bytes and len(eth_privkey) == 32
assert type(channel_adr) == bytes and len(channel_adr) == 20
assert type(channel_seq) == int and channel_seq > 0
assert type(balance) == int and balance >= 0
assert type(is_final) == bool
verifying_adr = a2b_hex('0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B'[2:])
# make a private key object from the raw private key bytes
# pkey = eth_keys.keys.PrivateKey(eth_privkey)
# get the canonical address of the account
# eth_adr = web3.Web3.toChecksumAddress(pkey.public_key.to_canonical_address())
# eth_adr = pkey.public_key.to_canonical_address()
# create EIP712 typed data object
data = _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final)
# FIXME: this fails on PyPy (but ot on CPy!) with
# Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b
_args = signing.sign_typed_data(data, eth_privkey)
signature = signing.v_r_s_to_signature(*_args)
assert len(signature) == _EIP712_SIG_LEN
return signature
def recover_eip712_signer(channel_adr, channel_seq, balance, is_final, signature):
"""
Recover the signer address the given EIP712 signature was signed with.
:param channel_adr: Channel contract address.
:type channel_adr: bytes
:param channel_seq: Payment channel off-chain transaction sequence number.
:type channel_seq: int
:param balance: Balance remaining in the payment/paying channel after buying/selling the key.
:type balance: int
:param is_final: Flag to indicate the transaction is considered final.
:type is_final: bool
:param signature: The EIP712 (32+32+1 raw bytes) signature to verify.
:type signature: bytes
:return: The (computed) signer address the signature was signed with.
:rtype: bytes
"""
assert type(channel_adr) == bytes and len(channel_adr) == 20
assert type(channel_seq) == int
assert type(balance) == int
assert type(is_final) == bool
assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN
verifying_adr = a2b_hex('0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B'[2:])
# recreate EIP712 typed data object
data = _create_eip712_data(verifying_adr, channel_adr, channel_seq, balance, is_final)
# this returns the signer (checksummed) address as a string, eg "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d"
signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature))
return a2b_hex(signer_address[2:])
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff