experimental ed25519 support

This commit is contained in:
Tobias Oberstein 2015-12-26 23:13:40 +01:00
parent a98f15b533
commit c488429922
7 changed files with 414 additions and 51 deletions

141
autobahn/wamp/keyring.py Normal file
View File

@ -0,0 +1,141 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Tavendo 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 os
import json
import binascii
from types import NoneType
__all__ = (
'KeyRing',
)
try:
import nacl # noqa
HAS_NACL = True
except ImportError:
HAS_NACL = False
class EncryptedPayload(object):
def __init__(self, algo, pkey, serializer, payload):
self.algo = algo
self.pkey = pkey
self.serializer = serializer
self.payload = payload
if HAS_NACL:
from nacl.encoding import Base64Encoder
from nacl.public import PrivateKey, Box
from nacl.utils import random
from pytrie import StringTrie
class KeyRing(object):
"""
In WAMP, a WAMP client connected to a router in general will
send and receive WAMP messages containing application payload
in the "args" and "kwargs" message fields.
Futher, a WAMP client will send the following WAMP message types:
* PUBLISH
* CALL
* YIELD
* ERROR
and will receive the following message types
* EVENT
* RESULT
* ERROR
* INVOCATION
A keyring maps an URI to a pair of Ed25519 keys, one for the
sending side, and one for the receiving side.
A client wishing to publish to topic T1 first looks up T1 in
a keyring. This will return the pair of keys (Sender_key, Receiver_key)
that have been stored under an URI K1 such that K1 is a longest match of T1.
The client generates a new random message key and encrypt the
message. The symmetric message encrypted key is then encrypted using
the Pub(Receiver_key) and Priv(Sender_key). This encrypted symmetric key
is attached to the WAMP message in `option.message_key`.
"""
def __init__(self):
self._uri_map = StringTrie()
def add(self, uri_prefix, key_base64=None):
if uri_prefix not in self._uri_map:
if key_base64:
key = PrivateKey(key_base64, encoder=Base64Encoder)
box = Box(key, key.public_key)
else:
box = None
self._uri_map[uri_prefix] = box
print("key set for {}".format(uri_prefix))
def check(self, uri):
"""
Return True if sending a message to the URI should have it's
application payload args and kwargs encrypted.
"""
try:
box = self._uri_map.longest_prefix_value(uri)
return box is not None
except KeyError:
return False
def encrypt(self, uri, args=None, kwargs=None):
box = self._uri_map.longest_prefix_value(uri)
payload = {
u'uri': uri,
u'args': args,
u'kwargs': kwargs
}
nonce = random(Box.NONCE_SIZE)
payload_ser = json.dumps(payload)
payload_encr = box.encrypt(payload_ser, nonce, encoder=Base64Encoder)
payload_bytes = payload_encr.encode().decode('ascii')
payload_key = binascii.b2a_base64(os.urandom(32)).strip().decode('ascii')
return EncryptedPayload(u'ed25519-sha512', payload_key, u'json', payload_bytes)
def decrypt(self, uri, encrypted_payload):
box = self._uri_map.longest_prefix_value(uri)
payload_ser = box.decrypt(encrypted_payload.payload, encoder=Base64Encoder)
payload = json.loads(payload_ser)
return payload[u'uri'], payload[u'args'], payload[u'kwargs']
else:
KeyRing = NoneType

View File

