1###############################################################################
2#
3# The MIT License (MIT)
4#
5# Copyright (c) Crossbar.io Technologies GmbH
6#
7# Permission is hereby granted, free of charge, to any person obtaining a copy
8# of this software and associated documentation files (the "Software"), to deal
9# in the Software without restriction, including without limitation the rights
10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# copies of the Software, and to permit persons to whom the Software is
12# furnished to do so, subject to the following conditions:
13#
14# The above copyright notice and this permission notice shall be included in
15# all copies or substantial portions of the Software.
16#
17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# THE SOFTWARE.
24#
25###############################################################################
26
27from __future__ import absolute_import
28
29import re
30import binascii
31
32import six
33
34import autobahn
35from autobahn.wamp.exception import ProtocolError
36from autobahn.wamp.role import ROLE_NAME_TO_CLASS
37
38try:
39    import cbor
40    import flatbuffers
41    from autobahn.wamp import message_fbs
42except ImportError:
43    _HAS_WAMP_FLATBUFFERS = False
44else:
45    _HAS_WAMP_FLATBUFFERS = True
46
47__all__ = ('Message',
48           'Hello',
49           'Welcome',
50           'Abort',
51           'Challenge',
52           'Authenticate',
53           'Goodbye',
54           'Error',
55           'Publish',
56           'Published',
57           'Subscribe',
58           'Subscribed',
59           'Unsubscribe',
60           'Unsubscribed',
61           'Event',
62           'Call',
63           'Cancel',
64           'Result',
65           'Register',
66           'Registered',
67           'Unregister',
68           'Unregistered',
69           'Invocation',
70           'Interrupt',
71           'Yield',
72           'check_or_raise_uri',
73           'check_or_raise_id',
74           'is_valid_enc_algo',
75           'is_valid_enc_serializer',
76           'PAYLOAD_ENC_CRYPTO_BOX',
77           'PAYLOAD_ENC_MQTT',
78           'PAYLOAD_ENC_STANDARD_IDENTIFIERS')
79
80
81# strict URI check allowing empty URI components
82_URI_PAT_STRICT_EMPTY = re.compile(r"^(([0-9a-z_]+\.)|\.)*([0-9a-z_]+)?$")
83
84# loose URI check allowing empty URI components
85_URI_PAT_LOOSE_EMPTY = re.compile(r"^(([^\s\.#]+\.)|\.)*([^\s\.#]+)?$")
86
87# strict URI check disallowing empty URI components
88_URI_PAT_STRICT_NON_EMPTY = re.compile(r"^([0-9a-z_]+\.)*([0-9a-z_]+)$")
89
90# loose URI check disallowing empty URI components
91_URI_PAT_LOOSE_NON_EMPTY = re.compile(r"^([^\s\.#]+\.)*([^\s\.#]+)$")
92
93# strict URI check disallowing empty URI components in all but the last component
94_URI_PAT_STRICT_LAST_EMPTY = re.compile(r"^([0-9a-z_]+\.)*([0-9a-z_]*)$")
95
96# loose URI check disallowing empty URI components in all but the last component
97_URI_PAT_LOOSE_LAST_EMPTY = re.compile(r"^([^\s\.#]+\.)*([^\s\.#]*)$")
98
99# custom (=implementation specific) WAMP attributes (used in WAMP message details/options)
100_CUSTOM_ATTRIBUTE = re.compile(r"^x_([a-z][0-9a-z_]+)?$")
101
102# Value for algo attribute in end-to-end encrypted messages using cryptobox, which
103# is a scheme based on Curve25519, SHA512, Salsa20 and Poly1305.
104# See: http://cr.yp.to/highspeed/coolnacl-20120725.pdf
105PAYLOAD_ENC_CRYPTO_BOX = u'cryptobox'
106
107# Payload transparency identifier for MQTT payloads (which are arbitrary binary).
108PAYLOAD_ENC_MQTT = u'mqtt'
109
110# Payload transparency identifier for XBR payloads
111PAYLOAD_ENC_XBR = u'xbr'
112
113# Payload transparency algorithm identifiers from the WAMP spec.
114PAYLOAD_ENC_STANDARD_IDENTIFIERS = [PAYLOAD_ENC_CRYPTO_BOX, PAYLOAD_ENC_MQTT, PAYLOAD_ENC_XBR]
115
116# Payload transparency serializer identifiers from the WAMP spec.
117PAYLOAD_ENC_STANDARD_SERIALIZERS = [u'json', u'msgpack', u'cbor', u'ubjson', u'flatbuffers']
118
119ENC_ALGO_NONE = 0
120ENC_ALGO_CRYPTOBOX = 1
121ENC_ALGO_MQTT = 2
122ENC_ALGO_XBR = 3
123
124ENC_ALGOS = {
125    ENC_ALGO_NONE: u'null',
126    ENC_ALGO_CRYPTOBOX: u'cryptobox',
127    ENC_ALGO_MQTT: u'mqtt',
128    ENC_ALGO_XBR: u'xbr',
129}
130
131ENC_ALGOS_FROMSTR = {key: value for value, key in ENC_ALGOS.items()}
132
133ENC_SER_NONE = 0
134ENC_SER_JSON = 1
135ENC_SER_MSGPACK = 2
136ENC_SER_CBOR = 3
137ENC_SER_UBJSON = 4
138ENC_SER_OPAQUE = 5
139ENC_SER_FLATBUFFERS = 6
140
141ENC_SERS = {
142    ENC_SER_NONE: u'null',
143    ENC_SER_JSON: u'json',
144    ENC_SER_MSGPACK: u'msgpack',
145    ENC_SER_CBOR: u'cbor',
146    ENC_SER_UBJSON: u'ubjson',
147    ENC_SER_OPAQUE: u'opaque',
148    ENC_SER_FLATBUFFERS: u'flatbuffers',
149}
150
151ENC_SERS_FROMSTR = {key: value for value, key in ENC_SERS.items()}
152
153
154def is_valid_enc_algo(enc_algo):
155    """
156    For WAMP payload transparency mode, check if the provided ``enc_algo``
157    identifier in the WAMP message is a valid one.
158
159    Currently defined standard identifiers are:
160
161    * ``"cryptobox"``
162    * ``"mqtt"``
163    * ``"xbr"``
164
165    Users can select arbitrary identifiers too, but these MUST start with ``u"x_"``.
166
167    :param enc_algo: The payload transparency algorithm identifier to check.
168    :type enc_algo: str
169
170    :returns: Returns ``True`` if and only if the payload transparency
171        algorithm identifier is valid.
172    :rtype: bool
173    """
174    return type(enc_algo) == six.text_type and (enc_algo in PAYLOAD_ENC_STANDARD_IDENTIFIERS or _CUSTOM_ATTRIBUTE.match(enc_algo))
175
176
177def is_valid_enc_serializer(enc_serializer):
178    """
179    For WAMP payload transparency mode, check if the provided ``enc_serializer``
180    identifier in the WAMP message is a valid one.
181
182    Currently, the only standard defined identifier are
183
184    * ``"json"``
185    * ``"msgpack"``
186    * ``"cbor"``
187    * ``"ubjson"``
188    * ``"flatbuffers"``
189
190    Users can select arbitrary identifiers too, but these MUST start with ``u"x_"``.
191
192    :param enc_serializer: The payload transparency serializer identifier to check.
193    :type enc_serializer: str
194
195    :returns: Returns ``True`` if and only if the payload transparency
196        serializer identifier is valid.
197    :rtype: bool
198    """
199    return type(enc_serializer) == six.text_type and (enc_serializer in PAYLOAD_ENC_STANDARD_SERIALIZERS or _CUSTOM_ATTRIBUTE.match(enc_serializer))
200
201
202def b2a(data, max_len=40):
203    if type(data) == six.text_type:
204        s = data
205    elif type(data) == six.binary_type:
206        s = binascii.b2a_hex(data).decode('ascii')
207    elif data is None:
208        s = u'-'
209    else:
210        s = u'{}'.format(data)
211    if len(s) > max_len:
212        return s[:max_len] + u'..'
213    else:
214        return s
215
216
217def check_or_raise_uri(value, message=u"WAMP message invalid", strict=False, allow_empty_components=False, allow_last_empty=False, allow_none=False):
218    """
219    Check a value for being a valid WAMP URI.
220
221    If the value is not a valid WAMP URI is invalid, raises :class:`autobahn.wamp.exception.ProtocolError`.
222    Otherwise return the value.
223
224    :param value: The value to check.
225    :type value: str or None
226
227    :param message: Prefix for message in exception raised when value is invalid.
228    :type message: str
229
230    :param strict: If ``True``, do a strict check on the URI (the WAMP spec SHOULD behavior).
231    :type strict: bool
232
233    :param allow_empty_components: If ``True``, allow empty URI components (for pattern based
234       subscriptions and registrations).
235    :type allow_empty_components: bool
236
237    :param allow_none: If ``True``, allow ``None`` for URIs.
238    :type allow_none: bool
239
240    :returns: The URI value (if valid).
241    :rtype: str
242
243    :raises: instance of :class:`autobahn.wamp.exception.ProtocolError`
244    """
245    if value is None:
246        if allow_none:
247            return
248        else:
249            raise ProtocolError(u"{0}: URI cannot be null".format(message))
250
251    if type(value) != six.text_type:
252        if not (value is None and allow_none):
253            raise ProtocolError(u"{0}: invalid type {1} for URI".format(message, type(value)))
254
255    if strict:
256        if allow_last_empty:
257            pat = _URI_PAT_STRICT_LAST_EMPTY
258        elif allow_empty_components:
259            pat = _URI_PAT_STRICT_EMPTY
260        else:
261            pat = _URI_PAT_STRICT_NON_EMPTY
262    else:
263        if allow_last_empty:
264            pat = _URI_PAT_LOOSE_LAST_EMPTY
265        elif allow_empty_components:
266            pat = _URI_PAT_LOOSE_EMPTY
267        else:
268            pat = _URI_PAT_LOOSE_NON_EMPTY
269
270    if not pat.match(value):
271        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))
272    else:
273        return value
274
275
276def check_or_raise_id(value, message=u"WAMP message invalid"):
277    """
278    Check a value for being a valid WAMP ID.
279
280    If the value is not a valid WAMP ID, raises :class:`autobahn.wamp.exception.ProtocolError`.
281    Otherwise return the value.
282
283    :param value: The value to check.
284    :type value: int
285
286    :param message: Prefix for message in exception raised when value is invalid.
287    :type message: str
288
289    :returns: The ID value (if valid).
290    :rtype: int
291
292    :raises: instance of :class:`autobahn.wamp.exception.ProtocolError`
293    """
294    if type(value) not in six.integer_types:
295        raise ProtocolError(u"{0}: invalid type {1} for ID".format(message, type(value)))
296    # the value 0 for WAMP IDs is possible in certain WAMP messages, e.g. UNREGISTERED with
297    # router revocation signaling!
298    if value < 0 or value > 9007199254740992:  # 2**53
299        raise ProtocolError(u"{0}: invalid value {1} for ID".format(message, value))
300    return value
301
302
303def check_or_raise_extra(value, message=u"WAMP message invalid"):
304    """
305    Check a value for being a valid WAMP extra dictionary.
306
307    If the value is not a valid WAMP extra dictionary, raises :class:`autobahn.wamp.exception.ProtocolError`.
308    Otherwise return the value.
309
310    :param value: The value to check.
311    :type value: dict
312
313    :param message: Prefix for message in exception raised when value is invalid.
314    :type message: str
315
316    :returns: The extra dictionary (if valid).
317    :rtype: dict
318
319    :raises: instance of :class:`autobahn.wamp.exception.ProtocolError`
320    """
321    if type(value) != dict:
322        raise ProtocolError(u"{0}: invalid type {1} for WAMP extra".format(message, type(value)))
323    for k in value.keys():
324        if not isinstance(k, six.text_type):
325            raise ProtocolError(u"{0}: invalid type {1} for key in WAMP extra ('{2}')".format(message, type(k), k))
326    return value
327
328
329def _validate_kwargs(kwargs, message=u"WAMP message invalid"):
330    """
331    Check a value for being a valid WAMP kwargs dictionary.
332
333    If the value is not a valid WAMP kwargs dictionary,
334    raises :class:`autobahn.wamp.exception.ProtocolError`.
335    Otherwise return the kwargs.
336
337    The WAMP spec requires that the keys in kwargs are proper
338    strings (unicode), not bytes. Note that the WAMP spec
339    says nothing about keys in application payload. Key in the
340    latter can be potentially of other type (if that is really
341    wanted).
342
343    :param kwargs: The keyword arguments to check.
344    :type kwargs: dict
345
346    :param message: Prefix for message in exception raised when
347        value is invalid.
348    :type message: str
349
350    :returns: The kwargs dictionary (if valid).
351    :rtype: dict
352
353    :raises: instance of
354        :class:`autobahn.wamp.exception.ProtocolError`
355    """
356    if kwargs is not None:
357        if type(kwargs) != dict:
358            raise ProtocolError(u"{0}: invalid type {1} for WAMP kwargs".format(message, type(kwargs)))
359        for k in kwargs.keys():
360            if not isinstance(k, six.text_type):
361                raise ProtocolError(u"{0}: invalid type {1} for key in WAMP kwargs ('{2}')".format(message, type(k), k))
362        return kwargs
363
364
365class Message(object):
366    """
367    WAMP message base class.
368
369    .. note:: This is not supposed to be instantiated, but subclassed only.
370    """
371
372    MESSAGE_TYPE = None
373    """
374    WAMP message type code.
375    """
376
377    __slots__ = (
378        '_from_fbs',
379        '_serialized',
380        '_correlation_id',
381        '_correlation_uri',
382        '_correlation_is_anchor',
383        '_correlation_is_last',
384    )
385
386    def __init__(self, from_fbs=None):
387        # only filled in case this object has flatbuffers underlying
388        self._from_fbs = from_fbs
389
390        # serialization cache: mapping from ISerializer instances to serialized bytes
391        self._serialized = {}
392
393        # user attributes for message correlation (mainly for message tracing)
394        self._correlation_id = None
395        self._correlation_uri = None
396        self._correlation_is_anchor = None
397        self._correlation_is_last = None
398
399    @property
400    def correlation_id(self):
401        return self._correlation_id
402
403    @correlation_id.setter
404    def correlation_id(self, value):
405        assert(value is None or type(value) == six.text_type)
406        self._correlation_id = value
407
408    @property
409    def correlation_uri(self):
410        return self._correlation_uri
411
412    @correlation_uri.setter
413    def correlation_uri(self, value):
414        assert(value is None or type(value) == six.text_type)
415        self._correlation_uri = value
416
417    @property
418    def correlation_is_anchor(self):
419        return self._correlation_is_anchor
420
421    @correlation_is_anchor.setter
422    def correlation_is_anchor(self, value):
423        assert(value is None or type(value) == bool)
424        self._correlation_is_anchor = value
425
426    @property
427    def correlation_is_last(self):
428        return self._correlation_is_last
429
430    @correlation_is_last.setter
431    def correlation_is_last(self, value):
432        assert(value is None or type(value) == bool)
433        self._correlation_is_last = value
434
435    def __eq__(self, other):
436        """
437        Compare this message to another message for equality.
438
439        :param other: The other message to compare with.
440        :type other: obj
441
442        :returns: ``True`` iff the messages are equal.
443        :rtype: bool
444        """
445        if not isinstance(other, self.__class__):
446            return False
447        # we only want the actual message data attributes (not eg _serialize)
448        for k in self.__slots__:
449            if k not in ['_serialized',
450                         '_correlation_id',
451                         '_correlation_uri',
452                         '_correlation_is_anchor',
453                         '_correlation_is_last'] and not k.startswith('_'):
454                if not getattr(self, k) == getattr(other, k):
455                    return False
456        return True
457
458    def __ne__(self, other):
459        """
460        Compare this message to another message for inequality.
461
462        :param other: The other message to compare with.
463        :type other: obj
464
465        :returns: ``True`` iff the messages are not equal.
466        :rtype: bool
467        """
468        return not self.__eq__(other)
469
470    @staticmethod
471    def parse(wmsg):
472        """
473        Factory method that parses a unserialized raw message (as returned byte
474        :func:`autobahn.interfaces.ISerializer.unserialize`) into an instance
475        of this class.
476
477        :returns: An instance of this class.
478        :rtype: obj
479        """
480        raise NotImplementedError()
481
482    def marshal(self):
483        raise NotImplementedError()
484
485    @staticmethod
486    def cast(buf):
487        raise NotImplementedError()
488
489    def build(self, builder):
490        raise NotImplementedError()
491
492    def uncache(self):
493        """
494        Resets the serialization cache.
495        """
496        self._serialized = {}
497
498    def serialize(self, serializer):
499        """
500        Serialize this object into a wire level bytes representation and cache
501        the resulting bytes. If the cache already contains an entry for the given
502        serializer, return the cached representation directly.
503
504        :param serializer: The wire level serializer to use.
505        :type serializer: An instance that implements :class:`autobahn.interfaces.ISerializer`
506
507        :returns: The serialized bytes.
508        :rtype: bytes
509        """
510        # only serialize if not cached ..
511        if serializer not in self._serialized:
512            if serializer.NAME == u'flatbuffers':
513                # flatbuffers get special treatment ..
514                builder = flatbuffers.Builder(1024)
515
516                # this is the core method writing out this message (self) to a (new) flatbuffer
517                # FIXME: implement this method for all classes derived from Message
518                obj = self.build(builder)
519
520                builder.Finish(obj)
521                buf = builder.Output()
522                self._serialized[serializer] = bytes(buf)
523            else:
524                # all other serializers first marshal() the object and then serialize the latter
525                self._serialized[serializer] = serializer.serialize(self.marshal())
526
527        # cache is filled now: return serialized, cached bytes
528        return self._serialized[serializer]
529
530
531class Hello(Message):
532    """
533    A WAMP ``HELLO`` message.
534
535    Format: ``[HELLO, Realm|uri, Details|dict]``
536    """
537
538    MESSAGE_TYPE = 1
539    """
540    The WAMP message code for this type of message.
541    """
542
543    __slots__ = (
544        'realm',
545        'roles',
546        'authmethods',
547        'authid',
548        'authrole',
549        'authextra',
550        'resumable',
551        'resume_session',
552        'resume_token',
553    )
554
555    def __init__(self,
556                 realm,
557                 roles,
558                 authmethods=None,
559                 authid=None,
560                 authrole=None,
561                 authextra=None,
562                 resumable=None,
563                 resume_session=None,
564                 resume_token=None):
565        """
566
567        :param realm: The URI of the WAMP realm to join.
568        :type realm: str
569
570        :param roles: The WAMP session roles and features to announce.
571        :type roles: dict of :class:`autobahn.wamp.role.RoleFeatures`
572
573        :param authmethods: The authentication methods to announce.
574        :type authmethods: list of str or None
575
576        :param authid: The authentication ID to announce.
577        :type authid: str or None
578
579        :param authrole: The authentication role to announce.
580        :type authrole: str or None
581
582        :param authextra: Application-specific "extra data" to be forwarded to the client.
583        :type authextra: dict or None
584
585        :param resumable: Whether the client wants this to be a session that can be later resumed.
586        :type resumable: bool or None
587
588        :param resume_session: The session the client would like to resume.
589        :type resume_session: int or None
590
591        :param resume_token: The secure authorisation token to resume the session.
592        :type resume_token: str or None
593        """
594        assert(realm is None or isinstance(realm, six.text_type))
595        assert(type(roles) == dict)
596        assert(len(roles) > 0)
597        for role in roles:
598            assert(role in [u'subscriber', u'publisher', u'caller', u'callee'])
599            assert(isinstance(roles[role], autobahn.wamp.role.ROLE_NAME_TO_CLASS[role]))
600        if authmethods:
601            assert(type(authmethods) == list)
602            for authmethod in authmethods:
603                assert(type(authmethod) == six.text_type)
604        assert(authid is None or type(authid) == six.text_type)
605        assert(authrole is None or type(authrole) == six.text_type)
606        assert(authextra is None or type(authextra) == dict)
607        assert(resumable is None or type(resumable) == bool)
608        assert(resume_session is None or type(resume_session) == int)
609        assert(resume_token is None or type(resume_token) == six.text_type)
610
611        Message.__init__(self)
612        self.realm = realm
613        self.roles = roles
614        self.authmethods = authmethods
615        self.authid = authid
616        self.authrole = authrole
617        self.authextra = authextra
618        self.resumable = resumable
619        self.resume_session = resume_session
620        self.resume_token = resume_token
621
622    @staticmethod
623    def parse(wmsg):
624        """
625        Verifies and parses an unserialized raw message into an actual WAMP message instance.
626
627        :param wmsg: The unserialized raw message.
628        :type wmsg: list
629
630        :returns: An instance of this class.
631        """
632        # this should already be verified by WampSerializer.unserialize
633        assert(len(wmsg) > 0 and wmsg[0] == Hello.MESSAGE_TYPE)
634
635        if len(wmsg) != 3:
636            raise ProtocolError("invalid message length {0} for HELLO".format(len(wmsg)))
637
638        realm = check_or_raise_uri(wmsg[1], u"'realm' in HELLO", allow_none=True)
639        details = check_or_raise_extra(wmsg[2], u"'details' in HELLO")
640
641        roles = {}
642
643        if u'roles' not in details:
644            raise ProtocolError(u"missing mandatory roles attribute in options in HELLO")
645
646        details_roles = check_or_raise_extra(details[u'roles'], u"'roles' in 'details' in HELLO")
647
648        if len(details_roles) == 0:
649            raise ProtocolError(u"empty 'roles' in 'details' in HELLO")
650
651        for role in details_roles:
652            if role not in [u'subscriber', u'publisher', u'caller', u'callee']:
653                raise ProtocolError("invalid role '{0}' in 'roles' in 'details' in HELLO".format(role))
654
655            role_cls = ROLE_NAME_TO_CLASS[role]
656
657            details_role = check_or_raise_extra(details_roles[role], "role '{0}' in 'roles' in 'details' in HELLO".format(role))
658
659            if u'features' in details_role:
660                check_or_raise_extra(details_role[u'features'], "'features' in role '{0}' in 'roles' in 'details' in HELLO".format(role))
661
662                role_features = role_cls(**details_role[u'features'])
663
664            else:
665                role_features = role_cls()
666
667            roles[role] = role_features
668
669        authmethods = None
670        if u'authmethods' in details:
671            details_authmethods = details[u'authmethods']
672            if type(details_authmethods) != list:
673                raise ProtocolError("invalid type {0} for 'authmethods' detail in HELLO".format(type(details_authmethods)))
674
675            for auth_method in details_authmethods:
676                if type(auth_method) != six.text_type:
677                    raise ProtocolError("invalid type {0} for item in 'authmethods' detail in HELLO".format(type(auth_method)))
678
679            authmethods = details_authmethods
680
681        authid = None
682        if u'authid' in details:
683            details_authid = details[u'authid']
684            if type(details_authid) != six.text_type:
685                raise ProtocolError("invalid type {0} for 'authid' detail in HELLO".format(type(details_authid)))
686
687            authid = details_authid
688
689        authrole = None
690        if u'authrole' in details:
691            details_authrole = details[u'authrole']
692            if type(details_authrole) != six.text_type:
693                raise ProtocolError("invalid type {0} for 'authrole' detail in HELLO".format(type(details_authrole)))
694
695            authrole = details_authrole
696
697        authextra = None
698        if u'authextra' in details:
699            details_authextra = details[u'authextra']
700            if type(details_authextra) != dict:
701                raise ProtocolError("invalid type {0} for 'authextra' detail in HELLO".format(type(details_authextra)))
702
703            authextra = details_authextra
704
705        resumable = None
706        if u'resumable' in details:
707            resumable = details[u'resumable']
708            if type(resumable) != bool:
709                raise ProtocolError("invalid type {0} for 'resumable' detail in HELLO".format(type(resumable)))
710
711        resume_session = None
712        if u'resume-session' in details:
713            resume_session = details[u'resume-session']
714            if type(resume_session) != int:
715                raise ProtocolError("invalid type {0} for 'resume-session' detail in HELLO".format(type(resume_session)))
716
717        resume_token = None
718        if u'resume-token' in details:
719            resume_token = details[u'resume-token']
720            if type(resume_token) != six.text_type:
721                raise ProtocolError("invalid type {0} for 'resume-token' detail in HELLO".format(type(resume_token)))
722        else:
723            if resume_session:
724                raise ProtocolError("resume-token must be provided if resume-session is provided in HELLO")
725
726        obj = Hello(realm, roles, authmethods, authid, authrole, authextra, resumable, resume_session, resume_token)
727
728        return obj
729
730    def marshal(self):
731        """
732        Marshal this object into a raw message for subsequent serialization to bytes.
733
734        :returns: The serialized raw message.
735        :rtype: list
736        """
737        details = {u'roles': {}}
738        for role in self.roles.values():
739            details[u'roles'][role.ROLE] = {}
740            for feature in role.__dict__:
741                if not feature.startswith('_') and feature != 'ROLE' and getattr(role, feature) is not None:
742                    if u'features' not in details[u'roles'][role.ROLE]:
743                        details[u'roles'][role.ROLE] = {u'features': {}}
744                    details[u'roles'][role.ROLE][u'features'][six.u(feature)] = getattr(role, feature)
745
746        if self.authmethods is not None:
747            details[u'authmethods'] = self.authmethods
748
749        if self.authid is not None:
750            details[u'authid'] = self.authid
751
752        if self.authrole is not None:
753            details[u'authrole'] = self.authrole
754
755        if self.authextra is not None:
756            details[u'authextra'] = self.authextra
757
758        if self.resumable is not None:
759            details[u'resumable'] = self.resumable
760
761        if self.resume_session is not None:
762            details[u'resume-session'] = self.resume_session
763
764        if self.resume_token is not None:
765            details[u'resume-token'] = self.resume_token
766
767        return [Hello.MESSAGE_TYPE, self.realm, details]
768
769    def __str__(self):
770        """
771        Return a string representation of this message.
772        """
773        return u"Hello(realm={}, roles={}, authmethods={}, authid={}, authrole={}, authextra={}, resumable={}, resume_session={}, resume_token={})".format(self.realm, self.roles, self.authmethods, self.authid, self.authrole, self.authextra, self.resumable, self.resume_session, self.resume_token)
774
775
776class Welcome(Message):
777    """
778    A WAMP ``WELCOME`` message.
779
780    Format: ``[WELCOME, Session|id, Details|dict]``
781    """
782
783    MESSAGE_TYPE = 2
784    """
785    The WAMP message code for this type of message.
786    """
787
788    __slots__ = (
789        'session',
790        'roles',
791        'realm',
792        'authid',
793        'authrole',
794        'authmethod',
795        'authprovider',
796        'authextra',
797        'resumed',
798        'resumable',
799        'resume_token',
800        'custom',
801    )
802
803    def __init__(self,
804                 session,
805                 roles,
806                 realm=None,
807                 authid=None,
808                 authrole=None,
809                 authmethod=None,
810                 authprovider=None,
811                 authextra=None,
812                 resumed=None,
813                 resumable=None,
814                 resume_token=None,
815                 custom=None):
816        """
817
818        :param session: The WAMP session ID the other peer is assigned.
819        :type session: int
820
821        :param roles: The WAMP roles to announce.
822        :type roles: dict of :class:`autobahn.wamp.role.RoleFeatures`
823
824        :param realm: The effective realm the session is joined on.
825        :type realm: str or None
826
827        :param authid: The authentication ID assigned.
828        :type authid: str or None
829
830        :param authrole: The authentication role assigned.
831        :type authrole: str or None
832
833        :param authmethod: The authentication method in use.
834        :type authmethod: str or None
835
836        :param authprovider: The authentication provided in use.
837        :type authprovider: str or None
838
839        :param authextra: Application-specific "extra data" to be forwarded to the client.
840        :type authextra: arbitrary or None
841
842        :param resumed: Whether the session is a resumed one.
843        :type resumed: bool or None
844
845        :param resumable: Whether this session can be resumed later.
846        :type resumable: bool or None
847
848        :param resume_token: The secure authorisation token to resume the session.
849        :type resume_token: str or None
850
851        :param custom: Implementation-specific "custom attributes" (`x_my_impl_attribute`) to be set.
852        :type custom: dict or None
853        """
854        assert(type(session) in six.integer_types)
855        assert(type(roles) == dict)
856        assert(len(roles) > 0)
857        for role in roles:
858            assert(role in [u'broker', u'dealer'])
859            assert(isinstance(roles[role], autobahn.wamp.role.ROLE_NAME_TO_CLASS[role]))
860        assert(realm is None or type(realm) == six.text_type)
861        assert(authid is None or type(authid) == six.text_type)
862        assert(authrole is None or type(authrole) == six.text_type)
863        assert(authmethod is None or type(authmethod) == six.text_type)
864        assert(authprovider is None or type(authprovider) == six.text_type)
865        assert(authextra is None or type(authextra) == dict)
866        assert(resumed is None or type(resumed) == bool)
867        assert(resumable is None or type(resumable) == bool)
868        assert(resume_token is None or type(resume_token) == six.text_type)
869        assert(custom is None or type(custom) == dict)
870        if custom:
871            for k in custom:
872                assert(_CUSTOM_ATTRIBUTE.match(k))
873
874        Message.__init__(self)
875        self.session = session
876        self.roles = roles
877        self.realm = realm
878        self.authid = authid
879        self.authrole = authrole
880        self.authmethod = authmethod
881        self.authprovider = authprovider
882        self.authextra = authextra
883        self.resumed = resumed
884        self.resumable = resumable
885        self.resume_token = resume_token
886        self.custom = custom or {}
887
888    @staticmethod
889    def parse(wmsg):
890        """
891        Verifies and parses an unserialized raw message into an actual WAMP message instance.
892
893        :param wmsg: The unserialized raw message.
894        :type wmsg: list
895
896        :returns: An instance of this class.
897        """
898        # this should already be verified by WampSerializer.unserialize
899        assert(len(wmsg) > 0 and wmsg[0] == Welcome.MESSAGE_TYPE)
900
901        if len(wmsg) != 3:
902            raise ProtocolError("invalid message length {0} for WELCOME".format(len(wmsg)))
903
904        session = check_or_raise_id(wmsg[1], u"'session' in WELCOME")
905        details = check_or_raise_extra(wmsg[2], u"'details' in WELCOME")
906
907        # FIXME: tigher value checking (types, URIs etc)
908        realm = details.get(u'realm', None)
909        authid = details.get(u'authid', None)
910        authrole = details.get(u'authrole', None)
911        authmethod = details.get(u'authmethod', None)
912        authprovider = details.get(u'authprovider', None)
913        authextra = details.get(u'authextra', None)
914
915        resumed = None
916        if u'resumed' in details:
917            resumed = details[u'resumed']
918            if not type(resumed) == bool:
919                raise ProtocolError("invalid type {0} for 'resumed' detail in WELCOME".format(type(resumed)))
920
921        resumable = None
922        if u'resumable' in details:
923            resumable = details[u'resumable']
924            if not type(resumable) == bool:
925                raise ProtocolError("invalid type {0} for 'resumable' detail in WELCOME".format(type(resumable)))
926
927        resume_token = None
928        if u'resume_token' in details:
929            resume_token = details[u'resume_token']
930            if not type(resume_token) == six.text_type:
931                raise ProtocolError("invalid type {0} for 'resume_token' detail in WELCOME".format(type(resume_token)))
932        elif resumable:
933            raise ProtocolError("resume_token required when resumable is given in WELCOME")
934
935        roles = {}
936
937        if u'roles' not in details:
938            raise ProtocolError(u"missing mandatory roles attribute in options in WELCOME")
939
940        details_roles = check_or_raise_extra(details['roles'], u"'roles' in 'details' in WELCOME")
941
942        if len(details_roles) == 0:
943            raise ProtocolError(u"empty 'roles' in 'details' in WELCOME")
944
945        for role in details_roles:
946            if role not in [u'broker', u'dealer']:
947                raise ProtocolError("invalid role '{0}' in 'roles' in 'details' in WELCOME".format(role))
948
949            role_cls = ROLE_NAME_TO_CLASS[role]
950
951            details_role = check_or_raise_extra(details_roles[role], "role '{0}' in 'roles' in 'details' in WELCOME".format(role))
952
953            if u'features' in details_role:
954                check_or_raise_extra(details_role[u'features'], "'features' in role '{0}' in 'roles' in 'details' in WELCOME".format(role))
955
956                role_features = role_cls(**details_roles[role][u'features'])
957
958            else:
959                role_features = role_cls()
960
961            roles[role] = role_features
962
963        custom = {}
964        for k in details:
965            if _CUSTOM_ATTRIBUTE.match(k):
966                custom[k] = details[k]
967
968        obj = Welcome(session, roles, realm, authid, authrole, authmethod, authprovider, authextra, resumed, resumable, resume_token, custom)
969
970        return obj
971
972    def marshal(self):
973        """
974        Marshal this object into a raw message for subsequent serialization to bytes.
975
976        :returns: The serialized raw message.
977        :rtype: list
978        """
979        details = {}
980        details.update(self.custom)
981
982        if self.realm:
983            details[u'realm'] = self.realm
984
985        if self.authid:
986            details[u'authid'] = self.authid
987
988        if self.authrole:
989            details[u'authrole'] = self.authrole
990
991        if self.authrole:
992            details[u'authmethod'] = self.authmethod
993
994        if self.authprovider:
995            details[u'authprovider'] = self.authprovider
996
997        if self.authextra:
998            details[u'authextra'] = self.authextra
999
1000        if self.resumed:
1001            details[u'resumed'] = self.resumed
1002
1003        if self.resumable:
1004            details[u'resumable'] = self.resumable
1005
1006        if self.resume_token:
1007            details[u'resume_token'] = self.resume_token
1008
1009        details[u'roles'] = {}
1010        for role in self.roles.values():
1011            details[u'roles'][role.ROLE] = {}
1012            for feature in role.__dict__:
1013                if not feature.startswith('_') and feature != 'ROLE' and getattr(role, feature) is not None:
1014                    if u'features' not in details[u'roles'][role.ROLE]:
1015                        details[u'roles'][role.ROLE] = {u'features': {}}
1016                    details[u'roles'][role.ROLE][u'features'][six.u(feature)] = getattr(role, feature)
1017
1018        return [Welcome.MESSAGE_TYPE, self.session, details]
1019
1020    def __str__(self):
1021        """
1022        Returns string representation of this message.
1023        """
1024        return u"Welcome(session={}, roles={}, realm={}, authid={}, authrole={}, authmethod={}, authprovider={}, authextra={}, resumed={}, resumable={}, resume_token={})".format(self.session, self.roles, self.realm, self.authid, self.authrole, self.authmethod, self.authprovider, self.authextra, self.resumed, self.resumable, self.resume_token)
1025
1026
1027class Abort(Message):
1028    """
1029    A WAMP ``ABORT`` message.
1030
1031    Format: ``[ABORT, Details|dict, Reason|uri]``
1032    """
1033
1034    MESSAGE_TYPE = 3
1035    """
1036    The WAMP message code for this type of message.
1037    """
1038
1039    __slots__ = (
1040        'reason',
1041        'message',
1042    )
1043
1044    def __init__(self, reason, message=None):
1045        """
1046
1047        :param reason: WAMP or application error URI for aborting reason.
1048        :type reason: str
1049
1050        :param message: Optional human-readable closing message, e.g. for logging purposes.
1051        :type message: str or None
1052        """
1053        assert(type(reason) == six.text_type)
1054        assert(message is None or type(message) == six.text_type)
1055
1056        Message.__init__(self)
1057        self.reason = reason
1058        self.message = message
1059
1060    @staticmethod
1061    def parse(wmsg):
1062        """
1063        Verifies and parses an unserialized raw message into an actual WAMP message instance.
1064
1065        :param wmsg: The unserialized raw message.
1066        :type wmsg: list
1067
1068        :returns: An instance of this class.
1069        """
1070        # this should already be verified by WampSerializer.unserialize
1071        assert(len(wmsg) > 0 and wmsg[0] == Abort.MESSAGE_TYPE)
1072
1073        if len(wmsg) != 3:
1074            raise ProtocolError("invalid message length {0} for ABORT".format(len(wmsg)))
1075
1076        details = check_or_raise_extra(wmsg[1], u"'details' in ABORT")
1077        reason = check_or_raise_uri(wmsg[2], u"'reason' in ABORT")
1078
1079        message = None
1080
1081        if u'message' in details:
1082
1083            details_message = details[u'message']
1084            if type(details_message) != six.text_type:
1085                raise ProtocolError("invalid type {0} for 'message' detail in ABORT".format(type(details_message)))
1086
1087            message = details_message
1088
1089        obj = Abort(reason, message)
1090
1091        return obj
1092
1093    def marshal(self):
1094        """
1095        Marshal this object into a raw message for subsequent serialization to bytes.
1096
1097        :returns: The serialized raw message.
1098        :rtype: list
1099        """
1100        details = {}
1101        if self.message:
1102            details[u'message'] = self.message
1103
1104        return [Abort.MESSAGE_TYPE, details, self.reason]
1105
1106    def __str__(self):
1107        """
1108        Returns string representation of this message.
1109        """
1110        return u"Abort(message={0}, reason={1})".format(self.message, self.reason)
1111
1112
1113class Challenge(Message):
1114    """
1115    A WAMP ``CHALLENGE`` message.
1116
1117    Format: ``[CHALLENGE, Method|string, Extra|dict]``
1118    """
1119
1120    MESSAGE_TYPE = 4
1121    """
1122    The WAMP message code for this type of message.
1123    """
1124
1125    __slots__ = (
1126        'method',
1127        'extra',
1128    )
1129
1130    def __init__(self, method, extra=None):
1131        """
1132
1133        :param method: The authentication method.
1134        :type method: str
1135
1136        :param extra: Authentication method specific information.
1137        :type extra: dict or None
1138        """
1139        assert(type(method) == six.text_type)
1140        assert(extra is None or type(extra) == dict)
1141
1142        Message.__init__(self)
1143        self.method = method
1144        self.extra = extra or {}
1145
1146    @staticmethod
1147    def parse(wmsg):
1148        """
1149        Verifies and parses an unserialized raw message into an actual WAMP message instance.
1150
1151        :param wmsg: The unserialized raw message.
1152        :type wmsg: list
1153
1154        :returns: An instance of this class.
1155        """
1156        # this should already be verified by WampSerializer.unserialize
1157        assert(len(wmsg) > 0 and wmsg[0] == Challenge.MESSAGE_TYPE)
1158
1159        if len(wmsg) != 3:
1160            raise ProtocolError("invalid message length {0} for CHALLENGE".format(len(wmsg)))
1161
1162        method = wmsg[1]
1163        if type(method) != six.text_type:
1164            raise ProtocolError("invalid type {0} for 'method' in CHALLENGE".format(type(method)))
1165
1166        extra = check_or_raise_extra(wmsg[2], u"'extra' in CHALLENGE")
1167
1168        obj = Challenge(method, extra)
1169
1170        return obj
1171
1172    def marshal(self):
1173        """
1174        Marshal this object into a raw message for subsequent serialization to bytes.
1175
1176        :returns: The serialized raw message.
1177        :rtype: list
1178        """
1179        return [Challenge.MESSAGE_TYPE, self.method, self.extra]
1180
1181    def __str__(self):
1182        """
1183        Returns string representation of this message.
1184        """
1185        return u"Challenge(method={0}, extra={1})".format(self.method, self.extra)
1186
1187
1188class Authenticate(Message):
1189    """
1190    A WAMP ``AUTHENTICATE`` message.
1191
1192    Format: ``[AUTHENTICATE, Signature|string, Extra|dict]``
1193    """
1194
1195    MESSAGE_TYPE = 5
1196    """
1197    The WAMP message code for this type of message.
1198    """
1199
1200    __slots__ = (
1201        'signature',
1202        'extra',
1203    )
1204
1205    def __init__(self, signature, extra=None):
1206        """
1207
1208        :param signature: The signature for the authentication challenge.
1209        :type signature: str
1210
1211        :param extra: Authentication method specific information.
1212        :type extra: dict or None
1213        """
1214        assert(type(signature) == six.text_type)
1215        assert(extra is None or type(extra) == dict)
1216
1217        Message.__init__(self)
1218        self.signature = signature
1219        self.extra = extra or {}
1220
1221    @staticmethod
1222    def parse(wmsg):
1223        """
1224        Verifies and parses an unserialized raw message into an actual WAMP message instance.
1225
1226        :param wmsg: The unserialized raw message.
1227        :type wmsg: list
1228
1229        :returns: An instance of this class.
1230        """
1231        # this should already be verified by WampSerializer.unserialize
1232        assert(len(wmsg) > 0 and wmsg[0] == Authenticate.MESSAGE_TYPE)
1233
1234        if len(wmsg) != 3:
1235            raise ProtocolError("invalid message length {0} for AUTHENTICATE".format(len(wmsg)))
1236
1237        signature = wmsg[1]
1238        if type(signature) != six.text_type:
1239            raise ProtocolError("invalid type {0} for 'signature' in AUTHENTICATE".format(type(signature)))
1240
1241        extra = check_or_raise_extra(wmsg[2], u"'extra' in AUTHENTICATE")
1242
1243        obj = Authenticate(signature, extra)
1244
1245        return obj
1246
1247    def marshal(self):
1248        """
1249        Marshal this object into a raw message for subsequent serialization to bytes.
1250
1251        :returns: The serialized raw message.
1252        :rtype: list
1253        """
1254        return [Authenticate.MESSAGE_TYPE, self.signature, self.extra]
1255
1256    def __str__(self):
1257        """
1258        Returns string representation of this message.
1259        """
1260        return u"Authenticate(signature={0}, extra={1})".format(self.signature, self.extra)
1261
1262
1263class Goodbye(Message):
1264    """
1265    A WAMP ``GOODBYE`` message.
1266
1267    Format: ``[GOODBYE, Details|dict, Reason|uri]``
1268    """
1269
1270    MESSAGE_TYPE = 6
1271    """
1272    The WAMP message code for this type of message.
1273    """
1274
1275    DEFAULT_REASON = u"wamp.close.normal"
1276    """
1277    Default WAMP closing reason.
1278    """
1279
1280    __slots__ = (
1281        'reason',
1282        'message',
1283        'resumable',
1284    )
1285
1286    def __init__(self, reason=DEFAULT_REASON, message=None, resumable=None):
1287        """
1288
1289        :param reason: Optional WAMP or application error URI for closing reason.
1290        :type reason: str
1291
1292        :param message: Optional human-readable closing message, e.g. for logging purposes.
1293        :type message: str or None
1294
1295        :param resumable: From the server: Whether the session is able to be resumed (true) or destroyed (false). From the client: Whether it should be resumable (true) or destroyed (false).
1296        :type resumable: bool or None
1297        """
1298        assert(type(reason) == six.text_type)
1299        assert(message is None or type(message) == six.text_type)
1300        assert(resumable is None or type(resumable) == bool)
1301
1302        Message.__init__(self)
1303        self.reason = reason
1304        self.message = message
1305        self.resumable = resumable
1306
1307    @staticmethod
1308    def parse(wmsg):
1309        """
1310        Verifies and parses an unserialized raw message into an actual WAMP message instance.
1311
1312        :param wmsg: The unserialized raw message.
1313        :type wmsg: list
1314
1315        :returns: An instance of this class.
1316        """
1317        # this should already be verified by WampSerializer.unserialize
1318        assert(len(wmsg) > 0 and wmsg[0] == Goodbye.MESSAGE_TYPE)
1319
1320        if len(wmsg) != 3:
1321            raise ProtocolError("invalid message length {0} for GOODBYE".format(len(wmsg)))
1322
1323        details = check_or_raise_extra(wmsg[1], u"'details' in GOODBYE")
1324        reason = check_or_raise_uri(wmsg[2], u"'reason' in GOODBYE")
1325
1326        message = None
1327        resumable = None
1328
1329        if u'message' in details:
1330
1331            details_message = details[u'message']
1332            if type(details_message) != six.text_type:
1333                raise ProtocolError("invalid type {0} for 'message' detail in GOODBYE".format(type(details_message)))
1334
1335            message = details_message
1336
1337        if u'resumable' in details:
1338            resumable = details[u'resumable']
1339            if type(resumable) != bool:
1340                raise ProtocolError("invalid type {0} for 'resumable' detail in GOODBYE".format(type(resumable)))
1341
1342        obj = Goodbye(reason=reason,
1343                      message=message,
1344                      resumable=resumable)
1345
1346        return obj
1347
1348    def marshal(self):
1349        """
1350        Marshal this object into a raw message for subsequent serialization to bytes.
1351
1352        :returns: The serialized raw message.
1353        :rtype: list
1354        """
1355        details = {}
1356        if self.message:
1357            details[u'message'] = self.message
1358
1359        if self.resumable:
1360            details[u'resumable'] = self.resumable
1361
1362        return [Goodbye.MESSAGE_TYPE, details, self.reason]
1363
1364    def __str__(self):
1365        """
1366        Returns string representation of this message.
1367        """
1368        return u"Goodbye(message={}, reason={}, resumable={})".format(self.message, self.reason, self.resumable)
1369
1370
1371class Error(Message):
1372    """
1373    A WAMP ``ERROR`` message.
1374
1375    Formats:
1376
1377    * ``[ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri]``
1378    * ``[ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri, Arguments|list]``
1379    * ``[ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri, Arguments|list, ArgumentsKw|dict]``
1380    * ``[ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri, Payload|binary]``
1381    """
1382
1383    MESSAGE_TYPE = 8
1384    """
1385    The WAMP message code for this type of message.
1386    """
1387
1388    __slots__ = (
1389        'request_type',
1390        'request',
1391        'error',
1392        'args',
1393        'kwargs',
1394        'payload',
1395        'enc_algo',
1396        'enc_key',
1397        'enc_serializer',
1398        'callee',
1399        'callee_authid',
1400        'callee_authrole',
1401        'forward_for',
1402    )
1403
1404    def __init__(self,
1405                 request_type,
1406                 request,
1407                 error,
1408                 args=None,
1409                 kwargs=None,
1410                 payload=None,
1411                 enc_algo=None,
1412                 enc_key=None,
1413                 enc_serializer=None,
1414                 callee=None,
1415                 callee_authid=None,
1416                 callee_authrole=None,
1417                 forward_for=None):
1418        """
1419
1420        :param request_type: The WAMP message type code for the original request.
1421        :type request_type: int
1422
1423        :param request: The WAMP request ID of the original request (`Call`, `Subscribe`, ...) this error occurred for.
1424        :type request: int
1425
1426        :param error: The WAMP or application error URI for the error that occurred.
1427        :type error: str
1428
1429        :param args: Positional values for application-defined exception.
1430           Must be serializable using any serializers in use.
1431        :type args: list or None
1432
1433        :param kwargs: Keyword values for application-defined exception.
1434           Must be serializable using any serializers in use.
1435        :type kwargs: dict or None
1436
1437        :param payload: Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
1438        :type payload: bytes or None
1439
1440        :param enc_algo: If using payload transparency, the encoding algorithm that was used to encode the payload.
1441        :type enc_algo: str or None
1442
1443        :param enc_key: If using payload transparency with an encryption algorithm, the payload encryption key.
1444        :type enc_key: str or None
1445
1446        :param enc_serializer: If using payload transparency, the payload object serializer that was used encoding the payload.
1447        :type enc_serializer: str or None
1448
1449        :param callee: The WAMP session ID of the effective callee that responded with the error. Only filled if callee is disclosed.
1450        :type callee: None or int
1451
1452        :param callee_authid: The WAMP authid of the responding callee. Only filled if callee is disclosed.
1453        :type callee_authid: None or unicode
1454
1455        :param callee_authrole: The WAMP authrole of the responding callee. Only filled if callee is disclosed.
1456        :type callee_authrole: None or unicode
1457
1458        :param forward_for: When this Error is forwarded for a client/callee (or from an intermediary router).
1459        :type forward_for: list[dict]
1460        """
1461        assert(type(request_type) in six.integer_types)
1462        assert(type(request) in six.integer_types)
1463        assert(type(error) == six.text_type)
1464        assert(args is None or type(args) in [list, tuple])
1465        assert(kwargs is None or type(kwargs) == dict)
1466        assert(payload is None or type(payload) == six.binary_type)
1467        assert(payload is None or (payload is not None and args is None and kwargs is None))
1468
1469        assert(enc_algo is None or is_valid_enc_algo(enc_algo))
1470        assert((enc_algo is None and enc_key is None and enc_serializer is None) or (payload is not None and enc_algo is not None))
1471        assert(enc_key is None or type(enc_key) == six.text_type)
1472        assert(enc_serializer is None or is_valid_enc_serializer(enc_serializer))
1473
1474        assert(callee is None or type(callee) in six.integer_types)
1475        assert(callee_authid is None or type(callee_authid) == six.text_type)
1476        assert(callee_authrole is None or type(callee_authrole) == six.text_type)
1477
1478        assert(forward_for is None or type(forward_for) == list)
1479        if forward_for:
1480            for ff in forward_for:
1481                assert type(ff) == dict
1482                assert 'session' in ff and type(ff['session']) in six.integer_types
1483                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
1484                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
1485
1486        Message.__init__(self)
1487        self.request_type = request_type
1488        self.request = request
1489        self.error = error
1490        self.args = args
1491        self.kwargs = _validate_kwargs(kwargs)
1492        self.payload = payload
1493
1494        # payload transparency related knobs
1495        self.enc_algo = enc_algo
1496        self.enc_key = enc_key
1497        self.enc_serializer = enc_serializer
1498
1499        # effective callee that responded with the error
1500        self.callee = callee
1501        self.callee_authid = callee_authid
1502        self.callee_authrole = callee_authrole
1503
1504        # message forwarding
1505        self.forward_for = forward_for
1506
1507    @staticmethod
1508    def parse(wmsg):
1509        """
1510        Verifies and parses an unserialized raw message into an actual WAMP message instance.
1511
1512        :param wmsg: The unserialized raw message.
1513        :type wmsg: list
1514
1515        :returns: An instance of this class.
1516        """
1517        # this should already be verified by WampSerializer.unserialize
1518        assert(len(wmsg) > 0 and wmsg[0] == Error.MESSAGE_TYPE)
1519
1520        if len(wmsg) not in (5, 6, 7):
1521            raise ProtocolError("invalid message length {0} for ERROR".format(len(wmsg)))
1522
1523        request_type = wmsg[1]
1524        if type(request_type) not in six.integer_types:
1525            raise ProtocolError("invalid type {0} for 'request_type' in ERROR".format(request_type))
1526
1527        if request_type not in [Subscribe.MESSAGE_TYPE,
1528                                Unsubscribe.MESSAGE_TYPE,
1529                                Publish.MESSAGE_TYPE,
1530                                Register.MESSAGE_TYPE,
1531                                Unregister.MESSAGE_TYPE,
1532                                Call.MESSAGE_TYPE,
1533                                Invocation.MESSAGE_TYPE]:
1534            raise ProtocolError("invalid value {0} for 'request_type' in ERROR".format(request_type))
1535
1536        request = check_or_raise_id(wmsg[2], u"'request' in ERROR")
1537        details = check_or_raise_extra(wmsg[3], u"'details' in ERROR")
1538        error = check_or_raise_uri(wmsg[4], u"'error' in ERROR")
1539
1540        args = None
1541        kwargs = None
1542        payload = None
1543        enc_algo = None
1544        enc_key = None
1545        enc_serializer = None
1546        callee = None
1547        callee_authid = None
1548        callee_authrole = None
1549        forward_for = None
1550
1551        if len(wmsg) == 6 and type(wmsg[5]) == six.binary_type:
1552
1553            payload = wmsg[5]
1554
1555            enc_algo = details.get(u'enc_algo', None)
1556            if enc_algo and not is_valid_enc_algo(enc_algo):
1557                raise ProtocolError("invalid value {0} for 'enc_algo' detail in EVENT".format(enc_algo))
1558
1559            enc_key = details.get(u'enc_key', None)
1560            if enc_key and type(enc_key) != six.text_type:
1561                raise ProtocolError("invalid type {0} for 'enc_key' detail in EVENT".format(type(enc_key)))
1562
1563            enc_serializer = details.get(u'enc_serializer', None)
1564            if enc_serializer and not is_valid_enc_serializer(enc_serializer):
1565                raise ProtocolError("invalid value {0} for 'enc_serializer' detail in EVENT".format(enc_serializer))
1566
1567        else:
1568            if len(wmsg) > 5:
1569                args = wmsg[5]
1570                if args is not None and type(args) != list:
1571                    raise ProtocolError("invalid type {0} for 'args' in ERROR".format(type(args)))
1572
1573            if len(wmsg) > 6:
1574                kwargs = wmsg[6]
1575                if type(kwargs) != dict:
1576                    raise ProtocolError("invalid type {0} for 'kwargs' in ERROR".format(type(kwargs)))
1577
1578        if u'callee' in details:
1579
1580            detail_callee = details[u'callee']
1581            if type(detail_callee) not in six.integer_types:
1582                raise ProtocolError("invalid type {0} for 'callee' detail in ERROR".format(type(detail_callee)))
1583
1584            callee = detail_callee
1585
1586        if u'callee_authid' in details:
1587
1588            detail_callee_authid = details[u'callee_authid']
1589            if type(detail_callee_authid) != six.text_type:
1590                raise ProtocolError("invalid type {0} for 'callee_authid' detail in ERROR".format(type(detail_callee_authid)))
1591
1592            callee_authid = detail_callee_authid
1593
1594        if u'callee_authrole' in details:
1595
1596            detail_callee_authrole = details[u'callee_authrole']
1597            if type(detail_callee_authrole) != six.text_type:
1598                raise ProtocolError("invalid type {0} for 'callee_authrole' detail in ERROR".format(type(detail_callee_authrole)))
1599
1600            callee_authrole = detail_callee_authrole
1601
1602        if u'forward_for' in details:
1603            forward_for = details[u'forward_for']
1604            valid = False
1605            if type(forward_for) == list:
1606                for ff in forward_for:
1607                    if type(ff) != dict:
1608                        break
1609                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
1610                        break
1611                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
1612                        break
1613                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
1614                        break
1615                valid = True
1616
1617            if not valid:
1618                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in ERROR")
1619
1620        obj = Error(request_type,
1621                    request,
1622                    error,
1623                    args=args,
1624                    kwargs=kwargs,
1625                    payload=payload,
1626                    enc_algo=enc_algo,
1627                    enc_key=enc_key,
1628                    enc_serializer=enc_serializer,
1629                    callee=callee,
1630                    callee_authid=callee_authid,
1631                    callee_authrole=callee_authrole,
1632                    forward_for=forward_for)
1633
1634        return obj
1635
1636    def marshal(self):
1637        """
1638        Marshal this object into a raw message for subsequent serialization to bytes.
1639
1640        :returns: The serialized raw message.
1641        :rtype: list
1642        """
1643        details = {}
1644
1645        if self.callee is not None:
1646            details[u'callee'] = self.callee
1647        if self.callee_authid is not None:
1648            details[u'callee_authid'] = self.callee_authid
1649        if self.callee_authrole is not None:
1650            details[u'callee_authrole'] = self.callee_authrole
1651        if self.forward_for is not None:
1652            details[u'forward_for'] = self.forward_for
1653
1654        if self.payload:
1655            if self.enc_algo is not None:
1656                details[u'enc_algo'] = self.enc_algo
1657            if self.enc_key is not None:
1658                details[u'enc_key'] = self.enc_key
1659            if self.enc_serializer is not None:
1660                details[u'enc_serializer'] = self.enc_serializer
1661            return [self.MESSAGE_TYPE, self.request_type, self.request, details, self.error, self.payload]
1662        else:
1663            if self.kwargs:
1664                return [self.MESSAGE_TYPE, self.request_type, self.request, details, self.error, self.args, self.kwargs]
1665            elif self.args:
1666                return [self.MESSAGE_TYPE, self.request_type, self.request, details, self.error, self.args]
1667            else:
1668                return [self.MESSAGE_TYPE, self.request_type, self.request, details, self.error]
1669
1670    def __str__(self):
1671        """
1672        Returns string representation of this message.
1673        """
1674        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}, callee={9}, callee_authid={10}, callee_authrole={11}, forward_for={12})".format(self.request_type, self.request, self.error, self.args, self.kwargs, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload), self.callee, self.callee_authid, self.callee_authrole, self.forward_for)
1675
1676
1677class Publish(Message):
1678    """
1679    A WAMP ``PUBLISH`` message.
1680
1681    Formats:
1682
1683    * ``[PUBLISH, Request|id, Options|dict, Topic|uri]``
1684    * ``[PUBLISH, Request|id, Options|dict, Topic|uri, Arguments|list]``
1685    * ``[PUBLISH, Request|id, Options|dict, Topic|uri, Arguments|list, ArgumentsKw|dict]``
1686    * ``[PUBLISH, Request|id, Options|dict, Topic|uri, Payload|binary]``
1687    """
1688
1689    MESSAGE_TYPE = 16
1690    """
1691    The WAMP message code for this type of message.
1692    """
1693
1694    __slots__ = (
1695        # uint64 (key)
1696        '_request',
1697
1698        # string (required, uri)
1699        '_topic',
1700
1701        # [uint8]
1702        '_args',
1703
1704        # [uint8]
1705        '_kwargs',
1706
1707        # [uint8]
1708        '_payload',
1709
1710        # Payload => uint8
1711        '_enc_algo',
1712
1713        # Serializer => uint8
1714        '_enc_serializer',
1715
1716        # [uint8]
1717        '_enc_key',
1718
1719        # bool
1720        '_acknowledge',
1721
1722        # bool
1723        '_exclude_me',
1724
1725        # [uint64]
1726        '_exclude',
1727
1728        # [string] (principal)
1729        '_exclude_authid',
1730
1731        # [string] (principal)
1732        '_exclude_authrole',
1733
1734        # [uint64]
1735        '_eligible',
1736
1737        # [string] (principal)
1738        '_eligible_authid',
1739
1740        # [string] (principal)
1741        '_eligible_authrole',
1742
1743        # bool
1744        '_retain',
1745
1746        # [Principal]
1747        '_forward_for',
1748    )
1749
1750    def __init__(self,
1751                 request=None,
1752                 topic=None,
1753                 args=None,
1754                 kwargs=None,
1755                 payload=None,
1756                 acknowledge=None,
1757                 exclude_me=None,
1758                 exclude=None,
1759                 exclude_authid=None,
1760                 exclude_authrole=None,
1761                 eligible=None,
1762                 eligible_authid=None,
1763                 eligible_authrole=None,
1764                 retain=None,
1765                 enc_algo=None,
1766                 enc_key=None,
1767                 enc_serializer=None,
1768                 forward_for=None,
1769                 from_fbs=None):
1770        """
1771
1772        :param request: The WAMP request ID of this request.
1773        :type request: int
1774
1775        :param topic: The WAMP or application URI of the PubSub topic the event should
1776           be published to.
1777        :type topic: str
1778
1779        :param args: Positional values for application-defined event payload.
1780           Must be serializable using any serializers in use.
1781        :type args: list or tuple or None
1782
1783        :param kwargs: Keyword values for application-defined event payload.
1784           Must be serializable using any serializers in use.
1785        :type kwargs: dict or None
1786
1787        :param payload: Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
1788        :type payload: bytes or None
1789
1790        :param acknowledge: If True, acknowledge the publication with a success or
1791           error response.
1792        :type acknowledge: bool or None
1793
1794        :param exclude_me: If ``True``, exclude the publisher from receiving the event, even
1795           if he is subscribed (and eligible).
1796        :type exclude_me: bool or None
1797
1798        :param exclude: List of WAMP session IDs to exclude from receiving this event.
1799        :type exclude: list of int or None
1800
1801        :param exclude_authid: List of WAMP authids to exclude from receiving this event.
1802        :type exclude_authid: list of str or None
1803
1804        :param exclude_authrole: List of WAMP authroles to exclude from receiving this event.
1805        :type exclude_authrole: list of str or None
1806
1807        :param eligible: List of WAMP session IDs eligible to receive this event.
1808        :type eligible: list of int or None
1809
1810        :param eligible_authid: List of WAMP authids eligible to receive this event.
1811        :type eligible_authid: list of str or None
1812
1813        :param eligible_authrole: List of WAMP authroles eligible to receive this event.
1814        :type eligible_authrole: list of str or None
1815
1816        :param retain: If ``True``, request the broker retain this event.
1817        :type retain: bool or None
1818
1819        :param enc_algo: If using payload transparency, the encoding algorithm that was used to encode the payload.
1820        :type enc_algo: str or None
1821
1822        :param enc_key: If using payload transparency with an encryption algorithm, the payload encryption key.
1823        :type enc_key: str or None
1824
1825        :param enc_serializer: If using payload transparency, the payload object serializer that was used encoding the payload.
1826        :type enc_serializer: str or None or None
1827
1828        :param forward_for: When this Call is forwarded for a client (or from an intermediary router).
1829        :type forward_for: list[dict]
1830        """
1831        assert(request is None or type(request) in six.integer_types)
1832        assert(topic is None or type(topic) == six.text_type)
1833        assert(args is None or type(args) in [list, tuple, six.text_type, six.binary_type])
1834        assert(kwargs is None or type(kwargs) in [dict, six.text_type, six.binary_type])
1835        assert(payload is None or type(payload) == six.binary_type)
1836        assert(payload is None or (payload is not None and args is None and kwargs is None))
1837        assert(acknowledge is None or type(acknowledge) == bool)
1838        assert(retain is None or type(retain) == bool)
1839
1840        # publisher exlusion and black-/whitelisting
1841        assert(exclude_me is None or type(exclude_me) == bool)
1842
1843        assert(exclude is None or type(exclude) == list)
1844        if exclude:
1845            for sessionid in exclude:
1846                assert(type(sessionid) in six.integer_types)
1847
1848        assert(exclude_authid is None or type(exclude_authid) == list)
1849        if exclude_authid:
1850            for authid in exclude_authid:
1851                assert(type(authid) == six.text_type)
1852
1853        assert(exclude_authrole is None or type(exclude_authrole) == list)
1854        if exclude_authrole:
1855            for authrole in exclude_authrole:
1856                assert(type(authrole) == six.text_type)
1857
1858        assert(eligible is None or type(eligible) == list)
1859        if eligible:
1860            for sessionid in eligible:
1861                assert(type(sessionid) in six.integer_types)
1862
1863        assert(eligible_authid is None or type(eligible_authid) == list)
1864        if eligible_authid:
1865            for authid in eligible_authid:
1866                assert(type(authid) == six.text_type)
1867
1868        assert(eligible_authrole is None or type(eligible_authrole) == list)
1869        if eligible_authrole:
1870            for authrole in eligible_authrole:
1871                assert(type(authrole) == six.text_type)
1872
1873        assert(enc_algo is None or is_valid_enc_algo(enc_algo))
1874        assert((enc_algo is None and enc_key is None and enc_serializer is None) or (payload is not None and enc_algo is not None))
1875        assert(enc_key is None or type(enc_key) == six.text_type)
1876        assert(enc_serializer is None or is_valid_enc_serializer(enc_serializer))
1877
1878        assert(forward_for is None or type(forward_for) == list)
1879        if forward_for:
1880            for ff in forward_for:
1881                assert type(ff) == dict
1882                assert 'session' in ff and type(ff['session']) in six.integer_types
1883                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
1884                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
1885
1886        Message.__init__(self, from_fbs=from_fbs)
1887        self._request = request
1888        self._topic = topic
1889        self._args = args
1890        self._kwargs = _validate_kwargs(kwargs)
1891        self._payload = payload
1892        self._acknowledge = acknowledge
1893
1894        # publisher exlusion and black-/whitelisting
1895        self._exclude_me = exclude_me
1896        self._exclude = exclude
1897        self._exclude_authid = exclude_authid
1898        self._exclude_authrole = exclude_authrole
1899        self._eligible = eligible
1900        self._eligible_authid = eligible_authid
1901        self._eligible_authrole = eligible_authrole
1902
1903        # event retention
1904        self._retain = retain
1905
1906        # payload transparency related knobs
1907        self._enc_algo = enc_algo
1908        self._enc_key = enc_key
1909        self._enc_serializer = enc_serializer
1910
1911        # message forwarding
1912        self._forward_for = forward_for
1913
1914    def __eq__(self, other):
1915        if not isinstance(other, self.__class__):
1916            return False
1917        if not Message.__eq__(self, other):
1918            return False
1919        if other.request != self.request:
1920            return False
1921        if other.topic != self.topic:
1922            return False
1923        if other.args != self.args:
1924            return False
1925        if other.kwargs != self.kwargs:
1926            return False
1927        if other.payload != self.payload:
1928            return False
1929        if other.acknowledge != self.acknowledge:
1930            return False
1931        if other.exclude_me != self.exclude_me:
1932            return False
1933        if other.exclude != self.exclude:
1934            return False
1935        if other.exclude_authid != self.exclude_authid:
1936            return False
1937        if other.exclude_authrole != self.exclude_authrole:
1938            return False
1939        if other.eligible != self.eligible:
1940            return False
1941        if other.eligible_authid != self.eligible_authid:
1942            return False
1943        if other.eligible_authrole != self.eligible_authrole:
1944            return False
1945        if other.retain != self.retain:
1946            return False
1947        if other.enc_algo != self.enc_algo:
1948            return False
1949        if other.enc_key != self.enc_key:
1950            return False
1951        if other.enc_serializer != self.enc_serializer:
1952            return False
1953        if other.forward_for != self.forward_for:
1954            return False
1955        return True
1956
1957    def __ne__(self, other):
1958        return not self.__eq__(other)
1959
1960    @property
1961    def request(self):
1962        if self._request is None and self._from_fbs:
1963            self._request = self._from_fbs.Request()
1964        return self._request
1965
1966    @request.setter
1967    def request(self, value):
1968        assert(value is None or type(value) in six.integer_types)
1969        self._request = value
1970
1971    @property
1972    def topic(self):
1973        if self._topic is None and self._from_fbs:
1974            s = self._from_fbs.Topic()
1975            if s:
1976                self._topic = s.decode('utf8')
1977        return self._topic
1978
1979    @topic.setter
1980    def topic(self, value):
1981        assert value is None or type(value) == str
1982        self._topic = value
1983
1984    @property
1985    def args(self):
1986        if self._args is None and self._from_fbs:
1987            if self._from_fbs.ArgsLength():
1988                self._args = cbor.loads(bytes(self._from_fbs.ArgsAsBytes()))
1989        return self._args
1990
1991    @args.setter
1992    def args(self, value):
1993        assert(value is None or type(value) in [list, tuple])
1994        self._args = value
1995
1996    @property
1997    def kwargs(self):
1998        if self._kwargs is None and self._from_fbs:
1999            if self._from_fbs.KwargsLength():
2000                self._kwargs = cbor.loads(bytes(self._from_fbs.KwargsAsBytes()))
2001        return self._kwargs
2002
2003    @kwargs.setter
2004    def kwargs(self, value):
2005        assert(value is None or type(value) == dict)
2006        self._kwargs = value
2007
2008    @property
2009    def payload(self):
2010        if self._payload is None and self._from_fbs:
2011            if self._from_fbs.PayloadLength():
2012                self._payload = self._from_fbs.PayloadAsBytes()
2013        return self._payload
2014
2015    @payload.setter
2016    def payload(self, value):
2017        assert value is None or type(value) == bytes
2018        self._payload = value
2019
2020    @property
2021    def acknowledge(self):
2022        if self._acknowledge is None and self._from_fbs:
2023            acknowledge = self._from_fbs.Acknowledge()
2024            if acknowledge:
2025                self._acknowledge = acknowledge
2026        return self._acknowledge
2027
2028    @acknowledge.setter
2029    def acknowledge(self, value):
2030        assert value is None or type(value) == bool
2031        self._acknowledge = value
2032
2033    @property
2034    def exclude_me(self):
2035        if self._exclude_me is None and self._from_fbs:
2036            exclude_me = self._from_fbs.ExcludeMe()
2037            if exclude_me is False:
2038                self._exclude_me = exclude_me
2039        return self._exclude_me
2040
2041    @exclude_me.setter
2042    def exclude_me(self, value):
2043        assert value is None or type(value) == bool
2044        self._exclude_me = value
2045
2046    @property
2047    def exclude(self):
2048        if self._exclude is None and self._from_fbs:
2049            if self._from_fbs.ExcludeLength():
2050                exclude = []
2051                for j in range(self._from_fbs.ExcludeLength()):
2052                    exclude.append(self._from_fbs.Exclude(j))
2053                self._exclude = exclude
2054        return self._exclude
2055
2056    @exclude.setter
2057    def exclude(self, value):
2058        assert value is None or type(value) == list
2059        if value:
2060            for x in value:
2061                assert type(x) == int
2062        self._exclude = value
2063
2064    @property
2065    def exclude_authid(self):
2066        if self._exclude_authid is None and self._from_fbs:
2067            if self._from_fbs.ExcludeAuthidLength():
2068                exclude_authid = []
2069                for j in range(self._from_fbs.ExcludeAuthidLength()):
2070                    exclude_authid.append(self._from_fbs.ExcludeAuthid(j).decode('utf8'))
2071                self._exclude_authid = exclude_authid
2072        return self._exclude_authid
2073
2074    @exclude_authid.setter
2075    def exclude_authid(self, value):
2076        assert value is None or type(value) == list
2077        if value:
2078            for x in value:
2079                assert type(x) == str
2080        self._exclude_authid = value
2081
2082    @property
2083    def exclude_authrole(self):
2084        if self._exclude_authrole is None and self._from_fbs:
2085            if self._from_fbs.ExcludeAuthroleLength():
2086                exclude_authrole = []
2087                for j in range(self._from_fbs.ExcludeAuthroleLength()):
2088                    exclude_authrole.append(self._from_fbs.ExcludeAuthrole(j).decode('utf8'))
2089                self._exclude_authrole = exclude_authrole
2090        return self._exclude_authrole
2091
2092    @exclude_authrole.setter
2093    def exclude_authrole(self, value):
2094        assert value is None or type(value) == list
2095        if value:
2096            for x in value:
2097                assert type(x) == str
2098        self._exclude_authrole = value
2099
2100    @property
2101    def eligible(self):
2102        if self._eligible is None and self._from_fbs:
2103            if self._from_fbs.EligibleLength():
2104                eligible = []
2105                for j in range(self._from_fbs.EligibleLength()):
2106                    eligible.append(self._from_fbs.Eligible(j))
2107                self._eligible = eligible
2108        return self._eligible
2109
2110    @eligible.setter
2111    def eligible(self, value):
2112        assert value is None or type(value) == list
2113        if value:
2114            for x in value:
2115                assert type(x) == int
2116        self._eligible = value
2117
2118    @property
2119    def eligible_authid(self):
2120        if self._eligible_authid is None and self._from_fbs:
2121            if self._from_fbs.EligibleAuthidLength():
2122                eligible_authid = []
2123                for j in range(self._from_fbs.EligibleAuthidLength()):
2124                    eligible_authid.append(self._from_fbs.EligibleAuthid(j).decode('utf8'))
2125                self._eligible_authid = eligible_authid
2126        return self._eligible_authid
2127
2128    @eligible_authid.setter
2129    def eligible_authid(self, value):
2130        assert value is None or type(value) == list
2131        if value:
2132            for x in value:
2133                assert type(x) == str
2134        self._eligible_authid = value
2135
2136    @property
2137    def eligible_authrole(self):
2138        if self._eligible_authrole is None and self._from_fbs:
2139            if self._from_fbs.EligibleAuthroleLength():
2140                eligible_authrole = []
2141                for j in range(self._from_fbs.EligibleAuthroleLength()):
2142                    eligible_authrole.append(self._from_fbs.EligibleAuthrole(j).decode('utf8'))
2143                self._eligible_authrole = eligible_authrole
2144        return self._eligible_authrole
2145
2146    @eligible_authrole.setter
2147    def eligible_authrole(self, value):
2148        assert value is None or type(value) == list
2149        if value:
2150            for x in value:
2151                assert type(x) == str
2152        self._eligible_authrole = value
2153
2154    @property
2155    def retain(self):
2156        if self._retain is None and self._from_fbs:
2157            retain = self._from_fbs.Retain()
2158            if retain:
2159                self._retain = retain
2160        return self._retain
2161
2162    @retain.setter
2163    def retain(self, value):
2164        assert value is None or type(value) == bool
2165        self._retain = value
2166
2167    @property
2168    def enc_algo(self):
2169        if self._enc_algo is None and self._from_fbs:
2170            enc_algo = self._from_fbs.EncAlgo()
2171            if enc_algo:
2172                self._enc_algo = enc_algo
2173        return self._enc_algo
2174
2175    @enc_algo.setter
2176    def enc_algo(self, value):
2177        assert value is None or value in [ENC_ALGO_CRYPTOBOX, ENC_ALGO_MQTT, ENC_ALGO_XBR]
2178        self._enc_algo = value
2179
2180    @property
2181    def enc_key(self):
2182        if self._enc_key is None and self._from_fbs:
2183            if self._from_fbs.EncKeyLength():
2184                self._enc_key = self._from_fbs.EncKeyAsBytes()
2185        return self._enc_key
2186
2187    @enc_key.setter
2188    def enc_key(self, value):
2189        assert value is None or type(value) == bytes
2190        self._enc_key = value
2191
2192    @property
2193    def enc_serializer(self):
2194        if self._enc_serializer is None and self._from_fbs:
2195            enc_serializer = self._from_fbs.EncSerializer()
2196            if enc_serializer:
2197                self._enc_serializer = enc_serializer
2198        return self._enc_serializer
2199
2200    @enc_serializer.setter
2201    def enc_serializer(self, value):
2202        assert value is None or value in [ENC_SER_JSON, ENC_SER_MSGPACK, ENC_SER_CBOR, ENC_SER_UBJSON]
2203        self._enc_serializer = value
2204
2205    @property
2206    def forward_for(self):
2207        # FIXME
2208        return self._forward_for
2209
2210    @forward_for.setter
2211    def forward_for(self, value):
2212        # FIXME
2213        self._forward_for = value
2214
2215    @staticmethod
2216    def cast(buf):
2217        return Publish(from_fbs=message_fbs.Publish.GetRootAsPublish(buf, 0))
2218
2219    def build(self, builder):
2220
2221        args = self.args
2222        if args:
2223            args = builder.CreateByteVector(cbor.dumps(args))
2224
2225        kwargs = self.kwargs
2226        if kwargs:
2227            kwargs = builder.CreateByteVector(cbor.dumps(kwargs))
2228
2229        payload = self.payload
2230        if payload:
2231            payload = builder.CreateByteVector(payload)
2232
2233        topic = self.topic
2234        if topic:
2235            topic = builder.CreateString(topic)
2236
2237        enc_key = self.enc_key
2238        if enc_key:
2239            enc_key = builder.CreateByteVector(enc_key)
2240
2241        # exclude: [int]
2242        exclude = self.exclude
2243        if exclude:
2244            message_fbs.PublishGen.PublishStartExcludeAuthidVector(builder, len(exclude))
2245            for session in reversed(exclude):
2246                builder.PrependUint64(session)
2247            exclude = builder.EndVector(len(exclude))
2248
2249        # exclude_authid: [string]
2250        exclude_authid = self.exclude_authid
2251        if exclude_authid:
2252            _exclude_authid = []
2253            for authid in exclude_authid:
2254                _exclude_authid.append(builder.CreateString(authid))
2255            message_fbs.PublishGen.PublishStartExcludeAuthidVector(builder, len(_exclude_authid))
2256            for o in reversed(_exclude_authid):
2257                builder.PrependUOffsetTRelative(o)
2258            exclude_authid = builder.EndVector(len(_exclude_authid))
2259
2260        # exclude_authrole: [string]
2261        exclude_authrole = self.exclude_authrole
2262        if exclude_authid:
2263            _exclude_authrole = []
2264            for authrole in exclude_authrole:
2265                _exclude_authrole.append(builder.CreateString(authrole))
2266            message_fbs.PublishGen.PublishStartExcludeAuthroleVector(builder, len(_exclude_authrole))
2267            for o in reversed(_exclude_authrole):
2268                builder.PrependUOffsetTRelative(o)
2269            exclude_authrole = builder.EndVector(len(_exclude_authrole))
2270
2271        # eligible: [int]
2272        eligible = self.eligible
2273        if eligible:
2274            message_fbs.PublishGen.PublishStartEligibleAuthidVector(builder, len(eligible))
2275            for session in reversed(eligible):
2276                builder.PrependUint64(session)
2277            eligible = builder.EndVector(len(eligible))
2278
2279        # eligible_authid: [string]
2280        eligible_authid = self.eligible_authid
2281        if eligible_authid:
2282            _eligible_authid = []
2283            for authid in eligible_authid:
2284                _eligible_authid.append(builder.CreateString(authid))
2285            message_fbs.PublishGen.PublishStartEligibleAuthidVector(builder, len(_eligible_authid))
2286            for o in reversed(_eligible_authid):
2287                builder.PrependUOffsetTRelative(o)
2288            eligible_authid = builder.EndVector(len(_eligible_authid))
2289
2290        # eligible_authrole: [string]
2291        eligible_authrole = self.eligible_authrole
2292        if eligible_authrole:
2293            _eligible_authrole = []
2294            for authrole in eligible_authrole:
2295                _eligible_authrole.append(builder.CreateString(authrole))
2296            message_fbs.PublishGen.PublishStartEligibleAuthroleVector(builder, len(_eligible_authrole))
2297            for o in reversed(_eligible_authrole):
2298                builder.PrependUOffsetTRelative(o)
2299            eligible_authrole = builder.EndVector(len(_eligible_authrole))
2300
2301        # now start and build a new object ..
2302        message_fbs.PublishGen.PublishStart(builder)
2303
2304        if self.request is not None:
2305            message_fbs.PublishGen.PublishAddRequest(builder, self.request)
2306
2307        if topic:
2308            message_fbs.PublishGen.PublishAddTopic(builder, topic)
2309
2310        if args:
2311            message_fbs.PublishGen.PublishAddArgs(builder, args)
2312        if kwargs:
2313            message_fbs.PublishGen.PublishAddKwargs(builder, kwargs)
2314        if payload:
2315            message_fbs.PublishGen.PublishAddPayload(builder, payload)
2316
2317        if self.acknowledge is not None:
2318            message_fbs.PublishGen.PublishAddAcknowledge(builder, self.acknowledge)
2319        if self.retain is not None:
2320            message_fbs.PublishGen.PublishAddRetain(builder, self.retain)
2321        if self.exclude_me is not None:
2322            message_fbs.PublishGen.PublishAddExcludeMe(builder, self.exclude_me)
2323
2324        if exclude:
2325            message_fbs.PublishGen.PublishAddExclude(builder, exclude)
2326        if exclude_authid:
2327            message_fbs.PublishGen.PublishAddExcludeAuthid(builder, exclude_authid)
2328        if exclude_authrole:
2329            message_fbs.PublishGen.PublishAddExcludeAuthrole(builder, exclude_authrole)
2330
2331        if eligible:
2332            message_fbs.PublishGen.PublishAddEligible(builder, eligible)
2333        if eligible_authid:
2334            message_fbs.PublishGen.PublishAddEligibleAuthid(builder, eligible_authid)
2335        if eligible_authrole:
2336            message_fbs.PublishGen.PublishAddEligibleAuthrole(builder, eligible_authrole)
2337
2338        if self.enc_algo:
2339            message_fbs.PublishGen.PublishAddEncAlgo(builder, self.enc_algo)
2340        if enc_key:
2341            message_fbs.PublishGen.PublishAddEncKey(builder, enc_key)
2342        if self.enc_serializer:
2343            message_fbs.PublishGen.PublishAddEncSerializer(builder, self.enc_serializer)
2344
2345        # FIXME: add forward_for
2346
2347        msg = message_fbs.PublishGen.PublishEnd(builder)
2348
2349        message_fbs.Message.MessageStart(builder)
2350        message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.PUBLISH)
2351        message_fbs.Message.MessageAddMsg(builder, msg)
2352        union_msg = message_fbs.Message.MessageEnd(builder)
2353
2354        return union_msg
2355
2356    @staticmethod
2357    def parse(wmsg):
2358        """
2359        Verifies and parses an unserialized raw message into an actual WAMP message instance.
2360
2361        :param wmsg: The unserialized raw message.
2362        :type wmsg: list
2363
2364        :returns: An instance of this class.
2365        """
2366        # this should already be verified by WampSerializer.unserialize
2367        assert(len(wmsg) > 0 and wmsg[0] == Publish.MESSAGE_TYPE)
2368
2369        if len(wmsg) not in (4, 5, 6):
2370            raise ProtocolError("invalid message length {0} for PUBLISH".format(len(wmsg)))
2371
2372        request = check_or_raise_id(wmsg[1], u"'request' in PUBLISH")
2373        options = check_or_raise_extra(wmsg[2], u"'options' in PUBLISH")
2374        topic = check_or_raise_uri(wmsg[3], u"'topic' in PUBLISH")
2375
2376        args = None
2377        kwargs = None
2378        payload = None
2379
2380        if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]:
2381
2382            payload = wmsg[4]
2383
2384            enc_algo = options.get(u'enc_algo', None)
2385            if enc_algo and not is_valid_enc_algo(enc_algo):
2386                raise ProtocolError("invalid value {0} for 'enc_algo' option in PUBLISH".format(enc_algo))
2387
2388            enc_key = options.get(u'enc_key', None)
2389            if enc_key and type(enc_key) != six.text_type:
2390                raise ProtocolError("invalid type {0} for 'enc_key' option in PUBLISH".format(type(enc_key)))
2391
2392            enc_serializer = options.get(u'enc_serializer', None)
2393            if enc_serializer and not is_valid_enc_serializer(enc_serializer):
2394                raise ProtocolError("invalid value {0} for 'enc_serializer' option in PUBLISH".format(enc_serializer))
2395
2396        else:
2397            if len(wmsg) > 4:
2398                args = wmsg[4]
2399                if type(args) not in [list, six.text_type, six.binary_type]:
2400                    raise ProtocolError("invalid type {0} for 'args' in PUBLISH".format(type(args)))
2401
2402            if len(wmsg) > 5:
2403                kwargs = wmsg[5]
2404                if type(kwargs) not in [dict, six.text_type, six.binary_type]:
2405                    raise ProtocolError("invalid type {0} for 'kwargs' in PUBLISH".format(type(kwargs)))
2406
2407            enc_algo = None
2408            enc_key = None
2409            enc_serializer = None
2410
2411        acknowledge = None
2412        exclude_me = None
2413        exclude = None
2414        exclude_authid = None
2415        exclude_authrole = None
2416        eligible = None
2417        eligible_authid = None
2418        eligible_authrole = None
2419        retain = None
2420        forward_for = None
2421
2422        if u'acknowledge' in options:
2423
2424            option_acknowledge = options[u'acknowledge']
2425            if type(option_acknowledge) != bool:
2426                raise ProtocolError("invalid type {0} for 'acknowledge' option in PUBLISH".format(type(option_acknowledge)))
2427
2428            acknowledge = option_acknowledge
2429
2430        if u'exclude_me' in options:
2431
2432            option_exclude_me = options[u'exclude_me']
2433            if type(option_exclude_me) != bool:
2434                raise ProtocolError("invalid type {0} for 'exclude_me' option in PUBLISH".format(type(option_exclude_me)))
2435
2436            exclude_me = option_exclude_me
2437
2438        if u'exclude' in options:
2439
2440            option_exclude = options[u'exclude']
2441            if type(option_exclude) != list:
2442                raise ProtocolError("invalid type {0} for 'exclude' option in PUBLISH".format(type(option_exclude)))
2443
2444            for _sessionid in option_exclude:
2445                if type(_sessionid) not in six.integer_types:
2446                    raise ProtocolError("invalid type {0} for value in 'exclude' option in PUBLISH".format(type(_sessionid)))
2447
2448            exclude = option_exclude
2449
2450        if u'exclude_authid' in options:
2451
2452            option_exclude_authid = options[u'exclude_authid']
2453            if type(option_exclude_authid) != list:
2454                raise ProtocolError("invalid type {0} for 'exclude_authid' option in PUBLISH".format(type(option_exclude_authid)))
2455
2456            for _authid in option_exclude_authid:
2457                if type(_authid) != six.text_type:
2458                    raise ProtocolError("invalid type {0} for value in 'exclude_authid' option in PUBLISH".format(type(_authid)))
2459
2460            exclude_authid = option_exclude_authid
2461
2462        if u'exclude_authrole' in options:
2463
2464            option_exclude_authrole = options[u'exclude_authrole']
2465            if type(option_exclude_authrole) != list:
2466                raise ProtocolError("invalid type {0} for 'exclude_authrole' option in PUBLISH".format(type(option_exclude_authrole)))
2467
2468            for _authrole in option_exclude_authrole:
2469                if type(_authrole) != six.text_type:
2470                    raise ProtocolError("invalid type {0} for value in 'exclude_authrole' option in PUBLISH".format(type(_authrole)))
2471
2472            exclude_authrole = option_exclude_authrole
2473
2474        if u'eligible' in options:
2475
2476            option_eligible = options[u'eligible']
2477            if type(option_eligible) != list:
2478                raise ProtocolError("invalid type {0} for 'eligible' option in PUBLISH".format(type(option_eligible)))
2479
2480            for sessionId in option_eligible:
2481                if type(sessionId) not in six.integer_types:
2482                    raise ProtocolError("invalid type {0} for value in 'eligible' option in PUBLISH".format(type(sessionId)))
2483
2484            eligible = option_eligible
2485
2486        if u'eligible_authid' in options:
2487
2488            option_eligible_authid = options[u'eligible_authid']
2489            if type(option_eligible_authid) != list:
2490                raise ProtocolError("invalid type {0} for 'eligible_authid' option in PUBLISH".format(type(option_eligible_authid)))
2491
2492            for _authid in option_eligible_authid:
2493                if type(_authid) != six.text_type:
2494                    raise ProtocolError("invalid type {0} for value in 'eligible_authid' option in PUBLISH".format(type(_authid)))
2495
2496            eligible_authid = option_eligible_authid
2497
2498        if u'eligible_authrole' in options:
2499
2500            option_eligible_authrole = options[u'eligible_authrole']
2501            if type(option_eligible_authrole) != list:
2502                raise ProtocolError("invalid type {0} for 'eligible_authrole' option in PUBLISH".format(type(option_eligible_authrole)))
2503
2504            for _authrole in option_eligible_authrole:
2505                if type(_authrole) != six.text_type:
2506                    raise ProtocolError("invalid type {0} for value in 'eligible_authrole' option in PUBLISH".format(type(_authrole)))
2507
2508            eligible_authrole = option_eligible_authrole
2509
2510        if u'retain' in options:
2511            retain = options[u'retain']
2512            if type(retain) != bool:
2513                raise ProtocolError("invalid type {0} for 'retain' option in PUBLISH".format(type(retain)))
2514
2515        if u'forward_for' in options:
2516            forward_for = options[u'forward_for']
2517            valid = False
2518            if type(forward_for) == list:
2519                for ff in forward_for:
2520                    if type(ff) != dict:
2521                        break
2522                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
2523                        break
2524                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
2525                        break
2526                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
2527                        break
2528                valid = True
2529
2530            if not valid:
2531                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in PUBLISH")
2532
2533        obj = Publish(request,
2534                      topic,
2535                      args=args,
2536                      kwargs=kwargs,
2537                      payload=payload,
2538                      acknowledge=acknowledge,
2539                      exclude_me=exclude_me,
2540                      exclude=exclude,
2541                      exclude_authid=exclude_authid,
2542                      exclude_authrole=exclude_authrole,
2543                      eligible=eligible,
2544                      eligible_authid=eligible_authid,
2545                      eligible_authrole=eligible_authrole,
2546                      retain=retain,
2547                      enc_algo=enc_algo,
2548                      enc_key=enc_key,
2549                      enc_serializer=enc_serializer,
2550                      forward_for=forward_for)
2551
2552        return obj
2553
2554    def marshal_options(self):
2555        options = {}
2556
2557        if self.acknowledge is not None:
2558            options[u'acknowledge'] = self.acknowledge
2559
2560        if self.exclude_me is not None:
2561            options[u'exclude_me'] = self.exclude_me
2562        if self.exclude is not None:
2563            options[u'exclude'] = self.exclude
2564        if self.exclude_authid is not None:
2565            options[u'exclude_authid'] = self.exclude_authid
2566        if self.exclude_authrole is not None:
2567            options[u'exclude_authrole'] = self.exclude_authrole
2568        if self.eligible is not None:
2569            options[u'eligible'] = self.eligible
2570        if self.eligible_authid is not None:
2571            options[u'eligible_authid'] = self.eligible_authid
2572        if self.eligible_authrole is not None:
2573            options[u'eligible_authrole'] = self.eligible_authrole
2574        if self.retain is not None:
2575            options[u'retain'] = self.retain
2576
2577        if self.payload:
2578            if self.enc_algo is not None:
2579                options[u'enc_algo'] = self.enc_algo
2580            if self.enc_key is not None:
2581                options[u'enc_key'] = self.enc_key
2582            if self.enc_serializer is not None:
2583                options[u'enc_serializer'] = self.enc_serializer
2584
2585        if self.forward_for is not None:
2586            options[u'forward_for'] = self.forward_for
2587
2588        return options
2589
2590    def marshal(self):
2591        """
2592        Marshal this object into a raw message for subsequent serialization to bytes.
2593
2594        :returns: The serialized raw message.
2595        :rtype: list
2596        """
2597        options = self.marshal_options()
2598
2599        if self.payload:
2600            return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.payload]
2601        else:
2602            if self.kwargs:
2603                return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args, self.kwargs]
2604            elif self.args:
2605                return [Publish.MESSAGE_TYPE, self.request, options, self.topic, self.args]
2606            else:
2607                return [Publish.MESSAGE_TYPE, self.request, options, self.topic]
2608
2609    def __str__(self):
2610        """
2611        Returns string representation of this message.
2612        """
2613        return u"Publish(request={}, topic={}, args={}, kwargs={}, acknowledge={}, exclude_me={}, exclude={}, exclude_authid={}, exclude_authrole={}, eligible={}, eligible_authid={}, eligible_authrole={}, retain={}, enc_algo={}, enc_key={}, enc_serializer={}, payload={}, forward_for={})".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.retain, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload), self.forward_for)
2614
2615
2616class Published(Message):
2617    """
2618    A WAMP ``PUBLISHED`` message.
2619
2620    Format: ``[PUBLISHED, PUBLISH.Request|id, Publication|id]``
2621    """
2622
2623    MESSAGE_TYPE = 17
2624    """
2625    The WAMP message code for this type of message.
2626    """
2627
2628    __slots__ = (
2629        'request',
2630        'publication',
2631    )
2632
2633    def __init__(self, request, publication):
2634        """
2635
2636        :param request: The request ID of the original `PUBLISH` request.
2637        :type request: int
2638
2639        :param publication: The publication ID for the published event.
2640        :type publication: int
2641        """
2642        assert(type(request) in six.integer_types)
2643        assert(type(publication) in six.integer_types)
2644
2645        Message.__init__(self)
2646        self.request = request
2647        self.publication = publication
2648
2649    @staticmethod
2650    def parse(wmsg):
2651        """
2652        Verifies and parses an unserialized raw message into an actual WAMP message instance.
2653
2654        :param wmsg: The unserialized raw message.
2655        :type wmsg: list
2656
2657        :returns: An instance of this class.
2658        """
2659        # this should already be verified by WampSerializer.unserialize
2660        assert(len(wmsg) > 0 and wmsg[0] == Published.MESSAGE_TYPE)
2661
2662        if len(wmsg) != 3:
2663            raise ProtocolError("invalid message length {0} for PUBLISHED".format(len(wmsg)))
2664
2665        request = check_or_raise_id(wmsg[1], u"'request' in PUBLISHED")
2666        publication = check_or_raise_id(wmsg[2], u"'publication' in PUBLISHED")
2667
2668        obj = Published(request, publication)
2669
2670        return obj
2671
2672    def marshal(self):
2673        """
2674        Marshal this object into a raw message for subsequent serialization to bytes.
2675
2676        :returns: The serialized raw message.
2677        :rtype: list
2678        """
2679        return [Published.MESSAGE_TYPE, self.request, self.publication]
2680
2681    def __str__(self):
2682        """
2683        Returns string representation of this message.
2684        """
2685        return u"Published(request={0}, publication={1})".format(self.request, self.publication)
2686
2687
2688class Subscribe(Message):
2689    """
2690    A WAMP ``SUBSCRIBE`` message.
2691
2692    Format: ``[SUBSCRIBE, Request|id, Options|dict, Topic|uri]``
2693    """
2694
2695    MESSAGE_TYPE = 32
2696    """
2697    The WAMP message code for this type of message.
2698    """
2699
2700    MATCH_EXACT = u'exact'
2701    MATCH_PREFIX = u'prefix'
2702    MATCH_WILDCARD = u'wildcard'
2703
2704    __slots__ = (
2705        'request',
2706        'topic',
2707        'match',
2708        'get_retained',
2709        'forward_for',
2710    )
2711
2712    def __init__(self,
2713                 request,
2714                 topic,
2715                 match=None,
2716                 get_retained=None,
2717                 forward_for=None):
2718        """
2719
2720        :param request: The WAMP request ID of this request.
2721        :type request: int
2722
2723        :param topic: The WAMP or application URI of the PubSub topic to subscribe to.
2724        :type topic: str
2725
2726        :param match: The topic matching method to be used for the subscription.
2727        :type match: str
2728
2729        :param get_retained: Whether the client wants the retained message we may have along with the subscription.
2730        :type get_retained: bool or None
2731
2732        :param forward_for: When this Subscribe is forwarded over a router-to-router link,
2733            or via an intermediary router.
2734        :type forward_for: list[dict]
2735        """
2736        assert(type(request) in six.integer_types)
2737        assert(type(topic) == six.text_type)
2738        assert(match is None or type(match) == six.text_type)
2739        assert(match is None or match in [Subscribe.MATCH_EXACT, Subscribe.MATCH_PREFIX, Subscribe.MATCH_WILDCARD])
2740        assert(get_retained is None or type(get_retained) is bool)
2741        assert(forward_for is None or type(forward_for) == list)
2742        if forward_for:
2743            for ff in forward_for:
2744                assert type(ff) == dict
2745                assert 'session' in ff and type(ff['session']) in six.integer_types
2746                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
2747                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
2748
2749        Message.__init__(self)
2750        self.request = request
2751        self.topic = topic
2752        self.match = match or Subscribe.MATCH_EXACT
2753        self.get_retained = get_retained
2754        self.forward_for = forward_for
2755
2756    @staticmethod
2757    def parse(wmsg):
2758        """
2759        Verifies and parses an unserialized raw message into an actual WAMP message instance.
2760
2761        :param wmsg: The unserialized raw message.
2762        :type wmsg: list
2763
2764        :returns: An instance of this class.
2765        """
2766        # this should already be verified by WampSerializer.unserialize
2767        assert(len(wmsg) > 0 and wmsg[0] == Subscribe.MESSAGE_TYPE)
2768
2769        if len(wmsg) != 4:
2770            raise ProtocolError("invalid message length {0} for SUBSCRIBE".format(len(wmsg)))
2771
2772        request = check_or_raise_id(wmsg[1], u"'request' in SUBSCRIBE")
2773        options = check_or_raise_extra(wmsg[2], u"'options' in SUBSCRIBE")
2774        topic = check_or_raise_uri(wmsg[3], u"'topic' in SUBSCRIBE", allow_empty_components=True)
2775
2776        match = Subscribe.MATCH_EXACT
2777        get_retained = None
2778        forward_for = None
2779
2780        if u'match' in options:
2781
2782            option_match = options[u'match']
2783            if type(option_match) != six.text_type:
2784                raise ProtocolError("invalid type {0} for 'match' option in SUBSCRIBE".format(type(option_match)))
2785
2786            if option_match not in [Subscribe.MATCH_EXACT, Subscribe.MATCH_PREFIX, Subscribe.MATCH_WILDCARD]:
2787                raise ProtocolError("invalid value {0} for 'match' option in SUBSCRIBE".format(option_match))
2788
2789            match = option_match
2790
2791        if u'get_retained' in options:
2792            get_retained = options[u'get_retained']
2793
2794            if type(get_retained) != bool:
2795                raise ProtocolError("invalid type {0} for 'get_retained' option in SUBSCRIBE".format(type(get_retained)))
2796
2797        if u'forward_for' in options:
2798            forward_for = options[u'forward_for']
2799            valid = False
2800            if type(forward_for) == list:
2801                for ff in forward_for:
2802                    if type(ff) != dict:
2803                        break
2804                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
2805                        break
2806                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
2807                        break
2808                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
2809                        break
2810                valid = True
2811
2812            if not valid:
2813                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in SUBSCRIBE")
2814
2815        obj = Subscribe(request, topic, match=match, get_retained=get_retained, forward_for=forward_for)
2816
2817        return obj
2818
2819    def marshal_options(self):
2820        options = {}
2821
2822        if self.match and self.match != Subscribe.MATCH_EXACT:
2823            options[u'match'] = self.match
2824
2825        if self.get_retained is not None:
2826            options[u'get_retained'] = self.get_retained
2827
2828        if self.forward_for is not None:
2829            options[u'forward_for'] = self.forward_for
2830
2831        return options
2832
2833    def marshal(self):
2834        """
2835        Marshal this object into a raw message for subsequent serialization to bytes.
2836
2837        :returns: The serialized raw message.
2838        :rtype: list
2839        """
2840        return [Subscribe.MESSAGE_TYPE, self.request, self.marshal_options(), self.topic]
2841
2842    def __str__(self):
2843        """
2844        Returns string representation of this message.
2845        """
2846        return u"Subscribe(request={0}, topic={1}, match={2}, get_retained={3}, forward_for={4})".format(self.request, self.topic, self.match, self.get_retained, self.forward_for)
2847
2848
2849class Subscribed(Message):
2850    """
2851    A WAMP ``SUBSCRIBED`` message.
2852
2853    Format: ``[SUBSCRIBED, SUBSCRIBE.Request|id, Subscription|id]``
2854    """
2855
2856    MESSAGE_TYPE = 33
2857    """
2858    The WAMP message code for this type of message.
2859    """
2860
2861    __slots__ = (
2862        'request',
2863        'subscription',
2864    )
2865
2866    def __init__(self, request, subscription):
2867        """
2868
2869        :param request: The request ID of the original ``SUBSCRIBE`` request.
2870        :type request: int
2871
2872        :param subscription: The subscription ID for the subscribed topic (or topic pattern).
2873        :type subscription: int
2874        """
2875        assert(type(request) in six.integer_types)
2876        assert(type(subscription) in six.integer_types)
2877
2878        Message.__init__(self)
2879        self.request = request
2880        self.subscription = subscription
2881
2882    @staticmethod
2883    def parse(wmsg):
2884        """
2885        Verifies and parses an unserialized raw message into an actual WAMP message instance.
2886
2887        :param wmsg: The unserialized raw message.
2888        :type wmsg: list
2889
2890        :returns: An instance of this class.
2891        """
2892        # this should already be verified by WampSerializer.unserialize
2893        assert(len(wmsg) > 0 and wmsg[0] == Subscribed.MESSAGE_TYPE)
2894
2895        if len(wmsg) != 3:
2896            raise ProtocolError("invalid message length {0} for SUBSCRIBED".format(len(wmsg)))
2897
2898        request = check_or_raise_id(wmsg[1], u"'request' in SUBSCRIBED")
2899        subscription = check_or_raise_id(wmsg[2], u"'subscription' in SUBSCRIBED")
2900
2901        obj = Subscribed(request, subscription)
2902
2903        return obj
2904
2905    def marshal(self):
2906        """
2907        Marshal this object into a raw message for subsequent serialization to bytes.
2908
2909        :returns: The serialized raw message.
2910        :rtype: list
2911        """
2912        return [Subscribed.MESSAGE_TYPE, self.request, self.subscription]
2913
2914    def __str__(self):
2915        """
2916        Returns string representation of this message.
2917        """
2918        return u"Subscribed(request={0}, subscription={1})".format(self.request, self.subscription)
2919
2920
2921class Unsubscribe(Message):
2922    """
2923    A WAMP ``UNSUBSCRIBE`` message.
2924
2925    Formats:
2926
2927    * ``[UNSUBSCRIBE, Request|id, SUBSCRIBED.Subscription|id]``
2928    * ``[UNSUBSCRIBE, Request|id, SUBSCRIBED.Subscription|id, Options|dict]``
2929    """
2930
2931    MESSAGE_TYPE = 34
2932    """
2933    The WAMP message code for this type of message.
2934    """
2935
2936    __slots__ = (
2937        'request',
2938        'subscription',
2939        'forward_for',
2940    )
2941
2942    def __init__(self, request, subscription, forward_for=None):
2943        """
2944
2945        :param request: The WAMP request ID of this request.
2946        :type request: int
2947
2948        :param subscription: The subscription ID for the subscription to unsubscribe from.
2949        :type subscription: int
2950
2951        :param forward_for: When this Unsubscribe is forwarded over a router-to-router link,
2952            or via an intermediary router.
2953        :type forward_for: list[dict]
2954        """
2955        assert(type(request) in six.integer_types)
2956        assert(type(subscription) in six.integer_types)
2957        if forward_for:
2958            for ff in forward_for:
2959                assert type(ff) == dict
2960                assert 'session' in ff and type(ff['session']) in six.integer_types
2961                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
2962                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
2963
2964        Message.__init__(self)
2965        self.request = request
2966        self.subscription = subscription
2967        self.forward_for = forward_for
2968
2969    @staticmethod
2970    def parse(wmsg):
2971        """
2972        Verifies and parses an unserialized raw message into an actual WAMP message instance.
2973
2974        :param wmsg: The unserialized raw message.
2975        :type wmsg: list
2976
2977        :returns: An instance of this class.
2978        """
2979        # this should already be verified by WampSerializer.unserialize
2980        assert(len(wmsg) > 0 and wmsg[0] == Unsubscribe.MESSAGE_TYPE)
2981
2982        if len(wmsg) not in [3, 4]:
2983            raise ProtocolError("invalid message length {0} for WAMP UNSUBSCRIBE".format(len(wmsg)))
2984
2985        request = check_or_raise_id(wmsg[1], u"'request' in UNSUBSCRIBE")
2986        subscription = check_or_raise_id(wmsg[2], u"'subscription' in UNSUBSCRIBE")
2987
2988        options = None
2989        if len(wmsg) > 3:
2990            options = check_or_raise_extra(wmsg[3], u"'options' in UNSUBSCRIBE")
2991
2992        forward_for = None
2993        if options and u'forward_for' in options:
2994            forward_for = options[u'forward_for']
2995            valid = False
2996            if type(forward_for) == list:
2997                for ff in forward_for:
2998                    if type(ff) != dict:
2999                        break
3000                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
3001                        break
3002                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
3003                        break
3004                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
3005                        break
3006                valid = True
3007
3008            if not valid:
3009                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in UNSUBSCRIBE")
3010
3011        obj = Unsubscribe(request, subscription, forward_for=forward_for)
3012
3013        return obj
3014
3015    def marshal(self):
3016        """
3017        Marshal this object into a raw message for subsequent serialization to bytes.
3018
3019        :returns: The serialized raw message.
3020        :rtype: list
3021        """
3022        if self.forward_for:
3023            options = {
3024                u'forward_for': self.forward_for,
3025            }
3026            return [Unsubscribe.MESSAGE_TYPE, self.request, self.subscription, options]
3027        else:
3028            return [Unsubscribe.MESSAGE_TYPE, self.request, self.subscription]
3029
3030    def __str__(self):
3031        """
3032        Returns string representation of this message.
3033        """
3034        return u"Unsubscribe(request={0}, subscription={1}, forward_for={2})".format(self.request, self.subscription, self.forward_for)
3035
3036
3037class Unsubscribed(Message):
3038    """
3039    A WAMP ``UNSUBSCRIBED`` message.
3040
3041    Formats:
3042
3043    * ``[UNSUBSCRIBED, UNSUBSCRIBE.Request|id]``
3044    * ``[UNSUBSCRIBED, UNSUBSCRIBE.Request|id, Details|dict]``
3045    """
3046
3047    MESSAGE_TYPE = 35
3048    """
3049    The WAMP message code for this type of message.
3050    """
3051
3052    __slots__ = (
3053        'request',
3054        'subscription',
3055        'reason',
3056    )
3057
3058    def __init__(self, request, subscription=None, reason=None):
3059        """
3060
3061        :param request: The request ID of the original ``UNSUBSCRIBE`` request or
3062            ``0`` is router triggered unsubscribe ("router revocation signaling").
3063        :type request: int
3064
3065        :param subscription: If unsubscribe was actively triggered by router, the ID
3066            of the subscription revoked.
3067        :type subscription: int or None
3068
3069        :param reason: The reason (an URI) for an active (router initiated) revocation.
3070        :type reason: str or None.
3071        """
3072        assert(type(request) in six.integer_types)
3073        assert(subscription is None or type(subscription) in six.integer_types)
3074        assert(reason is None or type(reason) == six.text_type)
3075        assert((request != 0 and subscription is None) or (request == 0 and subscription != 0))
3076
3077        Message.__init__(self)
3078        self.request = request
3079        self.subscription = subscription
3080        self.reason = reason
3081
3082    @staticmethod
3083    def parse(wmsg):
3084        """
3085        Verifies and parses an unserialized raw message into an actual WAMP message instance.
3086
3087        :param wmsg: The unserialized raw message.
3088        :type wmsg: list
3089
3090        :returns: An instance of this class.
3091        """
3092        # this should already be verified by WampSerializer.unserialize
3093        assert(len(wmsg) > 0 and wmsg[0] == Unsubscribed.MESSAGE_TYPE)
3094
3095        if len(wmsg) not in [2, 3]:
3096            raise ProtocolError("invalid message length {0} for UNSUBSCRIBED".format(len(wmsg)))
3097
3098        request = check_or_raise_id(wmsg[1], u"'request' in UNSUBSCRIBED")
3099
3100        subscription = None
3101        reason = None
3102
3103        if len(wmsg) > 2:
3104
3105            details = check_or_raise_extra(wmsg[2], u"'details' in UNSUBSCRIBED")
3106
3107            if u"subscription" in details:
3108                details_subscription = details[u"subscription"]
3109                if type(details_subscription) not in six.integer_types:
3110                    raise ProtocolError("invalid type {0} for 'subscription' detail in UNSUBSCRIBED".format(type(details_subscription)))
3111                subscription = details_subscription
3112
3113            if u"reason" in details:
3114                reason = check_or_raise_uri(details[u"reason"], u"'reason' in UNSUBSCRIBED")
3115
3116        obj = Unsubscribed(request, subscription, reason)
3117
3118        return obj
3119
3120    def marshal(self):
3121        """
3122        Marshal this object into a raw message for subsequent serialization to bytes.
3123
3124        :returns: The serialized raw message.
3125        :rtype: list
3126        """
3127        if self.reason is not None or self.subscription is not None:
3128            details = {}
3129            if self.reason is not None:
3130                details[u"reason"] = self.reason
3131            if self.subscription is not None:
3132                details[u"subscription"] = self.subscription
3133            return [Unsubscribed.MESSAGE_TYPE, self.request, details]
3134        else:
3135            return [Unsubscribed.MESSAGE_TYPE, self.request]
3136
3137    def __str__(self):
3138        """
3139        Returns string representation of this message.
3140        """
3141        return u"Unsubscribed(request={0}, reason={1}, subscription={2})".format(self.request, self.reason, self.subscription)
3142
3143
3144class Event(Message):
3145    """
3146    A WAMP ``EVENT`` message.
3147
3148    Formats:
3149
3150    * ``[EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict]``
3151    * ``[EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict, PUBLISH.Arguments|list]``
3152    * ``[EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict, PUBLISH.Arguments|list, PUBLISH.ArgumentsKw|dict]``
3153    * ``[EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict, PUBLISH.Payload|binary]``
3154    """
3155
3156    MESSAGE_TYPE = 36
3157    """
3158    The WAMP message code for this type of message.
3159    """
3160
3161    __slots__ = (
3162        # uint64
3163        '_subscription',
3164
3165        # uint64
3166        '_publication',
3167
3168        # [uint8]
3169        '_args',
3170
3171        # [uint8]
3172        '_kwargs',
3173
3174        # [uint8]
3175        '_payload',
3176
3177        # Payload => uint8
3178        '_enc_algo',
3179
3180        # Serializer => uint8
3181        '_enc_serializer',
3182
3183        # [uint8]
3184        '_enc_key',
3185
3186        # uint64
3187        '_publisher',
3188
3189        # string (principal)
3190        '_publisher_authid',
3191
3192        # string (principal)
3193        '_publisher_authrole',
3194
3195        # string (uri)
3196        '_topic',
3197
3198        # bool
3199        '_retained',
3200
3201        # bool - FIXME: rename to "acknowledge"
3202        '_x_acknowledged_delivery',
3203
3204        # [Principal]
3205        '_forward_for',
3206    )
3207
3208    def __init__(self, subscription=None, publication=None, args=None, kwargs=None, payload=None,
3209                 publisher=None, publisher_authid=None, publisher_authrole=None, topic=None,
3210                 retained=None, x_acknowledged_delivery=None,
3211                 enc_algo=None, enc_key=None, enc_serializer=None, forward_for=None,
3212                 from_fbs=None):
3213        """
3214
3215        :param subscription: The subscription ID this event is dispatched under.
3216        :type subscription: int
3217
3218        :param publication: The publication ID of the dispatched event.
3219        :type publication: int
3220
3221        :param args: Positional values for application-defined exception.
3222           Must be serializable using any serializers in use.
3223        :type args: list or tuple or None
3224
3225        :param kwargs: Keyword values for application-defined exception.
3226           Must be serializable using any serializers in use.
3227        :type kwargs: dict or None
3228
3229        :param payload: Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
3230        :type payload: bytes or None
3231
3232        :param publisher: The WAMP session ID of the publisher. Only filled if publisher is disclosed.
3233        :type publisher: None or int
3234
3235        :param publisher_authid: The WAMP authid of the publisher. Only filled if publisher is disclosed.
3236        :type publisher_authid: None or unicode
3237
3238        :param publisher_authrole: The WAMP authrole of the publisher. Only filled if publisher is disclosed.
3239        :type publisher_authrole: None or unicode
3240
3241        :param topic: For pattern-based subscriptions, the event MUST contain the actual topic published to.
3242        :type topic: str or None
3243
3244        :param retained: Whether the message was retained by the broker on the topic, rather than just published.
3245        :type retained: bool or None
3246
3247        :param x_acknowledged_delivery: Whether this Event should be acknowledged.
3248        :type x_acknowledged_delivery: bool or None
3249
3250        :param enc_algo: If using payload transparency, the encoding algorithm that was used to encode the payload.
3251        :type enc_algo: str or None
3252
3253        :param enc_key: If using payload transparency with an encryption algorithm, the payload encryption key.
3254        :type enc_key: str or None
3255
3256        :param enc_serializer: If using payload transparency, the payload object serializer that was used encoding the payload.
3257        :type enc_serializer: str or None
3258
3259        :param forward_for: When this Event is forwarded for a client (or from an intermediary router).
3260        :type forward_for: list[dict]
3261        """
3262        assert(subscription is None or type(subscription) in six.integer_types)
3263        assert(publication is None or type(publication) in six.integer_types)
3264        assert(args is None or type(args) in [list, tuple])
3265        assert(kwargs is None or type(kwargs) == dict)
3266        assert(payload is None or type(payload) == six.binary_type)
3267        assert(payload is None or (payload is not None and args is None and kwargs is None))
3268        assert(publisher is None or type(publisher) in six.integer_types)
3269        assert(publisher_authid is None or type(publisher_authid) == six.text_type)
3270        assert(publisher_authrole is None or type(publisher_authrole) == six.text_type)
3271        assert(topic is None or type(topic) == six.text_type)
3272        assert(retained is None or type(retained) == bool)
3273        assert(x_acknowledged_delivery is None or type(x_acknowledged_delivery) == bool)
3274        assert(enc_algo is None or is_valid_enc_algo(enc_algo))
3275        assert((enc_algo is None and enc_key is None and enc_serializer is None) or (payload is not None and enc_algo is not None))
3276        assert(enc_key is None or type(enc_key) == six.text_type)
3277        assert(enc_serializer is None or is_valid_enc_serializer(enc_serializer))
3278
3279        assert(forward_for is None or type(forward_for) == list)
3280        if forward_for:
3281            for ff in forward_for:
3282                assert type(ff) == dict
3283                assert 'session' in ff and type(ff['session']) in six.integer_types
3284                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
3285                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
3286
3287        Message.__init__(self, from_fbs=from_fbs)
3288        self._subscription = subscription
3289        self._publication = publication
3290        self._args = args
3291        self._kwargs = _validate_kwargs(kwargs)
3292        self._payload = payload
3293        self._publisher = publisher
3294        self._publisher_authid = publisher_authid
3295        self._publisher_authrole = publisher_authrole
3296        self._topic = topic
3297        self._retained = retained
3298        self._x_acknowledged_delivery = x_acknowledged_delivery
3299        self._enc_algo = enc_algo
3300        self._enc_key = enc_key
3301        self._enc_serializer = enc_serializer
3302        self._forward_for = forward_for
3303
3304    def __eq__(self, other):
3305        if not isinstance(other, self.__class__):
3306            return False
3307        if not Message.__eq__(self, other):
3308            return False
3309        if other.subscription != self.subscription:
3310            return False
3311        if other.publication != self.publication:
3312            return False
3313        if other.args != self.args:
3314            return False
3315        if other.kwargs != self.kwargs:
3316            return False
3317        if other.payload != self.payload:
3318            return False
3319        if other.publisher != self.publisher:
3320            return False
3321        if other.publisher_authid != self.publisher_authid:
3322            return False
3323        if other.publisher_authrole != self.publisher_authrole:
3324            return False
3325        if other.topic != self.topic:
3326            return False
3327        if other.retained != self.retained:
3328            return False
3329        if other.x_acknowledged_delivery != self.x_acknowledged_delivery:
3330            return False
3331        if other.enc_algo != self.enc_algo:
3332            return False
3333        if other.enc_key != self.enc_key:
3334            return False
3335        if other.enc_serializer != self.enc_serializer:
3336            return False
3337        if other.forward_for != self.forward_for:
3338            return False
3339        return True
3340
3341    def __ne__(self, other):
3342        return not self.__eq__(other)
3343
3344    @property
3345    def subscription(self):
3346        if self._subscription is None and self._from_fbs:
3347            self._subscription = self._from_fbs.Subscription()
3348        return self._subscription
3349
3350    @subscription.setter
3351    def subscription(self, value):
3352        assert(value is None or type(value) in six.integer_types)
3353        self._subscription = value
3354
3355    @property
3356    def publication(self):
3357        if self._publication is None and self._from_fbs:
3358            self._publication = self._from_fbs.Publication()
3359        return self._publication
3360
3361    @publication.setter
3362    def publication(self, value):
3363        assert(value is None or type(value) in six.integer_types)
3364        self._publication = value
3365
3366    @property
3367    def args(self):
3368        if self._args is None and self._from_fbs:
3369            if self._from_fbs.ArgsLength():
3370                self._args = cbor.loads(bytes(self._from_fbs.ArgsAsBytes()))
3371        return self._args
3372
3373    @args.setter
3374    def args(self, value):
3375        assert(value is None or type(value) in [list, tuple])
3376        self._args = value
3377
3378    @property
3379    def kwargs(self):
3380        if self._kwargs is None and self._from_fbs:
3381            if self._from_fbs.KwargsLength():
3382                self._kwargs = cbor.loads(bytes(self._from_fbs.KwargsAsBytes()))
3383        return self._kwargs
3384
3385    @kwargs.setter
3386    def kwargs(self, value):
3387        assert(value is None or type(value) == dict)
3388        self._kwargs = value
3389
3390    @property
3391    def payload(self):
3392        if self._payload is None and self._from_fbs:
3393            if self._from_fbs.PayloadLength():
3394                self._payload = self._from_fbs.PayloadAsBytes()
3395        return self._payload
3396
3397    @payload.setter
3398    def payload(self, value):
3399        assert value is None or type(value) == bytes
3400        self._payload = value
3401
3402    @property
3403    def publisher(self):
3404        if self._publisher is None and self._from_fbs:
3405            publisher = self._from_fbs.Publisher()
3406            if publisher:
3407                self._publisher = publisher
3408        return self._publisher
3409
3410    @publisher.setter
3411    def publisher(self, value):
3412        assert value is None or type(value) == int
3413        self._publisher = value
3414
3415    @property
3416    def publisher_authid(self):
3417        if self._publisher_authid is None and self._from_fbs:
3418            s = self._from_fbs.PublisherAuthid()
3419            if s:
3420                self._publisher_authid = s.decode('utf8')
3421        return self._publisher_authid
3422
3423    @publisher_authid.setter
3424    def publisher_authid(self, value):
3425        assert value is None or type(value) == str
3426        self._publisher_authid = value
3427
3428    @property
3429    def publisher_authrole(self):
3430        if self._publisher_authrole is None and self._from_fbs:
3431            s = self._from_fbs.PublisherAuthrole()
3432            if s:
3433                self._publisher_authrole = s.decode('utf8')
3434        return self._publisher_authrole
3435
3436    @publisher_authrole.setter
3437    def publisher_authrole(self, value):
3438        assert value is None or type(value) == str
3439        self._publisher_authrole = value
3440
3441    @property
3442    def topic(self):
3443        if self._topic is None and self._from_fbs:
3444            s = self._from_fbs.Topic()
3445            if s:
3446                self._topic = s.decode('utf8')
3447        return self._topic
3448
3449    @topic.setter
3450    def topic(self, value):
3451        assert value is None or type(value) == str
3452        self._topic = value
3453
3454    @property
3455    def retained(self):
3456        if self._retained is None and self._from_fbs:
3457            self._retained = self._from_fbs.Retained()
3458        return self._retained
3459
3460    @retained.setter
3461    def retained(self, value):
3462        assert value is None or type(value) == bool
3463        self._retained = value
3464
3465    @property
3466    def x_acknowledged_delivery(self):
3467        if self._x_acknowledged_delivery is None and self._from_fbs:
3468            x_acknowledged_delivery = self._from_fbs.Acknowledge()
3469            if x_acknowledged_delivery:
3470                self._x_acknowledged_delivery = x_acknowledged_delivery
3471        return self._x_acknowledged_delivery
3472
3473    @x_acknowledged_delivery.setter
3474    def x_acknowledged_delivery(self, value):
3475        assert value is None or type(value) == bool
3476        self._x_acknowledged_delivery = value
3477
3478    @property
3479    def enc_algo(self):
3480        if self._enc_algo is None and self._from_fbs:
3481            enc_algo = self._from_fbs.EncAlgo()
3482            if enc_algo:
3483                self._enc_algo = enc_algo
3484        return self._enc_algo
3485
3486    @enc_algo.setter
3487    def enc_algo(self, value):
3488        assert value is None or value in [ENC_ALGO_CRYPTOBOX, ENC_ALGO_MQTT, ENC_ALGO_XBR]
3489        self._enc_algo = value
3490
3491    @property
3492    def enc_key(self):
3493        if self._enc_key is None and self._from_fbs:
3494            if self._from_fbs.EncKeyLength():
3495                self._enc_key = self._from_fbs.EncKeyAsBytes()
3496        return self._enc_key
3497
3498    @enc_key.setter
3499    def enc_key(self, value):
3500        assert value is None or type(value) == bytes
3501        self._enc_key = value
3502
3503    @property
3504    def enc_serializer(self):
3505        if self._enc_serializer is None and self._from_fbs:
3506            enc_serializer = self._from_fbs.EncSerializer()
3507            if enc_serializer:
3508                self._enc_serializer = enc_serializer
3509        return self._enc_serializer
3510
3511    @enc_serializer.setter
3512    def enc_serializer(self, value):
3513        assert value is None or value in [ENC_SER_JSON, ENC_SER_MSGPACK, ENC_SER_CBOR, ENC_SER_UBJSON]
3514        self._enc_serializer = value
3515
3516    @property
3517    def forward_for(self):
3518        # FIXME
3519        return self._forward_for
3520
3521    @forward_for.setter
3522    def forward_for(self, value):
3523        # FIXME
3524        self._forward_for = value
3525
3526    @staticmethod
3527    def cast(buf):
3528        return Event(from_fbs=message_fbs.Event.GetRootAsEvent(buf, 0))
3529
3530    def build(self, builder):
3531
3532        args = self.args
3533        if args:
3534            args = builder.CreateByteVector(cbor.dumps(args))
3535
3536        kwargs = self.kwargs
3537        if kwargs:
3538            kwargs = builder.CreateByteVector(cbor.dumps(kwargs))
3539
3540        payload = self.payload
3541        if payload:
3542            payload = builder.CreateByteVector(payload)
3543
3544        publisher_authid = self.publisher_authid
3545        if publisher_authid:
3546            publisher_authid = builder.CreateString(publisher_authid)
3547
3548        publisher_authrole = self.publisher_authrole
3549        if publisher_authrole:
3550            publisher_authrole = builder.CreateString(publisher_authrole)
3551
3552        topic = self.topic
3553        if topic:
3554            topic = builder.CreateString(topic)
3555
3556        enc_key = self.enc_key
3557        if enc_key:
3558            enc_key = builder.CreateByteVector(enc_key)
3559
3560        message_fbs.EventGen.EventStart(builder)
3561
3562        if self.subscription:
3563            message_fbs.EventGen.EventAddSubscription(builder, self.subscription)
3564        if self.publication:
3565            message_fbs.EventGen.EventAddPublication(builder, self.publication)
3566
3567        if args:
3568            message_fbs.EventGen.EventAddArgs(builder, args)
3569        if kwargs:
3570            message_fbs.EventGen.EventAddKwargs(builder, kwargs)
3571        if payload:
3572            message_fbs.EventGen.EventAddPayload(builder, payload)
3573
3574        if self.publisher:
3575            message_fbs.EventGen.EventAddPublisher(builder, self.publisher)
3576        if publisher_authid:
3577            message_fbs.EventGen.EventAddPublisherAuthid(builder, publisher_authid)
3578        if publisher_authrole:
3579            message_fbs.EventGen.EventAddPublisherAuthrole(builder, publisher_authrole)
3580
3581        if topic:
3582            message_fbs.EventGen.EventAddTopic(builder, topic)
3583        if self.retained is not None:
3584            message_fbs.EventGen.EventAddRetained(builder, self.retained)
3585        if self.x_acknowledged_delivery is not None:
3586            message_fbs.EventGen.EventAddAcknowledge(builder, self.x_acknowledged_delivery)
3587
3588        if self.enc_algo:
3589            message_fbs.EventGen.EventAddEncAlgo(builder, self.enc_algo)
3590        if enc_key:
3591            message_fbs.EventGen.EventAddEncKey(builder, enc_key)
3592        if self.enc_serializer:
3593            message_fbs.EventGen.EventAddEncSerializer(builder, self.enc_serializer)
3594
3595        # FIXME: add forward_for
3596
3597        msg = message_fbs.EventGen.EventEnd(builder)
3598
3599        message_fbs.Message.MessageStart(builder)
3600        message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.EVENT)
3601        message_fbs.Message.MessageAddMsg(builder, msg)
3602        union_msg = message_fbs.Message.MessageEnd(builder)
3603
3604        return union_msg
3605
3606    @staticmethod
3607    def parse(wmsg):
3608        """
3609        Verifies and parses an unserialized raw message into an actual WAMP message instance.
3610
3611        :param wmsg: The unserialized raw message.
3612        :type wmsg: list
3613
3614        :returns: An instance of this class.
3615        """
3616        # this should already be verified by WampSerializer.unserialize
3617        assert(len(wmsg) > 0 and wmsg[0] == Event.MESSAGE_TYPE)
3618
3619        if len(wmsg) not in (4, 5, 6):
3620            raise ProtocolError("invalid message length {0} for EVENT".format(len(wmsg)))
3621
3622        subscription = check_or_raise_id(wmsg[1], u"'subscription' in EVENT")
3623        publication = check_or_raise_id(wmsg[2], u"'publication' in EVENT")
3624        details = check_or_raise_extra(wmsg[3], u"'details' in EVENT")
3625
3626        args = None
3627        kwargs = None
3628        payload = None
3629        enc_algo = None
3630        enc_key = None
3631        enc_serializer = None
3632
3633        if len(wmsg) == 5 and type(wmsg[4]) == six.binary_type:
3634
3635            payload = wmsg[4]
3636
3637            enc_algo = details.get(u'enc_algo', None)
3638            if enc_algo and not is_valid_enc_algo(enc_algo):
3639                raise ProtocolError("invalid value {0} for 'enc_algo' detail in EVENT".format(enc_algo))
3640
3641            enc_key = details.get(u'enc_key', None)
3642            if enc_key and type(enc_key) != six.text_type:
3643                raise ProtocolError("invalid type {0} for 'enc_key' detail in EVENT".format(type(enc_key)))
3644
3645            enc_serializer = details.get(u'enc_serializer', None)
3646            if enc_serializer and not is_valid_enc_serializer(enc_serializer):
3647                raise ProtocolError("invalid value {0} for 'enc_serializer' detail in EVENT".format(enc_serializer))
3648
3649        else:
3650            if len(wmsg) > 4:
3651                args = wmsg[4]
3652                if args is not None and type(args) != list:
3653                    raise ProtocolError("invalid type {0} for 'args' in EVENT".format(type(args)))
3654            if len(wmsg) > 5:
3655                kwargs = wmsg[5]
3656                if type(kwargs) != dict:
3657                    raise ProtocolError("invalid type {0} for 'kwargs' in EVENT".format(type(kwargs)))
3658
3659        publisher = None
3660        publisher_authid = None
3661        publisher_authrole = None
3662        topic = None
3663        retained = None
3664        forward_for = None
3665        x_acknowledged_delivery = None
3666
3667        if u'publisher' in details:
3668
3669            detail_publisher = details[u'publisher']
3670            if type(detail_publisher) not in six.integer_types:
3671                raise ProtocolError("invalid type {0} for 'publisher' detail in EVENT".format(type(detail_publisher)))
3672
3673            publisher = detail_publisher
3674
3675        if u'publisher_authid' in details:
3676
3677            detail_publisher_authid = details[u'publisher_authid']
3678            if type(detail_publisher_authid) != six.text_type:
3679                raise ProtocolError("invalid type {0} for 'publisher_authid' detail in EVENT".format(type(detail_publisher_authid)))
3680
3681            publisher_authid = detail_publisher_authid
3682
3683        if u'publisher_authrole' in details:
3684
3685            detail_publisher_authrole = details[u'publisher_authrole']
3686            if type(detail_publisher_authrole) != six.text_type:
3687                raise ProtocolError("invalid type {0} for 'publisher_authrole' detail in EVENT".format(type(detail_publisher_authrole)))
3688
3689            publisher_authrole = detail_publisher_authrole
3690
3691        if u'topic' in details:
3692
3693            detail_topic = details[u'topic']
3694            if type(detail_topic) != six.text_type:
3695                raise ProtocolError("invalid type {0} for 'topic' detail in EVENT".format(type(detail_topic)))
3696
3697            topic = detail_topic
3698
3699        if u'retained' in details:
3700            retained = details[u'retained']
3701            if type(retained) != bool:
3702                raise ProtocolError("invalid type {0} for 'retained' detail in EVENT".format(type(retained)))
3703
3704        if u'x_acknowledged_delivery' in details:
3705            x_acknowledged_delivery = details[u'x_acknowledged_delivery']
3706            if type(x_acknowledged_delivery) != bool:
3707                raise ProtocolError("invalid type {0} for 'x_acknowledged_delivery' detail in EVENT".format(type(x_acknowledged_delivery)))
3708
3709        if u'forward_for' in details:
3710            forward_for = details[u'forward_for']
3711            valid = False
3712            if type(forward_for) == list:
3713                for ff in forward_for:
3714                    if type(ff) != dict:
3715                        break
3716                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
3717                        break
3718                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
3719                        break
3720                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
3721                        break
3722                valid = True
3723
3724            if not valid:
3725                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in EVENT")
3726
3727        obj = Event(subscription,
3728                    publication,
3729                    args=args,
3730                    kwargs=kwargs,
3731                    payload=payload,
3732                    publisher=publisher,
3733                    publisher_authid=publisher_authid,
3734                    publisher_authrole=publisher_authrole,
3735                    topic=topic,
3736                    retained=retained,
3737                    x_acknowledged_delivery=x_acknowledged_delivery,
3738                    enc_algo=enc_algo,
3739                    enc_key=enc_key,
3740                    enc_serializer=enc_serializer,
3741                    forward_for=forward_for)
3742
3743        return obj
3744
3745    def marshal(self):
3746        """
3747        Marshal this object into a raw message for subsequent serialization to bytes.
3748
3749        :returns: The serialized raw message.
3750        :rtype: list
3751        """
3752        details = {}
3753
3754        if self.publisher is not None:
3755            details[u'publisher'] = self.publisher
3756
3757        if self.publisher_authid is not None:
3758            details[u'publisher_authid'] = self.publisher_authid
3759
3760        if self.publisher_authrole is not None:
3761            details[u'publisher_authrole'] = self.publisher_authrole
3762
3763        if self.topic is not None:
3764            details[u'topic'] = self.topic
3765
3766        if self.retained is not None:
3767            details[u'retained'] = self.retained
3768
3769        if self.x_acknowledged_delivery is not None:
3770            details[u'x_acknowledged_delivery'] = self.x_acknowledged_delivery
3771
3772        if self.forward_for is not None:
3773            details[u'forward_for'] = self.forward_for
3774
3775        if self.payload:
3776            if self.enc_algo is not None:
3777                details[u'enc_algo'] = self.enc_algo
3778            if self.enc_key is not None:
3779                details[u'enc_key'] = self.enc_key
3780            if self.enc_serializer is not None:
3781                details[u'enc_serializer'] = self.enc_serializer
3782            return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.payload]
3783        else:
3784            if self.kwargs:
3785                return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args, self.kwargs]
3786            elif self.args:
3787                return [Event.MESSAGE_TYPE, self.subscription, self.publication, details, self.args]
3788            else:
3789                return [Event.MESSAGE_TYPE, self.subscription, self.publication, details]
3790
3791    def __str__(self):
3792        """
3793        Returns string representation of this message.
3794        """
3795        return u"Event(subscription={}, publication={}, args={}, kwargs={}, publisher={}, publisher_authid={}, publisher_authrole={}, topic={}, retained={}, enc_algo={}, enc_key={}, enc_serializer={}, payload={}, forward_for={})".format(self.subscription, self.publication, self.args, self.kwargs, self.publisher, self.publisher_authid, self.publisher_authrole, self.topic, self.retained, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload), self.forward_for)
3796
3797
3798class EventReceived(Message):
3799    """
3800    A WAMP ``EVENT_RECEIVED`` message.
3801
3802    Format: ``[EVENT_RECEIVED, EVENT.Publication|id]``
3803    """
3804
3805    # NOTE: Implementation-specific message! Should be 37 on ratification.
3806    MESSAGE_TYPE = 337
3807    """
3808    The WAMP message code for this type of message.
3809    """
3810
3811    __slots__ = (
3812        'publication',
3813    )
3814
3815    def __init__(self, publication):
3816        """
3817
3818        :param publication: The publication ID for the sent event.
3819        :type publication: int
3820        """
3821        assert(type(publication) in six.integer_types)
3822
3823        Message.__init__(self)
3824        self.publication = publication
3825
3826    @staticmethod
3827    def parse(wmsg):
3828        """
3829        Verifies and parses an unserialized raw message into an actual WAMP message instance.
3830
3831        :param wmsg: The unserialized raw message.
3832        :type wmsg: list
3833
3834        :returns: An instance of this class.
3835        """
3836        # this should already be verified by WampSerializer.unserialize
3837        assert(len(wmsg) > 0 and wmsg[0] == EventReceived.MESSAGE_TYPE)
3838
3839        if len(wmsg) != 2:
3840            raise ProtocolError("invalid message length {0} for EVENT_RECEIVED".format(len(wmsg)))
3841
3842        publication = check_or_raise_id(wmsg[1], u"'publication' in EVENT_RECEIVED")
3843
3844        obj = EventReceived(publication)
3845
3846        return obj
3847
3848    def marshal(self):
3849        """
3850        Marshal this object into a raw message for subsequent serialization to bytes.
3851
3852        :returns: The serialized raw message.
3853        :rtype: list
3854        """
3855        return [EventReceived.MESSAGE_TYPE, self.publication]
3856
3857    def __str__(self):
3858        """
3859        Returns string representation of this message.
3860        """
3861        return u"EventReceived(publication={})".format(self.publication)
3862
3863
3864class Call(Message):
3865    """
3866    A WAMP ``CALL`` message.
3867
3868    Formats:
3869
3870    * ``[CALL, Request|id, Options|dict, Procedure|uri]``
3871    * ``[CALL, Request|id, Options|dict, Procedure|uri, Arguments|list]``
3872    * ``[CALL, Request|id, Options|dict, Procedure|uri, Arguments|list, ArgumentsKw|dict]``
3873    * ``[CALL, Request|id, Options|dict, Procedure|uri, Payload|binary]``
3874    """
3875
3876    MESSAGE_TYPE = 48
3877    """
3878    The WAMP message code for this type of message.
3879    """
3880
3881    __slots__ = (
3882        'request',
3883        'procedure',
3884        'args',
3885        'kwargs',
3886        'payload',
3887        'timeout',
3888        'receive_progress',
3889        'enc_algo',
3890        'enc_key',
3891        'enc_serializer',
3892        'caller',
3893        'caller_authid',
3894        'caller_authrole',
3895        'forward_for',
3896    )
3897
3898    def __init__(self,
3899                 request,
3900                 procedure,
3901                 args=None,
3902                 kwargs=None,
3903                 payload=None,
3904                 timeout=None,
3905                 receive_progress=None,
3906                 enc_algo=None,
3907                 enc_key=None,
3908                 enc_serializer=None,
3909                 caller=None,
3910                 caller_authid=None,
3911                 caller_authrole=None,
3912                 forward_for=None):
3913        """
3914
3915        :param request: The WAMP request ID of this request.
3916        :type request: int
3917
3918        :param procedure: The WAMP or application URI of the procedure which should be called.
3919        :type procedure: str
3920
3921        :param args: Positional values for application-defined call arguments.
3922           Must be serializable using any serializers in use.
3923        :type args: list or tuple or None
3924
3925        :param kwargs: Keyword values for application-defined call arguments.
3926           Must be serializable using any serializers in use.
3927        :type kwargs: dict or None
3928
3929        :param payload: Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
3930        :type payload: bytes or None
3931
3932        :param timeout: If present, let the callee automatically cancel
3933           the call after this ms.
3934        :type timeout: int or None
3935
3936        :param receive_progress: If ``True``, indicates that the caller wants to receive
3937           progressive call results.
3938        :type receive_progress: bool or None
3939
3940        :param enc_algo: If using payload transparency, the encoding algorithm that was used to encode the payload.
3941        :type enc_algo: str or None
3942
3943        :param enc_key: If using payload transparency with an encryption algorithm, the payload encryption key.
3944        :type enc_key: str or None
3945
3946        :param enc_serializer: If using payload transparency, the payload object serializer that was used encoding the payload.
3947        :type enc_serializer: str or None
3948
3949        :param caller: The WAMP session ID of the caller. Only filled if caller is disclosed.
3950        :type caller: None or int
3951
3952        :param caller_authid: The WAMP authid of the caller. Only filled if caller is disclosed.
3953        :type caller_authid: None or unicode
3954
3955        :param caller_authrole: The WAMP authrole of the caller. Only filled if caller is disclosed.
3956        :type caller_authrole: None or unicode
3957
3958        :param forward_for: When this Publish is forwarded for a client (or from an intermediary router).
3959        :type forward_for: list[dict]
3960        """
3961        assert(type(request) in six.integer_types)
3962        assert(type(procedure) == six.text_type)
3963        assert(args is None or type(args) in [list, tuple])
3964        assert(kwargs is None or type(kwargs) == dict)
3965        assert(payload is None or type(payload) == six.binary_type)
3966        assert(payload is None or (payload is not None and args is None and kwargs is None))
3967        assert(timeout is None or type(timeout) in six.integer_types)
3968        assert(receive_progress is None or type(receive_progress) == bool)
3969
3970        # payload transparency related knobs
3971        assert(enc_algo is None or is_valid_enc_algo(enc_algo))
3972        assert(enc_key is None or type(enc_key) == six.text_type)
3973        assert(enc_serializer is None or is_valid_enc_serializer(enc_serializer))
3974        assert((enc_algo is None and enc_key is None and enc_serializer is None) or (payload is not None and enc_algo is not None))
3975
3976        assert(caller is None or type(caller) in six.integer_types)
3977        assert(caller_authid is None or type(caller_authid) == six.text_type)
3978        assert(caller_authrole is None or type(caller_authrole) == six.text_type)
3979
3980        assert(forward_for is None or type(forward_for) == list)
3981        if forward_for:
3982            for ff in forward_for:
3983                assert type(ff) == dict
3984                assert 'session' in ff and type(ff['session']) in six.integer_types
3985                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
3986                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
3987
3988        Message.__init__(self)
3989        self.request = request
3990        self.procedure = procedure
3991        self.args = args
3992        self.kwargs = _validate_kwargs(kwargs)
3993        self.payload = payload
3994        self.timeout = timeout
3995        self.receive_progress = receive_progress
3996
3997        # payload transparency related knobs
3998        self.enc_algo = enc_algo
3999        self.enc_key = enc_key
4000        self.enc_serializer = enc_serializer
4001
4002        # message forwarding
4003        self.caller = caller
4004        self.caller_authid = caller_authid
4005        self.caller_authrole = caller_authrole
4006        self.forward_for = forward_for
4007
4008    @staticmethod
4009    def parse(wmsg):
4010        """
4011        Verifies and parses an unserialized raw message into an actual WAMP message instance.
4012
4013        :param wmsg: The unserialized raw message.
4014        :type wmsg: list
4015
4016        :returns: An instance of this class.
4017        """
4018        # this should already be verified by WampSerializer.unserialize
4019        assert(len(wmsg) > 0 and wmsg[0] == Call.MESSAGE_TYPE)
4020
4021        if len(wmsg) not in (4, 5, 6):
4022            raise ProtocolError("invalid message length {0} for CALL".format(len(wmsg)))
4023
4024        request = check_or_raise_id(wmsg[1], u"'request' in CALL")
4025        options = check_or_raise_extra(wmsg[2], u"'options' in CALL")
4026        procedure = check_or_raise_uri(wmsg[3], u"'procedure' in CALL")
4027
4028        args = None
4029        kwargs = None
4030        payload = None
4031        enc_algo = None
4032        enc_key = None
4033        enc_serializer = None
4034
4035        if len(wmsg) == 5 and type(wmsg[4]) in [six.text_type, six.binary_type]:
4036
4037            payload = wmsg[4]
4038
4039            enc_algo = options.get(u'enc_algo', None)
4040            if enc_algo and not is_valid_enc_algo(enc_algo):
4041                raise ProtocolError("invalid value {0} for 'enc_algo' detail in CALL".format(enc_algo))
4042
4043            enc_key = options.get(u'enc_key', None)
4044            if enc_key and type(enc_key) != six.text_type:
4045                raise ProtocolError("invalid type {0} for 'enc_key' detail in CALL".format(type(enc_key)))
4046
4047            enc_serializer = options.get(u'enc_serializer', None)
4048            if enc_serializer and not is_valid_enc_serializer(enc_serializer):
4049                raise ProtocolError("invalid value {0} for 'enc_serializer' detail in CALL".format(enc_serializer))
4050
4051        else:
4052            if len(wmsg) > 4:
4053                args = wmsg[4]
4054                if args is not None and type(args) != list:
4055                    raise ProtocolError("invalid type {0} for 'args' in CALL".format(type(args)))
4056
4057            if len(wmsg) > 5:
4058                kwargs = wmsg[5]
4059                if type(kwargs) != dict:
4060                    raise ProtocolError("invalid type {0} for 'kwargs' in CALL".format(type(kwargs)))
4061
4062        timeout = None
4063        receive_progress = None
4064        caller = None
4065        caller_authid = None
4066        caller_authrole = None
4067        forward_for = None
4068
4069        if u'timeout' in options:
4070
4071            option_timeout = options[u'timeout']
4072            if type(option_timeout) not in six.integer_types:
4073                raise ProtocolError("invalid type {0} for 'timeout' option in CALL".format(type(option_timeout)))
4074
4075            if option_timeout < 0:
4076                raise ProtocolError("invalid value {0} for 'timeout' option in CALL".format(option_timeout))
4077
4078            timeout = option_timeout
4079
4080        if u'receive_progress' in options:
4081
4082            option_receive_progress = options[u'receive_progress']
4083            if type(option_receive_progress) != bool:
4084                raise ProtocolError("invalid type {0} for 'receive_progress' option in CALL".format(type(option_receive_progress)))
4085
4086            receive_progress = option_receive_progress
4087
4088        if u'caller' in options:
4089
4090            option_caller = options[u'caller']
4091            if type(option_caller) not in six.integer_types:
4092                raise ProtocolError("invalid type {0} for 'caller' detail in CALL".format(type(option_caller)))
4093
4094            caller = option_caller
4095
4096        if u'caller_authid' in options:
4097
4098            option_caller_authid = options[u'caller_authid']
4099            if type(option_caller_authid) != six.text_type:
4100                raise ProtocolError("invalid type {0} for 'caller_authid' detail in CALL".format(type(option_caller_authid)))
4101
4102            caller_authid = option_caller_authid
4103
4104        if u'caller_authrole' in options:
4105
4106            option_caller_authrole = options[u'caller_authrole']
4107            if type(option_caller_authrole) != six.text_type:
4108                raise ProtocolError("invalid type {0} for 'caller_authrole' detail in CALL".format(type(option_caller_authrole)))
4109
4110            caller_authrole = option_caller_authrole
4111
4112        if u'forward_for' in options:
4113            forward_for = options[u'forward_for']
4114            valid = False
4115            if type(forward_for) == list:
4116                for ff in forward_for:
4117                    if type(ff) != dict:
4118                        break
4119                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
4120                        break
4121                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
4122                        break
4123                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
4124                        break
4125                valid = True
4126
4127            if not valid:
4128                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in CALL")
4129
4130        obj = Call(request,
4131                   procedure,
4132                   args=args,
4133                   kwargs=kwargs,
4134                   payload=payload,
4135                   timeout=timeout,
4136                   receive_progress=receive_progress,
4137                   enc_algo=enc_algo,
4138                   enc_key=enc_key,
4139                   enc_serializer=enc_serializer,
4140                   caller=caller,
4141                   caller_authid=caller_authid,
4142                   caller_authrole=caller_authrole,
4143                   forward_for=forward_for)
4144
4145        return obj
4146
4147    def marshal_options(self):
4148        options = {}
4149
4150        if self.timeout is not None:
4151            options[u'timeout'] = self.timeout
4152
4153        if self.receive_progress is not None:
4154            options[u'receive_progress'] = self.receive_progress
4155
4156        if self.payload:
4157            if self.enc_algo is not None:
4158                options[u'enc_algo'] = self.enc_algo
4159            if self.enc_key is not None:
4160                options[u'enc_key'] = self.enc_key
4161            if self.enc_serializer is not None:
4162                options[u'enc_serializer'] = self.enc_serializer
4163
4164        if self.caller is not None:
4165            options[u'caller'] = self.caller
4166        if self.caller_authid is not None:
4167            options[u'caller_authid'] = self.caller_authid
4168        if self.caller_authrole is not None:
4169            options[u'caller_authrole'] = self.caller_authrole
4170
4171        if self.forward_for is not None:
4172            options[u'forward_for'] = self.forward_for
4173
4174        return options
4175
4176    def marshal(self):
4177        """
4178        Marshal this object into a raw message for subsequent serialization to bytes.
4179
4180        :returns: The serialized raw message.
4181        :rtype: list
4182        """
4183        options = self.marshal_options()
4184
4185        if self.payload:
4186            return [Call.MESSAGE_TYPE, self.request, options, self.procedure, self.payload]
4187        else:
4188            if self.kwargs:
4189                return [Call.MESSAGE_TYPE, self.request, options, self.procedure, self.args, self.kwargs]
4190            elif self.args:
4191                return [Call.MESSAGE_TYPE, self.request, options, self.procedure, self.args]
4192            else:
4193                return [Call.MESSAGE_TYPE, self.request, options, self.procedure]
4194
4195    def __str__(self):
4196        """
4197        Returns string representation of this message.
4198        """
4199        return u"Call(request={}, procedure={}, args={}, kwargs={}, timeout={}, receive_progress={}, enc_algo={}, enc_key={}, enc_serializer={}, payload={}, caller={}, caller_authid={}, caller_authrole={}, forward_for={})".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), self.caller, self.caller_authid, self.caller_authrole, self.forward_for)
4200
4201
4202class Cancel(Message):
4203    """
4204    A WAMP ``CANCEL`` message.
4205
4206    Format: ``[CANCEL, CALL.Request|id, Options|dict]``
4207
4208    See: https://wamp-proto.org/static/rfc/draft-oberstet-hybi-crossbar-wamp.html#rfc.section.14.3.4
4209    """
4210
4211    MESSAGE_TYPE = 49
4212    """
4213    The WAMP message code for this type of message.
4214    """
4215
4216    SKIP = u'skip'
4217    KILL = u'kill'
4218    KILLNOWAIT = u'killnowait'
4219
4220    __slots__ = (
4221        'request',
4222        'mode',
4223        'forward_for',
4224    )
4225
4226    def __init__(self, request, mode=None, forward_for=None):
4227        """
4228
4229        :param request: The WAMP request ID of the original `CALL` to cancel.
4230        :type request: int
4231
4232        :param mode: Specifies how to cancel the call (``"skip"``, ``"killnowait"`` or ``"kill"``).
4233        :type mode: str or None
4234
4235        :param forward_for: When this Cancel is forwarded for a client (or from an intermediary router).
4236        :type forward_for: list[dict]
4237        """
4238        assert(type(request) in six.integer_types)
4239        assert(mode is None or type(mode) == six.text_type)
4240        assert(mode in [None, self.SKIP, self.KILLNOWAIT, self.KILL])
4241        assert(forward_for is None or type(forward_for) == list)
4242
4243        if forward_for:
4244            for ff in forward_for:
4245                assert type(ff) == dict
4246                assert 'session' in ff and type(ff['session']) in six.integer_types
4247                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
4248                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
4249
4250        Message.__init__(self)
4251        self.request = request
4252        self.mode = mode
4253
4254        # message forwarding
4255        self.forward_for = forward_for
4256
4257    @staticmethod
4258    def parse(wmsg):
4259        """
4260        Verifies and parses an unserialized raw message into an actual WAMP message instance.
4261
4262        :param wmsg: The unserialized raw message.
4263        :type wmsg: list
4264
4265        :returns: An instance of this class.
4266        """
4267        # this should already be verified by WampSerializer.unserialize
4268        assert(len(wmsg) > 0 and wmsg[0] == Cancel.MESSAGE_TYPE)
4269
4270        if len(wmsg) != 3:
4271            raise ProtocolError("invalid message length {0} for CANCEL".format(len(wmsg)))
4272
4273        request = check_or_raise_id(wmsg[1], u"'request' in CANCEL")
4274        options = check_or_raise_extra(wmsg[2], u"'options' in CANCEL")
4275
4276        # options
4277        #
4278        mode = None
4279        forward_for = None
4280
4281        if u'mode' in options:
4282
4283            option_mode = options[u'mode']
4284            if type(option_mode) != six.text_type:
4285                raise ProtocolError("invalid type {0} for 'mode' option in CANCEL".format(type(option_mode)))
4286
4287            if option_mode not in [Cancel.SKIP, Cancel.KILLNOWAIT, Cancel.KILL]:
4288                raise ProtocolError("invalid value '{0}' for 'mode' option in CANCEL".format(option_mode))
4289
4290            mode = option_mode
4291
4292        if u'forward_for' in options:
4293            forward_for = options[u'forward_for']
4294            valid = False
4295            if type(forward_for) == list:
4296                for ff in forward_for:
4297                    if type(ff) != dict:
4298                        break
4299                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
4300                        break
4301                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
4302                        break
4303                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
4304                        break
4305                valid = True
4306
4307            if not valid:
4308                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in CANCEL")
4309
4310        obj = Cancel(request, mode=mode, forward_for=forward_for)
4311
4312        return obj
4313
4314    def marshal(self):
4315        """
4316        Marshal this object into a raw message for subsequent serialization to bytes.
4317
4318        :returns: The serialized raw message.
4319        :rtype: list
4320        """
4321        options = {}
4322
4323        if self.mode is not None:
4324            options[u'mode'] = self.mode
4325        if self.forward_for is not None:
4326            options[u'forward_for'] = self.forward_for
4327
4328        return [Cancel.MESSAGE_TYPE, self.request, options]
4329
4330    def __str__(self):
4331        """
4332        Returns string representation of this message.
4333        """
4334        return u"Cancel(request={0}, mode={1})".format(self.request, self.mode)
4335
4336
4337class Result(Message):
4338    """
4339    A WAMP ``RESULT`` message.
4340
4341    Formats:
4342
4343    * ``[RESULT, CALL.Request|id, Details|dict]``
4344    * ``[RESULT, CALL.Request|id, Details|dict, YIELD.Arguments|list]``
4345    * ``[RESULT, CALL.Request|id, Details|dict, YIELD.Arguments|list, YIELD.ArgumentsKw|dict]``
4346    * ``[RESULT, CALL.Request|id, Details|dict, Payload|binary]``
4347    """
4348
4349    MESSAGE_TYPE = 50
4350    """
4351    The WAMP message code for this type of message.
4352    """
4353
4354    __slots__ = (
4355        'request',
4356        'args',
4357        'kwargs',
4358        'payload',
4359        'progress',
4360        'enc_algo',
4361        'enc_key',
4362        'enc_serializer',
4363        'callee',
4364        'callee_authid',
4365        'callee_authrole',
4366        'forward_for',
4367    )
4368
4369    def __init__(self,
4370                 request,
4371                 args=None,
4372                 kwargs=None,
4373                 payload=None,
4374                 progress=None,
4375                 enc_algo=None,
4376                 enc_key=None,
4377                 enc_serializer=None,
4378                 callee=None,
4379                 callee_authid=None,
4380                 callee_authrole=None,
4381                 forward_for=None):
4382        """
4383
4384        :param request: The request ID of the original `CALL` request.
4385        :type request: int
4386
4387        :param args: Positional values for application-defined event payload.
4388           Must be serializable using any serializers in use.
4389        :type args: list or tuple or None
4390
4391        :param kwargs: Keyword values for application-defined event payload.
4392           Must be serializable using any serializers in use.
4393        :type kwargs: dict or None
4394
4395        :param payload: Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
4396        :type payload: bytes or None
4397
4398        :param progress: If ``True``, this result is a progressive call result, and subsequent
4399           results (or a final error) will follow.
4400        :type progress: bool or None
4401
4402        :param enc_algo: If using payload transparency, the encoding algorithm that was used to encode the payload.
4403        :type enc_algo: str or None
4404
4405        :param enc_key: If using payload transparency with an encryption algorithm, the payload encryption key.
4406        :type enc_key: str or None
4407
4408        :param enc_serializer: If using payload transparency, the payload object serializer that was used encoding the payload.
4409        :type enc_serializer: str or None
4410
4411        :param callee: The WAMP session ID of the effective callee that responded with the result. Only filled if callee is disclosed.
4412        :type callee: None or int
4413
4414        :param callee_authid: The WAMP authid of the responding callee. Only filled if callee is disclosed.
4415        :type callee_authid: None or unicode
4416
4417        :param callee_authrole: The WAMP authrole of the responding callee. Only filled if callee is disclosed.
4418        :type callee_authrole: None or unicode
4419
4420        :param forward_for: When this Result is forwarded for a client/callee (or from an intermediary router).
4421        :type forward_for: list[dict]
4422        """
4423        assert(type(request) in six.integer_types)
4424        assert(args is None or type(args) in [list, tuple])
4425        assert(kwargs is None or type(kwargs) == dict)
4426        assert(payload is None or type(payload) == six.binary_type)
4427        assert(payload is None or (payload is not None and args is None and kwargs is None))
4428        assert(progress is None or type(progress) == bool)
4429
4430        assert(enc_algo is None or is_valid_enc_algo(enc_algo))
4431        assert(enc_key is None or type(enc_key) == six.text_type)
4432        assert(enc_serializer is None or is_valid_enc_serializer(enc_serializer))
4433        assert((enc_algo is None and enc_key is None and enc_serializer is None) or (payload is not None and enc_algo is not None))
4434
4435        assert(callee is None or type(callee) in six.integer_types)
4436        assert(callee_authid is None or type(callee_authid) == six.text_type)
4437        assert(callee_authrole is None or type(callee_authrole) == six.text_type)
4438
4439        assert(forward_for is None or type(forward_for) == list)
4440        if forward_for:
4441            for ff in forward_for:
4442                assert type(ff) == dict
4443                assert 'session' in ff and type(ff['session']) in six.integer_types
4444                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
4445                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
4446
4447        Message.__init__(self)
4448        self.request = request
4449        self.args = args
4450        self.kwargs = _validate_kwargs(kwargs)
4451        self.payload = payload
4452        self.progress = progress
4453
4454        # payload transparency related knobs
4455        self.enc_algo = enc_algo
4456        self.enc_key = enc_key
4457        self.enc_serializer = enc_serializer
4458
4459        # effective callee that responded with the result
4460        self.callee = callee
4461        self.callee_authid = callee_authid
4462        self.callee_authrole = callee_authrole
4463
4464        # message forwarding
4465        self.forward_for = forward_for
4466
4467    @staticmethod
4468    def parse(wmsg):
4469        """
4470        Verifies and parses an unserialized raw message into an actual WAMP message instance.
4471
4472        :param wmsg: The unserialized raw message.
4473        :type wmsg: list
4474
4475        :returns: An instance of this class.
4476        """
4477        # this should already be verified by WampSerializer.unserialize
4478        assert(len(wmsg) > 0 and wmsg[0] == Result.MESSAGE_TYPE)
4479
4480        if len(wmsg) not in (3, 4, 5):
4481            raise ProtocolError("invalid message length {0} for RESULT".format(len(wmsg)))
4482
4483        request = check_or_raise_id(wmsg[1], u"'request' in RESULT")
4484        details = check_or_raise_extra(wmsg[2], u"'details' in RESULT")
4485
4486        args = None
4487        kwargs = None
4488        payload = None
4489        progress = None
4490        enc_algo = None
4491        enc_key = None
4492        enc_serializer = None
4493        callee = None
4494        callee_authid = None
4495        callee_authrole = None
4496        forward_for = None
4497
4498        if len(wmsg) == 4 and type(wmsg[3]) in [six.text_type, six.binary_type]:
4499
4500            payload = wmsg[3]
4501
4502            enc_algo = details.get(u'enc_algo', None)
4503            if enc_algo and not is_valid_enc_algo(enc_algo):
4504                raise ProtocolError("invalid value {0} for 'enc_algo' detail in RESULT".format(enc_algo))
4505
4506            enc_key = details.get(u'enc_key', None)
4507            if enc_key and type(enc_key) != six.text_type:
4508                raise ProtocolError("invalid type {0} for 'enc_key' detail in RESULT".format(type(enc_key)))
4509
4510            enc_serializer = details.get(u'enc_serializer', None)
4511            if enc_serializer and not is_valid_enc_serializer(enc_serializer):
4512                raise ProtocolError("invalid value {0} for 'enc_serializer' detail in RESULT".format(enc_serializer))
4513
4514        else:
4515            if len(wmsg) > 3:
4516                args = wmsg[3]
4517                if args is not None and type(args) != list:
4518                    raise ProtocolError("invalid type {0} for 'args' in RESULT".format(type(args)))
4519
4520            if len(wmsg) > 4:
4521                kwargs = wmsg[4]
4522                if type(kwargs) != dict:
4523                    raise ProtocolError("invalid type {0} for 'kwargs' in RESULT".format(type(kwargs)))
4524
4525        if u'progress' in details:
4526
4527            detail_progress = details[u'progress']
4528            if type(detail_progress) != bool:
4529                raise ProtocolError("invalid type {0} for 'progress' option in RESULT".format(type(detail_progress)))
4530
4531            progress = detail_progress
4532
4533        if u'callee' in details:
4534
4535            detail_callee = details[u'callee']
4536            if type(detail_callee) not in six.integer_types:
4537                raise ProtocolError("invalid type {0} for 'callee' detail in RESULT".format(type(detail_callee)))
4538
4539            callee = detail_callee
4540
4541        if u'callee_authid' in details:
4542
4543            detail_callee_authid = details[u'callee_authid']
4544            if type(detail_callee_authid) != six.text_type:
4545                raise ProtocolError("invalid type {0} for 'callee_authid' detail in RESULT".format(type(detail_callee_authid)))
4546
4547            callee_authid = detail_callee_authid
4548
4549        if u'callee_authrole' in details:
4550
4551            detail_callee_authrole = details[u'callee_authrole']
4552            if type(detail_callee_authrole) != six.text_type:
4553                raise ProtocolError("invalid type {0} for 'callee_authrole' detail in RESULT".format(type(detail_callee_authrole)))
4554
4555            callee_authrole = detail_callee_authrole
4556
4557        if u'forward_for' in details:
4558            forward_for = details[u'forward_for']
4559            valid = False
4560            if type(forward_for) == list:
4561                for ff in forward_for:
4562                    if type(ff) != dict:
4563                        break
4564                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
4565                        break
4566                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
4567                        break
4568                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
4569                        break
4570                valid = True
4571
4572            if not valid:
4573                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in RESULT")
4574
4575        obj = Result(request,
4576                     args=args,
4577                     kwargs=kwargs,
4578                     payload=payload,
4579                     progress=progress,
4580                     enc_algo=enc_algo,
4581                     enc_key=enc_key,
4582                     enc_serializer=enc_serializer,
4583                     callee=callee,
4584                     callee_authid=callee_authid,
4585                     callee_authrole=callee_authrole,
4586                     forward_for=forward_for)
4587
4588        return obj
4589
4590    def marshal(self):
4591        """
4592        Marshal this object into a raw message for subsequent serialization to bytes.
4593
4594        :returns: The serialized raw message.
4595        :rtype: list
4596        """
4597        details = {}
4598
4599        if self.progress is not None:
4600            details[u'progress'] = self.progress
4601
4602        if self.callee is not None:
4603            details[u'callee'] = self.callee
4604        if self.callee_authid is not None:
4605            details[u'callee_authid'] = self.callee_authid
4606        if self.callee_authrole is not None:
4607            details[u'callee_authrole'] = self.callee_authrole
4608        if self.forward_for is not None:
4609            details[u'forward_for'] = self.forward_for
4610
4611        if self.payload:
4612            if self.enc_algo is not None:
4613                details[u'enc_algo'] = self.enc_algo
4614            if self.enc_key is not None:
4615                details[u'enc_key'] = self.enc_key
4616            if self.enc_serializer is not None:
4617                details[u'enc_serializer'] = self.enc_serializer
4618            return [Result.MESSAGE_TYPE, self.request, details, self.payload]
4619        else:
4620            if self.kwargs:
4621                return [Result.MESSAGE_TYPE, self.request, details, self.args, self.kwargs]
4622            elif self.args:
4623                return [Result.MESSAGE_TYPE, self.request, details, self.args]
4624            else:
4625                return [Result.MESSAGE_TYPE, self.request, details]
4626
4627    def __str__(self):
4628        """
4629        Returns string representation of this message.
4630        """
4631        return u"Result(request={0}, args={1}, kwargs={2}, progress={3}, enc_algo={4}, enc_key={5}, enc_serializer={6}, payload={7}, callee={8}, callee_authid={9}, callee_authrole={10}, forward_for={11})".format(self.request, self.args, self.kwargs, self.progress, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload), self.callee, self.callee_authid, self.callee_authrole, self.forward_for)
4632
4633
4634class Register(Message):
4635    """
4636    A WAMP ``REGISTER`` message.
4637
4638    Format: ``[REGISTER, Request|id, Options|dict, Procedure|uri]``
4639    """
4640
4641    MESSAGE_TYPE = 64
4642    """
4643    The WAMP message code for this type of message.
4644    """
4645
4646    MATCH_EXACT = u'exact'
4647    MATCH_PREFIX = u'prefix'
4648    MATCH_WILDCARD = u'wildcard'
4649
4650    INVOKE_SINGLE = u'single'
4651    INVOKE_FIRST = u'first'
4652    INVOKE_LAST = u'last'
4653    INVOKE_ROUNDROBIN = u'roundrobin'
4654    INVOKE_RANDOM = u'random'
4655    INVOKE_ALL = u'all'
4656
4657    __slots__ = (
4658        'request',
4659        'procedure',
4660        'match',
4661        'invoke',
4662        'concurrency',
4663        'force_reregister',
4664        'forward_for',
4665    )
4666
4667    def __init__(self,
4668                 request,
4669                 procedure,
4670                 match=None,
4671                 invoke=None,
4672                 concurrency=None,
4673                 force_reregister=None,
4674                 forward_for=None):
4675        """
4676
4677        :param request: The WAMP request ID of this request.
4678        :type request: int
4679
4680        :param procedure: The WAMP or application URI of the RPC endpoint provided.
4681        :type procedure: str
4682
4683        :param match: The procedure matching policy to be used for the registration.
4684        :type match: str
4685
4686        :param invoke: The procedure invocation policy to be used for the registration.
4687        :type invoke: str
4688
4689        :param concurrency: The (maximum) concurrency to be used for the registration.
4690        :type concurrency: int
4691
4692        :param forward_for: When this Register is forwarded over a router-to-router link,
4693            or via an intermediary router.
4694        :type forward_for: list[dict]
4695        """
4696        assert(type(request) in six.integer_types)
4697        assert(type(procedure) == six.text_type)
4698        assert(match is None or type(match) == six.text_type)
4699        assert(match is None or match in [Register.MATCH_EXACT, Register.MATCH_PREFIX, Register.MATCH_WILDCARD])
4700        assert(invoke is None or type(invoke) == six.text_type)
4701        assert(invoke is None or invoke in [Register.INVOKE_SINGLE, Register.INVOKE_FIRST, Register.INVOKE_LAST, Register.INVOKE_ROUNDROBIN, Register.INVOKE_RANDOM])
4702        assert(concurrency is None or (type(concurrency) in six.integer_types and concurrency > 0))
4703        assert force_reregister in [None, True, False]
4704        assert(forward_for is None or type(forward_for) == list)
4705        if forward_for:
4706            for ff in forward_for:
4707                assert type(ff) == dict
4708                assert 'session' in ff and type(ff['session']) in six.integer_types
4709                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
4710                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
4711
4712        Message.__init__(self)
4713        self.request = request
4714        self.procedure = procedure
4715        self.match = match or Register.MATCH_EXACT
4716        self.invoke = invoke or Register.INVOKE_SINGLE
4717        self.concurrency = concurrency
4718        self.force_reregister = force_reregister
4719        self.forward_for = forward_for
4720
4721    @staticmethod
4722    def parse(wmsg):
4723        """
4724        Verifies and parses an unserialized raw message into an actual WAMP message instance.
4725
4726        :param wmsg: The unserialized raw message.
4727        :type wmsg: list
4728
4729        :returns: An instance of this class.
4730        """
4731        # this should already be verified by WampSerializer.unserialize
4732        assert(len(wmsg) > 0 and wmsg[0] == Register.MESSAGE_TYPE)
4733
4734        if len(wmsg) != 4:
4735            raise ProtocolError("invalid message length {0} for REGISTER".format(len(wmsg)))
4736
4737        request = check_or_raise_id(wmsg[1], u"'request' in REGISTER")
4738        options = check_or_raise_extra(wmsg[2], u"'options' in REGISTER")
4739
4740        match = Register.MATCH_EXACT
4741        invoke = Register.INVOKE_SINGLE
4742        concurrency = None
4743        force_reregister = None
4744        forward_for = None
4745
4746        if u'match' in options:
4747
4748            option_match = options[u'match']
4749            if type(option_match) != six.text_type:
4750                raise ProtocolError("invalid type {0} for 'match' option in REGISTER".format(type(option_match)))
4751
4752            if option_match not in [Register.MATCH_EXACT, Register.MATCH_PREFIX, Register.MATCH_WILDCARD]:
4753                raise ProtocolError("invalid value {0} for 'match' option in REGISTER".format(option_match))
4754
4755            match = option_match
4756
4757        if match == Register.MATCH_EXACT:
4758            allow_empty_components = False
4759            allow_last_empty = False
4760
4761        elif match == Register.MATCH_PREFIX:
4762            allow_empty_components = False
4763            allow_last_empty = True
4764
4765        elif match == Register.MATCH_WILDCARD:
4766            allow_empty_components = True
4767            allow_last_empty = False
4768
4769        else:
4770            raise Exception("logic error")
4771
4772        procedure = check_or_raise_uri(wmsg[3], u"'procedure' in REGISTER", allow_empty_components=allow_empty_components, allow_last_empty=allow_last_empty)
4773
4774        if u'invoke' in options:
4775
4776            option_invoke = options[u'invoke']
4777            if type(option_invoke) != six.text_type:
4778                raise ProtocolError("invalid type {0} for 'invoke' option in REGISTER".format(type(option_invoke)))
4779
4780            if option_invoke not in [Register.INVOKE_SINGLE, Register.INVOKE_FIRST, Register.INVOKE_LAST, Register.INVOKE_ROUNDROBIN, Register.INVOKE_RANDOM]:
4781                raise ProtocolError("invalid value {0} for 'invoke' option in REGISTER".format(option_invoke))
4782
4783            invoke = option_invoke
4784
4785        if u'concurrency' in options:
4786
4787            options_concurrency = options[u'concurrency']
4788            if type(options_concurrency) not in six.integer_types:
4789                raise ProtocolError("invalid type {0} for 'concurrency' option in REGISTER".format(type(options_concurrency)))
4790
4791            if options_concurrency < 1:
4792                raise ProtocolError("invalid value {0} for 'concurrency' option in REGISTER".format(options_concurrency))
4793
4794            concurrency = options_concurrency
4795
4796        options_reregister = options.get(u'force_reregister', None)
4797        if options_reregister not in [True, False, None]:
4798            raise ProtocolError(
4799                "invalid type {0} for 'force_reregister option in REGISTER".format(
4800                    type(options_reregister)
4801                )
4802            )
4803        if options_reregister is not None:
4804            force_reregister = options_reregister
4805
4806        if u'forward_for' in options:
4807            forward_for = options[u'forward_for']
4808            valid = False
4809            if type(forward_for) == list:
4810                for ff in forward_for:
4811                    if type(ff) != dict:
4812                        break
4813                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
4814                        break
4815                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
4816                        break
4817                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
4818                        break
4819                valid = True
4820
4821            if not valid:
4822                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in REGISTER")
4823
4824        obj = Register(request, procedure, match=match, invoke=invoke, concurrency=concurrency,
4825                       force_reregister=force_reregister, forward_for=forward_for)
4826
4827        return obj
4828
4829    def marshal_options(self):
4830        options = {}
4831
4832        if self.match and self.match != Register.MATCH_EXACT:
4833            options[u'match'] = self.match
4834
4835        if self.invoke and self.invoke != Register.INVOKE_SINGLE:
4836            options[u'invoke'] = self.invoke
4837
4838        if self.concurrency:
4839            options[u'concurrency'] = self.concurrency
4840
4841        if self.force_reregister is not None:
4842            options[u'force_reregister'] = self.force_reregister
4843
4844        if self.forward_for is not None:
4845            options[u'forward_for'] = self.forward_for
4846
4847        return options
4848
4849    def marshal(self):
4850        """
4851        Marshal this object into a raw message for subsequent serialization to bytes.
4852
4853        :returns: The serialized raw message.
4854        :rtype: list
4855        """
4856        return [Register.MESSAGE_TYPE, self.request, self.marshal_options(), self.procedure]
4857
4858    def __str__(self):
4859        """
4860        Returns string representation of this message.
4861        """
4862        return u"Register(request={0}, procedure={1}, match={2}, invoke={3}, concurrency={4}, force_reregister={5}, forward_for={6})".format(self.request, self.procedure, self.match, self.invoke, self.concurrency, self.force_reregister, self.forward_for)
4863
4864
4865class Registered(Message):
4866    """
4867    A WAMP ``REGISTERED`` message.
4868
4869    Format: ``[REGISTERED, REGISTER.Request|id, Registration|id]``
4870    """
4871
4872    MESSAGE_TYPE = 65
4873    """
4874    The WAMP message code for this type of message.
4875    """
4876
4877    __slots__ = (
4878        'request',
4879        'registration',
4880    )
4881
4882    def __init__(self, request, registration):
4883        """
4884
4885        :param request: The request ID of the original ``REGISTER`` request.
4886        :type request: int
4887
4888        :param registration: The registration ID for the registered procedure (or procedure pattern).
4889        :type registration: int
4890        """
4891        assert(type(request) in six.integer_types)
4892        assert(type(registration) in six.integer_types)
4893
4894        Message.__init__(self)
4895        self.request = request
4896        self.registration = registration
4897
4898    @staticmethod
4899    def parse(wmsg):
4900        """
4901        Verifies and parses an unserialized raw message into an actual WAMP message instance.
4902
4903        :param wmsg: The unserialized raw message.
4904        :type wmsg: list
4905
4906        :returns: An instance of this class.
4907        """
4908        # this should already be verified by WampSerializer.unserialize
4909        assert(len(wmsg) > 0 and wmsg[0] == Registered.MESSAGE_TYPE)
4910
4911        if len(wmsg) != 3:
4912            raise ProtocolError("invalid message length {0} for REGISTERED".format(len(wmsg)))
4913
4914        request = check_or_raise_id(wmsg[1], u"'request' in REGISTERED")
4915        registration = check_or_raise_id(wmsg[2], u"'registration' in REGISTERED")
4916
4917        obj = Registered(request, registration)
4918
4919        return obj
4920
4921    def marshal(self):
4922        """
4923        Marshal this object into a raw message for subsequent serialization to bytes.
4924
4925        :returns: The serialized raw message.
4926        :rtype: list
4927        """
4928        return [Registered.MESSAGE_TYPE, self.request, self.registration]
4929
4930    def __str__(self):
4931        """
4932        Returns string representation of this message.
4933        """
4934        return u"Registered(request={0}, registration={1})".format(self.request, self.registration)
4935
4936
4937class Unregister(Message):
4938    """
4939    A WAMP `UNREGISTER` message.
4940
4941    Formats:
4942
4943    * ``[UNREGISTER, Request|id, REGISTERED.Registration|id]``
4944    * ``[UNREGISTER, Request|id, REGISTERED.Registration|id, Options|dict]``
4945    """
4946
4947    MESSAGE_TYPE = 66
4948    """
4949    The WAMP message code for this type of message.
4950    """
4951
4952    __slots__ = (
4953        'request',
4954        'registration',
4955        'forward_for',
4956    )
4957
4958    def __init__(self, request, registration, forward_for=None):
4959        """
4960
4961        :param request: The WAMP request ID of this request.
4962        :type request: int
4963
4964        :param registration: The registration ID for the registration to unregister.
4965        :type registration: int
4966
4967        :param forward_for: When this Unregister is forwarded over a router-to-router link,
4968            or via an intermediary router.
4969        :type forward_for: list[dict]
4970        """
4971        assert(type(request) in six.integer_types)
4972        assert(type(registration) in six.integer_types)
4973
4974        Message.__init__(self)
4975        self.request = request
4976        self.registration = registration
4977        self.forward_for = forward_for
4978
4979    @staticmethod
4980    def parse(wmsg):
4981        """
4982        Verifies and parses an unserialized raw message into an actual WAMP message instance.
4983
4984        :param wmsg: The unserialized raw message.
4985        :type wmsg: list
4986
4987        :returns: An instance of this class.
4988        """
4989        # this should already be verified by WampSerializer.unserialize
4990        assert(len(wmsg) > 0 and wmsg[0] == Unregister.MESSAGE_TYPE)
4991
4992        if len(wmsg) not in [3, 4]:
4993            raise ProtocolError("invalid message length {0} for WAMP UNREGISTER".format(len(wmsg)))
4994
4995        request = check_or_raise_id(wmsg[1], u"'request' in UNREGISTER")
4996        registration = check_or_raise_id(wmsg[2], u"'registration' in UNREGISTER")
4997
4998        options = None
4999        if len(wmsg) > 3:
5000            options = check_or_raise_extra(wmsg[3], u"'options' in UNREGISTER")
5001
5002        forward_for = None
5003        if options and u'forward_for' in options:
5004            forward_for = options[u'forward_for']
5005            valid = False
5006            if type(forward_for) == list:
5007                for ff in forward_for:
5008                    if type(ff) != dict:
5009                        break
5010                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
5011                        break
5012                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
5013                        break
5014                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
5015                        break
5016                valid = True
5017
5018            if not valid:
5019                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in UNREGISTER")
5020
5021        obj = Unregister(request, registration, forward_for=forward_for)
5022
5023        return obj
5024
5025    def marshal(self):
5026        """
5027        Marshal this object into a raw message for subsequent serialization to bytes.
5028
5029        :returns: The serialized raw message.
5030        :rtype: list
5031        """
5032        if self.forward_for:
5033            options = {
5034                u'forward_for': self.forward_for,
5035            }
5036            return [Unregister.MESSAGE_TYPE, self.request, self.registration, options]
5037        else:
5038            return [Unregister.MESSAGE_TYPE, self.request, self.registration]
5039
5040    def __str__(self):
5041        """
5042        Returns string representation of this message.
5043        """
5044        return u"Unregister(request={0}, registration={1})".format(self.request, self.registration)
5045
5046
5047class Unregistered(Message):
5048    """
5049    A WAMP ``UNREGISTERED`` message.
5050
5051    Formats:
5052
5053    * ``[UNREGISTERED, UNREGISTER.Request|id]``
5054    * ``[UNREGISTERED, UNREGISTER.Request|id, Details|dict]``
5055    """
5056
5057    MESSAGE_TYPE = 67
5058    """
5059    The WAMP message code for this type of message.
5060    """
5061
5062    __slots__ = (
5063        'request',
5064        'registration',
5065        'reason',
5066    )
5067
5068    def __init__(self, request, registration=None, reason=None):
5069        """
5070
5071        :param request: The request ID of the original ``UNREGISTER`` request.
5072        :type request: int
5073
5074        :param registration: If unregister was actively triggered by router, the ID
5075            of the registration revoked.
5076        :type registration: int or None
5077
5078        :param reason: The reason (an URI) for revocation.
5079        :type reason: str or None.
5080        """
5081        assert(type(request) in six.integer_types)
5082        assert(registration is None or type(registration) in six.integer_types)
5083        assert(reason is None or type(reason) == six.text_type)
5084        assert((request != 0 and registration is None) or (request == 0 and registration != 0))
5085
5086        Message.__init__(self)
5087        self.request = request
5088        self.registration = registration
5089        self.reason = reason
5090
5091    @staticmethod
5092    def parse(wmsg):
5093        """
5094        Verifies and parses an unserialized raw message into an actual WAMP message instance.
5095
5096        :param wmsg: The unserialized raw message.
5097        :type wmsg: list
5098
5099        :returns: An instance of this class.
5100        """
5101        # this should already be verified by WampSerializer.unserialize
5102        assert(len(wmsg) > 0 and wmsg[0] == Unregistered.MESSAGE_TYPE)
5103
5104        if len(wmsg) not in [2, 3]:
5105            raise ProtocolError("invalid message length {0} for UNREGISTERED".format(len(wmsg)))
5106
5107        request = check_or_raise_id(wmsg[1], u"'request' in UNREGISTERED")
5108
5109        registration = None
5110        reason = None
5111
5112        if len(wmsg) > 2:
5113
5114            details = check_or_raise_extra(wmsg[2], u"'details' in UNREGISTERED")
5115
5116            if u"registration" in details:
5117                details_registration = details[u"registration"]
5118                if type(details_registration) not in six.integer_types:
5119                    raise ProtocolError("invalid type {0} for 'registration' detail in UNREGISTERED".format(type(details_registration)))
5120                registration = details_registration
5121
5122            if u"reason" in details:
5123                reason = check_or_raise_uri(details[u"reason"], u"'reason' in UNREGISTERED")
5124
5125        obj = Unregistered(request, registration, reason)
5126
5127        return obj
5128
5129    def marshal(self):
5130        """
5131        Marshal this object into a raw message for subsequent serialization to bytes.
5132
5133        :returns: The serialized raw message.
5134        :rtype: list
5135        """
5136        if self.reason is not None or self.registration is not None:
5137            details = {}
5138            if self.reason is not None:
5139                details[u"reason"] = self.reason
5140            if self.registration is not None:
5141                details[u"registration"] = self.registration
5142            return [Unregistered.MESSAGE_TYPE, self.request, details]
5143        else:
5144            return [Unregistered.MESSAGE_TYPE, self.request]
5145
5146    def __str__(self):
5147        """
5148        Returns string representation of this message.
5149        """
5150        return u"Unregistered(request={0}, reason={1}, registration={2})".format(self.request, self.reason, self.registration)
5151
5152
5153class Invocation(Message):
5154    """
5155    A WAMP ``INVOCATION`` message.
5156
5157    Formats:
5158
5159    * ``[INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict]``
5160    * ``[INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict, CALL.Arguments|list]``
5161    * ``[INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict, CALL.Arguments|list, CALL.ArgumentsKw|dict]``
5162    * ``[INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict, Payload|binary]``
5163    """
5164
5165    MESSAGE_TYPE = 68
5166    """
5167    The WAMP message code for this type of message.
5168    """
5169
5170    __slots__ = (
5171        'request',
5172        'registration',
5173        'args',
5174        'kwargs',
5175        'payload',
5176        'timeout',
5177        'receive_progress',
5178        'caller',
5179        'caller_authid',
5180        'caller_authrole',
5181        'procedure',
5182        'enc_algo',
5183        'enc_key',
5184        'enc_serializer',
5185        'forward_for',
5186    )
5187
5188    def __init__(self,
5189                 request,
5190                 registration,
5191                 args=None,
5192                 kwargs=None,
5193                 payload=None,
5194                 timeout=None,
5195                 receive_progress=None,
5196                 caller=None,
5197                 caller_authid=None,
5198                 caller_authrole=None,
5199                 procedure=None,
5200                 enc_algo=None,
5201                 enc_key=None,
5202                 enc_serializer=None,
5203                 forward_for=None):
5204        """
5205
5206        :param request: The WAMP request ID of this request.
5207        :type request: int
5208
5209        :param registration: The registration ID of the endpoint to be invoked.
5210        :type registration: int
5211
5212        :param args: Positional values for application-defined event payload.
5213           Must be serializable using any serializers in use.
5214        :type args: list or tuple or None
5215
5216        :param kwargs: Keyword values for application-defined event payload.
5217           Must be serializable using any serializers in use.
5218        :type kwargs: dict or None
5219
5220        :param payload: Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
5221        :type payload: bytes or None
5222
5223        :param timeout: If present, let the callee automatically cancels
5224           the invocation after this ms.
5225        :type timeout: int or None
5226
5227        :param receive_progress: Indicates if the callee should produce progressive results.
5228        :type receive_progress: bool or None
5229
5230        :param caller: The WAMP session ID of the caller. Only filled if caller is disclosed.
5231        :type caller: None or int
5232
5233        :param caller_authid: The WAMP authid of the caller. Only filled if caller is disclosed.
5234        :type caller_authid: None or unicode
5235
5236        :param caller_authrole: The WAMP authrole of the caller. Only filled if caller is disclosed.
5237        :type caller_authrole: None or unicode
5238
5239        :param procedure: For pattern-based registrations, the invocation MUST include the actual procedure being called.
5240        :type procedure: str or None
5241
5242        :param enc_algo: If using payload transparency, the encoding algorithm that was used to encode the payload.
5243        :type enc_algo: str or None
5244
5245        :param enc_key: If using payload transparency with an encryption algorithm, the payload encryption key.
5246        :type enc_key: str or None
5247
5248        :param enc_serializer: If using payload transparency, the payload object serializer that was used encoding the payload.
5249        :type enc_serializer: str or None
5250
5251        :param forward_for: When this Call is forwarded for a client (or from an intermediary router).
5252        :type forward_for: list[dict]
5253        """
5254        assert(type(request) in six.integer_types)
5255        assert(type(registration) in six.integer_types)
5256        assert(args is None or type(args) in [list, tuple])
5257        assert(kwargs is None or type(kwargs) == dict)
5258        assert(payload is None or type(payload) == six.binary_type)
5259        assert(payload is None or (payload is not None and args is None and kwargs is None))
5260        assert(timeout is None or type(timeout) in six.integer_types)
5261        assert(receive_progress is None or type(receive_progress) == bool)
5262        assert(caller is None or type(caller) in six.integer_types)
5263        assert(caller_authid is None or type(caller_authid) == six.text_type)
5264        assert(caller_authrole is None or type(caller_authrole) == six.text_type)
5265        assert(procedure is None or type(procedure) == six.text_type)
5266        assert(enc_algo is None or is_valid_enc_algo(enc_algo))
5267        assert(enc_key is None or type(enc_key) == six.text_type)
5268        assert(enc_serializer is None or is_valid_enc_serializer(enc_serializer))
5269        assert((enc_algo is None and enc_key is None and enc_serializer is None) or (payload is not None and enc_algo is not None))
5270
5271        assert(forward_for is None or type(forward_for) == list)
5272        if forward_for:
5273            for ff in forward_for:
5274                assert type(ff) == dict
5275                assert 'session' in ff and type(ff['session']) in six.integer_types
5276                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
5277                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
5278
5279        Message.__init__(self)
5280        self.request = request
5281        self.registration = registration
5282        self.args = args
5283        self.kwargs = _validate_kwargs(kwargs)
5284        self.payload = payload
5285        self.timeout = timeout
5286        self.receive_progress = receive_progress
5287        self.caller = caller
5288        self.caller_authid = caller_authid
5289        self.caller_authrole = caller_authrole
5290        self.procedure = procedure
5291        self.enc_algo = enc_algo
5292        self.enc_key = enc_key
5293        self.enc_serializer = enc_serializer
5294
5295        # message forwarding
5296        self.forward_for = forward_for
5297
5298    @staticmethod
5299    def parse(wmsg):
5300        """
5301        Verifies and parses an unserialized raw message into an actual WAMP message instance.
5302
5303        :param wmsg: The unserialized raw message.
5304        :type wmsg: list
5305
5306        :returns: An instance of this class.
5307        """
5308        # this should already be verified by WampSerializer.unserialize
5309        assert(len(wmsg) > 0 and wmsg[0] == Invocation.MESSAGE_TYPE)
5310
5311        if len(wmsg) not in (4, 5, 6):
5312            raise ProtocolError("invalid message length {0} for INVOCATION".format(len(wmsg)))
5313
5314        request = check_or_raise_id(wmsg[1], u"'request' in INVOCATION")
5315        registration = check_or_raise_id(wmsg[2], u"'registration' in INVOCATION")
5316        details = check_or_raise_extra(wmsg[3], u"'details' in INVOCATION")
5317
5318        args = None
5319        kwargs = None
5320        payload = None
5321        enc_algo = None
5322        enc_key = None
5323        enc_serializer = None
5324
5325        if len(wmsg) == 5 and type(wmsg[4]) == six.binary_type:
5326
5327            payload = wmsg[4]
5328
5329            enc_algo = details.get(u'enc_algo', None)
5330            if enc_algo and not is_valid_enc_algo(enc_algo):
5331                raise ProtocolError("invalid value {0} for 'enc_algo' detail in INVOCATION".format(enc_algo))
5332
5333            enc_key = details.get(u'enc_key', None)
5334            if enc_key and type(enc_key) != six.text_type:
5335                raise ProtocolError("invalid type {0} for 'enc_key' detail in INVOCATION".format(type(enc_key)))
5336
5337            enc_serializer = details.get(u'enc_serializer', None)
5338            if enc_serializer and not is_valid_enc_serializer(enc_serializer):
5339                raise ProtocolError("invalid value {0} for 'enc_serializer' detail in INVOCATION".format(enc_serializer))
5340
5341        else:
5342            if len(wmsg) > 4:
5343                args = wmsg[4]
5344                if args is not None and type(args) != list:
5345                    raise ProtocolError("invalid type {0} for 'args' in INVOCATION".format(type(args)))
5346
5347            if len(wmsg) > 5:
5348                kwargs = wmsg[5]
5349                if type(kwargs) != dict:
5350                    raise ProtocolError("invalid type {0} for 'kwargs' in INVOCATION".format(type(kwargs)))
5351
5352        timeout = None
5353        receive_progress = None
5354        caller = None
5355        caller_authid = None
5356        caller_authrole = None
5357        procedure = None
5358        forward_for = None
5359
5360        if u'timeout' in details:
5361
5362            detail_timeout = details[u'timeout']
5363            if type(detail_timeout) not in six.integer_types:
5364                raise ProtocolError("invalid type {0} for 'timeout' detail in INVOCATION".format(type(detail_timeout)))
5365
5366            if detail_timeout < 0:
5367                raise ProtocolError("invalid value {0} for 'timeout' detail in INVOCATION".format(detail_timeout))
5368
5369            timeout = detail_timeout
5370
5371        if u'receive_progress' in details:
5372
5373            detail_receive_progress = details[u'receive_progress']
5374            if type(detail_receive_progress) != bool:
5375                raise ProtocolError("invalid type {0} for 'receive_progress' detail in INVOCATION".format(type(detail_receive_progress)))
5376
5377            receive_progress = detail_receive_progress
5378
5379        if u'caller' in details:
5380
5381            detail_caller = details[u'caller']
5382            if type(detail_caller) not in six.integer_types:
5383                raise ProtocolError("invalid type {0} for 'caller' detail in INVOCATION".format(type(detail_caller)))
5384
5385            caller = detail_caller
5386
5387        if u'caller_authid' in details:
5388
5389            detail_caller_authid = details[u'caller_authid']
5390            if type(detail_caller_authid) != six.text_type:
5391                raise ProtocolError("invalid type {0} for 'caller_authid' detail in INVOCATION".format(type(detail_caller_authid)))
5392
5393            caller_authid = detail_caller_authid
5394
5395        if u'caller_authrole' in details:
5396
5397            detail_caller_authrole = details[u'caller_authrole']
5398            if type(detail_caller_authrole) != six.text_type:
5399                raise ProtocolError("invalid type {0} for 'caller_authrole' detail in INVOCATION".format(type(detail_caller_authrole)))
5400
5401            caller_authrole = detail_caller_authrole
5402
5403        if u'procedure' in details:
5404
5405            detail_procedure = details[u'procedure']
5406            if type(detail_procedure) != six.text_type:
5407                raise ProtocolError("invalid type {0} for 'procedure' detail in INVOCATION".format(type(detail_procedure)))
5408
5409            procedure = detail_procedure
5410
5411        if u'forward_for' in details:
5412            forward_for = details[u'forward_for']
5413            valid = False
5414            if type(forward_for) == list:
5415                for ff in forward_for:
5416                    if type(ff) != dict:
5417                        break
5418                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
5419                        break
5420                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
5421                        break
5422                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
5423                        break
5424                valid = True
5425
5426            if not valid:
5427                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in INVOCATION")
5428
5429        obj = Invocation(request,
5430                         registration,
5431                         args=args,
5432                         kwargs=kwargs,
5433                         payload=payload,
5434                         timeout=timeout,
5435                         receive_progress=receive_progress,
5436                         caller=caller,
5437                         caller_authid=caller_authid,
5438                         caller_authrole=caller_authrole,
5439                         procedure=procedure,
5440                         enc_algo=enc_algo,
5441                         enc_key=enc_key,
5442                         enc_serializer=enc_serializer,
5443                         forward_for=forward_for)
5444
5445        return obj
5446
5447    def marshal(self):
5448        """
5449        Marshal this object into a raw message for subsequent serialization to bytes.
5450
5451        :returns: The serialized raw message.
5452        :rtype: list
5453        """
5454        options = {}
5455
5456        if self.timeout is not None:
5457            options[u'timeout'] = self.timeout
5458
5459        if self.receive_progress is not None:
5460            options[u'receive_progress'] = self.receive_progress
5461
5462        if self.caller is not None:
5463            options[u'caller'] = self.caller
5464
5465        if self.caller_authid is not None:
5466            options[u'caller_authid'] = self.caller_authid
5467
5468        if self.caller_authrole is not None:
5469            options[u'caller_authrole'] = self.caller_authrole
5470
5471        if self.procedure is not None:
5472            options[u'procedure'] = self.procedure
5473
5474        if self.forward_for is not None:
5475            options[u'forward_for'] = self.forward_for
5476
5477        if self.payload:
5478            if self.enc_algo is not None:
5479                options[u'enc_algo'] = self.enc_algo
5480            if self.enc_key is not None:
5481                options[u'enc_key'] = self.enc_key
5482            if self.enc_serializer is not None:
5483                options[u'enc_serializer'] = self.enc_serializer
5484            return [Invocation.MESSAGE_TYPE, self.request, self.registration, options, self.payload]
5485        else:
5486            if self.kwargs:
5487                return [Invocation.MESSAGE_TYPE, self.request, self.registration, options, self.args, self.kwargs]
5488            elif self.args:
5489                return [Invocation.MESSAGE_TYPE, self.request, self.registration, options, self.args]
5490            else:
5491                return [Invocation.MESSAGE_TYPE, self.request, self.registration, options]
5492
5493    def __str__(self):
5494        """
5495        Returns string representation of this message.
5496        """
5497        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))
5498
5499
5500class Interrupt(Message):
5501    """
5502    A WAMP ``INTERRUPT`` message.
5503
5504    Format: ``[INTERRUPT, INVOCATION.Request|id, Options|dict]``
5505
5506    See: https://wamp-proto.org/static/rfc/draft-oberstet-hybi-crossbar-wamp.html#rfc.section.14.3.4
5507    """
5508
5509    MESSAGE_TYPE = 69
5510    """
5511    The WAMP message code for this type of message.
5512    """
5513
5514    KILL = u'kill'
5515    KILLNOWAIT = u'killnowait'
5516
5517    __slots__ = (
5518        'request',
5519        'mode',
5520        'reason',
5521        'forward_for',
5522    )
5523
5524    def __init__(self, request, mode=None, reason=None, forward_for=None):
5525        """
5526
5527        :param request: The WAMP request ID of the original ``INVOCATION`` to interrupt.
5528        :type request: int
5529
5530        :param mode: Specifies how to interrupt the invocation (``"killnowait"`` or ``"kill"``).
5531            With ``"kill"``, the router will wait for the callee to return an ERROR before
5532            proceeding (sending back an ERROR to the original caller). With ``"killnowait"`` the
5533            router will immediately proceed (on the caller side returning an ERROR) - but still
5534            expects the callee to send an ERROR to conclude the message exchange for the inflight
5535            call.
5536        :type mode: str or None
5537
5538        :param reason: The reason (an URI) for the invocation interrupt, eg actively
5539            triggered by the caller (``"wamp.error.canceled"`` - ApplicationError.CANCELED) or
5540            passively because of timeout (``"wamp.error.timeout"`` - ApplicationError.TIMEOUT).
5541        :type reason: str or None.
5542
5543        :param forward_for: When this Call is forwarded for a client (or from an intermediary router).
5544        :type forward_for: list[dict]
5545        """
5546        assert(type(request) in six.integer_types)
5547        assert(mode is None or type(mode) == six.text_type)
5548        assert(mode is None or mode in [self.KILL, self.KILLNOWAIT])
5549        assert(reason is None or type(reason) == six.text_type)
5550
5551        assert(forward_for is None or type(forward_for) == list)
5552        if forward_for:
5553            for ff in forward_for:
5554                assert type(ff) == dict
5555                assert 'session' in ff and type(ff['session']) in six.integer_types
5556                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
5557                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
5558
5559        Message.__init__(self)
5560        self.request = request
5561        self.mode = mode
5562        self.reason = reason
5563
5564        # message forwarding
5565        self.forward_for = forward_for
5566
5567    @staticmethod
5568    def parse(wmsg):
5569        """
5570        Verifies and parses an unserialized raw message into an actual WAMP message instance.
5571
5572        :param wmsg: The unserialized raw message.
5573        :type wmsg: list
5574
5575        :returns: An instance of this class.
5576        """
5577        # this should already be verified by WampSerializer.unserialize
5578        assert(len(wmsg) > 0 and wmsg[0] == Interrupt.MESSAGE_TYPE)
5579
5580        if len(wmsg) != 3:
5581            raise ProtocolError("invalid message length {0} for INTERRUPT".format(len(wmsg)))
5582
5583        request = check_or_raise_id(wmsg[1], u"'request' in INTERRUPT")
5584        options = check_or_raise_extra(wmsg[2], u"'options' in INTERRUPT")
5585
5586        # options
5587        #
5588        mode = None
5589        reason = None
5590        forward_for = None
5591
5592        if u'mode' in options:
5593
5594            option_mode = options[u'mode']
5595            if type(option_mode) != six.text_type:
5596                raise ProtocolError("invalid type {0} for 'mode' option in INTERRUPT".format(type(option_mode)))
5597
5598            if option_mode not in [Interrupt.KILL, Interrupt.KILLNOWAIT]:
5599                raise ProtocolError("invalid value '{0}' for 'mode' option in INTERRUPT".format(option_mode))
5600
5601            mode = option_mode
5602
5603        if u'reason' in options:
5604            reason = check_or_raise_uri(options[u'reason'], u'"reason" in INTERRUPT')
5605
5606        if u'forward_for' in options:
5607            forward_for = options[u'forward_for']
5608            valid = False
5609            if type(forward_for) == list:
5610                for ff in forward_for:
5611                    if type(ff) != dict:
5612                        break
5613                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
5614                        break
5615                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
5616                        break
5617                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
5618                        break
5619                valid = True
5620
5621            if not valid:
5622                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in INTERRUPT")
5623
5624        obj = Interrupt(request, mode=mode, reason=reason, forward_for=forward_for)
5625
5626        return obj
5627
5628    def marshal(self):
5629        """
5630        Marshal this object into a raw message for subsequent serialization to bytes.
5631
5632        :returns: The serialized raw message.
5633        :rtype: list
5634        """
5635        options = {}
5636
5637        if self.mode is not None:
5638            options[u'mode'] = self.mode
5639
5640        if self.reason is not None:
5641            options[u'reason'] = self.reason
5642
5643        if self.forward_for is not None:
5644            options[u'forward_for'] = self.forward_for
5645
5646        return [Interrupt.MESSAGE_TYPE, self.request, options]
5647
5648    def __str__(self):
5649        """
5650        Returns string representation of this message.
5651        """
5652        return u"Interrupt(request={0}, mode={1}, reason={2})".format(self.request, self.mode, self.reason)
5653
5654
5655class Yield(Message):
5656    """
5657    A WAMP ``YIELD`` message.
5658
5659    Formats:
5660
5661    * ``[YIELD, INVOCATION.Request|id, Options|dict]``
5662    * ``[YIELD, INVOCATION.Request|id, Options|dict, Arguments|list]``
5663    * ``[YIELD, INVOCATION.Request|id, Options|dict, Arguments|list, ArgumentsKw|dict]``
5664    * ``[YIELD, INVOCATION.Request|id, Options|dict, Payload|binary]``
5665    """
5666
5667    MESSAGE_TYPE = 70
5668    """
5669    The WAMP message code for this type of message.
5670    """
5671
5672    __slots__ = (
5673        'request',
5674        'args',
5675        'kwargs',
5676        'payload',
5677        'progress',
5678        'enc_algo',
5679        'enc_key',
5680        'enc_serializer',
5681        'callee',
5682        'callee_authid',
5683        'callee_authrole',
5684        'forward_for',
5685    )
5686
5687    def __init__(self,
5688                 request,
5689                 args=None,
5690                 kwargs=None,
5691                 payload=None,
5692                 progress=None,
5693                 enc_algo=None,
5694                 enc_key=None,
5695                 enc_serializer=None,
5696                 callee=None,
5697                 callee_authid=None,
5698                 callee_authrole=None,
5699                 forward_for=None):
5700        """
5701
5702        :param request: The WAMP request ID of the original call.
5703        :type request: int
5704
5705        :param args: Positional values for application-defined event payload.
5706           Must be serializable using any serializers in use.
5707        :type args: list or tuple or None
5708
5709        :param kwargs: Keyword values for application-defined event payload.
5710           Must be serializable using any serializers in use.
5711        :type kwargs: dict or None
5712
5713        :param payload: Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset.
5714        :type payload: bytes or None
5715
5716        :param progress: If ``True``, this result is a progressive invocation result, and subsequent
5717           results (or a final error) will follow.
5718        :type progress: bool or None
5719
5720        :param enc_algo: If using payload transparency, the encoding algorithm that was used to encode the payload.
5721        :type enc_algo: str or None
5722
5723        :param enc_key: If using payload transparency with an encryption algorithm, the payload encryption key.
5724        :type enc_key: str or None
5725
5726        :param enc_serializer: If using payload transparency, the payload object serializer that was used encoding the payload.
5727        :type enc_serializer: str or None
5728
5729        :param callee: The WAMP session ID of the effective callee that responded with the error. Only filled if callee is disclosed.
5730        :type callee: None or int
5731
5732        :param callee_authid: The WAMP authid of the responding callee. Only filled if callee is disclosed.
5733        :type callee_authid: None or unicode
5734
5735        :param callee_authrole: The WAMP authrole of the responding callee. Only filled if callee is disclosed.
5736        :type callee_authrole: None or unicode
5737
5738        :param forward_for: When this Call is forwarded for a client (or from an intermediary router).
5739        :type forward_for: list[dict]
5740        """
5741        assert(type(request) in six.integer_types)
5742        assert(args is None or type(args) in [list, tuple])
5743        assert(kwargs is None or type(kwargs) == dict)
5744        assert(payload is None or type(payload) == six.binary_type)
5745        assert(payload is None or (payload is not None and args is None and kwargs is None))
5746        assert(progress is None or type(progress) == bool)
5747        assert(enc_algo is None or is_valid_enc_algo(enc_algo))
5748        assert((enc_algo is None and enc_key is None and enc_serializer is None) or (payload is not None and enc_algo is not None))
5749        assert(enc_key is None or type(enc_key) == six.text_type)
5750        assert(enc_serializer is None or is_valid_enc_serializer(enc_serializer))
5751
5752        assert(callee is None or type(callee) in six.integer_types)
5753        assert(callee_authid is None or type(callee_authid) == six.text_type)
5754        assert(callee_authrole is None or type(callee_authrole) == six.text_type)
5755
5756        assert(forward_for is None or type(forward_for) == list)
5757        if forward_for:
5758            for ff in forward_for:
5759                assert type(ff) == dict
5760                assert 'session' in ff and type(ff['session']) in six.integer_types
5761                assert 'authid' in ff and (ff['authid'] is None or type(ff['authid']) == six.text_type)
5762                assert 'authrole' in ff and type(ff['authrole']) == six.text_type
5763
5764        Message.__init__(self)
5765        self.request = request
5766        self.args = args
5767        self.kwargs = _validate_kwargs(kwargs)
5768        self.payload = payload
5769        self.progress = progress
5770        self.enc_algo = enc_algo
5771        self.enc_key = enc_key
5772        self.enc_serializer = enc_serializer
5773
5774        # effective callee that responded with the result
5775        self.callee = callee
5776        self.callee_authid = callee_authid
5777        self.callee_authrole = callee_authrole
5778
5779        # message forwarding
5780        self.forward_for = forward_for
5781
5782    @staticmethod
5783    def parse(wmsg):
5784        """
5785        Verifies and parses an unserialized raw message into an actual WAMP message instance.
5786
5787        :param wmsg: The unserialized raw message.
5788        :type wmsg: list
5789
5790        :returns: An instance of this class.
5791        """
5792        # this should already be verified by WampSerializer.unserialize
5793        assert(len(wmsg) > 0 and wmsg[0] == Yield.MESSAGE_TYPE)
5794
5795        if len(wmsg) not in (3, 4, 5):
5796            raise ProtocolError("invalid message length {0} for YIELD".format(len(wmsg)))
5797
5798        request = check_or_raise_id(wmsg[1], u"'request' in YIELD")
5799        options = check_or_raise_extra(wmsg[2], u"'options' in YIELD")
5800
5801        args = None
5802        kwargs = None
5803        payload = None
5804        enc_algo = None
5805        enc_key = None
5806        enc_serializer = None
5807
5808        if len(wmsg) == 4 and type(wmsg[3]) == six.binary_type:
5809
5810            payload = wmsg[3]
5811
5812            enc_algo = options.get(u'enc_algo', None)
5813            if enc_algo and not is_valid_enc_algo(enc_algo):
5814                raise ProtocolError("invalid value {0} for 'enc_algo' detail in YIELD".format(enc_algo))
5815
5816            enc_key = options.get(u'enc_key', None)
5817            if enc_key and type(enc_key) != six.text_type:
5818                raise ProtocolError("invalid type {0} for 'enc_key' detail in YIELD".format(type(enc_key)))
5819
5820            enc_serializer = options.get(u'enc_serializer', None)
5821            if enc_serializer and not is_valid_enc_serializer(enc_serializer):
5822                raise ProtocolError("invalid value {0} for 'enc_serializer' detail in YIELD".format(enc_serializer))
5823
5824        else:
5825            if len(wmsg) > 3:
5826                args = wmsg[3]
5827                if args is not None and type(args) != list:
5828                    raise ProtocolError("invalid type {0} for 'args' in YIELD".format(type(args)))
5829
5830            if len(wmsg) > 4:
5831                kwargs = wmsg[4]
5832                if type(kwargs) != dict:
5833                    raise ProtocolError("invalid type {0} for 'kwargs' in YIELD".format(type(kwargs)))
5834
5835        progress = None
5836        callee = None
5837        callee_authid = None
5838        callee_authrole = None
5839        forward_for = None
5840
5841        if u'progress' in options:
5842
5843            option_progress = options[u'progress']
5844            if type(option_progress) != bool:
5845                raise ProtocolError("invalid type {0} for 'progress' option in YIELD".format(type(option_progress)))
5846
5847            progress = option_progress
5848
5849        if u'callee' in options:
5850
5851            option_callee = options[u'callee']
5852            if type(option_callee) not in six.integer_types:
5853                raise ProtocolError("invalid type {0} for 'callee' detail in YIELD".format(type(option_callee)))
5854
5855            callee = option_callee
5856
5857        if u'callee_authid' in options:
5858
5859            option_callee_authid = options[u'callee_authid']
5860            if type(option_callee_authid) != six.text_type:
5861                raise ProtocolError("invalid type {0} for 'callee_authid' detail in YIELD".format(type(option_callee_authid)))
5862
5863            callee_authid = option_callee_authid
5864
5865        if u'callee_authrole' in options:
5866
5867            option_callee_authrole = options[u'callee_authrole']
5868            if type(option_callee_authrole) != six.text_type:
5869                raise ProtocolError("invalid type {0} for 'callee_authrole' detail in YIELD".format(type(option_callee_authrole)))
5870
5871            callee_authrole = option_callee_authrole
5872
5873        if u'forward_for' in options:
5874            forward_for = options[u'forward_for']
5875            valid = False
5876            if type(forward_for) == list:
5877                for ff in forward_for:
5878                    if type(ff) != dict:
5879                        break
5880                    if 'session' not in ff or type(ff['session']) not in six.integer_types:
5881                        break
5882                    if 'authid' not in ff or type(ff['authid']) != six.text_type:
5883                        break
5884                    if 'authrole' not in ff or type(ff['authrole']) != six.text_type:
5885                        break
5886                valid = True
5887
5888            if not valid:
5889                raise ProtocolError("invalid type/value {0} for/within 'forward_for' option in YIELD")
5890
5891        obj = Yield(request,
5892                    args=args,
5893                    kwargs=kwargs,
5894                    payload=payload,
5895                    progress=progress,
5896                    enc_algo=enc_algo,
5897                    enc_key=enc_key,
5898                    enc_serializer=enc_serializer,
5899                    callee=callee,
5900                    callee_authid=callee_authid,
5901                    callee_authrole=callee_authrole,
5902                    forward_for=forward_for)
5903
5904        return obj
5905
5906    def marshal(self):
5907        """
5908        Marshal this object into a raw message for subsequent serialization to bytes.
5909
5910        :returns: The serialized raw message.
5911        :rtype: list
5912        """
5913        options = {}
5914
5915        if self.progress is not None:
5916            options[u'progress'] = self.progress
5917
5918        if self.callee is not None:
5919            options[u'callee'] = self.callee
5920        if self.callee_authid is not None:
5921            options[u'callee_authid'] = self.callee_authid
5922        if self.callee_authrole is not None:
5923            options[u'callee_authrole'] = self.callee_authrole
5924        if self.forward_for is not None:
5925            options[u'forward_for'] = self.forward_for
5926
5927        if self.payload:
5928            if self.enc_algo is not None:
5929                options[u'enc_algo'] = self.enc_algo
5930            if self.enc_key is not None:
5931                options[u'enc_key'] = self.enc_key
5932            if self.enc_serializer is not None:
5933                options[u'enc_serializer'] = self.enc_serializer
5934            return [Yield.MESSAGE_TYPE, self.request, options, self.payload]
5935        else:
5936            if self.kwargs:
5937                return [Yield.MESSAGE_TYPE, self.request, options, self.args, self.kwargs]
5938            elif self.args:
5939                return [Yield.MESSAGE_TYPE, self.request, options, self.args]
5940            else:
5941                return [Yield.MESSAGE_TYPE, self.request, options]
5942
5943    def __str__(self):
5944        """
5945        Returns string representation of this message.
5946        """
5947        return u"Yield(request={0}, args={1}, kwargs={2}, progress={3}, enc_algo={4}, enc_key={5}, enc_serializer={6}, payload={7}, callee={8}, callee_authid={9}, callee_authrole={10}, forward_for={11})".format(self.request, self.args, self.kwargs, self.progress, self.enc_algo, self.enc_key, self.enc_serializer, b2a(self.payload), self.callee, self.callee_authid, self.callee_authrole, self.forward_for)
5948