mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 22:11:08 -05:00
okay fine
This commit is contained in:
45
.venv/lib/python3.12/site-packages/autobahn/__init__.py
Normal file
45
.venv/lib/python3.12/site-packages/autobahn/__init__.py
Normal file
@@ -0,0 +1,45 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 autobahn._version import __version__
|
||||
|
||||
version = __version__
|
||||
|
||||
import os
|
||||
import txaio
|
||||
|
||||
# this is used in the unit tests (trial/pytest), and when already done here, there
|
||||
# is no risk and headaches with finding out if/where an import implies a framework
|
||||
if os***REMOVED***iron.get('USE_TWISTED', False) and os***REMOVED***iron.get('USE_ASYNCIO', False):
|
||||
raise RuntimeError('fatal: _both_ USE_TWISTED and USE_ASYNCIO are set!')
|
||||
|
||||
if os***REMOVED***iron.get('USE_TWISTED', False):
|
||||
txaio.use_twisted()
|
||||
elif os***REMOVED***iron.get('USE_ASYNCIO', False):
|
||||
txaio.use_asyncio()
|
||||
else:
|
||||
# neither USE_TWISTED nor USE_ASYNCIO selected from env var
|
||||
pass
|
||||
410
.venv/lib/python3.12/site-packages/autobahn/__main__.py
Normal file
410
.venv/lib/python3.12/site-packages/autobahn/__main__.py
Normal file
@@ -0,0 +1,410 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
# this module is available as the 'wamp' command-line tool or as
|
||||
# 'python -m autobahn'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
from copy import copy
|
||||
|
||||
try:
|
||||
from autobahn.twisted.component import Component
|
||||
except ImportError:
|
||||
print("The 'wamp' command-line tool requires Twisted.")
|
||||
print(" pip install autobahn[twisted]")
|
||||
sys.exit(1)
|
||||
|
||||
from twisted.internet.defer import Deferred, inlineCallbacks
|
||||
from twisted.internet.task import react
|
||||
from twisted.internet.protocol import ProcessProtocol
|
||||
|
||||
from autobahn.wamp.exception import ApplicationError
|
||||
from autobahn.wamp.types import PublishOptions
|
||||
from autobahn.wamp.types import SubscribeOptions
|
||||
|
||||
import txaio
|
||||
txaio.use_twisted()
|
||||
|
||||
|
||||
# XXX other ideas to get 'connection config':
|
||||
# - if there .crossbar/ here, load that config and accept a --name or
|
||||
# so to indicate which transport to use
|
||||
|
||||
# wamp [options] {call,publish,subscribe,register} wamp-uri [args] [kwargs]
|
||||
#
|
||||
# kwargs are spec'd with a 2-value-consuming --keyword option:
|
||||
# --keyword name value
|
||||
|
||||
|
||||
top = argparse.ArgumentParser(prog="wamp")
|
||||
top.add_argument(
|
||||
'--url',
|
||||
action='store',
|
||||
help='A WAMP URL to connect to, like ws://127.0.0.1:8080/ws or rs://localhost:1234',
|
||||
required=True,
|
||||
)
|
||||
top.add_argument(
|
||||
'--realm', '-r',
|
||||
action='store',
|
||||
help='The realm to join',
|
||||
default='default',
|
||||
)
|
||||
top.add_argument(
|
||||
'--private-key', '-k',
|
||||
action='store',
|
||||
help='Hex-encoded private key (via WAMP_PRIVATE_KEY if not provided here)',
|
||||
default=os***REMOVED***iron.get('WAMP_PRIVATE_KEY', None),
|
||||
)
|
||||
top.add_argument(
|
||||
'--authid',
|
||||
action='store',
|
||||
help='The authid to use, if authenticating',
|
||||
default=None,
|
||||
)
|
||||
top.add_argument(
|
||||
'--authrole',
|
||||
action='store',
|
||||
help='The role to use, if authenticating',
|
||||
default=None,
|
||||
)
|
||||
top.add_argument(
|
||||
'--max-failures', '-m',
|
||||
action='store',
|
||||
type=int,
|
||||
help='Failures before giving up (0 forever)',
|
||||
default=0,
|
||||
)
|
||||
sub = top.add_subparsers(
|
||||
title="subcommands",
|
||||
dest="subcommand_name",
|
||||
)
|
||||
|
||||
|
||||
call = sub.add_parser(
|
||||
'call',
|
||||
help='Do a WAMP call() and print any results',
|
||||
)
|
||||
call.add_argument(
|
||||
'uri',
|
||||
type=str,
|
||||
help="A WAMP URI to call"
|
||||
)
|
||||
call.add_argument(
|
||||
'call_args',
|
||||
nargs='*',
|
||||
help="All additional arguments are positional args",
|
||||
)
|
||||
call.add_argument(
|
||||
'--keyword',
|
||||
nargs=2,
|
||||
action='append',
|
||||
help="Specify a keyword argument to send: name value",
|
||||
)
|
||||
|
||||
|
||||
publish = sub.add_parser(
|
||||
'publish',
|
||||
help='Do a WAMP publish() with the given args, kwargs',
|
||||
)
|
||||
publish.add_argument(
|
||||
'uri',
|
||||
type=str,
|
||||
help="A WAMP URI to publish"
|
||||
)
|
||||
publish.add_argument(
|
||||
'publish_args',
|
||||
nargs='*',
|
||||
help="All additional arguments are positional args",
|
||||
)
|
||||
publish.add_argument(
|
||||
'--keyword',
|
||||
nargs=2,
|
||||
action='append',
|
||||
help="Specify a keyword argument to send: name value",
|
||||
)
|
||||
|
||||
|
||||
register = sub.add_parser(
|
||||
'register',
|
||||
help='Do a WAMP register() and run a command when called',
|
||||
)
|
||||
register.add_argument(
|
||||
'uri',
|
||||
type=str,
|
||||
help="A WAMP URI to call"
|
||||
)
|
||||
register.add_argument(
|
||||
'--times',
|
||||
type=int,
|
||||
default=0,
|
||||
help="Listen for this number of events, then exit. Default: forever",
|
||||
)
|
||||
register.add_argument(
|
||||
'command',
|
||||
type=str,
|
||||
nargs='*',
|
||||
help=(
|
||||
"Takes one or more args: the executable to call, and any positional "
|
||||
"arguments. As well, the following environment variables are set: "
|
||||
"WAMP_ARGS, WAMP_KWARGS and _JSON variants."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
subscribe = sub.add_parser(
|
||||
'subscribe',
|
||||
help='Do a WAMP subscribe() and print one line of JSON per event',
|
||||
)
|
||||
subscribe.add_argument(
|
||||
'uri',
|
||||
type=str,
|
||||
help="A WAMP URI to call"
|
||||
)
|
||||
subscribe.add_argument(
|
||||
'--times',
|
||||
type=int,
|
||||
default=0,
|
||||
help="Listen for this number of events, then exit. Default: forever",
|
||||
)
|
||||
subscribe.add_argument(
|
||||
'--match',
|
||||
type=str,
|
||||
default='exact',
|
||||
choices=['exact', 'prefix'],
|
||||
help="Massed in the SubscribeOptions, how to match the URI",
|
||||
)
|
||||
|
||||
|
||||
def _create_component(options):
|
||||
"""
|
||||
Configure and return a Component instance according to the given
|
||||
`options`
|
||||
"""
|
||||
if options.url.startswith('ws://'):
|
||||
kind = 'websocket'
|
||||
elif options.url.startswith('rs://'):
|
||||
kind = 'rawsocket'
|
||||
else:
|
||||
raise ValueError(
|
||||
"URL should start with ws:// or rs://"
|
||||
)
|
||||
|
||||
authentication = dict()
|
||||
if options.private_key:
|
||||
if not options.authid:
|
||||
raise ValueError(
|
||||
"Require --authid and --authrole if --private-key (or WAMP_PRIVATE_KEY) is provided"
|
||||
)
|
||||
authentication["cryptosign"] = {
|
||||
"authid": options.authid,
|
||||
"authrole": options.authrole,
|
||||
"privkey": options.private_key,
|
||||
}
|
||||
|
||||
return Component(
|
||||
transports=[{
|
||||
"type": kind,
|
||||
"url": options.url,
|
||||
}],
|
||||
authentication=authentication if authentication else None,
|
||||
realm=options.realm,
|
||||
)
|
||||
|
||||
|
||||
@inlineCallbacks
|
||||
def do_call(reactor, session, options):
|
||||
call_args = list(options.call_args)
|
||||
call_kwargs = dict()
|
||||
if options.keyword is not None:
|
||||
call_kwargs = {
|
||||
k: v
|
||||
for k, v in options.keyword
|
||||
}
|
||||
|
||||
results = yield session.call(options.uri, *call_args, **call_kwargs)
|
||||
print("result: {}".format(results))
|
||||
|
||||
|
||||
@inlineCallbacks
|
||||
def do_publish(reactor, session, options):
|
||||
publish_args = list(options.publish_args)
|
||||
publish_kwargs = {} if options.keyword is None else {
|
||||
k: v
|
||||
for k, v in options.keyword
|
||||
}
|
||||
|
||||
yield session.publish(
|
||||
options.uri,
|
||||
*publish_args,
|
||||
options=PublishOptions(acknowledge=True),
|
||||
**publish_kwargs
|
||||
)
|
||||
|
||||
|
||||
@inlineCallbacks
|
||||
def do_register(reactor, session, options):
|
||||
"""
|
||||
run a command-line upon an RPC call
|
||||
"""
|
||||
|
||||
all_done = Deferred()
|
||||
countdown = [options.times]
|
||||
|
||||
@inlineCallbacks
|
||||
def called(*args, **kw):
|
||||
print("called: args={}, kwargs={}".format(args, kw), file=sys.stderr)
|
||||
env = copy(os***REMOVED***iron)
|
||||
env['WAMP_ARGS'] = ' '.join(args)
|
||||
env['WAMP_ARGS_JSON'] = json.dumps(args)
|
||||
env['WAMP_KWARGS'] = ' '.join('{}={}'.format(k, v) for k, v in kw.items())
|
||||
env['WAMP_KWARGS_JSON'] = json.dumps(kw)
|
||||
|
||||
exe = os.path.abspath(options.command[0])
|
||||
args = options.command
|
||||
done = Deferred()
|
||||
|
||||
class DumpOutput(ProcessProtocol):
|
||||
def outReceived(self, data):
|
||||
sys.stdout.write(data.decode('utf8'))
|
||||
|
||||
def errReceived(self, data):
|
||||
sys.stderr.write(data.decode('utf8'))
|
||||
|
||||
def processExited(self, reason):
|
||||
done.callback(reason.value.exitCode)
|
||||
|
||||
proto = DumpOutput()
|
||||
reactor.spawnProcess(
|
||||
proto, exe, args, env=env, path="."
|
||||
)
|
||||
code = yield done
|
||||
|
||||
if code != 0:
|
||||
print("Failed with exit-code {}".format(code))
|
||||
if countdown[0]:
|
||||
countdown[0] -= 1
|
||||
if countdown[0] <= 0:
|
||||
reactor.callLater(0, all_done.callback, None)
|
||||
|
||||
yield session.register(called, options.uri)
|
||||
yield all_done
|
||||
|
||||
|
||||
@inlineCallbacks
|
||||
def do_subscribe(reactor, session, options):
|
||||
"""
|
||||
print events (one line of JSON per event)
|
||||
"""
|
||||
|
||||
all_done = Deferred()
|
||||
countdown = [options.times]
|
||||
|
||||
@inlineCallbacks
|
||||
def published(*args, **kw):
|
||||
print(
|
||||
json.dumps({
|
||||
"args": args,
|
||||
"kwargs": kw,
|
||||
})
|
||||
)
|
||||
if countdown[0]:
|
||||
countdown[0] -= 1
|
||||
if countdown[0] <= 0:
|
||||
reactor.callLater(0, all_done.callback, None)
|
||||
|
||||
yield session.subscribe(published, options.uri, options=SubscribeOptions(match=options.match))
|
||||
yield all_done
|
||||
|
||||
|
||||
def _main():
|
||||
"""
|
||||
This is a magic name for `python -m autobahn`, and specified as
|
||||
our entry_point in setup.py
|
||||
"""
|
||||
react(_real_main)
|
||||
|
||||
|
||||
@inlineCallbacks
|
||||
def _real_main(reactor):
|
||||
"""
|
||||
Sanity check options, create a connection and run our subcommand
|
||||
"""
|
||||
options = top.parse_args()
|
||||
component = _create_component(options)
|
||||
|
||||
if options.subcommand_name is None:
|
||||
print("Must select a subcommand")
|
||||
sys.exit(1)
|
||||
|
||||
if options.subcommand_name == "register":
|
||||
exe = options.command[0]
|
||||
if not os.path.isabs(exe):
|
||||
print("Full path to the executable required. Found: {}".format(exe), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not os.path.exists(exe):
|
||||
print("Executable not found: {}".format(exe), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
subcommands = {
|
||||
"call": do_call,
|
||||
"register": do_register,
|
||||
"subscribe": do_subscribe,
|
||||
"publish": do_publish,
|
||||
}
|
||||
command_fn = subcommands[options.subcommand_name]
|
||||
|
||||
exit_code = [0]
|
||||
|
||||
@component.on_join
|
||||
@inlineCallbacks
|
||||
def _(session, details):
|
||||
print("connected: authrole={} authmethod={}".format(details.authrole, details.authmethod), file=sys.stderr)
|
||||
try:
|
||||
yield command_fn(reactor, session, options)
|
||||
except ApplicationError as e:
|
||||
print("\n{}: {}\n".format(e.error, ''.join(e.args)))
|
||||
exit_code[0] = 5
|
||||
yield session.leave()
|
||||
|
||||
failures = []
|
||||
|
||||
@component.on_connectfailure
|
||||
def _(comp, fail):
|
||||
print("connect failure: {}".format(fail))
|
||||
failures.append(fail)
|
||||
if options.max_failures > 0 and len(failures) > options.max_failures:
|
||||
print("Too many failures ({}). Exiting".format(len(failures)))
|
||||
reactor.stop()
|
||||
|
||||
yield component.start(reactor)
|
||||
# sys.exit(exit_code[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
||||
29
.venv/lib/python3.12/site-packages/autobahn/_version.py
Normal file
29
.venv/lib/python3.12/site-packages/autobahn/_version.py
Normal file
@@ -0,0 +1,29 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
__version__ = '24.4.2'
|
||||
|
||||
__build__ = '00000000-0000000'
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="e" version="1.1" viewBox="0 0 181.59 89.688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs id="f"><clipPath id="d"><path id="g" d="m441.22 802.44h142.17v-68.65h-142.17v68.65z" clip-rule="evenodd"/></clipPath><clipPath id="c"><path id="h" d="m0 0h595.28v841.89h-595.28v-841.89z"/></clipPath><clipPath id="b"><path id="i" d="m441.22 733.69h142.07v68.65h-142.07v-68.65z" clip-rule="evenodd"/></clipPath><clipPath id="a"><path id="j" d="m0 0h595.28v841.89h-595.28v-841.89z"/></clipPath></defs><g id="k" transform="matrix(1.25 0 0 -1.25 -543.6 1966.7)"><g id="l" transform="translate(-4.7446 769.43)"><g id="m" clip-path="url(#d)"><g id="n"><g id="o"><g id="p" clip-path="url(#c)"><g id="q"><g id="r" clip-path="url(#b)"><g id="s"><g id="t"><g id="u" clip-path="url(#a)"><g id="v"><g id="w" transform="translate(441.22 802.44)"><path id="x" d="m38.099-68.65h-38.099v68.65h76.098v-68.65h-37.999z" fill="#333" fill-rule="evenodd"/></g></g><g id="y"><g id="z" transform="translate(451.95 784.2)"><path id="aa" d="m0-32.07h15.841l26.569 26.558h13.134v5.5121h-15.44l-26.569-26.558h-13.535v-5.5121z" fill="#ff0" fill-rule="evenodd"/></g></g><g id="ab"><g id="ac" transform="translate(481.63 766.06)"><path id="ad" d="m0-3.9086 3.9102 3.9086 8.4219-8.4185h13.535v-5.512h-15.841l-10.026 10.022z" fill="#ff0" fill-rule="evenodd"/></g></g><g id="ae"><g id="af" transform="translate(451.95 784.2)"><path id="ag" d="m13.134-5.5121h-13.134v5.5121h15.44l10.327-10.222-3.9102-4.0088-8.7227 8.7191z" fill="#ff0" fill-rule="evenodd"/></g></g><g id="ah"><g id="ai" transform="translate(503.59 784.3)"><path id="aj" d="m19.952 0h7.6199l-9.3243-15.935 9.6251-16.236h-7.7201l-6.2162 11.325-6.1159-11.325h-7.8204l9.6251 16.236-9.3243 15.935h7.6198l6.0157-11.024 6.0156 11.024z" fill="#fff" fill-rule="evenodd"/></g></g><g id="ak"><g id="al" transform="translate(532.56 784.3)"><path id="am" d="m0 0h11.33c3.9102 0 6.8177-0.70154 8.9232-2.2048 2.0052-1.5033 3.0079-3.7081 3.0079-6.6145 0-1.5033-0.4011-2.9064-1.2032-4.109-0.8021-1.2027-2.0052-2.1047-3.4089-2.706 1.6042-0.4009 2.9076-1.2026 3.9102-2.5055 0.9024-1.2026 1.4037-2.7059 1.4037-4.5099 0-3.1068-1.0026-5.5121-2.9076-7.0153-2.0052-1.6036-4.8125-2.5055-8.5222-2.5055h-12.533v32.17zm6.7175-26.859h5.6146c1.6042 0 2.8073 0.4009 3.7097 1.1024 0.8021 0.8018 1.3034 1.804 1.3034 3.1068 0 3.0066-1.504 4.5099-4.6121 4.5099h-6.0156v-8.7191zm4.8125 13.43c3.4089 0.1002 5.1133 1.403 5.1133 4.0087 0 1.4031-0.5013 2.5055-1.3034 3.1068-0.9023 0.70153-2.2057 1.0022-4.0104 1.0022h-4.612v-8.1178h4.8125z" fill="#fff" fill-rule="evenodd"/></g></g><g id="an"><g id="ao" transform="translate(558.23 784.3)"><path id="ap" d="m6.7175-20.345v-11.826h-6.7175v32.17h12.031c3.8099 0 6.7175-0.80176 8.823-2.5055 2.0052-1.7037 3.0078-4.109 3.0078-7.2158 0-2.1046-0.401-4.0088-1.4036-5.5121-0.9024-1.4031-2.4063-2.6057-4.3113-3.5077l7.0183-13.129v-0.3006h-7.2188l-6.0156 11.826h-5.2136zm5.3138 5.3116c1.7045 0 2.9076 0.5011 3.8099 1.3029 1.0027 0.8018 1.4037 2.0044 1.4037 3.5077 0 1.5033-0.401 2.7059-1.3034 3.6079-0.8021 0.80175-2.2057 1.3028-3.9102 1.3028h-5.3138v-9.7214h5.3138z" fill="#fff" fill-rule="evenodd"/></g></g></g></g></g></g></g></g></g></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="e" version="1.1" viewBox="0 0 131.56 65.675" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs id="f"><clipPath id="d"><path id="g" d="m453.54 805.04h102.05v-49.34h-102.05v49.34z" clip-rule="evenodd"/></clipPath><clipPath id="c"><path id="h" d="m0 0h595.28v841.89h-595.28v-841.89z"/></clipPath><clipPath id="b"><path id="i" d="m453.54 805.04h102.05v-49.34h-102.05v49.34z" clip-rule="evenodd"/></clipPath><clipPath id="a"><path id="j" d="m0 0h595.28v841.89h-595.28v-841.89z"/></clipPath></defs><g id="k" transform="matrix(1.25 0 0 -1.25 -564.93 1008.3)"><g id="l"/><g id="m"/><g id="n"/><g id="o"/><g id="p"/><g id="q"/><g id="r"/><g id="s"/><g id="t"/><g id="u"/><g id="v"/><g id="w"/><g id="x"/><g id="y"/><g id="z"/><g id="aa"/><g id="ab"/><g id="ac"/><g id="ad"/><g id="ae"/><g id="af"/><g id="ag"/><g id="ah"/><g id="ai"/><g id="aj"><g id="ak" clip-path="url(#d)"><g id="al"><g id="am"><g id="an" clip-path="url(#c)"><g id="ao"><g id="ap" clip-path="url(#b)"><g id="aq"><g id="ar"><g id="as" clip-path="url(#a)"><g id="at"><g id="au" transform="translate(453.54 805.04)"><path id="av" d="m0 0h54.609v-49.34h-54.609v49.34z" fill="#b3b3b3" fill-rule="evenodd"/></g></g><g id="aw"><g id="ax" transform="translate(461.19 791.96)"><path id="ay" d="m0-23.086h11.42l19.085 19.085h9.4009v4.0011h-11.058l-19.085-19.085h-9.763v-4.0012z" fill="#ff0" fill-rule="evenodd"/></g></g><g id="az"><g id="ba" transform="translate(482.56 778.91)"><path id="bb" d="m0-2.8292 2.8292 2.8292 6.0383-6.0383h9.6692v-4.0012h-11.326l-7.2102 7.2103z" fill="#ff0" fill-rule="evenodd"/></g></g><g id="bc"><g id="bd" transform="translate(461.19 791.96)"><path id="be" d="m9.4947-4.0011h-9.4947v4.0011h11.152l7.3936-7.3937-2.8291-2.8291-6.2218 6.2217z" fill="#ff0" fill-rule="evenodd"/></g></g><g id="bf"><g id="bg" transform="translate(498.34 792.03)"><path id="bh" d="m9.9906-7.9734 4.3361 7.9734h5.4797l-6.7345-11.468 6.9093-11.658h-5.5433l-4.4473 8.1005-4.4473-8.1005h-5.5432l6.9092 11.658-6.7345 11.468h5.4797l4.3361-7.9734z" fill="#333" fill-rule="evenodd"/></g></g><g id="bi"><g id="bj" transform="translate(519.14 792.03)"><path id="bk" d="m0-23.126v23.126h8.1004c2.806 0 4.9344-0.54004 6.3852-1.6201 1.4506-1.0695 2.176-2.6419 2.176-4.7172 0-1.1331-0.2913-2.1338-0.8736-3.002-0.5824-0.85772-1.3925-1.4877-2.4302-1.8901 1.186-0.2965 2.1178-0.8947 2.7955-1.7948 0.6883-0.9001 1.0324-2.0013 1.0324-3.3037 0-2.2236-0.7095-3.9073-2.1284-5.0509s-3.4414-1.726-6.0674-1.7471h-8.9899zm4.765 10.07v-6.2421h4.082c1.1224 0 1.996 0.2647 2.6208 0.7942 0.6353 0.54 0.953 1.2813 0.953 2.2236 0 2.1178-1.096 3.1926-3.2879 3.2243h-4.3679zm0 3.3673h3.5261c2.4037 0.04234 3.6056 1.0007 3.6056 2.8749 0 1.0483-0.3071 1.8001-0.9213 2.2554-0.6035 0.46589-1.5618 0.69886-2.8748 0.69886h-3.3355v-5.8292z" fill="#333" fill-rule="evenodd"/></g></g><g id="bl"><g id="bm" transform="translate(537.58 792.03)"><path id="bn" d="m8.5611-14.66h-3.796v-8.4657h-4.765v23.126h8.5928c2.732 0 4.8392-0.60886 6.3216-1.8266s2.2237-2.9384 2.2237-5.1621c0-1.5777-0.3442-2.896-1.0324-3.9549-0.6777-1.0483-1.7101-1.8848-3.0973-2.5096l5.0033-9.4505v-0.2223h-5.1145l-4.3361 8.4657zm-3.796 3.8597h3.8437c1.1966 0 2.123 0.3018 2.7795 0.9053 0.6565 0.61418 0.9848 1.456 0.9848 2.5254 0 1.0907-0.3124 1.9484-0.9371 2.5731-0.6142 0.62476-1.5619 0.93712-2.8432 0.93712h-3.8278v-6.941z" fill="#333" fill-rule="evenodd"/></g></g></g></g></g></g></g></g></g></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,53 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import platform
|
||||
|
||||
import autobahn
|
||||
|
||||
# WebSocket protocol support
|
||||
from autobahn.asyncio.websocket import \
|
||||
WebSocketServerProtocol, \
|
||||
WebSocketClientProtocol, \
|
||||
WebSocketServerFactory, \
|
||||
WebSocketClientFactory
|
||||
|
||||
# WAMP support
|
||||
from autobahn.asyncio.wamp import ApplicationSession
|
||||
|
||||
|
||||
__all__ = (
|
||||
'WebSocketServerProtocol',
|
||||
'WebSocketClientProtocol',
|
||||
'WebSocketServerFactory',
|
||||
'WebSocketClientFactory',
|
||||
'ApplicationSession',
|
||||
)
|
||||
|
||||
__ident__ = 'Autobahn/{}-asyncio-{}/{}'.format(autobahn.__version__, platform.python_implementation(), platform.python_version())
|
||||
"""
|
||||
AutobahnPython library implementation (eg. "Autobahn/0.13.0-asyncio-CPython/3.5.1")
|
||||
"""
|
||||
417
.venv/lib/python3.12/site-packages/autobahn/asyncio/component.py
Normal file
417
.venv/lib/python3.12/site-packages/autobahn/asyncio/component.py
Normal file
@@ -0,0 +1,417 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import asyncio
|
||||
import ssl
|
||||
import signal
|
||||
from functools import wraps
|
||||
|
||||
import txaio
|
||||
from autobahn.asyncio.websocket import WampWebSocketClientFactory
|
||||
from autobahn.asyncio.rawsocket import WampRawSocketClientFactory
|
||||
|
||||
from autobahn.wamp import component
|
||||
from autobahn.wamp.exception import TransportLost
|
||||
|
||||
from autobahn.asyncio.wamp import Session
|
||||
from autobahn.wamp.serializer import create_transport_serializers, create_transport_serializer
|
||||
|
||||
|
||||
__all__ = ('Component', 'run')
|
||||
|
||||
|
||||
def _unique_list(seq):
|
||||
"""
|
||||
Return a list with unique elements from sequence, preserving order.
|
||||
"""
|
||||
seen = set()
|
||||
return [x for x in seq if x not in seen and not seen.add(x)]
|
||||
|
||||
|
||||
def _camel_case_from_snake_case(snake):
|
||||
parts = snake.split('_')
|
||||
return parts[0] + ''.join(s.capitalize() for s in parts[1:])
|
||||
|
||||
|
||||
def _create_transport_factory(loop, transport, session_factory):
|
||||
"""
|
||||
Create a WAMP-over-XXX transport factory.
|
||||
"""
|
||||
if transport.type == 'websocket':
|
||||
serializers = create_transport_serializers(transport)
|
||||
factory = WampWebSocketClientFactory(
|
||||
session_factory,
|
||||
url=transport.url,
|
||||
serializers=serializers,
|
||||
proxy=transport.proxy, # either None or a dict with host, port
|
||||
)
|
||||
|
||||
elif transport.type == 'rawsocket':
|
||||
serializer = create_transport_serializer(transport.serializers[0])
|
||||
factory = WampRawSocketClientFactory(session_factory, serializer=serializer)
|
||||
|
||||
else:
|
||||
assert(False), 'should not arrive here'
|
||||
|
||||
# set the options one at a time so we can give user better feedback
|
||||
for k, v in transport.options.items():
|
||||
try:
|
||||
factory.setProtocolOptions(**{k: v})
|
||||
except (TypeError, KeyError):
|
||||
# this allows us to document options as snake_case
|
||||
# until everything internally is upgraded from
|
||||
# camelCase
|
||||
try:
|
||||
factory.setProtocolOptions(
|
||||
**{_camel_case_from_snake_case(k): v}
|
||||
)
|
||||
except (TypeError, KeyError):
|
||||
raise ValueError(
|
||||
"Unknown {} transport option: {}={}".format(transport.type, k, v)
|
||||
)
|
||||
return factory
|
||||
|
||||
|
||||
class Component(component.Component):
|
||||
"""
|
||||
A component establishes a transport and attached a session
|
||||
to a realm using the transport for communication.
|
||||
|
||||
The transports a component tries to use can be configured,
|
||||
as well as the auto-reconnect strategy.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
session_factory = Session
|
||||
"""
|
||||
The factory of the session we will instantiate.
|
||||
"""
|
||||
|
||||
def _is_ssl_error(self, e):
|
||||
"""
|
||||
Internal helper.
|
||||
"""
|
||||
return isinstance(e, ssl.SSLError)
|
||||
|
||||
def _check_native_endpoint(self, endpoint):
|
||||
if isinstance(endpoint, dict):
|
||||
if 'tls' in endpoint:
|
||||
tls = endpoint['tls']
|
||||
if isinstance(tls, (dict, bool)):
|
||||
pass
|
||||
elif isinstance(tls, ssl.SSLContext):
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
"'tls' configuration must be a dict, bool or "
|
||||
"SSLContext instance"
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
"'endpoint' configuration must be a dict or IStreamClientEndpoint"
|
||||
" provider"
|
||||
)
|
||||
|
||||
# async function
|
||||
def _connect_transport(self, loop, transport, session_factory, done):
|
||||
"""
|
||||
Create and connect a WAMP-over-XXX transport.
|
||||
"""
|
||||
factory = _create_transport_factory(loop, transport, session_factory)
|
||||
|
||||
# XXX the rest of this should probably be factored into its
|
||||
# own method (or three!)...
|
||||
|
||||
if transport.proxy:
|
||||
timeout = transport.endpoint.get('timeout', 10) # in seconds
|
||||
if type(timeout) != int:
|
||||
raise ValueError('invalid type {} for timeout in client endpoint configuration'.format(type(timeout)))
|
||||
# do we support HTTPS proxies?
|
||||
|
||||
f = loop.create_connection(
|
||||
protocol_factory=factory,
|
||||
host=transport.proxy['host'],
|
||||
port=transport.proxy['port'],
|
||||
)
|
||||
time_f = asyncio.ensure_future(asyncio.wait_for(f, timeout=timeout))
|
||||
return self._wrap_connection_future(transport, done, time_f)
|
||||
|
||||
elif transport.endpoint['type'] == 'tcp':
|
||||
|
||||
version = transport.endpoint.get('version', 4)
|
||||
if version not in [4, 6]:
|
||||
raise ValueError('invalid IP version {} in client endpoint configuration'.format(version))
|
||||
|
||||
host = transport.endpoint['host']
|
||||
if type(host) != str:
|
||||
raise ValueError('invalid type {} for host in client endpoint configuration'.format(type(host)))
|
||||
|
||||
port = transport.endpoint['port']
|
||||
if type(port) != int:
|
||||
raise ValueError('invalid type {} for port in client endpoint configuration'.format(type(port)))
|
||||
|
||||
timeout = transport.endpoint.get('timeout', 10) # in seconds
|
||||
if type(timeout) != int:
|
||||
raise ValueError('invalid type {} for timeout in client endpoint configuration'.format(type(timeout)))
|
||||
|
||||
tls = transport.endpoint.get('tls', None)
|
||||
tls_hostname = None
|
||||
|
||||
# create a TLS enabled connecting TCP socket
|
||||
if tls:
|
||||
if isinstance(tls, dict):
|
||||
for k in tls.keys():
|
||||
if k not in ["hostname", "trust_root"]:
|
||||
raise ValueError("Invalid key '{}' in 'tls' config".format(k))
|
||||
hostname = tls.get('hostname', host)
|
||||
if type(hostname) != str:
|
||||
raise ValueError('invalid type {} for hostname in TLS client endpoint configuration'.format(hostname))
|
||||
cert_fname = tls.get('trust_root', None)
|
||||
|
||||
tls_hostname = hostname
|
||||
tls = True
|
||||
if cert_fname is not None:
|
||||
tls = ssl.create_default_context(
|
||||
purpose=ssl.Purpose.SERVER_AUTH,
|
||||
cafile=cert_fname,
|
||||
)
|
||||
|
||||
elif isinstance(tls, ssl.SSLContext):
|
||||
# tls=<an SSLContext> is valid
|
||||
tls_hostname = host
|
||||
|
||||
elif tls in [False, True]:
|
||||
if tls:
|
||||
tls_hostname = host
|
||||
|
||||
else:
|
||||
raise RuntimeError('unknown type {} for "tls" configuration in transport'.format(type(tls)))
|
||||
|
||||
f = loop.create_connection(
|
||||
protocol_factory=factory,
|
||||
host=host,
|
||||
port=port,
|
||||
ssl=tls,
|
||||
server_hostname=tls_hostname,
|
||||
)
|
||||
time_f = asyncio.ensure_future(asyncio.wait_for(f, timeout=timeout))
|
||||
return self._wrap_connection_future(transport, done, time_f)
|
||||
|
||||
elif transport.endpoint['type'] == 'unix':
|
||||
path = transport.endpoint['path']
|
||||
timeout = int(transport.endpoint.get('timeout', 10)) # in seconds
|
||||
|
||||
f = loop.create_unix_connection(
|
||||
protocol_factory=factory,
|
||||
path=path,
|
||||
)
|
||||
time_f = asyncio.ensure_future(asyncio.wait_for(f, timeout=timeout))
|
||||
return self._wrap_connection_future(transport, done, time_f)
|
||||
|
||||
else:
|
||||
assert(False), 'should not arrive here'
|
||||
|
||||
def _wrap_connection_future(self, transport, done, conn_f):
|
||||
|
||||
def on_connect_success(result):
|
||||
# async connect call returns a 2-tuple
|
||||
transport, proto = result
|
||||
|
||||
# in the case where we .abort() the transport / connection
|
||||
# during setup, we still get on_connect_success but our
|
||||
# transport is already closed (this will happen if
|
||||
# e.g. there's an "open handshake timeout") -- I don't
|
||||
# know if there's a "better" way to detect this? #python
|
||||
# doesn't know of one, anyway
|
||||
if transport.is_closing():
|
||||
if not txaio.is_called(done):
|
||||
reason = getattr(proto, "_onclose_reason", "Connection already closed")
|
||||
txaio.reject(done, TransportLost(reason))
|
||||
return
|
||||
|
||||
# if e.g. an SSL handshake fails, we will have
|
||||
# successfully connected (i.e. get here) but need to
|
||||
# 'listen' for the "connection_lost" from the underlying
|
||||
# protocol in case of handshake failure .. so we wrap
|
||||
# it. Also, we don't increment transport.success_count
|
||||
# here on purpose (because we might not succeed).
|
||||
|
||||
# XXX double-check that asyncio behavior on TLS handshake
|
||||
# failures is in fact as described above
|
||||
orig = proto.connection_lost
|
||||
|
||||
@wraps(orig)
|
||||
def lost(fail):
|
||||
rtn = orig(fail)
|
||||
if not txaio.is_called(done):
|
||||
# asyncio will call connection_lost(None) in case of
|
||||
# a transport failure, in which case we create an
|
||||
# appropriate exception
|
||||
if fail is None:
|
||||
fail = TransportLost("failed to complete connection")
|
||||
txaio.reject(done, fail)
|
||||
return rtn
|
||||
proto.connection_lost = lost
|
||||
|
||||
def on_connect_failure(err):
|
||||
transport.connect_failures += 1
|
||||
# failed to establish a connection in the first place
|
||||
txaio.reject(done, err)
|
||||
|
||||
txaio.add_callbacks(conn_f, on_connect_success, None)
|
||||
# the errback is added as a second step so it gets called if
|
||||
# there as an error in on_connect_success itself.
|
||||
txaio.add_callbacks(conn_f, None, on_connect_failure)
|
||||
return conn_f
|
||||
|
||||
# async function
|
||||
def start(self, loop=None):
|
||||
"""
|
||||
This starts the Component, which means it will start connecting
|
||||
(and re-connecting) to its configured transports. A Component
|
||||
runs until it is "done", which means one of:
|
||||
- There was a "main" function defined, and it completed successfully;
|
||||
- Something called ``.leave()`` on our session, and we left successfully;
|
||||
- ``.stop()`` was called, and completed successfully;
|
||||
- none of our transports were able to connect successfully (failure);
|
||||
|
||||
:returns: a Future which will resolve (to ``None``) when we are
|
||||
"done" or with an error if something went wrong.
|
||||
"""
|
||||
|
||||
if loop is None:
|
||||
self.log.warn("Using default loop")
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
return self._start(loop=loop)
|
||||
|
||||
|
||||
def run(components, start_loop=True, log_level='info'):
|
||||
"""
|
||||
High-level API to run a series of components.
|
||||
|
||||
This will only return once all the components have stopped
|
||||
(including, possibly, after all re-connections have failed if you
|
||||
have re-connections enabled). Under the hood, this calls
|
||||
|
||||
XXX fixme for asyncio
|
||||
|
||||
-- if you wish to manage the loop yourself, use the
|
||||
:meth:`autobahn.asyncio.component.Component.start` method to start
|
||||
each component yourself.
|
||||
|
||||
:param components: the Component(s) you wish to run
|
||||
:type components: instance or list of :class:`autobahn.asyncio.component.Component`
|
||||
|
||||
:param start_loop: When ``True`` (the default) this method
|
||||
start a new asyncio loop.
|
||||
:type start_loop: bool
|
||||
|
||||
:param log_level: a valid log-level (or None to avoid calling start_logging)
|
||||
:type log_level: string
|
||||
"""
|
||||
|
||||
# actually, should we even let people "not start" the logging? I'm
|
||||
# not sure that's wise... (double-check: if they already called
|
||||
# txaio.start_logging() what happens if we call it again?)
|
||||
if log_level is not None:
|
||||
txaio.start_logging(level=log_level)
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_closed():
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
loop = asyncio.get_event_loop()
|
||||
txaio.config.loop = loop
|
||||
log = txaio.make_logger()
|
||||
|
||||
# see https://github.com/python/asyncio/issues/341 asyncio has
|
||||
# "odd" handling of KeyboardInterrupt when using Tasks (as
|
||||
# run_until_complete does). Another option is to just resture
|
||||
# default SIGINT handling, which is to exit:
|
||||
# import signal
|
||||
# signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
async def nicely_exit(signal):
|
||||
log.info("Shutting down due to {signal}", signal=signal)
|
||||
|
||||
try:
|
||||
tasks = asyncio.Task.all_tasks()
|
||||
except AttributeError:
|
||||
# this changed with python >= 3.7
|
||||
tasks = asyncio.all_tasks()
|
||||
|
||||
for task in tasks:
|
||||
# Do not cancel the current task.
|
||||
|
||||
try:
|
||||
current_task = asyncio.Task.current_task()
|
||||
except AttributeError:
|
||||
current_task = asyncio.current_task()
|
||||
|
||||
if task is not current_task:
|
||||
task.cancel()
|
||||
|
||||
def cancel_all_callback(fut):
|
||||
try:
|
||||
fut.result()
|
||||
except asyncio.CancelledError:
|
||||
log.debug("All task cancelled")
|
||||
except Exception as e:
|
||||
log.error("Error while shutting down: {exception}", exception=e)
|
||||
finally:
|
||||
loop.stop()
|
||||
|
||||
fut = asyncio.gather(*tasks)
|
||||
fut.add_done_callback(cancel_all_callback)
|
||||
|
||||
try:
|
||||
loop.add_signal_handler(signal.SIGINT, lambda: asyncio.ensure_future(nicely_exit("SIGINT")))
|
||||
loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.ensure_future(nicely_exit("SIGTERM")))
|
||||
except NotImplementedError:
|
||||
# signals are not available on Windows
|
||||
pass
|
||||
|
||||
def done_callback(loop, arg):
|
||||
loop.stop()
|
||||
|
||||
# returns a future; could run_until_complete() but see below
|
||||
component._run(loop, components, done_callback)
|
||||
|
||||
if start_loop:
|
||||
try:
|
||||
loop.run_forever()
|
||||
# this is probably more-correct, but then you always get
|
||||
# "Event loop stopped before Future completed":
|
||||
# loop.run_until_complete(f)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
# finally:
|
||||
# signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
# signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
||||
|
||||
# Close the event loop at the end, otherwise an exception is
|
||||
# thrown. https://bugs.python.org/issue23548
|
||||
loop.close()
|
||||
517
.venv/lib/python3.12/site-packages/autobahn/asyncio/rawsocket.py
Normal file
517
.venv/lib/python3.12/site-packages/autobahn/asyncio/rawsocket.py
Normal file
@@ -0,0 +1,517 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import asyncio
|
||||
import struct
|
||||
import math
|
||||
import copy
|
||||
from typing import Optional
|
||||
|
||||
import txaio
|
||||
from autobahn.util import public, _LazyHexFormatter, hltype
|
||||
from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
from autobahn.asyncio.util import get_serializers, create_transport_details, transport_channel_id
|
||||
|
||||
__all__ = (
|
||||
'WampRawSocketServerProtocol',
|
||||
'WampRawSocketClientProtocol',
|
||||
'WampRawSocketServerFactory',
|
||||
'WampRawSocketClientFactory'
|
||||
)
|
||||
|
||||
FRAME_TYPE_DATA = 0
|
||||
FRAME_TYPE_PING = 1
|
||||
FRAME_TYPE_PONG = 2
|
||||
|
||||
MAGIC_BYTE = 0x7F
|
||||
|
||||
|
||||
class PrefixProtocol(asyncio.Protocol):
|
||||
|
||||
prefix_format = '!L'
|
||||
prefix_length = struct.calcsize(prefix_format)
|
||||
max_length = 16 * 1024 * 1024
|
||||
max_length_send = max_length
|
||||
log = txaio.make_logger() # @UndefinedVariable
|
||||
peer: Optional[str] = None
|
||||
is_server: Optional[bool] = None
|
||||
|
||||
@property
|
||||
def transport_details(self) -> Optional[TransportDetails]:
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.transport_details`
|
||||
"""
|
||||
return self._transport_details
|
||||
|
||||
def connection_made(self, transport):
|
||||
# asyncio networking framework entry point, called by asyncio
|
||||
# when the connection is established (either a client or a server)
|
||||
self.log.debug('RawSocker Asyncio: Connection made with peer {peer}', peer=self.peer)
|
||||
|
||||
self.transport = transport
|
||||
|
||||
# determine preliminary transport details (what is known at this point)
|
||||
self._transport_details = create_transport_details(self.transport, self.is_server)
|
||||
self._transport_details.channel_framing = TransportDetails.CHANNEL_FRAMING_RAWSOCKET
|
||||
|
||||
# backward compatibility
|
||||
self.peer = self._transport_details.peer
|
||||
|
||||
self._buffer = b''
|
||||
self._header = None
|
||||
self._wait_closed = txaio.create_future()
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
if hasattr(self, '_wait_closed'):
|
||||
return self._wait_closed
|
||||
else:
|
||||
f = txaio.create_future()
|
||||
f.set_result(True)
|
||||
return f
|
||||
|
||||
def connection_lost(self, exc):
|
||||
self.log.debug('RawSocker Asyncio: Connection lost')
|
||||
self.transport = None
|
||||
self._wait_closed.set_result(True)
|
||||
self._on_connection_lost(exc)
|
||||
|
||||
def _on_connection_lost(self, exc):
|
||||
pass
|
||||
|
||||
def protocol_error(self, msg):
|
||||
self.log.error(msg)
|
||||
self.transport.close()
|
||||
|
||||
def sendString(self, data):
|
||||
l = len(data)
|
||||
if l > self.max_length_send:
|
||||
raise ValueError('Data too big')
|
||||
header = struct.pack(self.prefix_format, len(data))
|
||||
self.transport.write(header)
|
||||
self.transport.write(data)
|
||||
|
||||
def ping(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
def pong(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
def data_received(self, data):
|
||||
self._buffer += data
|
||||
pos = 0
|
||||
remaining = len(self._buffer)
|
||||
while remaining >= self.prefix_length:
|
||||
# do not recalculate header if available from previous call
|
||||
if self._header:
|
||||
frame_type, frame_length = self._header
|
||||
else:
|
||||
header = self._buffer[pos:pos + self.prefix_length]
|
||||
frame_type = ord(header[0:1]) & 0b00000111
|
||||
if frame_type > FRAME_TYPE_PONG:
|
||||
self.protocol_error('Invalid frame type')
|
||||
return
|
||||
frame_length = struct.unpack(self.prefix_format, b'\0' + header[1:])[0]
|
||||
if frame_length > self.max_length:
|
||||
self.protocol_error('Frame too big')
|
||||
return
|
||||
|
||||
if remaining - self.prefix_length >= frame_length:
|
||||
self._header = None
|
||||
pos += self.prefix_length
|
||||
remaining -= self.prefix_length
|
||||
data = self._buffer[pos:pos + frame_length]
|
||||
pos += frame_length
|
||||
remaining -= frame_length
|
||||
|
||||
if frame_type == FRAME_TYPE_DATA:
|
||||
self.stringReceived(data)
|
||||
elif frame_type == FRAME_TYPE_PING:
|
||||
self.ping(data)
|
||||
elif frame_type == FRAME_TYPE_PONG:
|
||||
self.pong(data)
|
||||
else:
|
||||
# save heaader
|
||||
self._header = frame_type, frame_length
|
||||
break
|
||||
|
||||
self._buffer = self._buffer[pos:]
|
||||
|
||||
def stringReceived(self, data):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class RawSocketProtocol(PrefixProtocol):
|
||||
|
||||
def __init__(self):
|
||||
max_size = None
|
||||
if max_size:
|
||||
exp = int(math.ceil(math.log(max_size, 2))) - 9
|
||||
if exp > 15:
|
||||
raise ValueError('Maximum length is 16M')
|
||||
self.max_length = 2**(exp + 9)
|
||||
self._length_exp = exp
|
||||
else:
|
||||
self._length_exp = 15
|
||||
self.max_length = 2**24
|
||||
|
||||
def connection_made(self, transport):
|
||||
PrefixProtocol.connection_made(self, transport)
|
||||
self._handshake_done = False
|
||||
|
||||
def _on_handshake_complete(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def parse_handshake(self):
|
||||
buf = bytearray(self._buffer[:4])
|
||||
if buf[0] != MAGIC_BYTE:
|
||||
raise HandshakeError('Invalid magic byte in handshake')
|
||||
ser = buf[1] & 0x0F
|
||||
lexp = buf[1] >> 4
|
||||
self.max_length_send = 2**(lexp + 9)
|
||||
if buf[2] != 0 or buf[3] != 0:
|
||||
raise HandshakeError('Reserved bytes must be zero')
|
||||
return ser, lexp
|
||||
|
||||
def process_handshake(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def data_received(self, data):
|
||||
self.log.debug('RawSocker Asyncio: data received {data}', data=_LazyHexFormatter(data))
|
||||
if self._handshake_done:
|
||||
return PrefixProtocol.data_received(self, data)
|
||||
else:
|
||||
self._buffer += data
|
||||
if len(self._buffer) >= 4:
|
||||
try:
|
||||
self.process_handshake()
|
||||
except HandshakeError as e:
|
||||
self.protocol_error('Handshake error : {err}'.format(err=e))
|
||||
return
|
||||
self._handshake_done = True
|
||||
self._on_handshake_complete()
|
||||
data = self._buffer[4:]
|
||||
self._buffer = b''
|
||||
if data:
|
||||
PrefixProtocol.data_received(self, data)
|
||||
|
||||
|
||||
ERR_SERIALIZER_UNSUPPORTED = 1
|
||||
|
||||
ERRMAP = {
|
||||
0: "illegal (must not be used)",
|
||||
1: "serializer unsupported",
|
||||
2: "maximum message length unacceptable",
|
||||
3: "use of reserved bits (unsupported feature)",
|
||||
4: "maximum connection count reached"
|
||||
}
|
||||
|
||||
|
||||
class HandshakeError(Exception):
|
||||
def __init__(self, msg, code=0):
|
||||
Exception.__init__(self, msg if not code else msg + ' : %s' % ERRMAP.get(code))
|
||||
|
||||
|
||||
class RawSocketClientProtocol(RawSocketProtocol):
|
||||
|
||||
is_server = False
|
||||
|
||||
def check_serializer(self, ser_id):
|
||||
return True
|
||||
|
||||
def process_handshake(self):
|
||||
ser_id, err = self.parse_handshake()
|
||||
if ser_id == 0:
|
||||
raise HandshakeError('Server returned handshake error', err)
|
||||
if self.serializer_id != ser_id:
|
||||
raise HandshakeError('Server returned different serializer {0} then requested {1}'
|
||||
.format(ser_id, self.serializer_id))
|
||||
|
||||
@property
|
||||
def serializer_id(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def connection_made(self, transport):
|
||||
RawSocketProtocol.connection_made(self, transport)
|
||||
# start handshake
|
||||
hs = bytes(bytearray([MAGIC_BYTE,
|
||||
self._length_exp << 4 | self.serializer_id,
|
||||
0, 0]))
|
||||
transport.write(hs)
|
||||
self.log.debug('RawSocket Asyncio: Client handshake sent')
|
||||
|
||||
|
||||
class RawSocketServerProtocol(RawSocketProtocol):
|
||||
|
||||
is_server = True
|
||||
|
||||
def supports_serializer(self, ser_id):
|
||||
raise NotImplementedError()
|
||||
|
||||
def process_handshake(self):
|
||||
def send_response(lexp, ser_id):
|
||||
b2 = lexp << 4 | (ser_id & 0x0f)
|
||||
self.transport.write(bytes(bytearray([MAGIC_BYTE, b2, 0, 0])))
|
||||
ser_id, _lexp = self.parse_handshake()
|
||||
if not self.supports_serializer(ser_id):
|
||||
send_response(ERR_SERIALIZER_UNSUPPORTED, 0)
|
||||
raise HandshakeError('Serializer unsupported : {ser_id}'.format(ser_id=ser_id))
|
||||
send_response(self._length_exp, ser_id)
|
||||
|
||||
|
||||
# this is transport independent part of WAMP protocol
|
||||
class WampRawSocketMixinGeneral(object):
|
||||
|
||||
def _on_handshake_complete(self):
|
||||
self.log.debug("WampRawSocketProtocol: Handshake complete")
|
||||
# RawSocket connection established. Now let the user WAMP session factory
|
||||
# create a new WAMP session and fire off session open callback.
|
||||
try:
|
||||
if self._transport_details.is_secure:
|
||||
# now that the TLS opening handshake is complete, the actual TLS channel ID
|
||||
# will be available. make sure to set it!
|
||||
channel_id = {
|
||||
'tls-unique': transport_channel_id(self.transport, self._transport_details.is_server, 'tls-unique'),
|
||||
}
|
||||
self._transport_details.channel_id = channel_id
|
||||
|
||||
self._session = self.factory._factory()
|
||||
self._session.onOpen(self)
|
||||
except Exception as e:
|
||||
# Exceptions raised in onOpen are fatal ..
|
||||
self.log.warn("WampRawSocketProtocol: ApplicationSession constructor / onOpen raised ({err})", err=e)
|
||||
self.abort()
|
||||
else:
|
||||
self.log.info("ApplicationSession started.")
|
||||
|
||||
def stringReceived(self, payload):
|
||||
self.log.debug("WampRawSocketProtocol: RX octets: {octets}", octets=_LazyHexFormatter(payload))
|
||||
try:
|
||||
for msg in self._serializer.unserialize(payload):
|
||||
self.log.debug("WampRawSocketProtocol: RX WAMP message: {msg}", msg=msg)
|
||||
self._session.onMessage(msg)
|
||||
|
||||
except ProtocolError as e:
|
||||
self.log.warn("WampRawSocketProtocol: WAMP Protocol Error ({err}) - aborting connection", err=e)
|
||||
self.abort()
|
||||
|
||||
except Exception as e:
|
||||
self.log.warn("WampRawSocketProtocol: WAMP Internal Error ({err}) - aborting connection", err=e)
|
||||
self.abort()
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.send`
|
||||
"""
|
||||
if self.isOpen():
|
||||
self.log.debug('{func}: TX WAMP message: {msg}', func=hltype(self.send), msg=msg)
|
||||
try:
|
||||
payload, _ = self._serializer.serialize(msg)
|
||||
except Exception as e:
|
||||
# all exceptions raised from above should be serialization errors ..
|
||||
raise SerializationError("WampRawSocketProtocol: unable to serialize WAMP application payload ({0})"
|
||||
.format(e))
|
||||
else:
|
||||
self.sendString(payload)
|
||||
self.log.debug("WampRawSocketProtocol: TX octets: {octets}", octets=_LazyHexFormatter(payload))
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
def isOpen(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen`
|
||||
"""
|
||||
return hasattr(self, '_session') and self._session is not None
|
||||
|
||||
|
||||
# this is asyncio dependent part of WAMP protocol
|
||||
class WampRawSocketMixinAsyncio(object):
|
||||
"""
|
||||
Base class for asyncio-based WAMP-over-RawSocket protocols.
|
||||
"""
|
||||
|
||||
def _on_connection_lost(self, exc):
|
||||
try:
|
||||
wasClean = exc is None
|
||||
self._session.onClose(wasClean)
|
||||
except Exception as e:
|
||||
# silently ignore exceptions raised here ..
|
||||
self.log.warn("WampRawSocketProtocol: ApplicationSession.onClose raised ({err})", err=e)
|
||||
self._session = None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.close`
|
||||
"""
|
||||
if self.isOpen():
|
||||
self.transport.close()
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
def abort(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.abort`
|
||||
"""
|
||||
if self.isOpen():
|
||||
if hasattr(self.transport, 'abort'):
|
||||
# ProcessProtocol lacks abortConnection()
|
||||
self.transport.abort()
|
||||
else:
|
||||
self.transport.close()
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketServerProtocol(WampRawSocketMixinGeneral, WampRawSocketMixinAsyncio, RawSocketServerProtocol):
|
||||
"""
|
||||
asyncio-based WAMP-over-RawSocket server protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
def supports_serializer(self, ser_id):
|
||||
if ser_id in self.factory._serializers:
|
||||
self._serializer = copy.copy(self.factory._serializers[ser_id])
|
||||
self.log.debug(
|
||||
"WampRawSocketProtocol: client wants to use serializer '{serializer}'",
|
||||
serializer=ser_id,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
self.log.debug(
|
||||
"WampRawSocketProtocol: opening handshake - no suitable serializer found (client requested {serializer}, and we have {serializers}",
|
||||
serializer=ser_id,
|
||||
serializers=self.factory._serializers.keys(),
|
||||
)
|
||||
self.abort()
|
||||
return False
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketClientProtocol(WampRawSocketMixinGeneral, WampRawSocketMixinAsyncio, RawSocketClientProtocol):
|
||||
"""
|
||||
asyncio-based WAMP-over-RawSocket client protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
@property
|
||||
def serializer_id(self):
|
||||
if not hasattr(self, '_serializer'):
|
||||
self._serializer = copy.copy(self.factory._serializer)
|
||||
return self._serializer.RAWSOCKET_SERIALIZER_ID
|
||||
|
||||
|
||||
class WampRawSocketFactory(object):
|
||||
"""
|
||||
Adapter class for asyncio-based WebSocket client and server factories.def dataReceived(self, data):
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
@public
|
||||
def __call__(self):
|
||||
proto = self.protocol()
|
||||
proto.factory = self
|
||||
return proto
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketServerFactory(WampRawSocketFactory):
|
||||
"""
|
||||
asyncio-based WAMP-over-RawSocket server protocol factory.
|
||||
"""
|
||||
protocol = WampRawSocketServerProtocol
|
||||
|
||||
def __init__(self, factory, serializers=None):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or ``None``
|
||||
for all available serializers).
|
||||
:type serializers: list of objects implementing
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
if callable(factory):
|
||||
self._factory = factory
|
||||
else:
|
||||
self._factory = lambda: factory
|
||||
|
||||
# when no serializers were requested specifically, then support
|
||||
# all that are available
|
||||
if serializers is None:
|
||||
serializers = [serializer() for serializer in get_serializers()]
|
||||
|
||||
if not serializers:
|
||||
raise Exception("could not import any WAMP serializers")
|
||||
|
||||
self._serializers = {ser.RAWSOCKET_SERIALIZER_ID: ser for ser in serializers}
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketClientFactory(WampRawSocketFactory):
|
||||
"""
|
||||
asyncio-based WAMP-over-RawSocket client factory.
|
||||
"""
|
||||
protocol = WampRawSocketClientProtocol
|
||||
|
||||
def __init__(self, factory, serializer=None):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializer: The WAMP serializer to use (or ``None`` for
|
||||
"best" serializer, chosen as the first serializer available from
|
||||
this list: CBOR, MessagePack, UBJSON, JSON).
|
||||
:type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
if callable(factory):
|
||||
self._factory = factory
|
||||
else:
|
||||
self._factory = lambda: factory
|
||||
|
||||
# when no serializer was requested specifically, use the first
|
||||
# one available
|
||||
if serializer is None:
|
||||
serializers = get_serializers()
|
||||
if serializers:
|
||||
serializer = serializers[0]()
|
||||
|
||||
if serializer is None:
|
||||
raise Exception("could not import any WAMP serializer")
|
||||
|
||||
self._serializer = serializer
|
||||
@@ -0,0 +1,26 @@
|
||||
**DO NOT ADD a __init__.py file in this directory**
|
||||
|
||||
"Why not?" you ask; read on!
|
||||
|
||||
1. If we're running asyncio tests, we can't ever call txaio.use_twisted()
|
||||
|
||||
2. If we're running twisted tests, we can't ever call txaio.use_asycnio()...
|
||||
|
||||
3. ...and these are decided/called at import time
|
||||
|
||||
4. so: we can't *import* any of the autobahn.asyncio.* modules if we're
|
||||
running twisted tests (or vice versa)
|
||||
|
||||
5. ...but test-runners (py.test and trial) import things automagically
|
||||
(to "discover" tests)
|
||||
|
||||
6. We use py.test to run asyncio tests; see "setup.cfg" where we tell
|
||||
it "norecursedirs = autobahn/twisted/*" so it doesn't ipmort twisted
|
||||
stuff (and hence call txaio.use_twisted())
|
||||
|
||||
7. We use trial to run twisted tests; the lack of __init__ in here
|
||||
stops it from trying to import this (and hence the parent
|
||||
package). (The only files matching test_*.py are in this
|
||||
directory.)
|
||||
|
||||
*Therefore*, we don't put a __init__ file in this directory.
|
||||
@@ -0,0 +1,235 @@
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from unittest.mock import Mock, call
|
||||
|
||||
from autobahn.asyncio.rawsocket import PrefixProtocol, RawSocketClientProtocol, RawSocketServerProtocol, \
|
||||
WampRawSocketClientFactory, WampRawSocketServerFactory
|
||||
from autobahn.asyncio.util import get_serializers
|
||||
from autobahn.wamp import message
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_sers(event_loop):
|
||||
serializers = get_serializers()
|
||||
assert len(serializers) > 0
|
||||
m = serializers[0]().serialize(message.Abort('close'))
|
||||
assert m
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_prefix(event_loop):
|
||||
p = PrefixProtocol()
|
||||
transport = Mock()
|
||||
receiver = Mock()
|
||||
p.stringReceived = receiver
|
||||
p.connection_made(transport)
|
||||
small_msg = b'\x00\x00\x00\x04abcd'
|
||||
p.data_received(small_msg)
|
||||
receiver.assert_called_once_with(b'abcd')
|
||||
assert len(p._buffer) == 0
|
||||
|
||||
p.sendString(b'abcd')
|
||||
|
||||
# print(transport.write.call_args_list)
|
||||
transport.write.assert_has_calls([call(b'\x00\x00\x00\x04'), call(b'abcd')])
|
||||
|
||||
transport.reset_mock()
|
||||
receiver.reset_mock()
|
||||
|
||||
big_msg = b'\x00\x00\x00\x0C' + b'0123456789AB'
|
||||
|
||||
p.data_received(big_msg[0:2])
|
||||
assert not receiver.called
|
||||
|
||||
p.data_received(big_msg[2:6])
|
||||
assert not receiver.called
|
||||
|
||||
p.data_received(big_msg[6:11])
|
||||
assert not receiver.called
|
||||
|
||||
p.data_received(big_msg[11:16])
|
||||
receiver.assert_called_once_with(b'0123456789AB')
|
||||
|
||||
transport.reset_mock()
|
||||
receiver.reset_mock()
|
||||
|
||||
two_messages = b'\x00\x00\x00\x04' + b'abcd' + b'\x00\x00\x00\x05' + b'12345' + b'\x00'
|
||||
p.data_received(two_messages)
|
||||
receiver.assert_has_calls([call(b'abcd'), call(b'12345')])
|
||||
assert p._buffer == b'\x00'
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_is_closed(event_loop):
|
||||
class CP(RawSocketClientProtocol):
|
||||
@property
|
||||
def serializer_id(self):
|
||||
return 1
|
||||
client = CP()
|
||||
|
||||
on_hs = Mock()
|
||||
transport = Mock()
|
||||
receiver = Mock()
|
||||
client.stringReceived = receiver
|
||||
client._on_handshake_complete = on_hs
|
||||
assert client.is_closed.done()
|
||||
client.connection_made(transport)
|
||||
assert not client.is_closed.done()
|
||||
client.connection_lost(None)
|
||||
|
||||
assert client.is_closed.done()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_raw_socket_server1(event_loop):
|
||||
|
||||
server = RawSocketServerProtocol()
|
||||
ser = Mock(return_value=True)
|
||||
on_hs = Mock()
|
||||
transport = Mock()
|
||||
receiver = Mock()
|
||||
server.supports_serializer = ser
|
||||
server.stringReceived = receiver
|
||||
server._on_handshake_complete = on_hs
|
||||
server.stringReceived = receiver
|
||||
|
||||
server.connection_made(transport)
|
||||
hs = b'\x7F\xF1\x00\x00' + b'\x00\x00\x00\x04abcd'
|
||||
server.data_received(hs)
|
||||
|
||||
ser.assert_called_once_with(1)
|
||||
on_hs.assert_called_once_with()
|
||||
assert transport.write.called
|
||||
transport.write.assert_called_once_with(b'\x7F\xF1\x00\x00')
|
||||
assert not transport.close.called
|
||||
receiver.assert_called_once_with(b'abcd')
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_raw_socket_server_errors(event_loop):
|
||||
|
||||
server = RawSocketServerProtocol()
|
||||
ser = Mock(return_value=True)
|
||||
on_hs = Mock()
|
||||
transport = Mock()
|
||||
receiver = Mock()
|
||||
server.supports_serializer = ser
|
||||
server.stringReceived = receiver
|
||||
server._on_handshake_complete = on_hs
|
||||
server.stringReceived = receiver
|
||||
server.connection_made(transport)
|
||||
server.data_received(b'abcdef')
|
||||
transport.close.assert_called_once_with()
|
||||
|
||||
server = RawSocketServerProtocol()
|
||||
ser = Mock(return_value=False)
|
||||
on_hs = Mock()
|
||||
transport = Mock(spec_set=('close', 'write', 'get_extra_info'))
|
||||
receiver = Mock()
|
||||
server.supports_serializer = ser
|
||||
server.stringReceived = receiver
|
||||
server._on_handshake_complete = on_hs
|
||||
server.stringReceived = receiver
|
||||
server.connection_made(transport)
|
||||
server.data_received(b'\x7F\xF1\x00\x00')
|
||||
transport.close.assert_called_once_with()
|
||||
transport.write.assert_called_once_with(b'\x7F\x10\x00\x00')
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_raw_socket_client1(event_loop):
|
||||
class CP(RawSocketClientProtocol):
|
||||
@property
|
||||
def serializer_id(self):
|
||||
return 1
|
||||
client = CP()
|
||||
|
||||
on_hs = Mock()
|
||||
transport = Mock()
|
||||
receiver = Mock()
|
||||
client.stringReceived = receiver
|
||||
client._on_handshake_complete = on_hs
|
||||
|
||||
client.connection_made(transport)
|
||||
client.data_received(b'\x7F\xF1\x00\x00' + b'\x00\x00\x00\x04abcd')
|
||||
on_hs.assert_called_once_with()
|
||||
assert transport.write.called
|
||||
transport.write.called_one_with(b'\x7F\xF1\x00\x00')
|
||||
assert not transport.close.called
|
||||
receiver.assert_called_once_with(b'abcd')
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_raw_socket_client_error(event_loop):
|
||||
class CP(RawSocketClientProtocol):
|
||||
@property
|
||||
def serializer_id(self):
|
||||
return 1
|
||||
client = CP()
|
||||
|
||||
on_hs = Mock()
|
||||
transport = Mock(spec_set=('close', 'write', 'get_extra_info'))
|
||||
receiver = Mock()
|
||||
client.stringReceived = receiver
|
||||
client._on_handshake_complete = on_hs
|
||||
client.connection_made(transport)
|
||||
|
||||
client.data_received(b'\x7F\xF1\x00\x01')
|
||||
transport.close.assert_called_once_with()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_wamp_server(event_loop):
|
||||
transport = Mock(spec_set=('abort', 'close', 'write', 'get_extra_info'))
|
||||
transport.write = Mock(side_effect=lambda m: messages.append(m))
|
||||
server = Mock(spec=['onOpen', 'onMessage'])
|
||||
|
||||
def fact_server():
|
||||
return server
|
||||
|
||||
messages = []
|
||||
|
||||
proto = WampRawSocketServerFactory(fact_server)()
|
||||
proto.connection_made(transport)
|
||||
assert proto.transport_details.is_server is True
|
||||
assert proto.transport_details.channel_framing == TransportDetails.CHANNEL_FRAMING_RAWSOCKET
|
||||
assert proto.factory._serializers
|
||||
s = proto.factory._serializers[1].RAWSOCKET_SERIALIZER_ID
|
||||
proto.data_received(bytes(bytearray([0x7F, 0xF0 | s, 0, 0])))
|
||||
assert proto._serializer
|
||||
server.onOpen.assert_called_once_with(proto)
|
||||
|
||||
proto.send(message.Abort('close'))
|
||||
for d in messages[1:]:
|
||||
proto.data_received(d)
|
||||
assert server.onMessage.called
|
||||
assert isinstance(server.onMessage.call_args[0][0], message.Abort)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_wamp_client(event_loop):
|
||||
transport = Mock(spec_set=('abort', 'close', 'write', 'get_extra_info'))
|
||||
transport.write = Mock(side_effect=lambda m: messages.append(m))
|
||||
client = Mock(spec=['onOpen', 'onMessage'])
|
||||
|
||||
def fact_client():
|
||||
return client
|
||||
|
||||
messages = []
|
||||
|
||||
proto = WampRawSocketClientFactory(fact_client)()
|
||||
proto.connection_made(transport)
|
||||
assert proto.transport_details.is_server is False
|
||||
assert proto.transport_details.channel_framing == TransportDetails.CHANNEL_FRAMING_RAWSOCKET
|
||||
assert proto._serializer
|
||||
s = proto._serializer.RAWSOCKET_SERIALIZER_ID
|
||||
proto.data_received(bytes(bytearray([0x7F, 0xF0 | s, 0, 0])))
|
||||
client.onOpen.assert_called_once_with(proto)
|
||||
|
||||
proto.send(message.Abort('close'))
|
||||
for d in messages[1:]:
|
||||
proto.data_received(d)
|
||||
assert client.onMessage.called
|
||||
assert isinstance(client.onMessage.call_args[0][0], message.Abort)
|
||||
@@ -0,0 +1,71 @@
|
||||
import os
|
||||
import asyncio
|
||||
import pytest
|
||||
|
||||
import txaio
|
||||
|
||||
# because py.test tries to collect it as a test-case
|
||||
from unittest.mock import Mock
|
||||
|
||||
from autobahn.asyncio.websocket import WebSocketServerFactory
|
||||
|
||||
|
||||
async def echo_async(what, when):
|
||||
await asyncio.sleep(when)
|
||||
return what
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
@pytest.mark.asyncio
|
||||
async def test_echo_async():
|
||||
assert 'Hello!' == await echo_async('Hello!', 0)
|
||||
|
||||
|
||||
# @pytest.mark.asyncio(forbid_global_loop=True)
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
def test_websocket_custom_loop(event_loop):
|
||||
factory = WebSocketServerFactory(loop=event_loop)
|
||||
server = factory()
|
||||
transport = Mock()
|
||||
server.connection_made(transport)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os***REMOVED***iron.get('USE_ASYNCIO', False), reason='test runs on asyncio only')
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_on_connect_server(event_loop):
|
||||
|
||||
num = 42
|
||||
done = txaio.create_future()
|
||||
values = []
|
||||
|
||||
async def foo(x):
|
||||
await asyncio.sleep(1)
|
||||
return x * x
|
||||
|
||||
async def on_connect(req):
|
||||
v = await foo(num)
|
||||
values.append(v)
|
||||
txaio.resolve(done, req)
|
||||
|
||||
factory = WebSocketServerFactory()
|
||||
server = factory()
|
||||
server.onConnect = on_connect
|
||||
transport = Mock()
|
||||
|
||||
server.connection_made(transport)
|
||||
server.data = b'\r\n'.join([
|
||||
b'GET /ws HTTP/1.1',
|
||||
b'Host: www.example.com',
|
||||
b'Sec-WebSocket-Version: 13',
|
||||
b'Origin: http://www.example.com.malicious.com',
|
||||
b'Sec-WebSocket-Extensions: permessage-deflate',
|
||||
b'Sec-WebSocket-Key: tXAxWFUqnhi86Ajj7dRY5g==',
|
||||
b'Connection: keep-alive, Upgrade',
|
||||
b'Upgrade: websocket',
|
||||
b'\r\n', # last string doesn't get a \r\n from join()
|
||||
])
|
||||
server.processHandshake()
|
||||
await done
|
||||
|
||||
assert len(values) == 1
|
||||
assert values[0] == num * num
|
||||
@@ -0,0 +1,134 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import unittest
|
||||
from txaio.testutil import replace_loop
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from autobahn.asyncio.wamp import ApplicationRunner
|
||||
|
||||
|
||||
class TestApplicationRunner(unittest.TestCase):
|
||||
"""
|
||||
Test the autobahn.asyncio.wamp.ApplicationRunner class.
|
||||
"""
|
||||
def _assertRaisesRegex(self, exception, error, *args, **kw):
|
||||
try:
|
||||
self.assertRaisesRegex
|
||||
except AttributeError:
|
||||
f = self.assertRaisesRegexp
|
||||
else:
|
||||
f = self.assertRaisesRegex
|
||||
f(exception, error, *args, **kw)
|
||||
|
||||
def test_explicit_SSLContext(self):
|
||||
"""
|
||||
Ensure that loop.create_connection is called with the exact SSL
|
||||
context object that is passed (as ssl) to the __init__ method of
|
||||
ApplicationRunner.
|
||||
"""
|
||||
with replace_loop(Mock()) as loop:
|
||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
ssl = {}
|
||||
runner = ApplicationRunner('ws://127.0.0.1:8080/ws', 'realm',
|
||||
ssl=ssl)
|
||||
runner.run('_unused_')
|
||||
self.assertIs(ssl, loop.create_connection.call_args[1]['ssl'])
|
||||
|
||||
def test_omitted_SSLContext_insecure(self):
|
||||
"""
|
||||
Ensure that loop.create_connection is called with ssl=False
|
||||
if no ssl argument is passed to the __init__ method of
|
||||
ApplicationRunner and the websocket URL starts with "ws:".
|
||||
"""
|
||||
with replace_loop(Mock()) as loop:
|
||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
runner = ApplicationRunner('ws://127.0.0.1:8080/ws', 'realm')
|
||||
runner.run('_unused_')
|
||||
self.assertIs(False, loop.create_connection.call_args[1]['ssl'])
|
||||
|
||||
def test_omitted_SSLContext_secure(self):
|
||||
"""
|
||||
Ensure that loop.create_connection is called with ssl=True
|
||||
if no ssl argument is passed to the __init__ method of
|
||||
ApplicationRunner and the websocket URL starts with "wss:".
|
||||
"""
|
||||
with replace_loop(Mock()) as loop:
|
||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
runner = ApplicationRunner('wss://127.0.0.1:8080/wss', 'realm')
|
||||
runner.run(self.fail)
|
||||
self.assertIs(True, loop.create_connection.call_args[1]['ssl'])
|
||||
|
||||
def test_conflict_SSL_True_with_ws_url(self):
|
||||
"""
|
||||
ApplicationRunner must raise an exception if given an ssl value of True
|
||||
but only a "ws:" URL.
|
||||
"""
|
||||
with replace_loop(Mock()) as loop:
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
runner = ApplicationRunner('ws://127.0.0.1:8080/wss', 'realm',
|
||||
ssl=True)
|
||||
error = (r'^ssl argument value passed to ApplicationRunner '
|
||||
r'conflicts with the "ws:" prefix of the url '
|
||||
r'argument\. Did you mean to use "wss:"\?$')
|
||||
self._assertRaisesRegex(Exception, error, runner.run, '_unused_')
|
||||
|
||||
def test_conflict_SSLContext_with_ws_url(self):
|
||||
"""
|
||||
ApplicationRunner must raise an exception if given an ssl value that is
|
||||
an instance of SSLContext, but only a "ws:" URL.
|
||||
"""
|
||||
import ssl
|
||||
try:
|
||||
# Try to create an SSLContext, to be as rigorous as we can be
|
||||
# by avoiding making assumptions about the ApplicationRunner
|
||||
# implementation. If we happen to be on a Python that has no
|
||||
# SSLContext, we pass ssl=True, which will simply cause this
|
||||
# test to degenerate to the behavior of
|
||||
# test_conflict_SSL_True_with_ws_url (above). In fact, at the
|
||||
# moment (2015-05-10), none of this matters because the
|
||||
# ApplicationRunner implementation does not check to require
|
||||
# that its ssl argument is either a bool or an SSLContext. But
|
||||
# that may change, so we should be careful.
|
||||
ssl.create_default_context
|
||||
except AttributeError:
|
||||
context = True
|
||||
else:
|
||||
context = ssl.create_default_context()
|
||||
|
||||
with replace_loop(Mock()) as loop:
|
||||
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||
runner = ApplicationRunner('ws://127.0.0.1:8080/wss', 'realm',
|
||||
ssl=context)
|
||||
error = (r'^ssl argument value passed to ApplicationRunner '
|
||||
r'conflicts with the "ws:" prefix of the url '
|
||||
r'argument\. Did you mean to use "wss:"\?$')
|
||||
self._assertRaisesRegex(Exception, error, runner.run, '_unused_')
|
||||
147
.venv/lib/python3.12/site-packages/autobahn/asyncio/util.py
Normal file
147
.venv/lib/python3.12/site-packages/autobahn/asyncio/util.py
Normal file
@@ -0,0 +1,147 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import hashlib
|
||||
from subprocess import Popen
|
||||
from typing import Optional
|
||||
import asyncio
|
||||
from asyncio import sleep # noqa
|
||||
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
|
||||
__all = (
|
||||
'sleep',
|
||||
'peer2str',
|
||||
'transport_channel_id',
|
||||
'create_transport_details',
|
||||
)
|
||||
|
||||
|
||||
def transport_channel_id(transport, is_server: bool, channel_id_type: Optional[str] = None) -> bytes:
|
||||
"""
|
||||
Application-layer user authentication protocols are vulnerable to generic
|
||||
credential forwarding attacks, where an authentication credential sent by
|
||||
a client C to a server M may then be used by M to impersonate C at another
|
||||
server S. To prevent such credential forwarding attacks, modern authentication
|
||||
protocols rely on channel bindings. For example, WAMP-cryptosign can use
|
||||
the tls-unique channel identifier provided by the TLS layer to strongly bind
|
||||
authentication credentials to the underlying channel, so that a credential
|
||||
received on one TLS channel cannot be forwarded on another.
|
||||
|
||||
:param transport: The asyncio TLS transport to extract the TLS channel ID from.
|
||||
:param is_server: Flag indicating the transport is for a server.
|
||||
:param channel_id_type: TLS channel ID type, currently only "tls-unique" is supported.
|
||||
:returns: The TLS channel id (32 bytes).
|
||||
"""
|
||||
if channel_id_type is None:
|
||||
return b'\x00' * 32
|
||||
|
||||
# ssl.CHANNEL_BINDING_TYPES
|
||||
if channel_id_type not in ['tls-unique']:
|
||||
raise Exception("invalid channel ID type {}".format(channel_id_type))
|
||||
|
||||
ssl_obj = transport.get_extra_info('ssl_object')
|
||||
if ssl_obj is None:
|
||||
raise Exception("TLS transport channel_id for tls-unique requested, but ssl_obj not found on transport")
|
||||
|
||||
if not hasattr(ssl_obj, 'get_channel_binding'):
|
||||
raise Exception("TLS transport channel_id for tls-unique requested, but get_channel_binding not found on ssl_obj")
|
||||
|
||||
# https://python.readthedocs.io/en/latest/library/ssl.html#ssl.SSLSocket.get_channel_binding
|
||||
# https://tools.ietf.org/html/rfc5929.html
|
||||
tls_finished_msg: bytes = ssl_obj.get_channel_binding(cb_type='tls-unique')
|
||||
|
||||
if type(tls_finished_msg) != bytes:
|
||||
return b'\x00' * 32
|
||||
else:
|
||||
m = hashlib.sha256()
|
||||
m.update(tls_finished_msg)
|
||||
channel_id = m.digest()
|
||||
return channel_id
|
||||
|
||||
|
||||
def peer2str(transport: asyncio.transports.BaseTransport) -> str:
|
||||
# https://docs.python.org/3.9/library/asyncio-protocol.html?highlight=get_extra_info#asyncio.BaseTransport.get_extra_info
|
||||
# https://docs.python.org/3.9/library/socket.html#socket.socket.getpeername
|
||||
try:
|
||||
peer = transport.get_extra_info('peername')
|
||||
if isinstance(peer, tuple):
|
||||
ip_ver = 4 if len(peer) == 2 else 6
|
||||
return "tcp{2}:{0}:{1}".format(peer[0], peer[1], ip_ver)
|
||||
elif isinstance(peer, str):
|
||||
return "unix:{0}".format(peer)
|
||||
else:
|
||||
return "?:{0}".format(peer)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
proc: Popen = transport.get_extra_info('subprocess')
|
||||
# return 'process:{}'.format(transport.pid)
|
||||
return 'process:{}'.format(proc.pid)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
pipe = transport.get_extra_info('pipe')
|
||||
return 'pipe:{}'.format(pipe)
|
||||
except:
|
||||
pass
|
||||
|
||||
# gracefully fallback if we can't map the peer's transport
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def get_serializers():
|
||||
from autobahn.wamp import serializer
|
||||
|
||||
serializers = ['CBORSerializer', 'MsgPackSerializer', 'UBJSONSerializer', 'JsonSerializer']
|
||||
serializers = list(filter(lambda x: x, map(lambda s: getattr(serializer, s) if hasattr(serializer, s)
|
||||
else None, serializers)))
|
||||
return serializers
|
||||
|
||||
|
||||
def create_transport_details(transport, is_server: bool) -> TransportDetails:
|
||||
# Internal helper. Base class calls this to create a TransportDetails
|
||||
peer = peer2str(transport)
|
||||
|
||||
# https://docs.python.org/3.9/library/asyncio-protocol.html?highlight=get_extra_info#asyncio.BaseTransport.get_extra_info
|
||||
is_secure = transport.get_extra_info('peercert', None) is not None
|
||||
if is_secure:
|
||||
channel_id = {
|
||||
'tls-unique': transport_channel_id(transport, is_server, 'tls-unique'),
|
||||
}
|
||||
channel_type = TransportDetails.CHANNEL_TYPE_TLS
|
||||
peer_cert = None
|
||||
else:
|
||||
channel_id = {}
|
||||
channel_type = TransportDetails.CHANNEL_TYPE_TCP
|
||||
peer_cert = None
|
||||
channel_framing = TransportDetails.CHANNEL_FRAMING_WEBSOCKET
|
||||
|
||||
return TransportDetails(channel_type=channel_type, channel_framing=channel_framing,
|
||||
peer=peer, is_server=is_server, is_secure=is_secure,
|
||||
channel_id=channel_id, peer_cert=peer_cert)
|
||||
309
.venv/lib/python3.12/site-packages/autobahn/asyncio/wamp.py
Normal file
309
.venv/lib/python3.12/site-packages/autobahn/asyncio/wamp.py
Normal file
@@ -0,0 +1,309 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import asyncio
|
||||
import signal
|
||||
|
||||
import txaio
|
||||
txaio.use_asyncio() # noqa
|
||||
|
||||
from autobahn.util import public
|
||||
from autobahn.wamp import protocol
|
||||
from autobahn.wamp.types import ComponentConfig
|
||||
|
||||
from autobahn.websocket.util import parse_url as parse_ws_url
|
||||
from autobahn.rawsocket.util import parse_url as parse_rs_url
|
||||
|
||||
from autobahn.asyncio.websocket import WampWebSocketClientFactory
|
||||
from autobahn.asyncio.rawsocket import WampRawSocketClientFactory
|
||||
|
||||
from autobahn.websocket.compress import PerMessageDeflateOffer, \
|
||||
PerMessageDeflateResponse, PerMessageDeflateResponseAccept
|
||||
|
||||
from autobahn.wamp.interfaces import ITransportHandler, ISession
|
||||
|
||||
__all__ = (
|
||||
'ApplicationSession',
|
||||
'ApplicationSessionFactory',
|
||||
'ApplicationRunner'
|
||||
)
|
||||
|
||||
|
||||
@public
|
||||
class ApplicationSession(protocol.ApplicationSession):
|
||||
"""
|
||||
WAMP application session for asyncio-based applications.
|
||||
|
||||
Implements:
|
||||
|
||||
* ``autobahn.wamp.interfaces.ITransportHandler``
|
||||
* ``autobahn.wamp.interfaces.ISession``
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
|
||||
ITransportHandler.register(ApplicationSession)
|
||||
|
||||
# ISession.register collides with the abc.ABCMeta.register method
|
||||
ISession.abc_register(ApplicationSession)
|
||||
|
||||
|
||||
class ApplicationSessionFactory(protocol.ApplicationSessionFactory):
|
||||
"""
|
||||
WAMP application session factory for asyncio-based applications.
|
||||
"""
|
||||
|
||||
session: ApplicationSession = ApplicationSession
|
||||
"""
|
||||
The application session class this application session factory will use.
|
||||
Defaults to :class:`autobahn.asyncio.wamp.ApplicationSession`.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
|
||||
@public
|
||||
class ApplicationRunner(object):
|
||||
"""
|
||||
This class is a convenience tool mainly for development and quick hosting
|
||||
of WAMP application components.
|
||||
|
||||
It can host a WAMP application component in a WAMP-over-WebSocket client
|
||||
connecting to a WAMP router.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self,
|
||||
url,
|
||||
realm=None,
|
||||
extra=None,
|
||||
serializers=None,
|
||||
ssl=None,
|
||||
proxy=None,
|
||||
headers=None):
|
||||
"""
|
||||
|
||||
:param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://somehost.com:8090/somepath`)
|
||||
:type url: str
|
||||
|
||||
:param realm: The WAMP realm to join the application session to.
|
||||
:type realm: str
|
||||
|
||||
:param extra: Optional extra configuration to forward to the application component.
|
||||
:type extra: dict
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or None for default serializers).
|
||||
Serializers must implement :class:`autobahn.wamp.interfaces.ISerializer`.
|
||||
:type serializers: list
|
||||
|
||||
:param ssl: An (optional) SSL context instance or a bool. See
|
||||
the documentation for the `loop.create_connection` asyncio
|
||||
method, to which this value is passed as the ``ssl``
|
||||
keyword parameter.
|
||||
:type ssl: :class:`ssl.SSLContext` or bool
|
||||
|
||||
:param proxy: Explicit proxy server to use; a dict with ``host`` and ``port`` keys
|
||||
:type proxy: dict or None
|
||||
|
||||
:param headers: Additional headers to send (only applies to WAMP-over-WebSocket).
|
||||
:type headers: dict
|
||||
"""
|
||||
assert(type(url) == str)
|
||||
assert(realm is None or type(realm) == str)
|
||||
assert(extra is None or type(extra) == dict)
|
||||
assert(headers is None or type(headers) == dict)
|
||||
assert(proxy is None or type(proxy) == dict)
|
||||
self.url = url
|
||||
self.realm = realm
|
||||
self.extra = extra or dict()
|
||||
self.serializers = serializers
|
||||
self.ssl = ssl
|
||||
self.proxy = proxy
|
||||
self.headers = headers
|
||||
|
||||
@public
|
||||
def stop(self):
|
||||
"""
|
||||
Stop reconnecting, if auto-reconnecting was enabled.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@public
|
||||
def run(self, make, start_loop=True, log_level='info'):
|
||||
"""
|
||||
Run the application component. Under the hood, this runs the event
|
||||
loop (unless `start_loop=False` is passed) so won't return
|
||||
until the program is done.
|
||||
|
||||
:param make: A factory that produces instances of :class:`autobahn.asyncio.wamp.ApplicationSession`
|
||||
when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`.
|
||||
:type make: callable
|
||||
|
||||
:param start_loop: When ``True`` (the default) this method
|
||||
start a new asyncio loop.
|
||||
:type start_loop: bool
|
||||
|
||||
:returns: None is returned, unless you specify
|
||||
`start_loop=False` in which case the coroutine from calling
|
||||
`loop.create_connection()` is returned. This will yield the
|
||||
(transport, protocol) pair.
|
||||
"""
|
||||
if callable(make):
|
||||
def create():
|
||||
cfg = ComponentConfig(self.realm, self.extra)
|
||||
try:
|
||||
session = make(cfg)
|
||||
except Exception as e:
|
||||
self.log.error('ApplicationSession could not be instantiated: {}'.format(e))
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
loop.stop()
|
||||
raise
|
||||
else:
|
||||
return session
|
||||
else:
|
||||
create = make
|
||||
|
||||
if self.url.startswith('rs'):
|
||||
# try to parse RawSocket URL ..
|
||||
isSecure, host, port = parse_rs_url(self.url)
|
||||
|
||||
# use the first configured serializer if any (which means, auto-choose "best")
|
||||
serializer = self.serializers[0] if self.serializers else None
|
||||
|
||||
# create a WAMP-over-RawSocket transport client factory
|
||||
transport_factory = WampRawSocketClientFactory(create, serializer=serializer)
|
||||
|
||||
else:
|
||||
# try to parse WebSocket URL ..
|
||||
isSecure, host, port, resource, path, params = parse_ws_url(self.url)
|
||||
|
||||
# create a WAMP-over-WebSocket transport client factory
|
||||
transport_factory = WampWebSocketClientFactory(create, url=self.url, serializers=self.serializers, proxy=self.proxy, headers=self.headers)
|
||||
|
||||
# client WebSocket settings - similar to:
|
||||
# - http://crossbar.io/docs/WebSocket-Compression/#production-settings
|
||||
# - http://crossbar.io/docs/WebSocket-Options/#production-settings
|
||||
|
||||
# The permessage-deflate extensions offered to the server ..
|
||||
offers = [PerMessageDeflateOffer()]
|
||||
|
||||
# Function to accept permessage_delate responses from the server ..
|
||||
def accept(response):
|
||||
if isinstance(response, PerMessageDeflateResponse):
|
||||
return PerMessageDeflateResponseAccept(response)
|
||||
|
||||
# set WebSocket options for all client connections
|
||||
transport_factory.setProtocolOptions(maxFramePayloadSize=1048576,
|
||||
maxMessagePayloadSize=1048576,
|
||||
autoFragmentSize=65536,
|
||||
failByDrop=False,
|
||||
openHandshakeTimeout=2.5,
|
||||
closeHandshakeTimeout=1.,
|
||||
tcpNoDelay=True,
|
||||
autoPingInterval=10.,
|
||||
autoPingTimeout=5.,
|
||||
autoPingSize=12,
|
||||
perMessageCompressionOffers=offers,
|
||||
perMessageCompressionAccept=accept)
|
||||
# SSL context for client connection
|
||||
if self.ssl is None:
|
||||
ssl = isSecure
|
||||
else:
|
||||
if self.ssl and not isSecure:
|
||||
raise RuntimeError(
|
||||
'ssl argument value passed to %s conflicts with the "ws:" '
|
||||
'prefix of the url argument. Did you mean to use "wss:"?' %
|
||||
self.__class__.__name__)
|
||||
ssl = self.ssl
|
||||
|
||||
# start the client connection
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_closed() and start_loop:
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
loop = asyncio.get_event_loop()
|
||||
if hasattr(transport_factory, 'loop'):
|
||||
transport_factory.loop = loop
|
||||
|
||||
# assure we are using asyncio
|
||||
# txaio.use_asyncio()
|
||||
assert txaio._explicit_framework == 'asyncio'
|
||||
|
||||
txaio.config.loop = loop
|
||||
coro = loop.create_connection(transport_factory, host, port, ssl=ssl)
|
||||
|
||||
# start a asyncio loop
|
||||
if not start_loop:
|
||||
return coro
|
||||
else:
|
||||
(transport, protocol) = loop.run_until_complete(coro)
|
||||
|
||||
# start logging
|
||||
txaio.start_logging(level=log_level)
|
||||
|
||||
try:
|
||||
loop.add_signal_handler(signal.SIGTERM, loop.stop)
|
||||
except NotImplementedError:
|
||||
# signals are not available on Windows
|
||||
pass
|
||||
|
||||
# 4) now enter the asyncio event loop
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
# wait until we send Goodbye if user hit ctrl-c
|
||||
# (done outside this except so SIGTERM gets the same handling)
|
||||
pass
|
||||
|
||||
# give Goodbye message a chance to go through, if we still
|
||||
# have an active session
|
||||
if protocol._session:
|
||||
loop.run_until_complete(protocol._session.leave())
|
||||
|
||||
loop.close()
|
||||
|
||||
|
||||
# new API
|
||||
class Session(protocol._SessionShim):
|
||||
# XXX these methods are redundant, but put here for possibly
|
||||
# better clarity; maybe a bad idea.
|
||||
|
||||
def on_welcome(self, welcome_msg):
|
||||
pass
|
||||
|
||||
def on_join(self, details):
|
||||
pass
|
||||
|
||||
def on_leave(self, details):
|
||||
self.disconnect()
|
||||
|
||||
def on_connect(self):
|
||||
self.join(self.config.realm)
|
||||
|
||||
def on_disconnect(self):
|
||||
pass
|
||||
386
.venv/lib/python3.12/site-packages/autobahn/asyncio/websocket.py
Normal file
386
.venv/lib/python3.12/site-packages/autobahn/asyncio/websocket.py
Normal file
@@ -0,0 +1,386 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import asyncio
|
||||
from asyncio import iscoroutine
|
||||
from asyncio import Future
|
||||
from collections import deque
|
||||
from typing import Optional
|
||||
|
||||
import txaio
|
||||
txaio.use_asyncio() # noqa
|
||||
|
||||
from autobahn.util import public, hltype
|
||||
from autobahn.asyncio.util import create_transport_details, transport_channel_id
|
||||
from autobahn.wamp import websocket
|
||||
from autobahn.websocket import protocol
|
||||
|
||||
__all__ = (
|
||||
'WebSocketServerProtocol',
|
||||
'WebSocketClientProtocol',
|
||||
'WebSocketServerFactory',
|
||||
'WebSocketClientFactory',
|
||||
'WampWebSocketServerProtocol',
|
||||
'WampWebSocketClientProtocol',
|
||||
'WampWebSocketServerFactory',
|
||||
'WampWebSocketClientFactory',
|
||||
)
|
||||
|
||||
|
||||
def yields(value):
|
||||
"""
|
||||
Returns ``True`` iff the value yields.
|
||||
|
||||
.. seealso:: http://stackoverflow.com/questions/20730248/maybedeferred-analog-with-asyncio
|
||||
"""
|
||||
return isinstance(value, Future) or iscoroutine(value)
|
||||
|
||||
|
||||
class WebSocketAdapterProtocol(asyncio.Protocol):
|
||||
"""
|
||||
Adapter class for asyncio-based WebSocket client and server protocols.
|
||||
"""
|
||||
log = txaio.make_logger()
|
||||
|
||||
peer: Optional[str] = None
|
||||
is_server: Optional[bool] = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
# asyncio networking framework entry point, called by asyncio
|
||||
# when the connection is established (either a client or a server)
|
||||
self.log.debug('{func}(transport={transport})', func=hltype(self.connection_made),
|
||||
transport=transport)
|
||||
|
||||
self.transport = transport
|
||||
|
||||
# determine preliminary transport details (what is know at this point)
|
||||
self._transport_details = create_transport_details(self.transport, self.is_server)
|
||||
|
||||
# backward compatibility
|
||||
self.peer = self._transport_details.peer
|
||||
|
||||
self.receive_queue = deque()
|
||||
self._consume()
|
||||
|
||||
self._connectionMade()
|
||||
|
||||
def connection_lost(self, exc):
|
||||
self._connectionLost(exc)
|
||||
# according to asyncio docs, connection_lost(None) is called
|
||||
# if something else called transport.close()
|
||||
if exc is not None:
|
||||
self.transport.close()
|
||||
self.transport = None
|
||||
|
||||
def _consume(self):
|
||||
self.waiter = Future(loop=self.factory.loop or txaio.config.loop)
|
||||
|
||||
def process(_):
|
||||
while self.receive_queue:
|
||||
data = self.receive_queue.popleft()
|
||||
if self.transport:
|
||||
self._dataReceived(data)
|
||||
self._consume()
|
||||
|
||||
self.waiter.add_done_callback(process)
|
||||
|
||||
def data_received(self, data):
|
||||
self.receive_queue.append(data)
|
||||
if not self.waiter.done():
|
||||
self.waiter.set_result(None)
|
||||
|
||||
def _closeConnection(self, abort=False):
|
||||
if abort and hasattr(self.transport, 'abort'):
|
||||
self.transport.abort()
|
||||
else:
|
||||
self.transport.close()
|
||||
|
||||
def _onOpen(self):
|
||||
if self._transport_details.is_secure:
|
||||
# now that the TLS opening handshake is complete, the actual TLS channel ID
|
||||
# will be available. make sure to set it!
|
||||
channel_id = {
|
||||
'tls-unique': transport_channel_id(self.transport, self._transport_details.is_server, 'tls-unique'),
|
||||
}
|
||||
self._transport_details.channel_id = channel_id
|
||||
|
||||
res = self.onOpen()
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onMessageBegin(self, isBinary):
|
||||
res = self.onMessageBegin(isBinary)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onMessageFrameBegin(self, length):
|
||||
res = self.onMessageFrameBegin(length)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onMessageFrameData(self, payload):
|
||||
res = self.onMessageFrameData(payload)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onMessageFrameEnd(self):
|
||||
res = self.onMessageFrameEnd()
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onMessageFrame(self, payload):
|
||||
res = self.onMessageFrame(payload)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onMessageEnd(self):
|
||||
res = self.onMessageEnd()
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onMessage(self, payload, isBinary):
|
||||
res = self.onMessage(payload, isBinary)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onPing(self, payload):
|
||||
res = self.onPing(payload)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onPong(self, payload):
|
||||
res = self.onPong(payload)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def _onClose(self, wasClean, code, reason):
|
||||
res = self.onClose(wasClean, code, reason)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def registerProducer(self, producer, streaming):
|
||||
raise Exception("not implemented")
|
||||
|
||||
def unregisterProducer(self):
|
||||
# note that generic websocket/protocol.py code calls
|
||||
# .unregisterProducer whenever we dropConnection -- that's
|
||||
# correct behavior on Twisted so either we'd have to
|
||||
# try/except there, or special-case Twisted, ..or just make
|
||||
# this "not an error"
|
||||
pass
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServerProtocol):
|
||||
"""
|
||||
Base class for asyncio-based WebSocket server protocols.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.websocket.interfaces.IWebSocketChannel`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketClientProtocol(WebSocketAdapterProtocol, protocol.WebSocketClientProtocol):
|
||||
"""
|
||||
Base class for asyncio-based WebSocket client protocols.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.websocket.interfaces.IWebSocketChannel`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def _onConnect(self, response):
|
||||
res = self.onConnect(response)
|
||||
self.log.debug('{func}: {res}', func=hltype(self._onConnect), res=res)
|
||||
if yields(res):
|
||||
asyncio.ensure_future(res)
|
||||
|
||||
def startTLS(self):
|
||||
raise Exception("WSS over explicit proxies not implemented")
|
||||
|
||||
|
||||
class WebSocketAdapterFactory(object):
|
||||
"""
|
||||
Adapter class for asyncio-based WebSocket client and server factories.
|
||||
"""
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __call__(self):
|
||||
proto = self.protocol()
|
||||
proto.factory = self
|
||||
return proto
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory):
|
||||
"""
|
||||
Base class for asyncio-based WebSocket server factories.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
protocol = WebSocketServerProtocol
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
.. note::
|
||||
In addition to all arguments to the constructor of
|
||||
:meth:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`,
|
||||
you can supply a ``loop`` keyword argument to specify the
|
||||
asyncio event loop to be used.
|
||||
"""
|
||||
loop = kwargs.pop('loop', None)
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
|
||||
protocol.WebSocketServerFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketClientFactory(WebSocketAdapterFactory, protocol.WebSocketClientFactory):
|
||||
"""
|
||||
Base class for asyncio-based WebSocket client factories.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
|
||||
.. note::
|
||||
In addition to all arguments to the constructor of
|
||||
:meth:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`,
|
||||
you can supply a ``loop`` keyword argument to specify the
|
||||
asyncio event loop to be used.
|
||||
"""
|
||||
loop = kwargs.pop('loop', None)
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
|
||||
protocol.WebSocketClientFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketServerProtocol(websocket.WampWebSocketServerProtocol, WebSocketServerProtocol):
|
||||
"""
|
||||
asyncio-based WAMP-over-WebSocket server protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketServerFactory(websocket.WampWebSocketServerFactory, WebSocketServerFactory):
|
||||
"""
|
||||
asyncio-based WAMP-over-WebSocket server factory.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
protocol = WampWebSocketServerProtocol
|
||||
|
||||
def __init__(self, factory, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or ``None``
|
||||
for all available serializers).
|
||||
:type serializers: list of objects implementing
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
|
||||
serializers = kwargs.pop('serializers', None)
|
||||
|
||||
websocket.WampWebSocketServerFactory.__init__(self, factory, serializers)
|
||||
|
||||
kwargs['protocols'] = self._protocols
|
||||
|
||||
# noinspection PyCallByClass
|
||||
WebSocketServerFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketClientProtocol(websocket.WampWebSocketClientProtocol, WebSocketClientProtocol):
|
||||
"""
|
||||
asyncio-based WAMP-over-WebSocket client protocols.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketClientFactory(websocket.WampWebSocketClientFactory, WebSocketClientFactory):
|
||||
"""
|
||||
asyncio-based WAMP-over-WebSocket client factory.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
protocol = WampWebSocketClientProtocol
|
||||
|
||||
def __init__(self, factory, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializer: The WAMP serializer to use (or ``None`` for
|
||||
"best" serializer, chosen as the first serializer available from
|
||||
this list: CBOR, MessagePack, UBJSON, JSON).
|
||||
:type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
|
||||
serializers = kwargs.pop('serializers', None)
|
||||
|
||||
websocket.WampWebSocketClientFactory.__init__(self, factory, serializers)
|
||||
|
||||
kwargs['protocols'] = self._protocols
|
||||
|
||||
WebSocketClientFactory.__init__(self, *args, **kwargs)
|
||||
@@ -0,0 +1,93 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
try:
|
||||
from autobahn import xbr
|
||||
HAS_XBR = True
|
||||
except:
|
||||
HAS_XBR = False
|
||||
|
||||
|
||||
if HAS_XBR:
|
||||
import uuid
|
||||
import asyncio
|
||||
import txaio
|
||||
from autobahn.util import hl
|
||||
from autobahn.xbr._interfaces import IProvider, ISeller, IConsumer, IBuyer
|
||||
|
||||
def run_in_executor(*args, **kwargs):
|
||||
return asyncio.get_running_loop().run_in_executor(None, *args, **kwargs)
|
||||
|
||||
class SimpleBlockchain(xbr.SimpleBlockchain):
|
||||
backgroundCaller = run_in_executor
|
||||
|
||||
class KeySeries(xbr.KeySeries):
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self, api_id, price, interval, on_rotate=None):
|
||||
super().__init__(api_id, price, interval, on_rotate)
|
||||
self.running = False
|
||||
|
||||
async def start(self):
|
||||
"""
|
||||
Start offering and selling data encryption keys in the background.
|
||||
"""
|
||||
assert not self.running
|
||||
|
||||
self.log.info('Starting key rotation every {interval} seconds for api_id="{api_id}" ..',
|
||||
interval=hl(self._interval), api_id=hl(uuid.UUID(bytes=self._api_id)))
|
||||
self.running = True
|
||||
|
||||
async def rotate_with_interval():
|
||||
while self.running:
|
||||
await self._rotate()
|
||||
await asyncio.sleep(self._interval)
|
||||
|
||||
asyncio.create_task(rotate_with_interval())
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop offering/selling data encryption keys.
|
||||
"""
|
||||
if not self.running:
|
||||
raise RuntimeError('cannot stop {} - not currently running'.format(self.__class__.__name__))
|
||||
|
||||
self.running = False
|
||||
|
||||
class SimpleSeller(xbr.SimpleSeller):
|
||||
"""
|
||||
Simple XBR seller component. This component can be used by a XBR seller delegate to
|
||||
handle the automated selling of data encryption keys to the XBR market maker.
|
||||
"""
|
||||
xbr.SimpleSeller.KeySeries = KeySeries
|
||||
|
||||
class SimpleBuyer(xbr.SimpleBuyer):
|
||||
pass
|
||||
|
||||
ISeller.register(SimpleSeller)
|
||||
IProvider.register(SimpleSeller)
|
||||
IBuyer.register(SimpleBuyer)
|
||||
IConsumer.register(SimpleBuyer)
|
||||
48
.venv/lib/python3.12/site-packages/autobahn/exception.py
Normal file
48
.venv/lib/python3.12/site-packages/autobahn/exception.py
Normal file
@@ -0,0 +1,48 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 autobahn.util import public
|
||||
|
||||
__all__ = (
|
||||
'PayloadExceededError',
|
||||
)
|
||||
|
||||
|
||||
@public
|
||||
class PayloadExceededError(RuntimeError):
|
||||
"""
|
||||
Exception raised when the serialized and framed (eg WebSocket/RawSocket) WAMP payload
|
||||
exceeds the transport message size limit.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class Disconnected(RuntimeError):
|
||||
"""
|
||||
Exception raised when trying to perform an operation which
|
||||
requires a connection when the WebSocket/RawSocket is not
|
||||
currently connected
|
||||
"""
|
||||
29
.venv/lib/python3.12/site-packages/autobahn/nvx/__init__.py
Normal file
29
.venv/lib/python3.12/site-packages/autobahn/nvx/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 autobahn.nvx._utf8validator import Utf8Validator # noqa
|
||||
|
||||
__all__ = ('Utf8Validator',)
|
||||
648
.venv/lib/python3.12/site-packages/autobahn/nvx/_utf8validator.c
Normal file
648
.venv/lib/python3.12/site-packages/autobahn/nvx/_utf8validator.c
Normal file
@@ -0,0 +1,648 @@
|
||||
[AWS-SECRET-REMOVED]///////////////////////////////////////
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) typedef int 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.
|
||||
//
|
||||
[AWS-SECRET-REMOVED]///////////////////////////////////////
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// http://stackoverflow.com/questions/11228855/header-files-for-simd-intrinsics
|
||||
#if defined(__SSE2__) || defined(__SSE4_1__)
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
|
||||
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 1
|
||||
|
||||
|
||||
typedef struct {
|
||||
size_t current_index;
|
||||
size_t total_index;
|
||||
int state;
|
||||
int impl;
|
||||
} utf8_validator_t;
|
||||
|
||||
|
||||
#define UTF8_VALIDATOR_OPTIMAL 0
|
||||
#define UTF8_VALIDATOR_TABLE_DFA 1
|
||||
#define UTF8_VALIDATOR_UNROLLED_DFA 2
|
||||
#define UTF8_VALIDATOR_SSE2_DFA 3
|
||||
#define UTF8_VALIDATOR_SSE41_DFA 4
|
||||
|
||||
|
||||
int nvx_utf8vld_get_impl (void* utf8vld) {
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
return vld->impl;
|
||||
}
|
||||
|
||||
int nvx_utf8vld_set_impl (void* utf8vld, int impl) {
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
if (impl) {
|
||||
// set requested implementation
|
||||
//
|
||||
#ifndef __SSE4_1__
|
||||
# ifdef __SSE2__
|
||||
if (impl <= UTF8_VALIDATOR_SSE2_DFA) {
|
||||
vld->impl = impl;
|
||||
}
|
||||
# else
|
||||
if (impl <= UTF8_VALIDATOR_UNROLLED_DFA) {
|
||||
vld->impl = impl;
|
||||
}
|
||||
# endif
|
||||
#else
|
||||
if (impl <= UTF8_VALIDATOR_SSE41_DFA) {
|
||||
vld->impl = impl;
|
||||
}
|
||||
#endif
|
||||
|
||||
} else {
|
||||
// set optimal implementation
|
||||
//
|
||||
#ifndef __SSE4_1__
|
||||
# ifdef __SSE2__
|
||||
vld->impl = UTF8_VALIDATOR_SSE2_DFA;
|
||||
# else
|
||||
vld->impl = UTF8_VALIDATOR_UNROLLED_DFA;
|
||||
# endif
|
||||
#else
|
||||
vld->impl = UTF8_VALIDATOR_SSE41_DFA;
|
||||
#endif
|
||||
|
||||
}
|
||||
return vld->impl;
|
||||
}
|
||||
|
||||
|
||||
void nvx_utf8vld_reset (void* utf8vld) {
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
vld->state = 0;
|
||||
vld->current_index = -1;
|
||||
vld->total_index = -1;
|
||||
}
|
||||
|
||||
|
||||
void* nvx_utf8vld_new () {
|
||||
void* p = malloc(sizeof(utf8_validator_t));
|
||||
nvx_utf8vld_reset(p);
|
||||
nvx_utf8vld_set_impl(p, 0);
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
void nvx_utf8vld_free (void* utf8vld) {
|
||||
free (utf8vld);
|
||||
}
|
||||
|
||||
|
||||
// unrolled DFA from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
//
|
||||
static const uint8_t UTF8VALIDATOR_DFA[] __attribute__((aligned(64))) =
|
||||
{
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
|
||||
|
||||
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
|
||||
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
|
||||
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
|
||||
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
|
||||
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
|
||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // s7..s8
|
||||
};
|
||||
|
||||
|
||||
int _nvx_utf8vld_validate_table (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
int state = vld->state;
|
||||
|
||||
const uint8_t* end = data + length;
|
||||
|
||||
while (data < end && state != 1) {
|
||||
state = UTF8VALIDATOR_DFA[256 + state * 16 + UTF8VALIDATOR_DFA[*data++]];
|
||||
}
|
||||
|
||||
vld->state = state;
|
||||
|
||||
if (state == 0) {
|
||||
// UTF8 is valid and ends on codepoint
|
||||
return 0;
|
||||
} else {
|
||||
if (state == 1) {
|
||||
// UTF8 is invalid
|
||||
return -1;
|
||||
} else {
|
||||
// UTF8 is valid, but does not end on codepoint (needs more data)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// unrolled DFA from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
//
|
||||
#define DFA_TRANSITION(state, octet) \
|
||||
if (state == 0) { \
|
||||
if (octet >= 0x00 && octet <= 0x7f) { \
|
||||
/* reflective state 0 */ \
|
||||
} else if (octet >= 0xc2 && octet <= 0xdf) { \
|
||||
state = 2; \
|
||||
} else if ((octet >= 0xe1 && octet <= 0xec) || octet == 0xee || octet == 0xef) { \
|
||||
state = 3; \
|
||||
} else if (octet == 0xe0) { \
|
||||
state = 4; \
|
||||
} else if (octet == 0xed) { \
|
||||
state = 5; \
|
||||
} else if (octet == 0xf4) { \
|
||||
state = 8; \
|
||||
} else if (octet == 0xf1 || octet == 0xf2 || octet == 0xf3) { \
|
||||
state = 7; \
|
||||
} else if (octet == 0xf0) { \
|
||||
state = 6; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 2) { \
|
||||
if (octet >= 0x80 && octet <= 0xbf) { \
|
||||
state = 0; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 3) { \
|
||||
if (octet >= 0x80 && octet <= 0xbf) { \
|
||||
state = 2; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 4) { \
|
||||
if (octet >= 0xa0 && octet <= 0xbf) { \
|
||||
state = 2; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 5) { \
|
||||
if (octet >= 0x80 && octet <= 0x9f) { \
|
||||
state = 2; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 6) { \
|
||||
if (octet >= 0x90 && octet <= 0xbf) { \
|
||||
state = 3; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 7) { \
|
||||
if (octet >= 0x80 && octet <= 0xbf) { \
|
||||
state = 3; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 8) { \
|
||||
if (octet >= 0x80 && octet <= 0x8f) { \
|
||||
state = 3; \
|
||||
} else { \
|
||||
state = 1; \
|
||||
} \
|
||||
} else if (state == 1) { \
|
||||
/* refective state 1 */ \
|
||||
} else { \
|
||||
/* should not arrive here */ \
|
||||
}
|
||||
|
||||
|
||||
int _nvx_utf8vld_validate_unrolled (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
int state = vld->state;
|
||||
|
||||
const uint8_t* tail_end = data + length;
|
||||
|
||||
while (data < tail_end && state != 1) {
|
||||
|
||||
// get tail octet
|
||||
int octet = *data;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++data;
|
||||
}
|
||||
|
||||
vld->state = state;
|
||||
|
||||
if (state == 0) {
|
||||
// UTF8 is valid and ends on codepoint
|
||||
return 0;
|
||||
} else {
|
||||
if (state == 1) {
|
||||
// UTF8 is invalid
|
||||
return -1;
|
||||
} else {
|
||||
// UTF8 is valid, but does not end on codepoint (needs more data)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
__m128i _mm_load_si128 (__m128i const* mem_addr)
|
||||
#include "emmintrin.h"
|
||||
Instruction: movdqa
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
int _mm_movemask_epi8 (__m128i a)
|
||||
#include "emmintrin.h"
|
||||
Instruction: pmovmskb
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
__m128i _mm_srli_si128 (__m128i a, int imm)
|
||||
#include "emmintrin.h"
|
||||
Instruction: psrldq
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
int _mm_cvtsi128_si32 (__m128i a)
|
||||
#include "emmintrin.h"
|
||||
Instruction: movd
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
int _mm_extract_epi16 (__m128i a, int imm)
|
||||
#include "emmintrin.h"
|
||||
Instruction: pextrw
|
||||
CPUID Feature Flag: SSE2
|
||||
|
||||
int _mm_extract_epi8 (__m128i a, const int imm)
|
||||
#include "smmintrin.h"
|
||||
Instruction: pextrb
|
||||
CPUID Feature Flag: SSE4.1
|
||||
*/
|
||||
|
||||
#ifdef __SSE2__
|
||||
int _nvx_utf8vld_validate_sse2 (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
int state = vld->state;
|
||||
|
||||
const uint8_t* tail_end = data + length;
|
||||
|
||||
// process unaligned head (sub 16 octets)
|
||||
//
|
||||
size_t head_len = ((size_t) data) % sizeof(__m128i);
|
||||
if (head_len) {
|
||||
|
||||
const uint8_t* head_end = data + head_len;
|
||||
|
||||
while (data < head_end && state != UTF8_REJECT) {
|
||||
|
||||
// get head octet
|
||||
int octet = *data;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++data;
|
||||
}
|
||||
}
|
||||
|
||||
// process aligned middle (16 octet chunks)
|
||||
//
|
||||
const __m128i* ptr = ((const __m128i*) data);
|
||||
const __m128i* end = ((const __m128i*) data) + ((length - head_len) / sizeof(__m128i));
|
||||
|
||||
while (ptr < end && state != UTF8_REJECT) {
|
||||
|
||||
__builtin_prefetch(ptr + 1, 0, 3);
|
||||
//__builtin_prefetch(ptr + 4, 0, 3); // 16*4=64: cache-line prefetch
|
||||
|
||||
__m128i xmm1 = _mm_load_si128(ptr);
|
||||
|
||||
if (__builtin_expect(state || _mm_movemask_epi8(xmm1), 0)) {
|
||||
|
||||
// copy to different reg - this allows the prefetching to
|
||||
// do its job in the meantime (I guess ..)
|
||||
|
||||
// SSE2 variant
|
||||
//
|
||||
int octet;
|
||||
|
||||
// octet 0
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 1
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 2
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 3
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 4
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 5
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 6
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 7
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 8
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 9
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 10
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 11
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 12
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 13
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 14
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 15
|
||||
xmm1 = _mm_srli_si128(xmm1, 1);
|
||||
octet = 0xff & _mm_cvtsi128_si32(xmm1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
}
|
||||
++ptr;
|
||||
}
|
||||
|
||||
// process unaligned tail (sub 16 octets)
|
||||
//
|
||||
const uint8_t* tail_ptr = (const uint8_t*) ptr;
|
||||
|
||||
while (tail_ptr < tail_end && state != UTF8_REJECT) {
|
||||
|
||||
// get tail octet
|
||||
int octet = *tail_ptr;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++tail_ptr;
|
||||
}
|
||||
|
||||
vld->state = state;
|
||||
|
||||
if (state == UTF8_ACCEPT) {
|
||||
// UTF8 is valid and ends on codepoint
|
||||
return 0;
|
||||
} else {
|
||||
if (state == UTF8_REJECT) {
|
||||
// UTF8 is invalid
|
||||
return -1;
|
||||
} else {
|
||||
// UTF8 is valid, but does not end on codepoint (needs more data)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __SSE4_1__
|
||||
int _nvx_utf8vld_validate_sse4 (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
int state = vld->state;
|
||||
|
||||
const uint8_t* tail_end = data + length;
|
||||
|
||||
// process unaligned head (sub 16 octets)
|
||||
//
|
||||
size_t head_len = ((size_t) data) % sizeof(__m128i);
|
||||
if (head_len) {
|
||||
|
||||
const uint8_t* head_end = data + head_len;
|
||||
|
||||
while (data < head_end && state != UTF8_REJECT) {
|
||||
|
||||
// get head octet
|
||||
int octet = *data;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++data;
|
||||
}
|
||||
}
|
||||
|
||||
// process aligned middle (16 octet chunks)
|
||||
//
|
||||
const __m128i* ptr = ((const __m128i*) data);
|
||||
const __m128i* end = ((const __m128i*) data) + ((length - head_len) / sizeof(__m128i));
|
||||
|
||||
while (ptr < end && state != UTF8_REJECT) {
|
||||
|
||||
__builtin_prefetch(ptr + 1, 0, 3);
|
||||
//__builtin_prefetch(ptr + 4, 0, 3); // 16*4=64: cache-line prefetch
|
||||
|
||||
__m128i xmm1 = _mm_load_si128(ptr);
|
||||
|
||||
|
||||
if (__builtin_expect(state || _mm_movemask_epi8(xmm1), 0)) {
|
||||
|
||||
// copy to different reg - this allows the prefetching to
|
||||
// do its job in the meantime (I guess ..)
|
||||
|
||||
// SSE4.1 variant
|
||||
//
|
||||
int octet;
|
||||
|
||||
// octet 0
|
||||
octet = _mm_extract_epi8(xmm1, 0);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 1
|
||||
octet = _mm_extract_epi8(xmm1, 1);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 2
|
||||
octet = _mm_extract_epi8(xmm1, 2);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 3
|
||||
octet = _mm_extract_epi8(xmm1, 3);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 4
|
||||
octet = _mm_extract_epi8(xmm1, 4);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 5
|
||||
octet = _mm_extract_epi8(xmm1, 5);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 6
|
||||
octet = _mm_extract_epi8(xmm1, 6);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 7
|
||||
octet = _mm_extract_epi8(xmm1, 7);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 8
|
||||
octet = _mm_extract_epi8(xmm1, 8);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 9
|
||||
octet = _mm_extract_epi8(xmm1, 9);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 10
|
||||
octet = _mm_extract_epi8(xmm1, 10);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 11
|
||||
octet = _mm_extract_epi8(xmm1, 11);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 12
|
||||
octet = _mm_extract_epi8(xmm1, 12);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 13
|
||||
octet = _mm_extract_epi8(xmm1, 13);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 14
|
||||
octet = _mm_extract_epi8(xmm1, 14);
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
// octet 15
|
||||
octet = _mm_extract_epi8(xmm1, 15);
|
||||
DFA_TRANSITION(state, octet);
|
||||
}
|
||||
++ptr;
|
||||
}
|
||||
|
||||
// process unaligned tail (sub 16 octets)
|
||||
//
|
||||
const uint8_t* tail_ptr = (const uint8_t*) ptr;
|
||||
|
||||
while (tail_ptr < tail_end && state != UTF8_REJECT) {
|
||||
|
||||
// get tail octet
|
||||
int octet = *tail_ptr;
|
||||
|
||||
// do the DFA
|
||||
DFA_TRANSITION(state, octet);
|
||||
|
||||
++tail_ptr;
|
||||
}
|
||||
|
||||
vld->state = state;
|
||||
|
||||
if (state == UTF8_ACCEPT) {
|
||||
// UTF8 is valid and ends on codepoint
|
||||
return 0;
|
||||
} else {
|
||||
if (state == UTF8_REJECT) {
|
||||
// UTF8 is invalid
|
||||
return -1;
|
||||
} else {
|
||||
// UTF8 is valid, but does not end on codepoint (needs more data)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int nvx_utf8vld_validate (void* utf8vld, const uint8_t* data, size_t length) {
|
||||
|
||||
utf8_validator_t* vld = (utf8_validator_t*) utf8vld;
|
||||
|
||||
switch (vld->impl) {
|
||||
case UTF8_VALIDATOR_TABLE_DFA:
|
||||
return _nvx_utf8vld_validate_table(utf8vld, data, length);
|
||||
case UTF8_VALIDATOR_UNROLLED_DFA:
|
||||
return _nvx_utf8vld_validate_unrolled(utf8vld, data, length);
|
||||
#ifdef __SSE2__
|
||||
case UTF8_VALIDATOR_SSE2_DFA:
|
||||
return _nvx_utf8vld_validate_table(utf8vld, data, length);
|
||||
#endif
|
||||
#ifdef __SSE4_1__
|
||||
case UTF8_VALIDATOR_SSE41_DFA:
|
||||
return _nvx_utf8vld_validate_table(utf8vld, data, length);
|
||||
#endif
|
||||
default:
|
||||
return _nvx_utf8vld_validate_table(utf8vld, data, length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
from cffi import FFI
|
||||
|
||||
|
||||
ffi = FFI()
|
||||
|
||||
ffi.cdef("""
|
||||
void* nvx_utf8vld_new ();
|
||||
|
||||
void nvx_utf8vld_reset (void* utf8vld);
|
||||
|
||||
int nvx_utf8vld_validate (void* utf8vld, const uint8_t* data, size_t length);
|
||||
|
||||
void nvx_utf8vld_free (void* utf8vld);
|
||||
|
||||
int nvx_utf8vld_set_impl(void* utf8vld, int impl);
|
||||
|
||||
int nvx_utf8vld_get_impl(void* utf8vld);
|
||||
""")
|
||||
|
||||
if 'AUTOBAHN_USE_NVX' in os***REMOVED***iron and os***REMOVED***iron['AUTOBAHN_USE_NVX'] in ['1', 'true']:
|
||||
optional = False # :noindex:
|
||||
else:
|
||||
optional = True # :noindex:
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), '_utf8validator.c')) as fd:
|
||||
c_source = fd.read()
|
||||
ffi.set_source(
|
||||
"_nvx_utf8validator",
|
||||
c_source,
|
||||
libraries=[],
|
||||
extra_compile_args=['-std=c99', '-Wall', '-Wno-strict-prototypes', '-O3', '-march=native'],
|
||||
optional=optional
|
||||
)
|
||||
|
||||
|
||||
class Utf8Validator:
|
||||
"""
|
||||
:noindex:
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.ffi = ffi
|
||||
|
||||
from _nvx_utf8validator import lib
|
||||
self.lib = lib
|
||||
|
||||
self._vld = self.ffi.gc(self.lib.nvx_utf8vld_new(), self.lib.nvx_utf8vld_free)
|
||||
# print(self.lib.nvx_utf8vld_get_impl(self._vld))
|
||||
|
||||
def reset(self):
|
||||
self.lib.nvx_utf8vld_reset(self._vld)
|
||||
|
||||
def validate(self, ba):
|
||||
res = self.lib.nvx_utf8vld_validate(self._vld, ba, len(ba))
|
||||
return (res >= 0, res == 0, None, None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ffi.compile()
|
||||
@@ -0,0 +1,25 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
@@ -0,0 +1,359 @@
|
||||
# coding=utf-8
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import struct
|
||||
import unittest
|
||||
|
||||
from autobahn.websocket.utf8validator import Utf8Validator as StandardUtf8Validator
|
||||
|
||||
try:
|
||||
from _nvx_utf8validator import lib # noqa
|
||||
from autobahn.nvx import Utf8Validator as NvxUtf8Validator
|
||||
except ImportError:
|
||||
HAS_NVX = False
|
||||
else:
|
||||
HAS_NVX = True
|
||||
|
||||
|
||||
def _create_utf8_test_sequences():
|
||||
"""
|
||||
Create test sequences for UTF-8 decoder tests from
|
||||
http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
|
||||
"""
|
||||
|
||||
UTF8_TEST_SEQUENCES = []
|
||||
|
||||
# 1 Some correct UTF-8 text
|
||||
vss = b'\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5'
|
||||
vs = [b"Some valid UTF-8 sequences", []]
|
||||
vs[1].append((True, b'hello\x24world')) # U+0024
|
||||
vs[1].append((True, b'hello\xC2\xA2world')) # U+00A2
|
||||
vs[1].append((True, b'hello\xE2\x82\xACworld')) # U+20AC
|
||||
vs[1].append((True, b'hello\xF0\xA4\xAD\xA2world')) # U+24B62
|
||||
vs[1].append((True, vss))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# All prefixes of correct UTF-8 text
|
||||
vs = [
|
||||
b"All prefixes of a valid UTF-8 string that contains multi-byte code points",
|
||||
[]]
|
||||
v = StandardUtf8Validator()
|
||||
for i in range(1, len(vss) + 1):
|
||||
v.reset()
|
||||
res = v.validate(vss[:i])
|
||||
vs[1].append((res[0] and res[1], vss[:i]))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 2.1 First possible sequence of a certain length
|
||||
vs = [b"First possible sequence of a certain length", []]
|
||||
vs[1].append((True, b'\x00'))
|
||||
vs[1].append((True, b'\xc2\x80'))
|
||||
vs[1].append((True, b'\xe0\xa0\x80'))
|
||||
vs[1].append((True, b'\xf0\x90\x80\x80'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# the following conform to the UTF-8 integer encoding scheme, but
|
||||
# valid UTF-8 only allows for Unicode code points up to U+10FFFF
|
||||
vs = [b"First possible sequence length 5/6 (invalid codepoints)", []]
|
||||
vs[1].append((False, b'\xf8\x88\x80\x80\x80'))
|
||||
vs[1].append((False, b'\xfc\x84\x80\x80\x80\x80'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 2.2 Last possible sequence of a certain length
|
||||
vs = [b"Last possible sequence of a certain length", []]
|
||||
vs[1].append((True, b'\x7f'))
|
||||
vs[1].append((True, b'\xdf\xbf'))
|
||||
vs[1].append((True, b'\xef\xbf\xbf'))
|
||||
vs[1].append((True, b'\xf4\x8f\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# the following conform to the UTF-8 integer encoding scheme, but
|
||||
# valid UTF-8 only allows for Unicode code points up to U+10FFFF
|
||||
vs = [b"Last possible sequence length 4/5/6 (invalid codepoints)", []]
|
||||
vs[1].append((False, b'\xf7\xbf\xbf\xbf'))
|
||||
vs[1].append((False, b'\xfb\xbf\xbf\xbf\xbf'))
|
||||
vs[1].append((False, b'\xfd\xbf\xbf\xbf\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 2.3 Other boundary conditions
|
||||
vs = [b"Other boundary conditions", []]
|
||||
vs[1].append((True, b'\xed\x9f\xbf'))
|
||||
vs[1].append((True, b'\xee\x80\x80'))
|
||||
vs[1].append((True, b'\xef\xbf\xbd'))
|
||||
vs[1].append((True, b'\xf4\x8f\xbf\xbf'))
|
||||
vs[1].append((False, b'\xf4\x90\x80\x80'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 3.1 Unexpected continuation bytes
|
||||
vs = [b"Unexpected continuation bytes", []]
|
||||
vs[1].append((False, b'\x80'))
|
||||
vs[1].append((False, b'\xbf'))
|
||||
vs[1].append((False, b'\x80\xbf'))
|
||||
vs[1].append((False, b'\x80\xbf\x80'))
|
||||
vs[1].append((False, b'\x80\xbf\x80\xbf'))
|
||||
vs[1].append((False, b'\x80\xbf\x80\xbf\x80'))
|
||||
vs[1].append((False, b'\x80\xbf\x80\xbf\x80\xbf'))
|
||||
s = b''
|
||||
|
||||
# 3.2 Lonely start characters
|
||||
vs = [b"Lonely start characters", []]
|
||||
m = [(0xc0, 0xdf), (0xe0, 0xef), (0xf0, 0xf7), (0xf8, 0xfb), (0xfc, 0xfd)]
|
||||
for mm in m:
|
||||
s = b''
|
||||
for i in range(mm[0], mm[1]):
|
||||
s += struct.pack('BB', i, 0x20)
|
||||
# s += chr(i)
|
||||
# s += chr(0x20)
|
||||
vs[1].append((False, s))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 3.3 Sequences with last continuation byte missing
|
||||
vs = [b"Sequences with last continuation byte missing", []]
|
||||
k = [b'\xc0', b'\xe0\x80', b'\xf0\x80\x80', b'\xf8\x80\x80\x80', b'\xfc\x80\x80\x80\x80',
|
||||
b'\xdf', b'\xef\xbf', b'\xf7\xbf\xbf', b'\xfb\xbf\xbf\xbf', b'\xfd\xbf\xbf\xbf\xbf']
|
||||
for kk in k:
|
||||
vs[1].append((False, kk))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 3.4 Concatenation of incomplete sequences
|
||||
vs = [b"Concatenation of incomplete sequences", []]
|
||||
vs[1].append((False, b''.join(k)))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 3.5 Impossible bytes
|
||||
vs = [b"Impossible bytes", []]
|
||||
vs[1].append((False, b'\xfe'))
|
||||
vs[1].append((False, b'\xff'))
|
||||
vs[1].append((False, b'\xfe\xfe\xff\xff'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 4.1 Examples of an overlong ASCII character
|
||||
vs = [b"Examples of an overlong ASCII character", []]
|
||||
vs[1].append((False, b'\xc0\xaf'))
|
||||
vs[1].append((False, b'\xe0\x80\xaf'))
|
||||
vs[1].append((False, b'\xf0\x80\x80\xaf'))
|
||||
vs[1].append((False, b'\xf8\x80\x80\x80\xaf'))
|
||||
vs[1].append((False, b'\xfc\x80\x80\x80\x80\xaf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 4.2 Maximum overlong sequences
|
||||
vs = [b"Maximum overlong sequences", []]
|
||||
vs[1].append((False, b'\xc1\xbf'))
|
||||
vs[1].append((False, b'\xe0\x9f\xbf'))
|
||||
vs[1].append((False, b'\xf0\x8f\xbf\xbf'))
|
||||
vs[1].append((False, b'\xf8\x87\xbf\xbf\xbf'))
|
||||
vs[1].append((False, b'\xfc\x83\xbf\xbf\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 4.3 Overlong representation of the NUL character
|
||||
vs = [b"Overlong representation of the NUL character", []]
|
||||
vs[1].append((False, b'\xc0\x80'))
|
||||
vs[1].append((False, b'\xe0\x80\x80'))
|
||||
vs[1].append((False, b'\xf0\x80\x80\x80'))
|
||||
vs[1].append((False, b'\xf8\x80\x80\x80\x80'))
|
||||
vs[1].append((False, b'\xfc\x80\x80\x80\x80\x80'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 5.1 Single UTF-16 surrogates
|
||||
vs = [b"Single UTF-16 surrogates", []]
|
||||
vs[1].append((False, b'\xed\xa0\x80'))
|
||||
vs[1].append((False, b'\xed\xad\xbf'))
|
||||
vs[1].append((False, b'\xed\xae\x80'))
|
||||
vs[1].append((False, b'\xed\xaf\xbf'))
|
||||
vs[1].append((False, b'\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xbe\x80'))
|
||||
vs[1].append((False, b'\xed\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 5.2 Paired UTF-16 surrogates
|
||||
vs = [b"Paired UTF-16 surrogates", []]
|
||||
vs[1].append((False, b'\xed\xa0\x80\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xa0\x80\xed\xbf\xbf'))
|
||||
vs[1].append((False, b'\xed\xad\xbf\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xad\xbf\xed\xbf\xbf'))
|
||||
vs[1].append((False, b'\xed\xae\x80\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xae\x80\xed\xbf\xbf'))
|
||||
vs[1].append((False, b'\xed\xaf\xbf\xed\xb0\x80'))
|
||||
vs[1].append((False, b'\xed\xaf\xbf\xed\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# 5.3 Other illegal code positions
|
||||
# Those are non-character code points and valid UTF-8 by RFC 3629
|
||||
vs = [b"Non-character code points (valid UTF-8)", []]
|
||||
# https://bug686312.bugzilla.mozilla.org/attachment.cgi?id=561257
|
||||
# non-characters: EF BF [BE-BF]
|
||||
vs[1].append((True, b'\xef\xbf\xbe'))
|
||||
vs[1].append((True, b'\xef\xbf\xbf'))
|
||||
# non-characters: F[0-7] [89AB]F BF [BE-BF]
|
||||
for z1 in [b'\xf0', b'\xf1', b'\xf2', b'\xf3', b'\xf4']:
|
||||
for z2 in [b'\x8f', b'\x9f', b'\xaf', b'\xbf']:
|
||||
# those encode codepoints >U+10FFFF
|
||||
if not (z1 == b'\xf4' and z2 != b'\x8f'):
|
||||
for z3 in [b'\xbe', b'\xbf']:
|
||||
zz = z1 + z2 + b'\xbf' + z3
|
||||
if zz not in [b'\xf0\x8f\xbf\xbe',
|
||||
b'\xf0\x8f\xbf\xbf']: # filter overlong sequences
|
||||
vs[1].append((True, zz))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
# Unicode "specials", such as replacement char etc
|
||||
# http://en.wikipedia.org/wiki/Specials_%28Unicode_block%29
|
||||
vs = [b"Unicode specials (i.e. replacement char)", []]
|
||||
vs[1].append((True, b'\xef\xbf\xb9'))
|
||||
vs[1].append((True, b'\xef\xbf\xba'))
|
||||
vs[1].append((True, b'\xef\xbf\xbb'))
|
||||
vs[1].append((True, b'\xef\xbf\xbc'))
|
||||
vs[1].append((True, b'\xef\xbf\xbd')) # replacement char
|
||||
vs[1].append((True, b'\xef\xbf\xbe'))
|
||||
vs[1].append((True, b'\xef\xbf\xbf'))
|
||||
UTF8_TEST_SEQUENCES.append(vs)
|
||||
|
||||
return UTF8_TEST_SEQUENCES
|
||||
|
||||
|
||||
def _create_valid_utf8_test_sequences():
|
||||
"""
|
||||
Generate some exotic, but valid UTF8 test strings.
|
||||
"""
|
||||
VALID_UTF8_TEST_SEQUENCES = []
|
||||
for test in _create_utf8_test_sequences():
|
||||
valids = [x[1] for x in test[1] if x[0]]
|
||||
if len(valids) > 0:
|
||||
VALID_UTF8_TEST_SEQUENCES.append([test[0], valids])
|
||||
return VALID_UTF8_TEST_SEQUENCES
|
||||
|
||||
|
||||
@unittest.skipIf(not HAS_NVX, 'NVX native extensions not present')
|
||||
class TestNvxUtf8Validator(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# These tests verify the UTF-8 decoder/validator on the various test cases from
|
||||
# http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
|
||||
vs = []
|
||||
for k in _create_utf8_test_sequences():
|
||||
vs.extend(k[1])
|
||||
|
||||
# All Unicode code points
|
||||
for i in range(
|
||||
0, 0xffff): # should by 0x10ffff, but non-wide Python build is limited to 16-bits
|
||||
if i < 0xD800 or i > 0xDFFF: # filter surrogate code points, which are disallowed to encode in UTF-8
|
||||
vs.append((True, chr(i).encode("utf-8")))
|
||||
|
||||
# FIXME: UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800'
|
||||
# in position 0: surrogates not allowed
|
||||
if False:
|
||||
# 5.1 Single UTF-16 surrogates
|
||||
for i in range(0xD800, 0xDBFF): # high-surrogate
|
||||
ss = chr(i).encode("utf-8")
|
||||
vs.append((False, ss))
|
||||
for i in range(0xDC00, 0xDFFF): # low-surrogate
|
||||
ss = chr(i).encode("utf-8")
|
||||
vs.append((False, ss))
|
||||
|
||||
# 5.2 Paired UTF-16 surrogates
|
||||
for i in range(0xD800, 0xDBFF): # high-surrogate
|
||||
for j in range(0xDC00, 0xDFFF): # low-surrogate
|
||||
ss1 = chr(i).encode("utf-8")
|
||||
ss2 = chr(j).encode("utf-8")
|
||||
vs.append((False, ss1 + ss2))
|
||||
vs.append((False, ss2 + ss1))
|
||||
|
||||
self._TEST_SEQUENCES = vs
|
||||
|
||||
def test_standard_utf8validator(self):
|
||||
"""
|
||||
Test standard implementation of UTF8 validator.
|
||||
"""
|
||||
validator = StandardUtf8Validator()
|
||||
return self._test_utf8(validator)
|
||||
|
||||
def test_nvx_utf8validator(self):
|
||||
"""
|
||||
Test NVX implementation of UTF8 validator.
|
||||
"""
|
||||
validator = NvxUtf8Validator()
|
||||
return self._test_utf8(validator)
|
||||
|
||||
def test_standard_utf8validator_incremental(self):
|
||||
"""
|
||||
Test standard implementation of UTF8 validator in incremental mode.
|
||||
"""
|
||||
validator = StandardUtf8Validator()
|
||||
return self._test_utf8_incremental(validator)
|
||||
|
||||
# FIXME
|
||||
# see also (I think ..): https://twistedmatrix.com/trac/ticket/4811
|
||||
#
|
||||
# import pytest
|
||||
#
|
||||
# @pytest.mark.xfail(reason='NVX UTF8 validator lacks incremental mode implementation')
|
||||
# @unittest.expectedFailure
|
||||
# def test_nvx_utf8validator_incremental(self):
|
||||
# """
|
||||
# Test NVX implementation of UTF8 validator in incremental mode.
|
||||
# """
|
||||
# validator = NvxUtf8Validator()
|
||||
# return self._test_utf8_incremental(validator)
|
||||
|
||||
def _test_utf8(self, validator):
|
||||
for s in self._TEST_SEQUENCES:
|
||||
validator.reset()
|
||||
r = validator.validate(s[1])
|
||||
|
||||
# no UTF-8 decode error _and_ everything consumed
|
||||
res = r[0] and r[1]
|
||||
|
||||
self.assertEqual(res, s[0])
|
||||
|
||||
def _test_utf8_incremental(self, validator, withPositions=True):
|
||||
# These tests verify that the UTF-8 decoder/validator can operate incrementally.
|
||||
if withPositions:
|
||||
# testing validator 4 on incremental detection with positions
|
||||
k = 4
|
||||
else:
|
||||
# testing validator 2 on incremental detection without positions
|
||||
k = 2
|
||||
|
||||
validator.reset()
|
||||
self.assertEqual((True, True, 15, 15)[:k], validator.validate('µ@ßöäüàá'.encode('utf8'))[:k])
|
||||
|
||||
validator.reset()
|
||||
self.assertEqual((False, False, 0, 0)[:k], validator.validate(b"\xF5")[:k])
|
||||
|
||||
# the following 3 all fail on eating byte 7 (0xA0)
|
||||
validator.reset()
|
||||
self.assertEqual((True, True, 6, 6)[:k], validator.validate(b"\x65\x64\x69\x74\x65\x64")[:k])
|
||||
self.assertEqual((False, False, 1, 7)[:k], validator.validate(b"\xED\xA0\x80")[:k])
|
||||
|
||||
validator.reset()
|
||||
self.assertEqual((True, True, 4, 4)[:k], validator.validate(b"\x65\x64\x69\x74")[:k])
|
||||
self.assertEqual((False, False, 3, 7)[:k], validator.validate(b"\x65\x64\xED\xA0\x80")[:k])
|
||||
|
||||
validator.reset()
|
||||
self.assertEqual((True, False, 7, 7)[:k], validator.validate(b"\x65\x64\x69\x74\x65\x64\xED")[:k])
|
||||
self.assertEqual((False, False, 0, 7)[:k], validator.validate(b"\xA0\x80")[:k])
|
||||
@@ -0,0 +1,25 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
@@ -0,0 +1,25 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
@@ -0,0 +1,124 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import unittest
|
||||
|
||||
from autobahn.rawsocket.util import create_url, parse_url
|
||||
|
||||
|
||||
class TestCreateRsUrl(unittest.TestCase):
|
||||
|
||||
def test_create_url01(self):
|
||||
self.assertEqual(create_url("localhost"), "rs://localhost:80")
|
||||
|
||||
def test_create_url02(self):
|
||||
self.assertEqual(create_url("localhost", port=8090), "rs://localhost:8090")
|
||||
|
||||
def test_create_url03(self):
|
||||
self.assertEqual(create_url("localhost", isSecure=True), "rss://localhost:443")
|
||||
|
||||
def test_create_url04(self):
|
||||
self.assertEqual(create_url("localhost", isSecure=True, port=443), "rss://localhost:443")
|
||||
|
||||
def test_create_url05(self):
|
||||
self.assertEqual(create_url("localhost", isSecure=True, port=80), "rss://localhost:80")
|
||||
|
||||
def test_create_url06(self):
|
||||
self.assertEqual(create_url("unix", port="file.sock"), "rs://unix:file.sock")
|
||||
|
||||
def test_create_url07(self):
|
||||
self.assertEqual(create_url("unix", port="/tmp/file.sock"), "rs://unix:/tmp/file.sock")
|
||||
|
||||
def test_create_url08(self):
|
||||
self.assertEqual(create_url("unix", port="../file.sock"), "rs://unix:../file.sock")
|
||||
|
||||
def test_create_url09(self):
|
||||
self.assertEqual(create_url("unix", isSecure=True, port="file.sock"), "rss://unix:file.sock")
|
||||
|
||||
def test_create_url10(self):
|
||||
self.assertEqual(create_url("unix", isSecure=True, port="/tmp/file.sock"), "rss://unix:/tmp/file.sock")
|
||||
|
||||
def test_create_url11(self):
|
||||
self.assertEqual(create_url("unix", isSecure=True, port="../file.sock"), "rss://unix:../file.sock")
|
||||
|
||||
|
||||
class TestParseWsUrl(unittest.TestCase):
|
||||
|
||||
# parse_url -> (isSecure, host, port)
|
||||
|
||||
def test_parse_url01(self):
|
||||
self.assertEqual(parse_url("rs://localhost"), (False, 'localhost', 80))
|
||||
|
||||
def test_parse_url02(self):
|
||||
self.assertEqual(parse_url("rss://localhost"), (True, 'localhost', 443))
|
||||
|
||||
def test_parse_url03(self):
|
||||
self.assertEqual(parse_url("rs://localhost:9000"), (False, 'localhost', 9000))
|
||||
|
||||
def test_parse_url04(self):
|
||||
self.assertEqual(parse_url("rss://localhost:9000"), (True, 'localhost', 9000))
|
||||
|
||||
def test_parse_url05(self):
|
||||
self.assertRaises(Exception, parse_url, "ws://localhost")
|
||||
|
||||
def test_parse_url06(self):
|
||||
self.assertRaises(Exception, parse_url, "wss://localhost")
|
||||
|
||||
def test_parse_url07(self):
|
||||
self.assertRaises(Exception, parse_url, "ws://localhost:80")
|
||||
|
||||
def test_parse_url08(self):
|
||||
self.assertRaises(Exception, parse_url, "rs://localhost/somepath")
|
||||
|
||||
def test_parse_url09(self):
|
||||
self.assertRaises(Exception, parse_url, "rs://localhost#somefrag")
|
||||
|
||||
def test_parse_url10(self):
|
||||
self.assertRaises(Exception, parse_url, "rs://localhost?foo=bar")
|
||||
|
||||
def test_parse_url11(self):
|
||||
self.assertRaises(Exception, parse_url, "rss://")
|
||||
|
||||
def test_parse_url12(self):
|
||||
self.assertRaises(Exception, parse_url, "rs://")
|
||||
|
||||
def test_parse_url13(self):
|
||||
self.assertEqual(parse_url("rs://unix:file.sock"), (False, 'unix', 'file.sock'))
|
||||
|
||||
def test_parse_url14(self):
|
||||
self.assertEqual(parse_url("rs://unix:/tmp/file.sock"), (False, 'unix', '/tmp/file.sock'))
|
||||
|
||||
def test_parse_url15(self):
|
||||
self.assertEqual(parse_url("rs://unix:../file.sock"), (False, 'unix', '../file.sock'))
|
||||
|
||||
def test_parse_url16(self):
|
||||
self.assertEqual(parse_url("rss://unix:file.sock"), (True, 'unix', 'file.sock'))
|
||||
|
||||
def test_parse_url17(self):
|
||||
self.assertEqual(parse_url("rss://unix:/tmp/file.sock"), (True, 'unix', '/tmp/file.sock'))
|
||||
|
||||
def test_parse_url18(self):
|
||||
self.assertEqual(parse_url("rss://unix:../file.sock"), (True, 'unix', '../file.sock'))
|
||||
163
.venv/lib/python3.12/site-packages/autobahn/rawsocket/util.py
Normal file
163
.venv/lib/python3.12/site-packages/autobahn/rawsocket/util.py
Normal file
@@ -0,0 +1,163 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 autobahn.util import public
|
||||
|
||||
# The Python urlparse module currently does not contain the rs/rss
|
||||
# schemes, so we add those dynamically (which is a hack of course).
|
||||
#
|
||||
# Important: if you change this stuff (you shouldn't), make sure
|
||||
# _all_ our unit tests for WS URLs succeed
|
||||
#
|
||||
from urllib import parse as urlparse
|
||||
|
||||
wsschemes = ["rs", "rss"]
|
||||
urlparse.uses_relative.extend(wsschemes)
|
||||
urlparse.uses_netloc.extend(wsschemes)
|
||||
urlparse.uses_params.extend(wsschemes)
|
||||
urlparse.uses_query.extend(wsschemes)
|
||||
urlparse.uses_fragment.extend(wsschemes)
|
||||
|
||||
__all__ = (
|
||||
"create_url",
|
||||
"parse_url",
|
||||
)
|
||||
|
||||
|
||||
@public
|
||||
def create_url(hostname, port=None, isSecure=False):
|
||||
"""
|
||||
Create a RawSocket URL from components.
|
||||
|
||||
:param hostname: RawSocket server hostname (for TCP/IP sockets) or
|
||||
filesystem path (for Unix domain sockets).
|
||||
:type hostname: str
|
||||
|
||||
:param port: For TCP/IP sockets, RawSocket service port or ``None`` (to select default
|
||||
ports ``80`` or ``443`` depending on ``isSecure``. When ``hostname=="unix"``,
|
||||
this defines the path to the Unix domain socket instead of a TCP/IP network socket.
|
||||
:type port: int or str
|
||||
|
||||
:param isSecure: Set ``True`` for secure RawSocket (``rss`` scheme).
|
||||
:type isSecure: bool
|
||||
|
||||
:returns: Constructed RawSocket URL.
|
||||
:rtype: str
|
||||
"""
|
||||
# assert type(hostname) == str
|
||||
assert type(isSecure) == bool
|
||||
|
||||
if hostname == 'unix':
|
||||
|
||||
netloc = "unix:%s" % port
|
||||
else:
|
||||
assert port is None or (type(port) == int and port in range(0, 65535))
|
||||
|
||||
if port is not None:
|
||||
netloc = "%s:%d" % (hostname, port)
|
||||
else:
|
||||
if isSecure:
|
||||
netloc = "{}:443".format(hostname)
|
||||
else:
|
||||
netloc = "{}:80".format(hostname)
|
||||
|
||||
if isSecure:
|
||||
scheme = "rss"
|
||||
else:
|
||||
scheme = "rs"
|
||||
|
||||
return "{}://{}".format(scheme, netloc)
|
||||
|
||||
|
||||
@public
|
||||
def parse_url(url):
|
||||
"""
|
||||
Parses as RawSocket URL into it's components and returns a tuple:
|
||||
|
||||
- ``isSecure`` is a flag which is ``True`` for ``rss`` URLs.
|
||||
- ``host`` is the hostname or IP from the URL.
|
||||
|
||||
and for TCP/IP sockets:
|
||||
|
||||
- ``tcp_port`` is the port from the URL or standard port derived from
|
||||
scheme (``rs`` => ``80``, ``rss`` => ``443``).
|
||||
|
||||
or for Unix domain sockets:
|
||||
|
||||
- ``uds_path`` is the path on the local host filesystem.
|
||||
|
||||
:param url: A valid RawSocket URL, i.e. ``rs://localhost:9000`` for TCP/IP sockets or
|
||||
``rs://unix:/tmp/file.sock`` for Unix domain sockets (UDS).
|
||||
:type url: str
|
||||
|
||||
:returns: A 3-tuple ``(isSecure, host, tcp_port)`` (TCP/IP) or ``(isSecure, host, uds_path)`` (UDS).
|
||||
:rtype: tuple
|
||||
"""
|
||||
parsed = urlparse.urlparse(url)
|
||||
|
||||
if parsed.scheme not in ["rs", "rss"]:
|
||||
raise Exception("invalid RawSocket URL: protocol scheme '{}' is not for RawSocket".format(parsed.scheme))
|
||||
|
||||
if not parsed.hostname or parsed.hostname == "":
|
||||
raise Exception("invalid RawSocket URL: missing hostname")
|
||||
|
||||
if parsed.query is not None and parsed.query != "":
|
||||
raise Exception("invalid RawSocket URL: non-empty query '{}'".format(parsed.query))
|
||||
|
||||
if parsed.fragment is not None and parsed.fragment != "":
|
||||
raise Exception("invalid RawSocket URL: non-empty fragment '{}'".format(parsed.fragment))
|
||||
|
||||
if parsed.hostname == "unix":
|
||||
# Unix domain sockets sockets
|
||||
|
||||
# rs://unix:/tmp/file.sock => unix:/tmp/file.sock => /tmp/file.sock
|
||||
fp = parsed.netloc + parsed.path
|
||||
uds_path = fp.split(':')[1]
|
||||
|
||||
# note: we don't interpret "uds_path" in any further way: it needs to be
|
||||
# a path on the local host with a listening Unix domain sockets at the other end ..
|
||||
|
||||
return parsed.scheme == "rss", parsed.hostname, uds_path
|
||||
|
||||
else:
|
||||
# TCP/IP sockets
|
||||
|
||||
if parsed.path is not None and parsed.path != "":
|
||||
raise Exception("invalid RawSocket URL: non-empty path '{}'".format(parsed.path))
|
||||
|
||||
if parsed.port is None or parsed.port == "":
|
||||
if parsed.scheme == "rs":
|
||||
tcp_port = 80
|
||||
else:
|
||||
tcp_port = 443
|
||||
else:
|
||||
tcp_port = int(parsed.port)
|
||||
|
||||
if tcp_port < 1 or tcp_port > 65535:
|
||||
raise Exception("invalid port {}".format(tcp_port))
|
||||
|
||||
return parsed.scheme == "rss", parsed.hostname, tcp_port
|
||||
25
.venv/lib/python3.12/site-packages/autobahn/test/__init__.py
Normal file
25
.venv/lib/python3.12/site-packages/autobahn/test/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
111
.venv/lib/python3.12/site-packages/autobahn/test/test_rng.py
Normal file
111
.venv/lib/python3.12/site-packages/autobahn/test/test_rng.py
Normal file
@@ -0,0 +1,111 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import uuid
|
||||
import random
|
||||
from nacl import utils, public
|
||||
|
||||
from autobahn import util
|
||||
|
||||
|
||||
@unittest.skipIf(not ('AUTOBAHN_CI_ENABLE_RNG_DEPLETION_TESTS' in os***REMOVED***iron and os***REMOVED***iron['AUTOBAHN_CI_ENABLE_RNG_DEPLETION_TESTS']), 'entropy depletion tests not enabled (env var AUTOBAHN_CI_ENABLE_RNG_DEPLETION_TESTS not set)')
|
||||
@unittest.skipIf(not sys.platform.startswith('linux'), 'entropy depletion tests only available on Linux')
|
||||
class TestEntropy(unittest.TestCase):
|
||||
|
||||
def test_non_depleting(self):
|
||||
res = {}
|
||||
|
||||
with open('/dev/urandom', 'rb') as rng:
|
||||
for i in range(1000):
|
||||
for j in range(100):
|
||||
|
||||
# "reseed" (seems pointless, but ..)
|
||||
random.seed()
|
||||
|
||||
# random UUIDs
|
||||
v1 = uuid.uuid4() # noqa
|
||||
|
||||
# stdlib random
|
||||
v2 = random.random() # noqa
|
||||
v3 = random.getrandbits(32) # noqa
|
||||
v4 = random.randint(0, 9007199254740992) # noqa
|
||||
v5 = random.normalvariate(10, 100) # noqa
|
||||
v6 = random.choice(range(100)) # noqa
|
||||
|
||||
# PyNaCl
|
||||
v7 = utils.random(public.Box.NONCE_SIZE) # noqa
|
||||
|
||||
# Autobahn utils
|
||||
v8 = util.generate_token(4, 4) # noqa
|
||||
v9 = util.id() # noqa
|
||||
v10 = util.rid() # noqa
|
||||
v11 = util.newid() # noqa
|
||||
|
||||
# direct procfs access to PRNG
|
||||
d = rng.read(1000) # noqa
|
||||
|
||||
# check available entropy
|
||||
with open('/proc/sys/kernel/random/entropy_avail', 'r') as ent:
|
||||
ea = int(ent.read()) // 100
|
||||
if ea not in res:
|
||||
res[ea] = 0
|
||||
res[ea] += 1
|
||||
|
||||
skeys = sorted(res.keys())
|
||||
|
||||
print('\nsystem entropy depletion stats:')
|
||||
for k in skeys:
|
||||
print('{}: {}'.format(k, res[k]))
|
||||
|
||||
self.assertTrue(skeys[0] > 0)
|
||||
|
||||
def test_depleting(self):
|
||||
res = {}
|
||||
|
||||
with open('/dev/random', 'rb') as rng:
|
||||
for i in range(10000):
|
||||
|
||||
# direct procfs access to "real" RNG
|
||||
d = rng.read(1000) # noqa
|
||||
|
||||
# check available entropy
|
||||
with open('/proc/sys/kernel/random/entropy_avail', 'r') as ent:
|
||||
ea = int(ent.read()) // 100
|
||||
if ea not in res:
|
||||
res[ea] = 0
|
||||
res[ea] += 1
|
||||
|
||||
skeys = sorted(res.keys())
|
||||
|
||||
print('\nsystem entropy depletion stats:')
|
||||
for k in skeys:
|
||||
print('{}: {}'.format(k, res[k]))
|
||||
|
||||
self.assertTrue(skeys[0] == 0)
|
||||
@@ -0,0 +1,67 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from binascii import b2a_hex
|
||||
|
||||
from autobahn.util import IdGenerator, parse_activation_code, generate_activation_code, generate_token
|
||||
|
||||
|
||||
class TestIdGenerator(unittest.TestCase):
|
||||
def test_idgenerator_is_generator(self):
|
||||
"IdGenerator follows the generator protocol"
|
||||
g = IdGenerator()
|
||||
self.assertEqual(1, next(g))
|
||||
self.assertEqual(2, next(g))
|
||||
|
||||
def test_generator_wrap(self):
|
||||
g = IdGenerator()
|
||||
g._next = 2 ** 53 - 1 # cheat a little
|
||||
|
||||
v = next(g)
|
||||
self.assertEqual(v, 2 ** 53)
|
||||
v = next(g)
|
||||
self.assertEqual(v, 1)
|
||||
|
||||
def test_parse_valid_activation_codes(self):
|
||||
for i in range(20):
|
||||
code = generate_activation_code()
|
||||
parsed_code = parse_activation_code(code)
|
||||
self.assertTupleEqual(tuple(code.split('-')), parsed_code.groups())
|
||||
|
||||
def test_parse_invalid_activation_codes(self):
|
||||
for i in range(20):
|
||||
code = b2a_hex(os.urandom(20)).decode()
|
||||
parsed_code = parse_activation_code(code)
|
||||
self.assertEqual(None, parsed_code)
|
||||
|
||||
def test_generate_token(self):
|
||||
token = generate_token(5, 4)
|
||||
self.assertEqual(len(token), len('NUAG-UPQJ-MFGA-K5P5-MUGA'))
|
||||
self.assertEqual(len(token.split('-')), 5)
|
||||
for part in token.split('-'):
|
||||
self.assertEqual(len(part), 4)
|
||||
67
.venv/lib/python3.12/site-packages/autobahn/testutil.py
Normal file
67
.venv/lib/python3.12/site-packages/autobahn/testutil.py
Normal file
@@ -0,0 +1,67 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
class FakeTransport(object):
|
||||
_written = b""
|
||||
_open = True
|
||||
|
||||
def __init__(self):
|
||||
self._abort_calls = []
|
||||
|
||||
def abortConnection(self, *args, **kw):
|
||||
self._abort_calls.append((args, kw))
|
||||
|
||||
def write(self, msg):
|
||||
if not self._open:
|
||||
raise Exception("Can't write to a closed connection")
|
||||
self._written = self._written + msg
|
||||
|
||||
def loseConnection(self):
|
||||
self._open = False
|
||||
|
||||
def registerProducer(self, producer, streaming):
|
||||
# https://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IConsumer.html
|
||||
raise NotImplementedError
|
||||
|
||||
def unregisterProducer(self):
|
||||
# do nothing is correct! until we fake implement registerProducer ..;)
|
||||
pass
|
||||
|
||||
def getPeer(self):
|
||||
# for Twisted, this would be an IAddress
|
||||
class _FakePeer(object):
|
||||
pass
|
||||
return _FakePeer()
|
||||
|
||||
def getHost(self):
|
||||
# for Twisted, this would be an IAddress
|
||||
class _FakeHost(object):
|
||||
pass
|
||||
return _FakeHost()
|
||||
|
||||
def abort_called(self):
|
||||
return len(self._abort_calls) > 0
|
||||
@@ -0,0 +1,85 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import platform
|
||||
|
||||
import twisted
|
||||
|
||||
import autobahn
|
||||
|
||||
# Twisted specific utilities (these should really be in Twisted, but
|
||||
# they aren't, and we use these in example code, so it must be part of
|
||||
# the public API)
|
||||
from autobahn.twisted.util import sleep
|
||||
from autobahn.twisted.choosereactor import install_reactor
|
||||
|
||||
# WebSocket protocol support
|
||||
from autobahn.twisted.websocket import \
|
||||
WebSocketServerProtocol, \
|
||||
WebSocketClientProtocol, \
|
||||
WebSocketServerFactory, \
|
||||
WebSocketClientFactory
|
||||
|
||||
# support for running Twisted stream protocols over WebSocket
|
||||
from autobahn.twisted.websocket import WrappingWebSocketServerFactory, \
|
||||
WrappingWebSocketClientFactory
|
||||
|
||||
# Twisted Web support - FIXME: these imports trigger import of Twisted reactor!
|
||||
# from autobahn.twisted.resource import WebSocketResource, WSGIRootResource
|
||||
|
||||
# WAMP support
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
|
||||
|
||||
__all__ = (
|
||||
# this should really be in Twisted
|
||||
'sleep',
|
||||
'install_reactor',
|
||||
|
||||
# WebSocket
|
||||
'WebSocketServerProtocol',
|
||||
'WebSocketClientProtocol',
|
||||
'WebSocketServerFactory',
|
||||
'WebSocketClientFactory',
|
||||
|
||||
# wrapping stream protocols in WebSocket
|
||||
'WrappingWebSocketServerFactory',
|
||||
'WrappingWebSocketClientFactory',
|
||||
|
||||
# Twisted Web - FIXME: see comment for import above
|
||||
# 'WebSocketResource',
|
||||
|
||||
# this should really be in Twisted - FIXME: see comment for import above
|
||||
# 'WSGIRootResource',
|
||||
|
||||
# WAMP support
|
||||
'ApplicationSession',
|
||||
)
|
||||
|
||||
__ident__ = 'Autobahn/{}-Twisted/{}-{}/{}'.format(autobahn.__version__, twisted.__version__, platform.python_implementation(), platform.python_version())
|
||||
"""
|
||||
AutobahnPython library implementation (eg. "Autobahn/0.13.0-Twisted/15.5.0-CPython/3.5.1")
|
||||
"""
|
||||
@@ -0,0 +1,226 @@
|
||||
########################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
########################################
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import txaio
|
||||
txaio.use_twisted()
|
||||
|
||||
from twisted.python import reflect
|
||||
from twisted.internet.error import ReactorAlreadyInstalledError
|
||||
|
||||
__all__ = (
|
||||
'install_optimal_reactor',
|
||||
'install_reactor',
|
||||
'current_reactor_klass'
|
||||
)
|
||||
|
||||
|
||||
def current_reactor_klass():
|
||||
"""
|
||||
Return class name of currently installed Twisted reactor or None.
|
||||
"""
|
||||
if 'twisted.internet.reactor' in sys.modules:
|
||||
current_reactor = reflect.qual(sys.modules['twisted.internet.reactor'].__class__).split('.')[-1]
|
||||
else:
|
||||
current_reactor = None
|
||||
return current_reactor
|
||||
|
||||
|
||||
def install_optimal_reactor(require_optimal_reactor=True):
|
||||
"""
|
||||
Try to install the optimal Twisted reactor for this platform:
|
||||
|
||||
- Linux: epoll
|
||||
- BSD/OSX: kqueue
|
||||
- Windows: iocp
|
||||
- Other: select
|
||||
|
||||
Notes:
|
||||
|
||||
- This function exists, because the reactor types selected based on platform
|
||||
in `twisted.internet.default` are different from here.
|
||||
- The imports are inlined, because the Twisted code base is notorious for
|
||||
importing the reactor as a side-effect of merely importing. Hence we postpone
|
||||
all importing.
|
||||
|
||||
See: http://twistedmatrix.[AWS-SECRET-REMOVED]g-reactor.html#reactor-functionality
|
||||
|
||||
:param require_optimal_reactor: If ``True`` and the desired reactor could not be installed,
|
||||
raise ``ReactorAlreadyInstalledError``, else fallback to another reactor.
|
||||
:type require_optimal_reactor: bool
|
||||
|
||||
:returns: The Twisted reactor in place (`twisted.internet.reactor`).
|
||||
"""
|
||||
log = txaio.make_logger()
|
||||
|
||||
# determine currently installed reactor, if any
|
||||
#
|
||||
current_reactor = current_reactor_klass()
|
||||
|
||||
# depending on platform, install optimal reactor
|
||||
#
|
||||
if 'bsd' in sys.platform or sys.platform.startswith('darwin'):
|
||||
|
||||
# *BSD and MacOSX
|
||||
#
|
||||
if current_reactor != 'KQueueReactor':
|
||||
if current_reactor is None:
|
||||
try:
|
||||
from twisted.internet import kqreactor
|
||||
kqreactor.install()
|
||||
except:
|
||||
log.warn('Running on *BSD or MacOSX, but cannot install kqueue Twisted reactor: {tb}', tb=traceback.format_exc())
|
||||
else:
|
||||
log.debug('Running on *BSD or MacOSX and optimal reactor (kqueue) was installed.')
|
||||
else:
|
||||
log.warn('Running on *BSD or MacOSX, but cannot install kqueue Twisted reactor, because another reactor ({klass}) is already installed.', klass=current_reactor)
|
||||
if require_optimal_reactor:
|
||||
raise ReactorAlreadyInstalledError()
|
||||
else:
|
||||
log.debug('Running on *BSD or MacOSX and optimal reactor (kqueue) already installed.')
|
||||
|
||||
elif sys.platform in ['win32']:
|
||||
|
||||
# Windows
|
||||
#
|
||||
if current_reactor != 'IOCPReactor':
|
||||
if current_reactor is None:
|
||||
try:
|
||||
from twisted.internet.iocpreactor import reactor as iocpreactor
|
||||
iocpreactor.install()
|
||||
except:
|
||||
log.warn('Running on Windows, but cannot install IOCP Twisted reactor: {tb}', tb=traceback.format_exc())
|
||||
else:
|
||||
log.debug('Running on Windows and optimal reactor (ICOP) was installed.')
|
||||
else:
|
||||
log.warn('Running on Windows, but cannot install IOCP Twisted reactor, because another reactor ({klass}) is already installed.', klass=current_reactor)
|
||||
if require_optimal_reactor:
|
||||
raise ReactorAlreadyInstalledError()
|
||||
else:
|
||||
log.debug('Running on Windows and optimal reactor (ICOP) already installed.')
|
||||
|
||||
elif sys.platform.startswith('linux'):
|
||||
|
||||
# Linux
|
||||
#
|
||||
if current_reactor != 'EPollReactor':
|
||||
if current_reactor is None:
|
||||
try:
|
||||
from twisted.internet import epollreactor
|
||||
epollreactor.install()
|
||||
except:
|
||||
log.warn('Running on Linux, but cannot install Epoll Twisted reactor: {tb}', tb=traceback.format_exc())
|
||||
else:
|
||||
log.debug('Running on Linux and optimal reactor (epoll) was installed.')
|
||||
else:
|
||||
log.warn('Running on Linux, but cannot install Epoll Twisted reactor, because another reactor ({klass}) is already installed.', klass=current_reactor)
|
||||
if require_optimal_reactor:
|
||||
raise ReactorAlreadyInstalledError()
|
||||
else:
|
||||
log.debug('Running on Linux and optimal reactor (epoll) already installed.')
|
||||
|
||||
else:
|
||||
|
||||
# Other platform
|
||||
#
|
||||
if current_reactor != 'SelectReactor':
|
||||
if current_reactor is None:
|
||||
try:
|
||||
from twisted.internet import selectreactor
|
||||
selectreactor.install()
|
||||
# from twisted.internet import default as defaultreactor
|
||||
# defaultreactor.install()
|
||||
except:
|
||||
log.warn('Running on "{platform}", but cannot install Select Twisted reactor: {tb}', tb=traceback.format_exc(), platform=sys.platform)
|
||||
else:
|
||||
log.debug('Running on "{platform}" and optimal reactor (Select) was installed.', platform=sys.platform)
|
||||
else:
|
||||
log.warn('Running on "{platform}", but cannot install Select Twisted reactor, because another reactor ({klass}) is already installed.', klass=current_reactor, platform=sys.platform)
|
||||
if require_optimal_reactor:
|
||||
raise ReactorAlreadyInstalledError()
|
||||
else:
|
||||
log.debug('Running on "{platform}" and optimal reactor (Select) already installed.', platform=sys.platform)
|
||||
|
||||
from twisted.internet import reactor
|
||||
txaio.config.loop = reactor
|
||||
|
||||
return reactor
|
||||
|
||||
|
||||
def install_reactor(explicit_reactor=None, verbose=False, log=None, require_optimal_reactor=True):
|
||||
"""
|
||||
Install Twisted reactor.
|
||||
|
||||
:param explicit_reactor: If provided, install this reactor. Else, install
|
||||
the optimal reactor.
|
||||
:type explicit_reactor: obj
|
||||
|
||||
:param verbose: If ``True``, log (at level "info") the reactor that is
|
||||
in place afterwards.
|
||||
:type verbose: bool
|
||||
|
||||
:param log: Explicit logging to this txaio logger object.
|
||||
:type log: obj
|
||||
|
||||
:param require_optimal_reactor: If ``True`` and the desired reactor could not be installed,
|
||||
raise ``ReactorAlreadyInstalledError``, else fallback to another reactor.
|
||||
:type require_optimal_reactor: bool
|
||||
|
||||
:returns: The Twisted reactor in place (`twisted.internet.reactor`).
|
||||
"""
|
||||
if not log:
|
||||
log = txaio.make_logger()
|
||||
|
||||
if explicit_reactor:
|
||||
# install explicitly given reactor
|
||||
#
|
||||
from twisted.application.reactors import installReactor
|
||||
if verbose:
|
||||
log.info('Trying to install explicitly specified Twisted reactor "{reactor}" ..', reactor=explicit_reactor)
|
||||
try:
|
||||
installReactor(explicit_reactor)
|
||||
except:
|
||||
log.failure('Could not install Twisted reactor {reactor}\n{log_failure.value}',
|
||||
reactor=explicit_reactor)
|
||||
sys.exit(1)
|
||||
else:
|
||||
# automatically choose optimal reactor
|
||||
#
|
||||
if verbose:
|
||||
log.info('Automatically choosing optimal Twisted reactor ..')
|
||||
install_optimal_reactor(require_optimal_reactor)
|
||||
|
||||
# now the reactor is installed, import it
|
||||
from twisted.internet import reactor
|
||||
txaio.config.loop = reactor
|
||||
|
||||
if verbose:
|
||||
from twisted.python.reflect import qual
|
||||
log.info('Running on Twisted reactor {reactor}', reactor=qual(reactor.__class__))
|
||||
|
||||
return reactor
|
||||
380
.venv/lib/python3.12/site-packages/autobahn/twisted/component.py
Normal file
380
.venv/lib/python3.12/site-packages/autobahn/twisted/component.py
Normal file
@@ -0,0 +1,380 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 functools import wraps
|
||||
from typing import List
|
||||
|
||||
from twisted.internet.interfaces import IStreamClientEndpoint
|
||||
from twisted.internet.endpoints import UNIXClientEndpoint
|
||||
from twisted.internet.endpoints import TCP4ClientEndpoint
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.internet.error import ReactorNotRunning
|
||||
|
||||
try:
|
||||
_TLS = True
|
||||
from twisted.internet.endpoints import SSL4ClientEndpoint
|
||||
from twisted.internet.ssl import optionsForClientTLS, CertificateOptions, Certificate
|
||||
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
|
||||
from OpenSSL import SSL
|
||||
except ImportError:
|
||||
_TLS = False
|
||||
# there's no optionsForClientTLS in older Twisteds or we might be
|
||||
# missing OpenSSL entirely.
|
||||
|
||||
import txaio
|
||||
|
||||
from autobahn.twisted.websocket import WampWebSocketClientFactory
|
||||
from autobahn.twisted.rawsocket import WampRawSocketClientFactory
|
||||
|
||||
from autobahn.wamp import component
|
||||
|
||||
from autobahn.twisted.wamp import Session
|
||||
from autobahn.wamp.serializer import create_transport_serializers, create_transport_serializer
|
||||
|
||||
__all__ = ('Component', 'run')
|
||||
|
||||
|
||||
def _unique_list(seq):
|
||||
"""
|
||||
Return a list with unique elements from sequence, preserving order.
|
||||
"""
|
||||
seen = set()
|
||||
return [x for x in seq if x not in seen and not seen.add(x)]
|
||||
|
||||
|
||||
def _camel_case_from_snake_case(snake):
|
||||
parts = snake.split('_')
|
||||
return parts[0] + ''.join(s.capitalize() for s in parts[1:])
|
||||
|
||||
|
||||
def _create_transport_factory(reactor, transport, session_factory):
|
||||
"""
|
||||
Create a WAMP-over-XXX transport factory.
|
||||
"""
|
||||
if transport.type == 'websocket':
|
||||
serializers = create_transport_serializers(transport)
|
||||
factory = WampWebSocketClientFactory(
|
||||
session_factory,
|
||||
url=transport.url,
|
||||
serializers=serializers,
|
||||
proxy=transport.proxy, # either None or a dict with host, port
|
||||
)
|
||||
|
||||
elif transport.type == 'rawsocket':
|
||||
serializer = create_transport_serializer(transport.serializers[0])
|
||||
factory = WampRawSocketClientFactory(session_factory, serializer=serializer)
|
||||
|
||||
else:
|
||||
assert(False), 'should not arrive here'
|
||||
|
||||
# set the options one at a time so we can give user better feedback
|
||||
for k, v in transport.options.items():
|
||||
try:
|
||||
factory.setProtocolOptions(**{k: v})
|
||||
except (TypeError, KeyError):
|
||||
# this allows us to document options as snake_case
|
||||
# until everything internally is upgraded from
|
||||
# camelCase
|
||||
try:
|
||||
factory.setProtocolOptions(
|
||||
**{_camel_case_from_snake_case(k): v}
|
||||
)
|
||||
except (TypeError, KeyError):
|
||||
raise ValueError(
|
||||
"Unknown {} transport option: {}={}".format(transport.type, k, v)
|
||||
)
|
||||
return factory
|
||||
|
||||
|
||||
def _create_transport_endpoint(reactor, endpoint_config):
|
||||
"""
|
||||
Create a Twisted client endpoint for a WAMP-over-XXX transport.
|
||||
"""
|
||||
if IStreamClientEndpoint.providedBy(endpoint_config):
|
||||
endpoint = IStreamClientEndpoint(endpoint_config)
|
||||
else:
|
||||
# create a connecting TCP socket
|
||||
if endpoint_config['type'] == 'tcp':
|
||||
|
||||
version = endpoint_config.get('version', 4)
|
||||
if version not in [4, 6]:
|
||||
raise ValueError('invalid IP version {} in client endpoint configuration'.format(version))
|
||||
|
||||
host = endpoint_config['host']
|
||||
if type(host) != str:
|
||||
raise ValueError('invalid type {} for host in client endpoint configuration'.format(type(host)))
|
||||
|
||||
port = endpoint_config['port']
|
||||
if type(port) != int:
|
||||
raise ValueError('invalid type {} for port in client endpoint configuration'.format(type(port)))
|
||||
|
||||
timeout = endpoint_config.get('timeout', 10) # in seconds
|
||||
if type(timeout) != int:
|
||||
raise ValueError('invalid type {} for timeout in client endpoint configuration'.format(type(timeout)))
|
||||
|
||||
tls = endpoint_config.get('tls', None)
|
||||
|
||||
# create a TLS enabled connecting TCP socket
|
||||
if tls:
|
||||
if not _TLS:
|
||||
raise RuntimeError('TLS configured in transport, but TLS support is not installed (eg OpenSSL?)')
|
||||
|
||||
# FIXME: create TLS context from configuration
|
||||
if IOpenSSLClientConnectionCreator.providedBy(tls):
|
||||
# eg created from twisted.internet.ssl.optionsForClientTLS()
|
||||
context = IOpenSSLClientConnectionCreator(tls)
|
||||
|
||||
elif isinstance(tls, dict):
|
||||
for k in tls.keys():
|
||||
if k not in ["hostname", "trust_root"]:
|
||||
raise ValueError("Invalid key '{}' in 'tls' config".format(k))
|
||||
hostname = tls.get('hostname', host)
|
||||
if type(hostname) != str:
|
||||
raise ValueError('invalid type {} for hostname in TLS client endpoint configuration'.format(hostname))
|
||||
trust_root = None
|
||||
cert_fname = tls.get("trust_root", None)
|
||||
if cert_fname is not None:
|
||||
trust_root = Certificate.loadPEM(open(cert_fname, 'r').read())
|
||||
context = optionsForClientTLS(hostname, trustRoot=trust_root)
|
||||
|
||||
elif isinstance(tls, CertificateOptions):
|
||||
context = tls
|
||||
|
||||
elif tls is True:
|
||||
context = optionsForClientTLS(host)
|
||||
|
||||
else:
|
||||
raise RuntimeError('unknown type {} for "tls" configuration in transport'.format(type(tls)))
|
||||
|
||||
if version == 4:
|
||||
endpoint = SSL4ClientEndpoint(reactor, host, port, context, timeout=timeout)
|
||||
elif version == 6:
|
||||
# there is no SSL6ClientEndpoint!
|
||||
raise RuntimeError('TLS on IPv6 not implemented')
|
||||
else:
|
||||
assert(False), 'should not arrive here'
|
||||
|
||||
# create a non-TLS connecting TCP socket
|
||||
else:
|
||||
if host.endswith(".onion"):
|
||||
# hmm, can't log here?
|
||||
# self.log.info("{host} appears to be a Tor endpoint", host=host)
|
||||
try:
|
||||
import txtorcon
|
||||
endpoint = txtorcon.TorClientEndpoint(host, port)
|
||||
except ImportError:
|
||||
raise RuntimeError(
|
||||
"{} appears to be a Tor Onion service, but txtorcon is not installed".format(
|
||||
host,
|
||||
)
|
||||
)
|
||||
elif version == 4:
|
||||
endpoint = TCP4ClientEndpoint(reactor, host, port, timeout=timeout)
|
||||
elif version == 6:
|
||||
try:
|
||||
from twisted.internet.endpoints import TCP6ClientEndpoint
|
||||
except ImportError:
|
||||
raise RuntimeError('IPv6 is not supported (please upgrade Twisted)')
|
||||
endpoint = TCP6ClientEndpoint(reactor, host, port, timeout=timeout)
|
||||
else:
|
||||
assert(False), 'should not arrive here'
|
||||
|
||||
# create a connecting Unix domain socket
|
||||
elif endpoint_config['type'] == 'unix':
|
||||
path = endpoint_config['path']
|
||||
timeout = int(endpoint_config.get('timeout', 10)) # in seconds
|
||||
endpoint = UNIXClientEndpoint(reactor, path, timeout=timeout)
|
||||
|
||||
else:
|
||||
assert(False), 'should not arrive here'
|
||||
|
||||
return endpoint
|
||||
|
||||
|
||||
class Component(component.Component):
|
||||
"""
|
||||
A component establishes a transport and attached a session
|
||||
to a realm using the transport for communication.
|
||||
|
||||
The transports a component tries to use can be configured,
|
||||
as well as the auto-reconnect strategy.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
session_factory = Session
|
||||
"""
|
||||
The factory of the session we will instantiate.
|
||||
"""
|
||||
|
||||
def _is_ssl_error(self, e):
|
||||
"""
|
||||
Internal helper.
|
||||
|
||||
This is so we can just return False if we didn't import any
|
||||
TLS/SSL libraries. Otherwise, returns True if this is an
|
||||
OpenSSL.SSL.Error
|
||||
"""
|
||||
if _TLS:
|
||||
return isinstance(e, SSL.Error)
|
||||
return False
|
||||
|
||||
def _check_native_endpoint(self, endpoint):
|
||||
if IStreamClientEndpoint.providedBy(endpoint):
|
||||
pass
|
||||
elif isinstance(endpoint, dict):
|
||||
if 'tls' in endpoint:
|
||||
tls = endpoint['tls']
|
||||
if isinstance(tls, (dict, bool)):
|
||||
pass
|
||||
elif IOpenSSLClientConnectionCreator.providedBy(tls):
|
||||
pass
|
||||
elif isinstance(tls, CertificateOptions):
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
"'tls' configuration must be a dict, CertificateOptions or"
|
||||
" IOpenSSLClientConnectionCreator provider"
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
"'endpoint' configuration must be a dict or IStreamClientEndpoint"
|
||||
" provider"
|
||||
)
|
||||
|
||||
def _connect_transport(self, reactor, transport, session_factory, done):
|
||||
"""
|
||||
Create and connect a WAMP-over-XXX transport.
|
||||
|
||||
:param done: is a Deferred/Future from the parent which we
|
||||
should signal upon error if it is not done yet (XXX maybe an
|
||||
"on_error" callable instead?)
|
||||
"""
|
||||
transport_factory = _create_transport_factory(reactor, transport, session_factory)
|
||||
if transport.proxy:
|
||||
transport_endpoint = _create_transport_endpoint(
|
||||
reactor,
|
||||
{
|
||||
"type": "tcp",
|
||||
"host": transport.proxy["host"],
|
||||
"port": transport.proxy["port"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
transport_endpoint = _create_transport_endpoint(reactor, transport.endpoint)
|
||||
d = transport_endpoint.connect(transport_factory)
|
||||
|
||||
def on_connect_success(proto):
|
||||
|
||||
# if e.g. an SSL handshake fails, we will have
|
||||
# successfully connected (i.e. get here) but need to
|
||||
# 'listen' for the "connectionLost" from the underlying
|
||||
# protocol in case of handshake failure .. so we wrap
|
||||
# it. Also, we don't increment transport.success_count
|
||||
# here on purpose (because we might not succeed).
|
||||
orig = proto.connectionLost
|
||||
|
||||
@wraps(orig)
|
||||
def lost(fail):
|
||||
rtn = orig(fail)
|
||||
if not txaio.is_called(done):
|
||||
txaio.reject(done, fail)
|
||||
return rtn
|
||||
proto.connectionLost = lost
|
||||
|
||||
def on_connect_failure(err):
|
||||
transport.connect_failures += 1
|
||||
# failed to establish a connection in the first place
|
||||
txaio.reject(done, err)
|
||||
|
||||
txaio.add_callbacks(d, on_connect_success, None)
|
||||
txaio.add_callbacks(d, None, on_connect_failure)
|
||||
return d
|
||||
|
||||
def start(self, reactor=None):
|
||||
"""
|
||||
This starts the Component, which means it will start connecting
|
||||
(and re-connecting) to its configured transports. A Component
|
||||
runs until it is "done", which means one of:
|
||||
- There was a "main" function defined, and it completed successfully;
|
||||
- Something called ``.leave()`` on our session, and we left successfully;
|
||||
- ``.stop()`` was called, and completed successfully;
|
||||
- none of our transports were able to connect successfully (failure);
|
||||
|
||||
:returns: a Deferred that fires (with ``None``) when we are
|
||||
"done" or with a Failure if something went wrong.
|
||||
"""
|
||||
if reactor is None:
|
||||
self.log.warn("Using default reactor")
|
||||
from twisted.internet import reactor
|
||||
|
||||
return self._start(loop=reactor)
|
||||
|
||||
|
||||
def run(components: List[Component], log_level: str = 'info', stop_at_close: bool = True):
|
||||
"""
|
||||
High-level API to run a series of components.
|
||||
|
||||
This will only return once all the components have stopped
|
||||
(including, possibly, after all re-connections have failed if you
|
||||
have re-connections enabled). Under the hood, this calls
|
||||
:meth:`twisted.internet.reactor.run` -- if you wish to manage the
|
||||
reactor loop yourself, use the
|
||||
:meth:`autobahn.twisted.component.Component.start` method to start
|
||||
each component yourself.
|
||||
|
||||
:param components: the Component(s) you wish to run
|
||||
:param log_level: a valid log-level (or None to avoid calling start_logging)
|
||||
:param stop_at_close: Flag to control whether to stop the reactor when done.
|
||||
"""
|
||||
# only for Twisted > 12
|
||||
# ...so this isn't in all Twisted versions we test against -- need
|
||||
# to do "something else" if we can't import .. :/ (or drop some
|
||||
# support)
|
||||
from twisted.internet.task import react
|
||||
|
||||
# actually, should we even let people "not start" the logging? I'm
|
||||
# not sure that's wise... (double-check: if they already called
|
||||
# txaio.start_logging() what happens if we call it again?)
|
||||
if log_level is not None:
|
||||
txaio.start_logging(level=log_level)
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
if stop_at_close:
|
||||
def done_callback(reactor, arg):
|
||||
if isinstance(arg, Failure):
|
||||
log.error('Something went wrong: {log_failure}', failure=arg)
|
||||
try:
|
||||
log.warn('Stopping reactor ..')
|
||||
reactor.stop()
|
||||
except ReactorNotRunning:
|
||||
pass
|
||||
else:
|
||||
done_callback = None
|
||||
|
||||
react(component._run, (components, done_callback))
|
||||
@@ -0,0 +1,152 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 autobahn.wamp.cryptosign import HAS_CRYPTOSIGN, CryptosignKey
|
||||
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
|
||||
__all__ = [
|
||||
'HAS_CRYPTOSIGN_SSHAGENT'
|
||||
]
|
||||
|
||||
if HAS_CRYPTOSIGN:
|
||||
try:
|
||||
# WAMP-cryptosign support for SSH agent is currently
|
||||
# only available on Twisted (on Python 2)
|
||||
from twisted.internet.protocol import Factory
|
||||
from twisted.internet.endpoints import UNIXClientEndpoint
|
||||
from twisted.conch.ssh.agent import SSHAgentClient
|
||||
except ImportError:
|
||||
# twisted.conch is not yet fully ported to Python 3
|
||||
HAS_CRYPTOSIGN_SSHAGENT = False
|
||||
else:
|
||||
HAS_CRYPTOSIGN_SSHAGENT = True
|
||||
__all__.append('SSHAgentCryptosignKey')
|
||||
|
||||
|
||||
if HAS_CRYPTOSIGN_SSHAGENT:
|
||||
|
||||
import os
|
||||
from nacl import signing
|
||||
from autobahn.wamp.cryptosign import _read_ssh_ed25519_pubkey, _unpack, _pack
|
||||
|
||||
class SSHAgentCryptosignKey(CryptosignKey):
|
||||
"""
|
||||
A WAMP-cryptosign signing key that is a proxy to a private Ed25510 key
|
||||
actually held in SSH agent.
|
||||
|
||||
An instance of this class must be create via the class method new().
|
||||
The instance only holds the public key part, whereas the private key
|
||||
counterpart is held in SSH agent.
|
||||
"""
|
||||
|
||||
def __init__(self, key, comment=None, reactor=None):
|
||||
CryptosignKey.__init__(self, key, comment)
|
||||
if not reactor:
|
||||
from twisted.internet import reactor
|
||||
self._reactor = reactor
|
||||
|
||||
@classmethod
|
||||
def new(cls, pubkey=None, reactor=None):
|
||||
"""
|
||||
Create a proxy for a key held in SSH agent.
|
||||
|
||||
:param pubkey: A string with a public Ed25519 key in SSH format.
|
||||
:type pubkey: unicode
|
||||
"""
|
||||
if not HAS_CRYPTOSIGN_SSHAGENT:
|
||||
raise Exception("SSH agent integration is not supported on this platform")
|
||||
|
||||
pubkey, _ = _read_ssh_ed25519_pubkey(pubkey)
|
||||
|
||||
if not reactor:
|
||||
from twisted.internet import reactor
|
||||
|
||||
if "SSH_AUTH_SOCK" not in os***REMOVED***iron:
|
||||
raise Exception("no ssh-agent is running!")
|
||||
|
||||
factory = Factory()
|
||||
factory.noisy = False
|
||||
factory.protocol = SSHAgentClient
|
||||
endpoint = UNIXClientEndpoint(reactor, os***REMOVED***iron["SSH_AUTH_SOCK"])
|
||||
d = endpoint.connect(factory)
|
||||
|
||||
@inlineCallbacks
|
||||
def on_connect(agent):
|
||||
keys = yield agent.requestIdentities()
|
||||
|
||||
# if the key is found in ssh-agent, the raw public key (32 bytes), and the
|
||||
# key comment as returned from ssh-agent
|
||||
key_data = None
|
||||
key_comment = None
|
||||
|
||||
for blob, comment in keys:
|
||||
raw = _unpack(blob)
|
||||
algo = raw[0].decode('utf8')
|
||||
if algo == 'ssh-ed25519':
|
||||
algo, _pubkey = raw
|
||||
if _pubkey == pubkey:
|
||||
key_data = _pubkey
|
||||
key_comment = comment.decode('utf8')
|
||||
break
|
||||
|
||||
agent.transport.loseConnection()
|
||||
|
||||
if key_data:
|
||||
key = signing.VerifyKey(key_data)
|
||||
returnValue(cls(key, key_comment, reactor))
|
||||
else:
|
||||
raise Exception("Ed25519 key not held in ssh-agent")
|
||||
|
||||
return d.addCallback(on_connect)
|
||||
|
||||
def sign(self, challenge):
|
||||
if "SSH_AUTH_SOCK" not in os***REMOVED***iron:
|
||||
raise Exception("no ssh-agent is running!")
|
||||
|
||||
factory = Factory()
|
||||
factory.noisy = False
|
||||
factory.protocol = SSHAgentClient
|
||||
endpoint = UNIXClientEndpoint(self._reactor, os***REMOVED***iron["SSH_AUTH_SOCK"])
|
||||
d = endpoint.connect(factory)
|
||||
|
||||
@inlineCallbacks
|
||||
def on_connect(agent):
|
||||
# we are now connected to the locally running ssh-agent
|
||||
# that agent might be the openssh-agent, or eg on Ubuntu 14.04 by
|
||||
# default the gnome-keyring / ssh-askpass-gnome application
|
||||
blob = _pack(['ssh-ed25519'.encode(), self.public_key(binary=True)])
|
||||
|
||||
# now ask the agent
|
||||
signature_blob = yield agent.signData(blob, challenge)
|
||||
algo, signature = _unpack(signature_blob)
|
||||
|
||||
agent.transport.loseConnection()
|
||||
|
||||
returnValue(signature)
|
||||
|
||||
return d.addCallback(on_connect)
|
||||
128
.venv/lib/python3.12/site-packages/autobahn/twisted/forwarder.py
Normal file
128
.venv/lib/python3.12/site-packages/autobahn/twisted/forwarder.py
Normal file
@@ -0,0 +1,128 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import txaio
|
||||
txaio.use_twisted()
|
||||
|
||||
from twisted.python import usage
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from twisted.internet.protocol import Factory, Protocol
|
||||
from twisted.internet.endpoints import clientFromString, serverFromString
|
||||
from twisted.application import service
|
||||
|
||||
|
||||
class DestEndpointForwardingProtocol(Protocol):
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def connectionMade(self):
|
||||
self.log.debug("DestEndpointForwardingProtocol.connectionMade")
|
||||
pass
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.log.debug(
|
||||
"DestEndpointForwardingProtocol.dataReceived: {data}",
|
||||
data=data,
|
||||
)
|
||||
if self.factory._sourceProtocol:
|
||||
self.factory._sourceProtocol.transport.write(data)
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.log.debug("DestEndpointForwardingProtocol.connectionLost")
|
||||
if self.factory._sourceProtocol:
|
||||
self.factory._sourceProtocol.transport.loseConnection()
|
||||
|
||||
|
||||
class DestEndpointForwardingFactory(Factory):
|
||||
|
||||
def __init__(self, sourceProtocol):
|
||||
self._sourceProtocol = sourceProtocol
|
||||
self._proto = None
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
self._proto = DestEndpointForwardingProtocol()
|
||||
self._proto.factory = self
|
||||
return self._proto
|
||||
|
||||
|
||||
class EndpointForwardingProtocol(Protocol):
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
@inlineCallbacks
|
||||
def connectionMade(self):
|
||||
self.log.debug("EndpointForwardingProtocol.connectionMade")
|
||||
self._destFactory = DestEndpointForwardingFactory(self)
|
||||
self._destEndpoint = clientFromString(self.factory.service._reactor,
|
||||
self.factory.service._destEndpointDescriptor)
|
||||
self._destEndpointPort = yield self._destEndpoint.connect(self._destFactory)
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.log.debug(
|
||||
"EndpointForwardingProtocol.dataReceived: {data}",
|
||||
data=data,
|
||||
)
|
||||
if self._destFactory._proto:
|
||||
self._destFactory._proto.transport.write(data)
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.log.debug("EndpointForwardingProtocol.connectionLost")
|
||||
if self._destFactory._proto:
|
||||
self._destFactory._proto.transport.loseConnection()
|
||||
|
||||
|
||||
class EndpointForwardingService(service.Service):
|
||||
|
||||
def __init__(self, endpointDescriptor, destEndpointDescriptor, reactor=None):
|
||||
if reactor is None:
|
||||
from twisted.internet import reactor
|
||||
self._reactor = reactor
|
||||
self._endpointDescriptor = endpointDescriptor
|
||||
self._destEndpointDescriptor = destEndpointDescriptor
|
||||
|
||||
@inlineCallbacks
|
||||
def startService(self):
|
||||
factory = Factory.forProtocol(EndpointForwardingProtocol)
|
||||
factory.service = self
|
||||
self._endpoint = serverFromString(self._reactor, self._endpointDescriptor)
|
||||
self._endpointPort = yield self._endpoint.listen(factory)
|
||||
|
||||
def stopService(self):
|
||||
return self._endpointPort.stopListening()
|
||||
|
||||
|
||||
class Options(usage.Options):
|
||||
synopsis = "[options]"
|
||||
longdesc = 'Endpoint Forwarder.'
|
||||
optParameters = [
|
||||
["endpoint", "e", None, "Source endpoint."],
|
||||
["dest_endpoint", "d", None, "Destination endpoint."]
|
||||
]
|
||||
|
||||
|
||||
def makeService(config):
|
||||
service = EndpointForwardingService(config['endpoint'], config['dest_endpoint'])
|
||||
return service
|
||||
604
.venv/lib/python3.12/site-packages/autobahn/twisted/rawsocket.py
Normal file
604
.venv/lib/python3.12/site-packages/autobahn/twisted/rawsocket.py
Normal file
@@ -0,0 +1,604 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import copy
|
||||
import math
|
||||
from typing import Optional
|
||||
|
||||
import txaio
|
||||
|
||||
from twisted.internet.protocol import Factory
|
||||
from twisted.protocols.basic import Int32StringReceiver
|
||||
from twisted.internet.error import ConnectionDone
|
||||
from twisted.internet.defer import CancelledError
|
||||
|
||||
from autobahn.util import public, _LazyHexFormatter
|
||||
from autobahn.twisted.util import create_transport_details, transport_channel_id
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost, InvalidUriError
|
||||
from autobahn.exception import PayloadExceededError
|
||||
|
||||
__all__ = (
|
||||
'WampRawSocketServerProtocol',
|
||||
'WampRawSocketClientProtocol',
|
||||
'WampRawSocketServerFactory',
|
||||
'WampRawSocketClientFactory'
|
||||
)
|
||||
|
||||
|
||||
class WampRawSocketProtocol(Int32StringReceiver):
|
||||
"""
|
||||
Base class for Twisted-based WAMP-over-RawSocket protocols.
|
||||
"""
|
||||
log = txaio.make_logger()
|
||||
|
||||
peer: Optional[str] = None
|
||||
is_server: Optional[bool] = None
|
||||
|
||||
def __init__(self):
|
||||
# set the RawSocket maximum message size by default
|
||||
self._max_message_size = 2**24
|
||||
self._transport_details = None
|
||||
|
||||
@property
|
||||
def transport_details(self) -> Optional[TransportDetails]:
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.transport_details`
|
||||
"""
|
||||
return self._transport_details
|
||||
|
||||
def lengthLimitExceeded(self, length):
|
||||
# override hook in Int32StringReceiver base class that is fired when a message is (to be) received
|
||||
# that is larger than what we agreed to handle (by negotiation in the RawSocket opening handshake)
|
||||
emsg = 'RawSocket connection: length of received message exceeded (message was {} bytes, but current maximum is {} bytes)'.format(length, self.MAX_LENGTH)
|
||||
raise PayloadExceededError(emsg)
|
||||
|
||||
def connectionMade(self):
|
||||
# Twisted networking framework entry point, called by Twisted
|
||||
# when the connection is established (either a client or a server)
|
||||
|
||||
# determine preliminary transport details (what is know at this point)
|
||||
self._transport_details = create_transport_details(self.transport, self.is_server)
|
||||
self._transport_details.channel_framing = TransportDetails.CHANNEL_FRAMING_RAWSOCKET
|
||||
|
||||
# backward compatibility
|
||||
self.peer = self._transport_details.peer
|
||||
|
||||
# a Future/Deferred that fires when we hit STATE_CLOSED
|
||||
self.is_closed = txaio.create_future()
|
||||
|
||||
# this will hold an ApplicationSession object
|
||||
# once the RawSocket opening handshake has been
|
||||
# completed
|
||||
#
|
||||
self._session = None
|
||||
|
||||
# Will hold the negotiated serializer once the opening handshake is complete
|
||||
#
|
||||
self._serializer = None
|
||||
|
||||
# Will be set to True once the opening handshake is complete
|
||||
#
|
||||
self._handshake_complete = False
|
||||
|
||||
# Buffer for opening handshake received bytes.
|
||||
#
|
||||
self._handshake_bytes = b''
|
||||
|
||||
# Peer requested to _receive_ this maximum length of serialized messages - hence we must not send larger msgs!
|
||||
#
|
||||
self._max_len_send = None
|
||||
|
||||
def _on_handshake_complete(self):
|
||||
# RawSocket connection established. Now let the user WAMP session factory
|
||||
# create a new WAMP session and fire off session open callback.
|
||||
try:
|
||||
if self._transport_details.is_secure:
|
||||
# now that the TLS opening handshake is complete, the actual TLS channel ID
|
||||
# will be available. make sure to set it!
|
||||
channel_id = {
|
||||
'tls-unique': transport_channel_id(self.transport, self._transport_details.is_server, 'tls-unique'),
|
||||
}
|
||||
self._transport_details.channel_id = channel_id
|
||||
|
||||
self._session = self.factory._factory()
|
||||
self.log.debug('{klass}._on_handshake_complete(): calling {method}', session=self._session,
|
||||
klass=self.__class__.__name__, method=self._session.onOpen)
|
||||
res = self._session.onOpen(self)
|
||||
except Exception as e:
|
||||
# Exceptions raised in onOpen are fatal ..
|
||||
self.log.warn("{klass}._on_handshake_complete(): ApplicationSession constructor / onOpen raised ({err})",
|
||||
klass=self.__class__.__name__, err=e)
|
||||
self.abort()
|
||||
else:
|
||||
self.log.debug('{klass}._on_handshake_complete(): {session} started (res={res}).', klass=self.__class__.__name__,
|
||||
session=self._session, res=res)
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.log.debug('{klass}.connectionLost(reason="{reason}"', klass=self.__class__.__name__, reason=reason)
|
||||
txaio.resolve(self.is_closed, self)
|
||||
try:
|
||||
wasClean = isinstance(reason.value, ConnectionDone)
|
||||
if self._session:
|
||||
self._session.onClose(wasClean)
|
||||
except Exception as e:
|
||||
# silently ignore exceptions raised here ..
|
||||
self.log.warn('{klass}.connectionLost(): ApplicationSession.onClose raised "{err}"',
|
||||
klass=self.__class__.__name__, err=e)
|
||||
self._session = None
|
||||
|
||||
def stringReceived(self, payload):
|
||||
self.log.trace('{klass}.stringReceived(): RX {octets} octets',
|
||||
klass=self.__class__.__name__, octets=_LazyHexFormatter(payload))
|
||||
try:
|
||||
for msg in self._serializer.unserialize(payload):
|
||||
self.log.trace("{klass}.stringReceived: RX WAMP message: {msg}",
|
||||
klass=self.__class__.__name__, msg=msg)
|
||||
self._session.onMessage(msg)
|
||||
|
||||
except CancelledError as e:
|
||||
self.log.debug("{klass}.stringReceived: WAMP CancelledError - connection will continue!\n{err}",
|
||||
klass=self.__class__.__name__,
|
||||
err=e)
|
||||
|
||||
except InvalidUriError as e:
|
||||
self.log.warn("{klass}.stringReceived: WAMP InvalidUriError - aborting connection!\n{err}",
|
||||
klass=self.__class__.__name__,
|
||||
err=e)
|
||||
self.abort()
|
||||
|
||||
except ProtocolError as e:
|
||||
self.log.warn("{klass}.stringReceived: WAMP ProtocolError - aborting connection!\n{err}",
|
||||
klass=self.__class__.__name__,
|
||||
err=e)
|
||||
self.abort()
|
||||
|
||||
except PayloadExceededError as e:
|
||||
self.log.warn("{klass}.stringReceived: WAMP PayloadExceededError - aborting connection!\n{err}",
|
||||
klass=self.__class__.__name__,
|
||||
err=e)
|
||||
self.abort()
|
||||
|
||||
except SerializationError as e:
|
||||
self.log.warn("{klass}.stringReceived: WAMP SerializationError - aborting connection!\n{err}",
|
||||
klass=self.__class__.__name__,
|
||||
err=e)
|
||||
self.abort()
|
||||
|
||||
except Exception as e:
|
||||
self.log.failure()
|
||||
self.log.warn("{klass}.stringReceived: WAMP Exception - aborting connection!\n{err}",
|
||||
klass=self.__class__.__name__,
|
||||
err=e)
|
||||
self.abort()
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.send`
|
||||
"""
|
||||
if self.isOpen():
|
||||
self.log.trace('{klass}.send() (serializer={serializer}): TX WAMP message: "{msg}"',
|
||||
klass=self.__class__.__name__, msg=msg, serializer=self._serializer)
|
||||
try:
|
||||
payload, _ = self._serializer.serialize(msg)
|
||||
except SerializationError as e:
|
||||
# all exceptions raised from above should be serialization errors ..
|
||||
raise SerializationError("WampRawSocketProtocol: unable to serialize WAMP application payload ({0})".format(e))
|
||||
else:
|
||||
payload_len = len(payload)
|
||||
if 0 < self._max_len_send < payload_len:
|
||||
emsg = 'tried to send RawSocket message with size {} exceeding payload limit of {} octets'.format(
|
||||
payload_len, self._max_len_send)
|
||||
self.log.warn(emsg)
|
||||
raise PayloadExceededError(emsg)
|
||||
else:
|
||||
self.sendString(payload)
|
||||
self.log.trace('{klass}.send(): TX {octets} octets',
|
||||
klass=self.__class__.__name__, octets=_LazyHexFormatter(payload))
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
def isOpen(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen`
|
||||
"""
|
||||
return self._session is not None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.close`
|
||||
"""
|
||||
if self.isOpen():
|
||||
self.transport.loseConnection()
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
def abort(self):
|
||||
"""
|
||||
Implements :func:`autobahn.wamp.interfaces.ITransport.abort`
|
||||
"""
|
||||
if self.isOpen():
|
||||
if hasattr(self.transport, 'abortConnection'):
|
||||
# ProcessProtocol lacks abortConnection()
|
||||
self.transport.abortConnection()
|
||||
else:
|
||||
self.transport.loseConnection()
|
||||
else:
|
||||
raise TransportLost()
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketServerProtocol(WampRawSocketProtocol):
|
||||
"""
|
||||
Twisted-based WAMP-over-RawSocket server protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
def dataReceived(self, data):
|
||||
|
||||
if self._handshake_complete:
|
||||
WampRawSocketProtocol.dataReceived(self, data)
|
||||
else:
|
||||
remaining = 4 - len(self._handshake_bytes)
|
||||
self._handshake_bytes += data[:remaining]
|
||||
|
||||
if len(self._handshake_bytes) == 4:
|
||||
|
||||
self.log.debug(
|
||||
"WampRawSocketServerProtocol: opening handshake received - 0x{octets}",
|
||||
octets=_LazyHexFormatter(self._handshake_bytes),
|
||||
)
|
||||
|
||||
# first octet must be magic octet 0x7f
|
||||
#
|
||||
_magic = ord(self._handshake_bytes[0:1])
|
||||
if _magic != 127:
|
||||
self.log.warn(
|
||||
"WampRawSocketServerProtocol: invalid magic byte (octet 1) in"
|
||||
" opening handshake: was {magic}, but expected 127",
|
||||
magic=_magic,
|
||||
)
|
||||
self.abort()
|
||||
else:
|
||||
self.log.debug('WampRawSocketServerProtocol: correct magic byte received')
|
||||
|
||||
# peer requests us to send messages of maximum length 2**max_len_exp
|
||||
#
|
||||
self._max_len_send = 2 ** (9 + (ord(self._handshake_bytes[1:2]) >> 4))
|
||||
self.log.debug(
|
||||
"WampRawSocketServerProtocol: client requests us to send out most {max_bytes} bytes per message",
|
||||
max_bytes=self._max_len_send,
|
||||
)
|
||||
|
||||
# client wants to speak this serialization format
|
||||
#
|
||||
ser_id = ord(self._handshake_bytes[1:2]) & 0x0F
|
||||
if ser_id in self.factory._serializers:
|
||||
self._serializer = copy.copy(self.factory._serializers[ser_id])
|
||||
self.log.debug(
|
||||
"WampRawSocketServerProtocol: client wants to use serializer '{serializer}'",
|
||||
serializer=ser_id,
|
||||
)
|
||||
else:
|
||||
self.log.warn(
|
||||
"WampRawSocketServerProtocol: opening handshake - no suitable serializer found (client requested {serializer}, and we have {serializers}",
|
||||
serializer=ser_id,
|
||||
serializers=self.factory._serializers.keys(),
|
||||
)
|
||||
self.abort()
|
||||
|
||||
# we request the client to send message of maximum length 2**reply_max_len_exp
|
||||
#
|
||||
reply_max_len_exp = int(math.ceil(math.log(self._max_message_size, 2)))
|
||||
|
||||
# this is an instance attribute on the Twisted base class for maximum size
|
||||
# of _received_ messages
|
||||
self.MAX_LENGTH = 2**reply_max_len_exp
|
||||
|
||||
# send out handshake reply
|
||||
#
|
||||
reply_octet2 = bytes(bytearray([
|
||||
((reply_max_len_exp - 9) << 4) | self._serializer.RAWSOCKET_SERIALIZER_ID]))
|
||||
self.transport.write(b'\x7F') # magic byte
|
||||
self.transport.write(reply_octet2) # max length / serializer
|
||||
self.transport.write(b'\x00\x00') # reserved octets
|
||||
|
||||
self._handshake_complete = True
|
||||
|
||||
self._on_handshake_complete()
|
||||
|
||||
self.log.debug(
|
||||
"WampRawSocketServerProtocol: opening handshake completed: {serializer}",
|
||||
serializer=self._serializer,
|
||||
)
|
||||
|
||||
# consume any remaining data received already ..
|
||||
#
|
||||
data = data[remaining:]
|
||||
if data:
|
||||
self.dataReceived(data)
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketClientProtocol(WampRawSocketProtocol):
|
||||
"""
|
||||
Twisted-based WAMP-over-RawSocket client protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
WampRawSocketProtocol.connectionMade(self)
|
||||
self._serializer = copy.copy(self.factory._serializer)
|
||||
|
||||
# we request the peer to send messages of maximum length 2**reply_max_len_exp
|
||||
request_max_len_exp = int(math.ceil(math.log(self._max_message_size, 2)))
|
||||
|
||||
# this is an instance attribute on the Twisted base class for maximum size
|
||||
# of _received_ messages
|
||||
self.MAX_LENGTH = 2**request_max_len_exp
|
||||
|
||||
# send out handshake request
|
||||
#
|
||||
request_octet2 = bytes(bytearray([
|
||||
((request_max_len_exp - 9) << 4) | self._serializer.RAWSOCKET_SERIALIZER_ID]))
|
||||
self.transport.write(b'\x7F') # magic byte
|
||||
self.transport.write(request_octet2) # max length / serializer
|
||||
self.transport.write(b'\x00\x00') # reserved octets
|
||||
|
||||
def dataReceived(self, data):
|
||||
|
||||
if self._handshake_complete:
|
||||
WampRawSocketProtocol.dataReceived(self, data)
|
||||
else:
|
||||
remaining = 4 - len(self._handshake_bytes)
|
||||
self._handshake_bytes += data[:remaining]
|
||||
|
||||
if len(self._handshake_bytes) == 4:
|
||||
|
||||
self.log.debug(
|
||||
"WampRawSocketClientProtocol: opening handshake received - {handshake}",
|
||||
handshake=_LazyHexFormatter(self._handshake_bytes),
|
||||
)
|
||||
|
||||
if ord(self._handshake_bytes[0:1]) != 0x7f:
|
||||
self.log.debug(
|
||||
"WampRawSocketClientProtocol: invalid magic byte (octet 1) in opening handshake: was 0x{magic}, but expected 0x7f",
|
||||
magic=_LazyHexFormatter(self._handshake_bytes[0]),
|
||||
)
|
||||
self.abort()
|
||||
|
||||
# peer requests us to _send_ messages of maximum length 2**max_len_exp
|
||||
#
|
||||
self._max_len_send = 2 ** (9 + (ord(self._handshake_bytes[1:2]) >> 4))
|
||||
self.log.debug(
|
||||
"WampRawSocketClientProtocol: server requests us to send out most {max} bytes per message",
|
||||
max=self._max_len_send,
|
||||
)
|
||||
|
||||
# client wants to speak this serialization format
|
||||
#
|
||||
ser_id = ord(self._handshake_bytes[1:2]) & 0x0F
|
||||
if ser_id != self._serializer.RAWSOCKET_SERIALIZER_ID:
|
||||
self.log.error(
|
||||
"WampRawSocketClientProtocol: opening handshake - no suitable serializer found (server replied {serializer}, and we requested {serializers})",
|
||||
serializer=ser_id,
|
||||
serializers=self._serializer.RAWSOCKET_SERIALIZER_ID,
|
||||
)
|
||||
self.abort()
|
||||
|
||||
self._handshake_complete = True
|
||||
|
||||
self._on_handshake_complete()
|
||||
|
||||
self.log.debug(
|
||||
"WampRawSocketClientProtocol: opening handshake completed (using serializer {serializer})",
|
||||
serializer=self._serializer,
|
||||
)
|
||||
|
||||
# consume any remaining data received already ..
|
||||
#
|
||||
data = data[remaining:]
|
||||
if data:
|
||||
self.dataReceived(data)
|
||||
|
||||
|
||||
class WampRawSocketFactory(Factory):
|
||||
"""
|
||||
Base class for Twisted-based WAMP-over-RawSocket factories.
|
||||
"""
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self, factory):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
"""
|
||||
if callable(factory):
|
||||
self._factory = factory
|
||||
else:
|
||||
self._factory = lambda: factory
|
||||
|
||||
# RawSocket max payload size is 16M (https://wamp-proto.org/_static/gen/wamp_latest_ietf.html#handshake)
|
||||
self._max_message_size = 2**24
|
||||
|
||||
def resetProtocolOptions(self):
|
||||
self._max_message_size = 2**24
|
||||
|
||||
def setProtocolOptions(self, maxMessagePayloadSize=None):
|
||||
self.log.debug('{klass}.setProtocolOptions(maxMessagePayloadSize={maxMessagePayloadSize})',
|
||||
klass=self.__class__.__name__, [AWS-SECRET-REMOVED]ize)
|
||||
assert maxMessagePayloadSize is None or (type(maxMessagePayloadSize) == int and maxMessagePayloadSize >= 512 and maxMessagePayloadSize <= 2**24)
|
||||
if maxMessagePayloadSize is not None and maxMessagePayloadSize != self._max_message_size:
|
||||
self._max_message_size = maxMessagePayloadSize
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
self.log.debug('{klass}.buildProtocol(addr={addr})', klass=self.__class__.__name__, addr=addr)
|
||||
p = self.protocol()
|
||||
p.factory = self
|
||||
p.MAX_LENGTH = self._max_message_size
|
||||
p._max_message_size = self._max_message_size
|
||||
self.log.debug('{klass}.buildProtocol() -> proto={proto}, max_message_size={max_message_size}, MAX_LENGTH={MAX_LENGTH}',
|
||||
klass=self.__class__.__name__, proto=p, max_message_size=p._max_message_size, MAX_LENGTH=p.MAX_LENGTH)
|
||||
return p
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketServerFactory(WampRawSocketFactory):
|
||||
"""
|
||||
Twisted-based WAMP-over-RawSocket server protocol factory.
|
||||
"""
|
||||
|
||||
protocol = WampRawSocketServerProtocol
|
||||
|
||||
def __init__(self, factory, serializers=None):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or ``None``
|
||||
for all available serializers).
|
||||
:type serializers: list of objects implementing
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
WampRawSocketFactory.__init__(self, factory)
|
||||
|
||||
if serializers is None:
|
||||
serializers = []
|
||||
|
||||
# try CBOR WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import CBORSerializer
|
||||
serializers.append(CBORSerializer(batched=True))
|
||||
serializers.append(CBORSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# try MsgPack WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import MsgPackSerializer
|
||||
serializers.append(MsgPackSerializer(batched=True))
|
||||
serializers.append(MsgPackSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# try UBJSON WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import UBJSONSerializer
|
||||
serializers.append(UBJSONSerializer(batched=True))
|
||||
serializers.append(UBJSONSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# try JSON WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import JsonSerializer
|
||||
serializers.append(JsonSerializer(batched=True))
|
||||
serializers.append(JsonSerializer())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if not serializers:
|
||||
raise Exception("could not import any WAMP serializers")
|
||||
|
||||
self._serializers = {}
|
||||
for ser in serializers:
|
||||
self._serializers[ser.RAWSOCKET_SERIALIZER_ID] = ser
|
||||
|
||||
|
||||
@public
|
||||
class WampRawSocketClientFactory(WampRawSocketFactory):
|
||||
"""
|
||||
Twisted-based WAMP-over-RawSocket client protocol factory.
|
||||
"""
|
||||
|
||||
protocol = WampRawSocketClientProtocol
|
||||
|
||||
def __init__(self, factory, serializer=None):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializer: The WAMP serializer to use (or ``None`` for
|
||||
"best" serializer, chosen as the first serializer available from
|
||||
this list: CBOR, MessagePack, UBJSON, JSON).
|
||||
:type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
WampRawSocketFactory.__init__(self, factory)
|
||||
|
||||
# Reduce the factory logs noise
|
||||
self.noisy = False
|
||||
|
||||
if serializer is None:
|
||||
|
||||
# try CBOR WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import CBORSerializer
|
||||
serializer = CBORSerializer()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if serializer is None:
|
||||
|
||||
# try MsgPack WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import MsgPackSerializer
|
||||
serializer = MsgPackSerializer()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if serializer is None:
|
||||
|
||||
# try UBJSON WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import UBJSONSerializer
|
||||
serializer = UBJSONSerializer()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if serializer is None:
|
||||
# try JSON WAMP serializer
|
||||
try:
|
||||
from autobahn.wamp.serializer import JsonSerializer
|
||||
serializer = JsonSerializer()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if serializer is None:
|
||||
raise Exception("could not import any WAMP serializer")
|
||||
|
||||
self._serializer = serializer
|
||||
182
.venv/lib/python3.12/site-packages/autobahn/twisted/resource.py
Normal file
182
.venv/lib/python3.12/site-packages/autobahn/twisted/resource.py
Normal file
@@ -0,0 +1,182 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 zope.interface import implementer
|
||||
|
||||
from twisted.protocols.policies import ProtocolWrapper
|
||||
try:
|
||||
# starting from Twisted 22.10.0 we have `notFound`
|
||||
from twisted.web.pages import notFound
|
||||
except ImportError:
|
||||
try:
|
||||
# In Twisted < 22.10.0 && > 12.2 this was called `NoResource`
|
||||
from twisted.web.resource import NoResource as notFound
|
||||
except ImportError:
|
||||
# And in Twisted < 12.2 this was in a different place
|
||||
from twisted.web.error import NoResource as notFound
|
||||
|
||||
from twisted.web.resource import IResource, Resource
|
||||
|
||||
# The following triggers an import of reactor at module level!
|
||||
#
|
||||
from twisted.web.server import NOT_DONE_YET
|
||||
|
||||
__all__ = (
|
||||
'WebSocketResource',
|
||||
'WSGIRootResource',
|
||||
)
|
||||
|
||||
|
||||
class WSGIRootResource(Resource):
|
||||
"""
|
||||
Root resource when you want a WSGI resource be the default serving
|
||||
resource for a Twisted Web site, but have subpaths served by
|
||||
different resources.
|
||||
|
||||
This is a hack needed since
|
||||
`twisted.web.wsgi.WSGIResource <http://twistedmatrix.com/documents/current/api/twisted.web.wsgi.WSGIResource.html>`_.
|
||||
does not provide a ``putChild()`` method.
|
||||
|
||||
.. seealso::
|
||||
|
||||
* `Autobahn Twisted Web WSGI example <https://github.com/crossbario/autobahn-[AWS-SECRET-REMOVED]ocket/echo_wsgi>`_
|
||||
* `Original hack <http://blog.vrplumber.com/index.php?/archives/2426-Making-your-Twisted-resources-a-url-sub-tree-of-your-WSGI-resource....html>`_
|
||||
"""
|
||||
|
||||
def __init__(self, wsgiResource, children):
|
||||
"""
|
||||
|
||||
:param wsgiResource: The WSGI to serve as root resource.
|
||||
:type wsgiResource: Instance of `twisted.web.wsgi.WSGIResource <http://twistedmatrix.com/documents/current/api/twisted.web.wsgi.WSGIResource.html>`_.
|
||||
:param children: A dictionary with string keys constituting URL subpaths, and Twisted Web resources as values.
|
||||
:type children: dict
|
||||
"""
|
||||
Resource.__init__(self)
|
||||
self._wsgiResource = wsgiResource
|
||||
self.children = children
|
||||
|
||||
def getChild(self, path, request):
|
||||
request.prepath.pop()
|
||||
request.postpath.insert(0, path)
|
||||
return self._wsgiResource
|
||||
|
||||
|
||||
@implementer(IResource)
|
||||
class WebSocketResource(object):
|
||||
"""
|
||||
A Twisted Web resource for WebSocket.
|
||||
"""
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, factory):
|
||||
"""
|
||||
|
||||
:param factory: An instance of :class:`autobahn.twisted.websocket.WebSocketServerFactory`.
|
||||
:type factory: obj
|
||||
"""
|
||||
self._factory = factory
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def getChildWithDefault(self, name, request):
|
||||
"""
|
||||
This resource cannot have children, hence this will always fail.
|
||||
"""
|
||||
return notFound(message="No such child resource.")
|
||||
|
||||
def putChild(self, path, child):
|
||||
"""
|
||||
This resource cannot have children, hence this is always ignored.
|
||||
"""
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Render the resource. This will takeover the transport underlying
|
||||
the request, create a :class:`autobahn.twisted.websocket.WebSocketServerProtocol`
|
||||
and let that do any subsequent communication.
|
||||
"""
|
||||
# for reasons unknown, the transport is already None when the
|
||||
# request is over HTTP2. request.channel.getPeer() is valid at
|
||||
# this point however
|
||||
if request.channel.transport is None:
|
||||
# render an "error, yo're doing HTTPS over WSS" webpage
|
||||
from autobahn.websocket import protocol
|
||||
request.setResponseCode(426, b"Upgrade required")
|
||||
# RFC says MUST set upgrade along with 426 code:
|
||||
# https://tools.ietf.org/html/rfc7231#section-6.5.15
|
||||
request.setHeader(b"Upgrade", b"WebSocket")
|
||||
html = protocol._SERVER_STATUS_TEMPLATE % ("", protocol.__version__)
|
||||
return html.encode('utf8')
|
||||
|
||||
# Create Autobahn WebSocket protocol.
|
||||
#
|
||||
protocol = self._factory.buildProtocol(request.transport.getPeer())
|
||||
if not protocol:
|
||||
# If protocol creation fails, we signal "internal server error"
|
||||
request.setResponseCode(500)
|
||||
return b""
|
||||
|
||||
# Take over the transport from Twisted Web
|
||||
#
|
||||
transport, request.channel.transport = request.channel.transport, None
|
||||
|
||||
# Connect the transport to our protocol. Once #3204 is fixed, there
|
||||
# may be a cleaner way of doing this.
|
||||
# http://twistedmatrix.com/trac/ticket/3204
|
||||
#
|
||||
if isinstance(transport, ProtocolWrapper):
|
||||
# i.e. TLS is a wrapping protocol
|
||||
transport.wrappedProtocol = protocol
|
||||
elif isinstance(transport.protocol, ProtocolWrapper):
|
||||
# this happens in new-TLS
|
||||
transport.protocol.wrappedProtocol = protocol
|
||||
else:
|
||||
transport.protocol = protocol
|
||||
protocol.makeConnection(transport)
|
||||
|
||||
# On Twisted 16+, the transport is paused whilst the existing
|
||||
# request is served; there won't be any requests after us so
|
||||
# we can just resume this ourselves.
|
||||
# 17.1 version
|
||||
if hasattr(transport, "_networkProducer"):
|
||||
transport._networkProducer.resumeProducing()
|
||||
# 16.x version
|
||||
elif hasattr(transport, "resumeProducing"):
|
||||
transport.resumeProducing()
|
||||
|
||||
# We recreate the request and forward the raw data. This is somewhat
|
||||
# silly (since Twisted Web already did the HTTP request parsing
|
||||
# which we will do a 2nd time), but it's totally non-invasive to our
|
||||
# code. Maybe improve this.
|
||||
#
|
||||
data = request.method + b' ' + request.uri + b' HTTP/1.1\x0d\x0a'
|
||||
for h in request.requestHeaders.getAllRawHeaders():
|
||||
data += h[0] + b': ' + b",".join(h[1]) + b'\x0d\x0a'
|
||||
data += b"\x0d\x0a"
|
||||
data += request.content.read()
|
||||
protocol.dataReceived(data)
|
||||
|
||||
return NOT_DONE_YET
|
||||
@@ -0,0 +1,25 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
@@ -0,0 +1,124 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
# t.i.reactor doesn't exist until we've imported it once, but we
|
||||
# need it to exist so we can @patch it out in the tests ...
|
||||
from twisted.internet import reactor # noqa
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from twisted.internet.defer import inlineCallbacks, succeed
|
||||
from twisted.trial import unittest
|
||||
|
||||
from autobahn.twisted.wamp import ApplicationRunner
|
||||
|
||||
|
||||
def raise_error(*args, **kw):
|
||||
raise RuntimeError("we always fail")
|
||||
|
||||
|
||||
class TestApplicationRunner(unittest.TestCase):
|
||||
@patch('twisted.internet.reactor')
|
||||
def test_runner_default(self, fakereactor):
|
||||
fakereactor.connectTCP = Mock(side_effect=raise_error)
|
||||
runner = ApplicationRunner('ws://fake:1234/ws', 'dummy realm')
|
||||
|
||||
# we should get "our" RuntimeError when we call run
|
||||
self.assertRaises(RuntimeError, runner.run, raise_error)
|
||||
|
||||
# both reactor.run and reactor.stop should have been called
|
||||
self.assertEqual(fakereactor.run.call_count, 1)
|
||||
self.assertEqual(fakereactor.stop.call_count, 1)
|
||||
|
||||
@patch('twisted.internet.reactor')
|
||||
@inlineCallbacks
|
||||
def test_runner_no_run(self, fakereactor):
|
||||
fakereactor.connectTCP = Mock(side_effect=raise_error)
|
||||
runner = ApplicationRunner('ws://fake:1234/ws', 'dummy realm')
|
||||
|
||||
try:
|
||||
yield runner.run(raise_error, start_reactor=False)
|
||||
self.fail() # should have raise an exception, via Deferred
|
||||
|
||||
except RuntimeError as e:
|
||||
# make sure it's "our" exception
|
||||
self.assertEqual(e.args[0], "we always fail")
|
||||
|
||||
# neither reactor.run() NOR reactor.stop() should have been called
|
||||
# (just connectTCP() will have been called)
|
||||
self.assertEqual(fakereactor.run.call_count, 0)
|
||||
self.assertEqual(fakereactor.stop.call_count, 0)
|
||||
|
||||
@patch('twisted.internet.reactor')
|
||||
def test_runner_no_run_happypath(self, fakereactor):
|
||||
proto = Mock()
|
||||
fakereactor.connectTCP = Mock(return_value=succeed(proto))
|
||||
runner = ApplicationRunner('ws://fake:1234/ws', 'dummy realm')
|
||||
|
||||
d = runner.run(Mock(), start_reactor=False)
|
||||
|
||||
# shouldn't have actually connected to anything
|
||||
# successfully, and the run() call shouldn't have inserted
|
||||
# any of its own call/errbacks. (except the cleanup handler)
|
||||
self.assertFalse(d.called)
|
||||
self.assertEqual(1, len(d.callbacks))
|
||||
|
||||
# neither reactor.run() NOR reactor.stop() should have been called
|
||||
# (just connectTCP() will have been called)
|
||||
self.assertEqual(fakereactor.run.call_count, 0)
|
||||
self.assertEqual(fakereactor.stop.call_count, 0)
|
||||
|
||||
@patch('twisted.internet.reactor')
|
||||
def test_runner_bad_proxy(self, fakereactor):
|
||||
proxy = 'myproxy'
|
||||
|
||||
self.assertRaises(
|
||||
AssertionError,
|
||||
ApplicationRunner,
|
||||
'ws://fake:1234/ws', 'dummy realm',
|
||||
proxy=proxy
|
||||
)
|
||||
|
||||
@patch('twisted.internet.reactor')
|
||||
def test_runner_proxy(self, fakereactor):
|
||||
proto = Mock()
|
||||
fakereactor.connectTCP = Mock(return_value=succeed(proto))
|
||||
|
||||
proxy = {'host': 'myproxy', 'port': 3128}
|
||||
|
||||
runner = ApplicationRunner('ws://fake:1234/ws', 'dummy realm', proxy=proxy)
|
||||
|
||||
d = runner.run(Mock(), start_reactor=False)
|
||||
|
||||
# shouldn't have actually connected to anything
|
||||
# successfully, and the run() call shouldn't have inserted
|
||||
# any of its own call/errbacks. (except the cleanup handler)
|
||||
self.assertFalse(d.called)
|
||||
self.assertEqual(1, len(d.callbacks))
|
||||
|
||||
# neither reactor.run() NOR reactor.stop() should have been called
|
||||
# (just connectTCP() will have been called)
|
||||
self.assertEqual(fakereactor.run.call_count, 0)
|
||||
self.assertEqual(fakereactor.stop.call_count, 0)
|
||||
@@ -0,0 +1,139 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import sys
|
||||
from unittest.mock import Mock
|
||||
|
||||
import twisted.internet
|
||||
from twisted.trial import unittest
|
||||
|
||||
from autobahn.twisted import choosereactor
|
||||
|
||||
|
||||
class ChooseReactorTests(unittest.TestCase):
|
||||
|
||||
def patch_reactor(self, name, new_reactor):
|
||||
"""
|
||||
Patch ``name`` so that Twisted will grab a fake reactor instead of
|
||||
a real one.
|
||||
"""
|
||||
if hasattr(twisted.internet, name):
|
||||
self.patch(twisted.internet, name, new_reactor)
|
||||
else:
|
||||
def _cleanup():
|
||||
delattr(twisted.internet, name)
|
||||
setattr(twisted.internet, name, new_reactor)
|
||||
|
||||
def patch_modules(self):
|
||||
"""
|
||||
Patch ``sys.modules`` so that Twisted believes there is no
|
||||
installed reactor.
|
||||
"""
|
||||
old_modules = dict(sys.modules)
|
||||
|
||||
new_modules = dict(sys.modules)
|
||||
del new_modules["twisted.internet.reactor"]
|
||||
|
||||
def _cleanup():
|
||||
sys.modules = old_modules
|
||||
|
||||
self.addCleanup(_cleanup)
|
||||
sys.modules = new_modules
|
||||
|
||||
def test_unknown(self):
|
||||
"""
|
||||
``install_optimal_reactor`` will use the default reactor if it is
|
||||
unable to detect the platform it is running on.
|
||||
"""
|
||||
reactor_mock = Mock()
|
||||
self.patch_reactor("selectreactor", reactor_mock)
|
||||
self.patch(sys, "platform", "unknown")
|
||||
|
||||
# Emulate that a reactor has not been installed
|
||||
self.patch_modules()
|
||||
|
||||
choosereactor.install_optimal_reactor()
|
||||
reactor_mock.install.assert_called_once_with()
|
||||
|
||||
def test_mac(self):
|
||||
"""
|
||||
``install_optimal_reactor`` will install KQueueReactor on
|
||||
Darwin (OS X).
|
||||
"""
|
||||
reactor_mock = Mock()
|
||||
self.patch_reactor("kqreactor", reactor_mock)
|
||||
self.patch(sys, "platform", "darwin")
|
||||
|
||||
# Emulate that a reactor has not been installed
|
||||
self.patch_modules()
|
||||
|
||||
choosereactor.install_optimal_reactor()
|
||||
reactor_mock.install.assert_called_once_with()
|
||||
|
||||
def test_win(self):
|
||||
"""
|
||||
``install_optimal_reactor`` will install IOCPReactor on Windows.
|
||||
"""
|
||||
if sys.platform != 'win32':
|
||||
raise unittest.SkipTest('unit test requires Windows')
|
||||
|
||||
reactor_mock = Mock()
|
||||
self.patch_reactor("iocpreactor", reactor_mock)
|
||||
self.patch(sys, "platform", "win32")
|
||||
|
||||
# Emulate that a reactor has not been installed
|
||||
self.patch_modules()
|
||||
|
||||
choosereactor.install_optimal_reactor()
|
||||
reactor_mock.install.assert_called_once_with()
|
||||
|
||||
def test_bsd(self):
|
||||
"""
|
||||
``install_optimal_reactor`` will install KQueueReactor on BSD.
|
||||
"""
|
||||
reactor_mock = Mock()
|
||||
self.patch_reactor("kqreactor", reactor_mock)
|
||||
self.patch(sys, "platform", "freebsd11")
|
||||
|
||||
# Emulate that a reactor has not been installed
|
||||
self.patch_modules()
|
||||
|
||||
choosereactor.install_optimal_reactor()
|
||||
reactor_mock.install.assert_called_once_with()
|
||||
|
||||
def test_linux(self):
|
||||
"""
|
||||
``install_optimal_reactor`` will install EPollReactor on Linux.
|
||||
"""
|
||||
reactor_mock = Mock()
|
||||
self.patch_reactor("epollreactor", reactor_mock)
|
||||
self.patch(sys, "platform", "linux")
|
||||
|
||||
# Emulate that a reactor has not been installed
|
||||
self.patch_modules()
|
||||
|
||||
choosereactor.install_optimal_reactor()
|
||||
reactor_mock.install.assert_called_once_with()
|
||||
@@ -0,0 +1,438 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
if os***REMOVED***iron.get('USE_TWISTED', False):
|
||||
from autobahn.twisted.component import Component
|
||||
from zope.interface import directlyProvides
|
||||
from autobahn.wamp.message import Welcome, Goodbye, Hello, Abort
|
||||
from autobahn.wamp.serializer import JsonSerializer
|
||||
from autobahn.testutil import FakeTransport
|
||||
from twisted.internet.interfaces import IStreamClientEndpoint
|
||||
from twisted.internet.defer import inlineCallbacks, succeed, Deferred
|
||||
from twisted.internet.task import Clock
|
||||
from twisted.trial import unittest
|
||||
from txaio.testutil import replace_loop
|
||||
|
||||
class ConnectionTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
@patch('txaio.sleep', return_value=succeed(None))
|
||||
@inlineCallbacks
|
||||
def test_successful_connect(self, fake_sleep):
|
||||
endpoint = Mock()
|
||||
joins = []
|
||||
|
||||
def joined(session, details):
|
||||
joins.append((session, details))
|
||||
return session.leave()
|
||||
directlyProvides(endpoint, IStreamClientEndpoint)
|
||||
component = Component(
|
||||
transports={
|
||||
"type": "websocket",
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"endpoint": endpoint,
|
||||
}
|
||||
)
|
||||
component.on('join', joined)
|
||||
|
||||
def connect(factory, **kw):
|
||||
proto = factory.buildProtocol('ws://localhost/')
|
||||
transport = FakeTransport()
|
||||
proto.makeConnection(transport)
|
||||
|
||||
from autobahn.websocket.protocol import WebSocketProtocol
|
||||
from base64 import b64encode
|
||||
from hashlib import sha1
|
||||
key = proto.websocket_key + WebSocketProtocol._WS_MAGIC
|
||||
proto.data = (
|
||||
b"HTTP/1.1 101 Switching Protocols\x0d\x0a"
|
||||
b"Upgrade: websocket\x0d\x0a"
|
||||
b"Connection: upgrade\x0d\x0a"
|
||||
b"Sec-Websocket-Protocol: wamp.2.json\x0d\x0a"
|
||||
b"Sec-Websocket-Accept: " + b64encode(sha1(key).digest()) + b"\x0d\x0a\x0d\x0a"
|
||||
)
|
||||
proto.processHandshake()
|
||||
|
||||
from autobahn.wamp import role
|
||||
features = role.RoleBrokerFeatures(
|
||||
publisher_identification=True,
|
||||
pattern_based_subscription=True,
|
||||
session_meta_api=True,
|
||||
subscription_meta_api=True,
|
||||
subscriber_blackwhite_listing=True,
|
||||
publisher_exclusion=True,
|
||||
subscription_revocation=True,
|
||||
payload_transparency=True,
|
||||
payload_encryption_cryptobox=True,
|
||||
)
|
||||
|
||||
msg = Welcome(123456, dict(broker=features), realm='realm')
|
||||
serializer = JsonSerializer()
|
||||
data, is_binary = serializer.serialize(msg)
|
||||
proto.onMessage(data, is_binary)
|
||||
|
||||
msg = Goodbye()
|
||||
proto.onMessage(*serializer.serialize(msg))
|
||||
proto.onClose(True, 100, "some old reason")
|
||||
|
||||
return succeed(proto)
|
||||
endpoint.connect = connect
|
||||
|
||||
# XXX it would actually be nicer if we *could* support
|
||||
# passing a reactor in here, but the _batched_timer =
|
||||
# make_batched_timer() stuff (slash txaio in general)
|
||||
# makes this "hard".
|
||||
reactor = Clock()
|
||||
with replace_loop(reactor):
|
||||
yield component.start(reactor=reactor)
|
||||
self.assertTrue(len(joins), 1)
|
||||
# make sure we fire all our time-outs
|
||||
reactor.advance(3600)
|
||||
|
||||
@patch('txaio.sleep', return_value=succeed(None))
|
||||
def test_successful_proxy_connect(self, fake_sleep):
|
||||
endpoint = Mock()
|
||||
directlyProvides(endpoint, IStreamClientEndpoint)
|
||||
component = Component(
|
||||
transports={
|
||||
"type": "websocket",
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"endpoint": endpoint,
|
||||
"proxy": {
|
||||
"host": "10.0.0.0",
|
||||
"port": 65000,
|
||||
},
|
||||
"max_retries": 0,
|
||||
},
|
||||
is_fatal=lambda _: True,
|
||||
)
|
||||
|
||||
@component.on_join
|
||||
def joined(session, details):
|
||||
return session.leave()
|
||||
|
||||
def connect(factory, **kw):
|
||||
return succeed(Mock())
|
||||
endpoint.connect = connect
|
||||
|
||||
# XXX it would actually be nicer if we *could* support
|
||||
# passing a reactor in here, but the _batched_timer =
|
||||
# make_batched_timer() stuff (slash txaio in general)
|
||||
# makes this "hard".
|
||||
reactor = Clock()
|
||||
|
||||
got_proxy_connect = Deferred()
|
||||
|
||||
def _tcp(host, port, factory, **kw):
|
||||
self.assertEqual("10.0.0.0", host)
|
||||
self.assertEqual(port, 65000)
|
||||
got_proxy_connect.callback(None)
|
||||
return endpoint.connect(factory._wrappedFactory)
|
||||
reactor.connectTCP = _tcp
|
||||
|
||||
with replace_loop(reactor):
|
||||
d = component.start(reactor=reactor)
|
||||
|
||||
def done(x):
|
||||
if not got_proxy_connect.called:
|
||||
got_proxy_connect.callback(x)
|
||||
# make sure we fire all our time-outs
|
||||
d.addCallbacks(done, done)
|
||||
reactor.advance(3600)
|
||||
return got_proxy_connect
|
||||
|
||||
@patch('txaio.sleep', return_value=succeed(None))
|
||||
@inlineCallbacks
|
||||
def test_cancel(self, fake_sleep):
|
||||
"""
|
||||
if we start a component but call .stop before it connects, ever,
|
||||
it should still exit properly
|
||||
"""
|
||||
endpoint = Mock()
|
||||
directlyProvides(endpoint, IStreamClientEndpoint)
|
||||
component = Component(
|
||||
transports={
|
||||
"type": "websocket",
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"endpoint": endpoint,
|
||||
}
|
||||
)
|
||||
|
||||
def connect(factory, **kw):
|
||||
return Deferred()
|
||||
endpoint.connect = connect
|
||||
|
||||
# XXX it would actually be nicer if we *could* support
|
||||
# passing a reactor in here, but the _batched_timer =
|
||||
# make_batched_timer() stuff (slash txaio in general)
|
||||
# makes this "hard".
|
||||
reactor = Clock()
|
||||
with replace_loop(reactor):
|
||||
d = component.start(reactor=reactor)
|
||||
component.stop()
|
||||
yield d
|
||||
|
||||
@inlineCallbacks
|
||||
def test_cancel_while_waiting(self):
|
||||
"""
|
||||
if we start a component but call .stop before it connects, ever,
|
||||
it should still exit properly -- even if we're 'between'
|
||||
connection attempts
|
||||
"""
|
||||
endpoint = Mock()
|
||||
directlyProvides(endpoint, IStreamClientEndpoint)
|
||||
component = Component(
|
||||
transports={
|
||||
"type": "websocket",
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"endpoint": endpoint,
|
||||
"max_retries": 0,
|
||||
"max_retry_delay": 5,
|
||||
"initial_retry_delay": 5,
|
||||
},
|
||||
)
|
||||
|
||||
# XXX it would actually be nicer if we *could* support
|
||||
# passing a reactor in here, but the _batched_timer =
|
||||
# make_batched_timer() stuff (slash txaio in general)
|
||||
# makes this "hard".
|
||||
reactor = Clock()
|
||||
with replace_loop(reactor):
|
||||
|
||||
def connect(factory, **kw):
|
||||
d = Deferred()
|
||||
reactor.callLater(10, d.errback(RuntimeError("no connect for yo")))
|
||||
return d
|
||||
endpoint.connect = connect
|
||||
|
||||
d0 = component.start(reactor=reactor)
|
||||
assert component._delay_f is not None
|
||||
assert not component._done_f.called
|
||||
|
||||
d1 = component.stop()
|
||||
assert component._done_f is None
|
||||
assert d0.called
|
||||
|
||||
yield d1
|
||||
yield d0
|
||||
|
||||
@patch('txaio.sleep', return_value=succeed(None))
|
||||
@inlineCallbacks
|
||||
def test_connect_no_auth_method(self, fake_sleep):
|
||||
endpoint = Mock()
|
||||
|
||||
directlyProvides(endpoint, IStreamClientEndpoint)
|
||||
component = Component(
|
||||
transports={
|
||||
"type": "websocket",
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"endpoint": endpoint,
|
||||
},
|
||||
is_fatal=lambda e: True,
|
||||
)
|
||||
|
||||
def connect(factory, **kw):
|
||||
proto = factory.buildProtocol('boom')
|
||||
proto.makeConnection(Mock())
|
||||
|
||||
from autobahn.websocket.protocol import WebSocketProtocol
|
||||
from base64 import b64encode
|
||||
from hashlib import sha1
|
||||
key = proto.websocket_key + WebSocketProtocol._WS_MAGIC
|
||||
proto.data = (
|
||||
b"HTTP/1.1 101 Switching Protocols\x0d\x0a"
|
||||
b"Upgrade: websocket\x0d\x0a"
|
||||
b"Connection: upgrade\x0d\x0a"
|
||||
b"Sec-Websocket-Protocol: wamp.2.json\x0d\x0a"
|
||||
b"Sec-Websocket-Accept: " + b64encode(sha1(key).digest()) + b"\x0d\x0a\x0d\x0a"
|
||||
)
|
||||
proto.processHandshake()
|
||||
|
||||
from autobahn.wamp import role
|
||||
subrole = role.RoleSubscriberFeatures()
|
||||
|
||||
msg = Hello("realm", roles=dict(subscriber=subrole), authmethods=["anonymous"])
|
||||
serializer = JsonSerializer()
|
||||
data, is_binary = serializer.serialize(msg)
|
||||
proto.onMessage(data, is_binary)
|
||||
|
||||
msg = Abort(reason="wamp.error.no_auth_method")
|
||||
proto.onMessage(*serializer.serialize(msg))
|
||||
proto.onClose(False, 100, "wamp.error.no_auth_method")
|
||||
|
||||
return succeed(proto)
|
||||
endpoint.connect = connect
|
||||
|
||||
# XXX it would actually be nicer if we *could* support
|
||||
# passing a reactor in here, but the _batched_timer =
|
||||
# make_batched_timer() stuff (slash txaio in general)
|
||||
# makes this "hard".
|
||||
reactor = Clock()
|
||||
with replace_loop(reactor):
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
d = component.start(reactor=reactor)
|
||||
# make sure we fire all our time-outs
|
||||
reactor.advance(3600)
|
||||
yield d
|
||||
self.assertIn(
|
||||
"Exhausted all transport",
|
||||
str(ctx.exception)
|
||||
)
|
||||
|
||||
class InvalidTransportConfigs(unittest.TestCase):
|
||||
|
||||
def test_invalid_key(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=dict(
|
||||
foo='bar', # totally invalid key
|
||||
),
|
||||
)
|
||||
self.assertIn("'foo' is not", str(ctx.exception))
|
||||
|
||||
def test_invalid_key_transport_list(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=[
|
||||
dict(type='websocket', url='ws://127.0.0.1/ws'),
|
||||
dict(foo='bar'), # totally invalid key
|
||||
]
|
||||
)
|
||||
self.assertIn("'foo' is not a valid configuration item", str(ctx.exception))
|
||||
|
||||
def test_invalid_serializer_key(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=[
|
||||
{
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"serializer": ["quux"],
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertIn("only for rawsocket", str(ctx.exception))
|
||||
|
||||
def test_invalid_serializer(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=[
|
||||
{
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"serializers": ["quux"],
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertIn("Invalid serializer", str(ctx.exception))
|
||||
|
||||
def test_invalid_serializer_type_0(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=[
|
||||
{
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"serializers": [1, 2],
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertIn("must be a list", str(ctx.exception))
|
||||
|
||||
def test_invalid_serializer_type_1(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=[
|
||||
{
|
||||
"url": "ws://127.0.0.1/ws",
|
||||
"serializers": 1,
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertIn("must be a list", str(ctx.exception))
|
||||
|
||||
def test_invalid_type_key(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=[
|
||||
{
|
||||
"type": "bad",
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertIn("Invalid transport type", str(ctx.exception))
|
||||
|
||||
def test_invalid_type(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=[
|
||||
"foo"
|
||||
]
|
||||
)
|
||||
self.assertIn("invalid WebSocket URL", str(ctx.exception))
|
||||
|
||||
def test_no_url(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
transports=[
|
||||
{
|
||||
"type": "websocket",
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertIn("Transport requires 'url'", str(ctx.exception))
|
||||
|
||||
def test_endpoint_bogus_object(self):
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
Component(
|
||||
main=lambda r, s: None,
|
||||
transports=[
|
||||
{
|
||||
"type": "websocket",
|
||||
"url": "ws://example.com/ws",
|
||||
"endpoint": ("not", "a", "dict"),
|
||||
}
|
||||
]
|
||||
)
|
||||
self.assertIn("'endpoint' configuration must be", str(ctx.exception))
|
||||
|
||||
def test_endpoint_valid(self):
|
||||
Component(
|
||||
main=lambda r, s: None,
|
||||
transports=[
|
||||
{
|
||||
"type": "websocket",
|
||||
"url": "ws://example.com/ws",
|
||||
"endpoint": {
|
||||
"type": "tcp",
|
||||
"host": "1.2.3.4",
|
||||
"port": "4321",
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 twisted.trial.unittest import TestCase
|
||||
|
||||
|
||||
class PluginTests(TestCase):
|
||||
if True:
|
||||
skip = "Plugins don't work under Python3 yet"
|
||||
|
||||
def test_import(self):
|
||||
from twisted.plugins import autobahn_endpoints
|
||||
self.assertTrue(hasattr(autobahn_endpoints, 'AutobahnClientParser'))
|
||||
|
||||
def test_parse_client_basic(self):
|
||||
from twisted.plugins import autobahn_endpoints
|
||||
self.assertTrue(hasattr(autobahn_endpoints, 'AutobahnClientParser'))
|
||||
from twisted.internet.endpoints import clientFromString, quoteStringArgument
|
||||
from twisted.internet import reactor
|
||||
|
||||
ep_string = "autobahn:{0}:url={1}".format(
|
||||
quoteStringArgument('tcp:localhost:9000'),
|
||||
quoteStringArgument('ws://localhost:9000'),
|
||||
)
|
||||
# we're just testing that this doesn't fail entirely
|
||||
clientFromString(reactor, ep_string)
|
||||
@@ -0,0 +1,447 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 unittest.mock import Mock
|
||||
|
||||
import txaio
|
||||
txaio.use_twisted()
|
||||
|
||||
from autobahn.util import wildcards2patterns
|
||||
from autobahn.twisted.websocket import WebSocketServerFactory
|
||||
from autobahn.twisted.websocket import WebSocketServerProtocol
|
||||
from autobahn.twisted.websocket import WebSocketClientProtocol
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
from autobahn.websocket.types import ConnectingRequest
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.internet.error import ConnectionDone, ConnectionAborted, \
|
||||
ConnectionLost
|
||||
from twisted.trial import unittest
|
||||
try:
|
||||
from twisted.internet.testing import StringTransport
|
||||
except ImportError:
|
||||
from twisted.test.proto_helpers import StringTransport
|
||||
from autobahn.testutil import FakeTransport
|
||||
|
||||
|
||||
class ExceptionHandlingTests(unittest.TestCase):
|
||||
"""
|
||||
Tests that we format various exception variations properly during
|
||||
connectionLost
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = WebSocketServerFactory()
|
||||
self.proto = WebSocketServerProtocol()
|
||||
self.proto.factory = self.factory
|
||||
self.proto.log = Mock()
|
||||
|
||||
def tearDown(self):
|
||||
for call in [
|
||||
self.proto.autoPingPendingCall,
|
||||
self.proto.autoPingTimeoutCall,
|
||||
self.proto.openHandshakeTimeoutCall,
|
||||
self.proto.closeHandshakeTimeoutCall,
|
||||
]:
|
||||
if call is not None:
|
||||
call.cancel()
|
||||
|
||||
def test_connection_done(self):
|
||||
# pretend we connected
|
||||
self.proto._connectionMade()
|
||||
|
||||
self.proto.connectionLost(Failure(ConnectionDone()))
|
||||
|
||||
messages = ' '.join([str(x[1]) for x in self.proto.log.mock_calls])
|
||||
self.assertTrue('closed cleanly' in messages)
|
||||
|
||||
def test_connection_aborted(self):
|
||||
# pretend we connected
|
||||
self.proto._connectionMade()
|
||||
|
||||
self.proto.connectionLost(Failure(ConnectionAborted()))
|
||||
|
||||
messages = ' '.join([str(x[1]) for x in self.proto.log.mock_calls])
|
||||
self.assertTrue(' aborted ' in messages)
|
||||
|
||||
def test_connection_lost(self):
|
||||
# pretend we connected
|
||||
self.proto._connectionMade()
|
||||
|
||||
self.proto.connectionLost(Failure(ConnectionLost()))
|
||||
|
||||
messages = ' '.join([str(x[1]) for x in self.proto.log.mock_calls])
|
||||
self.assertTrue(' was lost ' in messages)
|
||||
|
||||
def test_connection_lost_arg(self):
|
||||
# pretend we connected
|
||||
self.proto._connectionMade()
|
||||
|
||||
self.proto.connectionLost(Failure(ConnectionLost("greetings")))
|
||||
|
||||
messages = ' '.join([str(x[1]) + str(x[2]) for x in self.proto.log.mock_calls])
|
||||
self.assertTrue(' was lost ' in messages)
|
||||
self.assertTrue('greetings' in messages)
|
||||
|
||||
|
||||
class Hixie76RejectionTests(unittest.TestCase):
|
||||
"""
|
||||
Hixie-76 should not be accepted by an Autobahn server.
|
||||
"""
|
||||
def test_handshake_fails(self):
|
||||
"""
|
||||
A handshake from a client only supporting Hixie-76 will fail.
|
||||
"""
|
||||
t = FakeTransport()
|
||||
f = WebSocketServerFactory()
|
||||
p = WebSocketServerProtocol()
|
||||
p.factory = f
|
||||
p.transport = t
|
||||
|
||||
# from http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
||||
http_request = b"GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\n^n:ds[4U"
|
||||
|
||||
p.openHandshakeTimeout = 0
|
||||
p._connectionMade()
|
||||
p.data = http_request
|
||||
p.processHandshake()
|
||||
self.assertIn(b"HTTP/1.1 400", t._written)
|
||||
self.assertIn(b"Hixie76 protocol not supported", t._written)
|
||||
|
||||
|
||||
class WebSocketOriginMatching(unittest.TestCase):
|
||||
"""
|
||||
Test that we match Origin: headers properly, when asked to
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = WebSocketServerFactory()
|
||||
self.factory.setProtocolOptions(
|
||||
allowedOrigins=['127.0.0.1:*', '*.example.com:*']
|
||||
)
|
||||
self.proto = WebSocketServerProtocol()
|
||||
self.proto.transport = StringTransport()
|
||||
self.proto.factory = self.factory
|
||||
self.proto.failHandshake = Mock()
|
||||
self.proto._connectionMade()
|
||||
|
||||
def tearDown(self):
|
||||
for call in [
|
||||
self.proto.autoPingPendingCall,
|
||||
self.proto.autoPingTimeoutCall,
|
||||
self.proto.openHandshakeTimeoutCall,
|
||||
self.proto.closeHandshakeTimeoutCall,
|
||||
]:
|
||||
if call is not None:
|
||||
call.cancel()
|
||||
|
||||
def test_match_full_origin(self):
|
||||
self.proto.data = b"\r\n".join([
|
||||
b'GET /ws HTTP/1.1',
|
||||
b'Host: www.example.com',
|
||||
b'Sec-WebSocket-Version: 13',
|
||||
b'Origin: http://www.example.com.malicious.com',
|
||||
b'Sec-WebSocket-Extensions: permessage-deflate',
|
||||
b'Sec-WebSocket-Key: tXAxWFUqnhi86Ajj7dRY5g==',
|
||||
b'Connection: keep-alive, Upgrade',
|
||||
b'Upgrade: websocket',
|
||||
b'\r\n', # last string doesn't get a \r\n from join()
|
||||
])
|
||||
self.proto.consumeData()
|
||||
|
||||
self.assertTrue(self.proto.failHandshake.called, "Handshake should have failed")
|
||||
arg = self.proto.failHandshake.mock_calls[0][1][0]
|
||||
self.assertTrue('not allowed' in arg)
|
||||
|
||||
def test_match_wrong_scheme_origin(self):
|
||||
# some monkey-business since we already did this in setUp, but
|
||||
# we want a different set of matching origins
|
||||
self.factory.setProtocolOptions(
|
||||
allowedOrigins=['http://*.example.com:*']
|
||||
)
|
||||
self.proto.allowedOriginsPatterns = self.factory.allowedOriginsPatterns
|
||||
self.proto.allowedOrigins = self.factory.allowedOrigins
|
||||
|
||||
# the actual test
|
||||
self.factory.isSecure = False
|
||||
self.proto.data = b"\r\n".join([
|
||||
b'GET /ws HTTP/1.1',
|
||||
b'Host: www.example.com',
|
||||
b'Sec-WebSocket-Version: 13',
|
||||
b'Origin: https://www.example.com',
|
||||
b'Sec-WebSocket-Extensions: permessage-deflate',
|
||||
b'Sec-WebSocket-Key: tXAxWFUqnhi86Ajj7dRY5g==',
|
||||
b'Connection: keep-alive, Upgrade',
|
||||
b'Upgrade: websocket',
|
||||
b'\r\n', # last string doesn't get a \r\n from join()
|
||||
])
|
||||
self.proto.consumeData()
|
||||
|
||||
self.assertTrue(self.proto.failHandshake.called, "Handshake should have failed")
|
||||
arg = self.proto.failHandshake.mock_calls[0][1][0]
|
||||
self.assertTrue('not allowed' in arg)
|
||||
|
||||
def test_match_origin_secure_scheme(self):
|
||||
self.factory.isSecure = True
|
||||
self.factory.port = 443
|
||||
self.proto.data = b"\r\n".join([
|
||||
b'GET /ws HTTP/1.1',
|
||||
b'Host: www.example.com',
|
||||
b'Sec-WebSocket-Version: 13',
|
||||
b'Origin: https://www.example.com',
|
||||
b'Sec-WebSocket-Extensions: permessage-deflate',
|
||||
b'Sec-WebSocket-Key: tXAxWFUqnhi86Ajj7dRY5g==',
|
||||
b'Connection: keep-alive, Upgrade',
|
||||
b'Upgrade: websocket',
|
||||
b'\r\n', # last string doesn't get a \r\n from join()
|
||||
])
|
||||
self.proto.consumeData()
|
||||
|
||||
self.assertFalse(self.proto.failHandshake.called, "Handshake should have succeeded")
|
||||
|
||||
def test_match_origin_documentation_example(self):
|
||||
"""
|
||||
Test the examples from the docs
|
||||
"""
|
||||
self.factory.setProtocolOptions(
|
||||
allowedOrigins=['*://*.example.com:*']
|
||||
)
|
||||
self.factory.isSecure = True
|
||||
self.factory.port = 443
|
||||
self.proto.data = b"\r\n".join([
|
||||
b'GET /ws HTTP/1.1',
|
||||
b'Host: www.example.com',
|
||||
b'Sec-WebSocket-Version: 13',
|
||||
b'Origin: http://www.example.com',
|
||||
b'Sec-WebSocket-Extensions: permessage-deflate',
|
||||
b'Sec-WebSocket-Key: tXAxWFUqnhi86Ajj7dRY5g==',
|
||||
b'Connection: keep-alive, Upgrade',
|
||||
b'Upgrade: websocket',
|
||||
b'\r\n', # last string doesn't get a \r\n from join()
|
||||
])
|
||||
self.proto.consumeData()
|
||||
|
||||
self.assertFalse(self.proto.failHandshake.called, "Handshake should have succeeded")
|
||||
|
||||
def test_match_origin_examples(self):
|
||||
"""
|
||||
All the example origins from RFC6454 (3.2.1)
|
||||
"""
|
||||
# we're just testing the low-level function here...
|
||||
from autobahn.websocket.protocol import _is_same_origin, _url_to_origin
|
||||
policy = wildcards2patterns(['*example.com:*'])
|
||||
|
||||
# should parametrize test ...
|
||||
for url in ['http://example.com/', 'http://example.com:80/',
|
||||
'http://example.com/path/file',
|
||||
'http://example.com/;semi=true',
|
||||
# 'http://example.com./',
|
||||
'//example.com/',
|
||||
'http://@example.com']:
|
||||
self.assertTrue(_is_same_origin(_url_to_origin(url), 'http', 80, policy), url)
|
||||
|
||||
def test_match_origin_counter_examples(self):
|
||||
"""
|
||||
All the example 'not-same' origins from RFC6454 (3.2.1)
|
||||
"""
|
||||
# we're just testing the low-level function here...
|
||||
from autobahn.websocket.protocol import _is_same_origin, _url_to_origin
|
||||
policy = wildcards2patterns(['example.com'])
|
||||
|
||||
for url in ['http://ietf.org/', 'http://example.org/',
|
||||
'https://example.com/', 'http://example.com:8080/',
|
||||
'http://www.example.com/']:
|
||||
self.assertFalse(_is_same_origin(_url_to_origin(url), 'http', 80, policy))
|
||||
|
||||
def test_match_origin_edge(self):
|
||||
# we're just testing the low-level function here...
|
||||
from autobahn.websocket.protocol import _is_same_origin, _url_to_origin
|
||||
policy = wildcards2patterns(['http://*example.com:80'])
|
||||
|
||||
self.assertTrue(
|
||||
_is_same_origin(_url_to_origin('http://example.com:80'), 'http', 80, policy)
|
||||
)
|
||||
self.assertFalse(
|
||||
_is_same_origin(_url_to_origin('http://example.com:81'), 'http', 81, policy)
|
||||
)
|
||||
self.assertFalse(
|
||||
_is_same_origin(_url_to_origin('https://example.com:80'), 'http', 80, policy)
|
||||
)
|
||||
|
||||
def test_origin_from_url(self):
|
||||
from autobahn.websocket.protocol import _url_to_origin
|
||||
|
||||
# basic function
|
||||
self.assertEqual(
|
||||
_url_to_origin('http://example.com'),
|
||||
('http', 'example.com', 80)
|
||||
)
|
||||
# should lower-case scheme
|
||||
self.assertEqual(
|
||||
_url_to_origin('hTTp://example.com'),
|
||||
('http', 'example.com', 80)
|
||||
)
|
||||
|
||||
def test_origin_file(self):
|
||||
from autobahn.websocket.protocol import _url_to_origin
|
||||
self.assertEqual('null', _url_to_origin('file:///etc/passwd'))
|
||||
|
||||
def test_origin_null(self):
|
||||
from autobahn.websocket.protocol import _is_same_origin, _url_to_origin
|
||||
self.assertEqual('null', _url_to_origin('null'))
|
||||
self.assertFalse(
|
||||
_is_same_origin(_url_to_origin('null'), 'http', 80, [])
|
||||
)
|
||||
self.assertFalse(
|
||||
_is_same_origin(_url_to_origin('null'), 'https', 80, [])
|
||||
)
|
||||
self.assertFalse(
|
||||
_is_same_origin(_url_to_origin('null'), '', 80, [])
|
||||
)
|
||||
self.assertFalse(
|
||||
_is_same_origin(_url_to_origin('null'), None, 80, [])
|
||||
)
|
||||
|
||||
|
||||
class WebSocketXForwardedFor(unittest.TestCase):
|
||||
"""
|
||||
Test that (only) a trusted X-Forwarded-For can replace the peer address.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = WebSocketServerFactory()
|
||||
self.factory.setProtocolOptions(
|
||||
trustXForwardedFor=2
|
||||
)
|
||||
self.proto = WebSocketServerProtocol()
|
||||
self.proto.transport = StringTransport()
|
||||
self.proto.factory = self.factory
|
||||
self.proto.failHandshake = Mock()
|
||||
self.proto._connectionMade()
|
||||
|
||||
def tearDown(self):
|
||||
for call in [
|
||||
self.proto.autoPingPendingCall,
|
||||
self.proto.autoPingTimeoutCall,
|
||||
self.proto.openHandshakeTimeoutCall,
|
||||
self.proto.closeHandshakeTimeoutCall,
|
||||
]:
|
||||
if call is not None:
|
||||
call.cancel()
|
||||
|
||||
def test_trusted_addresses(self):
|
||||
self.proto.data = b"\r\n".join([
|
||||
b'GET /ws HTTP/1.1',
|
||||
b'Host: www.example.com',
|
||||
b'Origin: http://www.example.com',
|
||||
b'Sec-WebSocket-Version: 13',
|
||||
b'Sec-WebSocket-Extensions: permessage-deflate',
|
||||
b'Sec-WebSocket-Key: tXAxWFUqnhi86Ajj7dRY5g==',
|
||||
b'Connection: keep-alive, Upgrade',
|
||||
b'Upgrade: websocket',
|
||||
b'X-Forwarded-For: 1.2.3.4, 2.3.4.5, 111.222.33.44',
|
||||
b'\r\n', # last string doesn't get a \r\n from join()
|
||||
])
|
||||
self.proto.consumeData()
|
||||
|
||||
self.assertEquals(
|
||||
self.proto.peer, "2.3.4.5",
|
||||
"The second address in X-Forwarded-For should have been picked as the peer address")
|
||||
|
||||
|
||||
class OnConnectingTests(unittest.TestCase):
|
||||
"""
|
||||
Tests related to onConnecting callback
|
||||
|
||||
These tests are testing generic behavior, but are somewhat tied to
|
||||
'a framework' so we're just testing using Twisted-specifics here.
|
||||
"""
|
||||
|
||||
def test_on_connecting_client_fails(self):
|
||||
|
||||
MAGIC_STR = 'bad stuff'
|
||||
|
||||
class TestProto(WebSocketClientProtocol):
|
||||
state = None
|
||||
wasClean = True
|
||||
log = Mock()
|
||||
|
||||
def onConnecting(self, transport_details):
|
||||
raise RuntimeError(MAGIC_STR)
|
||||
|
||||
proto = TestProto()
|
||||
proto.transport = FakeTransport()
|
||||
d = proto.startHandshake()
|
||||
self.successResultOf(d) # error is ignored
|
||||
# ... but error should be logged
|
||||
self.assertTrue(len(proto.log.mock_calls) > 0)
|
||||
magic_found = False
|
||||
for i in range(len(proto.log.mock_calls)):
|
||||
if MAGIC_STR in str(proto.log.mock_calls[i]):
|
||||
magic_found = True
|
||||
self.assertTrue(magic_found, 'MAGIC_STR not found when expected')
|
||||
|
||||
def test_on_connecting_client_success(self):
|
||||
|
||||
class TestProto(WebSocketClientProtocol):
|
||||
state = None
|
||||
wasClean = True
|
||||
perMessageCompressionOffers = []
|
||||
version = 18
|
||||
openHandshakeTimeout = 5
|
||||
log = Mock()
|
||||
|
||||
def onConnecting(self, transport_details):
|
||||
return ConnectingRequest(
|
||||
host="example.com",
|
||||
port=443,
|
||||
resource="/ws",
|
||||
)
|
||||
|
||||
proto = TestProto()
|
||||
proto.transport = FakeTransport()
|
||||
proto.factory = Mock()
|
||||
proto._connectionMade()
|
||||
|
||||
d = proto.startHandshake()
|
||||
req = self.successResultOf(d)
|
||||
self.assertEqual("example.com", req.host)
|
||||
self.assertEqual(443, req.port)
|
||||
self.assertEqual("/ws", req.resource)
|
||||
|
||||
def test_str_transport(self):
|
||||
details = TransportDetails(
|
||||
channel_type=TransportDetails.CHANNEL_TYPE_FUNCTION,
|
||||
peer="example.com",
|
||||
is_secure=False,
|
||||
channel_id={},
|
||||
)
|
||||
# we can str() this and it doesn't fail
|
||||
str(details)
|
||||
|
||||
def test_str_connecting(self):
|
||||
req = ConnectingRequest(host="example.com", port="1234", resource="/ws")
|
||||
# we can str() this and it doesn't fail
|
||||
str(req)
|
||||
@@ -0,0 +1,70 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
|
||||
from autobahn.twisted.rawsocket import (WampRawSocketServerFactory,
|
||||
WampRawSocketServerProtocol,
|
||||
WampRawSocketClientFactory,
|
||||
WampRawSocketClientProtocol)
|
||||
from autobahn.testutil import FakeTransport
|
||||
|
||||
|
||||
class RawSocketHandshakeTests(unittest.TestCase):
|
||||
|
||||
def test_handshake_succeeds(self):
|
||||
"""
|
||||
A client can connect to a server.
|
||||
"""
|
||||
session_mock = Mock()
|
||||
t = FakeTransport()
|
||||
f = WampRawSocketClientFactory(lambda: session_mock)
|
||||
p = WampRawSocketClientProtocol()
|
||||
p.transport = t
|
||||
p.factory = f
|
||||
|
||||
server_session_mock = Mock()
|
||||
st = FakeTransport()
|
||||
sf = WampRawSocketServerFactory(lambda: server_session_mock)
|
||||
sp = WampRawSocketServerProtocol()
|
||||
sp.transport = st
|
||||
sp.factory = sf
|
||||
|
||||
sp.connectionMade()
|
||||
p.connectionMade()
|
||||
|
||||
# Send the server the client handshake
|
||||
sp.dataReceived(t._written[0:1])
|
||||
sp.dataReceived(t._written[1:4])
|
||||
|
||||
# Send the client the server handshake
|
||||
p.dataReceived(st._written)
|
||||
|
||||
# The handshake succeeds, a session on each end is created
|
||||
# onOpen is called on the session
|
||||
session_mock.onOpen.assert_called_once_with(p)
|
||||
server_session_mock.onOpen.assert_called_once_with(sp)
|
||||
@@ -0,0 +1,81 @@
|
||||
from twisted.trial import unittest
|
||||
|
||||
try:
|
||||
from autobahn.twisted.testing import create_memory_agent, MemoryReactorClockResolver, create_pumper
|
||||
HAVE_TESTING = True
|
||||
except ImportError:
|
||||
HAVE_TESTING = False
|
||||
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from autobahn.twisted.websocket import WebSocketServerProtocol
|
||||
|
||||
|
||||
class TestAgent(unittest.TestCase):
|
||||
|
||||
skip = not HAVE_TESTING
|
||||
|
||||
def setUp(self):
|
||||
self.pumper = create_pumper()
|
||||
self.reactor = MemoryReactorClockResolver()
|
||||
return self.pumper.start()
|
||||
|
||||
def tearDown(self):
|
||||
return self.pumper.stop()
|
||||
|
||||
@inlineCallbacks
|
||||
def test_echo_server(self):
|
||||
|
||||
class EchoServer(WebSocketServerProtocol):
|
||||
def onMessage(self, msg, is_binary):
|
||||
self.sendMessage(msg)
|
||||
|
||||
agent = create_memory_agent(self.reactor, self.pumper, EchoServer)
|
||||
proto = yield agent.open("ws://localhost:1234/ws", dict())
|
||||
|
||||
messages = []
|
||||
|
||||
def got(msg, is_binary):
|
||||
messages.append(msg)
|
||||
proto.on("message", got)
|
||||
|
||||
proto.sendMessage(b"hello")
|
||||
|
||||
if True:
|
||||
# clean close
|
||||
proto.sendClose()
|
||||
else:
|
||||
# unclean close
|
||||
proto.transport.loseConnection()
|
||||
yield proto.is_closed
|
||||
self.assertEqual([b"hello"], messages)
|
||||
|
||||
# FIXME:
|
||||
# /twisted/util.py", line 162, in transport_channel_id channel_id_type, type(transport)))
|
||||
# builtins.RuntimeError: cannot determine TLS channel ID of type "tls-unique" when TLS is not
|
||||
# available on this transport <class 'twisted.test.iosim.FakeTransport'>
|
||||
# @inlineCallbacks
|
||||
# def test_secure_echo_server(self):
|
||||
|
||||
# class EchoServer(WebSocketServerProtocol):
|
||||
# def onMessage(self, msg, is_binary):
|
||||
# self.sendMessage(msg)
|
||||
|
||||
# agent = create_memory_agent(self.reactor, self.pumper, EchoServer)
|
||||
# proto = yield agent.open("wss://localhost:1234/ws", dict())
|
||||
|
||||
# messages = []
|
||||
|
||||
# def got(msg, is_binary):
|
||||
# messages.append(msg)
|
||||
# proto.on("message", got)
|
||||
|
||||
# proto.sendMessage(b"hello")
|
||||
|
||||
# if True:
|
||||
# # clean close
|
||||
# proto.sendClose()
|
||||
# else:
|
||||
# # unclean close
|
||||
# proto.transport.loseConnection()
|
||||
# yield proto.is_closed
|
||||
# self.assertEqual([b"hello"], messages)
|
||||
@@ -0,0 +1,90 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import unittest
|
||||
|
||||
from unittest.mock import patch
|
||||
from zope.interface import implementer
|
||||
from twisted.internet.interfaces import IReactorTime
|
||||
|
||||
|
||||
@implementer(IReactorTime)
|
||||
class FakeReactor(object):
|
||||
"""
|
||||
This just fakes out enough reactor methods so .run() can work.
|
||||
"""
|
||||
stop_called = False
|
||||
|
||||
def __init__(self, to_raise):
|
||||
self.stop_called = False
|
||||
self.to_raise = to_raise
|
||||
self.delayed = []
|
||||
|
||||
def run(self, *args, **kw):
|
||||
raise self.to_raise
|
||||
|
||||
def stop(self):
|
||||
self.stop_called = True
|
||||
|
||||
def callLater(self, delay, func, *args, **kwargs):
|
||||
self.delayed.append((delay, func, args, kwargs))
|
||||
|
||||
def connectTCP(self, *args, **kw):
|
||||
raise RuntimeError("ConnectTCP shouldn't get called")
|
||||
|
||||
|
||||
class TestWampTwistedRunner(unittest.TestCase):
|
||||
# XXX should figure out *why* but the test_protocol timeout
|
||||
# tests fail if we *don't* patch out this txaio stuff. So,
|
||||
# presumably it's messing up some global state that both tests
|
||||
# implicitly depend on ...
|
||||
|
||||
@patch('txaio.use_twisted')
|
||||
@patch('txaio.start_logging')
|
||||
@patch('txaio.config')
|
||||
def test_connect_error(self, *args):
|
||||
"""
|
||||
Ensure the runner doesn't swallow errors and that it exits the
|
||||
reactor properly if there is one.
|
||||
"""
|
||||
try:
|
||||
from autobahn.twisted.wamp import ApplicationRunner
|
||||
from twisted.internet.error import ConnectionRefusedError
|
||||
# the 'reactor' member doesn't exist until we import it
|
||||
from twisted.internet import reactor # noqa: F401
|
||||
except ImportError:
|
||||
raise unittest.SkipTest('No twisted')
|
||||
|
||||
runner = ApplicationRunner('ws://localhost:1', 'realm')
|
||||
exception = ConnectionRefusedError("It's a trap!")
|
||||
|
||||
with patch('twisted.internet.reactor', FakeReactor(exception)) as mockreactor:
|
||||
self.assertRaises(
|
||||
ConnectionRefusedError,
|
||||
# pass a no-op session-creation method
|
||||
runner.run, lambda _: None, start_reactor=True
|
||||
)
|
||||
self.assertTrue(mockreactor.stop_called)
|
||||
@@ -0,0 +1,293 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
# IHostnameResolver et al. were added in Twisted 17.1.0 .. before
|
||||
# that, it was IResolverSimple only.
|
||||
|
||||
try:
|
||||
from twisted.internet.interfaces import IHostnameResolver
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Twisted 17.1.0 or later required for autobahn.twisted.testing"
|
||||
)
|
||||
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.internet.address import IPv4Address
|
||||
from twisted.internet._resolver import HostResolution # "internal" class, but it's simple
|
||||
from twisted.internet.interfaces import ISSLTransport, IReactorPluggableNameResolver
|
||||
try:
|
||||
from twisted.internet.testing import MemoryReactorClock
|
||||
except ImportError:
|
||||
from twisted.test.proto_helpers import MemoryReactorClock
|
||||
from twisted.test import iosim
|
||||
|
||||
from zope.interface import directlyProvides, implementer
|
||||
|
||||
from autobahn.websocket.interfaces import IWebSocketClientAgent
|
||||
from autobahn.twisted.websocket import _TwistedWebSocketClientAgent
|
||||
from autobahn.twisted.websocket import WebSocketServerProtocol
|
||||
from autobahn.twisted.websocket import WebSocketServerFactory
|
||||
|
||||
|
||||
__all__ = (
|
||||
'create_pumper',
|
||||
'create_memory_agent',
|
||||
'MemoryReactorClockResolver',
|
||||
)
|
||||
|
||||
|
||||
@implementer(IHostnameResolver)
|
||||
class _StaticTestResolver(object):
|
||||
def resolveHostName(self, receiver, hostName, portNumber=0):
|
||||
"""
|
||||
Implement IHostnameResolver which always returns 127.0.0.1:31337
|
||||
"""
|
||||
resolution = HostResolution(hostName)
|
||||
receiver.resolutionBegan(resolution)
|
||||
receiver.addressResolved(
|
||||
IPv4Address('TCP', '127.0.0.1', 31337 if portNumber == 0 else portNumber)
|
||||
)
|
||||
receiver.resolutionComplete()
|
||||
|
||||
|
||||
@implementer(IReactorPluggableNameResolver)
|
||||
class _TestNameResolver(object):
|
||||
"""
|
||||
A test version of IReactorPluggableNameResolver
|
||||
"""
|
||||
|
||||
_resolver = None
|
||||
|
||||
@property
|
||||
def nameResolver(self):
|
||||
if self._resolver is None:
|
||||
self._resolver = _StaticTestResolver()
|
||||
return self._resolver
|
||||
|
||||
def installNameResolver(self, resolver):
|
||||
old = self._resolver
|
||||
self._resolver = resolver
|
||||
return old
|
||||
|
||||
|
||||
class MemoryReactorClockResolver(MemoryReactorClock, _TestNameResolver):
|
||||
"""
|
||||
Combine MemoryReactor, Clock and an IReactorPluggableNameResolver
|
||||
together.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class _TwistedWebMemoryAgent(IWebSocketClientAgent):
|
||||
"""
|
||||
A testing agent which will hook up an instance of
|
||||
`server_protocol` for every client that is created via the `open`
|
||||
API call.
|
||||
|
||||
:param reactor: the reactor to use for tests (usually an instance
|
||||
of MemoryReactorClockResolver)
|
||||
|
||||
:param pumper: an implementation IPumper (e.g. as returned by
|
||||
`create_pumper`)
|
||||
|
||||
:param server_protocol: the server-side WebSocket protocol class
|
||||
to instantiate (e.g. a subclass of `WebSocketServerProtocol`
|
||||
"""
|
||||
|
||||
def __init__(self, reactor, pumper, server_protocol):
|
||||
self._reactor = reactor
|
||||
self._server_protocol = server_protocol
|
||||
self._pumper = pumper
|
||||
|
||||
# our "real" underlying agent under test
|
||||
self._agent = _TwistedWebSocketClientAgent(self._reactor)
|
||||
self._pumps = set()
|
||||
self._servers = dict() # client -> server
|
||||
|
||||
def open(self, transport_config, options, protocol_class=None):
|
||||
"""
|
||||
Implement IWebSocketClientAgent with in-memory transports.
|
||||
|
||||
:param transport_config: a string starting with 'wss://' or
|
||||
'ws://'
|
||||
|
||||
:param options: a dict containing options
|
||||
|
||||
:param protocol_class: the client protocol class to
|
||||
instantiate (or `None` for defaults, which is to use
|
||||
`WebSocketClientProtocol`)
|
||||
"""
|
||||
is_secure = transport_config.startswith("wss://")
|
||||
|
||||
# call our "real" agent
|
||||
real_client_protocol = self._agent.open(
|
||||
transport_config, options,
|
||||
protocol_class=protocol_class,
|
||||
)
|
||||
|
||||
if is_secure:
|
||||
host, port, factory, context_factory, timeout, bindAddress = self._reactor.sslClients[-1]
|
||||
else:
|
||||
host, port, factory, timeout, bindAddress = self._reactor.tcpClients[-1]
|
||||
|
||||
server_address = IPv4Address('TCP', '127.0.0.1', port)
|
||||
client_address = IPv4Address('TCP', '127.0.0.1', 31337)
|
||||
|
||||
server_protocol = self._server_protocol()
|
||||
# the protocol could already have a factory
|
||||
if getattr(server_protocol, "factory", None) is None:
|
||||
server_protocol.factory = WebSocketServerFactory()
|
||||
|
||||
server_transport = iosim.FakeTransport(
|
||||
server_protocol, isServer=True,
|
||||
hostAddress=server_address, peerAddress=client_address)
|
||||
clientProtocol = factory.buildProtocol(None)
|
||||
client_transport = iosim.FakeTransport(
|
||||
clientProtocol, isServer=False,
|
||||
hostAddress=client_address, peerAddress=server_address)
|
||||
|
||||
if is_secure:
|
||||
directlyProvides(server_transport, ISSLTransport)
|
||||
directlyProvides(client_transport, ISSLTransport)
|
||||
|
||||
pump = iosim.connect(
|
||||
server_protocol, server_transport, clientProtocol, client_transport)
|
||||
self._pumper.add(pump)
|
||||
|
||||
def add_mapping(proto):
|
||||
self._servers[proto] = server_protocol
|
||||
return proto
|
||||
real_client_protocol.addCallback(add_mapping)
|
||||
return real_client_protocol
|
||||
|
||||
|
||||
class _Kalamazoo(object):
|
||||
"""
|
||||
Feeling whimsical about class names, see https://en.wikipedia.org/wiki/Handcar
|
||||
|
||||
This is 'an IOPump pumper', an object which causes a series of
|
||||
IOPumps it is monitoring to do their I/O operations
|
||||
periodically. This needs the 'real' reactor which trial drives,
|
||||
because reasons:
|
||||
|
||||
- so @inlineCallbacks / async-def functions work
|
||||
(if I could explain exactly why here, I would)
|
||||
|
||||
- we need to 'break the loop' of synchronous calls somewhere and
|
||||
polluting the tests themselves with that is bad
|
||||
|
||||
- get rid of e.g. .flush() calls in tests themselves (thus
|
||||
'teaching' the tests about details of I/O scheduling that they
|
||||
shouldn't know).
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._pumps = set()
|
||||
self._pumping = False
|
||||
self._waiting_for_stop = []
|
||||
from twisted.internet import reactor as global_reactor
|
||||
self._global_reactor = global_reactor
|
||||
|
||||
def add(self, p):
|
||||
"""
|
||||
Add a new IOPump. It will be removed when both its client and
|
||||
server are disconnected.
|
||||
"""
|
||||
self._pumps.add(p)
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Begin triggering I/O in all IOPump instances we have. We will keep
|
||||
periodically 'pumping' our IOPumps until `.stop()` is
|
||||
called. Call from `setUp()` for example.
|
||||
"""
|
||||
if self._pumping:
|
||||
return
|
||||
self._pumping = True
|
||||
self._global_reactor.callLater(0, self._pump_once)
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
:returns: a Deferred that fires when we have stopped pump()-ing
|
||||
|
||||
Call from `tearDown()`, for example.
|
||||
"""
|
||||
if self._pumping or len(self._waiting_for_stop):
|
||||
d = Deferred()
|
||||
self._waiting_for_stop.append(d)
|
||||
self._pumping = False
|
||||
return d
|
||||
d = Deferred()
|
||||
d.callback(None)
|
||||
return d
|
||||
|
||||
def _pump_once(self):
|
||||
"""
|
||||
flush all data from all our IOPump instances and schedule another
|
||||
iteration on the global reactor
|
||||
"""
|
||||
if self._pumping:
|
||||
self._flush()
|
||||
self._global_reactor.callLater(0.1, self._pump_once)
|
||||
else:
|
||||
for d in self._waiting_for_stop:
|
||||
d.callback(None)
|
||||
self._waiting_for_stop = []
|
||||
|
||||
def _flush(self):
|
||||
"""
|
||||
Flush all data between pending client/server pairs.
|
||||
"""
|
||||
old_pumps = self._pumps
|
||||
new_pumps = self._pumps = set()
|
||||
for p in old_pumps:
|
||||
p.flush()
|
||||
if p.clientIO.disconnected and p.serverIO.disconnected:
|
||||
continue
|
||||
new_pumps.add(p)
|
||||
|
||||
|
||||
def create_pumper():
|
||||
"""
|
||||
return a new instance implementing IPumper
|
||||
"""
|
||||
return _Kalamazoo()
|
||||
|
||||
|
||||
def create_memory_agent(reactor, pumper, server_protocol):
|
||||
"""
|
||||
return a new instance implementing `IWebSocketClientAgent`.
|
||||
|
||||
connection attempts will be satisfied by traversing the Upgrade
|
||||
request path starting at `resource` to find a `WebSocketResource`
|
||||
and then exchange data between client and server using purely
|
||||
in-memory buffers.
|
||||
"""
|
||||
# Note, we currently don't actually do any "resource traversing"
|
||||
# and basically accept any path at all to our websocket resource
|
||||
if server_protocol is None:
|
||||
server_protocol = WebSocketServerProtocol
|
||||
return _TwistedWebMemoryAgent(reactor, pumper, server_protocol)
|
||||
304
.venv/lib/python3.12/site-packages/autobahn/twisted/util.py
Normal file
304
.venv/lib/python3.12/site-packages/autobahn/twisted/util.py
Normal file
@@ -0,0 +1,304 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
import threading
|
||||
from typing import Optional, Union, Dict, Any
|
||||
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.internet.address import IPv4Address, UNIXAddress
|
||||
from twisted.internet.interfaces import ITransport, IProcessTransport
|
||||
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
|
||||
try:
|
||||
from twisted.internet.stdio import PipeAddress
|
||||
except ImportError:
|
||||
# stdio.PipeAddress is only avail on Twisted 13.0+
|
||||
PipeAddress = type(None)
|
||||
|
||||
try:
|
||||
from twisted.internet.address import IPv6Address
|
||||
_HAS_IPV6 = True
|
||||
except ImportError:
|
||||
_HAS_IPV6 = False
|
||||
IPv6Address = type(None)
|
||||
|
||||
try:
|
||||
from twisted.internet.interfaces import ISSLTransport
|
||||
from twisted.protocols.tls import TLSMemoryBIOProtocol
|
||||
from OpenSSL.SSL import Connection
|
||||
_HAS_TLS = True
|
||||
except ImportError:
|
||||
_HAS_TLS = False
|
||||
|
||||
__all = (
|
||||
'sleep',
|
||||
'peer2str',
|
||||
'transport_channel_id',
|
||||
'extract_peer_certificate',
|
||||
'create_transport_details',
|
||||
)
|
||||
|
||||
|
||||
def sleep(delay, reactor=None):
|
||||
"""
|
||||
Inline sleep for use in co-routines (Twisted ``inlineCallback`` decorated functions).
|
||||
|
||||
.. seealso::
|
||||
* `twisted.internet.defer.inlineCallbacks <http://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#inlineCallbacks>`__
|
||||
* `twisted.internet.interfaces.IReactorTime <http://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IReactorTime.html>`__
|
||||
|
||||
:param delay: Time to sleep in seconds.
|
||||
:type delay: float
|
||||
:param reactor: The Twisted reactor to use.
|
||||
:type reactor: None or provider of ``IReactorTime``.
|
||||
"""
|
||||
if not reactor:
|
||||
from twisted.internet import reactor
|
||||
d = Deferred()
|
||||
reactor.callLater(delay, d.callback, None)
|
||||
return d
|
||||
|
||||
|
||||
def peer2str(transport: Union[ITransport, IProcessTransport]) -> str:
|
||||
"""
|
||||
Return a *peer descriptor* given a Twisted transport, for example:
|
||||
|
||||
* ``tcp4:127.0.0.1:52914``: a TCPv4 socket
|
||||
* ``unix:/tmp/server.sock``: a Unix domain socket
|
||||
* ``process:142092``: a Pipe originating from a spawning (parent) process
|
||||
* ``pipe``: a Pipe terminating in a spawned (child) process
|
||||
|
||||
:returns: Returns a string representation of the peer of the Twisted transport.
|
||||
"""
|
||||
# IMPORTANT: we need to _first_ test for IProcessTransport
|
||||
if IProcessTransport.providedBy(transport):
|
||||
# note the PID of the forked process in the peer descriptor
|
||||
res = "process:{}".format(transport.pid)
|
||||
elif ITransport.providedBy(transport):
|
||||
addr: Union[IPv4Address, IPv6Address, UNIXAddress, PipeAddress] = transport.getPeer()
|
||||
if isinstance(addr, IPv4Address):
|
||||
res = "tcp4:{0}:{1}".format(addr.host, addr.port)
|
||||
elif _HAS_IPV6 and isinstance(addr, IPv6Address):
|
||||
res = "tcp6:{0}:{1}".format(addr.host, addr.port)
|
||||
elif isinstance(addr, UNIXAddress):
|
||||
if addr.name:
|
||||
res = "unix:{0}".format(addr.name)
|
||||
else:
|
||||
res = "unix"
|
||||
elif isinstance(addr, PipeAddress):
|
||||
# sadly, we don't have a way to get at the PID of the other side of the pipe
|
||||
# res = "pipe"
|
||||
res = "process:{0}".format(os.getppid())
|
||||
else:
|
||||
# gracefully fallback if we can't map the peer's address
|
||||
res = "unknown"
|
||||
else:
|
||||
# gracefully fallback if we can't map the peer's transport
|
||||
res = "unknown"
|
||||
return res
|
||||
|
||||
|
||||
if not _HAS_TLS:
|
||||
def transport_channel_id(transport: object, is_server: bool, channel_id_type: Optional[str] = None) -> Optional[bytes]:
|
||||
if channel_id_type is None:
|
||||
return b'\x00' * 32
|
||||
else:
|
||||
raise RuntimeError('cannot determine TLS channel ID of type "{}" when TLS is not available on this system'.format(channel_id_type))
|
||||
else:
|
||||
def transport_channel_id(transport: object, is_server: bool, channel_id_type: Optional[str] = None) -> Optional[bytes]:
|
||||
"""
|
||||
Return TLS channel ID of WAMP transport of the given TLS channel ID type.
|
||||
|
||||
Application-layer user authentication protocols are vulnerable to generic credential forwarding attacks,
|
||||
where an authentication credential sent by a client C to a server M may then be used by M to impersonate C at
|
||||
another server S.
|
||||
To prevent such credential forwarding attacks, modern authentication protocols rely on channel bindings.
|
||||
For example, WAMP-cryptosign can use the tls-unique channel identifier provided by the TLS layer to strongly
|
||||
bind authentication credentials to the underlying channel, so that a credential received on one TLS channel
|
||||
cannot be forwarded on another.
|
||||
|
||||
:param transport: The Twisted TLS transport to extract the TLS channel ID from. If the transport isn't
|
||||
TLS based, and non-empty ``channel_id_type`` is requested, ``None`` will be returned. If the transport
|
||||
is indeed TLS based, an empty ``channel_id_type`` of ``None`` is requested, 32 NUL bytes will be returned.
|
||||
:param is_server: Flag indicating that the transport is a server transport.
|
||||
:param channel_id_type: TLS channel ID type, if set currently only ``"tls-unique"`` is supported.
|
||||
:returns: The TLS channel ID (32 bytes).
|
||||
"""
|
||||
if channel_id_type is None:
|
||||
return b'\x00' * 32
|
||||
|
||||
if channel_id_type not in ['tls-unique']:
|
||||
raise RuntimeError('invalid TLS channel ID type "{}" requested'.format(channel_id_type))
|
||||
|
||||
if not isinstance(transport, TLSMemoryBIOProtocol):
|
||||
raise RuntimeError(
|
||||
'cannot determine TLS channel ID of type "{}" when TLS is not available on this transport {}'.format(
|
||||
channel_id_type, type(transport)))
|
||||
|
||||
# get access to the OpenSSL connection underlying the Twisted protocol
|
||||
# https://twistedmatrix.com/documents/current/api/twisted.protocols.tls.TLSMemoryBIOProtocol.html#getHandle
|
||||
connection: Connection = transport.getHandle()
|
||||
assert connection and isinstance(connection, Connection)
|
||||
|
||||
# Obtain latest TLS Finished message that we expected from peer, or None if handshake is not completed.
|
||||
# http://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Connection.get_peer_finished
|
||||
is_not_resumed = True
|
||||
|
||||
if channel_id_type == 'tls-unique':
|
||||
# see also: https://bugs.python.org/file22646/tls_channel_binding.patch
|
||||
if is_server != is_not_resumed:
|
||||
# for routers (=servers) XOR new sessions, the channel ID is based on the TLS Finished message we
|
||||
# expected to receive from the client: contents of the message or None if the TLS handshake has
|
||||
# not yet completed.
|
||||
tls_finished_msg = connection.get_peer_finished()
|
||||
else:
|
||||
# for clients XOR resumed sessions, the channel ID is based on the TLS Finished message we sent
|
||||
# to the router (=server): contents of the message or None if the TLS handshake has not yet completed.
|
||||
tls_finished_msg = connection.get_finished()
|
||||
|
||||
if tls_finished_msg is None:
|
||||
# this can occur when:
|
||||
# 1. we made a successful connection (in a TCP sense) but something failed with
|
||||
# the TLS handshake (e.g. invalid certificate)
|
||||
# 2. the TLS handshake has not yet completed
|
||||
return b'\x00' * 32
|
||||
else:
|
||||
m = hashlib.sha256()
|
||||
m.update(tls_finished_msg)
|
||||
return m.digest()
|
||||
else:
|
||||
raise NotImplementedError('should not arrive here (unhandled channel_id_type "{}")'.format(channel_id_type))
|
||||
|
||||
|
||||
if not _HAS_TLS:
|
||||
def extract_peer_certificate(transport: object) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Dummy when no TLS is available.
|
||||
|
||||
:param transport: Ignored.
|
||||
:return: Always return ``None``.
|
||||
"""
|
||||
return None
|
||||
else:
|
||||
def extract_peer_certificate(transport: TLSMemoryBIOProtocol) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Extract TLS x509 client certificate information from a Twisted stream transport, and
|
||||
return a dict with x509 TLS client certificate information (if the client provided a
|
||||
TLS client certificate).
|
||||
|
||||
:param transport: The secure transport from which to extract the peer certificate (if present).
|
||||
:returns: If the peer provided a certificate, the parsed certificate information set.
|
||||
"""
|
||||
# check if the Twisted transport is a TLSMemoryBIOProtocol
|
||||
if not (ISSLTransport.providedBy(transport) and hasattr(transport, 'getPeerCertificate')):
|
||||
return None
|
||||
|
||||
cert = transport.getPeerCertificate()
|
||||
if cert:
|
||||
# extract x509 name components from an OpenSSL X509Name object
|
||||
def maybe_bytes(_value):
|
||||
if isinstance(_value, bytes):
|
||||
return _value.decode('utf8')
|
||||
else:
|
||||
return _value
|
||||
|
||||
result = {
|
||||
'md5': '{}'.format(maybe_bytes(cert.digest('md5'))).upper(),
|
||||
'sha1': '{}'.format(maybe_bytes(cert.digest('sha1'))).upper(),
|
||||
'sha256': '{}'.format(maybe_bytes(cert.digest('sha256'))).upper(),
|
||||
'expired': bool(cert.has_expired()),
|
||||
'hash': maybe_bytes(cert.subject_name_hash()),
|
||||
'serial': int(cert.get_serial_number()),
|
||||
'signature_algorithm': maybe_bytes(cert.get_signature_algorithm()),
|
||||
'version': int(cert.get_version()),
|
||||
'not_before': maybe_bytes(cert.get_notBefore()),
|
||||
'not_after': maybe_bytes(cert.get_notAfter()),
|
||||
'extensions': []
|
||||
}
|
||||
|
||||
for i in range(cert.get_extension_count()):
|
||||
ext = cert.get_extension(i)
|
||||
ext_info = {
|
||||
'name': '{}'.format(maybe_bytes(ext.get_short_name())),
|
||||
'value': '{}'.format(maybe_bytes(ext)),
|
||||
'critical': ext.get_critical() != 0
|
||||
}
|
||||
result['extensions'].append(ext_info)
|
||||
|
||||
for entity, name in [('subject', cert.get_subject()), ('issuer', cert.get_issuer())]:
|
||||
result[entity] = {}
|
||||
for key, value in name.get_components():
|
||||
key = maybe_bytes(key)
|
||||
value = maybe_bytes(value)
|
||||
result[entity]['{}'.format(key).lower()] = '{}'.format(value)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def create_transport_details(transport: Union[ITransport, IProcessTransport], is_server: bool) -> TransportDetails:
|
||||
"""
|
||||
Create transport details from Twisted transport.
|
||||
|
||||
:param transport: The Twisted transport to extract information from.
|
||||
:param is_server: Flag indicating whether this transport side is a "server" (as in TCP server).
|
||||
:return: Transport details object filled with information from the Twisted transport.
|
||||
"""
|
||||
peer = peer2str(transport)
|
||||
|
||||
own_pid = os.getpid()
|
||||
if hasattr(threading, 'get_native_id'):
|
||||
# New in Python 3.8
|
||||
# https://docs.python.org/3/library/threading.html?highlight=get_native_id#threading.get_native_id
|
||||
own_tid = threading.get_native_id()
|
||||
else:
|
||||
own_tid = threading.get_ident()
|
||||
own_fd = -1
|
||||
|
||||
if _HAS_TLS and ISSLTransport.providedBy(transport):
|
||||
channel_id = {
|
||||
# this will only be filled when the TLS opening handshake is complete (!)
|
||||
'tls-unique': transport_channel_id(transport, is_server, 'tls-unique'),
|
||||
}
|
||||
channel_type = TransportDetails.CHANNEL_TYPE_TLS
|
||||
peer_cert = extract_peer_certificate(transport)
|
||||
is_secure = True
|
||||
else:
|
||||
channel_id = {}
|
||||
channel_type = TransportDetails.CHANNEL_TYPE_TCP
|
||||
peer_cert = None
|
||||
is_secure = False
|
||||
|
||||
# FIXME: really set a default (websocket)?
|
||||
channel_framing = TransportDetails.CHANNEL_FRAMING_WEBSOCKET
|
||||
|
||||
td = TransportDetails(channel_type=channel_type, channel_framing=channel_framing, peer=peer,
|
||||
is_server=is_server, own_pid=own_pid, own_tid=own_tid, own_fd=own_fd,
|
||||
is_secure=is_secure, channel_id=channel_id, peer_cert=peer_cert)
|
||||
|
||||
return td
|
||||
902
.venv/lib/python3.12/site-packages/autobahn/twisted/wamp.py
Normal file
902
.venv/lib/python3.12/site-packages/autobahn/twisted/wamp.py
Normal file
@@ -0,0 +1,902 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import inspect
|
||||
import binascii
|
||||
import random
|
||||
from typing import Optional, Dict, Any, List, Union
|
||||
|
||||
import txaio
|
||||
from autobahn.websocket.protocol import WebSocketProtocol
|
||||
|
||||
txaio.use_twisted() # noqa
|
||||
|
||||
from twisted.internet.defer import inlineCallbacks, succeed, Deferred
|
||||
from twisted.application import service
|
||||
from twisted.internet.interfaces import IReactorCore, IStreamClientEndpoint
|
||||
|
||||
try:
|
||||
from twisted.internet.ssl import CertificateOptions
|
||||
except ImportError:
|
||||
# PyOpenSSL / TLS not available
|
||||
CertificateOptions = Any
|
||||
|
||||
from autobahn.util import public
|
||||
|
||||
from autobahn.websocket.util import parse_url as parse_ws_url
|
||||
from autobahn.rawsocket.util import parse_url as parse_rs_url
|
||||
|
||||
from autobahn.twisted.websocket import WampWebSocketClientFactory
|
||||
from autobahn.twisted.rawsocket import WampRawSocketClientFactory
|
||||
|
||||
from autobahn.websocket.compress import PerMessageDeflateOffer, \
|
||||
PerMessageDeflateResponse, PerMessageDeflateResponseAccept
|
||||
|
||||
from autobahn.wamp import protocol, auth
|
||||
from autobahn.wamp.interfaces import ITransportHandler, ISession, IAuthenticator, ISerializer
|
||||
from autobahn.wamp.types import ComponentConfig
|
||||
|
||||
__all__ = [
|
||||
'ApplicationSession',
|
||||
'ApplicationSessionFactory',
|
||||
'ApplicationRunner',
|
||||
'Application',
|
||||
'Service',
|
||||
|
||||
# new API
|
||||
'Session',
|
||||
# 'run', # should probably move this method to here? instead of component
|
||||
]
|
||||
|
||||
|
||||
@public
|
||||
class ApplicationSession(protocol.ApplicationSession):
|
||||
"""
|
||||
WAMP application session for Twisted-based applications.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
* :class:`autobahn.wamp.interfaces.ISession`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
|
||||
ITransportHandler.register(ApplicationSession)
|
||||
|
||||
# ISession.register collides with the abc.ABCMeta.register method
|
||||
ISession.abc_register(ApplicationSession)
|
||||
|
||||
|
||||
class ApplicationSessionFactory(protocol.ApplicationSessionFactory):
|
||||
"""
|
||||
WAMP application session factory for Twisted-based applications.
|
||||
"""
|
||||
|
||||
session: ApplicationSession = ApplicationSession
|
||||
"""
|
||||
The application session class this application session factory will use. Defaults to :class:`autobahn.twisted.wamp.ApplicationSession`.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
|
||||
@public
|
||||
class ApplicationRunner(object):
|
||||
"""
|
||||
This class is a convenience tool mainly for development and quick hosting
|
||||
of WAMP application components.
|
||||
|
||||
It can host a WAMP application component in a WAMP-over-WebSocket client
|
||||
connecting to a WAMP router.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self,
|
||||
url: str,
|
||||
realm: Optional[str] = None,
|
||||
extra: Optional[Dict[str, Any]] = None,
|
||||
serializers: Optional[List[ISerializer]] = None,
|
||||
ssl: Optional[CertificateOptions] = None,
|
||||
proxy: Optional[Dict[str, Any]] = None,
|
||||
headers: Optional[Dict[str, Any]] = None,
|
||||
websocket_options: Optional[Dict[str, Any]] = None,
|
||||
max_retries: Optional[int] = None,
|
||||
initial_retry_delay: Optional[float] = None,
|
||||
max_retry_delay: Optional[float] = None,
|
||||
retry_delay_growth: Optional[float] = None,
|
||||
retry_delay_jitter: Optional[float] = None):
|
||||
"""
|
||||
|
||||
:param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://example.com:8080/mypath`)
|
||||
:param realm: The WAMP realm to join the application session to.
|
||||
:param extra: Optional extra configuration to forward to the application component.
|
||||
:param serializers: A list of WAMP serializers to use (or None for default serializers).
|
||||
Serializers must implement :class:`autobahn.wamp.interfaces.ISerializer`.
|
||||
:type serializers: list
|
||||
:param ssl: (Optional). If specified this should be an
|
||||
instance suitable to pass as ``sslContextFactory`` to
|
||||
:class:`twisted.internet.endpoints.SSL4ClientEndpoint`` such
|
||||
as :class:`twisted.internet.ssl.CertificateOptions`. Leaving
|
||||
it as ``None`` will use the result of calling Twisted
|
||||
:meth:`twisted.internet.ssl.platformTrust` which tries to use
|
||||
your distribution's CA certificates.
|
||||
:param proxy: Explicit proxy server to use; a dict with ``host`` and ``port`` keys.
|
||||
:param headers: Additional headers to send (only applies to WAMP-over-WebSocket).
|
||||
:param websocket_options: Specific WebSocket options to set (only applies to WAMP-over-WebSocket).
|
||||
If not provided, conservative and practical default are chosen.
|
||||
:param max_retries: Maximum number of reconnection attempts. Unlimited if set to -1.
|
||||
:param initial_retry_delay: Initial delay for reconnection attempt in seconds (Default: 1.0s).
|
||||
:param max_retry_delay: Maximum delay for reconnection attempts in seconds (Default: 60s).
|
||||
:param retry_delay_growth: The growth factor applied to the retry delay between reconnection
|
||||
attempts (Default 1.5).
|
||||
:param retry_delay_jitter: A 0-argument callable that introduces noise into the
|
||||
delay (Default ``random.random``).
|
||||
"""
|
||||
# IMPORTANT: keep this, as it is tested in
|
||||
# autobahn.twisted.test.test_tx_application_runner.TestApplicationRunner.test_runner_bad_proxy
|
||||
assert (proxy is None or type(proxy) == dict)
|
||||
|
||||
self.url = url
|
||||
self.realm = realm
|
||||
self.extra = extra or dict()
|
||||
self.serializers = serializers
|
||||
self.ssl = ssl
|
||||
self.proxy = proxy
|
||||
self.headers = headers
|
||||
self.websocket_options = websocket_options
|
||||
self.max_retries = max_retries
|
||||
self.initial_retry_delay = initial_retry_delay
|
||||
self.max_retry_delay = max_retry_delay
|
||||
self.retry_delay_growth = retry_delay_growth
|
||||
self.retry_delay_jitter = retry_delay_jitter
|
||||
|
||||
# this if for auto-reconnection when Twisted ClientService is avail
|
||||
self._client_service = None
|
||||
# total number of successful connections
|
||||
self._connect_successes = 0
|
||||
|
||||
@public
|
||||
def stop(self):
|
||||
"""
|
||||
Stop reconnecting, if auto-reconnecting was enabled.
|
||||
"""
|
||||
self.log.debug('{klass}.stop()', klass=self.__class__.__name__)
|
||||
|
||||
if self._client_service:
|
||||
return self._client_service.stopService()
|
||||
else:
|
||||
return succeed(None)
|
||||
|
||||
@public
|
||||
def run(self, make, start_reactor: bool = True, auto_reconnect: bool = False,
|
||||
log_level: str = 'info', endpoint: Optional[IStreamClientEndpoint] = None,
|
||||
reactor: Optional[IReactorCore] = None) -> Union[type(None), Deferred]:
|
||||
"""
|
||||
Run the application component.
|
||||
|
||||
:param make: A factory that produces instances of :class:`autobahn.twisted.wamp.ApplicationSession`
|
||||
when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`.
|
||||
:param start_reactor: When ``True`` (the default) this method starts
|
||||
the Twisted reactor and doesn't return until the reactor
|
||||
stops. If there are any problems starting the reactor or
|
||||
connect()-ing, we stop the reactor and raise the exception
|
||||
back to the caller.
|
||||
:param auto_reconnect:
|
||||
:param log_level:
|
||||
:param endpoint:
|
||||
:param reactor:
|
||||
:return: None is returned, unless you specify
|
||||
``start_reactor=False`` in which case the Deferred that
|
||||
connect() returns is returned; this will callback() with
|
||||
an IProtocol instance, which will actually be an instance
|
||||
of :class:`WampWebSocketClientProtocol`
|
||||
"""
|
||||
self.log.debug('{klass}.run()', klass=self.__class__.__name__)
|
||||
|
||||
if start_reactor:
|
||||
# only select framework, set loop and start logging when we are asked
|
||||
# start the reactor - otherwise we are running in a program that likely
|
||||
# already tool care of all this.
|
||||
from twisted.internet import reactor
|
||||
txaio.use_twisted()
|
||||
txaio.config.loop = reactor
|
||||
txaio.start_logging(level=log_level)
|
||||
|
||||
if callable(make):
|
||||
# factory for use ApplicationSession
|
||||
def create():
|
||||
cfg = ComponentConfig(self.realm, self.extra, runner=self)
|
||||
try:
|
||||
session = make(cfg)
|
||||
except Exception:
|
||||
self.log.failure('ApplicationSession could not be instantiated: {log_failure.value}')
|
||||
if start_reactor and reactor.running:
|
||||
reactor.stop()
|
||||
raise
|
||||
else:
|
||||
return session
|
||||
else:
|
||||
create = make
|
||||
|
||||
if self.url.startswith('rs'):
|
||||
# try to parse RawSocket URL
|
||||
isSecure, host, port = parse_rs_url(self.url)
|
||||
|
||||
# use the first configured serializer if any (which means, auto-choose "best")
|
||||
serializer = self.serializers[0] if self.serializers else None
|
||||
|
||||
# create a WAMP-over-RawSocket transport client factory
|
||||
transport_factory = WampRawSocketClientFactory(create, serializer=serializer)
|
||||
|
||||
else:
|
||||
# try to parse WebSocket URL
|
||||
isSecure, host, port, resource, path, params = parse_ws_url(self.url)
|
||||
|
||||
# create a WAMP-over-WebSocket transport client factory
|
||||
transport_factory = WampWebSocketClientFactory(create, url=self.url, serializers=self.serializers, proxy=self.proxy, headers=self.headers)
|
||||
|
||||
# client WebSocket settings - similar to:
|
||||
# - http://crossbar.io/docs/WebSocket-Compression/#production-settings
|
||||
# - http://crossbar.io/docs/WebSocket-Options/#production-settings
|
||||
|
||||
# The permessage-deflate extensions offered to the server
|
||||
offers = [PerMessageDeflateOffer()]
|
||||
|
||||
# Function to accept permessage-deflate responses from the server
|
||||
def accept(response):
|
||||
if isinstance(response, PerMessageDeflateResponse):
|
||||
return PerMessageDeflateResponseAccept(response)
|
||||
|
||||
# default WebSocket options for all client connections
|
||||
protocol_options = {
|
||||
'version': WebSocketProtocol.DEFAULT_SPEC_VERSION,
|
||||
'utf8validateIncoming': True,
|
||||
'acceptMaskedServerFrames': False,
|
||||
'maskClientFrames': True,
|
||||
'applyMask': True,
|
||||
'maxFramePayloadSize': 1048576,
|
||||
'maxMessagePayloadSize': 1048576,
|
||||
'autoFragmentSize': 65536,
|
||||
'failByDrop': True,
|
||||
'echoCloseCodeReason': False,
|
||||
'serverConnectionDropTimeout': 1.,
|
||||
'openHandshakeTimeout': 2.5,
|
||||
'closeHandshakeTimeout': 1.,
|
||||
'tcpNoDelay': True,
|
||||
'perMessageCompressionOffers': offers,
|
||||
'perMessageCompressionAccept': accept,
|
||||
'autoPingInterval': 10.,
|
||||
'autoPingTimeout': 5.,
|
||||
'autoPingSize': 12,
|
||||
|
||||
# see: https://github.com/crossbario/autobahn-python/issues/1327 and
|
||||
# _cancelAutoPingTimeoutCall
|
||||
'autoPingRestartOnAnyTraffic': True,
|
||||
}
|
||||
|
||||
# let user override above default options
|
||||
if self.websocket_options:
|
||||
protocol_options.update(self.websocket_options)
|
||||
|
||||
# set websocket protocol options on Autobahn/Twisted protocol factory, from where it will
|
||||
# be applied for every Autobahn/Twisted protocol instance from the factory
|
||||
transport_factory.setProtocolOptions(**protocol_options)
|
||||
|
||||
# supress pointless log noise
|
||||
transport_factory.noisy = False
|
||||
|
||||
if endpoint:
|
||||
client = endpoint
|
||||
else:
|
||||
# if user passed ssl= but isn't using isSecure, we'll never
|
||||
# use the ssl argument which makes no sense.
|
||||
context_factory = None
|
||||
if self.ssl is not None:
|
||||
if not isSecure:
|
||||
raise RuntimeError(
|
||||
'ssl= argument value passed to %s conflicts with the "ws:" '
|
||||
'prefix of the url argument. Did you mean to use "wss:"?' %
|
||||
self.__class__.__name__)
|
||||
context_factory = self.ssl
|
||||
elif isSecure:
|
||||
from twisted.internet.ssl import optionsForClientTLS
|
||||
context_factory = optionsForClientTLS(host)
|
||||
|
||||
from twisted.internet import reactor
|
||||
if self.proxy is not None:
|
||||
from twisted.internet.endpoints import TCP4ClientEndpoint
|
||||
client = TCP4ClientEndpoint(reactor, self.proxy['host'], self.proxy['port'])
|
||||
transport_factory.contextFactory = context_factory
|
||||
elif isSecure:
|
||||
from twisted.internet.endpoints import SSL4ClientEndpoint
|
||||
assert context_factory is not None
|
||||
client = SSL4ClientEndpoint(reactor, host, port, context_factory)
|
||||
else:
|
||||
from twisted.internet.endpoints import TCP4ClientEndpoint
|
||||
client = TCP4ClientEndpoint(reactor, host, port)
|
||||
|
||||
# as the reactor shuts down, we wish to wait until we've sent
|
||||
# out our "Goodbye" message; leave() returns a Deferred that
|
||||
# fires when the transport gets to STATE_CLOSED
|
||||
def cleanup(proto):
|
||||
if hasattr(proto, '_session') and proto._session is not None:
|
||||
if proto._session.is_attached():
|
||||
return proto._session.leave()
|
||||
elif proto._session.is_connected():
|
||||
return proto._session.disconnect()
|
||||
|
||||
# when our proto was created and connected, make sure it's cleaned
|
||||
# up properly later on when the reactor shuts down for whatever reason
|
||||
def init_proto(proto):
|
||||
self._connect_successes += 1
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', cleanup, proto)
|
||||
return proto
|
||||
|
||||
use_service = False
|
||||
if auto_reconnect:
|
||||
try:
|
||||
# since Twisted 16.1.0
|
||||
from twisted.application.internet import ClientService
|
||||
from twisted.application.internet import backoffPolicy
|
||||
use_service = True
|
||||
except ImportError:
|
||||
use_service = False
|
||||
|
||||
if use_service:
|
||||
# this code path is automatically reconnecting ..
|
||||
self.log.debug('using t.a.i.ClientService')
|
||||
|
||||
if (self.max_retries is not None or self.initial_retry_delay is not None or self.max_retry_delay is not None or self.retry_delay_growth is not None or self.retry_delay_jitter is not None):
|
||||
|
||||
if self.max_retry_delay > 0:
|
||||
kwargs = {}
|
||||
|
||||
def _jitter():
|
||||
j = 1 if self.retry_delay_jitter is None else self.retry_delay_jitter
|
||||
return random.random() * j
|
||||
|
||||
for key, val in [('initialDelay', self.initial_retry_delay),
|
||||
('maxDelay', self.max_retry_delay),
|
||||
('factor', self.retry_delay_growth),
|
||||
('jitter', _jitter)]:
|
||||
if val is not None:
|
||||
kwargs[key] = val
|
||||
|
||||
# retry policy that will only try to reconnect if we connected
|
||||
# successfully at least once before (so it fails on host unreachable etc ..)
|
||||
def retry(failed_attempts):
|
||||
if self._connect_successes > 0 and (self.max_retries == -1 or failed_attempts < self.max_retries):
|
||||
return backoffPolicy(**kwargs)(failed_attempts)
|
||||
else:
|
||||
print('hit stop')
|
||||
self.stop()
|
||||
return 100000000000000
|
||||
else:
|
||||
# immediately reconnect (zero delay)
|
||||
def retry(_):
|
||||
return 0
|
||||
else:
|
||||
retry = backoffPolicy()
|
||||
|
||||
# https://twistedmatrix.com/documents/current/api/twisted.application.internet.ClientService.html
|
||||
self._client_service = ClientService(client, transport_factory, retryPolicy=retry)
|
||||
self._client_service.startService()
|
||||
|
||||
d = self._client_service.whenConnected()
|
||||
|
||||
else:
|
||||
# this code path is only connecting once!
|
||||
self.log.debug('using t.i.e.connect()')
|
||||
|
||||
d = client.connect(transport_factory)
|
||||
|
||||
# if we connect successfully, the arg is a WampWebSocketClientProtocol
|
||||
d.addCallback(init_proto)
|
||||
|
||||
# if the user didn't ask us to start the reactor, then they
|
||||
# get to deal with any connect errors themselves.
|
||||
if start_reactor:
|
||||
# if an error happens in the connect(), we save the underlying
|
||||
# exception so that after the event-loop exits we can re-raise
|
||||
# it to the caller.
|
||||
|
||||
class ErrorCollector(object):
|
||||
exception = None
|
||||
|
||||
def __call__(self, failure):
|
||||
self.exception = failure.value
|
||||
reactor.stop()
|
||||
connect_error = ErrorCollector()
|
||||
d.addErrback(connect_error)
|
||||
|
||||
# now enter the Twisted reactor loop
|
||||
reactor.run()
|
||||
|
||||
# if the ApplicationSession sets an "error" key on the self.config.extra dictionary, which
|
||||
# has been set to the self.extra dictionary, extract the Exception from that and re-raise
|
||||
# it as the very last one (see below) exciting back to the caller of self.run()
|
||||
app_error = self.extra.get('error', None)
|
||||
|
||||
# if we exited due to a connection error, raise that to the caller
|
||||
if connect_error.exception:
|
||||
raise connect_error.exception
|
||||
elif app_error:
|
||||
raise app_error
|
||||
|
||||
else:
|
||||
# let the caller handle any errors
|
||||
return d
|
||||
|
||||
|
||||
class _ApplicationSession(ApplicationSession):
|
||||
"""
|
||||
WAMP application session class used internally with :class:`autobahn.twisted.app.Application`.
|
||||
"""
|
||||
|
||||
def __init__(self, config, app):
|
||||
"""
|
||||
|
||||
:param config: The component configuration.
|
||||
:type config: Instance of :class:`autobahn.wamp.types.ComponentConfig`
|
||||
:param app: The application this session is for.
|
||||
:type app: Instance of :class:`autobahn.twisted.wamp.Application`.
|
||||
"""
|
||||
# noinspection PyArgumentList
|
||||
ApplicationSession.__init__(self, config)
|
||||
self.app = app
|
||||
|
||||
@inlineCallbacks
|
||||
def onConnect(self):
|
||||
"""
|
||||
Implements :meth:`autobahn.wamp.interfaces.ISession.onConnect`
|
||||
"""
|
||||
yield self.app._fire_signal('onconnect')
|
||||
self.join(self.config.realm)
|
||||
|
||||
@inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
"""
|
||||
Implements :meth:`autobahn.wamp.interfaces.ISession.onJoin`
|
||||
"""
|
||||
for uri, proc in self.app._procs:
|
||||
yield self.register(proc, uri)
|
||||
|
||||
for uri, handler in self.app._handlers:
|
||||
yield self.subscribe(handler, uri)
|
||||
|
||||
yield self.app._fire_signal('onjoined')
|
||||
|
||||
@inlineCallbacks
|
||||
def onLeave(self, details):
|
||||
"""
|
||||
Implements :meth:`autobahn.wamp.interfaces.ISession.onLeave`
|
||||
"""
|
||||
yield self.app._fire_signal('onleave')
|
||||
self.disconnect()
|
||||
|
||||
@inlineCallbacks
|
||||
def onDisconnect(self):
|
||||
"""
|
||||
Implements :meth:`autobahn.wamp.interfaces.ISession.onDisconnect`
|
||||
"""
|
||||
yield self.app._fire_signal('ondisconnect')
|
||||
|
||||
|
||||
class Application(object):
|
||||
"""
|
||||
A WAMP application. The application object provides a simple way of
|
||||
creating, debugging and running WAMP application components.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self, prefix=None):
|
||||
"""
|
||||
|
||||
:param prefix: The application URI prefix to use for procedures and topics,
|
||||
e.g. ``"com.example.myapp"``.
|
||||
:type prefix: unicode
|
||||
"""
|
||||
self._prefix = prefix
|
||||
|
||||
# procedures to be registered once the app session has joined the router/realm
|
||||
self._procs = []
|
||||
|
||||
# event handler to be subscribed once the app session has joined the router/realm
|
||||
self._handlers = []
|
||||
|
||||
# app lifecycle signal handlers
|
||||
self._signals = {}
|
||||
|
||||
# once an app session is connected, this will be here
|
||||
self.session = None
|
||||
|
||||
def __call__(self, config):
|
||||
"""
|
||||
Factory creating a WAMP application session for the application.
|
||||
|
||||
:param config: Component configuration.
|
||||
:type config: Instance of :class:`autobahn.wamp.types.ComponentConfig`
|
||||
|
||||
:returns: obj -- An object that derives of
|
||||
:class:`autobahn.twisted.wamp.ApplicationSession`
|
||||
"""
|
||||
assert(self.session is None)
|
||||
self.session = _ApplicationSession(config, self)
|
||||
return self.session
|
||||
|
||||
def run(self, url="ws://localhost:8080/ws", realm="realm1", start_reactor=True):
|
||||
"""
|
||||
Run the application.
|
||||
|
||||
:param url: The URL of the WAMP router to connect to.
|
||||
:type url: unicode
|
||||
:param realm: The realm on the WAMP router to join.
|
||||
:type realm: unicode
|
||||
"""
|
||||
runner = ApplicationRunner(url, realm)
|
||||
return runner.run(self.__call__, start_reactor)
|
||||
|
||||
def register(self, uri=None):
|
||||
"""
|
||||
Decorator exposing a function as a remote callable procedure.
|
||||
|
||||
The first argument of the decorator should be the URI of the procedure
|
||||
to register under.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.register('com.myapp.add2')
|
||||
def add2(a, b):
|
||||
return a + b
|
||||
|
||||
Above function can then be called remotely over WAMP using the URI `com.myapp.add2`
|
||||
the function was registered under.
|
||||
|
||||
If no URI is given, the URI is constructed from the application URI prefix
|
||||
and the Python function name.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Application('com.myapp')
|
||||
|
||||
# implicit URI will be 'com.myapp.add2'
|
||||
@app.register()
|
||||
def add2(a, b):
|
||||
return a + b
|
||||
|
||||
If the function `yields` (is a co-routine), the `@inlineCallbacks` decorator
|
||||
will be applied automatically to it. In that case, if you wish to return something,
|
||||
you should use `returnValue`:
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from twisted.internet.defer import returnValue
|
||||
|
||||
@app.register('com.myapp.add2')
|
||||
def add2(a, b):
|
||||
res = yield stuff(a, b)
|
||||
returnValue(res)
|
||||
|
||||
:param uri: The URI of the procedure to register under.
|
||||
:type uri: unicode
|
||||
"""
|
||||
def decorator(func):
|
||||
if uri:
|
||||
_uri = uri
|
||||
else:
|
||||
assert(self._prefix is not None)
|
||||
_uri = "{0}.{1}".format(self._prefix, func.__name__)
|
||||
|
||||
if inspect.isgeneratorfunction(func):
|
||||
func = inlineCallbacks(func)
|
||||
|
||||
self._procs.append((_uri, func))
|
||||
return func
|
||||
return decorator
|
||||
|
||||
def subscribe(self, uri=None):
|
||||
"""
|
||||
Decorator attaching a function as an event handler.
|
||||
|
||||
The first argument of the decorator should be the URI of the topic
|
||||
to subscribe to. If no URI is given, the URI is constructed from
|
||||
the application URI prefix and the Python function name.
|
||||
|
||||
If the function yield, it will be assumed that it's an asynchronous
|
||||
process and inlineCallbacks will be applied to it.
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.subscribe('com.myapp.topic1')
|
||||
def onevent1(x, y):
|
||||
print("got event on topic1", x, y)
|
||||
|
||||
:param uri: The URI of the topic to subscribe to.
|
||||
:type uri: unicode
|
||||
"""
|
||||
def decorator(func):
|
||||
if uri:
|
||||
_uri = uri
|
||||
else:
|
||||
assert(self._prefix is not None)
|
||||
_uri = "{0}.{1}".format(self._prefix, func.__name__)
|
||||
|
||||
if inspect.isgeneratorfunction(func):
|
||||
func = inlineCallbacks(func)
|
||||
|
||||
self._handlers.append((_uri, func))
|
||||
return func
|
||||
return decorator
|
||||
|
||||
def signal(self, name):
|
||||
"""
|
||||
Decorator attaching a function as handler for application signals.
|
||||
|
||||
Signals are local events triggered internally and exposed to the
|
||||
developer to be able to react to the application lifecycle.
|
||||
|
||||
If the function yield, it will be assumed that it's an asynchronous
|
||||
coroutine and inlineCallbacks will be applied to it.
|
||||
|
||||
Current signals :
|
||||
|
||||
- `onjoined`: Triggered after the application session has joined the
|
||||
realm on the router and registered/subscribed all procedures
|
||||
and event handlers that were setup via decorators.
|
||||
- `onleave`: Triggered when the application session leaves the realm.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.signal('onjoined')
|
||||
def _():
|
||||
# do after the app has join a realm
|
||||
|
||||
:param name: The name of the signal to watch.
|
||||
:type name: unicode
|
||||
"""
|
||||
def decorator(func):
|
||||
if inspect.isgeneratorfunction(func):
|
||||
func = inlineCallbacks(func)
|
||||
self._signals.setdefault(name, []).append(func)
|
||||
return func
|
||||
return decorator
|
||||
|
||||
@inlineCallbacks
|
||||
def _fire_signal(self, name, *args, **kwargs):
|
||||
"""
|
||||
Utility method to call all signal handlers for a given signal.
|
||||
|
||||
:param name: The signal name.
|
||||
:type name: str
|
||||
"""
|
||||
for handler in self._signals.get(name, []):
|
||||
try:
|
||||
# FIXME: what if the signal handler is not a coroutine?
|
||||
# Why run signal handlers synchronously?
|
||||
yield handler(*args, **kwargs)
|
||||
except Exception as e:
|
||||
# FIXME
|
||||
self.log.info("Warning: exception in signal handler swallowed: {err}", err=e)
|
||||
|
||||
|
||||
class Service(service.MultiService):
|
||||
"""
|
||||
A WAMP application as a twisted service.
|
||||
The application object provides a simple way of creating, debugging and running WAMP application
|
||||
components inside a traditional twisted application
|
||||
|
||||
This manages application lifecycle of the wamp connection using startService and stopService
|
||||
Using services also allows to create integration tests that properly terminates their connections
|
||||
|
||||
It can host a WAMP application component in a WAMP-over-WebSocket client
|
||||
connecting to a WAMP router.
|
||||
"""
|
||||
factory = WampWebSocketClientFactory
|
||||
|
||||
def __init__(self, url, realm, make, extra=None, context_factory=None):
|
||||
"""
|
||||
|
||||
:param url: The WebSocket URL of the WAMP router to connect to (e.g. `ws://somehost.com:8090/somepath`)
|
||||
:type url: unicode
|
||||
|
||||
:param realm: The WAMP realm to join the application session to.
|
||||
:type realm: unicode
|
||||
|
||||
:param make: A factory that produces instances of :class:`autobahn.asyncio.wamp.ApplicationSession`
|
||||
when called with an instance of :class:`autobahn.wamp.types.ComponentConfig`.
|
||||
:type make: callable
|
||||
|
||||
:param extra: Optional extra configuration to forward to the application component.
|
||||
:type extra: dict
|
||||
|
||||
:param context_factory: optional, only for secure connections. Passed as contextFactory to
|
||||
the ``listenSSL()`` call; see https://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IReactorSSL.connectSSL.html
|
||||
:type context_factory: twisted.internet.ssl.ClientContextFactory or None
|
||||
|
||||
You can replace the attribute factory in order to change connectionLost or connectionFailed behaviour.
|
||||
The factory attribute must return a WampWebSocketClientFactory object
|
||||
"""
|
||||
self.url = url
|
||||
self.realm = realm
|
||||
self.extra = extra or dict()
|
||||
self.make = make
|
||||
self.context_factory = context_factory
|
||||
service.MultiService.__init__(self)
|
||||
self.setupService()
|
||||
|
||||
def setupService(self):
|
||||
"""
|
||||
Setup the application component.
|
||||
"""
|
||||
is_secure, host, port, resource, path, params = parse_ws_url(self.url)
|
||||
|
||||
# factory for use ApplicationSession
|
||||
def create():
|
||||
cfg = ComponentConfig(self.realm, self.extra)
|
||||
session = self.make(cfg)
|
||||
return session
|
||||
|
||||
# create a WAMP-over-WebSocket transport client factory
|
||||
transport_factory = self.factory(create, url=self.url)
|
||||
|
||||
# setup the client from a Twisted endpoint
|
||||
|
||||
if is_secure:
|
||||
from twisted.application.internet import SSLClient
|
||||
ctx = self.context_factory
|
||||
if ctx is None:
|
||||
from twisted.internet.ssl import optionsForClientTLS
|
||||
ctx = optionsForClientTLS(host)
|
||||
client = SSLClient(host, port, transport_factory, contextFactory=ctx)
|
||||
else:
|
||||
if self.context_factory is not None:
|
||||
raise Exception("context_factory specified on non-secure URI")
|
||||
from twisted.application.internet import TCPClient
|
||||
client = TCPClient(host, port, transport_factory)
|
||||
|
||||
client.setServiceParent(self)
|
||||
|
||||
|
||||
# new API
|
||||
class Session(protocol._SessionShim):
|
||||
# XXX these methods are redundant, but put here for possibly
|
||||
# better clarity; maybe a bad idea.
|
||||
|
||||
def on_welcome(self, welcome_msg):
|
||||
pass
|
||||
|
||||
def on_join(self, details):
|
||||
pass
|
||||
|
||||
def on_leave(self, details):
|
||||
self.disconnect()
|
||||
|
||||
def on_connect(self):
|
||||
self.join(self.config.realm)
|
||||
|
||||
def on_disconnect(self):
|
||||
pass
|
||||
|
||||
|
||||
# experimental authentication API
|
||||
class AuthCryptoSign(object):
|
||||
|
||||
def __init__(self, **kw):
|
||||
# should put in checkconfig or similar
|
||||
for key in kw.keys():
|
||||
if key not in ['authextra', 'authid', 'authrole', 'privkey']:
|
||||
raise ValueError(
|
||||
"Unexpected key '{}' for {}".format(key, self.__class__.__name__)
|
||||
)
|
||||
for key in ['privkey']:
|
||||
if key not in kw:
|
||||
raise ValueError(
|
||||
"Must provide '{}' for cryptosign".format(key)
|
||||
)
|
||||
for key in kw.get('authextra', dict()):
|
||||
if key not in ['pubkey', 'channel_binding', 'trustroot', 'challenge']:
|
||||
raise ValueError(
|
||||
"Unexpected key '{}' in 'authextra'".format(key)
|
||||
)
|
||||
|
||||
from autobahn.wamp.cryptosign import CryptosignKey
|
||||
self._privkey = CryptosignKey.from_bytes(
|
||||
binascii.a2b_hex(kw['privkey'])
|
||||
)
|
||||
|
||||
if 'pubkey' in kw.get('authextra', dict()):
|
||||
pubkey = kw['authextra']['pubkey']
|
||||
if pubkey != self._privkey.public_key():
|
||||
raise ValueError(
|
||||
"Public key doesn't correspond to private key"
|
||||
)
|
||||
else:
|
||||
kw['authextra'] = kw.get('authextra', dict())
|
||||
kw['authextra']['pubkey'] = self._privkey.public_key()
|
||||
self._args = kw
|
||||
|
||||
def on_challenge(self, session, challenge):
|
||||
# sign the challenge with our private key.
|
||||
channel_id_type = self._args['authextra'].get('channel_binding', None)
|
||||
channel_id = self.transport.transport_details.channel_id.get(channel_id_type, None)
|
||||
signed_challenge = self._privkey.sign_challenge(challenge, channel_id=channel_id,
|
||||
channel_id_type=channel_id_type)
|
||||
return signed_challenge
|
||||
|
||||
|
||||
IAuthenticator.register(AuthCryptoSign)
|
||||
|
||||
|
||||
class AuthWampCra(object):
|
||||
|
||||
def __init__(self, **kw):
|
||||
# should put in checkconfig or similar
|
||||
for key in kw.keys():
|
||||
if key not in ['authextra', 'authid', 'authrole', 'secret']:
|
||||
raise ValueError(
|
||||
"Unexpected key '{}' for {}".format(key, self.__class__.__name__)
|
||||
)
|
||||
for key in ['secret', 'authid']:
|
||||
if key not in kw:
|
||||
raise ValueError(
|
||||
"Must provide '{}' for wampcra".format(key)
|
||||
)
|
||||
|
||||
self._args = kw
|
||||
self._secret = kw.pop('secret')
|
||||
if not isinstance(self._secret, str):
|
||||
self._secret = self._secret.decode('utf8')
|
||||
|
||||
def on_challenge(self, session, challenge):
|
||||
key = self._secret.encode('utf8')
|
||||
if 'salt' in challenge.extra:
|
||||
key = auth.derive_key(
|
||||
key,
|
||||
challenge.extra['salt'],
|
||||
challenge.extra['iterations'],
|
||||
challenge.extra['keylen']
|
||||
)
|
||||
|
||||
signature = auth.compute_wcs(
|
||||
key,
|
||||
challenge.extra['challenge'].encode('utf8')
|
||||
)
|
||||
return signature.decode('ascii')
|
||||
|
||||
|
||||
IAuthenticator.register(AuthWampCra)
|
||||
907
.venv/lib/python3.12/site-packages/autobahn/twisted/websocket.py
Normal file
907
.venv/lib/python3.12/site-packages/autobahn/twisted/websocket.py
Normal file
@@ -0,0 +1,907 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 base64 import b64encode, b64decode
|
||||
from typing import Optional
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
import txaio
|
||||
txaio.use_twisted()
|
||||
|
||||
import twisted.internet.protocol
|
||||
from twisted.internet import endpoints
|
||||
from twisted.internet.interfaces import ITransport
|
||||
|
||||
from twisted.internet.error import ConnectionDone, ConnectionAborted, \
|
||||
ConnectionLost
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.internet.protocol import connectionDone
|
||||
|
||||
from autobahn.util import public, hltype, hlval
|
||||
from autobahn.util import _is_tls_error, _maybe_tls_reason
|
||||
from autobahn.wamp import websocket
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
from autobahn.websocket.types import ConnectionRequest, ConnectionResponse, ConnectionDeny
|
||||
from autobahn.websocket import protocol
|
||||
from autobahn.websocket.interfaces import IWebSocketClientAgent
|
||||
from autobahn.twisted.util import create_transport_details, transport_channel_id
|
||||
|
||||
from autobahn.websocket.compress import PerMessageDeflateOffer, \
|
||||
PerMessageDeflateOfferAccept, \
|
||||
PerMessageDeflateResponse, \
|
||||
PerMessageDeflateResponseAccept
|
||||
|
||||
|
||||
__all__ = (
|
||||
'create_client_agent',
|
||||
|
||||
'WebSocketAdapterProtocol',
|
||||
'WebSocketServerProtocol',
|
||||
'WebSocketClientProtocol',
|
||||
'WebSocketAdapterFactory',
|
||||
'WebSocketServerFactory',
|
||||
'WebSocketClientFactory',
|
||||
|
||||
'WrappingWebSocketAdapter',
|
||||
'WrappingWebSocketServerProtocol',
|
||||
'WrappingWebSocketClientProtocol',
|
||||
'WrappingWebSocketServerFactory',
|
||||
'WrappingWebSocketClientFactory',
|
||||
|
||||
'listenWS',
|
||||
'connectWS',
|
||||
|
||||
'WampWebSocketServerProtocol',
|
||||
'WampWebSocketServerFactory',
|
||||
'WampWebSocketClientProtocol',
|
||||
'WampWebSocketClientFactory',
|
||||
)
|
||||
|
||||
|
||||
def create_client_agent(reactor):
|
||||
"""
|
||||
:returns: an instance implementing IWebSocketClientAgent
|
||||
"""
|
||||
return _TwistedWebSocketClientAgent(reactor)
|
||||
|
||||
|
||||
def check_transport_config(transport_config):
|
||||
"""
|
||||
raises a ValueError if `transport_config` is invalid
|
||||
"""
|
||||
# XXX move me to "autobahn.websocket.util"
|
||||
if not isinstance(transport_config, str):
|
||||
raise ValueError(
|
||||
"'transport_config' must be a string, found {}".format(type(transport_config))
|
||||
)
|
||||
# XXX also accept everything Crossbar has in client transport configs? e.g like:
|
||||
# { "type": "websocket", "endpoint": {"type": "tcp", "host": "example.com", ...}}
|
||||
# XXX what about TLS options? (the above point would address that too)
|
||||
if not transport_config.startswith("ws://") and \
|
||||
not transport_config.startswith("wss://"):
|
||||
raise ValueError(
|
||||
"'transport_config' must start with 'ws://' or 'wss://'"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def check_client_options(options):
|
||||
"""
|
||||
raises a ValueError if `options` is invalid
|
||||
"""
|
||||
# XXX move me to "autobahn.websocket.util"
|
||||
if not isinstance(options, dict):
|
||||
raise ValueError(
|
||||
"'options' must be a dict"
|
||||
)
|
||||
|
||||
# anything that WebSocketClientFactory accepts (at least)
|
||||
valid_keys = [
|
||||
"origin",
|
||||
"protocols",
|
||||
"useragent",
|
||||
"headers",
|
||||
"proxy",
|
||||
]
|
||||
for actual_k in options.keys():
|
||||
if actual_k not in valid_keys:
|
||||
raise ValueError(
|
||||
"'options' may not contain '{}'".format(actual_k)
|
||||
)
|
||||
|
||||
|
||||
def _endpoint_from_config(reactor, factory, transport_config, options):
|
||||
# XXX might want some Crossbar code here? e.g. if we allow
|
||||
# "transport_config" to be a dict etc.
|
||||
|
||||
# ... passing in the Factory is weird, but that's what parses all
|
||||
# the options and the URL currently
|
||||
|
||||
if factory.isSecure:
|
||||
# create default client SSL context factory when none given
|
||||
from twisted.internet import ssl
|
||||
context_factory = ssl.optionsForClientTLS(factory.host)
|
||||
|
||||
if factory.proxy is not None:
|
||||
factory.contextFactory = context_factory
|
||||
endpoint = endpoints.HostnameEndpoint(
|
||||
reactor,
|
||||
factory.proxy['host'],
|
||||
factory.proxy['port'],
|
||||
# timeout, option?
|
||||
)
|
||||
else:
|
||||
if factory.isSecure:
|
||||
from twisted.internet import ssl
|
||||
endpoint = endpoints.SSL4ClientEndpoint(
|
||||
reactor,
|
||||
factory.host,
|
||||
factory.port,
|
||||
context_factory,
|
||||
# timeout, option?
|
||||
)
|
||||
else:
|
||||
endpoint = endpoints.HostnameEndpoint( # XXX right? not TCP4ClientEndpoint
|
||||
reactor,
|
||||
factory.host,
|
||||
factory.port,
|
||||
# timeout, option?
|
||||
# attemptDelay, option?
|
||||
)
|
||||
return endpoint
|
||||
|
||||
|
||||
class _TwistedWebSocketClientAgent(IWebSocketClientAgent):
|
||||
"""
|
||||
This agent creates connections using Twisted
|
||||
"""
|
||||
|
||||
def __init__(self, reactor):
|
||||
self._reactor = reactor
|
||||
|
||||
def open(self, transport_config, options, protocol_class=None):
|
||||
"""
|
||||
Open a new connection.
|
||||
|
||||
:param dict transport_config: valid transport configuration
|
||||
|
||||
:param dict options: additional options for the factory
|
||||
|
||||
:param protocol_class: a callable that returns an instance of
|
||||
the protocol (WebSocketClientProtocol if the default None
|
||||
is passed in)
|
||||
|
||||
:returns: a Deferred that fires with an instance of
|
||||
`protocol_class` (or WebSocketClientProtocol by default)
|
||||
that has successfully shaken hands (completed the
|
||||
handshake).
|
||||
"""
|
||||
check_transport_config(transport_config)
|
||||
check_client_options(options)
|
||||
|
||||
factory = WebSocketClientFactory(
|
||||
url=transport_config,
|
||||
reactor=self._reactor,
|
||||
**options
|
||||
)
|
||||
factory.protocol = WebSocketClientProtocol if protocol_class is None else protocol_class
|
||||
# XXX might want "contextFactory" for TLS ...? (or e.g. CA etc options?)
|
||||
|
||||
endpoint = _endpoint_from_config(self._reactor, factory, transport_config, options)
|
||||
|
||||
rtn_d = Deferred()
|
||||
proto_d = endpoint.connect(factory)
|
||||
|
||||
def failed(f):
|
||||
rtn_d.errback(f)
|
||||
|
||||
def got_proto(proto):
|
||||
|
||||
def handshake_completed(arg):
|
||||
rtn_d.callback(proto)
|
||||
return arg
|
||||
proto.is_open.addCallbacks(handshake_completed, failed)
|
||||
return proto
|
||||
proto_d.addCallbacks(got_proto, failed)
|
||||
return rtn_d
|
||||
|
||||
|
||||
class WebSocketAdapterProtocol(twisted.internet.protocol.Protocol):
|
||||
"""
|
||||
Adapter class for Twisted WebSocket client and server protocols.
|
||||
|
||||
Called from Twisted:
|
||||
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.connectionMade`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.connectionLost`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.dataReceived`
|
||||
|
||||
Called from Network-independent Code (WebSocket implementation):
|
||||
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onOpen`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessageBegin`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessageFrameData`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessageFrameEnd`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessageEnd`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onMessage`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onPing`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onPong`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._onClose`
|
||||
|
||||
FIXME:
|
||||
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._closeConnection`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol._create_transport_details`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.registerProducer`
|
||||
* :meth:`autobahn.twisted.websocket.WebSocketAdapterProtocol.unregisterProducer`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
peer: Optional[str] = None
|
||||
is_server: Optional[bool] = None
|
||||
|
||||
def connectionMade(self):
|
||||
# Twisted networking framework entry point, called by Twisted
|
||||
# when the connection is established (either a client or a server)
|
||||
|
||||
# determine preliminary transport details (what is know at this point)
|
||||
self._transport_details = create_transport_details(self.transport, self.is_server)
|
||||
self._transport_details.channel_framing = TransportDetails.CHANNEL_FRAMING_WEBSOCKET
|
||||
|
||||
# backward compatibility
|
||||
self.peer = self._transport_details.peer
|
||||
|
||||
# try to set "Nagle" option for TCP sockets
|
||||
try:
|
||||
self.transport.setTcpNoDelay(self.tcpNoDelay)
|
||||
except: # don't touch this! does not work: AttributeError, OSError
|
||||
# eg Unix Domain sockets throw Errno 22 on this
|
||||
pass
|
||||
|
||||
# ok, now forward to the networking framework independent code for websocket
|
||||
self._connectionMade()
|
||||
|
||||
# ok, done!
|
||||
self.log.debug('{func} connection established for peer="{peer}"',
|
||||
func=hltype(self.connectionMade),
|
||||
peer=hlval(self.peer))
|
||||
|
||||
def connectionLost(self, reason: Failure = connectionDone):
|
||||
# Twisted networking framework entry point, called by Twisted
|
||||
# when the connection is lost (either a client or a server)
|
||||
|
||||
was_clean = False
|
||||
if isinstance(reason.value, ConnectionDone):
|
||||
self.log.debug("Connection to/from {peer} was closed cleanly",
|
||||
peer=self.peer)
|
||||
was_clean = True
|
||||
|
||||
elif _is_tls_error(reason.value):
|
||||
self.log.error(_maybe_tls_reason(reason.value))
|
||||
|
||||
elif isinstance(reason.value, ConnectionAborted):
|
||||
self.log.debug("Connection to/from {peer} was aborted locally",
|
||||
peer=self.peer)
|
||||
|
||||
elif isinstance(reason.value, ConnectionLost):
|
||||
message = str(reason.value)
|
||||
if hasattr(reason.value, 'message'):
|
||||
message = reason.value.message
|
||||
self.log.debug(
|
||||
"Connection to/from {peer} was lost in a non-clean fashion: {message}",
|
||||
peer=self.peer,
|
||||
message=message,
|
||||
)
|
||||
|
||||
# at least: FileDescriptorOverrun, ConnectionFdescWentAway - but maybe others as well?
|
||||
else:
|
||||
self.log.debug("Connection to/from {peer} lost ({error_type}): {error})",
|
||||
peer=self.peer, error_type=type(reason.value), error=reason.value)
|
||||
|
||||
# ok, now forward to the networking framework independent code for websocket
|
||||
self._connectionLost(reason)
|
||||
|
||||
# ok, done!
|
||||
if was_clean:
|
||||
self.log.debug('{func} connection lost for peer="{peer}", closed cleanly',
|
||||
func=hltype(self.connectionLost),
|
||||
peer=hlval(self.peer))
|
||||
else:
|
||||
self.log.debug('{func} connection lost for peer="{peer}", closed with error {reason}',
|
||||
func=hltype(self.connectionLost),
|
||||
peer=hlval(self.peer),
|
||||
reason=reason)
|
||||
|
||||
def dataReceived(self, data: bytes):
|
||||
self.log.debug('{func} received {data_len} bytes for peer="{peer}"',
|
||||
func=hltype(self.dataReceived),
|
||||
peer=hlval(self.peer),
|
||||
data_len=hlval(len(data)))
|
||||
|
||||
# bytes received from Twisted, forward to the networking framework independent code for websocket
|
||||
self._dataReceived(data)
|
||||
|
||||
def _closeConnection(self, abort=False):
|
||||
if abort and hasattr(self.transport, 'abortConnection'):
|
||||
self.transport.abortConnection()
|
||||
else:
|
||||
# e.g. ProcessProtocol lacks abortConnection()
|
||||
self.transport.loseConnection()
|
||||
|
||||
def _onOpen(self):
|
||||
if self._transport_details.is_secure:
|
||||
# now that the TLS opening handshake is complete, the actual TLS channel ID
|
||||
# will be available. make sure to set it!
|
||||
channel_id = {
|
||||
'tls-unique': transport_channel_id(self.transport, self._transport_details.is_server, 'tls-unique'),
|
||||
}
|
||||
self._transport_details.channel_id = channel_id
|
||||
|
||||
self.onOpen()
|
||||
|
||||
def _onMessageBegin(self, isBinary):
|
||||
self.onMessageBegin(isBinary)
|
||||
|
||||
def _onMessageFrameBegin(self, length):
|
||||
self.onMessageFrameBegin(length)
|
||||
|
||||
def _onMessageFrameData(self, payload):
|
||||
self.onMessageFrameData(payload)
|
||||
|
||||
def _onMessageFrameEnd(self):
|
||||
self.onMessageFrameEnd()
|
||||
|
||||
def _onMessageFrame(self, payload):
|
||||
self.onMessageFrame(payload)
|
||||
|
||||
def _onMessageEnd(self):
|
||||
self.onMessageEnd()
|
||||
|
||||
def _onMessage(self, payload, isBinary):
|
||||
self.onMessage(payload, isBinary)
|
||||
|
||||
def _onPing(self, payload):
|
||||
self.onPing(payload)
|
||||
|
||||
def _onPong(self, payload):
|
||||
self.onPong(payload)
|
||||
|
||||
def _onClose(self, wasClean, code, reason):
|
||||
self.onClose(wasClean, code, reason)
|
||||
|
||||
def registerProducer(self, producer, streaming):
|
||||
"""
|
||||
Register a Twisted producer with this protocol.
|
||||
|
||||
:param producer: A Twisted push or pull producer.
|
||||
:type producer: object
|
||||
:param streaming: Producer type.
|
||||
:type streaming: bool
|
||||
"""
|
||||
self.transport.registerProducer(producer, streaming)
|
||||
|
||||
def unregisterProducer(self):
|
||||
"""
|
||||
Unregister Twisted producer with this protocol.
|
||||
"""
|
||||
self.transport.unregisterProducer()
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServerProtocol):
|
||||
"""
|
||||
Base class for Twisted-based WebSocket server protocols.
|
||||
|
||||
Implements :class:`autobahn.websocket.interfaces.IWebSocketChannel`.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
is_server = True
|
||||
|
||||
# def onConnect(self, request: ConnectionRequest) -> Union[Optional[str], Tuple[Optional[str], Dict[str, str]]]:
|
||||
# pass
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketClientProtocol(WebSocketAdapterProtocol, protocol.WebSocketClientProtocol):
|
||||
"""
|
||||
Base class for Twisted-based WebSocket client protocols.
|
||||
|
||||
Implements :class:`autobahn.websocket.interfaces.IWebSocketChannel`.
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
is_server = False
|
||||
|
||||
def _onConnect(self, response: ConnectionResponse):
|
||||
self.log.debug('{meth}(response={response})', meth=hltype(self._onConnect), response=response)
|
||||
return self.onConnect(response)
|
||||
|
||||
def startTLS(self):
|
||||
self.log.debug("Starting TLS upgrade")
|
||||
self.transport.startTLS(self.factory.contextFactory)
|
||||
|
||||
|
||||
class WebSocketAdapterFactory(object):
|
||||
"""
|
||||
Adapter class for Twisted-based WebSocket client and server factories.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory, twisted.internet.protocol.ServerFactory):
|
||||
"""
|
||||
Base class for Twisted-based WebSocket server factories.
|
||||
|
||||
Implements :class:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
|
||||
.. note::
|
||||
In addition to all arguments to the constructor of
|
||||
:meth:`autobahn.websocket.interfaces.IWebSocketServerChannelFactory`,
|
||||
you can supply a ``reactor`` keyword argument to specify the
|
||||
Twisted reactor to be used.
|
||||
"""
|
||||
# lazy import to avoid reactor install upon module import
|
||||
reactor = kwargs.pop('reactor', None)
|
||||
if reactor is None:
|
||||
from twisted.internet import reactor
|
||||
self.reactor = reactor
|
||||
|
||||
protocol.WebSocketServerFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
@public
|
||||
class WebSocketClientFactory(WebSocketAdapterFactory, protocol.WebSocketClientFactory, twisted.internet.protocol.ClientFactory):
|
||||
"""
|
||||
Base class for Twisted-based WebSocket client factories.
|
||||
|
||||
Implements :class:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`
|
||||
"""
|
||||
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
|
||||
.. note::
|
||||
In addition to all arguments to the constructor of
|
||||
:func:`autobahn.websocket.interfaces.IWebSocketClientChannelFactory`,
|
||||
you can supply a ``reactor`` keyword argument to specify the
|
||||
Twisted reactor to be used.
|
||||
"""
|
||||
# lazy import to avoid reactor install upon module import
|
||||
reactor = kwargs.pop('reactor', None)
|
||||
if reactor is None:
|
||||
from twisted.internet import reactor
|
||||
self.reactor = reactor
|
||||
|
||||
protocol.WebSocketClientFactory.__init__(self, *args, **kwargs)
|
||||
# we must up-call *before* we set up the contextFactory
|
||||
# because we need self.host etc to be set properly.
|
||||
if self.isSecure and self.proxy is not None:
|
||||
# if we have a proxy, then our factory will be used to
|
||||
# create the connection after CONNECT and if it's doing
|
||||
# TLS it needs a contextFactory
|
||||
from twisted.internet import ssl
|
||||
self.contextFactory = ssl.optionsForClientTLS(self.host)
|
||||
# NOTE: there's thus no way to send in our own
|
||||
# context-factory, nor any TLS options.
|
||||
|
||||
# Possibly we should allow 'proxy' to contain an actual
|
||||
# IStreamClientEndpoint instance instead of configuration for
|
||||
# how to make one
|
||||
|
||||
|
||||
@implementer(ITransport)
|
||||
class WrappingWebSocketAdapter(object):
|
||||
"""
|
||||
An adapter for stream-based transport over WebSocket.
|
||||
|
||||
This follows `websockify <https://github.com/kanaka/websockify>`_
|
||||
and should be compatible with that.
|
||||
|
||||
It uses WebSocket subprotocol negotiation and supports the
|
||||
following WebSocket subprotocols:
|
||||
|
||||
- ``binary`` (or a compatible subprotocol)
|
||||
- ``base64``
|
||||
|
||||
Octets are either transmitted as the payload of WebSocket binary
|
||||
messages when using the ``binary`` subprotocol (or an alternative
|
||||
binary compatible subprotocol), or encoded with Base64 and then
|
||||
transmitted as the payload of WebSocket text messages when using
|
||||
the ``base64`` subprotocol.
|
||||
"""
|
||||
|
||||
def onConnect(self, requestOrResponse):
|
||||
# Negotiate either the 'binary' or the 'base64' WebSocket subprotocol
|
||||
if isinstance(requestOrResponse, ConnectionRequest):
|
||||
request = requestOrResponse
|
||||
for p in request.protocols:
|
||||
if p in self.factory._subprotocols:
|
||||
self._binaryMode = (p != 'base64')
|
||||
return p
|
||||
raise ConnectionDeny(ConnectionDeny.NOT_ACCEPTABLE, 'this server only speaks {0} WebSocket subprotocols'.format(self.factory._subprotocols))
|
||||
elif isinstance(requestOrResponse, ConnectionResponse):
|
||||
response = requestOrResponse
|
||||
if response.protocol not in self.factory._subprotocols:
|
||||
self._fail_connection(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, 'this client only speaks {0} WebSocket subprotocols'.format(self.factory._subprotocols))
|
||||
self._binaryMode = (response.protocol != 'base64')
|
||||
else:
|
||||
# should not arrive here
|
||||
raise Exception("logic error")
|
||||
|
||||
def onOpen(self):
|
||||
self._proto.connectionMade()
|
||||
|
||||
def onMessage(self, payload, isBinary):
|
||||
if isBinary != self._binaryMode:
|
||||
self._fail_connection(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_UNSUPPORTED_DATA, 'message payload type does not match the negotiated subprotocol')
|
||||
else:
|
||||
if not isBinary:
|
||||
try:
|
||||
payload = b64decode(payload)
|
||||
except Exception as e:
|
||||
self._fail_connection(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INVALID_PAYLOAD, 'message payload base64 decoding error: {0}'.format(e))
|
||||
self._proto.dataReceived(payload)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def onClose(self, wasClean, code, reason):
|
||||
self._proto.connectionLost(None)
|
||||
|
||||
def write(self, data):
|
||||
# part of ITransport
|
||||
assert(type(data) == bytes)
|
||||
if self._binaryMode:
|
||||
self.sendMessage(data, isBinary=True)
|
||||
else:
|
||||
data = b64encode(data)
|
||||
self.sendMessage(data, isBinary=False)
|
||||
|
||||
def writeSequence(self, data):
|
||||
# part of ITransport
|
||||
for d in data:
|
||||
self.write(d)
|
||||
|
||||
def loseConnection(self):
|
||||
# part of ITransport
|
||||
self.sendClose()
|
||||
|
||||
def getPeer(self):
|
||||
# part of ITransport
|
||||
return self.transport.getPeer()
|
||||
|
||||
def getHost(self):
|
||||
# part of ITransport
|
||||
return self.transport.getHost()
|
||||
|
||||
|
||||
class WrappingWebSocketServerProtocol(WrappingWebSocketAdapter, WebSocketServerProtocol):
|
||||
"""
|
||||
Server protocol for stream-based transport over WebSocket.
|
||||
"""
|
||||
|
||||
|
||||
class WrappingWebSocketClientProtocol(WrappingWebSocketAdapter, WebSocketClientProtocol):
|
||||
"""
|
||||
Client protocol for stream-based transport over WebSocket.
|
||||
"""
|
||||
|
||||
|
||||
class WrappingWebSocketServerFactory(WebSocketServerFactory):
|
||||
"""
|
||||
Wrapping server factory for stream-based transport over WebSocket.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
factory,
|
||||
url,
|
||||
reactor=None,
|
||||
enableCompression=True,
|
||||
autoFragmentSize=0,
|
||||
subprotocol=None):
|
||||
"""
|
||||
|
||||
:param factory: Stream-based factory to be wrapped.
|
||||
:type factory: A subclass of ``twisted.internet.protocol.Factory``
|
||||
:param url: WebSocket URL of the server this server factory will work for.
|
||||
:type url: unicode
|
||||
"""
|
||||
self._factory = factory
|
||||
self._subprotocols = ['binary', 'base64']
|
||||
if subprotocol:
|
||||
self._subprotocols.append(subprotocol)
|
||||
|
||||
WebSocketServerFactory.__init__(self,
|
||||
url=url,
|
||||
reactor=reactor,
|
||||
protocols=self._subprotocols)
|
||||
|
||||
# automatically fragment outgoing traffic into WebSocket frames
|
||||
# of this size
|
||||
self.setProtocolOptions(autoFragmentSize=autoFragmentSize)
|
||||
|
||||
# play nice and perform WS closing handshake
|
||||
self.setProtocolOptions(failByDrop=False)
|
||||
|
||||
if enableCompression:
|
||||
# Enable WebSocket extension "permessage-deflate".
|
||||
|
||||
# Function to accept offers from the client ..
|
||||
def accept(offers):
|
||||
for offer in offers:
|
||||
if isinstance(offer, PerMessageDeflateOffer):
|
||||
return PerMessageDeflateOfferAccept(offer)
|
||||
|
||||
self.setProtocolOptions(perMessageCompressionAccept=accept)
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
proto = WrappingWebSocketServerProtocol()
|
||||
proto.factory = self
|
||||
proto._proto = self._factory.buildProtocol(addr)
|
||||
proto._proto.transport = proto
|
||||
return proto
|
||||
|
||||
def startFactory(self):
|
||||
self._factory.startFactory()
|
||||
WebSocketServerFactory.startFactory(self)
|
||||
|
||||
def stopFactory(self):
|
||||
self._factory.stopFactory()
|
||||
WebSocketServerFactory.stopFactory(self)
|
||||
|
||||
|
||||
class WrappingWebSocketClientFactory(WebSocketClientFactory):
|
||||
"""
|
||||
Wrapping client factory for stream-based transport over WebSocket.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
factory,
|
||||
url,
|
||||
reactor=None,
|
||||
enableCompression=True,
|
||||
autoFragmentSize=0,
|
||||
subprotocol=None):
|
||||
"""
|
||||
|
||||
:param factory: Stream-based factory to be wrapped.
|
||||
:type factory: A subclass of ``twisted.internet.protocol.Factory``
|
||||
:param url: WebSocket URL of the server this client factory will connect to.
|
||||
:type url: unicode
|
||||
"""
|
||||
self._factory = factory
|
||||
self._subprotocols = ['binary', 'base64']
|
||||
if subprotocol:
|
||||
self._subprotocols.append(subprotocol)
|
||||
|
||||
WebSocketClientFactory.__init__(self,
|
||||
url=url,
|
||||
reactor=reactor,
|
||||
protocols=self._subprotocols)
|
||||
|
||||
# automatically fragment outgoing traffic into WebSocket frames
|
||||
# of this size
|
||||
self.setProtocolOptions(autoFragmentSize=autoFragmentSize)
|
||||
|
||||
# play nice and perform WS closing handshake
|
||||
self.setProtocolOptions(failByDrop=False)
|
||||
|
||||
if enableCompression:
|
||||
# Enable WebSocket extension "permessage-deflate".
|
||||
|
||||
# The extensions offered to the server ..
|
||||
offers = [PerMessageDeflateOffer()]
|
||||
self.setProtocolOptions(perMessageCompressionOffers=offers)
|
||||
|
||||
# Function to accept responses from the server ..
|
||||
def accept(response):
|
||||
if isinstance(response, PerMessageDeflateResponse):
|
||||
return PerMessageDeflateResponseAccept(response)
|
||||
|
||||
self.setProtocolOptions(perMessageCompressionAccept=accept)
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
proto = WrappingWebSocketClientProtocol()
|
||||
proto.factory = self
|
||||
proto._proto = self._factory.buildProtocol(addr)
|
||||
proto._proto.transport = proto
|
||||
return proto
|
||||
|
||||
|
||||
@public
|
||||
def connectWS(factory, contextFactory=None, timeout=30, bindAddress=None):
|
||||
"""
|
||||
Establish WebSocket connection to a server. The connection parameters like target
|
||||
host, port, resource and others are provided via the factory.
|
||||
|
||||
:param factory: The WebSocket protocol factory to be used for creating client protocol instances.
|
||||
:type factory: An :class:`autobahn.websocket.WebSocketClientFactory` instance.
|
||||
|
||||
:param contextFactory: SSL context factory, required for secure WebSocket connections ("wss").
|
||||
:type contextFactory: A `twisted.internet.ssl.ClientContextFactory <http://twistedmatrix.com/documents/current/api/twisted.internet.ssl.ClientContextFactory.html>`_ instance.
|
||||
|
||||
:param timeout: Number of seconds to wait before assuming the connection has failed.
|
||||
:type timeout: int
|
||||
|
||||
:param bindAddress: A (host, port) tuple of local address to bind to, or None.
|
||||
:type bindAddress: tuple
|
||||
|
||||
:returns: The connector.
|
||||
:rtype: An object which implements `twisted.interface.IConnector <http://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IConnector.html>`_.
|
||||
"""
|
||||
# lazy import to avoid reactor install upon module import
|
||||
if hasattr(factory, 'reactor'):
|
||||
reactor = factory.reactor
|
||||
else:
|
||||
from twisted.internet import reactor
|
||||
|
||||
if factory.isSecure:
|
||||
if contextFactory is None:
|
||||
# create default client SSL context factory when none given
|
||||
from twisted.internet import ssl
|
||||
contextFactory = ssl.ClientContextFactory()
|
||||
|
||||
if factory.proxy is not None:
|
||||
factory.contextFactory = contextFactory
|
||||
conn = reactor.connectTCP(factory.proxy['host'], factory.proxy['port'], factory, timeout, bindAddress)
|
||||
else:
|
||||
if factory.isSecure:
|
||||
conn = reactor.connectSSL(factory.host, factory.port, factory, contextFactory, timeout, bindAddress)
|
||||
else:
|
||||
conn = reactor.connectTCP(factory.host, factory.port, factory, timeout, bindAddress)
|
||||
return conn
|
||||
|
||||
|
||||
@public
|
||||
def listenWS(factory, contextFactory=None, backlog=50, interface=''):
|
||||
"""
|
||||
Listen for incoming WebSocket connections from clients. The connection parameters like
|
||||
listening port and others are provided via the factory.
|
||||
|
||||
:param factory: The WebSocket protocol factory to be used for creating server protocol instances.
|
||||
:type factory: An :class:`autobahn.websocket.WebSocketServerFactory` instance.
|
||||
|
||||
:param contextFactory: SSL context factory, required for secure WebSocket connections ("wss").
|
||||
:type contextFactory: A twisted.internet.ssl.ContextFactory.
|
||||
|
||||
:param backlog: Size of the listen queue.
|
||||
:type backlog: int
|
||||
|
||||
:param interface: The interface (derived from hostname given) to bind to, defaults to '' (all).
|
||||
:type interface: str
|
||||
|
||||
:returns: The listening port.
|
||||
:rtype: An object that implements `twisted.interface.IListeningPort <http://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IListeningPort.html>`_.
|
||||
"""
|
||||
# lazy import to avoid reactor install upon module import
|
||||
if hasattr(factory, 'reactor'):
|
||||
reactor = factory.reactor
|
||||
else:
|
||||
from twisted.internet import reactor
|
||||
|
||||
if factory.isSecure:
|
||||
if contextFactory is None:
|
||||
raise Exception("Secure WebSocket listen requested, but no SSL context factory given")
|
||||
listener = reactor.listenSSL(factory.port, factory, contextFactory, backlog, interface)
|
||||
else:
|
||||
listener = reactor.listenTCP(factory.port, factory, backlog, interface)
|
||||
return listener
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketServerProtocol(websocket.WampWebSocketServerProtocol, WebSocketServerProtocol):
|
||||
"""
|
||||
Twisted-based WAMP-over-WebSocket server protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketServerFactory(websocket.WampWebSocketServerFactory, WebSocketServerFactory):
|
||||
"""
|
||||
Twisted-based WAMP-over-WebSocket server protocol factory.
|
||||
"""
|
||||
|
||||
protocol = WampWebSocketServerProtocol
|
||||
|
||||
def __init__(self, factory, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializers: A list of WAMP serializers to use (or ``None``
|
||||
for all available serializers).
|
||||
:type serializers: list of objects implementing
|
||||
:class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
|
||||
serializers = kwargs.pop('serializers', None)
|
||||
|
||||
websocket.WampWebSocketServerFactory.__init__(self, factory, serializers)
|
||||
|
||||
kwargs['protocols'] = self._protocols
|
||||
|
||||
# noinspection PyCallByClass
|
||||
WebSocketServerFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketClientProtocol(websocket.WampWebSocketClientProtocol, WebSocketClientProtocol):
|
||||
"""
|
||||
Twisted-based WAMP-over-WebSocket client protocol.
|
||||
|
||||
Implements:
|
||||
|
||||
* :class:`autobahn.wamp.interfaces.ITransport`
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class WampWebSocketClientFactory(websocket.WampWebSocketClientFactory, WebSocketClientFactory):
|
||||
"""
|
||||
Twisted-based WAMP-over-WebSocket client protocol factory.
|
||||
"""
|
||||
|
||||
protocol = WampWebSocketClientProtocol
|
||||
|
||||
def __init__(self, factory, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param factory: A callable that produces instances that implement
|
||||
:class:`autobahn.wamp.interfaces.ITransportHandler`
|
||||
:type factory: callable
|
||||
|
||||
:param serializer: The WAMP serializer to use (or ``None`` for
|
||||
"best" serializer, chosen as the first serializer available from
|
||||
this list: CBOR, MessagePack, UBJSON, JSON).
|
||||
:type serializer: object implementing :class:`autobahn.wamp.interfaces.ISerializer`
|
||||
"""
|
||||
|
||||
serializers = kwargs.pop('serializers', None)
|
||||
|
||||
websocket.WampWebSocketClientFactory.__init__(self, factory, serializers)
|
||||
|
||||
kwargs['protocols'] = self._protocols
|
||||
|
||||
WebSocketClientFactory.__init__(self, *args, **kwargs)
|
||||
|
||||
# Reduce the factory logs noise
|
||||
self.noisy = False
|
||||
@@ -0,0 +1,107 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from autobahn import xbr # noqa
|
||||
HAS_XBR = True
|
||||
except ImportError as e:
|
||||
sys.stderr.write('WARNING: could not import autobahn.xbr - {}\n'.format(e))
|
||||
HAS_XBR = False
|
||||
|
||||
|
||||
if HAS_XBR:
|
||||
import txaio
|
||||
txaio.use_twisted()
|
||||
from twisted.internet.threads import deferToThread
|
||||
from twisted.internet.task import LoopingCall
|
||||
from twisted.internet.defer import ensureDeferred
|
||||
|
||||
import uuid
|
||||
|
||||
from autobahn.util import hl
|
||||
from autobahn.xbr._interfaces import IProvider, ISeller, IConsumer, IBuyer, IDelegate
|
||||
from autobahn.xbr import _seller, _buyer, _blockchain
|
||||
|
||||
class SimpleBlockchain(_blockchain.SimpleBlockchain):
|
||||
log = txaio.make_logger()
|
||||
backgroundCaller = deferToThread
|
||||
|
||||
class KeySeries(_seller.KeySeries):
|
||||
log = txaio.make_logger()
|
||||
|
||||
def __init__(self, api_id, price, interval=None, count=None, on_rotate=None):
|
||||
super().__init__(api_id, price, interval, count, on_rotate)
|
||||
self.running = False
|
||||
self._run_loop = None
|
||||
self._started = None
|
||||
|
||||
async def start(self):
|
||||
"""
|
||||
Start offering and selling data encryption keys in the background.
|
||||
"""
|
||||
assert self._run_loop is None
|
||||
|
||||
self.log.info('Starting key rotation every {interval} seconds for api_id="{api_id}" ..',
|
||||
interval=hl(self._interval), api_id=hl(uuid.UUID(bytes=self._api_id)))
|
||||
|
||||
self.running = True
|
||||
|
||||
self._run_loop = LoopingCall(lambda: ensureDeferred(self._rotate()))
|
||||
self._started = self._run_loop.start(self._interval)
|
||||
|
||||
return self._started
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop offering/selling data encryption keys.
|
||||
"""
|
||||
if not self._run_loop:
|
||||
raise RuntimeError('cannot stop {} - not currently running'.format(self.__class__.__name__))
|
||||
|
||||
self._run_loop.stop()
|
||||
self._run_loop = None
|
||||
|
||||
return self._started
|
||||
|
||||
class SimpleSeller(_seller.SimpleSeller):
|
||||
"""
|
||||
Simple XBR seller component. This component can be used by a XBR seller delegate to
|
||||
handle the automated selling of data encryption keys to the XBR market maker.
|
||||
"""
|
||||
log = txaio.make_logger()
|
||||
KeySeries = KeySeries
|
||||
|
||||
class SimpleBuyer(_buyer.SimpleBuyer):
|
||||
log = txaio.make_logger()
|
||||
|
||||
ISeller.register(SimpleSeller)
|
||||
IProvider.register(SimpleSeller)
|
||||
IDelegate.register(SimpleSeller)
|
||||
IBuyer.register(SimpleBuyer)
|
||||
IConsumer.register(SimpleBuyer)
|
||||
IDelegate.register(SimpleBuyer)
|
||||
1044
.venv/lib/python3.12/site-packages/autobahn/util.py
Normal file
1044
.venv/lib/python3.12/site-packages/autobahn/util.py
Normal file
File diff suppressed because it is too large
Load Diff
81
.venv/lib/python3.12/site-packages/autobahn/wamp/__init__.py
Normal file
81
.venv/lib/python3.12/site-packages/autobahn/wamp/__init__.py
Normal file
@@ -0,0 +1,81 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 autobahn.wamp.types import \
|
||||
ComponentConfig, \
|
||||
SessionDetails, \
|
||||
CloseDetails, \
|
||||
RegisterOptions, \
|
||||
CallOptions, \
|
||||
CallDetails, \
|
||||
CallResult, \
|
||||
SubscribeOptions, \
|
||||
PublishOptions, \
|
||||
EventDetails
|
||||
|
||||
from autobahn.wamp.exception import \
|
||||
Error, \
|
||||
SessionNotReady, \
|
||||
SerializationError, \
|
||||
ProtocolError, \
|
||||
TransportLost, \
|
||||
ApplicationError, \
|
||||
InvalidUri
|
||||
|
||||
from autobahn.wamp.interfaces import ISession
|
||||
|
||||
from autobahn.wamp.uri import \
|
||||
error, \
|
||||
register, \
|
||||
subscribe
|
||||
|
||||
|
||||
__all__ = (
|
||||
'ComponentConfig',
|
||||
'SessionDetails',
|
||||
'CloseDetails',
|
||||
'RegisterOptions',
|
||||
'CallOptions',
|
||||
'CallDetails',
|
||||
'CallResult',
|
||||
'SubscribeOptions',
|
||||
'PublishOptions',
|
||||
'EventDetails',
|
||||
|
||||
'Error',
|
||||
'SessionNotReady',
|
||||
'SerializationError',
|
||||
'ProtocolError',
|
||||
'TransportLost',
|
||||
'ApplicationError',
|
||||
'InvalidUri',
|
||||
|
||||
'ISession',
|
||||
|
||||
'error',
|
||||
'register',
|
||||
'subscribe',
|
||||
)
|
||||
694
.venv/lib/python3.12/site-packages/autobahn/wamp/auth.py
Normal file
694
.venv/lib/python3.12/site-packages/autobahn/wamp/auth.py
Normal file
@@ -0,0 +1,694 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import base64
|
||||
import struct
|
||||
import time
|
||||
import binascii
|
||||
import hmac
|
||||
import hashlib
|
||||
import random
|
||||
from typing import Optional, Dict
|
||||
|
||||
from autobahn.util import public
|
||||
from autobahn.util import xor as xor_array
|
||||
from autobahn.wamp.interfaces import IAuthenticator
|
||||
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
# if we don't have argon2/passlib (see "authentication" extra) then
|
||||
# you don't get AuthScram and variants
|
||||
try:
|
||||
from argon2.low_level import hash_secret
|
||||
from argon2 import Type
|
||||
from passlib.utils import saslprep
|
||||
HAS_ARGON = True
|
||||
except ImportError:
|
||||
HAS_ARGON = False
|
||||
|
||||
|
||||
__all__ = (
|
||||
'AuthAnonymous',
|
||||
'AuthScram',
|
||||
'AuthCryptoSign',
|
||||
'AuthWampCra',
|
||||
'AuthTicket',
|
||||
'create_authenticator',
|
||||
'pbkdf2',
|
||||
'generate_totp_secret',
|
||||
'compute_totp',
|
||||
'check_totp',
|
||||
'qrcode_from_totp',
|
||||
'derive_key',
|
||||
'generate_wcs',
|
||||
'compute_wcs',
|
||||
'derive_scram_credential',
|
||||
)
|
||||
|
||||
|
||||
def create_authenticator(name, **kwargs):
|
||||
"""
|
||||
Accepts various keys and values to configure an authenticator. The
|
||||
valid keys depend on the kind of authenticator but all can
|
||||
understand: `authextra`, `authid` and `authrole`
|
||||
|
||||
:return: an instance implementing IAuthenticator with the given
|
||||
configuration.
|
||||
"""
|
||||
try:
|
||||
klass = {
|
||||
AuthScram.name: AuthScram,
|
||||
AuthCryptoSign.name: AuthCryptoSign,
|
||||
AuthCryptoSignProxy.name: AuthCryptoSignProxy,
|
||||
AuthWampCra.name: AuthWampCra,
|
||||
AuthAnonymous.name: AuthAnonymous,
|
||||
AuthAnonymousProxy.name: AuthAnonymousProxy,
|
||||
AuthTicket.name: AuthTicket,
|
||||
}[name]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"Unknown authenticator '{}'".format(name)
|
||||
)
|
||||
# this may raise further ValueErrors if the kwargs are wrong
|
||||
authenticator = klass(**kwargs)
|
||||
return authenticator
|
||||
|
||||
|
||||
# experimental authentication API
|
||||
class AuthAnonymous(object):
|
||||
name = 'anonymous'
|
||||
|
||||
def __init__(self, **kw):
|
||||
self._args = kw
|
||||
|
||||
@property
|
||||
def authextra(self):
|
||||
return self._args.get('authextra', dict())
|
||||
|
||||
def on_challenge(self, session, challenge):
|
||||
raise RuntimeError(
|
||||
"on_challenge called on anonymous authentication"
|
||||
)
|
||||
|
||||
def on_welcome(self, msg, authextra):
|
||||
return None
|
||||
|
||||
|
||||
IAuthenticator.register(AuthAnonymous)
|
||||
|
||||
|
||||
class AuthAnonymousProxy(AuthAnonymous):
|
||||
name = 'anonymous-proxy'
|
||||
|
||||
|
||||
IAuthenticator.register(AuthAnonymousProxy)
|
||||
|
||||
|
||||
class AuthTicket(object):
|
||||
name = 'ticket'
|
||||
|
||||
def __init__(self, **kw):
|
||||
self._args = kw
|
||||
try:
|
||||
self._ticket = self._args.pop('ticket')
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"ticket authentication requires 'ticket=' kwarg"
|
||||
)
|
||||
|
||||
@property
|
||||
def authextra(self):
|
||||
return self._args.get('authextra', dict())
|
||||
|
||||
def on_challenge(self, session, challenge):
|
||||
assert challenge.method == "ticket"
|
||||
return self._ticket
|
||||
|
||||
def on_welcome(self, msg, authextra):
|
||||
return None
|
||||
|
||||
|
||||
IAuthenticator.register(AuthTicket)
|
||||
|
||||
|
||||
class AuthCryptoSign(object):
|
||||
name = 'cryptosign'
|
||||
|
||||
def __init__(self, **kw):
|
||||
# should put in checkconfig or similar
|
||||
for key in kw.keys():
|
||||
if key not in ['authextra', 'authid', 'authrole', 'privkey']:
|
||||
raise ValueError(
|
||||
"Unexpected key '{}' for {}".format(key, self.__class__.__name__)
|
||||
)
|
||||
for key in ['privkey']:
|
||||
if key not in kw:
|
||||
raise ValueError(
|
||||
"Must provide '{}' for cryptosign".format(key)
|
||||
)
|
||||
|
||||
from autobahn.wamp.cryptosign import CryptosignKey
|
||||
self._privkey = CryptosignKey.from_bytes(
|
||||
binascii.a2b_hex(kw['privkey'])
|
||||
)
|
||||
|
||||
if 'pubkey' in kw.get('authextra', dict()):
|
||||
pubkey = kw['authextra']['pubkey']
|
||||
if pubkey != self._privkey.public_key():
|
||||
raise ValueError(
|
||||
"Public key doesn't correspond to private key"
|
||||
)
|
||||
else:
|
||||
kw['authextra'] = kw.get('authextra', dict())
|
||||
kw['authextra']['pubkey'] = self._privkey.public_key()
|
||||
|
||||
self._channel_binding = kw.get('authextra', dict()).get('channel_binding', None)
|
||||
self._args = kw
|
||||
|
||||
@property
|
||||
def authextra(self):
|
||||
return self._args.get('authextra', dict())
|
||||
|
||||
def on_challenge(self, session, challenge):
|
||||
channel_id = session._transport.transport_details.channel_id.get(self._channel_binding, None)
|
||||
return self._privkey.sign_challenge(challenge,
|
||||
channel_id=channel_id,
|
||||
channel_id_type=self._channel_binding)
|
||||
|
||||
def on_welcome(self, msg, authextra):
|
||||
return None
|
||||
|
||||
|
||||
IAuthenticator.register(AuthCryptoSign)
|
||||
|
||||
|
||||
class AuthCryptoSignProxy(AuthCryptoSign):
|
||||
name = 'cryptosign-proxy'
|
||||
|
||||
|
||||
IAuthenticator.register(AuthCryptoSignProxy)
|
||||
|
||||
|
||||
def _hash_argon2id13_secret(password, salt, iterations, memory):
|
||||
"""
|
||||
Internal helper. Returns the salted/hashed password using the
|
||||
argon2id-13 algorithm. The return value is base64-encoded.
|
||||
"""
|
||||
rawhash = hash_secret(
|
||||
secret=password,
|
||||
salt=base64.b64decode(salt),
|
||||
time_cost=iterations,
|
||||
memory_cost=memory,
|
||||
parallelism=1, # hard-coded by WAMP-SCRAM spec
|
||||
hash_len=32,
|
||||
type=Type.ID,
|
||||
version=0x13, # note this is decimal "19" which appears in places
|
||||
)
|
||||
# spits out stuff like:
|
||||
# '$argon2i$v=19$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ'
|
||||
|
||||
_, tag, ver, options, salt_data, hash_data = rawhash.split(b'$')
|
||||
return hash_data
|
||||
|
||||
|
||||
def _hash_pbkdf2_secret(password, salt, iterations):
|
||||
"""
|
||||
Internal helper for SCRAM authentication
|
||||
"""
|
||||
return pbkdf2(password, salt, iterations, keylen=32)
|
||||
|
||||
|
||||
class AuthScram(object):
|
||||
"""
|
||||
Implements "wamp-scram" authentication for components.
|
||||
|
||||
NOTE: This is a prototype of a draft spec; see
|
||||
https://github.com/wamp-proto/wamp-proto/issues/135
|
||||
"""
|
||||
name = 'scram'
|
||||
|
||||
def __init__(self, **kw):
|
||||
if not HAS_ARGON:
|
||||
raise RuntimeError(
|
||||
"Cannot support WAMP-SCRAM without argon2_cffi and "
|
||||
"passlib libraries; install autobahn['scram']"
|
||||
)
|
||||
self._args = kw
|
||||
self._client_nonce = None
|
||||
|
||||
@property
|
||||
def authextra(self):
|
||||
# is authextra() called exactly once per authentication?
|
||||
if self._client_nonce is None:
|
||||
self._client_nonce = base64.b64encode(os.urandom(16)).decode('ascii')
|
||||
return {
|
||||
"nonce": self._client_nonce,
|
||||
}
|
||||
|
||||
def on_challenge(self, session, challenge):
|
||||
assert challenge.method == "scram"
|
||||
assert self._client_nonce is not None
|
||||
required_args = ['nonce', 'kdf', 'salt', 'iterations']
|
||||
optional_args = ['memory', 'channel_binding']
|
||||
for k in required_args:
|
||||
if k not in challenge.extra:
|
||||
raise RuntimeError(
|
||||
"WAMP-SCRAM challenge option '{}' is "
|
||||
" required but not specified".format(k)
|
||||
)
|
||||
for k in challenge.extra:
|
||||
if k not in optional_args + required_args:
|
||||
raise RuntimeError(
|
||||
"WAMP-SCRAM challenge has unknown attribute '{}'".format(k)
|
||||
)
|
||||
|
||||
channel_binding = challenge.extra.get('channel_binding', '')
|
||||
server_nonce = challenge.extra['nonce'] # base64
|
||||
salt = challenge.extra['salt'] # base64
|
||||
iterations = int(challenge.extra['iterations'])
|
||||
memory = int(challenge.extra.get('memory', -1))
|
||||
password = self._args['password'].encode('utf8') # supplied by user
|
||||
authid = saslprep(self._args['authid'])
|
||||
algorithm = challenge.extra['kdf']
|
||||
client_nonce = self._client_nonce
|
||||
|
||||
self._auth_message = (
|
||||
"{client_first_bare},{server_first},{client_final_no_proof}".format(
|
||||
client_first_bare="n={},r={}".format(authid, client_nonce),
|
||||
server_first="r={},s={},i={}".format(server_nonce, salt, iterations),
|
||||
client_final_no_proof="c={},r={}".format(channel_binding, server_nonce),
|
||||
)
|
||||
).encode('ascii')
|
||||
|
||||
if algorithm == 'argon2id-13':
|
||||
if memory == -1:
|
||||
raise ValueError(
|
||||
"WAMP-SCRAM 'argon2id-13' challenge requires 'memory' parameter"
|
||||
)
|
||||
self._salted_[PASSWORD-REMOVED](password, salt, iterations, memory)
|
||||
elif algorithm == 'pbkdf2':
|
||||
self._salted_[PASSWORD-REMOVED](password, salt, iterations)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"WAMP-SCRAM specified unknown KDF '{}'".format(algorithm)
|
||||
)
|
||||
|
||||
client_key = hmac.new(self._salted_password, b"Client Key", hashlib.sha256).digest()
|
||||
stored_key = hashlib.new('sha256', client_key).digest()
|
||||
|
||||
client_signature = hmac.new(stored_key, self._auth_message, hashlib.sha256).digest()
|
||||
client_proof = xor_array(client_key, client_signature)
|
||||
|
||||
return base64.b64encode(client_proof)
|
||||
|
||||
def on_welcome(self, session, authextra):
|
||||
"""
|
||||
When the server is satisfied, it sends a 'WELCOME' message.
|
||||
|
||||
This hook allows us an opportunity to deny the session right
|
||||
before it gets set up -- we check the server-signature thus
|
||||
authorizing the server and if it fails we drop the connection.
|
||||
"""
|
||||
alleged_server_sig = base64.b64decode(authextra['scram_server_signature'])
|
||||
server_key = hmac.new(self._salted_password, b"Server Key", hashlib.sha256).digest()
|
||||
server_signature = hmac.new(server_key, self._auth_message, hashlib.sha256).digest()
|
||||
if not hmac.compare_digest(server_signature, alleged_server_sig):
|
||||
session.log.error("Verification of server SCRAM signature failed")
|
||||
return "Verification of server SCRAM signature failed"
|
||||
session.log.info(
|
||||
"Verification of server SCRAM signature successful"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
IAuthenticator.register(AuthScram)
|
||||
|
||||
|
||||
class AuthWampCra(object):
|
||||
name = 'wampcra'
|
||||
|
||||
def __init__(self, **kw):
|
||||
# should put in checkconfig or similar
|
||||
for key in kw.keys():
|
||||
if key not in ['authextra', 'authid', 'authrole', 'secret']:
|
||||
raise ValueError(
|
||||
"Unexpected key '{}' for {}".format(key, self.__class__.__name__)
|
||||
)
|
||||
for key in ['secret', 'authid']:
|
||||
if key not in kw:
|
||||
raise ValueError(
|
||||
"Must provide '{}' for wampcra".format(key)
|
||||
)
|
||||
|
||||
self._args = kw
|
||||
self._secret = kw.pop('secret')
|
||||
if not isinstance(self._secret, str):
|
||||
self._secret = self._secret.decode('utf8')
|
||||
|
||||
@property
|
||||
def authextra(self):
|
||||
return self._args.get('authextra', dict())
|
||||
|
||||
def on_challenge(self, session, challenge):
|
||||
key = self._secret.encode('utf8')
|
||||
if 'salt' in challenge.extra:
|
||||
key = derive_key(
|
||||
key,
|
||||
challenge.extra['salt'],
|
||||
challenge.extra['iterations'],
|
||||
challenge.extra['keylen']
|
||||
)
|
||||
|
||||
signature = compute_wcs(
|
||||
key,
|
||||
challenge.extra['challenge'].encode('utf8')
|
||||
)
|
||||
return signature.decode('ascii')
|
||||
|
||||
def on_welcome(self, msg, authextra):
|
||||
return None
|
||||
|
||||
|
||||
IAuthenticator.register(AuthWampCra)
|
||||
|
||||
|
||||
@public
|
||||
def generate_totp_secret(length=10):
|
||||
"""
|
||||
Generates a new Base32 encoded, random secret.
|
||||
|
||||
.. seealso:: http://en.wikipedia.org/wiki/Base32
|
||||
|
||||
:param length: The length of the entropy used to generate the secret.
|
||||
:type length: int
|
||||
|
||||
:returns: The generated secret in Base32 (letters ``A-Z`` and digits ``2-7``).
|
||||
The length of the generated secret is ``length * 8 / 5`` octets.
|
||||
:rtype: unicode
|
||||
"""
|
||||
assert(type(length) == int)
|
||||
return base64.b32encode(os.urandom(length)).decode('ascii')
|
||||
|
||||
|
||||
@public
|
||||
def compute_totp(secret, offset=0):
|
||||
"""
|
||||
Computes the current TOTP code.
|
||||
|
||||
:param secret: Base32 encoded secret.
|
||||
:type secret: unicode
|
||||
:param offset: Time offset (in steps, use eg -1, 0, +1 for compliance with RFC6238)
|
||||
for which to compute TOTP.
|
||||
:type offset: int
|
||||
|
||||
:returns: TOTP for current time (+/- offset).
|
||||
:rtype: unicode
|
||||
"""
|
||||
assert(type(secret) == str)
|
||||
assert(type(offset) == int)
|
||||
try:
|
||||
key = base64.b32decode(secret)
|
||||
except TypeError:
|
||||
raise Exception('invalid secret')
|
||||
interval = offset + int(time.time()) // 30
|
||||
msg = struct.pack('>Q', interval)
|
||||
digest = hmac.new(key, msg, hashlib.sha1).digest()
|
||||
o = 15 & (digest[19])
|
||||
token = (struct.unpack('>I', digest[o:o + 4])[0] & 0x7fffffff) % 1000000
|
||||
return '{0:06d}'.format(token)
|
||||
|
||||
|
||||
@public
|
||||
def check_totp(secret, ticket):
|
||||
"""
|
||||
Check a TOTP value received from a principal trying to authenticate against
|
||||
the expected value computed from the secret shared between the principal and
|
||||
the authenticating entity.
|
||||
|
||||
The Internet can be slow, and clocks might not match exactly, so some
|
||||
leniency is allowed. RFC6238 recommends looking an extra time step in either
|
||||
direction, which essentially opens the window from 30 seconds to 90 seconds.
|
||||
|
||||
:param secret: The secret shared between the principal (eg a client) that
|
||||
is authenticating, and the authenticating entity (eg a server).
|
||||
:type secret: unicode
|
||||
:param ticket: The TOTP value to be checked.
|
||||
:type ticket: unicode
|
||||
|
||||
:returns: ``True`` if the TOTP value is correct, else ``False``.
|
||||
:rtype: bool
|
||||
"""
|
||||
for offset in [0, 1, -1]:
|
||||
if ticket == compute_totp(secret, offset):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@public
|
||||
def qrcode_from_totp(secret, label, issuer):
|
||||
if type(secret) != str:
|
||||
raise Exception('secret must be of type unicode, not {}'.format(type(secret)))
|
||||
|
||||
if type(label) != str:
|
||||
raise Exception('label must be of type unicode, not {}'.format(type(label)))
|
||||
|
||||
try:
|
||||
import qrcode
|
||||
import qrcode.image.svg
|
||||
except ImportError:
|
||||
raise Exception('qrcode not installed')
|
||||
|
||||
return qrcode.make(
|
||||
'otpauth://totp/{}?secret={}&issuer={}'.format(label, secret, issuer),
|
||||
box_size=3,
|
||||
image_factory=qrcode.image.svg.SvgImage).to_string()
|
||||
|
||||
|
||||
@public
|
||||
def pbkdf2(data, salt, iterations=1000, keylen=32, hashfunc=None):
|
||||
"""
|
||||
Returns a binary digest for the PBKDF2 hash algorithm of ``data``
|
||||
with the given ``salt``. It iterates ``iterations`` time and produces a
|
||||
key of ``keylen`` bytes. By default SHA-256 is used as hash function,
|
||||
a different hashlib ``hashfunc`` can be provided.
|
||||
|
||||
:param data: The data for which to compute the PBKDF2 derived key.
|
||||
:type data: bytes
|
||||
:param salt: The salt to use for deriving the key.
|
||||
:type salt: bytes
|
||||
:param iterations: The number of iterations to perform in PBKDF2.
|
||||
:type iterations: int
|
||||
:param keylen: The length of the cryptographic key to derive.
|
||||
:type keylen: int
|
||||
:param hashfunc: Name of the hash algorithm to use
|
||||
:type hashfunc: str
|
||||
|
||||
:returns: The derived cryptographic key.
|
||||
:rtype: bytes
|
||||
"""
|
||||
if not (type(data) == bytes) or \
|
||||
not (type(salt) == bytes) or \
|
||||
not (type(iterations) == int) or \
|
||||
not (type(keylen) == int):
|
||||
raise ValueError("Invalid argument types")
|
||||
|
||||
# justification: WAMP-CRA uses SHA256 and users shouldn't have any
|
||||
# other reason to call this particular pbkdf2 function (arguably,
|
||||
# it should be private maybe?)
|
||||
if hashfunc is None:
|
||||
hashfunc = 'sha256'
|
||||
if hashfunc is callable:
|
||||
# used to take stuff from hashlib; translate?
|
||||
raise ValueError(
|
||||
"pbkdf2 now takes the name of a hash algorithm for 'hashfunc='"
|
||||
)
|
||||
|
||||
backend = default_backend()
|
||||
|
||||
# https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/#pbkdf2
|
||||
kdf = PBKDF2HMAC(
|
||||
algorithm=getattr(hashes, hashfunc.upper())(),
|
||||
length=keylen,
|
||||
salt=salt,
|
||||
iterations=iterations,
|
||||
backend=backend,
|
||||
)
|
||||
return kdf.derive(data)
|
||||
|
||||
|
||||
@public
|
||||
def derive_key(secret, salt, iterations=1000, keylen=32):
|
||||
"""
|
||||
Computes a derived cryptographic key from a password according to PBKDF2.
|
||||
|
||||
.. seealso:: http://en.wikipedia.org/wiki/PBKDF2
|
||||
|
||||
:param secret: The secret.
|
||||
:type secret: bytes or unicode
|
||||
:param salt: The salt to be used.
|
||||
:type salt: bytes or unicode
|
||||
:param iterations: Number of iterations of derivation algorithm to run.
|
||||
:type iterations: int
|
||||
:param keylen: Length of the key to derive in bytes.
|
||||
:type keylen: int
|
||||
|
||||
:return: The derived key in Base64 encoding.
|
||||
:rtype: bytes
|
||||
"""
|
||||
if not (type(secret) in [str, bytes]):
|
||||
raise ValueError("'secret' must be bytes")
|
||||
if not (type(salt) in [str, bytes]):
|
||||
raise ValueError("'salt' must be bytes")
|
||||
if not (type(iterations) == int):
|
||||
raise ValueError("'iterations' must be an integer")
|
||||
if not (type(keylen) == int):
|
||||
raise ValueError("'keylen' must be an integer")
|
||||
if type(secret) == str:
|
||||
secret = secret.encode('utf8')
|
||||
if type(salt) == str:
|
||||
salt = salt.encode('utf8')
|
||||
key = pbkdf2(secret, salt, iterations, keylen)
|
||||
return binascii.b2a_base64(key).strip()
|
||||
|
||||
|
||||
WCS_SECRET_CHARSET = "[AWS-SECRET-REMOVED]opqrstuvwxyz0123456789"
|
||||
"""
|
||||
The characters from which :func:`autobahn.wamp.auth.generate_wcs` generates secrets.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
def generate_wcs(length=14):
|
||||
"""
|
||||
Generates a new random secret for use with WAMP-CRA.
|
||||
|
||||
The secret generated is a random character sequence drawn from
|
||||
|
||||
- upper and lower case latin letters
|
||||
- digits
|
||||
-
|
||||
|
||||
:param length: The length of the secret to generate.
|
||||
:type length: int
|
||||
|
||||
:return: The generated secret. The length of the generated is ``length`` octets.
|
||||
:rtype: bytes
|
||||
"""
|
||||
assert(type(length) == int)
|
||||
return "".join(random.choice(WCS_SECRET_CHARSET) for _ in range(length)).encode('ascii')
|
||||
|
||||
|
||||
@public
|
||||
def compute_wcs(key, challenge):
|
||||
"""
|
||||
Compute an WAMP-CRA authentication signature from an authentication
|
||||
challenge and a (derived) key.
|
||||
|
||||
:param key: The key derived (via PBKDF2) from the secret.
|
||||
:type key: bytes
|
||||
:param challenge: The authentication challenge to sign.
|
||||
:type challenge: bytes
|
||||
|
||||
:return: The authentication signature.
|
||||
:rtype: bytes
|
||||
"""
|
||||
assert(type(key) in [str, bytes])
|
||||
assert(type(challenge) in [str, bytes])
|
||||
if type(key) == str:
|
||||
key = key.encode('utf8')
|
||||
if type(challenge) == str:
|
||||
challenge = challenge.encode('utf8')
|
||||
sig = hmac.new(key, challenge, hashlib.sha256).digest()
|
||||
return binascii.b2a_base64(sig).strip()
|
||||
|
||||
|
||||
def derive_scram_credential(email: str, password: str, salt: Optional[bytes] = None) -> Dict:
|
||||
"""
|
||||
Derive WAMP-SCRAM credentials from user email and password. The SCRAM parameters used
|
||||
are the following (these are also contained in the returned credentials):
|
||||
|
||||
* kdf ``argon2id-13``
|
||||
* time cost ``4096``
|
||||
* memory cost ``512``
|
||||
* parallelism ``1``
|
||||
|
||||
See `draft-irtf-cfrg-argon2 <https://datatracker.ietf.org/doc/draft-irtf-cfrg-argon2/>`__ and
|
||||
`argon2-cffi <https://argon2-cffi.readthedocs.io/en/stable/>`__.
|
||||
|
||||
:param email: User email.
|
||||
:param password: User password.
|
||||
:param salt: Optional salt to use (must be 16 bytes long). If none is given, compute salt
|
||||
from email as ``salt = SHA256(email)[:16]``.
|
||||
:return: WAMP-SCRAM credentials. When serialized, the returned credentials can be copy-pasted
|
||||
into the ``***REMOVED***`` node configuration for a Crossbar.io node.
|
||||
"""
|
||||
assert HAS_ARGON, 'missing dependency argon2'
|
||||
from argon2.low_level import hash_secret
|
||||
from argon2.low_level import Type
|
||||
|
||||
# derive salt from email
|
||||
if not salt:
|
||||
m = hashlib.sha256()
|
||||
m.update(email.encode('utf8'))
|
||||
salt = m.digest()[:16]
|
||||
assert len(salt) == 16
|
||||
|
||||
hash_data = hash_secret(
|
||||
secret=password.encode('utf8'),
|
||||
salt=salt,
|
||||
time_cost=4096,
|
||||
memory_cost=512,
|
||||
parallelism=1,
|
||||
hash_len=32,
|
||||
type=Type.ID,
|
||||
version=19,
|
||||
)
|
||||
_, tag, v, params, _, salted_[PASSWORD-REMOVED].decode('ascii').split('$')
|
||||
assert tag == 'argon2id'
|
||||
assert v == 'v=19' # argon's version 1.3 is represented as 0x13, which is 19 decimal...
|
||||
params = {
|
||||
k: v
|
||||
for k, v in
|
||||
[x.split('=') for x in params.split(',')]
|
||||
}
|
||||
|
||||
salted_[PASSWORD-REMOVED].encode('ascii')
|
||||
client_key = hmac.new(salted_password, b"Client Key", hashlib.sha256).digest()
|
||||
stored_key = hashlib.new('sha256', client_key).digest()
|
||||
server_key = hmac.new(salted_password, b"Server Key", hashlib.sha256).digest()
|
||||
|
||||
credential = {
|
||||
"kdf": "argon2id-13",
|
||||
"memory": int(params['m']),
|
||||
"iterations": int(params['t']),
|
||||
"salt": binascii.b2a_hex(salt).decode('ascii'),
|
||||
"stored-key": binascii.b2a_hex(stored_key).decode('ascii'),
|
||||
"server-key": binascii.b2a_hex(server_key).decode('ascii'),
|
||||
}
|
||||
return credential
|
||||
983
.venv/lib/python3.12/site-packages/autobahn/wamp/component.py
Normal file
983
.venv/lib/python3.12/site-packages/autobahn/wamp/component.py
Normal file
@@ -0,0 +1,983 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
import itertools
|
||||
import random
|
||||
from functools import partial
|
||||
|
||||
import txaio
|
||||
|
||||
from autobahn.util import ObservableMixin
|
||||
from autobahn.websocket.util import parse_url as parse_ws_url
|
||||
from autobahn.rawsocket.util import parse_url as parse_rs_url
|
||||
|
||||
from autobahn.wamp.types import ComponentConfig, SubscribeOptions, RegisterOptions
|
||||
from autobahn.wamp.exception import SessionNotReady, ApplicationError
|
||||
from autobahn.wamp.auth import create_authenticator, IAuthenticator
|
||||
from autobahn.wamp.serializer import SERID_TO_SER
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Component'
|
||||
)
|
||||
|
||||
|
||||
def _validate_endpoint(endpoint, check_native_endpoint=None):
|
||||
"""
|
||||
Check a WAMP connecting endpoint configuration.
|
||||
"""
|
||||
if check_native_endpoint:
|
||||
check_native_endpoint(endpoint)
|
||||
elif not isinstance(endpoint, dict):
|
||||
raise ValueError(
|
||||
"'endpoint' must be a dict"
|
||||
)
|
||||
|
||||
# note, we're falling through here -- check_native_endpoint can
|
||||
# disallow or allow dict-based config as it likes, but if it
|
||||
# *does* allow a dict through, we want to check "base options"
|
||||
# here so that both Twisted and asyncio don't have to check these
|
||||
# things as well.
|
||||
if isinstance(endpoint, dict):
|
||||
# XXX what about filling in anything missing from the URL? Or
|
||||
# is that only for when *nothing* is provided for endpoint?
|
||||
if 'type' not in endpoint:
|
||||
# could maybe just make tcp the default?
|
||||
raise ValueError("'type' required in endpoint configuration")
|
||||
if endpoint['type'] not in ['tcp', 'unix']:
|
||||
raise ValueError('invalid type "{}" in endpoint'.format(endpoint['type']))
|
||||
|
||||
for k in endpoint.keys():
|
||||
if k not in ['type', 'host', 'port', 'path', 'tls', 'timeout', 'version']:
|
||||
raise ValueError(
|
||||
"Invalid key '{}' in endpoint configuration".format(k)
|
||||
)
|
||||
|
||||
if endpoint['type'] == 'tcp':
|
||||
for k in ['host', 'port']:
|
||||
if k not in endpoint:
|
||||
raise ValueError(
|
||||
"'{}' required in 'tcp' endpoint config".format(k)
|
||||
)
|
||||
for k in ['path']:
|
||||
if k in endpoint:
|
||||
raise ValueError(
|
||||
"'{}' not valid in 'tcp' endpoint config".format(k)
|
||||
)
|
||||
elif endpoint['type'] == 'unix':
|
||||
for k in ['path']:
|
||||
if k not in endpoint:
|
||||
raise ValueError(
|
||||
"'{}' required for 'unix' endpoint config".format(k)
|
||||
)
|
||||
for k in ['host', 'port', 'tls']:
|
||||
if k in endpoint:
|
||||
raise ValueError(
|
||||
"'{}' not valid in 'unix' endpoint config".format(k)
|
||||
)
|
||||
else:
|
||||
assert False, 'should not arrive here'
|
||||
|
||||
|
||||
def _create_transport(index, transport, check_native_endpoint=None):
|
||||
"""
|
||||
Internal helper to insert defaults and create _Transport instances.
|
||||
|
||||
:param transport: a (possibly valid) transport configuration
|
||||
:type transport: dict
|
||||
|
||||
:returns: a _Transport instance
|
||||
|
||||
:raises: ValueError on invalid configuration
|
||||
"""
|
||||
if type(transport) != dict:
|
||||
raise ValueError('invalid type {} for transport configuration - must be a dict'.format(type(transport)))
|
||||
|
||||
valid_transport_keys = [
|
||||
'type', 'url', 'endpoint', 'serializer', 'serializers', 'options',
|
||||
'max_retries', 'max_retry_delay', 'initial_retry_delay',
|
||||
'retry_delay_growth', 'retry_delay_jitter', 'proxy', 'headers'
|
||||
]
|
||||
for k in transport.keys():
|
||||
if k not in valid_transport_keys:
|
||||
raise ValueError(
|
||||
"'{}' is not a valid configuration item".format(k)
|
||||
)
|
||||
|
||||
kind = 'websocket'
|
||||
if 'type' in transport:
|
||||
if transport['type'] not in ['websocket', 'rawsocket']:
|
||||
raise ValueError('Invalid transport type {}'.format(transport['type']))
|
||||
kind = transport['type']
|
||||
else:
|
||||
transport['type'] = 'websocket'
|
||||
|
||||
if 'proxy' in transport and kind != 'websocket':
|
||||
raise ValueError(
|
||||
"proxy= only supported for type=websocket transports"
|
||||
)
|
||||
proxy = transport.get("proxy", None)
|
||||
if proxy is not None:
|
||||
for k in proxy.keys():
|
||||
if k not in ['host', 'port']:
|
||||
raise ValueError(
|
||||
"Unknown key '{}' in proxy config".format(k)
|
||||
)
|
||||
for k in ['host', 'port']:
|
||||
if k not in proxy:
|
||||
raise ValueError(
|
||||
"Proxy config requires '{}'".formaT(k)
|
||||
)
|
||||
|
||||
options = dict()
|
||||
if 'options' in transport:
|
||||
options = transport['options']
|
||||
if not isinstance(options, dict):
|
||||
raise ValueError(
|
||||
'options must be a dict, not {}'.format(type(options))
|
||||
)
|
||||
|
||||
headers = transport.get("headers")
|
||||
|
||||
if kind == 'websocket':
|
||||
for key in ['url']:
|
||||
if key not in transport:
|
||||
raise ValueError("Transport requires '{}' key".format(key))
|
||||
# endpoint not required; we will deduce from URL if it's not provided
|
||||
# XXX not in the branch I rebased; can this go away? (is it redundant??)
|
||||
if 'endpoint' not in transport:
|
||||
is_secure, host, port, resource, path, params = parse_ws_url(transport['url'])
|
||||
endpoint_config = {
|
||||
'type': 'tcp',
|
||||
'host': host,
|
||||
'port': port,
|
||||
'tls': is_secure,
|
||||
}
|
||||
else:
|
||||
# note: we're avoiding mutating the incoming "configuration"
|
||||
# dict, so this should avoid that too...
|
||||
endpoint_config = transport['endpoint']
|
||||
_validate_endpoint(endpoint_config, check_native_endpoint)
|
||||
|
||||
if 'serializer' in transport:
|
||||
raise ValueError("'serializer' is only for rawsocket; use 'serializers'")
|
||||
if 'serializers' in transport:
|
||||
if not isinstance(transport['serializers'], (list, tuple)):
|
||||
raise ValueError("'serializers' must be a list of strings")
|
||||
if not all([
|
||||
isinstance(s, (str, str))
|
||||
for s in transport['serializers']]):
|
||||
raise ValueError("'serializers' must be a list of strings")
|
||||
valid_serializers = SERID_TO_SER.keys()
|
||||
for serial in transport['serializers']:
|
||||
if serial not in valid_serializers:
|
||||
raise ValueError(
|
||||
"Invalid serializer '{}' (expected one of: {})".format(
|
||||
serial,
|
||||
', '.join([repr(s) for s in valid_serializers]),
|
||||
)
|
||||
)
|
||||
serializer_config = transport.get('serializers', ['cbor', 'json'])
|
||||
|
||||
elif kind == 'rawsocket':
|
||||
if 'endpoint' not in transport:
|
||||
if transport['url'].startswith('rs'):
|
||||
# # try to parse RawSocket URL ..
|
||||
isSecure, host, port = parse_rs_url(transport['url'])
|
||||
elif transport['url'].startswith('ws'):
|
||||
# try to parse WebSocket URL ..
|
||||
isSecure, host, port, resource, path, params = parse_ws_url(transport['url'])
|
||||
else:
|
||||
raise RuntimeError()
|
||||
if host == 'unix':
|
||||
# here, "port" is actually holding the path on the host, eg "/tmp/file.sock"
|
||||
endpoint_config = {
|
||||
'type': 'unix',
|
||||
'path': port,
|
||||
}
|
||||
else:
|
||||
endpoint_config = {
|
||||
'type': 'tcp',
|
||||
'host': host,
|
||||
'port': port,
|
||||
}
|
||||
else:
|
||||
endpoint_config = transport['endpoint']
|
||||
if 'serializers' in transport:
|
||||
raise ValueError("'serializers' is only for websocket; use 'serializer'")
|
||||
if headers is not None:
|
||||
raise ValueError("'headers' not supported for rawsocket transport")
|
||||
# always a list; len == 1 for rawsocket
|
||||
if 'serializer' in transport:
|
||||
if not isinstance(transport['serializer'], (str, str)):
|
||||
raise ValueError("'serializer' must be a string")
|
||||
serializer_config = [transport['serializer']]
|
||||
else:
|
||||
serializer_config = ['cbor']
|
||||
|
||||
else:
|
||||
assert False, 'should not arrive here'
|
||||
|
||||
kw = {}
|
||||
for key in ['max_retries', 'max_retry_delay', 'initial_retry_delay',
|
||||
'retry_delay_growth', 'retry_delay_jitter']:
|
||||
if key in transport:
|
||||
kw[key] = transport[key]
|
||||
|
||||
return _Transport(
|
||||
index,
|
||||
kind=kind,
|
||||
url=transport.get('url', None),
|
||||
endpoint=endpoint_config,
|
||||
serializers=serializer_config,
|
||||
proxy=proxy,
|
||||
options=options,
|
||||
headers=headers,
|
||||
**kw
|
||||
)
|
||||
|
||||
|
||||
class _Transport(object):
|
||||
"""
|
||||
Thin-wrapper for WAMP transports used by a Connection.
|
||||
"""
|
||||
|
||||
def __init__(self, idx, kind, url, endpoint, serializers,
|
||||
max_retries=-1,
|
||||
max_retry_delay=300,
|
||||
initial_retry_delay=1.5,
|
||||
retry_delay_growth=1.5,
|
||||
retry_delay_jitter=0.1,
|
||||
proxy=None,
|
||||
options=None,
|
||||
headers=None):
|
||||
"""
|
||||
"""
|
||||
if options is None:
|
||||
options = dict()
|
||||
self.idx = idx
|
||||
|
||||
self.type = kind
|
||||
self.url = url
|
||||
self.endpoint = endpoint
|
||||
self.options = options
|
||||
self.headers = headers
|
||||
|
||||
self.serializers = serializers
|
||||
if self.type == 'rawsocket' and len(serializers) != 1:
|
||||
raise ValueError(
|
||||
"'rawsocket' transport requires exactly one serializer"
|
||||
)
|
||||
|
||||
self.max_retries = max_retries
|
||||
self.max_retry_delay = max_retry_delay
|
||||
self.initial_retry_delay = initial_retry_delay
|
||||
self.retry_delay_growth = retry_delay_growth
|
||||
self.retry_delay_jitter = retry_delay_jitter
|
||||
self.proxy = proxy # this is a dict of proxy config
|
||||
|
||||
# used via can_reconnect() and failed() to record this
|
||||
# transport is never going to work
|
||||
self._permanent_failure = False
|
||||
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
set connection failure rates and retry-delay to initial values
|
||||
"""
|
||||
self.connect_attempts = 0
|
||||
self.connect_sucesses = 0
|
||||
self.connect_failures = 0
|
||||
self.retry_delay = self.initial_retry_delay
|
||||
|
||||
def failed(self):
|
||||
"""
|
||||
Mark this transport as failed, meaning we won't try to connect to
|
||||
it any longer (that is: can_reconnect() will always return
|
||||
False afer calling this).
|
||||
"""
|
||||
self._permanent_failure = True
|
||||
|
||||
def can_reconnect(self):
|
||||
if self._permanent_failure:
|
||||
return False
|
||||
if self.max_retries == -1:
|
||||
return True
|
||||
return self.connect_attempts < self.max_retries + 1
|
||||
|
||||
def next_delay(self):
|
||||
if self.connect_attempts == 0:
|
||||
# if we never tried before, try immediately
|
||||
return 0
|
||||
elif self.max_retries != -1 and self.connect_attempts >= self.max_retries + 1:
|
||||
raise RuntimeError('max reconnects reached')
|
||||
else:
|
||||
self.retry_delay = self.retry_delay * self.retry_delay_growth
|
||||
self.retry_delay = random.normalvariate(self.retry_delay, self.retry_delay * self.retry_delay_jitter)
|
||||
if self.retry_delay > self.max_retry_delay:
|
||||
self.retry_delay = self.max_retry_delay
|
||||
return self.retry_delay
|
||||
|
||||
def describe_endpoint(self):
|
||||
"""
|
||||
returns a human-readable description of the endpoint
|
||||
"""
|
||||
if isinstance(self.endpoint, dict):
|
||||
return self.endpoint['type']
|
||||
return repr(self.endpoint)
|
||||
|
||||
|
||||
# this could probably implement twisted.application.service.IService
|
||||
# if we wanted; or via an adapter...which just adds a startService()
|
||||
# and stopService() [latter can be async]
|
||||
|
||||
class Component(ObservableMixin):
|
||||
"""
|
||||
A WAMP application component. A component holds configuration for
|
||||
(and knows how to create) transports and sessions.
|
||||
"""
|
||||
|
||||
session_factory = None
|
||||
"""
|
||||
The factory of the session we will instantiate.
|
||||
"""
|
||||
|
||||
def subscribe(self, topic, options=None, check_types=False):
|
||||
"""
|
||||
A decorator as a shortcut for subscribing during on-join
|
||||
|
||||
For example::
|
||||
|
||||
@component.subscribe(
|
||||
"some.topic",
|
||||
options=SubscribeOptions(match='prefix'),
|
||||
)
|
||||
def topic(*args, **kw):
|
||||
print("some.topic({}, {}): event received".format(args, kw))
|
||||
"""
|
||||
assert options is None or isinstance(options, SubscribeOptions)
|
||||
|
||||
def decorator(fn):
|
||||
|
||||
def do_subscription(session, details):
|
||||
return session.subscribe(fn, topic=topic, options=options, check_types=check_types)
|
||||
self.on('join', do_subscription)
|
||||
return fn
|
||||
return decorator
|
||||
|
||||
def register(self, uri, options=None, check_types=False):
|
||||
"""
|
||||
A decorator as a shortcut for registering during on-join
|
||||
|
||||
For example::
|
||||
|
||||
@component.register(
|
||||
"com.example.add",
|
||||
options=RegisterOptions(invoke='roundrobin'),
|
||||
)
|
||||
def add(*args, **kw):
|
||||
print("add({}, {}): event received".format(args, kw))
|
||||
"""
|
||||
assert options is None or isinstance(options, RegisterOptions)
|
||||
|
||||
def decorator(fn):
|
||||
|
||||
def do_registration(session, details):
|
||||
return session.register(fn, procedure=uri, options=options, check_types=check_types)
|
||||
self.on('join', do_registration)
|
||||
return fn
|
||||
return decorator
|
||||
|
||||
def __init__(self, main=None, transports=None, config=None, realm='realm1', extra=None,
|
||||
authentication=None, session_factory=None, is_fatal=None):
|
||||
"""
|
||||
:param main: After a transport has been connected and a session
|
||||
has been established and joined to a realm, this (async)
|
||||
procedure will be run until it finishes -- which signals that
|
||||
the component has run to completion. In this case, it usually
|
||||
doesn't make sense to use the ``on_*`` kwargs. If you do not
|
||||
pass a main() procedure, the session will not be closed
|
||||
(unless you arrange for .leave() to be called).
|
||||
|
||||
:type main: callable taking two args ``reactor`` and ``ISession``
|
||||
|
||||
:param transports: Transport configurations for creating
|
||||
transports. Each transport can be a WAMP URL, or a dict
|
||||
containing the following configuration keys:
|
||||
|
||||
- ``type`` (optional): ``websocket`` (default) or ``rawsocket``
|
||||
- ``url``: the router URL
|
||||
- ``endpoint`` (optional, derived from URL if not provided):
|
||||
- ``type``: "tcp" or "unix"
|
||||
- ``host``, ``port``: only for TCP
|
||||
- ``path``: only for unix
|
||||
- ``timeout``: in seconds
|
||||
- ``tls``: ``True`` or (under Twisted) an
|
||||
``twisted.internet.ssl.IOpenSSLClientComponentCreator``
|
||||
instance (such as returned from
|
||||
``twisted.internet.ssl.optionsForClientTLS``) or
|
||||
``CertificateOptions`` instance.
|
||||
- ``max_retries``: Maximum number of reconnection attempts. Unlimited if set to -1.
|
||||
- ``initial_retry_delay``: Initial delay for reconnection attempt in seconds (Default: 1.0s).
|
||||
- ``max_retry_delay``: Maximum delay for reconnection attempts in seconds (Default: 60s).
|
||||
- ``retry_delay_growth``: The growth factor applied to the retry delay between reconnection attempts (Default 1.5).
|
||||
- ``retry_delay_jitter``: A 0-argument callable that introduces nose into the delay. (Default random.random)
|
||||
- ``serializer`` (only for raw socket): Specify an accepted serializer (e.g. 'json', 'msgpack', 'cbor', 'ubjson', 'flatbuffers')
|
||||
- ``serializers``: Specify list of accepted serializers
|
||||
- ``options``: tbd
|
||||
- ``proxy``: tbd
|
||||
|
||||
:type transports: None or str or list
|
||||
|
||||
:param realm: the realm to join
|
||||
:type realm: str
|
||||
|
||||
:param authentication: configuration of authenticators
|
||||
:type authentication: dict
|
||||
|
||||
:param session_factory: if None, ``ApplicationSession`` is
|
||||
used, otherwise a callable taking a single ``config`` argument
|
||||
that is used to create a new `ApplicationSession` instance.
|
||||
|
||||
:param is_fatal: a callable taking a single argument, an
|
||||
``Exception`` instance. The callable should return ``True`` if
|
||||
this error is "fatal", meaning we should not try connecting to
|
||||
the current transport again. The default behavior (on None) is
|
||||
to always return ``False``
|
||||
"""
|
||||
self.set_valid_events(
|
||||
[
|
||||
'start', # fired by base class
|
||||
'connect', # fired by ApplicationSession
|
||||
'join', # fired by ApplicationSession
|
||||
'ready', # fired by ApplicationSession
|
||||
'leave', # fired by ApplicationSession
|
||||
'disconnect', # fired by ApplicationSession
|
||||
'connectfailure', # fired by base class
|
||||
]
|
||||
)
|
||||
|
||||
if is_fatal is not None and not callable(is_fatal):
|
||||
raise ValueError('"is_fatal" must be a callable or None')
|
||||
self._is_fatal = is_fatal
|
||||
|
||||
if main is not None and not callable(main):
|
||||
raise ValueError('"main" must be a callable if given')
|
||||
self._entry = main
|
||||
|
||||
# use WAMP-over-WebSocket to localhost when no transport is specified at all
|
||||
if transports is None:
|
||||
transports = 'ws://127.0.0.1:8080/ws'
|
||||
|
||||
# allows to provide a URL instead of a list of transports
|
||||
if isinstance(transports, (str, str)):
|
||||
url = transports
|
||||
# 'endpoint' will get filled in by parsing the 'url'
|
||||
transport = {
|
||||
'type': 'websocket',
|
||||
'url': url,
|
||||
}
|
||||
transports = [transport]
|
||||
|
||||
# allows single transport instead of a list (convenience)
|
||||
elif isinstance(transports, dict):
|
||||
transports = [transports]
|
||||
|
||||
# XXX do we want to be able to provide an infinite iterable of
|
||||
# transports here? e.g. a generator that makes new transport
|
||||
# to try?
|
||||
|
||||
# now check and save list of transports
|
||||
self._transports = []
|
||||
for idx, transport in enumerate(transports):
|
||||
# allows to provide a URL instead of transport dict
|
||||
if type(transport) == str:
|
||||
_transport = {
|
||||
'type': 'websocket',
|
||||
'url': transport,
|
||||
}
|
||||
else:
|
||||
_transport = transport
|
||||
self._transports.append(
|
||||
_create_transport(idx, _transport, self._check_native_endpoint)
|
||||
)
|
||||
|
||||
# XXX should have some checkconfig support
|
||||
self._authentication = authentication or {}
|
||||
|
||||
if session_factory:
|
||||
self.session_factory = session_factory
|
||||
self._realm = realm
|
||||
self._extra = extra
|
||||
|
||||
self._delay_f = None
|
||||
self._done_f = None
|
||||
self._session = None
|
||||
self._stopping = False
|
||||
|
||||
def _can_reconnect(self):
|
||||
# check if any of our transport has any reconnect attempt left
|
||||
for transport in self._transports:
|
||||
if transport.can_reconnect():
|
||||
return True
|
||||
return False
|
||||
|
||||
def _start(self, loop=None):
|
||||
"""
|
||||
This starts the Component, which means it will start connecting
|
||||
(and re-connecting) to its configured transports. A Component
|
||||
runs until it is "done", which means one of:
|
||||
|
||||
- There was a "main" function defined, and it completed successfully;
|
||||
- Something called ``.leave()`` on our session, and we left successfully;
|
||||
- ``.stop()`` was called, and completed successfully;
|
||||
- none of our transports were able to connect successfully (failure);
|
||||
|
||||
:returns: a Future/Deferred which will resolve (to ``None``) when we are
|
||||
"done" or with an error if something went wrong.
|
||||
"""
|
||||
|
||||
# we can only be "start()ed" once before we stop .. but that
|
||||
# doesn't have to be an error we can give back another future
|
||||
# that fires when our "real" _done_f is completed.
|
||||
if self._done_f is not None:
|
||||
d = txaio.create_future()
|
||||
|
||||
def _cb(arg):
|
||||
txaio.resolve(d, arg)
|
||||
|
||||
txaio.add_callbacks(self._done_f, _cb, _cb)
|
||||
return d
|
||||
|
||||
# this future will be returned, and thus has the semantics
|
||||
# specified in the docstring.
|
||||
self._done_f = txaio.create_future()
|
||||
|
||||
def _reset(arg):
|
||||
"""
|
||||
if the _done_f future is resolved (good or bad), we want to set it
|
||||
to None in our class
|
||||
"""
|
||||
self._done_f = None
|
||||
return arg
|
||||
txaio.add_callbacks(self._done_f, _reset, _reset)
|
||||
|
||||
# Create a generator of transports that .can_reconnect()
|
||||
transport_gen = itertools.cycle(self._transports)
|
||||
|
||||
# this is a 1-element list so we can set it from closures in
|
||||
# this function
|
||||
transport_candidate = [0]
|
||||
|
||||
def error(fail):
|
||||
self._delay_f = None
|
||||
if self._stopping:
|
||||
# might be better to add framework-specific checks in
|
||||
# subclasses to see if this is CancelledError (for
|
||||
# Twisted) and whatever asyncio does .. but tracking
|
||||
# if we're in the shutdown path is fine too
|
||||
txaio.resolve(self._done_f, None)
|
||||
else:
|
||||
self.log.info("Internal error {msg}", msg=txaio.failure_message(fail))
|
||||
self.log.debug("{tb}", tb=txaio.failure_format_traceback(fail))
|
||||
txaio.reject(self._done_f, fail)
|
||||
|
||||
def attempt_connect(_):
|
||||
self._delay_f = None
|
||||
|
||||
def handle_connect_error(fail):
|
||||
# FIXME - make txaio friendly
|
||||
# Can connect_f ever be in a cancelled state?
|
||||
# if txaio.using_asyncio and isinstance(fail.value, asyncio.CancelledError):
|
||||
# unrecoverable_error = True
|
||||
|
||||
self.log.debug('component failed: {error}', error=txaio.failure_message(fail))
|
||||
self.log.debug('{tb}', tb=txaio.failure_format_traceback(fail))
|
||||
# If this is a "fatal error" that will never work,
|
||||
# we bail out now
|
||||
if isinstance(fail.value, ApplicationError):
|
||||
self.log.error("{msg}", msg=fail.value.error_message())
|
||||
|
||||
elif isinstance(fail.value, OSError):
|
||||
# failed to connect entirely, like nobody
|
||||
# listening etc.
|
||||
self.log.info("Connection failed with OS error: {msg}", msg=txaio.failure_message(fail))
|
||||
|
||||
elif self._is_ssl_error(fail.value):
|
||||
# Quoting pyOpenSSL docs: "Whenever
|
||||
# [SSL.Error] is raised directly, it has a
|
||||
# list of error messages from the OpenSSL
|
||||
# error queue, where each item is a tuple
|
||||
# (lib, function, reason). Here lib, function
|
||||
# and reason are all strings, describing where
|
||||
# and what the problem is. See err(3) for more
|
||||
# information."
|
||||
# (and 'args' is a 1-tuple containing the above
|
||||
# 3-tuple...)
|
||||
ssl_lib, ssl_func, ssl_reason = fail.value.args[0][0]
|
||||
self.log.error("TLS failure: {reason}", reason=ssl_reason)
|
||||
else:
|
||||
self.log.error(
|
||||
'Connection failed: {error}',
|
||||
error=txaio.failure_message(fail),
|
||||
)
|
||||
|
||||
if self._is_fatal is None:
|
||||
is_fatal = False
|
||||
else:
|
||||
is_fatal = self._is_fatal(fail.value)
|
||||
if is_fatal:
|
||||
self.log.info("Error was fatal; failing transport")
|
||||
transport_candidate[0].failed()
|
||||
|
||||
txaio.call_later(0, transport_check, None)
|
||||
return
|
||||
|
||||
def notify_connect_error(fail):
|
||||
chain_f = txaio.create_future()
|
||||
# hmm, if connectfailure took a _Transport instead of
|
||||
# (or in addition to?) self it could .failed() the
|
||||
# transport and we could do away with the is_fatal
|
||||
# listener?
|
||||
handler_f = self.fire('connectfailure', self, fail.value)
|
||||
txaio.add_callbacks(
|
||||
handler_f,
|
||||
lambda _: txaio.reject(chain_f, fail),
|
||||
lambda _: txaio.reject(chain_f, fail)
|
||||
)
|
||||
return chain_f
|
||||
|
||||
def connect_error(fail):
|
||||
notify_f = notify_connect_error(fail)
|
||||
txaio.add_callbacks(notify_f, None, handle_connect_error)
|
||||
|
||||
def session_done(x):
|
||||
txaio.resolve(self._done_f, None)
|
||||
|
||||
connect_f = txaio.as_future(
|
||||
self._connect_once,
|
||||
loop, transport_candidate[0],
|
||||
)
|
||||
txaio.add_callbacks(connect_f, session_done, connect_error)
|
||||
|
||||
def transport_check(_):
|
||||
self.log.debug('Entering re-connect loop')
|
||||
|
||||
if not self._can_reconnect():
|
||||
err_msg = "Component failed: Exhausted all transport connect attempts"
|
||||
self.log.info(err_msg)
|
||||
try:
|
||||
raise RuntimeError(err_msg)
|
||||
except RuntimeError as e:
|
||||
txaio.reject(self._done_f, e)
|
||||
return
|
||||
|
||||
while True:
|
||||
|
||||
transport = next(transport_gen)
|
||||
|
||||
if transport.can_reconnect():
|
||||
transport_candidate[0] = transport
|
||||
break
|
||||
|
||||
delay = transport.next_delay()
|
||||
self.log.warn(
|
||||
'trying transport {transport_idx} ("{transport_url}") using connect delay {transport_delay}',
|
||||
transport_idx=transport.idx,
|
||||
transport_url=transport.url,
|
||||
transport_delay=delay,
|
||||
)
|
||||
|
||||
self._delay_f = txaio.sleep(delay)
|
||||
txaio.add_callbacks(self._delay_f, attempt_connect, error)
|
||||
|
||||
# issue our first event, then start reconnect loop
|
||||
start_f = self.fire('start', loop, self)
|
||||
txaio.add_callbacks(start_f, transport_check, error)
|
||||
return self._done_f
|
||||
|
||||
def stop(self):
|
||||
self._stopping = True
|
||||
if self._session and self._session.is_attached():
|
||||
return self._session.leave()
|
||||
elif self._delay_f:
|
||||
# This cancel request will actually call the "error" callback of
|
||||
# the _delay_f future. Nothing to worry about.
|
||||
return txaio.as_future(txaio.cancel, self._delay_f)
|
||||
# if (for some reason -- should we log warning here to figure
|
||||
# out if this can evern happen?) we've not fired _done_f, we
|
||||
# do that now (causing our "main" to exit, and thus react() to
|
||||
# quit)
|
||||
if not txaio.is_called(self._done_f):
|
||||
txaio.resolve(self._done_f, None)
|
||||
return txaio.create_future_success(None)
|
||||
|
||||
def _connect_once(self, reactor, transport):
|
||||
|
||||
self.log.info(
|
||||
'connecting once using transport type "{transport_type}" '
|
||||
'over endpoint "{endpoint_desc}"',
|
||||
transport_type=transport.type,
|
||||
endpoint_desc=transport.describe_endpoint(),
|
||||
)
|
||||
|
||||
done = txaio.create_future()
|
||||
|
||||
# factory for ISession objects
|
||||
def create_session():
|
||||
cfg = ComponentConfig(self._realm, self._extra)
|
||||
try:
|
||||
self._session = session = self.session_factory(cfg)
|
||||
for auth_name, auth_config in self._authentication.items():
|
||||
if isinstance(auth_config, IAuthenticator):
|
||||
session.add_authenticator(auth_config)
|
||||
else:
|
||||
authenticator = create_authenticator(auth_name, **auth_config)
|
||||
session.add_authenticator(authenticator)
|
||||
|
||||
except Exception as e:
|
||||
# couldn't instantiate session calls, which is fatal.
|
||||
# let the reconnection logic deal with that
|
||||
f = txaio.create_failure(e)
|
||||
txaio.reject(done, f)
|
||||
raise
|
||||
else:
|
||||
# hook up the listener to the parent so we can bubble
|
||||
# up events happning on the session onto the
|
||||
# connection. This lets you do component.on('join',
|
||||
# cb) which will work just as if you called
|
||||
# session.on('join', cb) for every session created.
|
||||
session._parent = self
|
||||
|
||||
# listen on leave events; if we get errors
|
||||
# (e.g. no_such_realm), an on_leave can happen without
|
||||
# an on_join before
|
||||
def on_leave(session, details):
|
||||
self.log.info(
|
||||
"session leaving '{details.reason}'",
|
||||
details=details,
|
||||
)
|
||||
if not txaio.is_called(done):
|
||||
if details.reason in ["wamp.close.normal", "wamp.close.goodbye_and_out"]:
|
||||
txaio.resolve(done, None)
|
||||
else:
|
||||
f = txaio.create_failure(
|
||||
ApplicationError(details.reason, details.message)
|
||||
)
|
||||
txaio.reject(done, f)
|
||||
session.on('leave', on_leave)
|
||||
|
||||
# if we were given a "main" procedure, we run through
|
||||
# it completely (i.e. until its Deferred fires) and
|
||||
# then disconnect this session
|
||||
def on_join(session, details):
|
||||
transport.reset()
|
||||
transport.connect_sucesses += 1
|
||||
self.log.debug("session on_join: {details}", details=details)
|
||||
d = txaio.as_future(self._entry, reactor, session)
|
||||
|
||||
def main_success(_):
|
||||
self.log.debug("main_success")
|
||||
|
||||
def leave():
|
||||
try:
|
||||
session.leave()
|
||||
except SessionNotReady:
|
||||
# someone may have already called
|
||||
# leave()
|
||||
pass
|
||||
txaio.call_later(0, leave)
|
||||
|
||||
def main_error(err):
|
||||
self.log.debug("main_error: {err}", err=err)
|
||||
txaio.reject(done, err)
|
||||
session.disconnect()
|
||||
txaio.add_callbacks(d, main_success, main_error)
|
||||
if self._entry is not None:
|
||||
session.on('join', on_join)
|
||||
|
||||
# listen on disconnect events. Note that in case we
|
||||
# had a "main" procedure, we could have already
|
||||
# resolve()'d our "done" future
|
||||
def on_disconnect(session, was_clean):
|
||||
self.log.debug(
|
||||
"session on_disconnect: was_clean={was_clean}",
|
||||
was_clean=was_clean,
|
||||
)
|
||||
if not txaio.is_called(done):
|
||||
if not was_clean:
|
||||
self.log.warn(
|
||||
"Session disconnected uncleanly"
|
||||
)
|
||||
else:
|
||||
# eg the session has left the realm, and the transport was properly
|
||||
# shut down. successfully finish the connection
|
||||
txaio.resolve(done, None)
|
||||
session.on('disconnect', on_disconnect)
|
||||
|
||||
# return the fresh session object
|
||||
return session
|
||||
|
||||
transport.connect_attempts += 1
|
||||
|
||||
d = txaio.as_future(
|
||||
self._connect_transport,
|
||||
reactor, transport, create_session, done,
|
||||
)
|
||||
|
||||
def on_error(err):
|
||||
"""
|
||||
this may seem redundant after looking at _connect_transport, but
|
||||
it will handle a case where something goes wrong in
|
||||
_connect_transport itself -- as the only connect our
|
||||
caller has is the 'done' future
|
||||
"""
|
||||
transport.connect_failures += 1
|
||||
# something bad has happened, and maybe didn't get caught
|
||||
# upstream yet
|
||||
if not txaio.is_called(done):
|
||||
txaio.reject(done, err)
|
||||
txaio.add_callbacks(d, None, on_error)
|
||||
|
||||
return done
|
||||
|
||||
def on_join(self, fn):
|
||||
"""
|
||||
A decorator as a shortcut for listening for 'join' events.
|
||||
|
||||
For example::
|
||||
|
||||
@component.on_join
|
||||
def joined(session, details):
|
||||
print("Session {} joined: {}".format(session, details))
|
||||
"""
|
||||
self.on('join', fn)
|
||||
|
||||
def on_leave(self, fn):
|
||||
"""
|
||||
A decorator as a shortcut for listening for 'leave' events.
|
||||
"""
|
||||
self.on('leave', fn)
|
||||
|
||||
def on_connect(self, fn):
|
||||
"""
|
||||
A decorator as a shortcut for listening for 'connect' events.
|
||||
"""
|
||||
self.on('connect', fn)
|
||||
|
||||
def on_disconnect(self, fn):
|
||||
"""
|
||||
A decorator as a shortcut for listening for 'disconnect' events.
|
||||
"""
|
||||
self.on('disconnect', fn)
|
||||
|
||||
def on_ready(self, fn):
|
||||
"""
|
||||
A decorator as a shortcut for listening for 'ready' events.
|
||||
"""
|
||||
self.on('ready', fn)
|
||||
|
||||
def on_connectfailure(self, fn):
|
||||
"""
|
||||
A decorator as a shortcut for listening for 'connectfailure' events.
|
||||
"""
|
||||
self.on('connectfailure', fn)
|
||||
|
||||
|
||||
def _run(reactor, components, done_callback=None):
|
||||
"""
|
||||
Internal helper. Use "run" method from autobahn.twisted.wamp or
|
||||
autobahn.asyncio.wamp
|
||||
|
||||
This is the generic parts of the run() method so that there's very
|
||||
little code in the twisted/asyncio specific run() methods.
|
||||
|
||||
This is called by react() (or run_until_complete() so any errors
|
||||
coming out of this should be handled properly. Logging will
|
||||
already be started.
|
||||
"""
|
||||
# let user pass a single component to run, too
|
||||
# XXX probably want IComponent? only demand it, here and below?
|
||||
if isinstance(components, Component):
|
||||
components = [components]
|
||||
|
||||
if type(components) != list:
|
||||
raise ValueError(
|
||||
'"components" must be a list of Component objects - encountered'
|
||||
' {0}'.format(type(components))
|
||||
)
|
||||
|
||||
for c in components:
|
||||
if not isinstance(c, Component):
|
||||
raise ValueError(
|
||||
'"components" must be a list of Component objects - encountered'
|
||||
'item of type {0}'.format(type(c))
|
||||
)
|
||||
|
||||
# validation complete; proceed with startup
|
||||
log = txaio.make_logger()
|
||||
|
||||
def component_success(comp, arg):
|
||||
log.debug("Component '{c}' successfully completed: {arg}", c=comp, arg=arg)
|
||||
return arg
|
||||
|
||||
def component_failure(comp, f):
|
||||
log.error("Component '{c}' error: {msg}", c=comp, msg=txaio.failure_message(f))
|
||||
log.debug("Component error: {tb}", tb=txaio.failure_format_traceback(f))
|
||||
# double-check: is a component-failure still fatal to the
|
||||
# startup process (because we passed consume_exception=False
|
||||
# to gather() below?)
|
||||
return None
|
||||
|
||||
def component_start(comp):
|
||||
# the future from start() errbacks if we fail, or callbacks
|
||||
# when the component is considered "done" (so maybe never)
|
||||
d = txaio.as_future(comp.start, reactor)
|
||||
txaio.add_callbacks(
|
||||
d,
|
||||
partial(component_success, comp),
|
||||
partial(component_failure, comp),
|
||||
)
|
||||
return d
|
||||
|
||||
# note that these are started in parallel -- maybe we want to add
|
||||
# a "connected" signal to components so we could start them in the
|
||||
# order they're given to run() as "a" solution to dependencies.
|
||||
dl = []
|
||||
for comp in components:
|
||||
d = component_start(comp)
|
||||
dl.append(d)
|
||||
done_d = txaio.gather(dl, consume_exceptions=False)
|
||||
|
||||
if done_callback:
|
||||
def all_done(arg):
|
||||
log.debug("All components ended; stopping reactor")
|
||||
done_callback(reactor, arg)
|
||||
txaio.add_callbacks(done_d, all_done, all_done)
|
||||
|
||||
return done_d
|
||||
268
.venv/lib/python3.12/site-packages/autobahn/wamp/cryptobox.py
Normal file
268
.venv/lib/python3.12/site-packages/autobahn/wamp/cryptobox.py
Normal file
@@ -0,0 +1,268 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 autobahn.util import public
|
||||
from autobahn.wamp.interfaces import IPayloadCodec
|
||||
from autobahn.wamp.types import EncodedPayload
|
||||
from autobahn.wamp.serializer import _dumps as _json_dumps
|
||||
from autobahn.wamp.serializer import _loads as _json_loads
|
||||
|
||||
__all__ = [
|
||||
'HAS_CRYPTOBOX',
|
||||
'EncodedPayload'
|
||||
]
|
||||
|
||||
try:
|
||||
# try to import everything we need for WAMP-cryptobox
|
||||
from nacl.encoding import Base64Encoder, RawEncoder, HexEncoder
|
||||
from nacl.public import PrivateKey, PublicKey, Box
|
||||
from nacl.utils import random
|
||||
from pytrie import StringTrie
|
||||
except ImportError:
|
||||
HAS_CRYPTOBOX = False
|
||||
else:
|
||||
HAS_CRYPTOBOX = True
|
||||
__all__.extend(['Key', 'KeyRing'])
|
||||
|
||||
|
||||
if HAS_CRYPTOBOX:
|
||||
|
||||
@public
|
||||
class Key(object):
|
||||
"""
|
||||
Holds originator and responder keys for an URI.
|
||||
|
||||
The originator is either a caller or a publisher. The responder is either a callee or subscriber.
|
||||
"""
|
||||
|
||||
def __init__(self, originator_priv=None, originator_pub=None, responder_priv=None, responder_pub=None):
|
||||
# the originator private and public keys, as available
|
||||
if originator_priv:
|
||||
self.originator_priv = PrivateKey(originator_priv, encoder=Base64Encoder)
|
||||
else:
|
||||
self.originator_priv = None
|
||||
|
||||
if self.originator_priv:
|
||||
self.originator_pub = self.originator_priv.public_key
|
||||
assert(originator_pub is None or originator_pub == self.originator_pub)
|
||||
else:
|
||||
self.originator_pub = PublicKey(originator_pub, encoder=Base64Encoder)
|
||||
|
||||
# the responder private and public keys, as available
|
||||
if responder_priv:
|
||||
self.responder_priv = PrivateKey(responder_priv, encoder=Base64Encoder)
|
||||
else:
|
||||
self.responder_priv = None
|
||||
|
||||
if self.responder_priv:
|
||||
self.responder_pub = self.responder_priv.public_key
|
||||
assert(responder_pub is None or responder_pub == self.responder_pub)
|
||||
else:
|
||||
self.responder_pub = PublicKey(responder_pub, encoder=Base64Encoder)
|
||||
|
||||
# this crypto box is for originators (callers, publishers):
|
||||
#
|
||||
# 1. _encrypting_ WAMP messages outgoing from originators: CALL*, PUBLISH*
|
||||
# 2. _decrypting_ WAMP messages incoming to originators: RESULT*, ERROR
|
||||
#
|
||||
if self.originator_priv and self.responder_pub:
|
||||
self.originator_box = Box(self.originator_priv, self.responder_pub)
|
||||
else:
|
||||
self.originator_box = None
|
||||
|
||||
# this crypto box is for responders (callees, subscribers):
|
||||
#
|
||||
# 1. _decrypting_ WAMP messages incoming to responders: INVOCATION*, EVENT*
|
||||
# 2. _encrypting_ WAMP messages outgoing from responders: YIELD*, ERROR
|
||||
#
|
||||
if self.responder_priv and self.originator_pub:
|
||||
self.responder_box = Box(self.responder_priv, self.originator_pub)
|
||||
else:
|
||||
self.responder_box = None
|
||||
|
||||
if not (self.originator_box or self.responder_box):
|
||||
raise Exception("insufficient keys provided for at least originator or responder role")
|
||||
|
||||
@public
|
||||
class SymKey(object):
|
||||
"""
|
||||
Holds a symmetric key for an URI.
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
pass
|
||||
|
||||
@public
|
||||
class KeyRing(object):
|
||||
"""
|
||||
A keyring holds (cryptobox) public-private key pairs for use with WAMP-cryptobox payload
|
||||
encryption. The keyring can be set on a WAMP session and then transparently will get used
|
||||
for encrypting and decrypting WAMP message payloads.
|
||||
"""
|
||||
|
||||
@public
|
||||
def __init__(self, default_key=None):
|
||||
"""
|
||||
|
||||
Create a new key ring to hold public and private keys mapped from an URI space.
|
||||
"""
|
||||
assert(default_key is None or isinstance(default_key, Key) or type(default_key == str))
|
||||
self._uri_to_key = StringTrie()
|
||||
if type(default_key) == str:
|
||||
default_key = Key(originator_priv=default_key, responder_priv=default_key)
|
||||
self._default_key = default_key
|
||||
|
||||
@public
|
||||
def generate_key(self):
|
||||
"""
|
||||
Generate a new private key and return a pair with the base64 encodings
|
||||
of (priv_key, pub_key).
|
||||
"""
|
||||
key = PrivateKey.generate()
|
||||
priv_key = key.encode(encoder=Base64Encoder)
|
||||
pub_key = key.public_key.encode(encoder=Base64Encoder)
|
||||
return priv_key.decode('ascii'), pub_key.decode('ascii')
|
||||
|
||||
@public
|
||||
def generate_key_hex(self):
|
||||
"""
|
||||
Generate a new private key and return a pair with the hex encodings
|
||||
of (priv_key, pub_key).
|
||||
"""
|
||||
key = PrivateKey.generate()
|
||||
priv_key = key.encode(encoder=HexEncoder)
|
||||
pub_key = key.public_key.encode(encoder=HexEncoder)
|
||||
return priv_key.decode('ascii'), pub_key.decode('ascii')
|
||||
|
||||
@public
|
||||
def set_key(self, uri, key):
|
||||
"""
|
||||
Add a key set for a given URI.
|
||||
"""
|
||||
assert(type(uri) == str)
|
||||
assert(key is None or isinstance(key, Key) or type(key) == str)
|
||||
if type(key) == str:
|
||||
key = Key(originator_priv=key, responder_priv=key)
|
||||
if uri == '':
|
||||
self._default_key = key
|
||||
else:
|
||||
if key is None:
|
||||
if uri in self._uri_to_key:
|
||||
del self._uri_to_key[uri]
|
||||
else:
|
||||
self._uri_to_key[uri] = key
|
||||
|
||||
@public
|
||||
def rotate_key(self, uri):
|
||||
assert(type(uri) == str)
|
||||
if uri in self._uri_to_key:
|
||||
self._uri_to_key[uri].rotate()
|
||||
else:
|
||||
self._uri_to_key[uri].rotate()
|
||||
|
||||
def _get_box(self, is_originating, uri, match_exact=False):
|
||||
try:
|
||||
if match_exact:
|
||||
key = self._uri_to_key[uri]
|
||||
else:
|
||||
key = self._uri_to_key.longest_prefix_value(uri)
|
||||
except KeyError:
|
||||
if self._default_key:
|
||||
key = self._default_key
|
||||
else:
|
||||
return None
|
||||
|
||||
if is_originating:
|
||||
return key.originator_box
|
||||
else:
|
||||
return key.responder_box
|
||||
|
||||
@public
|
||||
def encode(self, is_originating, uri, args=None, kwargs=None):
|
||||
"""
|
||||
Encrypt the given WAMP URI, args and kwargs into an EncodedPayload instance, or None
|
||||
if the URI should not be encrypted.
|
||||
"""
|
||||
assert(type(is_originating) == bool)
|
||||
assert(type(uri) == str)
|
||||
assert(args is None or type(args) in (list, tuple))
|
||||
assert(kwargs is None or type(kwargs) == dict)
|
||||
|
||||
box = self._get_box(is_originating, uri)
|
||||
|
||||
if not box:
|
||||
# if we didn't find a crypto box, then return None, which
|
||||
# signals that the payload travel unencrypted (normal)
|
||||
return None
|
||||
|
||||
payload = {
|
||||
'uri': uri,
|
||||
'args': args,
|
||||
'kwargs': kwargs
|
||||
}
|
||||
nonce = random(Box.NONCE_SIZE)
|
||||
payload_ser = _json_dumps(payload).encode('utf8')
|
||||
|
||||
payload_encr = box.encrypt(payload_ser, nonce, encoder=RawEncoder)
|
||||
|
||||
# above returns an instance of http://pynacl.readthedocs.io/en/latest/utils/#nacl.utils.EncryptedMessage
|
||||
# which is a bytes _subclass_! hence we apply bytes() to get at the underlying plain
|
||||
# bytes "scalar", which is the concatenation of `payload_encr.nonce + payload_encr.ciphertext`
|
||||
payload_bytes = bytes(payload_encr)
|
||||
payload_key = None
|
||||
|
||||
return EncodedPayload(payload_bytes, 'cryptobox', 'json', enc_key=payload_key)
|
||||
|
||||
@public
|
||||
def decode(self, is_originating, uri, encoded_payload):
|
||||
"""
|
||||
Decrypt the given WAMP URI and EncodedPayload into a tuple ``(uri, args, kwargs)``.
|
||||
"""
|
||||
assert(type(uri) == str)
|
||||
assert(isinstance(encoded_payload, EncodedPayload))
|
||||
assert(encoded_payload.enc_algo == 'cryptobox')
|
||||
|
||||
box = self._get_box(is_originating, uri)
|
||||
|
||||
if not box:
|
||||
raise Exception("received encrypted payload, but can't find key!")
|
||||
|
||||
payload_ser = box.decrypt(encoded_payload.payload, encoder=RawEncoder)
|
||||
|
||||
if encoded_payload.enc_serializer != 'json':
|
||||
raise Exception("received encrypted payload, but don't know how to process serializer '{}'".format(encoded_payload.enc_serializer))
|
||||
|
||||
payload = _json_loads(payload_ser.decode('utf8'))
|
||||
|
||||
uri = payload.get('uri', None)
|
||||
args = payload.get('args', None)
|
||||
kwargs = payload.get('kwargs', None)
|
||||
|
||||
return uri, args, kwargs
|
||||
|
||||
# A WAMP-cryptobox keyring can work as a codec for
|
||||
# payload transparency
|
||||
IPayloadCodec.register(KeyRing)
|
||||
1022
.venv/lib/python3.12/site-packages/autobahn/wamp/cryptosign.py
Normal file
1022
.venv/lib/python3.12/site-packages/autobahn/wamp/cryptosign.py
Normal file
File diff suppressed because it is too large
Load Diff
326
.venv/lib/python3.12/site-packages/autobahn/wamp/exception.py
Normal file
326
.venv/lib/python3.12/site-packages/autobahn/wamp/exception.py
Normal file
@@ -0,0 +1,326 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int 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 autobahn.util import public
|
||||
from autobahn.wamp.uri import error
|
||||
|
||||
__all__ = (
|
||||
'Error',
|
||||
'SessionNotReady',
|
||||
'SerializationError',
|
||||
'InvalidUriError',
|
||||
'ProtocolError',
|
||||
'TransportLost',
|
||||
'ApplicationError',
|
||||
'NotAuthorized',
|
||||
'InvalidUri',
|
||||
'InvalidPayload',
|
||||
'TypeCheckError',
|
||||
)
|
||||
|
||||
|
||||
@public
|
||||
class Error(RuntimeError):
|
||||
"""
|
||||
Base class for all exceptions related to WAMP.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class SessionNotReady(Error):
|
||||
"""
|
||||
The application tried to perform a WAMP interaction, but the
|
||||
session is not yet fully established.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class SerializationError(Error):
|
||||
"""
|
||||
Exception raised when the WAMP serializer could not serialize the
|
||||
application payload (``args`` or ``kwargs`` for ``CALL``, ``PUBLISH``, etc).
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class InvalidUriError(Error):
|
||||
"""
|
||||
Exception raised when an invalid WAMP URI was used.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class ProtocolError(Error):
|
||||
"""
|
||||
Exception raised when WAMP protocol was violated. Protocol errors
|
||||
are fatal and are handled by the WAMP implementation. They are
|
||||
not supposed to be handled at the application level.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class TransportLost(Error):
|
||||
"""
|
||||
Exception raised when the transport underlying the WAMP session
|
||||
was lost or is not connected.
|
||||
"""
|
||||
|
||||
|
||||
@public
|
||||
class ApplicationError(Error):
|
||||
"""
|
||||
Base class for all exceptions that can/may be handled
|
||||
at the application level.
|
||||
"""
|
||||
|
||||
INVALID_URI = "wamp.error.invalid_uri"
|
||||
"""
|
||||
Peer provided an incorrect URI for a URI-based attribute of a WAMP message
|
||||
such as a realm, topic or procedure.
|
||||
"""
|
||||
|
||||
INVALID_PAYLOAD = "wamp.error.invalid_payload"
|
||||
"""
|
||||
The application payload could not be serialized.
|
||||
"""
|
||||
|
||||
PAYLOAD_SIZE_EXCEEDED = "wamp.error.payload_size_exceeded"
|
||||
"""
|
||||
The application payload could not be transported becuase the serialized/framed payload
|
||||
exceeds the transport limits.
|
||||
"""
|
||||
|
||||
NO_SUCH_PROCEDURE = "wamp.error.no_such_procedure"
|
||||
"""
|
||||
A Dealer could not perform a call, since not procedure is currently registered
|
||||
under the given URI.
|
||||
"""
|
||||
|
||||
PROCEDURE_ALREADY_EXISTS = "wamp.error.procedure_already_exists"
|
||||
"""
|
||||
A procedure could not be registered, since a procedure with the given URI is
|
||||
already registered.
|
||||
"""
|
||||
|
||||
PROCEDURE_EXISTS_INVOCATION_POLICY_CONFLICT = "wamp.error.procedure_exists_with_different_invocation_policy"
|
||||
"""
|
||||
A procedure could not be registered, since a procedure with the given URI is
|
||||
already registered, and the registration has a conflicting invocation policy.
|
||||
"""
|
||||
|
||||
NO_SUCH_REGISTRATION = "wamp.error.no_such_registration"
|
||||
"""
|
||||
A Dealer could not perform a unregister, since the given registration is not active.
|
||||
"""
|
||||
|
||||
NO_SUCH_SUBSCRIPTION = "wamp.error.no_such_subscription"
|
||||
"""
|
||||
A Broker could not perform a unsubscribe, since the given subscription is not active.
|
||||
"""
|
||||
|
||||
NO_SUCH_SESSION = "wamp.error.no_such_session"
|
||||
"""
|
||||
A router could not perform an operation, since a session ID specified was non-existant.
|
||||
"""
|
||||
|
||||
INVALID_ARGUMENT = "wamp.error.invalid_argument"
|
||||
"""
|
||||
A call failed, since the given argument types or values are not acceptable to the
|
||||
called procedure - in which case the *Callee* may throw this error. Or a Router
|
||||
performing *payload validation* checked the payload (``args`` / ``kwargs``) of a call,
|
||||
call result, call error or publish, and the payload did not conform.
|
||||
"""
|
||||
|
||||
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
|
||||
SYSTEM_SHUTDOWN = "wamp.error.system_shutdown"
|
||||
"""
|
||||
The *Peer* is shutting down completely - used as a ``GOODBYE`` (or ``ABORT``) reason.
|
||||
"""
|
||||
|
||||
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
|
||||
CLOSE_REALM = "wamp.error.close_realm"
|
||||
"""
|
||||
The *Peer* want to leave the realm - used as a ``GOODBYE`` reason.
|
||||
"""
|
||||
|
||||
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
|
||||
GOODBYE_AND_OUT = "wamp.error.goodbye_and_out"
|
||||
"""
|
||||
A *Peer* acknowledges ending of a session - used as a ``GOOBYE`` reply reason.
|
||||
"""
|
||||
|
||||
NOT_AUTHORIZED = "wamp.error.not_authorized"
|
||||
"""
|
||||
A call, register, publish or subscribe failed, since the session is not authorized
|
||||
to perform the operation.
|
||||
"""
|
||||
|
||||
AUTHORIZATION_FAILED = "wamp.error.authorization_failed"
|
||||
"""
|
||||
A Dealer or Broker could not determine if the *Peer* is authorized to perform
|
||||
a join, call, register, publish or subscribe, since the authorization operation
|
||||
*itself* failed. E.g. a custom authorizer did run into an error.
|
||||
"""
|
||||
|
||||
AUTHENTICATION_FAILED = "wamp.error.authentication_failed"
|
||||
"""
|
||||
Something failed with the authentication itself, that is, authentication could
|
||||
not run to end.
|
||||
"""
|
||||
|
||||
NO_AUTH_METHOD = "wamp.error.no_auth_method"
|
||||
"""
|
||||
No authentication method the peer offered is available or active.
|
||||
"""
|
||||
|
||||
NO_SUCH_REALM = "wamp.error.no_such_realm"
|
||||
"""
|
||||
Peer wanted to join a non-existing realm (and the *Router* did not allow to auto-create
|
||||
the realm).
|
||||
"""
|
||||
|
||||
NO_SUCH_ROLE = "wamp.error.no_such_role"
|
||||
"""
|
||||
A *Peer* was to be authenticated under a Role that does not (or no longer) exists on the Router.
|
||||
For example, the *Peer* was successfully authenticated, but the Role configured does not
|
||||
exists - hence there is some misconfiguration in the Router.
|
||||
"""
|
||||
|
||||
NO_SUCH_PRINCIPAL = "wamp.error.no_such_principal"
|
||||
"""
|
||||
A *Peer* was authenticated for an authid that does not or longer exists.
|
||||
"""
|
||||
|
||||
CANCELED = "wamp.error.canceled"
|
||||
"""
|
||||
A Dealer or Callee canceled a call previously issued (WAMP AP).
|
||||
"""
|
||||
|
||||
TIMEOUT = "wamp.error.timeout"
|
||||
"""
|
||||
A pending (in-flight) call was timed out.
|
||||
"""
|
||||
|
||||
# FIXME: this currently isn't used neither in Autobahn nor Crossbar. Check!
|
||||
NO_ELIGIBLE_CALLEE = "wamp.error.no_eligible_callee"
|
||||
"""
|
||||
A *Dealer* could not perform a call, since a procedure with the given URI is registered,
|
||||
but *Callee Black- and Whitelisting* and/or *Caller Exclusion* lead to the
|
||||
exclusion of (any) *Callee* providing the procedure (WAMP AP).
|
||||
"""
|
||||
|
||||
ENC_NO_PAYLOAD_CODEC = "wamp.error.no_payload_codec"
|
||||
"""
|
||||
WAMP message in payload transparency mode received, but no codec set
|
||||
or codec did not decode the payload.
|
||||
"""
|
||||
|
||||
ENC_TRUSTED_URI_MISMATCH = "wamp.error.encryption.trusted_uri_mismatch"
|
||||
"""
|
||||
WAMP-cryptobox application payload end-to-end encryption error.
|
||||
"""
|
||||
|
||||
ENC_DECRYPT_ERROR = "wamp.error.encryption.decrypt_error"
|
||||
"""
|
||||
WAMP-cryptobox application payload end-to-end encryption error.
|
||||
"""
|
||||
|
||||
TYPE_CHECK_ERROR = "wamp.error.type_check_error"
|
||||
"""
|
||||
WAMP procedure called with wrong argument types or subscription published
|
||||
with wrong argument types.
|
||||
"""
|
||||
|
||||
def __init__(self, error, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param error: The URI of the error that occurred, e.g. ``wamp.error.not_authorized``.
|
||||
:type error: str
|
||||
"""
|
||||
Exception.__init__(self, *args)
|
||||
self.kwargs = kwargs
|
||||
self.error = error
|
||||
self.enc_algo = kwargs.pop('enc_algo', None)
|
||||
self.callee = kwargs.pop('callee', None)
|
||||
self.callee_authid = kwargs.pop('callee_authid', None)
|
||||
self.callee_authrole = kwargs.pop('callee_authrole', None)
|
||||
self.forward_for = kwargs.pop('forward_for', None)
|
||||
|
||||
@public
|
||||
def error_message(self):
|
||||
"""
|
||||
Get the error message of this exception.
|
||||
|
||||
:returns: The error message.
|
||||
:rtype: str
|
||||
"""
|
||||
return '{0}: {1}'.format(
|
||||
self.error,
|
||||
' '.join([str(a) for a in self.args]),
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
if self.kwargs and 'traceback' in self.kwargs:
|
||||
tb = ':\n' + self.kwargs.pop('traceback') + '\n'
|
||||
self.kwargs['traceback'] = '...'
|
||||
else:
|
||||
tb = ''
|
||||
return "ApplicationError(error=<{0}>, args={1}, kwargs={2}, enc_algo={3}, callee={4}, callee_authid={5}, callee_authrole={6}, forward_for={7}){8}".format(
|
||||
self.error, list(self.args), self.kwargs, self.enc_algo, self.callee, self.callee_authid, self.callee_authrole, self.forward_for, tb)
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
|
||||
@error(ApplicationError.NOT_AUTHORIZED)
|
||||
class NotAuthorized(Exception):
|
||||
"""
|
||||
Not authorized to perform the respective action.
|
||||
"""
|
||||
|
||||
|
||||
@error(ApplicationError.INVALID_URI)
|
||||
class InvalidUri(Exception):
|
||||
"""
|
||||
The URI for a topic, procedure or error is not a valid WAMP URI.
|
||||
"""
|
||||
|
||||
|
||||
@error(ApplicationError.INVALID_PAYLOAD)
|
||||
class InvalidPayload(Exception):
|
||||
"""
|
||||
The URI for a topic, procedure or error is not a valid WAMP URI.
|
||||
"""
|
||||
|
||||
|
||||
class TypeCheckError(ApplicationError):
|
||||
"""
|
||||
The URI for a topic published with invalid argument types or a
|
||||
procedure called with invalid arguments types.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(ApplicationError.TYPE_CHECK_ERROR, *args, **kwargs)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,52 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: wamp
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Map(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Map()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsMap(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Map
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Map
|
||||
def Key(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Map
|
||||
def Value(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
def MapStart(builder): builder.StartObject(2)
|
||||
def Start(builder):
|
||||
return MapStart(builder)
|
||||
def MapAddKey(builder, key): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(key), 0)
|
||||
def AddKey(builder, key):
|
||||
return MapAddKey(builder, key)
|
||||
def MapAddValue(builder, value): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(value), 0)
|
||||
def AddValue(builder, value):
|
||||
return MapAddValue(builder, value)
|
||||
def MapEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return MapEnd(builder)
|
||||
@@ -0,0 +1,32 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: wamp
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Void(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Void()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsVoid(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Void
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def VoidStart(builder): builder.StartObject(0)
|
||||
def Start(builder):
|
||||
return VoidStart(builder)
|
||||
def VoidEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return VoidEnd(builder)
|
||||
@@ -0,0 +1,62 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Abort(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Abort()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAbort(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Abort
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Abort
|
||||
def Session(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Abort
|
||||
def Reason(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Abort
|
||||
def Message(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
def AbortStart(builder): builder.StartObject(3)
|
||||
def Start(builder):
|
||||
return AbortStart(builder)
|
||||
def AbortAddSession(builder, session): builder.PrependUint64Slot(0, session, 0)
|
||||
def AddSession(builder, session):
|
||||
return AbortAddSession(builder, session)
|
||||
def AbortAddReason(builder, reason): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(reason), 0)
|
||||
def AddReason(builder, reason):
|
||||
return AbortAddReason(builder, reason)
|
||||
def AbortAddMessage(builder, message): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(message), 0)
|
||||
def AddMessage(builder, message):
|
||||
return AbortAddMessage(builder, message)
|
||||
def AbortEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AbortEnd(builder)
|
||||
@@ -0,0 +1,31 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class AnyMessage(object):
|
||||
NONE = 0
|
||||
Hello = 1
|
||||
Welcome = 2
|
||||
Abort = 3
|
||||
Challenge = 4
|
||||
Authenticate = 5
|
||||
Goodbye = 6
|
||||
Error = 7
|
||||
Publish = 8
|
||||
Published = 9
|
||||
Subscribe = 10
|
||||
Subscribed = 11
|
||||
Unsubscribe = 12
|
||||
Unsubscribed = 13
|
||||
Event = 14
|
||||
EventReceived = 15
|
||||
Call = 16
|
||||
Cancel = 17
|
||||
Result = 18
|
||||
Register = 19
|
||||
Registered = 20
|
||||
Unregister = 21
|
||||
Unregistered = 22
|
||||
Invocation = 23
|
||||
Interrupt = 24
|
||||
Yield = 25
|
||||
@@ -0,0 +1,72 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthCraChallenge(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthCraChallenge()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthCraChallenge(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthCraChallenge
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# AuthCraChallenge
|
||||
def Challenge(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# AuthCraChallenge
|
||||
def Salt(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# AuthCraChallenge
|
||||
def Iterations(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos)
|
||||
return 1000
|
||||
|
||||
# AuthCraChallenge
|
||||
def Keylen(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 32
|
||||
|
||||
def AuthCraChallengeStart(builder): builder.StartObject(4)
|
||||
def Start(builder):
|
||||
return AuthCraChallengeStart(builder)
|
||||
def AuthCraChallengeAddChallenge(builder, challenge): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(challenge), 0)
|
||||
def AddChallenge(builder, challenge):
|
||||
return AuthCraChallengeAddChallenge(builder, challenge)
|
||||
def AuthCraChallengeAddSalt(builder, salt): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(salt), 0)
|
||||
def AddSalt(builder, salt):
|
||||
return AuthCraChallengeAddSalt(builder, salt)
|
||||
def AuthCraChallengeAddIterations(builder, iterations): builder.PrependUint32Slot(2, iterations, 1000)
|
||||
def AddIterations(builder, iterations):
|
||||
return AuthCraChallengeAddIterations(builder, iterations)
|
||||
def AuthCraChallengeAddKeylen(builder, keylen): builder.PrependUint8Slot(3, keylen, 32)
|
||||
def AddKeylen(builder, keylen):
|
||||
return AuthCraChallengeAddKeylen(builder, keylen)
|
||||
def AuthCraChallengeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthCraChallengeEnd(builder)
|
||||
@@ -0,0 +1,32 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthCraRequest(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthCraRequest()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthCraRequest(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthCraRequest
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def AuthCraRequestStart(builder): builder.StartObject(0)
|
||||
def Start(builder):
|
||||
return AuthCraRequestStart(builder)
|
||||
def AuthCraRequestEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthCraRequestEnd(builder)
|
||||
@@ -0,0 +1,32 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthCraWelcome(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthCraWelcome()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthCraWelcome(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthCraWelcome
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def AuthCraWelcomeStart(builder): builder.StartObject(0)
|
||||
def Start(builder):
|
||||
return AuthCraWelcomeStart(builder)
|
||||
def AuthCraWelcomeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthCraWelcomeEnd(builder)
|
||||
@@ -0,0 +1,42 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthCryptosignChallenge(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthCryptosignChallenge()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthCryptosignChallenge(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthCryptosignChallenge
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# AuthCryptosignChallenge
|
||||
def ChannelBinding(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
def AuthCryptosignChallengeStart(builder): builder.StartObject(1)
|
||||
def Start(builder):
|
||||
return AuthCryptosignChallengeStart(builder)
|
||||
def [AWS-SECRET-REMOVED](builder, channelBinding): builder.PrependUint8Slot(0, channelBinding, 0)
|
||||
def AddChannelBinding(builder, channelBinding):
|
||||
return [AWS-SECRET-REMOVED](builder, channelBinding)
|
||||
def AuthCryptosignChallengeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthCryptosignChallengeEnd(builder)
|
||||
@@ -0,0 +1,52 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthCryptosignRequest(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthCryptosignRequest()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthCryptosignRequest(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthCryptosignRequest
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# AuthCryptosignRequest
|
||||
def Pubkey(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# AuthCryptosignRequest
|
||||
def ChannelBinding(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
def AuthCryptosignRequestStart(builder): builder.StartObject(2)
|
||||
def Start(builder):
|
||||
return AuthCryptosignRequestStart(builder)
|
||||
def AuthCryptosignRequestAddPubkey(builder, pubkey): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(pubkey), 0)
|
||||
def AddPubkey(builder, pubkey):
|
||||
return AuthCryptosignRequestAddPubkey(builder, pubkey)
|
||||
def AuthCryptosignRequestAddChannelBinding(builder, channelBinding): builder.PrependUint8Slot(1, channelBinding, 0)
|
||||
def AddChannelBinding(builder, channelBinding):
|
||||
return AuthCryptosignRequestAddChannelBinding(builder, channelBinding)
|
||||
def AuthCryptosignRequestEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthCryptosignRequestEnd(builder)
|
||||
@@ -0,0 +1,32 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthCryptosignWelcome(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthCryptosignWelcome()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthCryptosignWelcome(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthCryptosignWelcome
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def AuthCryptosignWelcomeStart(builder): builder.StartObject(0)
|
||||
def Start(builder):
|
||||
return AuthCryptosignWelcomeStart(builder)
|
||||
def AuthCryptosignWelcomeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthCryptosignWelcomeEnd(builder)
|
||||
@@ -0,0 +1,10 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class AuthFactor(object):
|
||||
NONE = 0
|
||||
AuthTicketRequest = 1
|
||||
AuthCraRequest = 2
|
||||
AuthScramRequest = 3
|
||||
AuthCryptosignRequest = 4
|
||||
@@ -0,0 +1,12 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class AuthMethod(object):
|
||||
ANONYMOUS = 0
|
||||
COOKIE = 1
|
||||
TLS = 2
|
||||
TICKET = 3
|
||||
CRA = 4
|
||||
SCRAM = 5
|
||||
CRYPTOSIGN = 6
|
||||
@@ -0,0 +1,7 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class AuthMode(object):
|
||||
FIRST = 0
|
||||
MULTIFACTOR = 1
|
||||
@@ -0,0 +1,92 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthScramChallenge(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthScramChallenge()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthScramChallenge(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthScramChallenge
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# AuthScramChallenge
|
||||
def Nonce(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# AuthScramChallenge
|
||||
def Salt(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# AuthScramChallenge
|
||||
def Kdf(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 2
|
||||
|
||||
# AuthScramChallenge
|
||||
def Iterations(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# AuthScramChallenge
|
||||
def Memory(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# AuthScramChallenge
|
||||
def ChannelBinding(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
|
||||
|
||||
def AuthScramChallengeStart(builder): builder.StartObject(6)
|
||||
def Start(builder):
|
||||
return AuthScramChallengeStart(builder)
|
||||
def AuthScramChallengeAddNonce(builder, nonce): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(nonce), 0)
|
||||
def AddNonce(builder, nonce):
|
||||
return AuthScramChallengeAddNonce(builder, nonce)
|
||||
def AuthScramChallengeAddSalt(builder, salt): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(salt), 0)
|
||||
def AddSalt(builder, salt):
|
||||
return AuthScramChallengeAddSalt(builder, salt)
|
||||
def AuthScramChallengeAddKdf(builder, kdf): builder.PrependUint8Slot(2, kdf, 2)
|
||||
def AddKdf(builder, kdf):
|
||||
return AuthScramChallengeAddKdf(builder, kdf)
|
||||
def AuthScramChallengeAddIterations(builder, iterations): builder.PrependUint32Slot(3, iterations, 0)
|
||||
def AddIterations(builder, iterations):
|
||||
return AuthScramChallengeAddIterations(builder, iterations)
|
||||
def AuthScramChallengeAddMemory(builder, memory): builder.PrependUint32Slot(4, memory, 0)
|
||||
def AddMemory(builder, memory):
|
||||
return AuthScramChallengeAddMemory(builder, memory)
|
||||
def AuthScramChallengeAddChannelBinding(builder, channelBinding): builder.PrependUint8Slot(5, channelBinding, 0)
|
||||
def AddChannelBinding(builder, channelBinding):
|
||||
return AuthScramChallengeAddChannelBinding(builder, channelBinding)
|
||||
def AuthScramChallengeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthScramChallengeEnd(builder)
|
||||
@@ -0,0 +1,52 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthScramRequest(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthScramRequest()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthScramRequest(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthScramRequest
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# AuthScramRequest
|
||||
def Nonce(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# AuthScramRequest
|
||||
def ChannelBinding(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
def AuthScramRequestStart(builder): builder.StartObject(2)
|
||||
def Start(builder):
|
||||
return AuthScramRequestStart(builder)
|
||||
def AuthScramRequestAddNonce(builder, nonce): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(nonce), 0)
|
||||
def AddNonce(builder, nonce):
|
||||
return AuthScramRequestAddNonce(builder, nonce)
|
||||
def AuthScramRequestAddChannelBinding(builder, channelBinding): builder.PrependUint8Slot(1, channelBinding, 0)
|
||||
def AddChannelBinding(builder, channelBinding):
|
||||
return AuthScramRequestAddChannelBinding(builder, channelBinding)
|
||||
def AuthScramRequestEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthScramRequestEnd(builder)
|
||||
@@ -0,0 +1,42 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthScramWelcome(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthScramWelcome()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthScramWelcome(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthScramWelcome
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# AuthScramWelcome
|
||||
def Verifier(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
def AuthScramWelcomeStart(builder): builder.StartObject(1)
|
||||
def Start(builder):
|
||||
return AuthScramWelcomeStart(builder)
|
||||
def AuthScramWelcomeAddVerifier(builder, verifier): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(verifier), 0)
|
||||
def AddVerifier(builder, verifier):
|
||||
return AuthScramWelcomeAddVerifier(builder, verifier)
|
||||
def AuthScramWelcomeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthScramWelcomeEnd(builder)
|
||||
@@ -0,0 +1,32 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthTicketChallenge(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthTicketChallenge()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthTicketChallenge(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthTicketChallenge
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def AuthTicketChallengeStart(builder): builder.StartObject(0)
|
||||
def Start(builder):
|
||||
return AuthTicketChallengeStart(builder)
|
||||
def AuthTicketChallengeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthTicketChallengeEnd(builder)
|
||||
@@ -0,0 +1,32 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthTicketRequest(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthTicketRequest()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthTicketRequest(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthTicketRequest
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def AuthTicketRequestStart(builder): builder.StartObject(0)
|
||||
def Start(builder):
|
||||
return AuthTicketRequestStart(builder)
|
||||
def AuthTicketRequestEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthTicketRequestEnd(builder)
|
||||
@@ -0,0 +1,32 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class AuthTicketWelcome(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = AuthTicketWelcome()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthTicketWelcome(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# AuthTicketWelcome
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
def AuthTicketWelcomeStart(builder): builder.StartObject(0)
|
||||
def Start(builder):
|
||||
return AuthTicketWelcomeStart(builder)
|
||||
def AuthTicketWelcomeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthTicketWelcomeEnd(builder)
|
||||
@@ -0,0 +1,66 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Authenticate(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Authenticate()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsAuthenticate(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Authenticate
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Authenticate
|
||||
def Session(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Authenticate
|
||||
def Signature(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Authenticate
|
||||
def Extra(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from wamp.Map import Map
|
||||
obj = Map()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
def AuthenticateStart(builder): builder.StartObject(3)
|
||||
def Start(builder):
|
||||
return AuthenticateStart(builder)
|
||||
def AuthenticateAddSession(builder, session): builder.PrependUint64Slot(0, session, 0)
|
||||
def AddSession(builder, session):
|
||||
return AuthenticateAddSession(builder, session)
|
||||
def AuthenticateAddSignature(builder, signature): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(signature), 0)
|
||||
def AddSignature(builder, signature):
|
||||
return AuthenticateAddSignature(builder, signature)
|
||||
def AuthenticateAddExtra(builder, extra): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(extra), 0)
|
||||
def AddExtra(builder, extra):
|
||||
return AuthenticateAddExtra(builder, extra)
|
||||
def AuthenticateEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return AuthenticateEnd(builder)
|
||||
@@ -0,0 +1,172 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class BrokerFeatures(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = BrokerFeatures()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsBrokerFeatures(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# BrokerFeatures
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# BrokerFeatures
|
||||
def PublisherIdentification(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def PublisherExclusion(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def SubscriberBlackwhiteListing(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def PatternBasedSubscription(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def PublicationTrustlevels(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def SubscriptionRevocation(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def SessionMetaApi(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def SubscriptionMetaApi(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def EventRetention(self):
|
||||
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
|
||||
|
||||
# BrokerFeatures
|
||||
def EventHistory(self):
|
||||
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 False
|
||||
|
||||
# BrokerFeatures
|
||||
def AcknowledgeEventReceived(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def AcknowledgeSubscriberReceived(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# BrokerFeatures
|
||||
def PayloadTransparency(self):
|
||||
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
|
||||
|
||||
# BrokerFeatures
|
||||
def PayloadEncryptionCryptobox(self):
|
||||
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 BrokerFeaturesStart(builder): builder.StartObject(14)
|
||||
def Start(builder):
|
||||
return BrokerFeaturesStart(builder)
|
||||
def [AWS-SECRET-REMOVED](builder, publisherIdentification): builder.PrependBoolSlot(0, publisherIdentification, 0)
|
||||
def AddPublisherIdentification(builder, publisherIdentification):
|
||||
return [AWS-SECRET-REMOVED](builder, publisherIdentification)
|
||||
def BrokerFeaturesAddPublisherExclusion(builder, publisherExclusion): builder.PrependBoolSlot(1, publisherExclusion, 0)
|
||||
def AddPublisherExclusion(builder, publisherExclusion):
|
||||
return BrokerFeaturesAddPublisherExclusion(builder, publisherExclusion)
|
||||
def [AWS-SECRET-REMOVED]ting(builder, subscriberBlackwhiteListing): builder.PrependBoolSlot(2, subscriberBlackwhiteListing, 0)
|
||||
def AddSubscriberBlackwhiteListing(builder, subscriberBlackwhiteListing):
|
||||
return [AWS-SECRET-REMOVED]ting(builder, subscriberBlackwhiteListing)
|
||||
def [AWS-SECRET-REMOVED]n(builder, patternBasedSubscription): builder.PrependBoolSlot(3, patternBasedSubscription, 0)
|
||||
def AddPatternBasedSubscription(builder, patternBasedSubscription):
|
||||
return [AWS-SECRET-REMOVED]n(builder, patternBasedSubscription)
|
||||
def BrokerFeaturesAddPublicationTrustlevels(builder, publicationTrustlevels): builder.PrependBoolSlot(4, publicationTrustlevels, 0)
|
||||
def AddPublicationTrustlevels(builder, publicationTrustlevels):
|
||||
return BrokerFeaturesAddPublicationTrustlevels(builder, publicationTrustlevels)
|
||||
def BrokerFeaturesAddSubscriptionRevocation(builder, subscriptionRevocation): builder.PrependBoolSlot(5, subscriptionRevocation, 0)
|
||||
def AddSubscriptionRevocation(builder, subscriptionRevocation):
|
||||
return BrokerFeaturesAddSubscriptionRevocation(builder, subscriptionRevocation)
|
||||
def BrokerFeaturesAddSessionMetaApi(builder, sessionMetaApi): builder.PrependBoolSlot(6, sessionMetaApi, 0)
|
||||
def AddSessionMetaApi(builder, sessionMetaApi):
|
||||
return BrokerFeaturesAddSessionMetaApi(builder, sessionMetaApi)
|
||||
def BrokerFeaturesAddSubscriptionMetaApi(builder, subscriptionMetaApi): builder.PrependBoolSlot(7, subscriptionMetaApi, 0)
|
||||
def AddSubscriptionMetaApi(builder, subscriptionMetaApi):
|
||||
return BrokerFeaturesAddSubscriptionMetaApi(builder, subscriptionMetaApi)
|
||||
def BrokerFeaturesAddEventRetention(builder, eventRetention): builder.PrependBoolSlot(8, eventRetention, 0)
|
||||
def AddEventRetention(builder, eventRetention):
|
||||
return BrokerFeaturesAddEventRetention(builder, eventRetention)
|
||||
def BrokerFeaturesAddEventHistory(builder, eventHistory): builder.PrependBoolSlot(9, eventHistory, 0)
|
||||
def AddEventHistory(builder, eventHistory):
|
||||
return BrokerFeaturesAddEventHistory(builder, eventHistory)
|
||||
def [AWS-SECRET-REMOVED]d(builder, acknowledgeEventReceived): builder.PrependBoolSlot(10, acknowledgeEventReceived, 0)
|
||||
def AddAcknowledgeEventReceived(builder, acknowledgeEventReceived):
|
||||
return [AWS-SECRET-REMOVED]d(builder, acknowledgeEventReceived)
|
||||
def [AWS-SECRET-REMOVED]ceived(builder, acknowledgeSubscriberReceived): builder.PrependBoolSlot(11, acknowledgeSubscriberReceived, 0)
|
||||
def AddAcknowledgeSubscriberReceived(builder, acknowledgeSubscriberReceived):
|
||||
return [AWS-SECRET-REMOVED]ceived(builder, acknowledgeSubscriberReceived)
|
||||
def BrokerFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(12, payloadTransparency, 0)
|
||||
def AddPayloadTransparency(builder, payloadTransparency):
|
||||
return BrokerFeaturesAddPayloadTransparency(builder, payloadTransparency)
|
||||
def [AWS-SECRET-REMOVED]box(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(13, payloadEncryptionCryptobox, 0)
|
||||
def AddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox):
|
||||
return [AWS-SECRET-REMOVED]box(builder, payloadEncryptionCryptobox)
|
||||
def BrokerFeaturesEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return BrokerFeaturesEnd(builder)
|
||||
@@ -0,0 +1,238 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Call(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Call()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsCall(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Call
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Call
|
||||
def Session(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def Request(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def Procedure(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Call
|
||||
def Payload(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
|
||||
|
||||
# Call
|
||||
def PayloadAsNumpy(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
|
||||
|
||||
# Call
|
||||
def PayloadLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def PayloadIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
return o == 0
|
||||
|
||||
# Call
|
||||
def EncAlgo(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def EncSerializer(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
|
||||
|
||||
# Call
|
||||
def EncKey(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
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
|
||||
|
||||
# Call
|
||||
def EncKeyAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def EncKeyLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def EncKeyIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
return o == 0
|
||||
|
||||
# Call
|
||||
def Timeout(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def ReceiveProgress(self):
|
||||
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
|
||||
|
||||
# Call
|
||||
def TransactionHash(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Call
|
||||
def Caller(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def CallerAuthid(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Call
|
||||
def CallerAuthrole(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Call
|
||||
def ForwardFor(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30))
|
||||
if o != 0:
|
||||
x = self._tab.Vector(o)
|
||||
x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8
|
||||
from wamp.proto.Principal import Principal
|
||||
obj = Principal()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# Call
|
||||
def ForwardForLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Call
|
||||
def ForwardForIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30))
|
||||
return o == 0
|
||||
|
||||
def CallStart(builder): builder.StartObject(14)
|
||||
def Start(builder):
|
||||
return CallStart(builder)
|
||||
def CallAddSession(builder, session): builder.PrependUint64Slot(0, session, 0)
|
||||
def AddSession(builder, session):
|
||||
return CallAddSession(builder, session)
|
||||
def CallAddRequest(builder, request): builder.PrependUint64Slot(1, request, 0)
|
||||
def AddRequest(builder, request):
|
||||
return CallAddRequest(builder, request)
|
||||
def CallAddProcedure(builder, procedure): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(procedure), 0)
|
||||
def AddProcedure(builder, procedure):
|
||||
return CallAddProcedure(builder, procedure)
|
||||
def CallAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
|
||||
def AddPayload(builder, payload):
|
||||
return CallAddPayload(builder, payload)
|
||||
def CallStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartPayloadVector(builder, numElems):
|
||||
return CallStartPayloadVector(builder, numElems)
|
||||
def CallAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(4, encAlgo, 0)
|
||||
def AddEncAlgo(builder, encAlgo):
|
||||
return CallAddEncAlgo(builder, encAlgo)
|
||||
def CallAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(5, encSerializer, 0)
|
||||
def AddEncSerializer(builder, encSerializer):
|
||||
return CallAddEncSerializer(builder, encSerializer)
|
||||
def CallAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(6, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
|
||||
def AddEncKey(builder, encKey):
|
||||
return CallAddEncKey(builder, encKey)
|
||||
def CallStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartEncKeyVector(builder, numElems):
|
||||
return CallStartEncKeyVector(builder, numElems)
|
||||
def CallAddTimeout(builder, timeout): builder.PrependUint32Slot(7, timeout, 0)
|
||||
def AddTimeout(builder, timeout):
|
||||
return CallAddTimeout(builder, timeout)
|
||||
def CallAddReceiveProgress(builder, receiveProgress): builder.PrependBoolSlot(8, receiveProgress, 0)
|
||||
def AddReceiveProgress(builder, receiveProgress):
|
||||
return CallAddReceiveProgress(builder, receiveProgress)
|
||||
def CallAddTransactionHash(builder, transactionHash): builder.PrependUOffsetTRelativeSlot(9, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0)
|
||||
def AddTransactionHash(builder, transactionHash):
|
||||
return CallAddTransactionHash(builder, transactionHash)
|
||||
def CallAddCaller(builder, caller): builder.PrependUint64Slot(10, caller, 0)
|
||||
def AddCaller(builder, caller):
|
||||
return CallAddCaller(builder, caller)
|
||||
def CallAddCallerAuthid(builder, callerAuthid): builder.PrependUOffsetTRelativeSlot(11, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthid), 0)
|
||||
def AddCallerAuthid(builder, callerAuthid):
|
||||
return CallAddCallerAuthid(builder, callerAuthid)
|
||||
def CallAddCallerAuthrole(builder, callerAuthrole): builder.PrependUOffsetTRelativeSlot(12, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthrole), 0)
|
||||
def AddCallerAuthrole(builder, callerAuthrole):
|
||||
return CallAddCallerAuthrole(builder, callerAuthrole)
|
||||
def CallAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot(13, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0)
|
||||
def AddForwardFor(builder, forwardFor):
|
||||
return CallAddForwardFor(builder, forwardFor)
|
||||
def CallStartForwardForVector(builder, numElems): return builder.StartVector(8, numElems, 8)
|
||||
def StartForwardForVector(builder, numElems):
|
||||
return CallStartForwardForVector(builder, numElems)
|
||||
def CallEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return CallEnd(builder)
|
||||
@@ -0,0 +1,132 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class CalleeFeatures(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = CalleeFeatures()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsCalleeFeatures(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# CalleeFeatures
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# CalleeFeatures
|
||||
def CallerIdentification(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def CallTrustlevels(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def CallTimeout(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def CallCanceling(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def ProgressiveCallResults(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def RegistrationRevocation(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def PatternBasedRegistration(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def SharedRegistration(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CalleeFeatures
|
||||
def PayloadTransparency(self):
|
||||
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
|
||||
|
||||
# CalleeFeatures
|
||||
def PayloadEncryptionCryptobox(self):
|
||||
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 False
|
||||
|
||||
def CalleeFeaturesStart(builder): builder.StartObject(10)
|
||||
def Start(builder):
|
||||
return CalleeFeaturesStart(builder)
|
||||
def CalleeFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
|
||||
def AddCallerIdentification(builder, callerIdentification):
|
||||
return CalleeFeaturesAddCallerIdentification(builder, callerIdentification)
|
||||
def CalleeFeaturesAddCallTrustlevels(builder, callTrustlevels): builder.PrependBoolSlot(1, callTrustlevels, 0)
|
||||
def AddCallTrustlevels(builder, callTrustlevels):
|
||||
return CalleeFeaturesAddCallTrustlevels(builder, callTrustlevels)
|
||||
def CalleeFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(2, callTimeout, 0)
|
||||
def AddCallTimeout(builder, callTimeout):
|
||||
return CalleeFeaturesAddCallTimeout(builder, callTimeout)
|
||||
def CalleeFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(3, callCanceling, 0)
|
||||
def AddCallCanceling(builder, callCanceling):
|
||||
return CalleeFeaturesAddCallCanceling(builder, callCanceling)
|
||||
def CalleeFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(4, progressiveCallResults, 0)
|
||||
def AddProgressiveCallResults(builder, progressiveCallResults):
|
||||
return CalleeFeaturesAddProgressiveCallResults(builder, progressiveCallResults)
|
||||
def CalleeFeaturesAddRegistrationRevocation(builder, registrationRevocation): builder.PrependBoolSlot(5, registrationRevocation, 0)
|
||||
def AddRegistrationRevocation(builder, registrationRevocation):
|
||||
return CalleeFeaturesAddRegistrationRevocation(builder, registrationRevocation)
|
||||
def [AWS-SECRET-REMOVED]n(builder, patternBasedRegistration): builder.PrependBoolSlot(6, patternBasedRegistration, 0)
|
||||
def AddPatternBasedRegistration(builder, patternBasedRegistration):
|
||||
return [AWS-SECRET-REMOVED]n(builder, patternBasedRegistration)
|
||||
def CalleeFeaturesAddSharedRegistration(builder, sharedRegistration): builder.PrependBoolSlot(7, sharedRegistration, 0)
|
||||
def AddSharedRegistration(builder, sharedRegistration):
|
||||
return CalleeFeaturesAddSharedRegistration(builder, sharedRegistration)
|
||||
def CalleeFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(8, payloadTransparency, 0)
|
||||
def AddPayloadTransparency(builder, payloadTransparency):
|
||||
return CalleeFeaturesAddPayloadTransparency(builder, payloadTransparency)
|
||||
def [AWS-SECRET-REMOVED]box(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(9, payloadEncryptionCryptobox, 0)
|
||||
def AddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox):
|
||||
return [AWS-SECRET-REMOVED]box(builder, payloadEncryptionCryptobox)
|
||||
def CalleeFeaturesEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return CalleeFeaturesEnd(builder)
|
||||
@@ -0,0 +1,92 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class CallerFeatures(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = CallerFeatures()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsCallerFeatures(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# CallerFeatures
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# CallerFeatures
|
||||
def CallerIdentification(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def CallTimeout(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def CallCanceling(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def ProgressiveCallResults(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def PayloadTransparency(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# CallerFeatures
|
||||
def PayloadEncryptionCryptobox(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
def CallerFeaturesStart(builder): builder.StartObject(6)
|
||||
def Start(builder):
|
||||
return CallerFeaturesStart(builder)
|
||||
def CallerFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
|
||||
def AddCallerIdentification(builder, callerIdentification):
|
||||
return CallerFeaturesAddCallerIdentification(builder, callerIdentification)
|
||||
def CallerFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(1, callTimeout, 0)
|
||||
def AddCallTimeout(builder, callTimeout):
|
||||
return CallerFeaturesAddCallTimeout(builder, callTimeout)
|
||||
def CallerFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(2, callCanceling, 0)
|
||||
def AddCallCanceling(builder, callCanceling):
|
||||
return CallerFeaturesAddCallCanceling(builder, callCanceling)
|
||||
def CallerFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(3, progressiveCallResults, 0)
|
||||
def AddProgressiveCallResults(builder, progressiveCallResults):
|
||||
return CallerFeaturesAddProgressiveCallResults(builder, progressiveCallResults)
|
||||
def CallerFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(4, payloadTransparency, 0)
|
||||
def AddPayloadTransparency(builder, payloadTransparency):
|
||||
return CallerFeaturesAddPayloadTransparency(builder, payloadTransparency)
|
||||
def [AWS-SECRET-REMOVED]box(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(5, payloadEncryptionCryptobox, 0)
|
||||
def AddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox):
|
||||
return [AWS-SECRET-REMOVED]box(builder, payloadEncryptionCryptobox)
|
||||
def CallerFeaturesEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return CallerFeaturesEnd(builder)
|
||||
@@ -0,0 +1,92 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Cancel(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Cancel()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsCancel(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Cancel
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Cancel
|
||||
def Session(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Cancel
|
||||
def Request(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Cancel
|
||||
def Mode(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Cancel
|
||||
def ForwardFor(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
x = self._tab.Vector(o)
|
||||
x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8
|
||||
from wamp.proto.Principal import Principal
|
||||
obj = Principal()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# Cancel
|
||||
def ForwardForLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Cancel
|
||||
def ForwardForIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
return o == 0
|
||||
|
||||
def CancelStart(builder): builder.StartObject(4)
|
||||
def Start(builder):
|
||||
return CancelStart(builder)
|
||||
def CancelAddSession(builder, session): builder.PrependUint64Slot(0, session, 0)
|
||||
def AddSession(builder, session):
|
||||
return CancelAddSession(builder, session)
|
||||
def CancelAddRequest(builder, request): builder.PrependUint64Slot(1, request, 0)
|
||||
def AddRequest(builder, request):
|
||||
return CancelAddRequest(builder, request)
|
||||
def CancelAddMode(builder, mode): builder.PrependUint8Slot(2, mode, 0)
|
||||
def AddMode(builder, mode):
|
||||
return CancelAddMode(builder, mode)
|
||||
def CancelAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0)
|
||||
def AddForwardFor(builder, forwardFor):
|
||||
return CancelAddForwardFor(builder, forwardFor)
|
||||
def CancelStartForwardForVector(builder, numElems): return builder.StartVector(8, numElems, 8)
|
||||
def StartForwardForVector(builder, numElems):
|
||||
return CancelStartForwardForVector(builder, numElems)
|
||||
def CancelEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return CancelEnd(builder)
|
||||
@@ -0,0 +1,8 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class CancelMode(object):
|
||||
SKIP = 0
|
||||
ABORT = 1
|
||||
KILL = 2
|
||||
@@ -0,0 +1,66 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Challenge(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Challenge()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsChallenge(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Challenge
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Challenge
|
||||
def Session(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Challenge
|
||||
def Method(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Challenge
|
||||
def Extra(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from wamp.Map import Map
|
||||
obj = Map()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
def ChallengeStart(builder): builder.StartObject(3)
|
||||
def Start(builder):
|
||||
return ChallengeStart(builder)
|
||||
def ChallengeAddSession(builder, session): builder.PrependUint64Slot(0, session, 0)
|
||||
def AddSession(builder, session):
|
||||
return ChallengeAddSession(builder, session)
|
||||
def ChallengeAddMethod(builder, method): builder.PrependUint8Slot(1, method, 0)
|
||||
def AddMethod(builder, method):
|
||||
return ChallengeAddMethod(builder, method)
|
||||
def ChallengeAddExtra(builder, extra): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(extra), 0)
|
||||
def AddExtra(builder, extra):
|
||||
return ChallengeAddExtra(builder, extra)
|
||||
def ChallengeEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return ChallengeEnd(builder)
|
||||
@@ -0,0 +1,7 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
class ChannelBinding(object):
|
||||
NONE = 0
|
||||
TLS_UNIQUE = 1
|
||||
@@ -0,0 +1,88 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class ClientRoles(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = ClientRoles()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsClientRoles(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# ClientRoles
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# ClientRoles
|
||||
def Publisher(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from wamp.proto.PublisherFeatures import PublisherFeatures
|
||||
obj = PublisherFeatures()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# ClientRoles
|
||||
def Subscriber(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from wamp.proto.SubscriberFeatures import SubscriberFeatures
|
||||
obj = SubscriberFeatures()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# ClientRoles
|
||||
def Caller(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from wamp.proto.CallerFeatures import CallerFeatures
|
||||
obj = CallerFeatures()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# ClientRoles
|
||||
def Callee(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
x = self._tab.Indirect(o + self._tab.Pos)
|
||||
from wamp.proto.CalleeFeatures import CalleeFeatures
|
||||
obj = CalleeFeatures()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
def ClientRolesStart(builder): builder.StartObject(4)
|
||||
def Start(builder):
|
||||
return ClientRolesStart(builder)
|
||||
def ClientRolesAddPublisher(builder, publisher): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(publisher), 0)
|
||||
def AddPublisher(builder, publisher):
|
||||
return ClientRolesAddPublisher(builder, publisher)
|
||||
def ClientRolesAddSubscriber(builder, subscriber): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(subscriber), 0)
|
||||
def AddSubscriber(builder, subscriber):
|
||||
return ClientRolesAddSubscriber(builder, subscriber)
|
||||
def ClientRolesAddCaller(builder, caller): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(caller), 0)
|
||||
def AddCaller(builder, caller):
|
||||
return ClientRolesAddCaller(builder, caller)
|
||||
def ClientRolesAddCallee(builder, callee): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(callee), 0)
|
||||
def AddCallee(builder, callee):
|
||||
return ClientRolesAddCallee(builder, callee)
|
||||
def ClientRolesEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return ClientRolesEnd(builder)
|
||||
@@ -0,0 +1,162 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class DealerFeatures(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = DealerFeatures()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsDealerFeatures(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# DealerFeatures
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# DealerFeatures
|
||||
def CallerIdentification(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def CallTrustlevels(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def CallTimeout(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def CallCanceling(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def ProgressiveCallResults(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def RegistrationRevocation(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def PatternBasedRegistration(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def SharedRegistration(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def SessionMetaApi(self):
|
||||
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
|
||||
|
||||
# DealerFeatures
|
||||
def RegistrationMetaApi(self):
|
||||
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 False
|
||||
|
||||
# DealerFeatures
|
||||
def TestamentMetaApi(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def PayloadTransparency(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# DealerFeatures
|
||||
def PayloadEncryptionCryptobox(self):
|
||||
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
|
||||
|
||||
def DealerFeaturesStart(builder): builder.StartObject(13)
|
||||
def Start(builder):
|
||||
return DealerFeaturesStart(builder)
|
||||
def DealerFeaturesAddCallerIdentification(builder, callerIdentification): builder.PrependBoolSlot(0, callerIdentification, 0)
|
||||
def AddCallerIdentification(builder, callerIdentification):
|
||||
return DealerFeaturesAddCallerIdentification(builder, callerIdentification)
|
||||
def DealerFeaturesAddCallTrustlevels(builder, callTrustlevels): builder.PrependBoolSlot(1, callTrustlevels, 0)
|
||||
def AddCallTrustlevels(builder, callTrustlevels):
|
||||
return DealerFeaturesAddCallTrustlevels(builder, callTrustlevels)
|
||||
def DealerFeaturesAddCallTimeout(builder, callTimeout): builder.PrependBoolSlot(2, callTimeout, 0)
|
||||
def AddCallTimeout(builder, callTimeout):
|
||||
return DealerFeaturesAddCallTimeout(builder, callTimeout)
|
||||
def DealerFeaturesAddCallCanceling(builder, callCanceling): builder.PrependBoolSlot(3, callCanceling, 0)
|
||||
def AddCallCanceling(builder, callCanceling):
|
||||
return DealerFeaturesAddCallCanceling(builder, callCanceling)
|
||||
def DealerFeaturesAddProgressiveCallResults(builder, progressiveCallResults): builder.PrependBoolSlot(4, progressiveCallResults, 0)
|
||||
def AddProgressiveCallResults(builder, progressiveCallResults):
|
||||
return DealerFeaturesAddProgressiveCallResults(builder, progressiveCallResults)
|
||||
def DealerFeaturesAddRegistrationRevocation(builder, registrationRevocation): builder.PrependBoolSlot(5, registrationRevocation, 0)
|
||||
def AddRegistrationRevocation(builder, registrationRevocation):
|
||||
return DealerFeaturesAddRegistrationRevocation(builder, registrationRevocation)
|
||||
def [AWS-SECRET-REMOVED]n(builder, patternBasedRegistration): builder.PrependBoolSlot(6, patternBasedRegistration, 0)
|
||||
def AddPatternBasedRegistration(builder, patternBasedRegistration):
|
||||
return [AWS-SECRET-REMOVED]n(builder, patternBasedRegistration)
|
||||
def DealerFeaturesAddSharedRegistration(builder, sharedRegistration): builder.PrependBoolSlot(7, sharedRegistration, 0)
|
||||
def AddSharedRegistration(builder, sharedRegistration):
|
||||
return DealerFeaturesAddSharedRegistration(builder, sharedRegistration)
|
||||
def DealerFeaturesAddSessionMetaApi(builder, sessionMetaApi): builder.PrependBoolSlot(8, sessionMetaApi, 0)
|
||||
def AddSessionMetaApi(builder, sessionMetaApi):
|
||||
return DealerFeaturesAddSessionMetaApi(builder, sessionMetaApi)
|
||||
def DealerFeaturesAddRegistrationMetaApi(builder, registrationMetaApi): builder.PrependBoolSlot(9, registrationMetaApi, 0)
|
||||
def AddRegistrationMetaApi(builder, registrationMetaApi):
|
||||
return DealerFeaturesAddRegistrationMetaApi(builder, registrationMetaApi)
|
||||
def DealerFeaturesAddTestamentMetaApi(builder, testamentMetaApi): builder.PrependBoolSlot(10, testamentMetaApi, 0)
|
||||
def AddTestamentMetaApi(builder, testamentMetaApi):
|
||||
return DealerFeaturesAddTestamentMetaApi(builder, testamentMetaApi)
|
||||
def DealerFeaturesAddPayloadTransparency(builder, payloadTransparency): builder.PrependBoolSlot(11, payloadTransparency, 0)
|
||||
def AddPayloadTransparency(builder, payloadTransparency):
|
||||
return DealerFeaturesAddPayloadTransparency(builder, payloadTransparency)
|
||||
def [AWS-SECRET-REMOVED]box(builder, payloadEncryptionCryptobox): builder.PrependBoolSlot(12, payloadEncryptionCryptobox, 0)
|
||||
def AddPayloadEncryptionCryptobox(builder, payloadEncryptionCryptobox):
|
||||
return [AWS-SECRET-REMOVED]box(builder, payloadEncryptionCryptobox)
|
||||
def DealerFeaturesEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return DealerFeaturesEnd(builder)
|
||||
@@ -0,0 +1,158 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Error(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Error()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsError(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Error
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Error
|
||||
def Session(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def RequestType(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def Request(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def Error(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Error
|
||||
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
|
||||
|
||||
# Error
|
||||
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
|
||||
|
||||
# Error
|
||||
def PayloadLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def PayloadIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
return o == 0
|
||||
|
||||
# Error
|
||||
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
|
||||
|
||||
# Error
|
||||
def EncSerializer(self):
|
||||
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
|
||||
|
||||
# Error
|
||||
def EncKey(self, j):
|
||||
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))
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def EncKeyAsNumpy(self):
|
||||
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
|
||||
|
||||
# Error
|
||||
def EncKeyLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Error
|
||||
def EncKeyIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
return o == 0
|
||||
|
||||
def ErrorStart(builder): builder.StartObject(8)
|
||||
def Start(builder):
|
||||
return ErrorStart(builder)
|
||||
def ErrorAddSession(builder, session): builder.PrependUint64Slot(0, session, 0)
|
||||
def AddSession(builder, session):
|
||||
return ErrorAddSession(builder, session)
|
||||
def ErrorAddRequestType(builder, requestType): builder.PrependUint16Slot(1, requestType, 0)
|
||||
def AddRequestType(builder, requestType):
|
||||
return ErrorAddRequestType(builder, requestType)
|
||||
def ErrorAddRequest(builder, request): builder.PrependUint64Slot(2, request, 0)
|
||||
def AddRequest(builder, request):
|
||||
return ErrorAddRequest(builder, request)
|
||||
def ErrorAddError(builder, error): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(error), 0)
|
||||
def AddError(builder, error):
|
||||
return ErrorAddError(builder, error)
|
||||
def ErrorAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
|
||||
def AddPayload(builder, payload):
|
||||
return ErrorAddPayload(builder, payload)
|
||||
def ErrorStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartPayloadVector(builder, numElems):
|
||||
return ErrorStartPayloadVector(builder, numElems)
|
||||
def ErrorAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(5, encAlgo, 0)
|
||||
def AddEncAlgo(builder, encAlgo):
|
||||
return ErrorAddEncAlgo(builder, encAlgo)
|
||||
def ErrorAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(6, encSerializer, 0)
|
||||
def AddEncSerializer(builder, encSerializer):
|
||||
return ErrorAddEncSerializer(builder, encSerializer)
|
||||
def ErrorAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
|
||||
def AddEncKey(builder, encKey):
|
||||
return ErrorAddEncKey(builder, encKey)
|
||||
def ErrorStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartEncKeyVector(builder, numElems):
|
||||
return ErrorStartEncKeyVector(builder, numElems)
|
||||
def ErrorEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return ErrorEnd(builder)
|
||||
@@ -0,0 +1,317 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class Event(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = Event()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsEvent(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# Event
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# Event
|
||||
def Session(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def Subscription(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def Publication(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# Positional values for application-defined event payload.
|
||||
# Event
|
||||
def Args(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 ArgsAsNumpy(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 ArgsLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def ArgsIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
return o == 0
|
||||
|
||||
# Keyword values for application-defined event payload.
|
||||
# Event
|
||||
def Kwargs(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 KwargsAsNumpy(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 KwargsLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def KwargsIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
return o == 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(14))
|
||||
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(14))
|
||||
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(14))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def PayloadIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
return o == 0
|
||||
|
||||
# Event
|
||||
def EncAlgo(self):
|
||||
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 EncSerializer(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18))
|
||||
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(20))
|
||||
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 EncKeyAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
|
||||
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(20))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def EncKeyIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20))
|
||||
return o == 0
|
||||
|
||||
# Event
|
||||
def Publisher(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22))
|
||||
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(24))
|
||||
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(26))
|
||||
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(28))
|
||||
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(30))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# Event
|
||||
def TransactionHash(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32))
|
||||
if o != 0:
|
||||
return self._tab.String(o + self._tab.Pos)
|
||||
return None
|
||||
|
||||
# Event
|
||||
def Acknowledge(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34))
|
||||
if o != 0:
|
||||
return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
|
||||
return False
|
||||
|
||||
# Event
|
||||
def ForwardFor(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36))
|
||||
if o != 0:
|
||||
x = self._tab.Vector(o)
|
||||
x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8
|
||||
from wamp.proto.Principal import Principal
|
||||
obj = Principal()
|
||||
obj.Init(self._tab.Bytes, x)
|
||||
return obj
|
||||
return None
|
||||
|
||||
# Event
|
||||
def ForwardForLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# Event
|
||||
def ForwardForIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36))
|
||||
return o == 0
|
||||
|
||||
def EventStart(builder): builder.StartObject(17)
|
||||
def Start(builder):
|
||||
return EventStart(builder)
|
||||
def EventAddSession(builder, session): builder.PrependUint64Slot(0, session, 0)
|
||||
def AddSession(builder, session):
|
||||
return EventAddSession(builder, session)
|
||||
def EventAddSubscription(builder, subscription): builder.PrependUint64Slot(1, subscription, 0)
|
||||
def AddSubscription(builder, subscription):
|
||||
return EventAddSubscription(builder, subscription)
|
||||
def EventAddPublication(builder, publication): builder.PrependUint64Slot(2, publication, 0)
|
||||
def AddPublication(builder, publication):
|
||||
return EventAddPublication(builder, publication)
|
||||
def EventAddArgs(builder, args): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(args), 0)
|
||||
def AddArgs(builder, args):
|
||||
return EventAddArgs(builder, args)
|
||||
def EventStartArgsVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartArgsVector(builder, numElems):
|
||||
return EventStartArgsVector(builder, numElems)
|
||||
def EventAddKwargs(builder, kwargs): builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(kwargs), 0)
|
||||
def AddKwargs(builder, kwargs):
|
||||
return EventAddKwargs(builder, kwargs)
|
||||
def EventStartKwargsVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartKwargsVector(builder, numElems):
|
||||
return EventStartKwargsVector(builder, numElems)
|
||||
def EventAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
|
||||
def AddPayload(builder, payload):
|
||||
return EventAddPayload(builder, payload)
|
||||
def EventStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartPayloadVector(builder, numElems):
|
||||
return EventStartPayloadVector(builder, numElems)
|
||||
def EventAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(6, encAlgo, 0)
|
||||
def AddEncAlgo(builder, encAlgo):
|
||||
return EventAddEncAlgo(builder, encAlgo)
|
||||
def EventAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(7, encSerializer, 0)
|
||||
def AddEncSerializer(builder, encSerializer):
|
||||
return EventAddEncSerializer(builder, encSerializer)
|
||||
def EventAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(8, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
|
||||
def AddEncKey(builder, encKey):
|
||||
return EventAddEncKey(builder, encKey)
|
||||
def EventStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartEncKeyVector(builder, numElems):
|
||||
return EventStartEncKeyVector(builder, numElems)
|
||||
def EventAddPublisher(builder, publisher): builder.PrependUint64Slot(9, publisher, 0)
|
||||
def AddPublisher(builder, publisher):
|
||||
return EventAddPublisher(builder, publisher)
|
||||
def EventAddPublisherAuthid(builder, publisherAuthid): builder.PrependUOffsetTRelativeSlot(10, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthid), 0)
|
||||
def AddPublisherAuthid(builder, publisherAuthid):
|
||||
return EventAddPublisherAuthid(builder, publisherAuthid)
|
||||
def EventAddPublisherAuthrole(builder, publisherAuthrole): builder.PrependUOffsetTRelativeSlot(11, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthrole), 0)
|
||||
def AddPublisherAuthrole(builder, publisherAuthrole):
|
||||
return EventAddPublisherAuthrole(builder, publisherAuthrole)
|
||||
def EventAddTopic(builder, topic): builder.PrependUOffsetTRelativeSlot(12, flatbuffers.number_types.UOffsetTFlags.py_type(topic), 0)
|
||||
def AddTopic(builder, topic):
|
||||
return EventAddTopic(builder, topic)
|
||||
def EventAddRetained(builder, retained): builder.PrependBoolSlot(13, retained, 0)
|
||||
def AddRetained(builder, retained):
|
||||
return EventAddRetained(builder, retained)
|
||||
def EventAddTransactionHash(builder, transactionHash): builder.PrependUOffsetTRelativeSlot(14, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0)
|
||||
def AddTransactionHash(builder, transactionHash):
|
||||
return EventAddTransactionHash(builder, transactionHash)
|
||||
def EventAddAcknowledge(builder, acknowledge): builder.PrependBoolSlot(15, acknowledge, 0)
|
||||
def AddAcknowledge(builder, acknowledge):
|
||||
return EventAddAcknowledge(builder, acknowledge)
|
||||
def EventAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot(16, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0)
|
||||
def AddForwardFor(builder, forwardFor):
|
||||
return EventAddForwardFor(builder, forwardFor)
|
||||
def EventStartForwardForVector(builder, numElems): return builder.StartVector(8, numElems, 8)
|
||||
def StartForwardForVector(builder, numElems):
|
||||
return EventStartForwardForVector(builder, numElems)
|
||||
def EventEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return EventEnd(builder)
|
||||
@@ -0,0 +1,138 @@
|
||||
# automatically generated by the FlatBuffers compiler, do not modify
|
||||
|
||||
# namespace: proto
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
class EventReceived(object):
|
||||
__slots__ = ['_tab']
|
||||
|
||||
@classmethod
|
||||
def GetRootAs(cls, buf, offset=0):
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = EventReceived()
|
||||
x.Init(buf, n + offset)
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def GetRootAsEventReceived(cls, buf, offset=0):
|
||||
"""This method is deprecated. Please switch to GetRootAs."""
|
||||
return cls.GetRootAs(buf, offset)
|
||||
# EventReceived
|
||||
def Init(self, buf, pos):
|
||||
self._tab = flatbuffers.table.Table(buf, pos)
|
||||
|
||||
# EventReceived
|
||||
def Session(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def Publication(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def Payload(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
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
|
||||
|
||||
# EventReceived
|
||||
def PayloadAsNumpy(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
|
||||
|
||||
# EventReceived
|
||||
def PayloadLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def PayloadIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
|
||||
return o == 0
|
||||
|
||||
# EventReceived
|
||||
def EncAlgo(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncSerializer(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12))
|
||||
if o != 0:
|
||||
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncKey(self, j):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
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
|
||||
|
||||
# EventReceived
|
||||
def EncKeyAsNumpy(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncKeyLength(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
if o != 0:
|
||||
return self._tab.VectorLen(o)
|
||||
return 0
|
||||
|
||||
# EventReceived
|
||||
def EncKeyIsNone(self):
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
|
||||
return o == 0
|
||||
|
||||
def EventReceivedStart(builder): builder.StartObject(6)
|
||||
def Start(builder):
|
||||
return EventReceivedStart(builder)
|
||||
def EventReceivedAddSession(builder, session): builder.PrependUint64Slot(0, session, 0)
|
||||
def AddSession(builder, session):
|
||||
return EventReceivedAddSession(builder, session)
|
||||
def EventReceivedAddPublication(builder, publication): builder.PrependUint64Slot(1, publication, 0)
|
||||
def AddPublication(builder, publication):
|
||||
return EventReceivedAddPublication(builder, publication)
|
||||
def EventReceivedAddPayload(builder, payload): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0)
|
||||
def AddPayload(builder, payload):
|
||||
return EventReceivedAddPayload(builder, payload)
|
||||
def EventReceivedStartPayloadVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartPayloadVector(builder, numElems):
|
||||
return EventReceivedStartPayloadVector(builder, numElems)
|
||||
def EventReceivedAddEncAlgo(builder, encAlgo): builder.PrependUint8Slot(3, encAlgo, 0)
|
||||
def AddEncAlgo(builder, encAlgo):
|
||||
return EventReceivedAddEncAlgo(builder, encAlgo)
|
||||
def EventReceivedAddEncSerializer(builder, encSerializer): builder.PrependUint8Slot(4, encSerializer, 0)
|
||||
def AddEncSerializer(builder, encSerializer):
|
||||
return EventReceivedAddEncSerializer(builder, encSerializer)
|
||||
def EventReceivedAddEncKey(builder, encKey): builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0)
|
||||
def AddEncKey(builder, encKey):
|
||||
return EventReceivedAddEncKey(builder, encKey)
|
||||
def EventReceivedStartEncKeyVector(builder, numElems): return builder.StartVector(1, numElems, 1)
|
||||
def StartEncKeyVector(builder, numElems):
|
||||
return EventReceivedStartEncKeyVector(builder, numElems)
|
||||
def EventReceivedEnd(builder): return builder.EndObject()
|
||||
def End(builder):
|
||||
return EventReceivedEnd(builder)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user