@ -27,6 +27,7 @@
from __future__ import absolute_import
import re
import six
import autobahn
@ -84,6 +85,9 @@ _URI_PAT_LOOSE_LAST_EMPTY = re.compile(r"^([^\s\.#]+\.)*([^\s\.#]*)$")
# custom (=implementation specific) WAMP attributes (used in WAMP message details/options)
_CUSTOM_ATTRIBUTE = re.compile(r"^x_([a-z][0-9a-z_]+)?$")
# value for algo attribute in end-to-end encrypted messages
_ED25519_SHA512 = u'ed25519-sha512'
def check_or_raise_uri(value, message=u"WAMP message invalid", strict=False, allowEmptyComponents=False):
"""
@ -967,7 +971,11 @@ class Publish(Message):
exclude_me=None,
exclude=None,
eligible=None,
disclose_me=None):
disclose_me=None,
ep_algo=None,
ep_key=None,
ep_serializer=None,
ep_payload=None):
"""
:param request: The WAMP request ID of this request.
@ -997,14 +1005,21 @@ class Publish(Message):
"""
assert(type(request) in six.integer_types)
assert(type(topic) == six.text_type)
assert(args is None or type(args) in [list, tuple])
assert(kwargs is None or type(kwargs) == dict)
assert(args is None or type(args) in [list, tuple, six.text_type, six.binary_type])
assert(kwargs is None or type(kwargs) in [dict, six.text_type, six.binary_type])
assert(acknowledge is None or type(acknowledge) == bool)
assert(exclude_me is None or type(exclude_me) == bool)
assert(exclude is None or type(exclude) == list)
assert(eligible is None or type(eligible) == list)
assert(disclose_me is None or type(disclose_me) == bool)
# end-to-end app payload encryption
assert(ep_algo is None or ep_algo in [_ED25519_SHA512])
assert(ep_key is None or type(ep_key) in [six.text_type, six.binary_type])
assert(ep_serializer is None or ep_serializer in [u'json', u'msgpack', u'cbor'])
assert(ep_payload is None or (ep_serializer == u'json' and type(ep_payload) == six.text_type) or (ep_serializer in [u'msgpack', u'cbor'] and type(ep_payload) == six.binary_type))
assert((ep_algo is None and ep_key is None and ep_serializer is None and ep_payload is None) or (ep_algo is not None and ep_key is not None and ep_serializer is not None and ep_payload is not None))
Message.__init__(self)
self.request = request
self.topic = topic
@ -1016,6 +1031,12 @@ class Publish(Message):
self.eligible = eligible
self.disclose_me = disclose_me
# end-to-end app payload encryption
self.ep_algo = ep_algo
self.ep_key = ep_key
self.ep_serializer = ep_serializer
self.ep_payload = ep_payload
@staticmethod
def parse(wmsg):
"""
@ -1038,16 +1059,39 @@ class Publish(Message):
topic = check_or_raise_uri(wmsg[3], u"'topic' in PUBLISH")
args = None
if len(wmsg) > 4:
args = wmsg[4]
if type(args) != list:
raise ProtocolError("invalid type {0} for 'args' in PUBLISH".format(type(args)))
kwargs = None
if len(wmsg) > 5:
kwargs = wmsg[5]
if type(kwargs) != dict:
raise ProtocolError("invalid type {0} for 'kwargs' in PUBLISH".format(type(kwargs)))
ep_algo = None
ep_key = None
ep_serializer = None
ep_payload = None
if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]:
ep_payload = wmsg[4]
ep_algo = options.get(u'ep_algo', None)
if ep_algo != _ED25519_SHA512:
raise ProtocolError("invalid value {0} for 'ep_algo' option in PUBLISH".format(ep_algo))
ep_key = options.get(u'ep_key', None)
if type(ep_key) not in [six.text_type, six.binary_type]:
raise ProtocolError("invalid type {0} for 'ep_key' option in PUBLISH".format(type(ep_key)))
ep_serializer = options.get(u'ep_serializer', None)
if ep_serializer not in [u'json', u'msgpack', u'cbor']:
raise ProtocolError("invalid value {0} for 'ep_serializer' option in PUBLISH".format(ep_serializer))
else:
if len(wmsg) > 4:
args = wmsg[4]
if type(args) not in [list, six.text_type, six.binary_type]:
raise ProtocolError("invalid type {0} for 'args' in PUBLISH".format(type(args)))
if len(wmsg) > 5:
kwargs = wmsg[5]
if type(kwargs) not in [dict, six.text_type, six.binary_type]:
raise ProtocolError("invalid type {0} for 'kwargs' in PUBLISH".format(type(kwargs)))
acknowledge = None
exclude_me = None
@ -1111,7 +1155,11 @@ class Publish(Message):
exclude_me=exclude_me,
exclude=exclude,
eligible=eligible,
disclose_me=disclose_me)
disclose_me=disclose_me,
ep_algo=ep_algo,
ep_key=ep_key,
ep_serializer=ep_serializer,
ep_payload=ep_payload)
return obj
@ -1134,18 +1182,24 @@ class Publish(Message):
if self.disclose_me is not None:
options[u'disclose_me'] = self.disclose_me
if self.kwargs:
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args, self.kwargs]
elif self.args:
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args]
if self.ep_payload:
options[u'ep_algo'] = self.ep_algo
options[u'ep_key'] = self.ep_key
options[u'ep_serializer'] = self.ep_serializer
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.ep_payload]
else:
return [Publish.MESSAGE_TYPE, self.request, options, self.topic]
if self.kwargs:
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args, self.kwargs]
elif self.args:
return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args]
else:
return [Publish.MESSAGE_TYPE, self.request, options, self.topic]
def __str__(self):
"""
Returns string representation of this message.
"""
return "WAMP PUBLISH Message (request = {0}, topic = {1}, args = {2}, kwargs = {3}, acknowledge = {4}, exclude_me = {5}, exclude = {6}, eligible = {7}, disclose_me = {8})".format(self.request, self.topic, self.args, self.kwargs, self.acknowledge, self.exclude_me, self.exclude, self.eligible, self.disclose_me)
return "WAMP PUBLISH Message (request = {0}, topic = {1}, args = {2}, kwargs = {3}, acknowledge = {4}, exclude_me = {5}, exclude = {6}, eligible = {7}, disclose_me = {8}, ep_algo = {9}, ep_key = {10}, ep_serializer = {11}, ep_payload_len = {12})".format(self.request, self.topic, self.args, self.kwargs, self.acknowledge, self.exclude_me, self.exclude, self.eligible, self.disclose_me, self.ep_algo, self.ep_key, self.ep_serializer, len(self.ep_payload) if self.ep_payload else 0)
class Published(Message):
@ -1555,7 +1609,8 @@ class Event(Message):
The WAMP message code for this type of message.
"""
def __init__(self, subscription, publication, args=None, kwargs=None, publisher=None, topic=None):
def __init__(self, subscription, publication, args=None, kwargs=None, publisher=None, topic=None,
ep_algo=None, ep_key=None, ep_serializer=None, ep_payload=None):
"""
:param subscription: The subscription ID this event is dispatched under.
@ -1580,6 +1635,13 @@ class Event(Message):
assert(publisher is None or type(publisher) in six.integer_types)
assert(topic is None or type(topic) == six.text_type)
# end-to-end app payload encryption
assert(ep_algo is None or ep_algo in [_ED25519_SHA512])
assert(ep_key is None or type(ep_key) in [six.text_type, six.binary_type])
assert(ep_serializer is None or ep_serializer in [u'json', u'msgpack', u'cbor'])
assert(ep_payload is None or (ep_serializer == u'json' and type(ep_payload) == six.text_type) or (ep_serializer in [u'msgpack', u'cbor'] and type(ep_payload) == six.binary_type))
assert((ep_algo is None and ep_key is None and ep_serializer is None and ep_payload is None) or (ep_algo is not None and ep_key is not None and ep_serializer is not None and ep_payload is not None))
Message.__init__(self)
self.subscription = subscription
self.publication = publication
@ -1588,6 +1650,12 @@ class Event(Message):
self.publisher = publisher
self.topic = topic
# end-to-end app payload encryption
self.ep_algo = ep_algo
self.ep_key = ep_key
self.ep_serializer = ep_serializer
self.ep_payload = ep_payload
@staticmethod
def parse(wmsg):
"""
@ -1610,16 +1678,38 @@ class Event(Message):
details = check_or_raise_extra(wmsg[3], u"'details' in EVENT")
args = None
if len(wmsg) > 4:
args = wmsg[4]
if type(args) != list:
raise ProtocolError("invalid type {0} for 'args' in EVENT".format(type(args)))
kwargs = None
if len(wmsg) > 5:
kwargs = wmsg[5]
if type(kwargs) != dict:
raise ProtocolError("invalid type {0} for 'kwargs' in EVENT".format(type(kwargs)))
ep_algo = None
ep_key = None
ep_serializer = None
ep_payload = None
if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]:
ep_payload = wmsg[4]
ep_algo = details.get(u'ep_algo', None)
if ep_algo != _ED25519_SHA512:
raise ProtocolError("invalid value {0} for 'ep_algo' detail in EVENT".format(ep_algo))
ep_key = details.get(u'ep_key', None)
if type(ep_key) not in [six.text_type, six.binary_type]:
raise ProtocolError("invalid type {0} for 'ep_key' detail in EVENT".format(type(ep_key)))
ep_serializer = details.get(u'ep_serializer', None)
if ep_serializer not in [u'json', u'msgpack', u'cbor']:
raise ProtocolError("invalid value {0} for 'ep_serializer' detail in EVENT".format(ep_serializer))
else:
if len(wmsg) > 4:
args = wmsg[4]
if type(args) not in [list, six.text_type, six.binary_type]:
raise ProtocolError("invalid type {0} for 'args' in EVENT".format(type(args)))
if len(wmsg) > 5:
kwargs = wmsg[5]
if type(kwargs) not in [dict]:
raise ProtocolError("invalid type {0} for 'kwargs' in EVENT".format(type(kwargs)))
publisher = None
if u'publisher' in details:
@ -1644,7 +1734,11 @@ class Event(Message):
args=args,
kwargs=kwargs,
publisher=publisher,
topic=topic)
topic=topic,
ep_algo=ep_algo,
ep_key=ep_key,
ep_serializer=ep_serializer,
ep_payload=ep_payload)
return obj
@ -1662,18 +1756,24 @@ class Event(Message):
if self.topic is not None:
details[u'topic'] = self.topic
if self.kwargs:
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args, self.kwargs]
elif self.args:
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args]
if self.ep_payload:
details[u'ep_algo'] = self.ep_algo
details[u'ep_key'] = self.ep_key
details[u'ep_serializer'] = self.ep_serializer
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.ep_payload]
else:
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details]
if self.kwargs:
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args, self.kwargs]
elif self.args:
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args]
else:
return [Event.MESSAGE_TYPE, self.subscription, self.publication, details]
def __str__(self):
"""
Returns string representation of this message.
"""
return "WAMP EVENT Message (subscription = {0}, publication = {1}, args = {2}, kwargs = {3}, publisher = {4}, topic = {5})".format(self.subscription, self.publication, self.args, self.kwargs, self.publisher, self.topic)
return "WAMP EVENT Message (subscription = {0}, publication = {1}, args = {2}, kwargs = {3}, publisher = {4}, topic = {5}, ep_algo = {6}, ep_key = {7}, ep_serializer = {8}, ep_payload_len = {9})".format(self.subscription, self.publication, self.args, self.kwargs, self.publisher, self.topic, self.ep_algo, self.ep_key, self.ep_serializer, len(self.ep_payload) if self.ep_payload else 0)
class Call(Message):

View File

@ -39,6 +39,7 @@ from autobahn.wamp import exception
from autobahn.wamp.exception import ApplicationError, ProtocolError, SessionNotReady, SerializationError
from autobahn.wamp.interfaces import IApplicationSession # noqa
from autobahn.wamp.types import SessionDetails
from autobahn.wamp.keyring import EncryptedPayload
from autobahn.util import IdGenerator, ObservableMixin
from autobahn.wamp.request import \
@ -227,6 +228,7 @@ class ApplicationSession(BaseSession):
self._transport = None
self._session_id = None
self._realm = None
self._keyring = None
self._goodbye_sent = False
self._transport_is_closing = False
@ -430,6 +432,16 @@ class ApplicationSession(BaseSession):
for subscription in self._subscriptions[msg.subscription]:
handler = subscription.handler
topic = msg.topic or subscription.topic
if msg.ep_payload:
if self._keyring:
encrypted_payload = EncryptedPayload(msg.ep_algo, msg.ep_key, msg.ep_serializer, msg.ep_payload)
decrypted_topic, msg.args, msg.kwargs = self._keyring.decrypt(topic, encrypted_payload)
if topic != decrypted_topic:
raise Exception("envelope topic URI does not match encrypted one")
else:
raise Exception("received encrypted payload, but no keyring active")
invoke_args = (handler.obj,) if handler.obj else tuple()
if msg.args:
@ -437,13 +449,15 @@ class ApplicationSession(BaseSession):
invoke_kwargs = msg.kwargs if msg.kwargs else dict()
if handler.details_arg:
invoke_kwargs[handler.details_arg] = types.EventDetails(publication=msg.publication, publisher=msg.publisher, topic=msg.topic or subscription.topic)
invoke_kwargs[handler.details_arg] = types.EventDetails(publication=msg.publication, publisher=msg.publisher, topic=topic)
def _error(e):
errmsg = 'While firing {0} subscribed under {1}.'.format(
handler.fn, msg.subscription)
return self._swallow_error(e, errmsg)
print("$$", invoke_args)
future = txaio.as_future(handler.fn, *invoke_args, **invoke_kwargs)
txaio.add_callbacks(future, None, _error)
@ -458,7 +472,7 @@ class ApplicationSession(BaseSession):
publish_request = self._publish_reqs.pop(msg.request)
# create a new publication object
publication = Publication(msg.publication)
publication = Publication(msg.publication, was_encrypted=publish_request.was_encrypted)
# resolve deferred/future for publishing successfully
txaio.resolve(publish_request.on_reply, publication)
@ -821,19 +835,36 @@ class ApplicationSession(BaseSession):
if not self._transport:
raise exception.TransportLost()
request_id = self._request_id_gen.next()
if 'options' in kwargs and isinstance(kwargs['options'], types.PublishOptions):
options = kwargs.pop('options')
msg = message.Publish(request_id, topic, args=args, kwargs=kwargs, **options.message_attr())
else:
options = None
msg = message.Publish(request_id, topic, args=args, kwargs=kwargs)
request_id = self._request_id_gen.next()
if self._keyring and self._keyring.check(topic):
encrypted_payload = self._keyring.encrypt(topic, args, kwargs)
else:
encrypted_payload = None
if encrypted_payload:
msg = message.Publish(request_id,
topic,
ep_algo=encrypted_payload.algo,
ep_key=encrypted_payload.pkey,
ep_serializer=encrypted_payload.serializer,
ep_payload=encrypted_payload.payload,
**options.message_attr())
else:
if options:
msg = message.Publish(request_id, topic, args=args, kwargs=kwargs, **options.message_attr())
else:
msg = message.Publish(request_id, topic, args=args, kwargs=kwargs)
if options and options.acknowledge:
# only acknowledged publications expect a reply ..
on_reply = txaio.create_future()
self._publish_reqs[request_id] = PublishRequest(request_id, on_reply)
self._publish_reqs[request_id] = PublishRequest(request_id, on_reply, was_encrypted=(encrypted_payload is not None))
else:
on_reply = None

View File

@ -51,13 +51,14 @@ class Publication(object):
an acknowledged publish).
"""
__slots__ = ('id')
__slots__ = ('id', 'was_encrypted')
def __init__(self, publication_id):
def __init__(self, publication_id, was_encrypted):
self.id = publication_id
self.was_encrypted = was_encrypted
def __str__(self):
return "Publication(id={0})".format(self.id)
return "Publication(id={0}, was_encrypted={1})".format(self.id, self.was_encrypted)
class Subscription(object):
@ -180,6 +181,12 @@ class PublishRequest(Request):
Object representing an outstanding request to publish (acknowledged) an event.
"""
__slots__ = ('was_encrypted')
def __init__(self, request_id, on_reply, was_encrypted):
Request.__init__(self, request_id, on_reply)
self.was_encrypted = was_encrypted
class SubscribeRequest(Request):
"""

