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 os
30import unittest
31import six
32
33from autobahn.wamp import message
34from autobahn.wamp import role
35from autobahn.wamp import serializer
36
37
38# FIXME: autobahn.wamp.serializer.JsonObjectSerializer uses a patched JSON
39# encoder/decoder - however, the patching currently only works on Python 3!
40def must_skip(ser, contains_binary):
41    if contains_binary and ser.SERIALIZER_ID.startswith(u'json') and six.PY2:
42        return True
43    else:
44        return False
45
46
47def generate_test_messages():
48    """
49    List of WAMP test message used for serializers. Expand this if you add more
50    options or messages.
51
52    This list of WAMP message does not contain any binary app payloads!
53    """
54    msgs = [
55        message.Hello(u"realm1", {u'subscriber': role.RoleSubscriberFeatures()}),
56        message.Goodbye(),
57        message.Yield(123456),
58        message.Yield(123456, args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}),
59        message.Yield(123456, args=[u'hello']),
60        message.Yield(123456, progress=True),
61        message.Interrupt(123456),
62        message.Interrupt(123456, mode=message.Interrupt.KILL),
63        message.Invocation(123456, 789123),
64        message.Invocation(123456, 789123, args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}),
65        message.Invocation(123456, 789123, timeout=10000),
66        message.Result(123456),
67        message.Result(123456, args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}),
68        message.Result(123456, progress=True),
69        message.Cancel(123456),
70        message.Cancel(123456, mode=message.Cancel.KILL),
71        message.Call(123456, u'com.myapp.procedure1'),
72        message.Call(123456, u'com.myapp.procedure1', args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}),
73        message.Call(123456, u'com.myapp.procedure1', timeout=10000),
74        message.Unregistered(123456),
75        message.Unregister(123456, 789123),
76        message.Registered(123456, 789123),
77        message.Register(123456, u'com.myapp.procedure1'),
78        message.Register(123456, u'com.myapp.procedure1', match=u'prefix'),
79        message.Register(123456, u'com.myapp.procedure1', invoke=u'roundrobin'),
80        message.Event(123456, 789123),
81        message.Event(123456, 789123, args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}),
82        message.Event(123456, 789123, publisher=300),
83        message.Published(123456, 789123),
84        message.Publish(123456, u'com.myapp.topic1'),
85        message.Publish(123456, u'com.myapp.topic1', args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}),
86        message.Publish(123456, u'com.myapp.topic1', exclude_me=False, exclude=[300], eligible=[100, 200, 300]),
87        message.Unsubscribed(123456),
88        message.Unsubscribe(123456, 789123),
89        message.Subscribed(123456, 789123),
90        message.Subscribe(123456, u'com.myapp.topic1'),
91        message.Subscribe(123456, u'com.myapp.topic1', match=message.Subscribe.MATCH_PREFIX),
92        message.Error(message.Call.MESSAGE_TYPE, 123456, u'com.myapp.error1'),
93        message.Error(message.Call.MESSAGE_TYPE, 123456, u'com.myapp.error1', args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}),
94        message.Call(123456, u'com.myapp.\u4f60\u597d\u4e16\u754c', args=[1, 2, 3]),
95        message.Result(123456, args=[1, 2, 3], kwargs={u'en': u'Hello World', u'jp': u'\u3053\u3093\u306b\u3061\u306f\u4e16\u754c'})
96    ]
97    return [(False, msg) for msg in msgs]
98
99
100def generate_test_messages_binary():
101    """
102    Generate WAMP test messages which contain binary app payloads.
103
104    With the JSON serializer, this currently only works on Python 3 (both CPython3 and PyPy3),
105    because even on Python 3, we need to patch the stdlib JSON, and on Python 2, the patching
106    would be even hackier.
107    """
108    msgs = []
109    for binary in [b'',
110                   b'\x00',
111                   b'\30',
112                   os.urandom(4),
113                   os.urandom(16),
114                   os.urandom(128),
115                   os.urandom(256),
116                   os.urandom(512),
117                   os.urandom(1024)]:
118        msgs.append(message.Event(123456, 789123, args=[binary]))
119        msgs.append(message.Event(123456, 789123, args=[binary], kwargs={u'foo': binary}))
120    return [(True, msg) for msg in msgs]
121
122
123def create_serializers():
124    _serializers = []
125
126    _serializers.append(serializer.JsonSerializer())
127    _serializers.append(serializer.JsonSerializer(batched=True))
128
129    _serializers.append(serializer.MsgPackSerializer())
130    _serializers.append(serializer.MsgPackSerializer(batched=True))
131
132    _serializers.append(serializer.CBORSerializer())
133    _serializers.append(serializer.CBORSerializer(batched=True))
134
135    _serializers.append(serializer.UBJSONSerializer())
136    _serializers.append(serializer.UBJSONSerializer(batched=True))
137
138    # FIXME: implement full FlatBuffers serializer for WAMP
139    if six.PY3:
140        # WAMP-FlatBuffers currently only supports Python 3
141        # _serializers.append(serializer.FlatBuffersSerializer())
142        # _serializers.append(serializer.FlatBuffersSerializer(batched=True))
143        pass
144
145    return _serializers
146
147
148@unittest.skipIf(not six.PY3, 'WAMP-FlatBuffers currently only supports Python 3')
149class TestFlatBuffersSerializer(unittest.TestCase):
150
151    def test_basic(self):
152        messages = [
153            message.Event(123456,
154                          789123,
155                          args=[1, 2, 3],
156                          kwargs={u'foo': 23, u'bar': u'hello'},
157                          publisher=666,
158                          retained=True),
159            message.Publish(123456,
160                            'com.example.topic1',
161                            args=[1, 2, 3],
162                            kwargs={u'foo': 23, u'bar': u'hello'},
163                            retain=True)
164        ]
165
166        ser = serializer.FlatBuffersSerializer()
167
168        # from pprint import pprint
169
170        for msg in messages:
171
172            # serialize message
173            payload, binary = ser.serialize(msg)
174
175            # unserialize message again
176            msg2 = ser.unserialize(payload, binary)[0]
177
178            # pprint(msg.marshal())
179            # pprint(msg2.marshal())
180
181            # must be equal: message roundtrips via the serializer
182            self.assertEqual(msg, msg2)
183            # self.assertEqual(msg.subscription, msg2.subscription)
184            # self.assertEqual(msg.publication, msg2.publication)
185
186
187class TestSerializer(unittest.TestCase):
188
189    def setUp(self):
190        self._test_messages = generate_test_messages() + generate_test_messages_binary()
191        self._test_serializers = create_serializers()
192        # print('Testing WAMP serializers {} with {} WAMP test messages'.format([ser.SERIALIZER_ID for ser in self._test_serializers], len(self._test_messages)))
193
194    def test_deep_equal_msg(self):
195        """
196        Test deep object equality assert (because I am paranoid).
197        """
198        v = os.urandom(10)
199        o1 = [1, 2, {u'foo': u'bar', u'bar': v, u'baz': [9, 3, 2], u'goo': {u'moo': [1, 2, 3]}}, v]
200        o2 = [1, 2, {u'goo': {u'moo': [1, 2, 3]}, u'bar': v, u'baz': [9, 3, 2], u'foo': u'bar'}, v]
201        self.assertEqual(o1, o2)
202
203    def test_roundtrip_msg(self):
204        """
205        Test round-tripping over each serializer.
206        """
207        for ser in self._test_serializers:
208
209            for contains_binary, msg in self._test_messages:
210
211                if not must_skip(ser, contains_binary):
212                    # serialize message
213                    payload, binary = ser.serialize(msg)
214
215                    # unserialize message again
216                    msg2 = ser.unserialize(payload, binary)
217
218                    # must be equal: message roundtrips via the serializer
219                    self.assertEqual([msg], msg2)
220
221    def test_crosstrip_msg(self):
222        """
223        Test cross-tripping over 2 serializers (as is done by WAMP routers).
224        """
225        for ser1 in self._test_serializers:
226
227            for contains_binary, msg in self._test_messages:
228
229                if not must_skip(ser1, contains_binary):
230                    # serialize message
231                    payload, binary = ser1.serialize(msg)
232
233                    # unserialize message again
234                    msg1 = ser1.unserialize(payload, binary)
235                    msg1 = msg1[0]
236
237                    for ser2 in self._test_serializers:
238
239                        if not must_skip(ser2, contains_binary):
240                            # serialize message
241                            payload, binary = ser2.serialize(msg1)
242
243                            # unserialize message again
244                            msg2 = ser2.unserialize(payload, binary)
245
246                            # must be equal: message crosstrips via
247                            # the serializers ser1 -> ser2
248                            self.assertEqual([msg], msg2)
249
250    def test_cache_msg(self):
251        """
252        Test message serialization caching.
253        """
254        for contains_binary, msg in self._test_messages:
255
256            # message serialization cache is initially empty
257            self.assertEqual(msg._serialized, {})
258
259            for ser in self._test_serializers:
260
261                if not must_skip(ser, contains_binary):
262
263                    # verify message serialization is not yet cached
264                    self.assertFalse(ser._serializer in msg._serialized)
265                    payload, binary = ser.serialize(msg)
266
267                    # now the message serialization must be cached
268                    self.assertTrue(ser._serializer in msg._serialized)
269                    self.assertEqual(msg._serialized[ser._serializer], payload)
270
271                    # and after resetting the serialization cache, message
272                    # serialization is gone
273                    msg.uncache()
274                    self.assertFalse(ser._serializer in msg._serialized)
275