############################################################################### # # 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 re import binascii import six import autobahn from autobahn import util from autobahn.wamp.exception import ProtocolError from autobahn.wamp.role import ROLE_NAME_TO_CLASS __all__ = ('Message', 'Hello', 'Welcome', 'Abort', 'Challenge', 'Authenticate', 'Goodbye', 'Error', 'Publish', 'Published', 'Subscribe', 'Subscribed', 'Unsubscribe', 'Unsubscribed', 'Event', 'Call', 'Cancel', 'Result', 'Register', 'Registered', 'Unregister', 'Unregistered', 'Invocation', 'Interrupt', 'Yield', 'check_or_raise_uri', 'check_or_raise_id', 'PAYLOAD_ENC_CRYPTO_BOX') # strict URI check allowing empty URI components _URI_PAT_STRICT_EMPTY = re.compile(r"^(([0-9a-z_]+\.)|\.)*([0-9a-z_]+)?$") # loose URI check allowing empty URI components _URI_PAT_LOOSE_EMPTY = re.compile(r"^(([^\s\.#]+\.)|\.)*([^\s\.#]+)?$") # strict URI check disallowing empty URI components _URI_PAT_STRICT_NON_EMPTY = re.compile(r"^([0-9a-z_]+\.)*([0-9a-z_]+)$") # loose URI check disallowing empty URI components _URI_PAT_LOOSE_NON_EMPTY = re.compile(r"^([^\s\.#]+\.)*([^\s\.#]+)$") # strict URI check disallowing empty URI components in all but the last component _URI_PAT_STRICT_LAST_EMPTY = re.compile(r"^([0-9a-z_]+\.)*([0-9a-z_]*)$") # loose URI check disallowing empty URI components in all but the last component _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 using cryptobox, which # is a scheme based on Curve25519, SHA512, Salsa20 and Poly1305. # See: http://cr.yp.to/highspeed/coolnacl-20120725.pdf PAYLOAD_ENC_CRYPTO_BOX = u'cryptobox' def b2a(data, max_len=40): if type(data) == six.text_type: s = data elif type(data) == six.binary_type: s = binascii.b2a_hex(data) elif data is None: s = '-' else: s = u'{}'.format(data) if len(s) > max_len: return s[:max_len] + u'..' else: return s def check_or_raise_uri(value, message=u"WAMP message invalid", strict=False, allow_empty_components=False, allow_last_empty=False, allow_none=False): """ Check a value for being a valid WAMP URI. If the value is not a valid WAMP URI is invalid, raises :class:`autobahn.wamp.exception.ProtocolError`. Otherwise return the value. :param value: The value to check. :type value: unicode or None :param message: Prefix for message in exception raised when value is invalid. :type message: unicode :param strict: If ``True``, do a strict check on the URI (the WAMP spec SHOULD behavior). :type strict: bool :param allow_empty_components: If ``True``, allow empty URI components (for pattern based subscriptions and registrations). :type allow_empty_components: bool :param allow_none: If ``True``, allow ``None`` for URIs. :type allow_none: bool :returns: The URI value (if valid). :rtype: unicode :raises: instance of :class:`autobahn.wamp.exception.ProtocolError` """ if value is None: if allow_none: return else: raise ProtocolError(u"{0}: URI cannot be null".format(message)) if type(value) != six.text_type: if not (value is None and allow_none): raise ProtocolError(u"{0}: invalid type {1} for URI".format(message, type(value))) if strict: if allow_last_empty: pat = _URI_PAT_STRICT_LAST_EMPTY elif allow_empty_components: pat = _URI_PAT_STRICT_EMPTY else: pat = _URI_PAT_STRICT_NON_EMPTY else: if allow_last_empty: pat = _URI_PAT_LOOSE_LAST_EMPTY elif allow_empty_components: pat = _URI_PAT_LOOSE_EMPTY else: pat = _URI_PAT_LOOSE_NON_EMPTY if not pat.match(value): raise ProtocolError(u"{0}: invalid value '{1}' for URI (did not match pattern {2}, strict={3}, allow_empty_components={4}, allow_last_empty={5}, allow_none={6})".format(message, value, pat.pattern, strict, allow_empty_components, allow_last_empty, allow_none)) else: return value def check_or_raise_id(value, message=u"WAMP message invalid"): """ Check a value for being a valid WAMP ID. If the value is not a valid WAMP ID, raises :class:`autobahn.wamp.exception.ProtocolError`. Otherwise return the value. :param value: The value to check. :type value: int :param message: Prefix for message in exception raised when value is invalid. :type message: unicode :returns: The ID value (if valid). :rtype: int :raises: instance of :class:`autobahn.wamp.exception.ProtocolError` """ if type(value) not in six.integer_types: raise ProtocolError(u"{0}: invalid type {1} for ID".format(message, type(value))) # the value 0 for WAMP IDs is possible in certain WAMP messages, e.g. UNREGISTERED with # router revocation signaling! if value < 0 or value > 9007199254740992: # 2**53 raise ProtocolError(u"{0}: invalid value {1} for ID".format(message, value)) return value def check_or_raise_extra(value, message=u"WAMP message invalid"): """ Check a value for being a valid WAMP extra dictionary. If the value is not a valid WAMP extra dictionary, raises :class:`autobahn.wamp.exception.ProtocolError`. Otherwise return the value. :param value: The value to check. :type value: dict :param message: Prefix for message in exception raised when value is invalid. :type message: unicode :returns: The extra dictionary (if valid). :rtype: dict :raises: instance of :class:`autobahn.wamp.exception.ProtocolError` """ if type(value) != dict: raise ProtocolError(u"{0}: invalid type {1}".format(message, type(value))) for k in value.keys(): if type(k) != six.text_type: raise ProtocolError(u"{0}: invalid type {1} for key '{2}'".format(message, type(k), k)) return value class Message(util.EqualityMixin): """ WAMP message base class. .. note:: This is not supposed to be instantiated. """ MESSAGE_TYPE = None """ WAMP message type code. """ def __init__(self): # serialization cache: mapping from ISerializer instances to serialized bytes self._serialized = {} @staticmethod def parse(wmsg): """ Factory method that parses a unserialized raw message (as returned byte :func:`autobahn.interfaces.ISerializer.unserialize`) into an instance of this class. :returns: obj -- An instance of this class. """ def uncache(self): """ Resets the serialization cache. """ self._serialized = {} def serialize(self, serializer): """ Serialize this object into a wire level bytes representation and cache the resulting bytes. If the cache already contains an entry for the given serializer, return the cached representation directly. :param serializer: The wire level serializer to use. :type serializer: An instance that implements :class:`autobahn.interfaces.ISerializer` :returns: bytes -- The serialized bytes. """ # only serialize if not cached .. if serializer not in self._serialized: self._serialized[serializer] = serializer.serialize(self.marshal()) return self._serialized[serializer] class Hello(Message): """ A WAMP ``HELLO`` message. Format: ``[HELLO, Realm|uri, Details|dict]`` """ MESSAGE_TYPE = 1 """ The WAMP message code for this type of message. """ def __init__(self, realm, roles, authmethods=None, authid=None, authrole=None, authextra=None): """ :param realm: The URI of the WAMP realm to join. :type realm: unicode :param roles: The WAMP session roles and features to announce. :type roles: dict of :class:`autobahn.wamp.role.RoleFeatures` :param authmethods: The authentication methods to announce. :type authmethods: list of unicode or None :param authid: The authentication ID to announce. :type authid: unicode or None :param authrole: The authentication role to announce. :type authrole: unicode or None :param authextra: Application-specific "extra data" to be forwarded to the client. :type authextra: arbitrary or None """ assert(realm is None or type(realm) == six.text_type) assert(type(roles) == dict) assert(len(roles) > 0) for role in roles: assert(role in [u'subscriber', u'publisher', u'caller', u'callee']) assert(isinstance(roles[role], autobahn.wamp.role.ROLE_NAME_TO_CLASS[role])) if authmethods: assert(type(authmethods) == list) for authmethod in authmethods: assert(type(authmethod) == six.text_type) assert(authid is None or type(authid) == six.text_type) assert(authrole is None or type(authrole) == six.text_type) assert(authextra is None or type(authextra) == dict) Message.__init__(self) self.realm = realm self.roles = roles self.authmethods = authmethods self.authid = authid self.authrole = authrole self.authextra = authextra @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Hello.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for HELLO".format(len(wmsg))) realm = check_or_raise_uri(wmsg[1], u"'realm' in HELLO", allow_none=True) details = check_or_raise_extra(wmsg[2], u"'details' in HELLO") roles = {} if u'roles' not in details: raise ProtocolError(u"missing mandatory roles attribute in options in HELLO") details_roles = check_or_raise_extra(details[u'roles'], u"'roles' in 'details' in HELLO") if len(details_roles) == 0: raise ProtocolError(u"empty 'roles' in 'details' in HELLO") for role in details_roles: if role not in [u'subscriber', u'publisher', u'caller', u'callee']: raise ProtocolError("invalid role '{0}' in 'roles' in 'details' in HELLO".format(role)) role_cls = ROLE_NAME_TO_CLASS[role] details_role = check_or_raise_extra(details_roles[role], "role '{0}' in 'roles' in 'details' in HELLO".format(role)) if u'features' in details_role: check_or_raise_extra(details_role[u'features'], "'features' in role '{0}' in 'roles' in 'details' in HELLO".format(role)) role_features = role_cls(**details_role[u'features']) else: role_features = role_cls() roles[role] = role_features authmethods = None if u'authmethods' in details: details_authmethods = details[u'authmethods'] if type(details_authmethods) != list: raise ProtocolError("invalid type {0} for 'authmethods' detail in HELLO".format(type(details_authmethods))) for auth_method in details_authmethods: if type(auth_method) != six.text_type: raise ProtocolError("invalid type {0} for item in 'authmethods' detail in HELLO".format(type(auth_method))) authmethods = details_authmethods authid = None if u'authid' in details: details_authid = details[u'authid'] if type(details_authid) != six.text_type: raise ProtocolError("invalid type {0} for 'authid' detail in HELLO".format(type(details_authid))) authid = details_authid authrole = None if u'authrole' in details: details_authrole = details[u'authrole'] if type(details_authrole) != six.text_type: raise ProtocolError("invalid type {0} for 'authrole' detail in HELLO".format(type(details_authrole))) authrole = details_authrole authextra = None if u'authextra' in details: details_authextra = details[u'authextra'] if type(details_authextra) != dict: raise ProtocolError("invalid type {0} for 'authextra' detail in HELLO".format(type(details_authextra))) authextra = details_authextra obj = Hello(realm, roles, authmethods, authid, authrole, authextra) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ details = {u'roles': {}} for role in self.roles.values(): details[u'roles'][role.ROLE] = {} for feature in role.__dict__: if not feature.startswith('_') and feature != 'ROLE' and getattr(role, feature) is not None: if u'features' not in details[u'roles'][role.ROLE]: details[u'roles'][role.ROLE] = {u'features': {}} details[u'roles'][role.ROLE][u'features'][six.u(feature)] = getattr(role, feature) if self.authmethods: details[u'authmethods'] = self.authmethods if self.authid: details[u'authid'] = self.authid if self.authrole: details[u'authrole'] = self.authrole if self.authextra: details[u'authextra'] = self.authextra return [Hello.MESSAGE_TYPE, self.realm, details] def __str__(self): """ Return a string representation of this message. """ return u"Hello(realm={0}, roles={1}, authmethods={2}, authid={3}, authrole={4}, authextra={5})".format(self.realm, self.roles, self.authmethods, self.authid, self.authrole, self.authextra) class Welcome(Message): """ A WAMP ``WELCOME`` message. Format: ``[WELCOME, Session|id, Details|dict]`` """ MESSAGE_TYPE = 2 """ The WAMP message code for this type of message. """ def __init__(self, session, roles, realm=None, authid=None, authrole=None, authmethod=None, authprovider=None, authextra=None, custom=None): """ :param session: The WAMP session ID the other peer is assigned. :type session: int :param roles: The WAMP roles to announce. :type roles: dict of :class:`autobahn.wamp.role.RoleFeatures` :param realm: The effective realm the session is joined on. :type realm: unicode or None :param authid: The authentication ID assigned. :type authid: unicode or None :param authrole: The authentication role assigned. :type authrole: unicode or None :param authmethod: The authentication method in use. :type authmethod: unicode or None :param authprovider: The authentication provided in use. :type authprovider: unicode or None :param authextra: Application-specific "extra data" to be forwarded to the client. :type authextra: arbitrary or None :param custom: Implementation-specific "custom attributes" (`x_my_impl_attribute`) to be set. :type custom: dict or None """ assert(type(session) in six.integer_types) assert(type(roles) == dict) assert(len(roles) > 0) for role in roles: assert(role in [u'broker', u'dealer']) assert(isinstance(roles[role], autobahn.wamp.role.ROLE_NAME_TO_CLASS[role])) assert(realm is None or type(realm) == six.text_type) assert(authid is None or type(authid) == six.text_type) assert(authrole is None or type(authrole) == six.text_type) assert(authmethod is None or type(authmethod) == six.text_type) assert(authprovider is None or type(authprovider) == six.text_type) assert(authextra is None or type(authextra) == dict) assert(custom is None or type(custom) == dict) if custom: for k in custom: assert(_CUSTOM_ATTRIBUTE.match(k)) Message.__init__(self) self.session = session self.roles = roles self.realm = realm self.authid = authid self.authrole = authrole self.authmethod = authmethod self.authprovider = authprovider self.authextra = authextra self.custom = custom or {} @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Welcome.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for WELCOME".format(len(wmsg))) session = check_or_raise_id(wmsg[1], u"'session' in WELCOME") details = check_or_raise_extra(wmsg[2], u"'details' in WELCOME") # FIXME: tigher value checking (types, URIs etc) realm = details.get(u'realm', None) authid = details.get(u'authid', None) authrole = details.get(u'authrole', None) authmethod = details.get(u'authmethod', None) authprovider = details.get(u'authprovider', None) authextra = details.get(u'authextra', None) roles = {} if u'roles' not in details: raise ProtocolError(u"missing mandatory roles attribute in options in WELCOME") details_roles = check_or_raise_extra(details['roles'], u"'roles' in 'details' in WELCOME") if len(details_roles) == 0: raise ProtocolError(u"empty 'roles' in 'details' in WELCOME") for role in details_roles: if role not in [u'broker', u'dealer']: raise ProtocolError("invalid role '{0}' in 'roles' in 'details' in WELCOME".format(role)) role_cls = ROLE_NAME_TO_CLASS[role] details_role = check_or_raise_extra(details_roles[role], "role '{0}' in 'roles' in 'details' in WELCOME".format(role)) if u'features' in details_role: check_or_raise_extra(details_role[u'features'], "'features' in role '{0}' in 'roles' in 'details' in WELCOME".format(role)) role_features = role_cls(**details_roles[role][u'features']) else: role_features = role_cls() roles[role] = role_features custom = {} for k in details: if _CUSTOM_ATTRIBUTE.match(k): custom[k] = details[k] obj = Welcome(session, roles, realm, authid, authrole, authmethod, authprovider, authextra, custom) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ details = {} details.update(self.custom) if self.realm: details[u'realm'] = self.realm if self.authid: details[u'authid'] = self.authid if self.authrole: details[u'authrole'] = self.authrole if self.authrole: details[u'authmethod'] = self.authmethod if self.authprovider: details[u'authprovider'] = self.authprovider if self.authextra: details[u'authextra'] = self.authextra details[u'roles'] = {} for role in self.roles.values(): details[u'roles'][role.ROLE] = {} for feature in role.__dict__: if not feature.startswith('_') and feature != 'ROLE' and getattr(role, feature) is not None: if u'features' not in details[u'roles'][role.ROLE]: details[u'roles'][role.ROLE] = {u'features': {}} details[u'roles'][role.ROLE][u'features'][six.u(feature)] = getattr(role, feature) return [Welcome.MESSAGE_TYPE, self.session, details] def __str__(self): """ Returns string representation of this message. """ return u"Welcome(session={0}, roles={1}, realm={2}, authid={3}, authrole={4}, authmethod={5}, authprovider={6}, authextra={7})".format(self.session, self.roles, self.realm, self.authid, self.authrole, self.authmethod, self.authprovider, self.authextra) class Abort(Message): """ A WAMP ``ABORT`` message. Format: ``[ABORT, Details|dict, Reason|uri]`` """ MESSAGE_TYPE = 3 """ The WAMP message code for this type of message. """ def __init__(self, reason, message=None): """ :param reason: WAMP or application error URI for aborting reason. :type reason: unicode :param message: Optional human-readable closing message, e.g. for logging purposes. :type message: unicode or None """ assert(type(reason) == six.text_type) assert(message is None or type(message) == six.text_type) Message.__init__(self) self.reason = reason self.message = message @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize ## assert(len(wmsg) > 0 and wmsg[0] == Abort.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for ABORT".format(len(wmsg))) details = check_or_raise_extra(wmsg[1], u"'details' in ABORT") reason = check_or_raise_uri(wmsg[2], u"'reason' in ABORT") message = None if u'message' in details: details_message = details[u'message'] if type(details_message) != six.text_type: raise ProtocolError("invalid type {0} for 'message' detail in ABORT".format(type(details_message))) message = details_message obj = Abort(reason, message) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ details = {} if self.message: details[u'message'] = self.message return [Abort.MESSAGE_TYPE, details, self.reason] def __str__(self): """ Returns string representation of this message. """ return u"Abort(message={0}, reason={1})".format(self.message, self.reason) class Challenge(Message): """ A WAMP ``CHALLENGE`` message. Format: ``[CHALLENGE, Method|string, Extra|dict]`` """ MESSAGE_TYPE = 4 """ The WAMP message code for this type of message. """ def __init__(self, method, extra=None): """ :param method: The authentication method. :type method: unicode :param extra: Authentication method specific information. :type extra: dict or None """ assert(type(method) == six.text_type) assert(extra is None or type(extra) == dict) Message.__init__(self) self.method = method self.extra = extra or {} @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize ## assert(len(wmsg) > 0 and wmsg[0] == Challenge.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for CHALLENGE".format(len(wmsg))) method = wmsg[1] if type(method) != six.text_type: raise ProtocolError("invalid type {0} for 'method' in CHALLENGE".format(type(method))) extra = check_or_raise_extra(wmsg[2], u"'extra' in CHALLENGE") obj = Challenge(method, extra) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ return [Challenge.MESSAGE_TYPE, self.method, self.extra] def __str__(self): """ Returns string representation of this message. """ return u"Challenge(method={0}, extra={1})".format(self.method, self.extra) class Authenticate(Message): """ A WAMP ``AUTHENTICATE`` message. Format: ``[AUTHENTICATE, Signature|string, Extra|dict]`` """ MESSAGE_TYPE = 5 """ The WAMP message code for this type of message. """ def __init__(self, signature, extra=None): """ :param signature: The signature for the authentication challenge. :type signature: unicode :param extra: Authentication method specific information. :type extra: dict or None """ assert(type(signature) == six.text_type) assert(extra is None or type(extra) == dict) Message.__init__(self) self.signature = signature self.extra = extra or {} @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Authenticate.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for AUTHENTICATE".format(len(wmsg))) signature = wmsg[1] if type(signature) != six.text_type: raise ProtocolError("invalid type {0} for 'signature' in AUTHENTICATE".format(type(signature))) extra = check_or_raise_extra(wmsg[2], u"'extra' in AUTHENTICATE") obj = Authenticate(signature, extra) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ return [Authenticate.MESSAGE_TYPE, self.signature, self.extra] def __str__(self): """ Returns string representation of this message. """ return u"Authenticate(signature={0}, extra={1})".format(self.signature, self.extra) class Goodbye(Message): """ A WAMP ``GOODBYE`` message. Format: ``[GOODBYE, Details|dict, Reason|uri]`` """ MESSAGE_TYPE = 6 """ The WAMP message code for this type of message. """ DEFAULT_REASON = u"wamp.close.normal" """ Default WAMP closing reason. """ def __init__(self, reason=DEFAULT_REASON, message=None): """ :param reason: Optional WAMP or application error URI for closing reason. :type reason: unicode :param message: Optional human-readable closing message, e.g. for logging purposes. :type message: unicode or None """ assert(type(reason) == six.text_type) assert(message is None or type(message) == six.text_type) Message.__init__(self) self.reason = reason self.message = message @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize ## assert(len(wmsg) > 0 and wmsg[0] == Goodbye.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for GOODBYE".format(len(wmsg))) details = check_or_raise_extra(wmsg[1], u"'details' in GOODBYE") reason = check_or_raise_uri(wmsg[2], u"'reason' in GOODBYE") message = None if u'message' in details: details_message = details[u'message'] if type(details_message) != six.text_type: raise ProtocolError("invalid type {0} for 'message' detail in GOODBYE".format(type(details_message))) message = details_message obj = Goodbye(reason, message) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ details = {} if self.message: details[u'message'] = self.message return [Goodbye.MESSAGE_TYPE, details, self.reason] def __str__(self): """ Returns string representation of this message. """ return u"Goodbye(message={0}, reason={1})".format(self.message, self.reason) class Error(Message): """ A WAMP ``ERROR`` message. Formats: * ``[ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri]`` * ``[ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri, Arguments|list]`` * ``[ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri, Arguments|list, ArgumentsKw|dict]`` * ``[ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri, Payload|string/binary]`` """ MESSAGE_TYPE = 8 """ The WAMP message code for this type of message. """ def __init__(self, request_type, request, error, args=None, kwargs=None, payload=None, enc_algo=None, enc_key=None, enc_serializer=None): """ :param request_type: The WAMP message type code for the original request. :type request_type: int :param request: The WAMP request ID of the original request (`Call`, `Subscribe`, ...) this error occurred for. :type request: int :param error: The WAMP or application error URI for the error that occurred. :type error: unicode :param args: Positional values for application-defined exception. Must be serializable using any serializers in use. :type args: list or None :param kwargs: Keyword values for application-defined exception. Must be serializable using any serializers in use. :type kwargs: dict or None :param payload: Alternative, transparent payload. If given, `args` and `kwargs` must be left unset. :type payload: unicode or bytes :param enc_algo: If using payload encryption, the algorithm used (currently, only "cryptobox" is valid). :type enc_algo: unicode :param enc_key: If using payload encryption, the message encryption key. :type enc_key: unicode or binary :param enc_serializer: If using payload encryption, the encrypted payload object serializer. :type enc_serializer: unicode """ assert(type(request_type) in six.integer_types) assert(type(request) in six.integer_types) assert(type(error) == six.text_type) assert(args is None or type(args) in [list, tuple]) assert(kwargs is None or type(kwargs) == dict) assert(payload is None or type(payload) in [six.text_type, six.binary_type]) assert(payload is None or (payload is not None and args is None and kwargs is None)) Message.__init__(self) self.request_type = request_type self.request = request self.error = error self.args = args self.kwargs = kwargs self.payload = payload # end-to-end app payload encryption self.enc_algo = enc_algo self.enc_key = enc_key self.enc_serializer = enc_serializer @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Error.MESSAGE_TYPE) if len(wmsg) not in (5, 6, 7): raise ProtocolError("invalid message length {0} for ERROR".format(len(wmsg))) request_type = wmsg[1] if type(request_type) not in six.integer_types: raise ProtocolError("invalid type {0} for 'request_type' in ERROR".format(request_type)) if request_type not in [Subscribe.MESSAGE_TYPE, Unsubscribe.MESSAGE_TYPE, Publish.MESSAGE_TYPE, Register.MESSAGE_TYPE, Unregister.MESSAGE_TYPE, Call.MESSAGE_TYPE, Invocation.MESSAGE_TYPE]: raise ProtocolError("invalid value {0} for 'request_type' in ERROR".format(request_type)) request = check_or_raise_id(wmsg[2], u"'request' in ERROR") details = check_or_raise_extra(wmsg[3], u"'details' in ERROR") error = check_or_raise_uri(wmsg[4], u"'error' in ERROR") args = None kwargs = None payload = None enc_algo = None enc_key = None enc_serializer = None if len(wmsg) == 6 and type(wmsg[5]) in [six.text_type, six.binary_type]: payload = wmsg[5] enc_algo = details.get(u'enc_algo', None) if enc_algo and enc_algo not in [PAYLOAD_ENC_CRYPTO_BOX]: raise ProtocolError("invalid value {0} for 'enc_algo' detail in EVENT".format(enc_algo)) enc_key = details.get(u'enc_key', None) if enc_key and type(enc_key) not in [six.text_type, six.binary_type]: raise ProtocolError("invalid type {0} for 'enc_key' detail in EVENT".format(type(enc_key))) enc_serializer = details.get(u'enc_serializer', None) if enc_serializer and enc_serializer not in [u'json', u'msgpack', u'cbor', u'ubjson']: raise ProtocolError("invalid value {0} for 'enc_serializer' detail in EVENT".format(enc_serializer)) else: if len(wmsg) > 5: args = wmsg[5] if type(args) != list: raise ProtocolError("invalid type {0} for 'args' in ERROR".format(type(args))) if len(wmsg) > 6: kwargs = wmsg[6] if type(kwargs) != dict: raise ProtocolError("invalid type {0} for 'kwargs' in ERROR".format(type(kwargs))) obj = Error(request_type, request, error, args=args, kwargs=kwargs, payload=payload, enc_algo=enc_algo, enc_key=enc_key, enc_serializer=enc_serializer) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ details = {} if self.payload: if self.enc_algo is not None: details[u'enc_algo'] = self.enc_algo if self.enc_key is not None: details[u'enc_key'] = self.enc_key if self.enc_serializer is not None: details[u'enc_serializer'] = self.enc_serializer return [self.MESSAGE_TYPE, self.request_type, self.request, details, self.error, self.payload] else: if self.kwargs: return [self.MESSAGE_TYPE, self.request_type, self.request, details, self.error, self.args, self.kwargs] elif self.args: return [self.MESSAGE_TYPE, self.request_type, self.request, details, self.error, self.args] else: return [self.MESSAGE_TYPE, self.request_type, self.request, details, self.error] def __str__(self): """ Returns string representation of this message. """ return u"Error(request_type={0}, request={1}, error={2}, args={3}, kwargs={4}, enc_algo={5}, enc_key={6}, enc_serializer={7}, payload={8})".format(self.request_type, self.request, self.error, self.args, self.kwargs, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload)) class Publish(Message): """ A WAMP ``PUBLISH`` message. Formats: * ``[PUBLISH, Request|id, Options|dict, Topic|uri]`` * ``[PUBLISH, Request|id, Options|dict, Topic|uri, Arguments|list]`` * ``[PUBLISH, Request|id, Options|dict, Topic|uri, Arguments|list, ArgumentsKw|dict]`` * ``[PUBLISH, Request|id, Options|dict, Topic|uri, Payload|string/binary]`` """ MESSAGE_TYPE = 16 """ The WAMP message code for this type of message. """ def __init__(self, request, topic, args=None, kwargs=None, payload=None, acknowledge=None, exclude_me=None, exclude=None, exclude_authid=None, exclude_authrole=None, eligible=None, eligible_authid=None, eligible_authrole=None, enc_algo=None, enc_key=None, enc_serializer=None): """ :param request: The WAMP request ID of this request. :type request: int :param topic: The WAMP or application URI of the PubSub topic the event should be published to. :type topic: unicode :param args: Positional values for application-defined event payload. Must be serializable using any serializers in use. :type args: list or tuple or None :param kwargs: Keyword values for application-defined event payload. Must be serializable using any serializers in use. :type kwargs: dict or None :param payload: Alternative, transparent payload. If given, `args` and `kwargs` must be left unset. :type payload: unicode or bytes :param acknowledge: If True, acknowledge the publication with a success or error response. :type acknowledge: bool or None :param exclude_me: If ``True``, exclude the publisher from receiving the event, even if he is subscribed (and eligible). :type exclude_me: bool or None :param exclude: List of WAMP session IDs to exclude from receiving this event. :type exclude: list of int or None :param exclude_authid: List of WAMP authids to exclude from receiving this event. :type exclude_authid: list of unicode or None :param exclude_authrole: List of WAMP authroles to exclude from receiving this event. :type exclude_authrole: list of unicode or None :param eligible: List of WAMP session IDs eligible to receive this event. :type eligible: list of int or None :param eligible_authid: List of WAMP authids eligible to receive this event. :type eligible_authid: list of unicode or None :param eligible_authrole: List of WAMP authroles eligible to receive this event. :type eligible_authrole: list of unicode or None :param enc_algo: If using payload encryption, the algorithm used (currently, only "cryptobox" is valid). :type enc_algo: unicode :param enc_key: If using payload encryption, the message encryption key. :type enc_key: unicode or binary :param enc_serializer: If using payload encryption, the encrypted payload object serializer. :type enc_serializer: unicode """ assert(type(request) in six.integer_types) assert(type(topic) == six.text_type) 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(payload is None or type(payload) in [six.text_type, six.binary_type]) assert(payload is None or (payload is not None and args is None and kwargs is None)) assert(acknowledge is None or type(acknowledge) == bool) # publisher exlusion and black-/whitelisting assert(exclude_me is None or type(exclude_me) == bool) assert(exclude is None or type(exclude) == list) if exclude: for sessionid in exclude: assert(type(sessionid) in six.integer_types) assert(exclude_authid is None or type(exclude_authid) == list) if exclude_authid: for authid in exclude_authid: assert(type(authid) == six.text_type) assert(exclude_authrole is None or type(exclude_authrole) == list) if exclude_authrole: for authrole in exclude_authrole: assert(type(authrole) == six.text_type) assert(eligible is None or type(eligible) == list) if eligible: for sessionid in eligible: assert(type(sessionid) in six.integer_types) assert(eligible_authid is None or type(eligible_authid) == list) if eligible_authid: for authid in eligible_authid: assert(type(authid) == six.text_type) assert(eligible_authrole is None or type(eligible_authrole) == list) if eligible_authrole: for authrole in eligible_authrole: assert(type(authrole) == six.text_type) # end-to-end app payload encryption assert(enc_algo is None or enc_algo in [PAYLOAD_ENC_CRYPTO_BOX]) assert(enc_key is None or type(enc_key) in [six.text_type, six.binary_type]) assert(enc_serializer is None or enc_serializer in [u'json', u'msgpack', u'cbor', u'ubjson']) assert((enc_algo is None and enc_key is None and enc_serializer is None) or (enc_algo is not None and payload is not None)) Message.__init__(self) self.request = request self.topic = topic self.args = args self.kwargs = kwargs self.payload = payload self.acknowledge = acknowledge # publisher exlusion and black-/whitelisting self.exclude_me = exclude_me self.exclude = exclude self.exclude_authid = exclude_authid self.exclude_authrole = exclude_authrole self.eligible = eligible self.eligible_authid = eligible_authid self.eligible_authrole = eligible_authrole # end-to-end app payload encryption self.enc_algo = enc_algo self.enc_key = enc_key self.enc_serializer = enc_serializer @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Publish.MESSAGE_TYPE) if len(wmsg) not in (4, 5, 6): raise ProtocolError("invalid message length {0} for PUBLISH".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in PUBLISH") options = check_or_raise_extra(wmsg[2], u"'options' in PUBLISH") topic = check_or_raise_uri(wmsg[3], u"'topic' in PUBLISH") args = None kwargs = None payload = None if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]: payload = wmsg[4] enc_algo = options.get(u'enc_algo', None) if enc_algo and enc_algo != PAYLOAD_ENC_CRYPTO_BOX: raise ProtocolError("invalid value {0} for 'enc_algo' option in PUBLISH".format(enc_algo)) enc_key = options.get(u'enc_key', None) if enc_key and type(enc_key) not in [six.text_type, six.binary_type]: raise ProtocolError("invalid type {0} for 'enc_key' option in PUBLISH".format(type(enc_key))) enc_serializer = options.get(u'enc_serializer', None) if enc_serializer and enc_serializer not in [u'json', u'msgpack', u'cbor', u'ubjson']: raise ProtocolError("invalid value {0} for 'enc_serializer' option in PUBLISH".format(enc_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))) enc_algo = None enc_key = None enc_serializer = None acknowledge = None exclude_me = None exclude = None exclude_authid = None exclude_authrole = None eligible = None eligible_authid = None eligible_authrole = None if u'acknowledge' in options: option_acknowledge = options[u'acknowledge'] if type(option_acknowledge) != bool: raise ProtocolError("invalid type {0} for 'acknowledge' option in PUBLISH".format(type(option_acknowledge))) acknowledge = option_acknowledge if u'exclude_me' in options: option_exclude_me = options[u'exclude_me'] if type(option_exclude_me) != bool: raise ProtocolError("invalid type {0} for 'exclude_me' option in PUBLISH".format(type(option_exclude_me))) exclude_me = option_exclude_me if u'exclude' in options: option_exclude = options[u'exclude'] if type(option_exclude) != list: raise ProtocolError("invalid type {0} for 'exclude' option in PUBLISH".format(type(option_exclude))) for _sessionid in option_exclude: if type(_sessionid) not in six.integer_types: raise ProtocolError("invalid type {0} for value in 'exclude' option in PUBLISH".format(type(_sessionid))) exclude = option_exclude if u'exclude_authid' in options: option_exclude_authid = options[u'exclude_authid'] if type(option_exclude_authid) != list: raise ProtocolError("invalid type {0} for 'exclude_authid' option in PUBLISH".format(type(option_exclude_authid))) for _authid in option_exclude_authid: if type(_authid) != six.text_type: raise ProtocolError("invalid type {0} for value in 'exclude_authid' option in PUBLISH".format(type(_authid))) exclude_authid = option_exclude_authid if u'exclude_authrole' in options: option_exclude_authrole = options[u'exclude_authrole'] if type(option_exclude_authrole) != list: raise ProtocolError("invalid type {0} for 'exclude_authrole' option in PUBLISH".format(type(option_exclude_authrole))) for _authrole in option_exclude_authrole: if type(_authrole) != six.text_type: raise ProtocolError("invalid type {0} for value in 'exclude_authrole' option in PUBLISH".format(type(_authrole))) exclude_authrole = option_exclude_authrole if u'eligible' in options: option_eligible = options[u'eligible'] if type(option_eligible) != list: raise ProtocolError("invalid type {0} for 'eligible' option in PUBLISH".format(type(option_eligible))) for sessionId in option_eligible: if type(sessionId) not in six.integer_types: raise ProtocolError("invalid type {0} for value in 'eligible' option in PUBLISH".format(type(sessionId))) eligible = option_eligible if u'eligible_authid' in options: option_eligible_authid = options[u'eligible_authid'] if type(option_eligible_authid) != list: raise ProtocolError("invalid type {0} for 'eligible_authid' option in PUBLISH".format(type(option_eligible_authid))) for _authid in option_eligible_authid: if type(_authid) != six.text_type: raise ProtocolError("invalid type {0} for value in 'eligible_authid' option in PUBLISH".format(type(_authid))) eligible_authid = option_eligible_authid if u'eligible_authrole' in options: option_eligible_authrole = options[u'eligible_authrole'] if type(option_eligible_authrole) != list: raise ProtocolError("invalid type {0} for 'eligible_authrole' option in PUBLISH".format(type(option_eligible_authrole))) for _authrole in option_eligible_authrole: if type(_authrole) != six.text_type: raise ProtocolError("invalid type {0} for value in 'eligible_authrole' option in PUBLISH".format(type(_authrole))) eligible_authrole = option_eligible_authrole obj = Publish(request, topic, args=args, kwargs=kwargs, payload=payload, acknowledge=acknowledge, exclude_me=exclude_me, exclude=exclude, exclude_authid=exclude_authid, exclude_authrole=exclude_authrole, eligible=eligible, eligible_authid=eligible_authid, eligible_authrole=eligible_authrole, enc_algo=enc_algo, enc_key=enc_key, enc_serializer=enc_serializer) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ options = {} if self.acknowledge is not None: options[u'acknowledge'] = self.acknowledge if self.exclude_me is not None: options[u'exclude_me'] = self.exclude_me if self.exclude is not None: options[u'exclude'] = self.exclude if self.exclude_authid is not None: options[u'exclude_authid'] = self.exclude_authid if self.eligible_authrole is not None: options[u'eligible_authrole'] = self.eligible_authrole if self.eligible is not None: options[u'eligible'] = self.eligible if self.eligible_authid is not None: options[u'eligible_authid'] = self.eligible_authid if self.eligible_authrole is not None: options[u'eligible_authrole'] = self.eligible_authrole if self.payload: if self.enc_algo is not None: options[u'enc_algo'] = self.enc_algo if self.enc_key is not None: options[u'enc_key'] = self.enc_key if self.enc_serializer is not None: options[u'enc_serializer'] = self.enc_serializer return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.payload] else: 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 u"Publish(request={0}, topic={1}, args={2}, kwargs={3}, acknowledge={4}, exclude_me={5}, exclude={6}, exclude_authid={7}, exclude_authrole={8}, eligible={9}, eligible_authid={10}, eligible_authrole={11}, enc_algo={12}, enc_key={13}, enc_serializer={14}, payload={15})".format(self.request, self.topic, self.args, self.kwargs, self.acknowledge, self.exclude_me, self.exclude, self.exclude_authid, self.exclude_authrole, self.eligible, self.eligible_authid, self.eligible_authrole, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload)) class Published(Message): """ A WAMP ``PUBLISHED`` message. Format: ``[PUBLISHED, PUBLISH.Request|id, Publication|id]`` """ MESSAGE_TYPE = 17 """ The WAMP message code for this type of message. """ def __init__(self, request, publication): """ :param request: The request ID of the original `PUBLISH` request. :type request: int :param publication: The publication ID for the published event. :type publication: int """ assert(type(request) in six.integer_types) assert(type(publication) in six.integer_types) Message.__init__(self) self.request = request self.publication = publication @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Published.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for PUBLISHED".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in PUBLISHED") publication = check_or_raise_id(wmsg[2], u"'publication' in PUBLISHED") obj = Published(request, publication) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ return [Published.MESSAGE_TYPE, self.request, self.publication] def __str__(self): """ Returns string representation of this message. """ return u"Published(request={0}, publication={1})".format(self.request, self.publication) class Subscribe(Message): """ A WAMP ``SUBSCRIBE`` message. Format: ``[SUBSCRIBE, Request|id, Options|dict, Topic|uri]`` """ MESSAGE_TYPE = 32 """ The WAMP message code for this type of message. """ MATCH_EXACT = u'exact' MATCH_PREFIX = u'prefix' MATCH_WILDCARD = u'wildcard' def __init__(self, request, topic, match=None): """ :param request: The WAMP request ID of this request. :type request: int :param topic: The WAMP or application URI of the PubSub topic to subscribe to. :type topic: unicode :param match: The topic matching method to be used for the subscription. :type match: unicode """ assert(type(request) in six.integer_types) assert(type(topic) == six.text_type) assert(match is None or type(match) == six.text_type) assert(match is None or match in [Subscribe.MATCH_EXACT, Subscribe.MATCH_PREFIX, Subscribe.MATCH_WILDCARD]) Message.__init__(self) self.request = request self.topic = topic self.match = match or Subscribe.MATCH_EXACT @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Subscribe.MESSAGE_TYPE) if len(wmsg) != 4: raise ProtocolError("invalid message length {0} for SUBSCRIBE".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in SUBSCRIBE") options = check_or_raise_extra(wmsg[2], u"'options' in SUBSCRIBE") topic = check_or_raise_uri(wmsg[3], u"'topic' in SUBSCRIBE", allow_empty_components=True) match = Subscribe.MATCH_EXACT if u'match' in options: option_match = options[u'match'] if type(option_match) != six.text_type: raise ProtocolError("invalid type {0} for 'match' option in SUBSCRIBE".format(type(option_match))) if option_match not in [Subscribe.MATCH_EXACT, Subscribe.MATCH_PREFIX, Subscribe.MATCH_WILDCARD]: raise ProtocolError("invalid value {0} for 'match' option in SUBSCRIBE".format(option_match)) match = option_match obj = Subscribe(request, topic, match=match) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ options = {} if self.match and self.match != Subscribe.MATCH_EXACT: options[u'match'] = self.match return [Subscribe.MESSAGE_TYPE, self.request, options, self.topic] def __str__(self): """ Returns string representation of this message. """ return u"Subscribe(request={0}, topic={1}, match={2})".format(self.request, self.topic, self.match) class Subscribed(Message): """ A WAMP ``SUBSCRIBED`` message. Format: ``[SUBSCRIBED, SUBSCRIBE.Request|id, Subscription|id]`` """ MESSAGE_TYPE = 33 """ The WAMP message code for this type of message. """ def __init__(self, request, subscription): """ :param request: The request ID of the original ``SUBSCRIBE`` request. :type request: int :param subscription: The subscription ID for the subscribed topic (or topic pattern). :type subscription: int """ assert(type(request) in six.integer_types) assert(type(subscription) in six.integer_types) Message.__init__(self) self.request = request self.subscription = subscription @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Subscribed.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for SUBSCRIBED".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in SUBSCRIBED") subscription = check_or_raise_id(wmsg[2], u"'subscription' in SUBSCRIBED") obj = Subscribed(request, subscription) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ return [Subscribed.MESSAGE_TYPE, self.request, self.subscription] def __str__(self): """ Returns string representation of this message. """ return u"Subscribed(request={0}, subscription={1})".format(self.request, self.subscription) class Unsubscribe(Message): """ A WAMP ``UNSUBSCRIBE`` message. Format: ``[UNSUBSCRIBE, Request|id, SUBSCRIBED.Subscription|id]`` """ MESSAGE_TYPE = 34 """ The WAMP message code for this type of message. """ def __init__(self, request, subscription): """ :param request: The WAMP request ID of this request. :type request: int :param subscription: The subscription ID for the subscription to unsubscribe from. :type subscription: int """ assert(type(request) in six.integer_types) assert(type(subscription) in six.integer_types) Message.__init__(self) self.request = request self.subscription = subscription @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Unsubscribe.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for WAMP UNSUBSCRIBE".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in UNSUBSCRIBE") subscription = check_or_raise_id(wmsg[2], u"'subscription' in UNSUBSCRIBE") obj = Unsubscribe(request, subscription) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ return [Unsubscribe.MESSAGE_TYPE, self.request, self.subscription] def __str__(self): """ Returns string representation of this message. """ return u"Unsubscribe(request={0}, subscription={1})".format(self.request, self.subscription) class Unsubscribed(Message): """ A WAMP ``UNSUBSCRIBED`` message. Formats: * ``[UNSUBSCRIBED, UNSUBSCRIBE.Request|id]`` * ``[UNSUBSCRIBED, UNSUBSCRIBE.Request|id, Details|dict]`` """ MESSAGE_TYPE = 35 """ The WAMP message code for this type of message. """ def __init__(self, request, subscription=None, reason=None): """ :param request: The request ID of the original ``UNSUBSCRIBE`` request or ``0`` is router triggered unsubscribe ("router revocation signaling"). :type request: int :param subscription: If unsubscribe was actively triggered by router, the ID of the subscription revoked. :type subscription: int or None :param reason: The reason (an URI) for revocation. :type reason: unicode or None. """ assert(type(request) in six.integer_types) assert(subscription is None or type(subscription) in six.integer_types) assert(reason is None or type(reason) == six.text_type) assert((request != 0 and subscription is None) or (request == 0 and subscription != 0)) Message.__init__(self) self.request = request self.subscription = subscription self.reason = reason @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Unsubscribed.MESSAGE_TYPE) if len(wmsg) not in [2, 3]: raise ProtocolError("invalid message length {0} for UNSUBSCRIBED".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in UNSUBSCRIBED") subscription = None reason = None if len(wmsg) > 2: details = check_or_raise_extra(wmsg[2], u"'details' in UNSUBSCRIBED") if u"subscription" in details: details_subscription = details[u"subscription"] if type(details_subscription) not in six.integer_types: raise ProtocolError("invalid type {0} for 'subscription' detail in UNSUBSCRIBED".format(type(details_subscription))) subscription = details_subscription if u"reason" in details: reason = check_or_raise_uri(details[u"reason"], u"'reason' in UNSUBSCRIBED") obj = Unsubscribed(request, subscription, reason) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ if self.reason is not None or self.subscription is not None: details = {} if self.reason is not None: details[u"reason"] = self.reason if self.subscription is not None: details[u"subscription"] = self.subscription return [Unsubscribed.MESSAGE_TYPE, self.request, details] else: return [Unsubscribed.MESSAGE_TYPE, self.request] def __str__(self): """ Returns string representation of this message. """ return u"Unsubscribed(request={0}, reason={1}, subscription={2})".format(self.request, self.reason, self.subscription) class Event(Message): """ A WAMP ``EVENT`` message. Formats: * ``[EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict]`` * ``[EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict, PUBLISH.Arguments|list]`` * ``[EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict, PUBLISH.Arguments|list, PUBLISH.ArgumentsKw|dict]`` * ``[EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict, PUBLISH.Payload|string/binary]`` """ MESSAGE_TYPE = 36 """ The WAMP message code for this type of message. """ def __init__(self, subscription, publication, args=None, kwargs=None, payload=None, publisher=None, publisher_authid=None, publisher_authrole=None, topic=None, enc_algo=None, enc_key=None, enc_serializer=None): """ :param subscription: The subscription ID this event is dispatched under. :type subscription: int :param publication: The publication ID of the dispatched event. :type publication: int :param args: Positional values for application-defined exception. Must be serializable using any serializers in use. :type args: list or tuple or None :param kwargs: Keyword values for application-defined exception. Must be serializable using any serializers in use. :param payload: Alternative, transparent payload. If given, `args` and `kwargs` must be left unset. :type payload: unicode or bytes :type kwargs: dict or None :param publisher: The WAMP session ID of the pubisher. Only filled if pubisher is disclosed. :type publisher: None or int :param publisher_authid: The WAMP authid of the pubisher. Only filled if pubisher is disclosed. :type publisher_authid: None or unicode :param publisher_authrole: The WAMP authrole of the pubisher. Only filled if pubisher is disclosed. :type publisher_authrole: None or unicode :param topic: For pattern-based subscriptions, the event MUST contain the actual topic published to. :type topic: unicode or None :param enc_algo: If using payload encryption, the algorithm used (currently, only "cryptobox" is valid). :type enc_algo: unicode :param enc_key: If using payload encryption, the message encryption key. :type enc_key: unicode or binary :param enc_serializer: If using payload encryption, the encrypted payload object serializer. :type enc_serializer: unicode """ assert(type(subscription) in six.integer_types) assert(type(publication) in six.integer_types) assert(args is None or type(args) in [list, tuple]) assert(kwargs is None or type(kwargs) == dict) assert(payload is None or type(payload) in [six.text_type, six.binary_type]) assert(payload is None or (payload is not None and args is None and kwargs is None)) assert(publisher is None or type(publisher) in six.integer_types) assert(publisher_authid is None or type(publisher_authid) == six.text_type) assert(publisher_authrole is None or type(publisher_authrole) == six.text_type) assert(topic is None or type(topic) == six.text_type) # end-to-end app payload encryption assert(enc_algo is None or enc_algo in [PAYLOAD_ENC_CRYPTO_BOX]) assert(enc_key is None or type(enc_key) in [six.text_type, six.binary_type]) assert(enc_serializer is None or enc_serializer in [u'json', u'msgpack', u'cbor', u'ubjson']) assert((enc_algo is None and enc_key is None and enc_serializer is None) or (enc_algo is not None and payload is not None)) Message.__init__(self) self.subscription = subscription self.publication = publication self.args = args self.kwargs = kwargs self.payload = payload self.publisher = publisher self.publisher_authid = publisher_authid self.publisher_authrole = publisher_authrole self.topic = topic # end-to-end app payload encryption self.enc_algo = enc_algo self.enc_key = enc_key self.enc_serializer = enc_serializer @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Event.MESSAGE_TYPE) if len(wmsg) not in (4, 5, 6): raise ProtocolError("invalid message length {0} for EVENT".format(len(wmsg))) subscription = check_or_raise_id(wmsg[1], u"'subscription' in EVENT") publication = check_or_raise_id(wmsg[2], u"'publication' in EVENT") details = check_or_raise_extra(wmsg[3], u"'details' in EVENT") args = None kwargs = None payload = None enc_algo = None enc_key = None enc_serializer = None if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]: payload = wmsg[4] enc_algo = details.get(u'enc_algo', None) if enc_algo and enc_algo not in [PAYLOAD_ENC_CRYPTO_BOX]: raise ProtocolError("invalid value {0} for 'enc_algo' detail in EVENT".format(enc_algo)) enc_key = details.get(u'enc_key', None) if enc_key and type(enc_key) not in [six.text_type, six.binary_type]: raise ProtocolError("invalid type {0} for 'enc_key' detail in EVENT".format(type(enc_key))) enc_serializer = details.get(u'enc_serializer', None) if enc_serializer and enc_serializer not in [u'json', u'msgpack', u'cbor', u'ubjson']: raise ProtocolError("invalid value {0} for 'enc_serializer' detail in EVENT".format(enc_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 publisher_authid = None publisher_authrole = None topic = None if u'publisher' in details: detail_publisher = details[u'publisher'] if type(detail_publisher) not in six.integer_types: raise ProtocolError("invalid type {0} for 'publisher' detail in EVENT".format(type(detail_publisher))) publisher = detail_publisher if u'publisher_authid' in details: detail_publisher_authid = details[u'publisher_authid'] if type(detail_publisher_authid) != six.text_type: raise ProtocolError("invalid type {0} for 'publisher_authid' detail in INVOCATION".format(type(detail_publisher_authid))) publisher_authid = detail_publisher_authid if u'publisher_authrole' in details: detail_publisher_authrole = details[u'publisher_authrole'] if type(detail_publisher_authrole) != six.text_type: raise ProtocolError("invalid type {0} for 'publisher_authrole' detail in INVOCATION".format(type(detail_publisher_authrole))) publisher_authrole = detail_publisher_authrole if u'topic' in details: detail_topic = details[u'topic'] if type(detail_topic) != six.text_type: raise ProtocolError("invalid type {0} for 'topic' detail in EVENT".format(type(detail_topic))) topic = detail_topic obj = Event(subscription, publication, args=args, kwargs=kwargs, payload=payload, publisher=publisher, publisher_authid=publisher_authid, publisher_authrole=publisher_authrole, topic=topic, enc_algo=enc_algo, enc_key=enc_key, enc_serializer=enc_serializer) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ details = {} if self.publisher is not None: details[u'publisher'] = self.publisher if self.publisher_authid is not None: details[u'publisher_authid'] = self.publisher_authid if self.publisher_authrole is not None: details[u'publisher_authrole'] = self.publisher_authrole if self.topic is not None: details[u'topic'] = self.topic if self.payload: if self.enc_algo is not None: details[u'enc_algo'] = self.enc_algo if self.enc_key is not None: details[u'enc_key'] = self.enc_key if self.enc_serializer is not None: details[u'enc_serializer'] = self.enc_serializer return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.payload] else: 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 u"Event(subscription={0}, publication={1}, args={2}, kwargs={3}, publisher={4}, publisher_authid={5}, publisher_authrole={6}, topic={7}, enc_algo={8}, enc_key={9}, enc_serializer={9}, payload={10})".format(self.subscription, self.publication, self.args, self.kwargs, self.publisher, self.publisher_authid, self.publisher_authrole, self.topic, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload)) class Call(Message): """ A WAMP ``CALL`` message. Formats: * ``[CALL, Request|id, Options|dict, Procedure|uri]`` * ``[CALL, Request|id, Options|dict, Procedure|uri, Arguments|list]`` * ``[CALL, Request|id, Options|dict, Procedure|uri, Arguments|list, ArgumentsKw|dict]`` * ``[CALL, Request|id, Options|dict, Procedure|uri, Payload|string/binary]`` """ MESSAGE_TYPE = 48 """ The WAMP message code for this type of message. """ def __init__(self, request, procedure, args=None, kwargs=None, payload=None, timeout=None, receive_progress=None, enc_algo=None, enc_key=None, enc_serializer=None): """ :param request: The WAMP request ID of this request. :type request: int :param procedure: The WAMP or application URI of the procedure which should be called. :type procedure: unicode :param args: Positional values for application-defined call arguments. Must be serializable using any serializers in use. :type args: list or tuple or None :param kwargs: Keyword values for application-defined call arguments. Must be serializable using any serializers in use. :type kwargs: dict or None :param payload: Alternative, transparent payload. If given, `args` and `kwargs` must be left unset. :type payload: unicode or bytes :param timeout: If present, let the callee automatically cancel the call after this ms. :type timeout: int or None :param receive_progress: If ``True``, indicates that the caller wants to receive progressive call results. :type receive_progress: bool or None :param enc_algo: If using payload encryption, the algorithm used (currently, only "cryptobox" is valid). :type enc_algo: unicode :param enc_key: If using payload encryption, the message encryption key. :type enc_key: unicode or binary :param enc_serializer: If using payload encryption, the encrypted payload object serializer. :type enc_serializer: unicode """ assert(type(request) in six.integer_types) assert(type(procedure) == six.text_type) assert(args is None or type(args) in [list, tuple]) assert(kwargs is None or type(kwargs) == dict) assert(payload is None or type(payload) in [six.text_type, six.binary_type]) assert(payload is None or (payload is not None and args is None and kwargs is None)) assert(timeout is None or type(timeout) in six.integer_types) assert(receive_progress is None or type(receive_progress) == bool) # end-to-end app payload encryption assert(enc_algo is None or enc_algo in [PAYLOAD_ENC_CRYPTO_BOX]) assert(enc_key is None or type(enc_key) in [six.text_type, six.binary_type]) assert(enc_serializer is None or enc_serializer in [u'json', u'msgpack', u'cbor', u'ubjson']) assert((enc_algo is None and enc_key is None and enc_serializer is None) or (enc_algo is not None and payload is not None)) Message.__init__(self) self.request = request self.procedure = procedure self.args = args self.kwargs = kwargs self.payload = payload self.timeout = timeout self.receive_progress = receive_progress # end-to-end app payload encryption self.enc_algo = enc_algo self.enc_key = enc_key self.enc_serializer = enc_serializer @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Call.MESSAGE_TYPE) if len(wmsg) not in (4, 5, 6): raise ProtocolError("invalid message length {0} for CALL".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in CALL") options = check_or_raise_extra(wmsg[2], u"'options' in CALL") procedure = check_or_raise_uri(wmsg[3], u"'procedure' in CALL") args = None kwargs = None payload = None enc_algo = None enc_key = None enc_serializer = None if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]: payload = wmsg[4] enc_algo = options.get(u'enc_algo', None) if enc_algo and enc_algo not in [PAYLOAD_ENC_CRYPTO_BOX]: raise ProtocolError("invalid value {0} for 'enc_algo' detail in EVENT".format(enc_algo)) enc_key = options.get(u'enc_key', None) if enc_key and type(enc_key) not in [six.text_type, six.binary_type]: raise ProtocolError("invalid type {0} for 'enc_key' detail in EVENT".format(type(enc_key))) enc_serializer = options.get(u'enc_serializer', None) if enc_serializer and enc_serializer not in [u'json', u'msgpack', u'cbor', u'ubjson']: raise ProtocolError("invalid value {0} for 'enc_serializer' detail in EVENT".format(enc_serializer)) else: if len(wmsg) > 4: args = wmsg[4] if type(args) != list: raise ProtocolError("invalid type {0} for 'args' in CALL".format(type(args))) if len(wmsg) > 5: kwargs = wmsg[5] if type(kwargs) != dict: raise ProtocolError("invalid type {0} for 'kwargs' in CALL".format(type(kwargs))) timeout = None receive_progress = None if u'timeout' in options: option_timeout = options[u'timeout'] if type(option_timeout) not in six.integer_types: raise ProtocolError("invalid type {0} for 'timeout' option in CALL".format(type(option_timeout))) if option_timeout < 0: raise ProtocolError("invalid value {0} for 'timeout' option in CALL".format(option_timeout)) timeout = option_timeout if u'receive_progress' in options: option_receive_progress = options[u'receive_progress'] if type(option_receive_progress) != bool: raise ProtocolError("invalid type {0} for 'receive_progress' option in CALL".format(type(option_receive_progress))) receive_progress = option_receive_progress obj = Call(request, procedure, args=args, kwargs=kwargs, payload=payload, timeout=timeout, receive_progress=receive_progress, enc_algo=enc_algo, enc_key=enc_key, enc_serializer=enc_serializer) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ options = {} if self.timeout is not None: options[u'timeout'] = self.timeout if self.receive_progress is not None: options[u'receive_progress'] = self.receive_progress if self.payload: if self.enc_algo is not None: options[u'enc_algo'] = self.enc_algo if self.enc_key is not None: options[u'enc_key'] = self.enc_key if self.enc_serializer is not None: options[u'enc_serializer'] = self.enc_serializer return [Call.MESSAGE_TYPE, self.request, options, self.procedure, self.payload] else: if self.kwargs: return [Call.MESSAGE_TYPE, self.request, options, self.procedure, self.args, self.kwargs] elif self.args: return [Call.MESSAGE_TYPE, self.request, options, self.procedure, self.args] else: return [Call.MESSAGE_TYPE, self.request, options, self.procedure] def __str__(self): """ Returns string representation of this message. """ return u"Call(request={0}, procedure={1}, args={2}, kwargs={3}, timeout={4}, receive_progress={5}, enc_algo={6}, enc_key={7}, enc_serializer={8}, payload={9})".format(self.request, self.procedure, self.args, self.kwargs, self.timeout, self.receive_progress, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload)) class Cancel(Message): """ A WAMP ``CANCEL`` message. Format: ``[CANCEL, CALL.Request|id, Options|dict]`` """ MESSAGE_TYPE = 49 """ The WAMP message code for this type of message. """ SKIP = u'skip' ABORT = u'abort' KILL = u'kill' def __init__(self, request, mode=None): """ :param request: The WAMP request ID of the original `CALL` to cancel. :type request: int :param mode: Specifies how to cancel the call (``"skip"``, ``"abort"`` or ``"kill"``). :type mode: unicode or None """ assert(type(request) in six.integer_types) assert(mode is None or type(mode) == six.text_type) assert(mode in [None, self.SKIP, self.ABORT, self.KILL]) Message.__init__(self) self.request = request self.mode = mode @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Cancel.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for CANCEL".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in CANCEL") options = check_or_raise_extra(wmsg[2], u"'options' in CANCEL") # options # mode = None if u'mode' in options: option_mode = options[u'mode'] if type(option_mode) != six.text_type: raise ProtocolError("invalid type {0} for 'mode' option in CANCEL".format(type(option_mode))) if option_mode not in [Cancel.SKIP, Cancel.ABORT, Cancel.KILL]: raise ProtocolError("invalid value '{0}' for 'mode' option in CANCEL".format(option_mode)) mode = option_mode obj = Cancel(request, mode=mode) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ options = {} if self.mode is not None: options[u'mode'] = self.mode return [Cancel.MESSAGE_TYPE, self.request, options] def __str__(self): """ Returns string representation of this message. """ return u"Cancel(request={0}, mode={1})".format(self.request, self.mode) class Result(Message): """ A WAMP ``RESULT`` message. Formats: * ``[RESULT, CALL.Request|id, Details|dict]`` * ``[RESULT, CALL.Request|id, Details|dict, YIELD.Arguments|list]`` * ``[RESULT, CALL.Request|id, Details|dict, YIELD.Arguments|list, YIELD.ArgumentsKw|dict]`` * ``[RESULT, CALL.Request|id, Details|dict, Payload|string/binary]`` """ MESSAGE_TYPE = 50 """ The WAMP message code for this type of message. """ def __init__(self, request, args=None, kwargs=None, payload=None, progress=None, enc_algo=None, enc_key=None, enc_serializer=None): """ :param request: The request ID of the original `CALL` request. :type request: int :param args: Positional values for application-defined event payload. Must be serializable using any serializers in use. :type args: list or tuple or None :param kwargs: Keyword values for application-defined event payload. Must be serializable using any serializers in use. :type kwargs: dict or None :param payload: Alternative, transparent payload. If given, `args` and `kwargs` must be left unset. :type payload: unicode or bytes :param progress: If ``True``, this result is a progressive call result, and subsequent results (or a final error) will follow. :type progress: bool or None :param enc_algo: If using payload encryption, the algorithm used (currently, only "cryptobox" is valid). :type enc_algo: unicode :param enc_key: If using payload encryption, the message encryption key. :type enc_key: unicode or binary :param enc_serializer: If using payload encryption, the encrypted payload object serializer. :type enc_serializer: unicode """ assert(type(request) in six.integer_types) assert(args is None or type(args) in [list, tuple]) assert(kwargs is None or type(kwargs) == dict) assert(payload is None or type(payload) in [six.text_type, six.binary_type]) assert(payload is None or (payload is not None and args is None and kwargs is None)) assert(progress is None or type(progress) == bool) # end-to-end app payload encryption assert(enc_algo is None or enc_algo in [PAYLOAD_ENC_CRYPTO_BOX]) assert(enc_key is None or type(enc_key) in [six.text_type, six.binary_type]) assert(enc_serializer is None or enc_serializer in [u'json', u'msgpack', u'cbor', u'ubjson']) assert((enc_algo is None and enc_key is None and enc_serializer is None) or (enc_algo is not None and payload is not None)) Message.__init__(self) self.request = request self.args = args self.kwargs = kwargs self.payload = payload self.progress = progress # end-to-end app payload encryption self.enc_algo = enc_algo self.enc_key = enc_key self.enc_serializer = enc_serializer @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Result.MESSAGE_TYPE) if len(wmsg) not in (3, 4, 5): raise ProtocolError("invalid message length {0} for RESULT".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in RESULT") details = check_or_raise_extra(wmsg[2], u"'details' in RESULT") args = None kwargs = None payload = None enc_algo = None enc_key = None enc_serializer = None if len(wmsg) == 4 and type(wmsg[3]) in [six.text_type, six.binary_type]: payload = wmsg[3] enc_algo = details.get(u'enc_algo', None) if enc_algo and enc_algo not in [PAYLOAD_ENC_CRYPTO_BOX]: raise ProtocolError("invalid value {0} for 'enc_algo' detail in EVENT".format(enc_algo)) enc_key = details.get(u'enc_key', None) if enc_key and type(enc_key) not in [six.text_type, six.binary_type]: raise ProtocolError("invalid type {0} for 'enc_key' detail in EVENT".format(type(enc_key))) enc_serializer = details.get(u'enc_serializer', None) if enc_serializer and enc_serializer not in [u'json', u'msgpack', u'cbor', u'ubjson']: raise ProtocolError("invalid value {0} for 'enc_serializer' detail in EVENT".format(enc_serializer)) else: if len(wmsg) > 3: args = wmsg[3] if type(args) != list: raise ProtocolError("invalid type {0} for 'args' in RESULT".format(type(args))) if len(wmsg) > 4: kwargs = wmsg[4] if type(kwargs) != dict: raise ProtocolError("invalid type {0} for 'kwargs' in RESULT".format(type(kwargs))) progress = None if u'progress' in details: detail_progress = details[u'progress'] if type(detail_progress) != bool: raise ProtocolError("invalid type {0} for 'progress' option in RESULT".format(type(detail_progress))) progress = detail_progress obj = Result(request, args=args, kwargs=kwargs, payload=payload, progress=progress, enc_algo=enc_algo, enc_key=enc_key, enc_serializer=enc_serializer) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ details = {} if self.progress is not None: details[u'progress'] = self.progress if self.payload: if self.enc_algo is not None: details[u'enc_algo'] = self.enc_algo if self.enc_key is not None: details[u'enc_key'] = self.enc_key if self.enc_serializer is not None: details[u'enc_serializer'] = self.enc_serializer return [Result.MESSAGE_TYPE, self.request, details, self.payload] else: if self.kwargs: return [Result.MESSAGE_TYPE, self.request, details, self.args, self.kwargs] elif self.args: return [Result.MESSAGE_TYPE, self.request, details, self.args] else: return [Result.MESSAGE_TYPE, self.request, details] def __str__(self): """ Returns string representation of this message. """ return u"Result(request={0}, args={1}, kwargs={2}, progress={3}, enc_algo={4}, enc_key={5}, enc_serializer={6}, payload={7})".format(self.request, self.args, self.kwargs, self.progress, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload)) class Register(Message): """ A WAMP ``REGISTER`` message. Format: ``[REGISTER, Request|id, Options|dict, Procedure|uri]`` """ MESSAGE_TYPE = 64 """ The WAMP message code for this type of message. """ MATCH_EXACT = u'exact' MATCH_PREFIX = u'prefix' MATCH_WILDCARD = u'wildcard' INVOKE_SINGLE = u'single' INVOKE_FIRST = u'first' INVOKE_LAST = u'last' INVOKE_ROUNDROBIN = u'roundrobin' INVOKE_RANDOM = u'random' INVOKE_ALL = u'all' def __init__(self, request, procedure, match=None, invoke=None, concurrency=None): """ :param request: The WAMP request ID of this request. :type request: int :param procedure: The WAMP or application URI of the RPC endpoint provided. :type procedure: unicode :param match: The procedure matching policy to be used for the registration. :type match: unicode :param invoke: The procedure invocation policy to be used for the registration. :type invoke: unicode :param concurrency: The (maximum) concurrency to be used for the registration. :type concurrency: int """ assert(type(request) in six.integer_types) assert(type(procedure) == six.text_type) assert(match is None or type(match) == six.text_type) assert(match is None or match in [Register.MATCH_EXACT, Register.MATCH_PREFIX, Register.MATCH_WILDCARD]) assert(invoke is None or type(invoke) == six.text_type) assert(invoke is None or invoke in [Register.INVOKE_SINGLE, Register.INVOKE_FIRST, Register.INVOKE_LAST, Register.INVOKE_ROUNDROBIN, Register.INVOKE_RANDOM]) assert(concurrency is None or (type(concurrency) in six.integer_types and concurrency > 0)) Message.__init__(self) self.request = request self.procedure = procedure self.match = match or Register.MATCH_EXACT self.invoke = invoke or Register.INVOKE_SINGLE self.concurrency = concurrency @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Register.MESSAGE_TYPE) if len(wmsg) != 4: raise ProtocolError("invalid message length {0} for REGISTER".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in REGISTER") options = check_or_raise_extra(wmsg[2], u"'options' in REGISTER") match = Register.MATCH_EXACT invoke = Register.INVOKE_SINGLE concurrency = None if u'match' in options: option_match = options[u'match'] if type(option_match) != six.text_type: raise ProtocolError("invalid type {0} for 'match' option in REGISTER".format(type(option_match))) if option_match not in [Register.MATCH_EXACT, Register.MATCH_PREFIX, Register.MATCH_WILDCARD]: raise ProtocolError("invalid value {0} for 'match' option in REGISTER".format(option_match)) match = option_match if match == Register.MATCH_EXACT: allow_empty_components = False allow_last_empty = False elif match == Register.MATCH_PREFIX: allow_empty_components = False allow_last_empty = True elif match == Register.MATCH_WILDCARD: allow_empty_components = True allow_last_empty = False else: raise Exception("logic error") procedure = check_or_raise_uri(wmsg[3], u"'procedure' in REGISTER", allow_empty_components=allow_empty_components, allow_last_empty=allow_last_empty) if u'invoke' in options: option_invoke = options[u'invoke'] if type(option_invoke) != six.text_type: raise ProtocolError("invalid type {0} for 'invoke' option in REGISTER".format(type(option_invoke))) if option_invoke not in [Register.INVOKE_SINGLE, Register.INVOKE_FIRST, Register.INVOKE_LAST, Register.INVOKE_ROUNDROBIN, Register.INVOKE_RANDOM]: raise ProtocolError("invalid value {0} for 'invoke' option in REGISTER".format(option_invoke)) invoke = option_invoke if u'concurrency' in options: options_concurrency = options[u'concurrency'] if type(options_concurrency) not in six.integer_types: raise ProtocolError("invalid type {0} for 'concurrency' option in REGISTER".format(type(options_concurrency))) if options_concurrency < 1: raise ProtocolError("invalid value {0} for 'concurrency' option in REGISTER".format(options_concurrency)) concurrency = options_concurrency obj = Register(request, procedure, match=match, invoke=invoke, concurrency=concurrency) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ options = {} if self.match and self.match != Register.MATCH_EXACT: options[u'match'] = self.match if self.invoke and self.invoke != Register.INVOKE_SINGLE: options[u'invoke'] = self.invoke if self.concurrency: options[u'concurrency'] = self.concurrency return [Register.MESSAGE_TYPE, self.request, options, self.procedure] def __str__(self): """ Returns string representation of this message. """ return u"Register(request={0}, procedure={1}, match={2}, invoke={3}, concurrency={4})".format(self.request, self.procedure, self.match, self.invoke, self.concurrency) class Registered(Message): """ A WAMP ``REGISTERED`` message. Format: ``[REGISTERED, REGISTER.Request|id, Registration|id]`` """ MESSAGE_TYPE = 65 """ The WAMP message code for this type of message. """ def __init__(self, request, registration): """ :param request: The request ID of the original ``REGISTER`` request. :type request: int :param registration: The registration ID for the registered procedure (or procedure pattern). :type registration: int """ assert(type(request) in six.integer_types) assert(type(registration) in six.integer_types) Message.__init__(self) self.request = request self.registration = registration @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Registered.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for REGISTERED".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in REGISTERED") registration = check_or_raise_id(wmsg[2], u"'registration' in REGISTERED") obj = Registered(request, registration) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ return [Registered.MESSAGE_TYPE, self.request, self.registration] def __str__(self): """ Returns string representation of this message. """ return u"Registered(request={0}, registration={1})".format(self.request, self.registration) class Unregister(Message): """ A WAMP `UNREGISTER` message. Format: ``[UNREGISTER, Request|id, REGISTERED.Registration|id]`` """ MESSAGE_TYPE = 66 """ The WAMP message code for this type of message. """ def __init__(self, request, registration): """ :param request: The WAMP request ID of this request. :type request: int :param registration: The registration ID for the registration to unregister. :type registration: int """ assert(type(request) in six.integer_types) assert(type(registration) in six.integer_types) Message.__init__(self) self.request = request self.registration = registration @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Unregister.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for WAMP UNREGISTER".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in UNREGISTER") registration = check_or_raise_id(wmsg[2], u"'registration' in UNREGISTER") obj = Unregister(request, registration) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ return [Unregister.MESSAGE_TYPE, self.request, self.registration] def __str__(self): """ Returns string representation of this message. """ return u"Unregister(request={0}, registration={1})".format(self.request, self.registration) class Unregistered(Message): """ A WAMP ``UNREGISTERED`` message. Formats: * ``[UNREGISTERED, UNREGISTER.Request|id]`` * ``[UNREGISTERED, UNREGISTER.Request|id, Details|dict]`` """ MESSAGE_TYPE = 67 """ The WAMP message code for this type of message. """ def __init__(self, request, registration=None, reason=None): """ :param request: The request ID of the original ``UNREGISTER`` request. :type request: int :param registration: If unregister was actively triggered by router, the ID of the registration revoked. :type registration: int or None :param reason: The reason (an URI) for revocation. :type reason: unicode or None. """ assert(type(request) in six.integer_types) assert(registration is None or type(registration) in six.integer_types) assert(reason is None or type(reason) == six.text_type) assert((request != 0 and registration is None) or (request == 0 and registration != 0)) Message.__init__(self) self.request = request self.registration = registration self.reason = reason @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Unregistered.MESSAGE_TYPE) if len(wmsg) not in [2, 3]: raise ProtocolError("invalid message length {0} for UNREGISTERED".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in UNREGISTERED") registration = None reason = None if len(wmsg) > 2: details = check_or_raise_extra(wmsg[2], u"'details' in UNREGISTERED") if u"registration" in details: details_registration = details[u"registration"] if type(details_registration) not in six.integer_types: raise ProtocolError("invalid type {0} for 'registration' detail in UNREGISTERED".format(type(details_registration))) registration = details_registration if u"reason" in details: reason = check_or_raise_uri(details[u"reason"], u"'reason' in UNREGISTERED") obj = Unregistered(request, registration, reason) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ if self.reason is not None or self.registration is not None: details = {} if self.reason is not None: details[u"reason"] = self.reason if self.registration is not None: details[u"registration"] = self.registration return [Unregistered.MESSAGE_TYPE, self.request, details] else: return [Unregistered.MESSAGE_TYPE, self.request] def __str__(self): """ Returns string representation of this message. """ return u"Unregistered(request={0}, reason={1}, registration={2})".format(self.request, self.reason, self.registration) class Invocation(Message): """ A WAMP ``INVOCATION`` message. Formats: * ``[INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict]`` * ``[INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict, CALL.Arguments|list]`` * ``[INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict, CALL.Arguments|list, CALL.ArgumentsKw|dict]`` * ``[INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict, Payload|string/binary]`` """ MESSAGE_TYPE = 68 """ The WAMP message code for this type of message. """ def __init__(self, request, registration, args=None, kwargs=None, payload=None, timeout=None, receive_progress=None, caller=None, caller_authid=None, caller_authrole=None, procedure=None, enc_algo=None, enc_key=None, enc_serializer=None): """ :param request: The WAMP request ID of this request. :type request: int :param registration: The registration ID of the endpoint to be invoked. :type registration: int :param args: Positional values for application-defined event payload. Must be serializable using any serializers in use. :type args: list or tuple or None :param kwargs: Keyword values for application-defined event payload. Must be serializable using any serializers in use. :type kwargs: dict or None :param payload: Alternative, transparent payload. If given, `args` and `kwargs` must be left unset. :type payload: unicode or bytes :param timeout: If present, let the callee automatically cancels the invocation after this ms. :type timeout: int or None :param receive_progress: Indicates if the callee should produce progressive results. :type receive_progress: bool or None :param caller: The WAMP session ID of the caller. Only filled if caller is disclosed. :type caller: None or int :param caller_authid: The WAMP authid of the caller. Only filled if caller is disclosed. :type caller_authid: None or unicode :param caller_authrole: The WAMP authrole of the caller. Only filled if caller is disclosed. :type caller_authrole: None or unicode :param procedure: For pattern-based registrations, the invocation MUST include the actual procedure being called. :type procedure: unicode or None :param enc_algo: If using payload encryption, the algorithm used (currently, only "cryptobox" is valid). :type enc_algo: unicode :param enc_key: If using payload encryption, the message encryption key. :type enc_key: unicode or binary :param enc_serializer: If using payload encryption, the encrypted payload object serializer. :type enc_serializer: unicode """ assert(type(request) in six.integer_types) assert(type(registration) in six.integer_types) assert(args is None or type(args) in [list, tuple]) assert(kwargs is None or type(kwargs) == dict) assert(payload is None or type(payload) in [six.text_type, six.binary_type]) assert(payload is None or (payload is not None and args is None and kwargs is None)) assert(timeout is None or type(timeout) in six.integer_types) assert(receive_progress is None or type(receive_progress) == bool) assert(caller is None or type(caller) in six.integer_types) assert(caller_authid is None or type(caller_authid) == six.text_type) assert(caller_authrole is None or type(caller_authrole) == six.text_type) assert(procedure is None or type(procedure) == six.text_type) # end-to-end app payload encryption assert(enc_algo is None or enc_algo in [PAYLOAD_ENC_CRYPTO_BOX]) assert(enc_key is None or type(enc_key) in [six.text_type, six.binary_type]) assert(enc_serializer is None or enc_serializer in [u'json', u'msgpack', u'cbor', u'ubjson']) assert((enc_algo is None and enc_key is None and enc_serializer is None) or (enc_algo is not None and payload is not None)) Message.__init__(self) self.request = request self.registration = registration self.args = args self.kwargs = kwargs self.payload = payload self.timeout = timeout self.receive_progress = receive_progress self.caller = caller self.caller_authid = caller_authid self.caller_authrole = caller_authrole self.procedure = procedure # end-to-end app payload encryption self.enc_algo = enc_algo self.enc_key = enc_key self.enc_serializer = enc_serializer @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Invocation.MESSAGE_TYPE) if len(wmsg) not in (4, 5, 6): raise ProtocolError("invalid message length {0} for INVOCATION".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in INVOCATION") registration = check_or_raise_id(wmsg[2], u"'registration' in INVOCATION") details = check_or_raise_extra(wmsg[3], u"'details' in INVOCATION") args = None kwargs = None payload = None enc_algo = None enc_key = None enc_serializer = None if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]: payload = wmsg[4] enc_algo = details.get(u'enc_algo', None) if enc_algo and enc_algo not in [PAYLOAD_ENC_CRYPTO_BOX]: raise ProtocolError("invalid value {0} for 'enc_algo' detail in EVENT".format(enc_algo)) enc_key = details.get(u'enc_key', None) if enc_key and type(enc_key) not in [six.text_type, six.binary_type]: raise ProtocolError("invalid type {0} for 'enc_key' detail in EVENT".format(type(enc_key))) enc_serializer = details.get(u'enc_serializer', None) if enc_serializer and enc_serializer not in [u'json', u'msgpack', u'cbor', u'ubjson']: raise ProtocolError("invalid value {0} for 'enc_serializer' detail in EVENT".format(enc_serializer)) else: if len(wmsg) > 4: args = wmsg[4] if type(args) != list: raise ProtocolError("invalid type {0} for 'args' in INVOCATION".format(type(args))) if len(wmsg) > 5: kwargs = wmsg[5] if type(kwargs) != dict: raise ProtocolError("invalid type {0} for 'kwargs' in INVOCATION".format(type(kwargs))) timeout = None receive_progress = None caller = None caller_authid = None caller_authrole = None procedure = None if u'timeout' in details: detail_timeout = details[u'timeout'] if type(detail_timeout) not in six.integer_types: raise ProtocolError("invalid type {0} for 'timeout' detail in INVOCATION".format(type(detail_timeout))) if detail_timeout < 0: raise ProtocolError("invalid value {0} for 'timeout' detail in INVOCATION".format(detail_timeout)) timeout = detail_timeout if u'receive_progress' in details: detail_receive_progress = details[u'receive_progress'] if type(detail_receive_progress) != bool: raise ProtocolError("invalid type {0} for 'receive_progress' detail in INVOCATION".format(type(detail_receive_progress))) receive_progress = detail_receive_progress if u'caller' in details: detail_caller = details[u'caller'] if type(detail_caller) not in six.integer_types: raise ProtocolError("invalid type {0} for 'caller' detail in INVOCATION".format(type(detail_caller))) caller = detail_caller if u'caller_authid' in details: detail_caller_authid = details[u'caller_authid'] if type(detail_caller_authid) != six.text_type: raise ProtocolError("invalid type {0} for 'caller_authid' detail in INVOCATION".format(type(detail_caller_authid))) caller_authid = detail_caller_authid if u'caller_authrole' in details: detail_caller_authrole = details[u'caller_authrole'] if type(detail_caller_authrole) != six.text_type: raise ProtocolError("invalid type {0} for 'caller_authrole' detail in INVOCATION".format(type(detail_caller_authrole))) caller_authrole = detail_caller_authrole if u'procedure' in details: detail_procedure = details[u'procedure'] if type(detail_procedure) != six.text_type: raise ProtocolError("invalid type {0} for 'procedure' detail in INVOCATION".format(type(detail_procedure))) procedure = detail_procedure obj = Invocation(request, registration, args=args, kwargs=kwargs, payload=payload, timeout=timeout, receive_progress=receive_progress, caller=caller, caller_authid=caller_authid, caller_authrole=caller_authrole, procedure=procedure, enc_algo=enc_algo, enc_key=enc_key, enc_serializer=enc_serializer) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ options = {} if self.timeout is not None: options[u'timeout'] = self.timeout if self.receive_progress is not None: options[u'receive_progress'] = self.receive_progress if self.caller is not None: options[u'caller'] = self.caller if self.caller_authid is not None: options[u'caller_authid'] = self.caller_authid if self.caller_authrole is not None: options[u'caller_authrole'] = self.caller_authrole if self.procedure is not None: options[u'procedure'] = self.procedure if self.payload: if self.enc_algo is not None: options[u'enc_algo'] = self.enc_algo if self.enc_key is not None: options[u'enc_key'] = self.enc_key if self.enc_serializer is not None: options[u'enc_serializer'] = self.enc_serializer return [Invocation.MESSAGE_TYPE, self.request, self.registration, options, self.payload] else: if self.kwargs: return [Invocation.MESSAGE_TYPE, self.request, self.registration, options, self.args, self.kwargs] elif self.args: return [Invocation.MESSAGE_TYPE, self.request, self.registration, options, self.args] else: return [Invocation.MESSAGE_TYPE, self.request, self.registration, options] def __str__(self): """ Returns string representation of this message. """ return u"Invocation(request={0}, registration={1}, args={2}, kwargs={3}, timeout={4}, receive_progress={5}, caller={6}, caller_authid={7}, caller_authrole={8}, procedure={9}, enc_algo={10}, enc_key={11}, enc_serializer={12}, payload={13})".format(self.request, self.registration, self.args, self.kwargs, self.timeout, self.receive_progress, self.caller, self.caller_authid, self.caller_authrole, self.procedure, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload)) class Interrupt(Message): """ A WAMP ``INTERRUPT`` message. Format: ``[INTERRUPT, INVOCATION.Request|id, Options|dict]`` """ MESSAGE_TYPE = 69 """ The WAMP message code for this type of message. """ ABORT = u'abort' KILL = u'kill' def __init__(self, request, mode=None): """ :param request: The WAMP request ID of the original ``INVOCATION`` to interrupt. :type request: int :param mode: Specifies how to interrupt the invocation (``"abort"`` or ``"kill"``). :type mode: unicode or None """ assert(type(request) in six.integer_types) assert(mode is None or type(mode) == six.text_type) assert(mode is None or mode in [self.ABORT, self.KILL]) Message.__init__(self) self.request = request self.mode = mode @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Interrupt.MESSAGE_TYPE) if len(wmsg) != 3: raise ProtocolError("invalid message length {0} for INTERRUPT".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in INTERRUPT") options = check_or_raise_extra(wmsg[2], u"'options' in INTERRUPT") # options # mode = None if u'mode' in options: option_mode = options[u'mode'] if type(option_mode) != six.text_type: raise ProtocolError("invalid type {0} for 'mode' option in INTERRUPT".format(type(option_mode))) if option_mode not in [Interrupt.ABORT, Interrupt.KILL]: raise ProtocolError("invalid value '{0}' for 'mode' option in INTERRUPT".format(option_mode)) mode = option_mode obj = Interrupt(request, mode=mode) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ options = {} if self.mode is not None: options[u'mode'] = self.mode return [Interrupt.MESSAGE_TYPE, self.request, options] def __str__(self): """ Returns string representation of this message. """ return u"Interrupt(request={0}, mode={1})".format(self.request, self.mode) class Yield(Message): """ A WAMP ``YIELD`` message. Formats: * ``[YIELD, INVOCATION.Request|id, Options|dict]`` * ``[YIELD, INVOCATION.Request|id, Options|dict, Arguments|list]`` * ``[YIELD, INVOCATION.Request|id, Options|dict, Arguments|list, ArgumentsKw|dict]`` * ``[YIELD, INVOCATION.Request|id, Options|dict, Payload|string/binary]`` """ MESSAGE_TYPE = 70 """ The WAMP message code for this type of message. """ def __init__(self, request, args=None, kwargs=None, payload=None, progress=None, enc_algo=None, enc_key=None, enc_serializer=None): """ :param request: The WAMP request ID of the original call. :type request: int :param args: Positional values for application-defined event payload. Must be serializable using any serializers in use. :type args: list or tuple or None :param kwargs: Keyword values for application-defined event payload. Must be serializable using any serializers in use. :type kwargs: dict or None :param payload: Alternative, transparent payload. If given, `args` and `kwargs` must be left unset. :type payload: unicode or bytes :param progress: If ``True``, this result is a progressive invocation result, and subsequent results (or a final error) will follow. :type progress: bool or None :param enc_algo: If using payload encryption, the algorithm used (currently, only "cryptobox" is valid). :type enc_algo: unicode :param enc_key: If using payload encryption, the message encryption key. :type enc_key: unicode or binary :param enc_serializer: If using payload encryption, the encrypted payload object serializer. :type enc_serializer: unicode """ assert(type(request) in six.integer_types) assert(args is None or type(args) in [list, tuple]) assert(kwargs is None or type(kwargs) == dict) assert(payload is None or type(payload) in [six.text_type, six.binary_type]) assert(payload is None or (payload is not None and args is None and kwargs is None)) assert(progress is None or type(progress) == bool) # end-to-end app payload encryption assert(enc_algo is None or enc_algo in [PAYLOAD_ENC_CRYPTO_BOX]) assert(enc_key is None or type(enc_key) in [six.text_type, six.binary_type]) assert(enc_serializer is None or enc_serializer in [u'json', u'msgpack', u'cbor', u'ubjson']) assert((enc_algo is None and enc_key is None and enc_serializer is None) or (enc_algo is not None and payload is not None)) Message.__init__(self) self.request = request self.args = args self.kwargs = kwargs self.payload = payload self.progress = progress # end-to-end app payload encryption self.enc_algo = enc_algo self.enc_key = enc_key self.enc_serializer = enc_serializer @staticmethod def parse(wmsg): """ Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list :returns: An instance of this class. """ # this should already be verified by WampSerializer.unserialize # assert(len(wmsg) > 0 and wmsg[0] == Yield.MESSAGE_TYPE) if len(wmsg) not in (3, 4, 5): raise ProtocolError("invalid message length {0} for YIELD".format(len(wmsg))) request = check_or_raise_id(wmsg[1], u"'request' in YIELD") options = check_or_raise_extra(wmsg[2], u"'options' in YIELD") args = None kwargs = None payload = None enc_algo = None enc_key = None enc_serializer = None if len(wmsg) == 4 and type(wmsg[3]) in [six.text_type, six.binary_type]: payload = wmsg[3] enc_algo = options.get(u'enc_algo', None) if enc_algo and enc_algo not in [PAYLOAD_ENC_CRYPTO_BOX]: raise ProtocolError("invalid value {0} for 'enc_algo' detail in EVENT".format(enc_algo)) enc_key = options.get(u'enc_key', None) if enc_key and type(enc_key) not in [six.text_type, six.binary_type]: raise ProtocolError("invalid type {0} for 'enc_key' detail in EVENT".format(type(enc_key))) enc_serializer = options.get(u'enc_serializer', None) if enc_serializer and enc_serializer not in [u'json', u'msgpack', u'cbor', u'ubjson']: raise ProtocolError("invalid value {0} for 'enc_serializer' detail in EVENT".format(enc_serializer)) else: if len(wmsg) > 3: args = wmsg[3] if type(args) != list: raise ProtocolError("invalid type {0} for 'args' in YIELD".format(type(args))) if len(wmsg) > 4: kwargs = wmsg[4] if type(kwargs) != dict: raise ProtocolError("invalid type {0} for 'kwargs' in YIELD".format(type(kwargs))) progress = None if u'progress' in options: option_progress = options[u'progress'] if type(option_progress) != bool: raise ProtocolError("invalid type {0} for 'progress' option in YIELD".format(type(option_progress))) progress = option_progress obj = Yield(request, args=args, kwargs=kwargs, payload=payload, progress=progress, enc_algo=enc_algo, enc_key=enc_key, enc_serializer=enc_serializer) return obj def marshal(self): """ Marshal this object into a raw message for subsequent serialization to bytes. :returns: list -- The serialized raw message. """ options = {} if self.progress is not None: options[u'progress'] = self.progress if self.payload: if self.enc_algo is not None: options[u'enc_algo'] = self.enc_algo if self.enc_key is not None: options[u'enc_key'] = self.enc_key if self.enc_serializer is not None: options[u'enc_serializer'] = self.enc_serializer return [Yield.MESSAGE_TYPE, self.request, options, self.payload] else: if self.kwargs: return [Yield.MESSAGE_TYPE, self.request, options, self.args, self.kwargs] elif self.args: return [Yield.MESSAGE_TYPE, self.request, options, self.args] else: return [Yield.MESSAGE_TYPE, self.request, options] def __str__(self): """ Returns string representation of this message. """ return u"Yield(request={0}, args={1}, kwargs={2}, progress={3}, enc_algo={4}, enc_key={5}, enc_serializer={6}, payload={7})".format(self.request, self.args, self.kwargs, self.progress, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload))