diff --git a/.coveragerc b/.coveragerc index 0c8de9809..fe5714c3b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,12 +1,3 @@ [run] -source = autobahn -branch = True - -[report] -precision = 2 -exclude_lines = - def __repr__ - raise AssertionError - pragma: no cover - if self\._debug - raise NotImplementedError +omit = + */test/*.py diff --git a/.gitignore b/.gitignore index 07d48ee00..4d698d3bc 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ node.key *.swo *.swn *.so +coverage.xml +.coverage.* diff --git a/.travis.yml b/.travis.yml index ad769dc93..a31a275ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,6 @@ install: script: - tox -c tox.ini -e $TOX_ENV -after_script: - - codecov - cache: directories: - $HOME/.cache/pip @@ -107,6 +104,11 @@ matrix: env: - TOX_ENV=py27-asyncio + + - python: "3.7" + skip_cleanup: true + env: TOX_ENV=coverage + # # Build wheel and upload to S3 # https://docs.travis-ci.com/user/build-stages/matrix-expansion/ diff --git a/Makefile b/Makefile index 1f5e40e8b..fbdfdb590 100755 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: test docs pep8 +.PHONY: test docs pep8 build all: @echo "Targets:" @@ -12,12 +12,20 @@ all: # install locally install: + -pip uninstall -y pytest_asyncio # remove the broken shit + -pip uninstall -y pytest_cov # remove the broken shit # enforce use of bundled libsodium - SODIUM_INSTALL=bundled pip install -e .[all,dev] + AUTOBAHN_USE_NVX=1 SODIUM_INSTALL=bundled pip install -e .[all,dev] + +build: + -rm -f dist/* + # python setup.py sdist bdist_wheel --universal + AUTOBAHN_USE_NVX=1 python setup.py sdist bdist_wheel + ls -la dist # upload to our internal deployment system upload: clean - python setup.py bdist_wheel + python setup.py sdist bdist_wheel --universal aws s3 cp --acl public-read \ dist/autobahn-*.whl \ s3://fabric-deploy/autobahn/ @@ -34,6 +42,11 @@ clean: -rm -rf ./_trial_temp -rm -rf ./.tox -rm -rf ./.eggs + -rm -rf ./htmlcov + -rm -f ./.coverage + -rm -f ./coverage.xml + -rm -f ./.coverage.* + -rm -rf ~/coverage -rm -f ./twisted/plugins/dropin.cache -find . -name "*dropin.cache.new" -type f -exec rm -f {} \; -find . -name "*.tar.gz" -type f -exec rm -f {} \; @@ -70,7 +83,10 @@ test_pytest: test_setuptools: python setup.py test -test: flake8 test_twisted test_asyncio +test: + tox -e flake8,py37-twtrunk,py37-asyncio,coverage + +#test: flake8 test_twisted test_asyncio # test under Twisted test_twisted: @@ -80,6 +96,9 @@ test_twisted: test_tx_serializer: USE_TWISTED=1 trial autobahn.wamp.test.test_serializer +test_tx_cryptobox: + USE_TWISTED=1 trial autobahn.wamp.test.test_cryptobox + test_tx_choosereactor: USE_TWISTED=1 trial autobahn.twisted.test.test_choosereactor @@ -141,7 +160,9 @@ autopep8: # This will run pep8, pyflakes and can skip lines that end with # noqa flake8: - flake8 --ignore=E402,E501,E722,E741,N801,N802,N803,N805,N806,N815 autobahn + flake8 --ignore=E402,E501,E722,E741,N801,N802,N803,N805,N806,N815 \ + --exclude "autobahn/wamp/message_fbs.py,autobahn/wamp/gen/*"\ + autobahn # run PyLint pylint: diff --git a/autobahn/twisted/test/test_component.py b/autobahn/twisted/test/test_component.py index ed1030b47..fa4f27cb3 100644 --- a/autobahn/twisted/test/test_component.py +++ b/autobahn/twisted/test/test_component.py @@ -2,7 +2,7 @@ # # The MIT License (MIT) # -# Copyright (c) Tavendo GmbH +# Copyright (c) Crossbar.io Technologies GmbH # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/autobahn/wamp/flatbuffers/pubsub.fbs b/autobahn/wamp/flatbuffers/pubsub.fbs index 9a0023172..d29def88f 100644 --- a/autobahn/wamp/flatbuffers/pubsub.fbs +++ b/autobahn/wamp/flatbuffers/pubsub.fbs @@ -74,7 +74,13 @@ table Publish // The WAMP or application URI of the PubSub topic the event should be published to. topic: string (required, uri); - // Raw application payload: error arguments. This might be encrypted (with Payload==Payload.CRYPTOBOX), and is serialized according to enc_serializer. + /// Positional values for application-defined event payload. + args: [uint8]; + + /// Keyword values for application-defined event payload. + kwargs: [uint8]; + + /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; // The encoding algorithm that was used to encode the payload. @@ -135,7 +141,13 @@ table Event // The publication ID of the dispatched event. publication: uint64; - // Raw application payload: error arguments. This might be encrypted (with Payload==Payload.CRYPTOBOX), and is serialized according to enc_serializer. + /// Positional values for application-defined event payload. + args: [uint8]; + + /// Keyword values for application-defined event payload. + kwargs: [uint8]; + + /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; // The encoding algorithm that was used to encode the payload. @@ -164,6 +176,15 @@ table Event // Hint to request acknowledgement of the reception of this Event by a subscriber. acknowledge: bool; + + // The WAMP session ID of the pubisher. Only filled when the publisher is disclosed. + forward_for: uint64; + + // The WAMP authrole of the publisher. Only filled when publisher is disclosed. + forward_for_authid: string (principal); + + // The WAMP authrole of the publisher. Only filled when publisher is disclosed. + forward_for_authrole: string (principal); } diff --git a/autobahn/wamp/gen/__init__.py b/autobahn/wamp/gen/__init__.py new file mode 100644 index 000000000..89caae255 --- /dev/null +++ b/autobahn/wamp/gen/__init__.py @@ -0,0 +1,25 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### diff --git a/autobahn/wamp/gen/schema/pubsub.bfbs b/autobahn/wamp/gen/schema/pubsub.bfbs index 983f18c2f..d30404ed8 100644 Binary files a/autobahn/wamp/gen/schema/pubsub.bfbs and b/autobahn/wamp/gen/schema/pubsub.bfbs differ diff --git a/autobahn/wamp/gen/schema/wamp.bfbs b/autobahn/wamp/gen/schema/wamp.bfbs index 98a071001..823d5dd80 100644 Binary files a/autobahn/wamp/gen/schema/wamp.bfbs and b/autobahn/wamp/gen/schema/wamp.bfbs differ diff --git a/autobahn/wamp/gen/wamp/proto/Event.py b/autobahn/wamp/gen/wamp/proto/Event.py index 345bbc901..5ddde79a2 100644 --- a/autobahn/wamp/gen/wamp/proto/Event.py +++ b/autobahn/wamp/gen/wamp/proto/Event.py @@ -32,8 +32,9 @@ def Publication(self): return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos) return 0 +# /// Positional values for application-defined event payload. # Event - def Payload(self, j): + def Args(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: a = self._tab.Vector(o) @@ -41,36 +42,82 @@ def Payload(self, j): return 0 # Event - def PayloadAsNumpy(self): + def ArgsAsNumpy(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) return 0 # Event - def PayloadLength(self): + def ArgsLength(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: return self._tab.VectorLen(o) return 0 +# /// Keyword values for application-defined event payload. # Event - def EncAlgo(self): + def Kwargs(self, j): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + a = self._tab.Vector(o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1)) + return 0 + + # Event + def KwargsAsNumpy(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return 0 + + # Event + def KwargsLength(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + return self._tab.VectorLen(o) + return 0 + +# /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. + # Event + def Payload(self, j): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + a = self._tab.Vector(o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1)) + return 0 + + # Event + def PayloadAsNumpy(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return 0 + + # Event + def PayloadLength(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + return self._tab.VectorLen(o) + return 0 + + # Event + def EncAlgo(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 # Event def EncSerializer(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 # Event def EncKey(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: a = self._tab.Vector(o) return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1)) @@ -78,73 +125,101 @@ def EncKey(self, j): # Event def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) return 0 # Event def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.VectorLen(o) return 0 # Event def Publisher(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos) return 0 # Event def PublisherAuthid(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Event def PublisherAuthrole(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Event def Topic(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Event def Retained(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) return False # Event def Acknowledge(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) return False -def EventStart(builder): builder.StartObject(12) + # Event + def ForwardFor(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos) + return 0 + + # Event + def ForwardForAuthid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # Event + def ForwardForAuthrole(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + +def EventStart(builder): builder.StartObject(17) def EventAddSubscription(builder, subscription): builder.PrependUint64Slot(0, subscription, 0) def EventAddPublication(builder, publication): builder.PrependUint64Slot(1, publication, 0) -def EventAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0) +def EventAddArgs(builder, args): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(args), 0) +def EventStartArgsVector(builder, numElems): return builder.StartVector(1, numElems, 1) +def EventAddKwargs(builder, kwargs): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(kwargs), 0) +def EventStartKwargsVector(builder, numElems): return builder.StartVector(1, numElems, 1) +def EventAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0) def EventStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1) -def EventAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(3, encAlgo, 0) -def EventAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(4, encSerializer, 0) -def EventAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0) +def EventAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(5, encAlgo, 0) +def EventAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(6, encSerializer, 0) +def EventAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0) def EventStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1) -def EventAddPublisher(builder, publisher): builder.PrependUint64Slot(6, publisher, 0) -def EventAddPublisherAuthid(builder, publisherAuthid): builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthid), 0) -def EventAddPublisherAuthrole(builder, publisherAuthrole): builder.PrependUOffsetTRelativeSlot(8, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthrole), 0) -def EventAddTopic(builder, topic): builder.PrependUOffsetTRelativeSlot(9, flatbuffers.number_types.UOffsetTFlags.py_type(topic), 0) -def EventAddRetained(builder, retained): builder.PrependBoolSlot(10, retained, 0) -def EventAddAcknowledge(builder, acknowledge): builder.PrependBoolSlot(11, acknowledge, 0) +def EventAddPublisher(builder, publisher): builder.PrependUint64Slot(8, publisher, 0) +def EventAddPublisherAuthid(builder, publisherAuthid): builder.PrependUOffsetTRelativeSlot(9, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthid), 0) +def EventAddPublisherAuthrole(builder, publisherAuthrole): builder.PrependUOffsetTRelativeSlot(10, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthrole), 0) +def EventAddTopic(builder, topic): builder.PrependUOffsetTRelativeSlot(11, flatbuffers.number_types.UOffsetTFlags.py_type(topic), 0) +def EventAddRetained(builder, retained): builder.PrependBoolSlot(12, retained, 0) +def EventAddAcknowledge(builder, acknowledge): builder.PrependBoolSlot(13, acknowledge, 0) +def EventAddForwardFor(builder, forwardFor): builder.PrependUint64Slot(14, forwardFor, 0) +def EventAddForwardForAuthid(builder, forwardForAuthid): builder.PrependUOffsetTRelativeSlot(15, flatbuffers.number_types.UOffsetTFlags.py_type(forwardForAuthid), 0) +def EventAddForwardForAuthrole(builder, forwardForAuthrole): builder.PrependUOffsetTRelativeSlot(16, flatbuffers.number_types.UOffsetTFlags.py_type(forwardForAuthrole), 0) def EventEnd(builder): return builder.EndObject() diff --git a/autobahn/wamp/gen/wamp/proto/Publish.py b/autobahn/wamp/gen/wamp/proto/Publish.py index c7c15ba13..3b1869237 100644 --- a/autobahn/wamp/gen/wamp/proto/Publish.py +++ b/autobahn/wamp/gen/wamp/proto/Publish.py @@ -32,8 +32,9 @@ def Topic(self): return self._tab.String(o + self._tab.Pos) return None +# /// Positional values for application-defined event payload. # Publish - def Payload(self, j): + def Args(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: a = self._tab.Vector(o) @@ -41,36 +42,82 @@ def Payload(self, j): return 0 # Publish - def PayloadAsNumpy(self): + def ArgsAsNumpy(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) return 0 # Publish - def PayloadLength(self): + def ArgsLength(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: return self._tab.VectorLen(o) return 0 +# /// Keyword values for application-defined event payload. # Publish - def EncAlgo(self): + def Kwargs(self, j): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + a = self._tab.Vector(o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1)) + return 0 + + # Publish + def KwargsAsNumpy(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return 0 + + # Publish + def KwargsLength(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + return self._tab.VectorLen(o) + return 0 + +# /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. + # Publish + def Payload(self, j): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + a = self._tab.Vector(o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1)) + return 0 + + # Publish + def PayloadAsNumpy(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return 0 + + # Publish + def PayloadLength(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + return self._tab.VectorLen(o) + return 0 + + # Publish + def EncAlgo(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 # Publish def EncSerializer(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 # Publish def EncKey(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: a = self._tab.Vector(o) return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1)) @@ -78,35 +125,35 @@ def EncKey(self, j): # Publish def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) return 0 # Publish def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def Acknowledge(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) return False # Publish def ExcludeMe(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) return True # Publish def Exclude(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: a = self._tab.Vector(o) return self._tab.Get(flatbuffers.number_types.Uint64Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 8)) @@ -114,21 +161,21 @@ def Exclude(self, j): # Publish def ExcludeAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint64Flags, o) return 0 # Publish def ExcludeLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def ExcludeAuthid(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: a = self._tab.Vector(o) return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) @@ -136,14 +183,14 @@ def ExcludeAuthid(self, j): # Publish def ExcludeAuthidLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def ExcludeAuthrole(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: a = self._tab.Vector(o) return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) @@ -151,14 +198,14 @@ def ExcludeAuthrole(self, j): # Publish def ExcludeAuthroleLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def Eligible(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: a = self._tab.Vector(o) return self._tab.Get(flatbuffers.number_types.Uint64Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 8)) @@ -166,21 +213,21 @@ def Eligible(self, j): # Publish def EligibleAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint64Flags, o) return 0 # Publish def EligibleLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def EligibleAuthid(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) if o != 0: a = self._tab.Vector(o) return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) @@ -188,14 +235,14 @@ def EligibleAuthid(self, j): # Publish def EligibleAuthidLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def EligibleAuthrole(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) if o != 0: a = self._tab.Vector(o) return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) @@ -203,40 +250,44 @@ def EligibleAuthrole(self, j): # Publish def EligibleAuthroleLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def Retain(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) if o != 0: return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) return False -def PublishStart(builder): builder.StartObject(15) +def PublishStart(builder): builder.StartObject(17) def PublishAddRequest(builder, request): builder.PrependUint64Slot(0, request, 0) def PublishAddTopic(builder, topic): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(topic), 0) -def PublishAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0) +def PublishAddArgs(builder, args): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(args), 0) +def PublishStartArgsVector(builder, numElems): return builder.StartVector(1, numElems, 1) +def PublishAddKwargs(builder, kwargs): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(kwargs), 0) +def PublishStartKwargsVector(builder, numElems): return builder.StartVector(1, numElems, 1) +def PublishAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0) def PublishStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1) -def PublishAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(3, encAlgo, 0) -def PublishAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(4, encSerializer, 0) -def PublishAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0) +def PublishAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(5, encAlgo, 0) +def PublishAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(6, encSerializer, 0) +def PublishAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0) def PublishStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1) -def PublishAddAcknowledge(builder, acknowledge): builder.PrependBoolSlot(6, acknowledge, 0) -def PublishAddExcludeMe(builder, excludeMe): builder.PrependBoolSlot(7, excludeMe, 1) -def PublishAddExclude(builder, exclude): builder.PrependUOffsetTRelativeSlot(8, flatbuffers.number_types.UOffsetTFlags.py_type(exclude), 0) +def PublishAddAcknowledge(builder, acknowledge): builder.PrependBoolSlot(8, acknowledge, 0) +def PublishAddExcludeMe(builder, excludeMe): builder.PrependBoolSlot(9, excludeMe, 1) +def PublishAddExclude(builder, exclude): builder.PrependUOffsetTRelativeSlot(10, flatbuffers.number_types.UOffsetTFlags.py_type(exclude), 0) def PublishStartExcludeVector(builder, numElems): return builder.StartVector(8, numElems, 8) -def PublishAddExcludeAuthid(builder, excludeAuthid): builder.PrependUOffsetTRelativeSlot(9, flatbuffers.number_types.UOffsetTFlags.py_type(excludeAuthid), 0) +def PublishAddExcludeAuthid(builder, excludeAuthid): builder.PrependUOffsetTRelativeSlot(11, flatbuffers.number_types.UOffsetTFlags.py_type(excludeAuthid), 0) def PublishStartExcludeAuthidVector(builder, numElems): return builder.StartVector(4, numElems, 4) -def PublishAddExcludeAuthrole(builder, excludeAuthrole): builder.PrependUOffsetTRelativeSlot(10, flatbuffers.number_types.UOffsetTFlags.py_type(excludeAuthrole), 0) +def PublishAddExcludeAuthrole(builder, excludeAuthrole): builder.PrependUOffsetTRelativeSlot(12, flatbuffers.number_types.UOffsetTFlags.py_type(excludeAuthrole), 0) def PublishStartExcludeAuthroleVector(builder, numElems): return builder.StartVector(4, numElems, 4) -def PublishAddEligible(builder, eligible): builder.PrependUOffsetTRelativeSlot(11, flatbuffers.number_types.UOffsetTFlags.py_type(eligible), 0) +def PublishAddEligible(builder, eligible): builder.PrependUOffsetTRelativeSlot(13, flatbuffers.number_types.UOffsetTFlags.py_type(eligible), 0) def PublishStartEligibleVector(builder, numElems): return builder.StartVector(8, numElems, 8) -def PublishAddEligibleAuthid(builder, eligibleAuthid): builder.PrependUOffsetTRelativeSlot(12, flatbuffers.number_types.UOffsetTFlags.py_type(eligibleAuthid), 0) +def PublishAddEligibleAuthid(builder, eligibleAuthid): builder.PrependUOffsetTRelativeSlot(14, flatbuffers.number_types.UOffsetTFlags.py_type(eligibleAuthid), 0) def PublishStartEligibleAuthidVector(builder, numElems): return builder.StartVector(4, numElems, 4) -def PublishAddEligibleAuthrole(builder, eligibleAuthrole): builder.PrependUOffsetTRelativeSlot(13, flatbuffers.number_types.UOffsetTFlags.py_type(eligibleAuthrole), 0) +def PublishAddEligibleAuthrole(builder, eligibleAuthrole): builder.PrependUOffsetTRelativeSlot(15, flatbuffers.number_types.UOffsetTFlags.py_type(eligibleAuthrole), 0) def PublishStartEligibleAuthroleVector(builder, numElems): return builder.StartVector(4, numElems, 4) -def PublishAddRetain(builder, retain): builder.PrependBoolSlot(14, retain, 0) +def PublishAddRetain(builder, retain): builder.PrependBoolSlot(16, retain, 0) def PublishEnd(builder): return builder.EndObject() diff --git a/autobahn/wamp/message.py b/autobahn/wamp/message.py index 2eb306d6c..49a39e7dc 100644 --- a/autobahn/wamp/message.py +++ b/autobahn/wamp/message.py @@ -34,6 +34,18 @@ import autobahn from autobahn.wamp.exception import ProtocolError from autobahn.wamp.role import ROLE_NAME_TO_CLASS +from autobahn.wamp import message_fbs + +try: + import cbor +except ImportError: + pass + +try: + import flatbuffers +except ImportError: + pass + __all__ = ('Message', 'Hello', @@ -107,6 +119,19 @@ # Payload transparency serializer identifiers from the WAMP spec. PAYLOAD_ENC_STANDARD_SERIALIZERS = [u'json', u'msgpack', u'cbor', u'ubjson', u'flatbuffers'] +ENC_ALGO_NONE = 0 +ENC_ALGO_CRYPTOBOX = 1 +ENC_ALGO_MQTT = 2 +ENC_ALGO_XBR = 3 + +ENC_SER_NONE = 0 +ENC_SER_JSON = 1 +ENC_SER_MSGPACK = 2 +ENC_SER_CBOR = 3 +ENC_SER_UBJSON = 4 +ENC_SER_OPAQUE = 5 +ENC_SER_FLATBUFFERS = 6 + def is_valid_enc_algo(enc_algo): """ @@ -332,6 +357,7 @@ class Message(object): """ __slots__ = ( + '_from_fbs', '_serialized', '_correlation_id', '_correlation_uri', @@ -339,7 +365,10 @@ class Message(object): '_correlation_is_last', ) - def __init__(self): + def __init__(self, from_fbs=None): + # only filled in case this object has flatbuffers underlying + self._from_fbs = from_fbs + # serialization cache: mapping from ISerializer instances to serialized bytes self._serialized = {} @@ -403,7 +432,7 @@ def __eq__(self, other): '_correlation_id', '_correlation_uri', '_correlation_is_anchor', - '_correlation_is_last']: + '_correlation_is_last'] and not k.startswith('_'): if not getattr(self, k) == getattr(other, k): return False return True @@ -430,6 +459,17 @@ def parse(wmsg): :returns: An instance of this class. :rtype: obj """ + raise NotImplementedError() + + def marshal(self): + raise NotImplementedError() + + @staticmethod + def cast(buf): + raise NotImplementedError() + + def build(self, builder): + raise NotImplementedError() def uncache(self): """ @@ -451,7 +491,22 @@ def serialize(self, serializer): """ # only serialize if not cached .. if serializer not in self._serialized: - self._serialized[serializer] = serializer.serialize(self.marshal()) + if serializer.NAME == u'flatbuffers': + # flatbuffers get special treatment .. + builder = flatbuffers.Builder(1024) + + # this is the core method writing out this message (self) to a (new) flatbuffer + # FIXME: implement this method for all classes derived from Message + obj = self.build(builder) + + builder.Finish(obj) + buf = builder.Output() + self._serialized[serializer] = bytes(buf) + else: + # all other serializers first marshal() the object and then serialize the latter + self._serialized[serializer] = serializer.serialize(self.marshal()) + + # cache is filled now: return serialized, cached bytes return self._serialized[serializer] @@ -1526,29 +1581,29 @@ class Publish(Message): """ __slots__ = ( - 'request', - 'topic', - 'args', - 'kwargs', - 'payload', - 'acknowledge', - 'exclude_me', - 'exclude', - 'exclude_authid', - 'exclude_authrole', - 'eligible', - 'eligible_authid', - 'eligible_authrole', - 'retain', - 'enc_algo', - 'enc_key', - 'enc_serializer', - 'forward_for', + '_request', + '_topic', + '_args', + '_kwargs', + '_payload', + '_acknowledge', + '_exclude_me', + '_exclude', + '_exclude_authid', + '_exclude_authrole', + '_eligible', + '_eligible_authid', + '_eligible_authrole', + '_retain', + '_enc_algo', + '_enc_key', + '_enc_serializer', + '_forward_for', ) def __init__(self, - request, - topic, + request=None, + topic=None, args=None, kwargs=None, payload=None, @@ -1564,7 +1619,8 @@ def __init__(self, enc_algo=None, enc_key=None, enc_serializer=None, - forward_for=None): + forward_for=None, + from_fbs=None): """ :param request: The WAMP request ID of this request. @@ -1626,8 +1682,8 @@ def __init__(self, :param forward_for: When this Call is forwarded for a client (or from an intermediary router). :type forward_for: list[dict] """ - assert(type(request) in six.integer_types) - assert(type(topic) == six.text_type) + assert(request is None or type(request) in six.integer_types) + assert(topic is None or type(topic) == six.text_type) assert(args is None or type(args) in [list, tuple, six.text_type, six.binary_type]) assert(kwargs is None or type(kwargs) in [dict, six.text_type, six.binary_type]) assert(payload is None or type(payload) == six.binary_type) @@ -1681,33 +1737,475 @@ def __init__(self, assert 'authid' in ff and type(ff['authid']) == six.text_type assert 'authrole' in ff and type(ff['authrole']) == six.text_type - Message.__init__(self) - self.request = request - self.topic = topic - self.args = args - self.kwargs = _validate_kwargs(kwargs) - self.payload = payload - self.acknowledge = acknowledge + Message.__init__(self, from_fbs=from_fbs) + self._request = request + self._topic = topic + self._args = args + self._kwargs = _validate_kwargs(kwargs) + self._payload = payload + self._acknowledge = acknowledge # publisher exlusion and black-/whitelisting - self.exclude_me = exclude_me - self.exclude = exclude - self.exclude_authid = exclude_authid - self.exclude_authrole = exclude_authrole - self.eligible = eligible - self.eligible_authid = eligible_authid - self.eligible_authrole = eligible_authrole + self._exclude_me = exclude_me + self._exclude = exclude + self._exclude_authid = exclude_authid + self._exclude_authrole = exclude_authrole + self._eligible = eligible + self._eligible_authid = eligible_authid + self._eligible_authrole = eligible_authrole # event retention - self.retain = retain + self._retain = retain # payload transparency related knobs - self.enc_algo = enc_algo - self.enc_key = enc_key - self.enc_serializer = enc_serializer + self._enc_algo = enc_algo + self._enc_key = enc_key + self._enc_serializer = enc_serializer # message forwarding - self.forward_for = forward_for + self._forward_for = forward_for + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + if not Message.__eq__(self, other): + return False + if other.request != self.request: + return False + if other.topic != self.topic: + return False + if other.args != self.args: + return False + if other.kwargs != self.kwargs: + return False + if other.payload != self.payload: + return False + if other.acknowledge != self.acknowledge: + return False + if other.exclude_me != self.exclude_me: + return False + if other.exclude != self.exclude: + return False + if other.exclude_authid != self.exclude_authid: + return False + if other.exclude_authrole != self.exclude_authrole: + return False + if other.eligible != self.eligible: + return False + if other.eligible_authid != self.eligible_authid: + return False + if other.eligible_authrole != self.eligible_authrole: + return False + if other.retain != self.retain: + return False + if other.enc_algo != self.enc_algo: + return False + if other.enc_key != self.enc_key: + return False + if other.enc_serializer != self.enc_serializer: + return False + if other.forward_for != self.forward_for: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + @property + def request(self): + if self._request is None and self._from_fbs: + self._request = self._from_fbs.Request() + return self._request + + @request.setter + def request(self, value): + assert(value is None or type(value) in six.integer_types) + self._request = value + + @property + def topic(self): + if self._topic is None and self._from_fbs: + s = self._from_fbs.Topic() + if s: + self._topic = s.decode('utf8') + return self._topic + + @topic.setter + def topic(self, value): + assert value is None or type(value) == str + self._topic = value + + @property + def args(self): + if self._args is None and self._from_fbs: + if self._from_fbs.ArgsLength(): + self._args = cbor.loads(bytes(self._from_fbs.ArgsAsBytes())) + return self._args + + @args.setter + def args(self, value): + assert(value is None or type(value) in [list, tuple]) + self._args = value + + @property + def kwargs(self): + if self._kwargs is None and self._from_fbs: + if self._from_fbs.KwargsLength(): + self._kwargs = cbor.loads(bytes(self._from_fbs.KwargsAsBytes())) + return self._kwargs + + @kwargs.setter + def kwargs(self, value): + assert(value is None or type(value) == dict) + self._kwargs = value + + @property + def payload(self): + if self._payload is None and self._from_fbs: + if self._from_fbs.PayloadLength(): + self._payload = self._from_fbs.PayloadAsBytes() + return self._payload + + @payload.setter + def payload(self, value): + assert value is None or type(value) == bytes + self._payload = value + + @property + def acknowledge(self): + if self._acknowledge is None and self._from_fbs: + acknowledge = self._from_fbs.Acknowledge() + if acknowledge: + self._acknowledge = acknowledge + return self._acknowledge + + @acknowledge.setter + def acknowledge(self, value): + assert value is None or type(value) == bool + self._acknowledge = value + + @property + def exclude_me(self): + if self._exclude_me is None and self._from_fbs: + exclude_me = self._from_fbs.ExcludeMe() + if exclude_me is False: + self._exclude_me = exclude_me + return self._exclude_me + + @exclude_me.setter + def exclude_me(self, value): + assert value is None or type(value) == bool + self._exclude_me = value + + @property + def exclude(self): + if self._exclude is None and self._from_fbs: + if self._from_fbs.ExcludeLength(): + exclude = [] + for j in range(self._from_fbs.ExcludeLength()): + exclude.append(self._from_fbs.Exclude(j)) + self._exclude = exclude + return self._exclude + + @exclude.setter + def exclude(self, value): + assert value is None or type(value) == list + if value: + for x in value: + assert type(x) == int + self._exclude = value + + @property + def exclude_authid(self): + if self._exclude_authid is None and self._from_fbs: + if self._from_fbs.ExcludeAuthidLength(): + exclude_authid = [] + for j in range(self._from_fbs.ExcludeAuthidLength()): + exclude_authid.append(self._from_fbs.ExcludeAuthid(j).decode('utf8')) + self._exclude_authid = exclude_authid + return self._exclude_authid + + @exclude_authid.setter + def exclude_authid(self, value): + assert value is None or type(value) == list + if value: + for x in value: + assert type(x) == str + self._exclude_authid = value + + @property + def exclude_authrole(self): + if self._exclude_authrole is None and self._from_fbs: + if self._from_fbs.ExcludeAuthroleLength(): + exclude_authrole = [] + for j in range(self._from_fbs.ExcludeAuthroleLength()): + exclude_authrole.append(self._from_fbs.ExcludeAuthrole(j).decode('utf8')) + self._exclude_authrole = exclude_authrole + return self._exclude_authrole + + @exclude_authrole.setter + def exclude_authrole(self, value): + assert value is None or type(value) == list + if value: + for x in value: + assert type(x) == str + self._exclude_authrole = value + + @property + def eligible(self): + if self._eligible is None and self._from_fbs: + if self._from_fbs.EligibleLength(): + eligible = [] + for j in range(self._from_fbs.EligibleLength()): + eligible.append(self._from_fbs.Eligible(j)) + self._eligible = eligible + return self._eligible + + @eligible.setter + def eligible(self, value): + assert value is None or type(value) == list + if value: + for x in value: + assert type(x) == int + self._eligible = value + + @property + def eligible_authid(self): + if self._eligible_authid is None and self._from_fbs: + if self._from_fbs.EligibleAuthidLength(): + eligible_authid = [] + for j in range(self._from_fbs.EligibleAuthidLength()): + eligible_authid.append(self._from_fbs.EligibleAuthid(j).decode('utf8')) + self._eligible_authid = eligible_authid + return self._eligible_authid + + @eligible_authid.setter + def eligible_authid(self, value): + assert value is None or type(value) == list + if value: + for x in value: + assert type(x) == str + self._eligible_authid = value + + @property + def eligible_authrole(self): + if self._eligible_authrole is None and self._from_fbs: + if self._from_fbs.EligibleAuthroleLength(): + eligible_authrole = [] + for j in range(self._from_fbs.EligibleAuthroleLength()): + eligible_authrole.append(self._from_fbs.EligibleAuthrole(j).decode('utf8')) + self._eligible_authrole = eligible_authrole + return self._eligible_authrole + + @eligible_authrole.setter + def eligible_authrole(self, value): + assert value is None or type(value) == list + if value: + for x in value: + assert type(x) == str + self._eligible_authrole = value + + @property + def retain(self): + if self._retain is None and self._from_fbs: + retain = self._from_fbs.Retain() + if retain: + self._retain = retain + return self._retain + + @retain.setter + def retain(self, value): + assert value is None or type(value) == bool + self._retain = value + + @property + def enc_algo(self): + if self._enc_algo is None and self._from_fbs: + enc_algo = self._from_fbs.EncAlgo() + if enc_algo: + self._enc_algo = enc_algo + return self._enc_algo + + @enc_algo.setter + def enc_algo(self, value): + assert value is None or value in [ENC_ALGO_CRYPTOBOX, ENC_ALGO_MQTT, ENC_ALGO_XBR] + self._enc_algo = value + + @property + def enc_key(self): + if self._enc_key is None and self._from_fbs: + if self._from_fbs.EncKeyLength(): + self._enc_key = self._from_fbs.EncKeyAsBytes() + return self._enc_key + + @enc_key.setter + def enc_key(self, value): + assert value is None or type(value) == bytes + self._enc_key = value + + @property + def enc_serializer(self): + if self._enc_serializer is None and self._from_fbs: + enc_serializer = self._from_fbs.EncSerializer() + if enc_serializer: + self._enc_serializer = enc_serializer + return self._enc_serializer + + @enc_serializer.setter + def enc_serializer(self, value): + assert value is None or value in [ENC_SER_JSON, ENC_SER_MSGPACK, ENC_SER_CBOR, ENC_SER_UBJSON] + self._enc_serializer = value + + @property + def forward_for(self): + # FIXME + return None + + @forward_for.setter + def forward_for(self, value): + # FIXME + pass + + @staticmethod + def cast(buf): + return Publish(from_fbs=message_fbs.Publish.GetRootAsPublish(buf, 0)) + + def build(self, builder): + + args = self.args + if args: + args = builder.CreateByteVector(cbor.dumps(args)) + + kwargs = self.kwargs + if kwargs: + kwargs = builder.CreateByteVector(cbor.dumps(kwargs)) + + payload = self.payload + if payload: + payload = builder.CreateByteVector(payload) + + topic = self.topic + if topic: + topic = builder.CreateString(topic) + + enc_key = self.enc_key + if enc_key: + enc_key = builder.CreateByteVector(enc_key) + + # exclude: [int] + exclude = self.exclude + if exclude: + message_fbs.PublishGen.PublishStartExcludeAuthidVector(builder, len(exclude)) + for session in reversed(exclude): + builder.PrependUint64(session) + exclude = builder.EndVector(len(exclude)) + + # exclude_authid: [string] + exclude_authid = self.exclude_authid + if exclude_authid: + _exclude_authid = [] + for authid in exclude_authid: + _exclude_authid.append(builder.CreateString(authid)) + message_fbs.PublishGen.PublishStartExcludeAuthidVector(builder, len(_exclude_authid)) + for o in reversed(_exclude_authid): + builder.PrependUOffsetTRelative(o) + exclude_authid = builder.EndVector(len(_exclude_authid)) + + # exclude_authrole: [string] + exclude_authrole = self.exclude_authrole + if exclude_authid: + _exclude_authrole = [] + for authrole in exclude_authrole: + _exclude_authrole.append(builder.CreateString(authrole)) + message_fbs.PublishGen.PublishStartExcludeAuthroleVector(builder, len(_exclude_authrole)) + for o in reversed(_exclude_authrole): + builder.PrependUOffsetTRelative(o) + exclude_authrole = builder.EndVector(len(_exclude_authrole)) + + # eligible: [int] + eligible = self.eligible + if eligible: + message_fbs.PublishGen.PublishStartEligibleAuthidVector(builder, len(eligible)) + for session in reversed(eligible): + builder.PrependUint64(session) + eligible = builder.EndVector(len(eligible)) + + # eligible_authid: [string] + eligible_authid = self.eligible_authid + if eligible_authid: + _eligible_authid = [] + for authid in eligible_authid: + _eligible_authid.append(builder.CreateString(authid)) + message_fbs.PublishGen.PublishStartEligibleAuthidVector(builder, len(_eligible_authid)) + for o in reversed(_eligible_authid): + builder.PrependUOffsetTRelative(o) + eligible_authid = builder.EndVector(len(_eligible_authid)) + + # eligible_authrole: [string] + eligible_authrole = self.eligible_authrole + if eligible_authrole: + _eligible_authrole = [] + for authrole in eligible_authrole: + _eligible_authrole.append(builder.CreateString(authrole)) + message_fbs.PublishGen.PublishStartEligibleAuthroleVector(builder, len(_eligible_authrole)) + for o in reversed(_eligible_authrole): + builder.PrependUOffsetTRelative(o) + eligible_authrole = builder.EndVector(len(_eligible_authrole)) + + # now start and build a new object .. + message_fbs.PublishGen.PublishStart(builder) + + if self.request is not None: + message_fbs.PublishGen.PublishAddRequest(builder, self.request) + + if topic: + message_fbs.PublishGen.PublishAddTopic(builder, topic) + + if args: + message_fbs.PublishGen.PublishAddArgs(builder, args) + if kwargs is not None: + message_fbs.PublishGen.PublishAddKwargs(builder, kwargs) + if payload is not None: + message_fbs.PublishGen.PublishAddPayload(builder, payload) + + if self.acknowledge is not None: + message_fbs.PublishGen.PublishAddAcknowledge(builder, self.acknowledge) + if self.retain is not None: + message_fbs.PublishGen.PublishAddRetain(builder, self.retain) + if self.exclude_me is not None: + message_fbs.PublishGen.PublishAddExcludeMe(builder, self.exclude_me) + + if exclude: + message_fbs.PublishGen.PublishAddExclude(builder, exclude) + if exclude_authid: + message_fbs.PublishGen.PublishAddExcludeAuthid(builder, exclude_authid) + if exclude_authrole: + message_fbs.PublishGen.PublishAddExcludeAuthrole(builder, exclude_authrole) + + if eligible: + message_fbs.PublishGen.PublishAddEligible(builder, eligible) + if eligible_authid: + message_fbs.PublishGen.PublishAddEligibleAuthid(builder, eligible_authid) + if eligible_authrole: + message_fbs.PublishGen.PublishAddEligibleAuthrole(builder, eligible_authrole) + + if self.enc_algo: + message_fbs.PublishGen.PublishAddEncAlgo(builder, self.enc_algo) + if enc_key: + message_fbs.PublishGen.PublishAddEncKey(builder, enc_key) + if self.enc_serializer: + message_fbs.PublishGen.PublishAddEncSerializer(builder, self.enc_serializer) + + # FIXME: add forward_for + + msg = message_fbs.PublishGen.PublishEnd(builder) + + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.PUBLISH) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): @@ -2431,27 +2929,28 @@ class Event(Message): """ __slots__ = ( - 'subscription', - 'publication', - 'args', - 'kwargs', - 'payload', - 'publisher', - 'publisher_authid', - 'publisher_authrole', - 'topic', - 'retained', - 'x_acknowledged_delivery', - 'enc_algo', - 'enc_key', - 'enc_serializer', - 'forward_for', + '_subscription', + '_publication', + '_args', + '_kwargs', + '_payload', + '_publisher', + '_publisher_authid', + '_publisher_authrole', + '_topic', + '_retained', + '_x_acknowledged_delivery', + '_enc_algo', + '_enc_key', + '_enc_serializer', + '_forward_for', ) - def __init__(self, subscription, publication, args=None, kwargs=None, payload=None, + def __init__(self, subscription=None, publication=None, args=None, kwargs=None, payload=None, publisher=None, publisher_authid=None, publisher_authrole=None, topic=None, retained=None, x_acknowledged_delivery=None, - enc_algo=None, enc_key=None, enc_serializer=None, forward_for=None): + enc_algo=None, enc_key=None, enc_serializer=None, forward_for=None, + from_fbs=None): """ :param subscription: The subscription ID this event is dispatched under. @@ -2501,8 +3000,8 @@ def __init__(self, subscription, publication, args=None, kwargs=None, payload=No :param forward_for: When this Event is forwarded for a client (or from an intermediary router). :type forward_for: list[dict] """ - assert(type(subscription) in six.integer_types) - assert(type(publication) in six.integer_types) + assert(subscription is None or type(subscription) in six.integer_types) + assert(publication is None or type(publication) in six.integer_types) assert(args is None or type(args) in [list, tuple]) assert(kwargs is None or type(kwargs) == dict) assert(payload is None or type(payload) == six.binary_type) @@ -2526,22 +3025,324 @@ def __init__(self, subscription, publication, args=None, kwargs=None, payload=No assert 'authid' in ff and type(ff['authid']) == six.text_type assert 'authrole' in ff and type(ff['authrole']) == six.text_type - Message.__init__(self) - self.subscription = subscription - self.publication = publication - self.args = args - self.kwargs = _validate_kwargs(kwargs) - self.payload = payload - self.publisher = publisher - self.publisher_authid = publisher_authid - self.publisher_authrole = publisher_authrole - self.topic = topic - self.retained = retained - self.x_acknowledged_delivery = x_acknowledged_delivery - self.enc_algo = enc_algo - self.enc_key = enc_key - self.enc_serializer = enc_serializer - self.forward_for = forward_for + Message.__init__(self, from_fbs=from_fbs) + self._subscription = subscription + self._publication = publication + self._args = args + self._kwargs = _validate_kwargs(kwargs) + self._payload = payload + self._publisher = publisher + self._publisher_authid = publisher_authid + self._publisher_authrole = publisher_authrole + self._topic = topic + self._retained = retained + self._x_acknowledged_delivery = x_acknowledged_delivery + self._enc_algo = enc_algo + self._enc_key = enc_key + self._enc_serializer = enc_serializer + self._forward_for = forward_for + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + if not Message.__eq__(self, other): + return False + if other.subscription != self.subscription: + return False + if other.publication != self.publication: + return False + if other.args != self.args: + return False + if other.kwargs != self.kwargs: + return False + if other.payload != self.payload: + return False + if other.publisher != self.publisher: + return False + if other.publisher_authid != self.publisher_authid: + return False + if other.publisher_authrole != self.publisher_authrole: + return False + if other.topic != self.topic: + return False + if other.retained != self.retained: + return False + if other.x_acknowledged_delivery != self.x_acknowledged_delivery: + return False + if other.enc_algo != self.enc_algo: + return False + if other.enc_key != self.enc_key: + return False + if other.enc_serializer != self.enc_serializer: + return False + if other.forward_for != self.forward_for: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + @property + def subscription(self): + if self._subscription is None and self._from_fbs: + self._subscription = self._from_fbs.Subscription() + return self._subscription + + @subscription.setter + def subscription(self, value): + assert(value is None or type(value) in six.integer_types) + self._subscription = value + + @property + def publication(self): + if self._publication is None and self._from_fbs: + self._publication = self._from_fbs.Publication() + return self._publication + + @publication.setter + def publication(self, value): + assert(value is None or type(value) in six.integer_types) + self._publication = value + + @property + def args(self): + if self._args is None and self._from_fbs: + if self._from_fbs.ArgsLength(): + self._args = cbor.loads(bytes(self._from_fbs.ArgsAsBytes())) + return self._args + + @args.setter + def args(self, value): + assert(value is None or type(value) in [list, tuple]) + self._args = value + + @property + def kwargs(self): + if self._kwargs is None and self._from_fbs: + if self._from_fbs.KwargsLength(): + self._kwargs = cbor.loads(bytes(self._from_fbs.KwargsAsBytes())) + return self._kwargs + + @kwargs.setter + def kwargs(self, value): + assert(value is None or type(value) == dict) + self._kwargs = value + + @property + def payload(self): + if self._payload is None and self._from_fbs: + if self._from_fbs.PayloadLength(): + self._payload = self._from_fbs.PayloadAsBytes() + return self._payload + + @payload.setter + def payload(self, value): + assert value is None or type(value) == bytes + self._payload = value + + @property + def publisher(self): + if self._publisher is None and self._from_fbs: + publisher = self._from_fbs.Publisher() + if publisher: + self._publisher = publisher + return self._publisher + + @publisher.setter + def publisher(self, value): + assert value is None or type(value) == int + self._publisher = value + + @property + def publisher_authid(self): + if self._publisher_authid is None and self._from_fbs: + s = self._from_fbs.PublisherAuthid() + if s: + self._publisher_authid = s.decode('utf8') + return self._publisher_authid + + @publisher_authid.setter + def publisher_authid(self, value): + assert value is None or type(value) == str + self._publisher_authid = value + + @property + def publisher_authrole(self): + if self._publisher_authrole is None and self._from_fbs: + s = self._from_fbs.PublisherAuthrole() + if s: + self._publisher_authrole = s.decode('utf8') + return self._publisher_authrole + + @publisher_authrole.setter + def publisher_authrole(self, value): + assert value is None or type(value) == str + self._publisher_authrole = value + + @property + def topic(self): + if self._topic is None and self._from_fbs: + s = self._from_fbs.Topic() + if s: + self._topic = s.decode('utf8') + return self._topic + + @topic.setter + def topic(self, value): + assert value is None or type(value) == str + self._topic = value + + @property + def retained(self): + if self._retained is None and self._from_fbs: + self._retained = self._from_fbs.Retained() + return self._retained + + @retained.setter + def retained(self, value): + assert value is None or type(value) == bool + self._retained = value + + @property + def x_acknowledged_delivery(self): + if self._x_acknowledged_delivery is None and self._from_fbs: + x_acknowledged_delivery = self._from_fbs.Acknowledge() + if x_acknowledged_delivery: + self._x_acknowledged_delivery = x_acknowledged_delivery + return self._x_acknowledged_delivery + + @x_acknowledged_delivery.setter + def x_acknowledged_delivery(self, value): + assert value is None or type(value) == bool + self._x_acknowledged_delivery = value + + @property + def enc_algo(self): + if self._enc_algo is None and self._from_fbs: + enc_algo = self._from_fbs.EncAlgo() + if enc_algo: + self._enc_algo = enc_algo + return self._enc_algo + + @enc_algo.setter + def enc_algo(self, value): + assert value is None or value in [ENC_ALGO_CRYPTOBOX, ENC_ALGO_MQTT, ENC_ALGO_XBR] + self._enc_algo = value + + @property + def enc_key(self): + if self._enc_key is None and self._from_fbs: + if self._from_fbs.EncKeyLength(): + self._enc_key = self._from_fbs.EncKeyAsBytes() + return self._enc_key + + @enc_key.setter + def enc_key(self, value): + assert value is None or type(value) == bytes + self._enc_key = value + + @property + def enc_serializer(self): + if self._enc_serializer is None and self._from_fbs: + enc_serializer = self._from_fbs.EncSerializer() + if enc_serializer: + self._enc_serializer = enc_serializer + return self._enc_serializer + + @enc_serializer.setter + def enc_serializer(self, value): + assert value is None or value in [ENC_SER_JSON, ENC_SER_MSGPACK, ENC_SER_CBOR, ENC_SER_UBJSON] + self._enc_serializer = value + + @property + def forward_for(self): + # FIXME + return None + + @forward_for.setter + def forward_for(self, value): + # FIXME + pass + + @staticmethod + def cast(buf): + return Event(from_fbs=message_fbs.Event.GetRootAsEvent(buf, 0)) + + def build(self, builder): + + args = self.args + if args: + args = builder.CreateByteVector(cbor.dumps(args)) + + kwargs = self.kwargs + if kwargs: + kwargs = builder.CreateByteVector(cbor.dumps(kwargs)) + + payload = self.payload + if payload: + payload = builder.CreateByteVector(payload) + + publisher_authid = self.publisher_authid + if publisher_authid: + publisher_authid = builder.CreateString(publisher_authid) + + publisher_authrole = self.publisher_authrole + if publisher_authrole: + publisher_authrole = builder.CreateString(publisher_authrole) + + topic = self.topic + if topic: + topic = builder.CreateString(topic) + + enc_key = self.enc_key + if enc_key: + enc_key = builder.CreateByteVector(enc_key) + + message_fbs.EventGen.EventStart(builder) + + if self.subscription: + message_fbs.EventGen.EventAddSubscription(builder, self.subscription) + if self.publication: + message_fbs.EventGen.EventAddPublication(builder, self.publication) + + if args: + message_fbs.EventGen.EventAddArgs(builder, args) + if kwargs is not None: + message_fbs.EventGen.EventAddKwargs(builder, kwargs) + if payload is not None: + message_fbs.EventGen.EventAddPayload(builder, payload) + + if self.publisher: + message_fbs.EventGen.EventAddPublisher(builder, self.publisher) + if publisher_authid: + message_fbs.EventGen.EventAddPublisherAuthid(builder, publisher_authid) + if publisher_authrole: + message_fbs.EventGen.EventAddPublisherAuthrole(builder, publisher_authrole) + + if topic: + message_fbs.EventGen.EventAddTopic(builder, topic) + if self.retained is not None: + message_fbs.EventGen.EventAddRetained(builder, self.retained) + if self.x_acknowledged_delivery is not None: + message_fbs.EventGen.EventAddAcknowledge(builder, self.x_acknowledged_delivery) + + if self.enc_algo: + message_fbs.EventGen.EventAddEncAlgo(builder, self.enc_algo) + if enc_key: + message_fbs.EventGen.EventAddEncKey(builder, enc_key) + if self.enc_serializer: + message_fbs.EventGen.EventAddEncSerializer(builder, self.enc_serializer) + + # FIXME: add forward_for + + msg = message_fbs.EventGen.EventEnd(builder) + + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.EVENT) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): diff --git a/autobahn/wamp/message_fbs.py b/autobahn/wamp/message_fbs.py new file mode 100644 index 000000000..849623fdf --- /dev/null +++ b/autobahn/wamp/message_fbs.py @@ -0,0 +1,131 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### + +from __future__ import absolute_import + +import flatbuffers +from autobahn.wamp.gen.wamp.proto import Event as EventGen +from autobahn.wamp.gen.wamp.proto import Publish as PublishGen + +from autobahn.wamp.gen.wamp.proto import Message +from autobahn.wamp.gen.wamp.proto.MessageType import MessageType + +__all__ = ( + 'Event', + 'Message', + 'MessageType', +) + + +class Event(EventGen.Event): + + @classmethod + def GetRootAsEvent(cls, buf, offset): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Event() + x.Init(buf, n + offset) + return x + + def Init(self, buf, pos): + self._tab = flatbuffers.table.Table(buf, pos) + + def ArgsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off:_off+_len] + return None + + def KwargsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off:_off+_len] + return None + + def PayloadAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off:_off+_len] + return None + + def EncKeyAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off:_off+_len] + return None + + +class Publish(PublishGen.Publish): + + @classmethod + def GetRootAsEvent(cls, buf, offset): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Event() + x.Init(buf, n + offset) + return x + + def Init(self, buf, pos): + self._tab = flatbuffers.table.Table(buf, pos) + + def ArgsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off:_off+_len] + return None + + def KwargsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off:_off+_len] + return None + + def PayloadAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off:_off+_len] + return None + + def EncKeyAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off:_off+_len] + return None + diff --git a/autobahn/wamp/serializer.py b/autobahn/wamp/serializer.py index 2176e5fdf..7924b589b 100644 --- a/autobahn/wamp/serializer.py +++ b/autobahn/wamp/serializer.py @@ -34,6 +34,7 @@ from autobahn.wamp.interfaces import IObjectSerializer, ISerializer from autobahn.wamp.exception import ProtocolError from autobahn.wamp import message +from autobahn.wamp import message_fbs # note: __all__ must be a list here, since we dynamically # extend it depending on availability of more serializers @@ -85,7 +86,6 @@ class Serializer(object): def __init__(self, serializer): """ - Constructor. :param serializer: The object serializer to use for WAMP wire-level serialization. :type serializer: An object that implements :class:`autobahn.interfaces.IObjectSerializer`. @@ -104,39 +104,42 @@ def unserialize(self, payload, isBinary=None): """ if isBinary is not None: if isBinary != self._serializer.BINARY: - raise ProtocolError("invalid serialization of WAMP message (binary {0}, but expected {1})".format(isBinary, self._serializer.BINARY)) - + raise ProtocolError( + "invalid serialization of WAMP message (binary {0}, but expected {1})".format(isBinary, + self._serializer.BINARY)) try: raw_msgs = self._serializer.unserialize(payload) except Exception as e: - raise ProtocolError("invalid serialization of WAMP message ({0})".format(e)) - - msgs = [] + raise ProtocolError("invalid serialization of WAMP message: {0} {1}".format(type(e).__name__, e)) - for raw_msg in raw_msgs: + if self._serializer.NAME == u'flatbuffers': + msgs = raw_msgs + else: + msgs = [] + for raw_msg in raw_msgs: - if type(raw_msg) != list: - raise ProtocolError("invalid type {0} for WAMP message".format(type(raw_msg))) + if type(raw_msg) != list: + raise ProtocolError("invalid type {0} for WAMP message".format(type(raw_msg))) - if len(raw_msg) == 0: - raise ProtocolError(u"missing message type in WAMP message") + if len(raw_msg) == 0: + raise ProtocolError(u"missing message type in WAMP message") - message_type = raw_msg[0] + message_type = raw_msg[0] - if type(message_type) not in six.integer_types: - # CBOR doesn't roundtrip number types - # https://bitbucket.org/bodhisnarkva/cbor/issues/6/number-types-dont-roundtrip - raise ProtocolError("invalid type {0} for WAMP message type".format(type(message_type))) + if type(message_type) not in six.integer_types: + # CBOR doesn't roundtrip number types + # https://bitbucket.org/bodhisnarkva/cbor/issues/6/number-types-dont-roundtrip + raise ProtocolError("invalid type {0} for WAMP message type".format(type(message_type))) - Klass = self.MESSAGE_TYPE_MAP.get(message_type) + Klass = self.MESSAGE_TYPE_MAP.get(message_type) - if Klass is None: - raise ProtocolError("invalid WAMP message type {0}".format(message_type)) + if Klass is None: + raise ProtocolError("invalid WAMP message type {0}".format(message_type)) - # this might again raise `ProtocolError` .. - msg = Klass.parse(raw_msg) + # this might again raise `ProtocolError` .. + msg = Klass.parse(raw_msg) - msgs.append(msg) + msgs.append(msg) return msgs @@ -676,6 +679,104 @@ def __init__(self, batched=False): __all__.append('UBJSONSerializer') +_HAS_FLATBUFFERS = False +try: + import flatbuffers # noqa +except ImportError: + pass +else: + _HAS_FLATBUFFERS = True + + +if _HAS_FLATBUFFERS: + + class FlatBuffersObjectSerializer(object): + + NAME = u'flatbuffers' + + BINARY = True + """ + Flag that indicates whether this serializer needs a binary clean transport. + """ + + MESSAGE_TYPE_MAP = { + message_fbs.MessageType.EVENT: (message_fbs.Event, message.Event), + message_fbs.MessageType.PUBLISH: (message_fbs.Publish, message.Publish), + } + + def __init__(self, batched=False): + """ + + :param batched: Flag that controls whether serializer operates in batched mode. + :type batched: bool + """ + assert not batched, 'WAMP-FlatBuffers serialization does not support message batching currently' + self._batched = batched + + def serialize(self, obj): + """ + Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.serialize` + """ + raise NotImplementedError() + + def unserialize(self, payload): + """ + Implements :func:`autobahn.wamp.interfaces.IObjectSerializer.unserialize` + """ + union_msg = message_fbs.Message.Message.GetRootAsMessage(payload, 0) + msg_type = union_msg.MsgType() + + if msg_type in self.MESSAGE_TYPE_MAP: + fbs_klass, wamp_klass = self.MESSAGE_TYPE_MAP[msg_type] + fbs_msg = fbs_klass() + _tab = union_msg.Msg() + fbs_msg.Init(_tab.Bytes, _tab.Pos) + msg = wamp_klass(from_fbs=fbs_msg) + return [msg] + else: + raise NotImplementedError('message type {} not yet implemented for WAMP-FlatBuffers'.format(msg_type)) + + IObjectSerializer.register(FlatBuffersObjectSerializer) + + __all__.append('FlatBuffersObjectSerializer') + SERID_TO_OBJSER[FlatBuffersObjectSerializer.NAME] = FlatBuffersObjectSerializer + + class FlatBuffersSerializer(Serializer): + + SERIALIZER_ID = u"flatbuffers" + """ + ID used as part of the WebSocket subprotocol name to identify the + serializer with WAMP-over-WebSocket. + """ + + RAWSOCKET_SERIALIZER_ID = 5 + """ + ID used in lower four bits of second octet in RawSocket opening + handshake identify the serializer with WAMP-over-RawSocket. + """ + + MIME_TYPE = u"application/x-flatbuffers" + """ + MIME type announced in HTTP request/response headers when running + WAMP-over-Longpoll HTTP fallback. + """ + + def __init__(self, batched=False): + """ + + :param batched: Flag to control whether to put this serialized into batched mode. + :type batched: bool + """ + Serializer.__init__(self, FlatBuffersObjectSerializer(batched=batched)) + if batched: + self.SERIALIZER_ID = u"flatbuffers.batched" + + ISerializer.register(FlatBuffersSerializer) + SERID_TO_SER[FlatBuffersSerializer.SERIALIZER_ID] = FlatBuffersSerializer + + __all__.append('FlatBuffersSerializer') + + def create_transport_serializer(serializer_id): batched = False if '.' in serializer_id: diff --git a/autobahn/wamp/test/test_cryptobox.py b/autobahn/wamp/test/test_cryptobox.py new file mode 100644 index 000000000..74e785b08 --- /dev/null +++ b/autobahn/wamp/test/test_cryptobox.py @@ -0,0 +1,39 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### + +from __future__ import absolute_import + +from autobahn.wamp import cryptobox + +import unittest + + +@unittest.skipIf(not cryptobox.HAS_CRYPTOBOX, 'no cryptobox support present') +class TestCryptoBox(unittest.TestCase): + + def test_create_keyring(self): + kr = cryptobox.KeyRing() + assert kr diff --git a/autobahn/wamp/test/test_cryptosign.py b/autobahn/wamp/test/test_cryptosign.py index fd6d69d00..ebf376842 100644 --- a/autobahn/wamp/test/test_cryptosign.py +++ b/autobahn/wamp/test/test_cryptosign.py @@ -2,7 +2,7 @@ # # The MIT License (MIT) # -# Copyright John-Mark Gurney +# Copyright (c) Crossbar.io Technologies GmbH # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/autobahn/wamp/test/test_serializer.py b/autobahn/wamp/test/test_serializer.py index cd9da90ae..8e47805c3 100644 --- a/autobahn/wamp/test/test_serializer.py +++ b/autobahn/wamp/test/test_serializer.py @@ -120,35 +120,78 @@ def generate_test_messages_binary(): return [(True, msg) for msg in msgs] -class TestSerializer(unittest.TestCase): +def create_serializers(): + _serializers = [] + + _serializers.append(serializer.JsonSerializer()) + _serializers.append(serializer.JsonSerializer(batched=True)) + + _serializers.append(serializer.MsgPackSerializer()) + _serializers.append(serializer.MsgPackSerializer(batched=True)) + + _serializers.append(serializer.CBORSerializer()) + _serializers.append(serializer.CBORSerializer(batched=True)) + + _serializers.append(serializer.UBJSONSerializer()) + _serializers.append(serializer.UBJSONSerializer(batched=True)) + + # FIXME: implement full FlatBuffers serializer for WAMP + if six.PY3: + # WAMP-FlatBuffers currently only supports Python 3 + # _serializers.append(serializer.FlatBuffersSerializer()) + # _serializers.append(serializer.FlatBuffersSerializer(batched=True)) + pass + + return _serializers - def setUp(self): - self._test_messages = generate_test_messages() + generate_test_messages_binary() - self._test_serializers = [] +@unittest.skipIf(not six.PY3, 'WAMP-FlatBuffers currently only supports Python 3') +class TestFlatBuffersSerializer(unittest.TestCase): - # JSON serializer is always available - self._test_serializers.append(serializer.JsonSerializer()) - self._test_serializers.append(serializer.JsonSerializer(batched=True)) + def test_basic(self): + messages = [ + message.Event(123456, + 789123, + args=[1, 2, 3], + kwargs={u'foo': 23, u'bar': u'hello'}, + publisher=666, + retained=True), + message.Publish(123456, + 'com.example.topic1', + args=[1, 2, 3], + kwargs={u'foo': 23, u'bar': u'hello'}, + retain=True) + ] - # MsgPack serializer is optional - if hasattr(serializer, 'MsgPackSerializer'): - self._test_serializers.append(serializer.MsgPackSerializer()) - self._test_serializers.append(serializer.MsgPackSerializer(batched=True)) + ser = serializer.FlatBuffersSerializer() - # CBOR serializer is optional - if hasattr(serializer, 'CBORSerializer'): - self._test_serializers.append(serializer.CBORSerializer()) - self._test_serializers.append(serializer.CBORSerializer(batched=True)) + # from pprint import pprint - # UBJSON serializer is optional - if hasattr(serializer, 'UBJSONSerializer'): - self._test_serializers.append(serializer.UBJSONSerializer()) - self._test_serializers.append(serializer.UBJSONSerializer(batched=True)) + for msg in messages: - print('Testing WAMP serializers {} with {} WAMP test messages'.format([ser.SERIALIZER_ID for ser in self._test_serializers], len(self._test_messages))) + # serialize message + payload, binary = ser.serialize(msg) + + # unserialize message again + msg2 = ser.unserialize(payload, binary)[0] + + # pprint(msg.marshal()) + # pprint(msg2.marshal()) + + # must be equal: message roundtrips via the serializer + self.assertEqual(msg, msg2) + # self.assertEqual(msg.subscription, msg2.subscription) + # self.assertEqual(msg.publication, msg2.publication) + + +class TestSerializer(unittest.TestCase): + + def setUp(self): + self._test_messages = generate_test_messages() + generate_test_messages_binary() + self._test_serializers = create_serializers() + # print('Testing WAMP serializers {} with {} WAMP test messages'.format([ser.SERIALIZER_ID for ser in self._test_serializers], len(self._test_messages))) - def test_deep_equal(self): + def test_deep_equal_msg(self): """ Test deep object equality assert (because I am paranoid). """ @@ -157,7 +200,7 @@ def test_deep_equal(self): o2 = [1, 2, {u'goo': {u'moo': [1, 2, 3]}, u'bar': v, u'baz': [9, 3, 2], u'foo': u'bar'}, v] self.assertEqual(o1, o2) - def test_roundtrip(self): + def test_roundtrip_msg(self): """ Test round-tripping over each serializer. """ @@ -175,7 +218,7 @@ def test_roundtrip(self): # must be equal: message roundtrips via the serializer self.assertEqual([msg], msg2) - def test_crosstrip(self): + def test_crosstrip_msg(self): """ Test cross-tripping over 2 serializers (as is done by WAMP routers). """ @@ -204,7 +247,7 @@ def test_crosstrip(self): # the serializers ser1 -> ser2 self.assertEqual([msg], msg2) - def test_caching(self): + def test_cache_msg(self): """ Test message serialization caching. """ diff --git a/setup.py b/setup.py index ea3f5487f..057997ae9 100644 --- a/setup.py +++ b/setup.py @@ -108,7 +108,8 @@ extras_require_serialization.extend([ 'cbor2>=4.1.2', # MIT license 'cbor>=1.0.0', # Apache 2.0 license - 'py-ubjson>=0.8.4' # Apache 2.0 license + 'py-ubjson>=0.8.4', # Apache 2.0 license + 'flatbuffers>=1.10', # Apache 2.0 license ]) # TLS transport encryption @@ -135,14 +136,22 @@ 'cffi>=1.11.5', # MIT license ] +# cffi based extension modules to build, currently only NVX +cffi_modules = [] +if 'AUTOBAHN_USE_NVX' in os.environ: + # FIXME: building this extension will make the wheel + # produced no longer universal (as in "autobahn-18.4.1-py2.py3-none-any.whl"). + # on the other hand, I don't know how to selectively include this + # based on the install flavor the user has chosen (eg pip install autobahn[nvx] + # should make the following be included) + cffi_modules.append('autobahn/nvx/_utf8validator.py:ffi') + # everything extras_require_all = extras_require_twisted + extras_require_asyncio + \ extras_require_accelerate + extras_require_compress + \ extras_require_serialization + extras_require_encryption + \ extras_require_scram + extras_require_nvx -# extras_require_all += extras_require_compress - # development dependencies extras_require_dev = [ # flake8 will install the version "it needs" @@ -237,6 +246,9 @@ def run_tests(self): 'autobahn', 'autobahn.test', 'autobahn.wamp', + 'autobahn.wamp.gen', + 'autobahn.wamp.gen.wamp', + 'autobahn.wamp.gen.wamp.proto', 'autobahn.wamp.test', 'autobahn.websocket', 'autobahn.websocket.test', @@ -249,15 +261,7 @@ def run_tests(self): 'autobahn.nvx.test', ], package_data={'autobahn.asyncio': ['test/*']}, - - cffi_modules=[ - # FIXME: building this extension will make the wheel - # produced no longer unniversal (as in "autobahn-18.4.1-py2.py3-none-any.whl"). - # on the other hand, I don't know how to selectively include this - # based on the install flavor the user has chosen (eg pip install autobahn[nvx] - # should make the following be included) - # 'autobahn/nvx/_utf8validator.py:ffi' - ], + cffi_modules=cffi_modules, # this flag will make files from MANIFEST.in go into _source_ distributions only include_package_data=True, diff --git a/tox.ini b/tox.ini index a88528100..3fcd3f316 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,7 @@ [tox] envlist = flake8 + coverage # CPython py27-{tw154,tw189,twtrunk,asyncio} @@ -47,21 +48,33 @@ extras = nvx commands = + # print versions we actually use sh -c "which python" python -V - coverage --version - asyncio: coverage run --source {envsitepackagesdir}/autobahn/ {envbindir}/py.test -v {envsitepackagesdir}/autobahn/ - tw154: coverage run --source {envsitepackagesdir}/autobahn/ {envbindir}/trial autobahn - tw189,twtrunk: coverage run --source {envsitepackagesdir}/autobahn/ -m twisted.trial autobahn - coverage report -whitelist_externals = sh + asyncio: {envbindir}/py.test -v {envsitepackagesdir}/autobahn + tw154,tw189: {envbindir}/trial {envsitepackagesdir}/autobahn + twtrunk: python -m twisted.trial {envsitepackagesdir}/autobahn + +whitelist_externals = + sh + coverage + codecov + cp + mkdir + rm + ls + mv setenv = - PYUBJSON_NO_EXTENSION = 1 SODIUM_INSTALL = bundled - #AUTOBAHN_USE_UJSON=1 - #AUTOBAHN_USE_CBOR2=1 + + # env var for serializers + PYUBJSON_NO_EXTENSION = 1 + #AUTOBAHN_USE_NVX = 1 + #AUTOBAHN_USE_UJSON = 1 + #AUTOBAHN_USE_CBOR2 = 1 + asyncio: USE_ASYNCIO = 1 tw154,tw189,twtrunk: USE_TWISTED = 1 @@ -78,4 +91,40 @@ commands = python -V flake8 --version ; These ignores will be removed when they are fixed and we are flake8-clean - flake8 --ignore=E402,E501,E722,E741,N801,N802,N803,N805,N806,N815 autobahn + flake8 --ignore=E402,E501,E722,E741,N801,N802,N803,N805,N806,N815 \ + --exclude "autobahn/wamp/message_fbs.py,autobahn/wamp/gen/*" \ + autobahn + + +[testenv:coverage] +skip_install = False +deps = + coverage + codecov + mock + unittest2 + pytest + pytest_asyncio + pytest-twisted + twisted + git+https://github.com/crossbario/txaio +extras = + encryption + serialization + scram + nvx +passenv = + CODECOV_TOKEN +commands = + # test autobahn on asyncio (run under coverage) + sh -c 'USE_ASYNCIO=1 coverage run --parallel-mode --include "*/autobahn/asyncio/*" --omit "*/twisted/*" --omit "*/test/*.py" {envbindir}/py.test -v {envsitepackagesdir}/autobahn' + + # test autobahn on twisted (run under coverage) + sh -c 'USE_TWISTED=1 coverage run --parallel-mode --include "*/autobahn/*" --omit "*/asyncio/*" --omit "*/test/*.py" -m twisted.trial {envsitepackagesdir}/autobahn' + + twtrunk,asyncio: sh -c "mkdir -p {homedir}/coverage && cp {toxinidir}/.coverage.* {homedir}/coverage/ && ls -la {homedir}/coverage" + + coverage combine + coverage report + coverage html + codecov