View File

@ -53,15 +53,21 @@ class ComponentConfig(object):
provided to the constructor of :class:`autobahn.wamp.protocol.ApplicationSession`.
"""
def __init__(self, realm=None, extra=None):
def __init__(self, realm=None, extra=None, keyring=None):
"""
:param realm: The realm the session should join.
:type realm: unicode
:param extra: Optional user-supplied object with extra
configuration. This can be any object you like, and is
accessible in your `ApplicationSession` subclass via
`self.config.extra`. `dict` is a good default choice.
:type extra: dict or None
:param keyring: A mapper from WAMP URIs to "from"/"to" Ed25519 keys. When using
WAMP end-to-end encryption, application payload is encrypted using a
symmetric message key, which in turn is encrypted using the "to" URI (topic being
published to or procedure being called) public key and the "from" URI
private key. In both cases, the key for the longest matching URI is used.
:type keyring: obj implementing IKeyRing
"""
realm = realm or u'default'
# FIXME
@ -71,9 +77,10 @@ class ComponentConfig(object):
raise RuntimeError('"realm" must be of type Unicode - was {0}'.format(type(realm)))
self.realm = realm
self.extra = extra
self.keyring = keyring
def __str__(self):
return "ComponentConfig(realm = {0}, extra = {1})".format(self.realm, self.extra)
return "ComponentConfig(realm = {0}, extra = {1}, keyring = {2})".format(self.realm, self.extra, self.keyring)
class HelloReturn(object):
@ -359,7 +366,7 @@ class PublishOptions(object):
u'exclude_me': self.exclude_me,
u'exclude': self.exclude,
u'eligible': self.eligible,
u'disclose_me': self.disclose_me
u'disclose_me': self.disclose_me,
}
def __str__(self):

View File

@ -0,0 +1,76 @@
###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Tavendo 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 print_function
from os import environ
from twisted.internet.defer import inlineCallbacks
from autobahn.twisted.util import sleep
from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner
from autobahn.wamp.types import PublishOptions
ENCRYPTION_KEY = u'z1JePdJbQkbRCWjldZYImgj5hpsZ2cEtX7CQmQmdta4='
from autobahn.wamp.keyring import KeyRing
class Component(ApplicationSession):
"""
An application component that publishes an event every second.
"""
@inlineCallbacks
def onJoin(self, details):
print("session attached")
self._keyring = KeyRing()
self._keyring.add(u'com.myapp.topic1', ENCRYPTION_KEY)
def on_message(*args, **kwargs):
print("received: args={}, kwargs={}".format(args, kwargs))
yield self.subscribe(on_message, u'com.myapp.topic1')
yield self.subscribe(on_message, u'com.myapp.topic2')
options = PublishOptions(acknowledge=True, exclude_me=False)
counter = 0
while True:
msg = u"Hello, world! [{}]".format(counter)
yield self.publish(u'com.myapp.topic1', msg, options=options)
yield self.publish(u'com.myapp.topic2', msg, options=options)
print('published', counter)
counter += 1
yield sleep(1)
if __name__ == '__main__':
runner = ApplicationRunner(
environ.get("AUTOBAHN_DEMO_ROUTER", u"ws://127.0.0.1:8080/ws"),
u"realm1",
debug_wamp=False, # optional; log many WAMP details
debug=False, # optional; log even more details
)
runner.run(Component)

View File

@ -135,7 +135,8 @@ extras_require_all = extras_require_twisted + extras_require_asyncio + \
# development dependencies
#
extras_require_dev = [
"pep8>=1.6.2", # MIT license
# flake8 will install the version "it needs"
# "pep8>=1.6.2", # MIT license
"pep8-naming>=0.3.3", # MIT license
"flake8>=2.4.1", # MIT license
"pyflakes>=0.9.2", # MIT license