├── docs ├── .nojekyll ├── requirements.txt ├── _static │ ├── custom.css │ ├── file.png │ ├── plus.png │ ├── minus.png │ ├── documentation_options.js │ └── pygments.css ├── objects.inv ├── _sources │ ├── namespaces.rst │ ├── qname.rst │ ├── service.rst │ ├── udp.rst │ ├── scope.rst │ ├── uri.rst │ ├── actions │ │ ├── bye.rst │ │ ├── hello.rst │ │ ├── probe.rst │ │ ├── resolve.rst │ │ ├── probematch.rst │ │ └── resolvematch.rst │ ├── daemon.rst │ ├── envelope.rst │ ├── threaded.rst │ ├── message.rst │ ├── publishing.rst │ ├── discovery.rst │ ├── cmdline.rst │ ├── networking.rst │ ├── serialize_deserialize.rst │ ├── appsupport.rst │ ├── index.rst │ ├── wsdiscovery.rst │ ├── terms.rst │ ├── semantics.rst │ └── conf.py ├── README.txt ├── Makefile ├── make.bat ├── search.html ├── wsdiscovery.html ├── semantics.html ├── networking.html ├── terms.html ├── index.html ├── message.html ├── scope.html ├── uri.html ├── appsupport.html ├── qname.html ├── actions │ ├── bye.html │ ├── hello.html │ ├── probe.html │ ├── resolve.html │ ├── probematch.html │ └── resolvematch.html ├── py-modindex.html └── serialize_deserialize.html ├── tests ├── __init__.py ├── fixtures.py ├── data │ └── probe_response.xml └── test_probing.py ├── requirements.txt ├── wsdiscovery ├── async.py ├── __init__.py ├── scope.py ├── qname.py ├── actions │ ├── __init__.py │ ├── resolve.py │ ├── bye.py │ ├── probe.py │ ├── hello.py │ ├── resolvematch.py │ └── probematch.py ├── uri.py ├── namespaces.py ├── message.py ├── publishing.py ├── service.py ├── udp.py ├── envelope.py ├── daemon.py ├── cmdline.py └── discovery.py ├── upload-to-pypi.sh ├── MANIFEST.in ├── PKG-INFO ├── .readthedocs.yml ├── CHANGES.md ├── .github └── workflows │ └── pythonpackage.yml ├── .gitignore ├── setup.py ├── README.md └── LICENSE /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ifaddr 2 | click 3 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==3.0.1 2 | sphinx-click==2.3.2 3 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /wsdiscovery/async.py: -------------------------------------------------------------------------------- 1 | """A discovery daemon implementation using asyncio is planned.""" 2 | -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreikop/python-ws-discovery/HEAD/docs/objects.inv -------------------------------------------------------------------------------- /upload-to-pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python setup.py sdist 4 | 5 | python3 -m twine upload dist/* 6 | -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreikop/python-ws-discovery/HEAD/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreikop/python-ws-discovery/HEAD/docs/_static/plus.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreikop/python-ws-discovery/HEAD/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_sources/namespaces.rst: -------------------------------------------------------------------------------- 1 | Namespace definitions 2 | ===================== 3 | 4 | .. automodule:: wsdiscovery.namespaces 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/_sources/qname.rst: -------------------------------------------------------------------------------- 1 | QName representation 2 | ==================== 3 | 4 | .. automodule:: wsdiscovery.qname 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst 2 | include *.md 3 | include *.txt 4 | include .coveragerc 5 | include LICENSE 6 | include tox.ini 7 | recursive-include tests *.py 8 | -------------------------------------------------------------------------------- /docs/_sources/service.rst: -------------------------------------------------------------------------------- 1 | Service representation 2 | ====================== 3 | 4 | .. automodule:: wsdiscovery.service 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/_sources/udp.rst: -------------------------------------------------------------------------------- 1 | UDP communications handling 2 | ============================ 3 | 4 | .. automodule:: wsdiscovery.udp 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/_sources/scope.rst: -------------------------------------------------------------------------------- 1 | Service Scope representation 2 | ============================ 3 | 4 | .. automodule:: wsdiscovery.scope 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/_sources/uri.rst: -------------------------------------------------------------------------------- 1 | URI parser for scope matching 2 | ============================== 3 | 4 | .. automodule:: wsdiscovery.uri 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/_sources/actions/bye.rst: -------------------------------------------------------------------------------- 1 | :term:`Bye` message (de)serialization 2 | ======================================= 3 | 4 | .. automodule:: wsdiscovery.actions.bye 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/_sources/actions/hello.rst: -------------------------------------------------------------------------------- 1 | :term:`Hello` message (de)serialization 2 | ======================================= 3 | 4 | .. automodule:: wsdiscovery.actions.hello 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/_sources/actions/probe.rst: -------------------------------------------------------------------------------- 1 | :term:`Probe` message (de)serialization 2 | ======================================= 3 | 4 | .. automodule:: wsdiscovery.actions.probe 5 | :members: 6 | -------------------------------------------------------------------------------- /wsdiscovery/__init__.py: -------------------------------------------------------------------------------- 1 | from .qname import QName 2 | from .scope import Scope 3 | 4 | # for backwards compatibility only 5 | from .discovery import ThreadedWSDiscovery as WSDiscovery 6 | 7 | -------------------------------------------------------------------------------- /docs/_sources/actions/resolve.rst: -------------------------------------------------------------------------------- 1 | :term:`Resolve` message (de)serialization 2 | ========================================== 3 | 4 | .. automodule:: wsdiscovery.actions.resolve 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/_sources/daemon.rst: -------------------------------------------------------------------------------- 1 | Generic networking base daemon 2 | ============================== 3 | 4 | .. automodule:: wsdiscovery.daemon 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | -------------------------------------------------------------------------------- /docs/_sources/envelope.rst: -------------------------------------------------------------------------------- 1 | WS-Discovery message envelope & factories 2 | ========================================== 3 | 4 | .. automodule:: wsdiscovery.envelope 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/_sources/actions/probematch.rst: -------------------------------------------------------------------------------- 1 | :term:`Probe match` message (de)serialization 2 | ============================================== 3 | 4 | .. automodule:: wsdiscovery.actions.probematch 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/_sources/threaded.rst: -------------------------------------------------------------------------------- 1 | Threaded networking base classes 2 | ================================= 3 | 4 | .. automodule:: wsdiscovery.threaded 5 | :members: 6 | :undoc-members: 7 | :private-members: 8 | -------------------------------------------------------------------------------- /docs/_sources/actions/resolvematch.rst: -------------------------------------------------------------------------------- 1 | :term:`Resolve match` message (de)serialization 2 | =============================================== 3 | 4 | .. automodule:: wsdiscovery.actions.resolvematch 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/_sources/message.rst: -------------------------------------------------------------------------------- 1 | WS-Discovery message facilities 2 | ================================ 3 | 4 | .. toctree:: 5 | :caption: The following are provided: 6 | 7 | envelope 8 | serialize_deserialize 9 | 10 | -------------------------------------------------------------------------------- /docs/_sources/publishing.rst: -------------------------------------------------------------------------------- 1 | Service publishing implementation 2 | ================================= 3 | 4 | .. autoclass:: wsdiscovery.publishing.ThreadedWSPublishing 5 | :show-inheritance: 6 | :members: start, stop, publishService, clearLocalServices 7 | -------------------------------------------------------------------------------- /docs/_sources/discovery.rst: -------------------------------------------------------------------------------- 1 | Service discovery implementation 2 | ================================= 3 | 4 | .. autoclass:: wsdiscovery.discovery.ThreadedWSDiscovery 5 | :show-inheritance: 6 | :members: start, stop, searchServices, clearRemoteServices, 7 | setRemoteServiceByeCallback, setRemoteServiceHelloCallback, setRemoveServiceDisappearedCallback 8 | 9 | -------------------------------------------------------------------------------- /docs/_sources/cmdline.rst: -------------------------------------------------------------------------------- 1 | Command-line utilities 2 | ======================= 3 | 4 | There are two command-line tools available for discovering and 5 | publishing services. 6 | 7 | .. click:: wsdiscovery.cmdline:discover 8 | :prog: wsdiscover 9 | :show-nested: 10 | 11 | .. click:: wsdiscovery.cmdline:publish 12 | :prog: wspublish 13 | :show-nested: 14 | 15 | -------------------------------------------------------------------------------- /docs/_sources/networking.rst: -------------------------------------------------------------------------------- 1 | Networking daemon base classes 2 | =============================== 3 | 4 | The framework decouples WS-Discovery messaging functionality 5 | from the actual networking system implementation. 6 | 7 | A set of base classes are provided for creating networked service 8 | discovery and/or publishing implementations: 9 | 10 | .. toctree:: 11 | 12 | daemon 13 | threaded 14 | 15 | -------------------------------------------------------------------------------- /docs/_sources/serialize_deserialize.rst: -------------------------------------------------------------------------------- 1 | WS-Discovery messages (de)serialization 2 | ======================================== 3 | 4 | .. automodule:: wsdiscovery.message 5 | :members: 6 | :undoc-members: 7 | 8 | Serialization & deserialization functions for each message: 9 | 10 | .. toctree:: 11 | :glob: 12 | :maxdepth: 2 13 | 14 | actions/* 15 | 16 | .. seealso:: 17 | :doc:`envelope` 18 | 19 | -------------------------------------------------------------------------------- /docs/README.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory contains pre-generated HTML docs suitable for use in e.g. github pages. 3 | 4 | All the Sphinx reStructuredText source files are in _sources directory. 5 | 6 | Run "make" to build the html sources in the current directory. Use "make help" to see 7 | other options. Note: you of course have to first install Sphinx and the Sphinx 8 | extensions; see _sources/conf.py for a list of ones used in this project. 9 | -------------------------------------------------------------------------------- /docs/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '2.0', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: false, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false 12 | }; -------------------------------------------------------------------------------- /PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.2 2 | Name: WSDiscovery 3 | Version: 2.0.1dev 4 | Summary: WS-Discovery implementation for python 5 | Home-page: https://github.com/andreikop/python-ws-discovery 6 | Author: L.A. Fernando 7 | Author-email: lafernando@gmail.com 8 | Maintainer: Andrei Kopats 9 | Maintainer-email: andrei.kopats@gmail.com 10 | Classifier: Operating System :: POSIX :: Linux 11 | Classifier: Operating System :: MacOS :: MacOS X 12 | Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) 13 | -------------------------------------------------------------------------------- /docs/_sources/appsupport.rst: -------------------------------------------------------------------------------- 1 | Framework for discovery applications 2 | ===================================== 3 | 4 | Reusable facilities for implementing networked service 5 | publishing and discovery applications. 6 | 7 | .. toctree:: 8 | :caption: Full publish & discovery implementations: 9 | :maxdepth: 2 10 | 11 | publishing 12 | discovery 13 | 14 | .. toctree:: 15 | :caption: Facilities provided by the framework: 16 | :maxdepth: 2 17 | 18 | networking 19 | message 20 | service 21 | scope 22 | qname 23 | uri 24 | udp 25 | namespaces 26 | -------------------------------------------------------------------------------- /docs/_sources/index.rst: -------------------------------------------------------------------------------- 1 | Package documentation 2 | ====================== 3 | 4 | This package provides a short introduction to WS-Discovery, simple 5 | command-line tools for discovering and publishing services, and a 6 | framework for building WS-Discovery service publishing or discovery 7 | applications. 8 | 9 | .. toctree:: 10 | :glob: 11 | :maxdepth: 2 12 | :caption: Table of contents: 13 | 14 | wsdiscovery 15 | cmdline 16 | appsupport 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | -------------------------------------------------------------------------------- /tests/fixtures.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from wsdiscovery import WSDiscovery 4 | 5 | 6 | @pytest.fixture 7 | def wsd(): 8 | "provide the discovery client" 9 | client = WSDiscovery() 10 | client.start() 11 | yield client 12 | client.stop() 13 | 14 | 15 | @pytest.fixture 16 | def probe_response(): 17 | "provide a captured SOAP probe response" 18 | 19 | DISCOVER_IP = "192.168.1.104" 20 | 21 | curdir = os.path.dirname(__file__) 22 | with open(curdir + "/data/probe_response.xml", "rb") as resp: 23 | canned = resp.read() 24 | return (canned, DISCOVER_IP) 25 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | 3 | # You can set these variables from the command line. 4 | SPHINXOPTS = 5 | SPHINXBUILD = sphinx-build 6 | SOURCEDIR = _sources 7 | BUILDDIR = . 8 | 9 | 10 | # Build html docs into current directory 11 | docs: 12 | @$(SPHINXBUILD) "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | help: 15 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 16 | 17 | .PHONY: help Makefile 18 | 19 | # Catch-all target: route all unknown targets to Sphinx using the new 20 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 21 | %: Makefile 22 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 23 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/_sources/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF and ePub 17 | formats: all 18 | 19 | # Set the version of Python and requirements required to build your docs 20 | python: 21 | install: 22 | - method: pip 23 | path: . 24 | - requirements: requirements.txt 25 | - requirements: docs/requirements.txt 26 | -------------------------------------------------------------------------------- /wsdiscovery/scope.py: -------------------------------------------------------------------------------- 1 | """Service scopes are used to constrain service discovery.""" 2 | 3 | 4 | class Scope: 5 | "Service scope implementation." 6 | 7 | def __init__(self, value, matchBy=None): 8 | self._matchBy = matchBy 9 | self._value = value 10 | 11 | def getMatchBy(self): 12 | return self._matchBy 13 | 14 | def getValue(self): 15 | return self._value 16 | 17 | def getQuotedValue(self): 18 | return self._value.replace(' ', '%20') 19 | 20 | def __repr__(self): 21 | if self.getMatchBy() == None or len(self.getMatchBy()) == 0: 22 | return self.getValue() 23 | else: 24 | return self.getMatchBy() + ":" + self.getValue() 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/_sources/wsdiscovery.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | WS-Discovery in a nutshell 3 | =========================== 4 | 5 | WS-Discovery (Web Services Discovery) is a standard widely used by network cameras and other connected 6 | devices such as printers. It is based on uni- and multicast `SOAP over UDP`_. 7 | 8 | .. toctree:: 9 | :glob: 10 | :maxdepth: 2 11 | :caption: Short introductory materials: 12 | 13 | semantics 14 | terms 15 | 16 | For details, see the full `WS-Discovery specification`_. 17 | 18 | .. _`SOAP over UDP`: http://docs.oasis-open.org/ws-dd/soapoverudp/1.1/wsdd-soapoverudp-1.1-spec.html 19 | .. _`WS-Discovery specification`: http://docs.oasis-open.org/ws-dd/discovery/1.1/wsdd-discovery-1.1-spec.html 20 | -------------------------------------------------------------------------------- /wsdiscovery/qname.py: -------------------------------------------------------------------------------- 1 | """Qualified name support; see e.g. https://en.wikipedia.org/wiki/QName""" 2 | 3 | 4 | class QName: 5 | "Qualified name implementation" 6 | 7 | def __init__(self, namespace, localname, namespace_prefix=None): 8 | self._namespace = namespace 9 | self._localname = localname 10 | self._namespace_prefix = namespace_prefix 11 | 12 | def getNamespace(self): 13 | return self._namespace 14 | 15 | def getLocalname(self): 16 | return self._localname 17 | 18 | def getNamespacePrefix(self): 19 | return self._namespace_prefix 20 | 21 | def getFullname(self): 22 | return self.getNamespace() + ":" + self.getLocalname() 23 | 24 | def __repr__(self): 25 | return self.getFullname() 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /wsdiscovery/actions/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The actions subpackage provides WS-Discovery action message construction and parsing. 3 | """ 4 | 5 | from .bye import NS_ACTION_BYE, constructBye, createByeMessage, parseByeMessage 6 | from .hello import NS_ACTION_HELLO, constructHello, createHelloMessage, parseHelloMessage 7 | from .probe import NS_ACTION_PROBE, constructProbe, createProbeMessage, parseProbeMessage 8 | from .probematch import NS_ACTION_PROBE_MATCH, constructProbeMatch, createProbeMatchMessage, parseProbeMatchMessage 9 | from .probematch import ProbeResolveMatch 10 | from .resolve import NS_ACTION_RESOLVE, constructResolve, createResolveMessage, parseResolveMessage 11 | from .resolvematch import NS_ACTION_RESOLVE_MATCH, constructResolveMatch, createResolveMatchMessage, parseResolveMatchMessage 12 | 13 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /wsdiscovery/uri.py: -------------------------------------------------------------------------------- 1 | """Module with URI implementation that supports service scope matching""" 2 | 3 | from urllib.parse import unquote 4 | 5 | 6 | class URI: 7 | "URI implementation with additional functionality useful for service scope matching" 8 | 9 | def __init__(self, uri): 10 | uri = unquote(uri) 11 | i1 = uri.find(":") 12 | i2 = uri.find("@") 13 | self._scheme = uri[:i1] 14 | if i2 != -1: 15 | self._authority = uri[i1 + 1: i2] 16 | self._path = uri[i2 + 1:] 17 | else: 18 | self._authority = "" 19 | self._path = uri[i1 + 1:] 20 | 21 | def getScheme(self): 22 | return self._scheme 23 | 24 | def getAuthority(self): 25 | return self._authority 26 | 27 | def getPath(self): 28 | return self._path 29 | 30 | def getPathExQueryFragment(self): 31 | i = self._path.find("?") 32 | path = self.getPath() 33 | if i != -1: 34 | return path[:self._path.find("?")] 35 | else: 36 | return path 37 | 38 | -------------------------------------------------------------------------------- /docs/_sources/terms.rst: -------------------------------------------------------------------------------- 1 | WS-Discovery terms 2 | =================== 3 | 4 | .. glossary:: 5 | 6 | 7 | Action 8 | In WS-Addressing, Action (URI) identifies the semantics of the message. 9 | 10 | QName 11 | A name used in XML is uniquely qualified when it's associated with a 12 | namespace (URI) it belongs to. This package provides a :doc:`qname` 13 | implementation. 14 | 15 | Scope 16 | Scopes are common identifiers used for organizing web services into 17 | logical groups, serving a purpose similar to that of categories or tags. 18 | This package provides a :doc:`scope` implementation. 19 | 20 | EPR 21 | An Endpoint reference is an :abbr:`URI (Uniform Resource Identifier)` 22 | that identifies a SOAP resource such as a WS-Discovery service. A 23 | EPR is included in a :term:`Probe match` message. A :term:`Resolve` 24 | message can then be used to retrieve actual network address for service. 25 | 26 | Envelope 27 | SOAP messages are wrapped in so-called envelopes. This package provides 28 | a :doc:`envelope` implementation. 29 | -------------------------------------------------------------------------------- /docs/_sources/semantics.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | WS-Discovery message semantics 3 | =============================== 4 | 5 | WS-Discovery defines the following **message semantics** for managing and discovering 6 | the availability of networked services. 7 | 8 | See :doc:`terms` for explanation of some common terms. 9 | 10 | .. glossary:: 11 | 12 | Hello 13 | A service *must* send a one-way multicast Hello message when it joins a network, 14 | or its metadata changes. 15 | 16 | Probe 17 | To discover services, optionally limited to a particular service type or scope, 18 | a client sends a Probe message. Probe can be unicast or multicast. 19 | 20 | Probe match 21 | When a service receives a matching Probe, it *must* respond with a Probe Match message. 22 | 23 | Resolve 24 | A client may send a one-way multicast Resolve message to locate service 25 | address(es). 26 | 27 | Resolve match 28 | When a service matches a Resolve message, it *must* respond with a unicast 29 | Resolve Match message. 30 | 31 | Bye 32 | A service *should* send a one-way multicast Bye message when preparing to 33 | leave a network. 34 | -------------------------------------------------------------------------------- /wsdiscovery/namespaces.py: -------------------------------------------------------------------------------- 1 | """SOAP & WS-Discovery XML namespaces used by the package""" 2 | 3 | 4 | #: Addressing namespace 5 | NS_ADDRESSING = "http://schemas.xmlsoap.org/ws/2004/08/addressing" 6 | 7 | #: Discovery namespace 8 | NS_DISCOVERY = "http://schemas.xmlsoap.org/ws/2005/04/discovery" 9 | 10 | #: SOAP envelope namespace 11 | NS_SOAPENV = "http://www.w3.org/2003/05/soap-envelope" 12 | 13 | NS_ADDRESS_ALL = "urn:schemas-xmlsoap-org:ws:2005:04:discovery" 14 | NS_ADDRESS_UNKNOWN = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous" 15 | 16 | #: :term:`Bye` message namespace 17 | NS_ACTION_BYE = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Bye" 18 | 19 | #: :term:`Hello` message namespace 20 | NS_ACTION_HELLO = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Hello" 21 | 22 | #: :term:`Probe` message namespace 23 | NS_ACTION_PROBE = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe" 24 | 25 | #: :term:`Probe match` message namespace 26 | NS_ACTION_PROBE_MATCH = "http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches" 27 | 28 | #: :term:`Resolve` message namespace 29 | NS_ACTION_RESOLVE = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Resolve" 30 | 31 | #: :term:`Resolve match` message namespace 32 | NS_ACTION_RESOLVE_MATCH = "http://schemas.xmlsoap.org/ws/2005/04/discovery/ResolveMatches" 33 | 34 | 35 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========== 3 | 4 | 2.0.1 (unreleased) 5 | ------------------- 6 | 7 | - Fix socket leaks in `NetworkingThread` 8 | 9 | 2.0.0 (2020-04-16) 10 | ------------------- 11 | 12 | - decoupled threaded networking from ws-discovery implementation 13 | - refactored app-level discovery & publishing code into their own modules 14 | - refactored message construction, serialization & deserialization 15 | - added new ``wspublish`` command-line client to publish a service 16 | - added unicast discovery support to ``wsdiscover``command-line client 17 | - collected all namespaces in one module 18 | - improved README example 19 | - good documentation in reStructuredText with Sphinx 20 | - removed Python 2 support code 21 | 22 | 1.1.2 (2019-01-01) 23 | ------------------- 24 | 25 | - Refactoring & Python2 fixes 26 | - Introduce automated Travis testing 27 | 28 | 1.1.1 (2018-12-21) 29 | ------------------- 30 | 31 | - Fix packaging 32 | 33 | 1.1.0 (2018-12-21) 34 | ------------------- 35 | 36 | - Add a simple command-line client (petri) 37 | - Debugging support, including message capture (petri) 38 | - Fix breakage caused by refactoring (petri) 39 | - Simple tests (petri) 40 | 41 | 1.0.0 (2018-12-18) 42 | ------------------- 43 | 44 | - Improved packaging (petri) 45 | - Modularize & refactor (petri) 46 | - Better Python2 support (mleinart) 47 | 48 | 0.2 (2017-05-19) 49 | ----------------- 50 | 51 | - First release @pypi (petri) 52 | -------------------------------------------------------------------------------- /.github/workflows/pythonpackage.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | pip install -r requirements.txt 31 | - name: Lint with flake8 32 | run: | 33 | pip install flake8 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Test with pytest 39 | run: | 40 | pip install pytest 41 | pytest 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # BBEdit 105 | 106 | *.bbprojectd/ 107 | 108 | # Apple 109 | .DS_Store 110 | -------------------------------------------------------------------------------- /tests/data/probe_response.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | uuid:2419d68a-2dd2-21b2-a205-78A5DD0F9593 5 | urn:uuid:2de9f5ad-abd2-4c0e-9ba8-178098d67f01 6 | http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 7 | http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches 8 | 9 | 10 | 11 | 12 | 13 | urn:uuid:2419d68a-2dd2-21b2-a205-78A5DD0F9593 14 | 15 | 16 | ttl 17 | 18 | onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/Model/631GA onvif://www.onvif.org/Name/IPCAM onvif://www.onvif.org/location/country/china 19 | http://192.168.1.104:80/onvif/device_service 20 | 1 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/test_probing.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import selectors 3 | import socket 4 | from .fixtures import probe_response 5 | from wsdiscovery.threaded import NetworkingThread, MULTICAST_PORT 6 | from wsdiscovery import WSDiscovery 7 | 8 | 9 | def test_probing(monkeypatch, probe_response): 10 | "mock up socket registration, event selection & socket message response" 11 | 12 | sck = None 13 | 14 | def mock_register(selector, rsock, evtmask): 15 | """get hold of the multicast socket that will send the Probe message, 16 | when the socket is registered with the selector""" 17 | global sck 18 | # The Probe is sent multicast, we use that to identify the right socket. 19 | # Identification could be done better, but this is enough for now. 20 | if (rsock.proto == socket.IPPROTO_UDP and 21 | rsock.getsockname()[1] == MULTICAST_PORT): 22 | sck = rsock 23 | 24 | def mock_select(*args): 25 | "set a mock Probe response event in motion for the same socket" 26 | global sck 27 | if sck and sck.getsockname()[1] == MULTICAST_PORT: 28 | key = selectors.SelectorKey(sck, sck.fileno(), [], "") 29 | # to mock just one response we just nullify the sock 30 | sck = None 31 | return [(key, selectors.EVENT_READ)] 32 | else: 33 | return [] 34 | 35 | def mock_recvfrom(*args): 36 | return probe_response 37 | 38 | monkeypatch.setattr(selectors.DefaultSelector, "register", mock_register) 39 | monkeypatch.setattr(selectors.DefaultSelector, "select", mock_select) 40 | monkeypatch.setattr(socket.socket, "recvfrom", mock_recvfrom) 41 | 42 | # we cannot use a fixture that'd start discovery for us, since the socket 43 | # selector registration happens at startup time 44 | wsd = WSDiscovery() 45 | wsd.start() 46 | found = wsd.searchServices() 47 | 48 | assert len(found) == 1 49 | assert probe_response[1] in found[0].getXAddrs()[0] 50 | assert len(found[0].getScopes()) == 4 51 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os.path 4 | 5 | try: 6 | from setuptools.commands import setup 7 | except: 8 | from distutils.core import setup 9 | 10 | here = os.path.abspath(os.path.dirname(__file__)) 11 | 12 | try: 13 | README = open(os.path.join(here, "README.md")).read() 14 | CHANGES = open(os.path.join(here, "CHANGES.md")).read() 15 | except Exception: 16 | README = CHANGES = "" 17 | 18 | 19 | setup(name='WSDiscovery', 20 | version='2.1.2', 21 | description='WS-Discovery implementation for python', 22 | long_description=README + "\n\n" + CHANGES, 23 | long_description_content_type="text/markdown", 24 | author='Andrei Kopats', 25 | author_email='andrei.kopats@gmail.com', 26 | url='https://github.com/andreikop/python-ws-discovery.git', 27 | classifiers=[ 28 | 'Development Status :: 4 - Beta', 29 | 'Environment :: Console', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', 32 | 'Operating System :: MacOS :: MacOS X', 33 | 'Operating System :: POSIX :: Linux', 34 | 'Programming Language :: Python :: 3', 35 | 'Programming Language :: Python :: 3.9', 36 | 'Programming Language :: Python :: 3.10', 37 | 'Programming Language :: Python :: 3.11', 38 | 'Programming Language :: Python :: 3.12', 39 | 'Programming Language :: Python :: 3.13', 40 | 'Topic :: Software Development :: Libraries', 41 | 'Topic :: Software Development :: Libraries :: Python Modules', 42 | 'Topic :: Communications' 43 | ], 44 | packages=['wsdiscovery', 'wsdiscovery.actions'], 45 | setup_requires=['ifaddr', 'click'], 46 | install_requires=['ifaddr', 'click'], 47 | tests_require = ['pytest', 'mock'], 48 | entry_points = { 49 | 'console_scripts': [ 50 | 'wsdiscover=wsdiscovery.cmdline:discover', 51 | 'wspublish=wsdiscovery.cmdline:publish' 52 | ], 53 | } 54 | ) 55 | -------------------------------------------------------------------------------- /wsdiscovery/actions/resolve.py: -------------------------------------------------------------------------------- 1 | "Serialize & parse WS-Discovery Resolve SOAP messages" 2 | 3 | import uuid 4 | 5 | from ..namespaces import NS_ADDRESSING, NS_DISCOVERY, NS_ACTION_RESOLVE, NS_ADDRESS_ALL 6 | from ..envelope import SoapEnvelope 7 | from ..util import createSkelSoapMessage, getBodyEl, getHeaderEl, addElementWithText, \ 8 | addEPR, addTypes, addScopes, getDocAsString, getScopes 9 | 10 | 11 | def constructResolve(epr): 12 | "construct an envelope that represents a ``Resolve`` message" 13 | 14 | env = SoapEnvelope() 15 | env.setAction(NS_ACTION_RESOLVE) 16 | env.setTo(NS_ADDRESS_ALL) 17 | env.setMessageId(uuid.uuid4().urn) 18 | env.setEPR(epr) 19 | return env 20 | 21 | 22 | def createResolveMessage(env): 23 | "serialize a SOAP envelope object into a string" 24 | 25 | doc = createSkelSoapMessage(NS_ACTION_RESOLVE) 26 | 27 | bodyEl = getBodyEl(doc) 28 | headerEl = getHeaderEl(doc) 29 | 30 | addElementWithText(doc, headerEl, "a:MessageID", NS_ADDRESSING, env.getMessageId()) 31 | addElementWithText(doc, headerEl, "a:To", NS_ADDRESSING, env.getTo()) 32 | 33 | if len(env.getReplyTo()) > 0: 34 | addElementWithText(doc, headerEl, "a:ReplyTo", NS_ADDRESSING, env.getReplyTo()) 35 | 36 | resolveEl = doc.createElementNS(NS_DISCOVERY, "d:Resolve") 37 | addEPR(doc, resolveEl, env.getEPR()) 38 | bodyEl.appendChild(resolveEl) 39 | 40 | return getDocAsString(doc) 41 | 42 | 43 | def parseResolveMessage(dom): 44 | "parse a XML message into a SOAP envelope object" 45 | 46 | env = SoapEnvelope() 47 | env.setAction(NS_ACTION_RESOLVE) 48 | 49 | env.setMessageId(dom.getElementsByTagNameNS(NS_ADDRESSING, "MessageID")[0].firstChild.data.strip()) 50 | 51 | replyToNodes = dom.getElementsByTagNameNS(NS_ADDRESSING, "ReplyTo") 52 | if len(replyToNodes) > 0: 53 | env.setReplyTo(replyToNodes[0].firstChild.data.strip()) 54 | 55 | env.setTo(dom.getElementsByTagNameNS(NS_ADDRESSING, "To")[0].firstChild.data.strip()) 56 | env.setEPR(dom.getElementsByTagNameNS(NS_ADDRESSING, "Address")[0].firstChild.data.strip()) 57 | 58 | return env 59 | 60 | 61 | -------------------------------------------------------------------------------- /wsdiscovery/actions/bye.py: -------------------------------------------------------------------------------- 1 | "Serialize & parse WS-Discovery Bye SOAP messages" 2 | 3 | import uuid 4 | from ..namespaces import NS_ADDRESSING, NS_DISCOVERY, NS_ACTION_BYE, NS_ADDRESS_ALL 5 | from ..envelope import SoapEnvelope 6 | from ..util import createSkelSoapMessage, getBodyEl, getHeaderEl, addElementWithText, \ 7 | addTypes, addScopes, getDocAsString, getScopes, addEPR, \ 8 | _parseAppSequence 9 | 10 | 11 | def constructBye(service): 12 | "construct an envelope that represents a ``Bye`` message" 13 | 14 | env = SoapEnvelope() 15 | env.setAction(NS_ACTION_BYE) 16 | env.setTo(NS_ADDRESS_ALL) 17 | env.setMessageId(uuid.uuid4().urn) 18 | env.setInstanceId(str(service.getInstanceId())) 19 | env.setMessageNumber(str(service.getMessageNumber())) 20 | env.setEPR(service.getEPR()) 21 | return env 22 | 23 | 24 | def createByeMessage(env): 25 | "serialize a SOAP envelope object into a string" 26 | doc = createSkelSoapMessage(NS_ACTION_BYE) 27 | 28 | bodyEl = getBodyEl(doc) 29 | headerEl = getHeaderEl(doc) 30 | 31 | addElementWithText(doc, headerEl, "a:MessageID", NS_ADDRESSING, env.getMessageId()) 32 | addElementWithText(doc, headerEl, "a:To", NS_ADDRESSING, env.getTo()) 33 | 34 | appSeqEl = doc.createElementNS(NS_DISCOVERY, "d:AppSequence") 35 | appSeqEl.setAttribute("InstanceId", env.getInstanceId()) 36 | appSeqEl.setAttribute("MessageNumber", env.getMessageNumber()) 37 | headerEl.appendChild(appSeqEl) 38 | 39 | byeEl = doc.createElementNS(NS_DISCOVERY, "d:Bye") 40 | addEPR(doc, byeEl, env.getEPR()) 41 | bodyEl.appendChild(byeEl) 42 | 43 | return getDocAsString(doc) 44 | 45 | 46 | def parseByeMessage(dom): 47 | "parse a XML message into a SOAP envelope object" 48 | env = SoapEnvelope() 49 | env.setAction(NS_ACTION_BYE) 50 | 51 | env.setMessageId(dom.getElementsByTagNameNS(NS_ADDRESSING, "MessageID")[0].firstChild.data.strip()) 52 | env.setTo(dom.getElementsByTagNameNS(NS_ADDRESSING, "To")[0].firstChild.data.strip()) 53 | 54 | _parseAppSequence(dom, env) 55 | 56 | env.setEPR(dom.getElementsByTagNameNS(NS_ADDRESSING, "Address")[0].firstChild.data.strip()) 57 | 58 | return env 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /wsdiscovery/message.py: -------------------------------------------------------------------------------- 1 | """Functions to serialize and deserialize messages between SOAP envelope & string representations""" 2 | 3 | import io, sys 4 | from .namespaces import NS_ADDRESSING, NS_SOAPENV 5 | from .actions import * 6 | from xml.dom import minidom 7 | 8 | import logging 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | def createSOAPMessage(env): 13 | "serialize SOAP envelopes into XML strings" 14 | 15 | if env.getAction() == NS_ACTION_PROBE: 16 | return createProbeMessage(env) 17 | if env.getAction() == NS_ACTION_PROBE_MATCH: 18 | return createProbeMatchMessage(env) 19 | if env.getAction() == NS_ACTION_RESOLVE: 20 | return createResolveMessage(env) 21 | if env.getAction() == NS_ACTION_RESOLVE_MATCH: 22 | return createResolveMatchMessage(env) 23 | if env.getAction() == NS_ACTION_HELLO: 24 | return createHelloMessage(env) 25 | if env.getAction() == NS_ACTION_BYE: 26 | return createByeMessage(env) 27 | 28 | 29 | def parseSOAPMessage(data, ipAddr): 30 | "deserialize XML message strings into SOAP envelope objects" 31 | try: 32 | dom = minidom.parseString(data) 33 | except Exception as ex: 34 | logger.debug('Failed to parse message from %s\n%s: %s', ipAddr, data, ex) 35 | return None 36 | 37 | if dom.getElementsByTagNameNS(NS_SOAPENV, "Fault"): 38 | logger.debug('Fault received from %s: %s', ipAddr, data) 39 | return None 40 | 41 | actions = dom.getElementsByTagNameNS(NS_ADDRESSING, "Action") 42 | if len(actions) == 0: 43 | logger.warning('No action received from %s: %s', ipAddr, data) 44 | return None 45 | 46 | soapAction = actions[0].firstChild.data.strip() 47 | if soapAction == NS_ACTION_PROBE: 48 | return parseProbeMessage(dom) 49 | elif soapAction == NS_ACTION_PROBE_MATCH: 50 | return parseProbeMatchMessage(dom) 51 | elif soapAction == NS_ACTION_RESOLVE: 52 | return parseResolveMessage(dom) 53 | elif soapAction == NS_ACTION_RESOLVE_MATCH: 54 | return parseResolveMatchMessage(dom) 55 | elif soapAction == NS_ACTION_BYE: 56 | return parseByeMessage(dom) 57 | elif soapAction == NS_ACTION_HELLO: 58 | return parseHelloMessage(dom) 59 | -------------------------------------------------------------------------------- /docs/_sources/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'WSDiscovery' 21 | copyright = '2020, L.A. Fernando, Andrei Kopats, Pieter Jordaan, Petri Savolainen, Michael Leinartas' 22 | author = 'L.A. Fernando, Andrei Kopats, Pieter Jordaan, Petri Savolainen, Michael Leinartas' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '2.0' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx_click.ext"] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # List of patterns, relative to source directory, that match files and 39 | # directories to ignore when looking for source files. 40 | # This pattern also affects html_static_path and html_extra_path. 41 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 42 | 43 | # Define the top-level master doc 44 | master_doc = 'index' 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'alabaster' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | # html_static_path = ['_static'] 57 | 58 | # do not copy source rst files into build dir 59 | html_copy_source = False 60 | -------------------------------------------------------------------------------- /wsdiscovery/publishing.py: -------------------------------------------------------------------------------- 1 | """Publisher application.""" 2 | 3 | import logging 4 | import random 5 | import time 6 | import uuid 7 | 8 | from .actions import * 9 | from .uri import URI 10 | from .util import filterServices, _generateInstanceId 11 | from .service import Service 12 | from .threaded import ThreadedNetworking 13 | from .daemon import Daemon 14 | 15 | 16 | class Publishing: 17 | "networking-agnostic generic service publishing mixin" 18 | 19 | def __init__(self, **kwargs): 20 | self._localServices = {} 21 | super().__init__(**kwargs) 22 | 23 | def _handle_probe(self, env, addr): 24 | "handle NS_ACTION_PROBE" 25 | services = filterServices(list(self._localServices.values()), env.getTypes(), env.getScopes()) 26 | self._sendProbeMatch(services, env.getMessageId(), addr) 27 | 28 | def _handle_resolve(self, env, addr): 29 | "handle NS_ACTION_RESOLVE" 30 | if env.getEPR() in self._localServices: 31 | service = self._localServices[env.getEPR()] 32 | self._sendResolveMatch(service, env.getMessageId(), addr) 33 | 34 | 35 | def _networkAddressAdded(self, addr): 36 | self.addSourceAddr(addr) 37 | for service in list(self._localServices.values()): 38 | self._sendHello(service) 39 | 40 | def _networkAddressRemoved(self, addr): 41 | self.removeSourceAddr(addr) 42 | 43 | 44 | def publishService(self, types, scopes, xAddrs): 45 | """Publish a service with the given TYPES, SCOPES and XAddrs (service addresses) 46 | 47 | if xAddrs contains item, which includes {ip} pattern, one item per IP address will be sent 48 | """ 49 | 50 | if not self._serverStarted: 51 | raise Exception("Server not started") 52 | 53 | instanceId = _generateInstanceId() 54 | 55 | service = Service(types, scopes, xAddrs, self.uuid, instanceId) 56 | self._localServices[self.uuid] = service 57 | self._sendHello(service) 58 | 59 | 60 | def clearLocalServices(self): 61 | 'send Bye messages for the services and remove them' 62 | 63 | for service in list(self._localServices.values()): 64 | self._sendBye(service) 65 | 66 | self._localServices.clear() 67 | 68 | def stop(self): 69 | self.clearLocalServices() 70 | 71 | 72 | class ThreadedWSPublishing(ThreadedNetworking, Publishing, Daemon): 73 | "threaded service publishing" 74 | 75 | def __init__(self, **kwargs): 76 | super().__init__(**kwargs) 77 | -------------------------------------------------------------------------------- /wsdiscovery/service.py: -------------------------------------------------------------------------------- 1 | """Discoverable WS-Discovery service.""" 2 | import socket 3 | 4 | from .util import _getNetworkAddrs 5 | 6 | 7 | class Service: 8 | "A web service representation implementation" 9 | 10 | def __init__(self, types, scopes, xAddrs, epr, instanceId): 11 | self._types = types 12 | self._scopes = scopes 13 | self._xAddrs = xAddrs 14 | self._epr = epr 15 | self._instanceId = instanceId 16 | self._messageNumber = 0 17 | self._metadataVersion = 1 18 | 19 | def getTypes(self): 20 | "get service types" 21 | return self._types 22 | 23 | def setTypes(self, types): 24 | "set service types" 25 | self._types = types 26 | 27 | def getScopes(self): 28 | "get the service scopes" 29 | return self._scopes 30 | 31 | def setScopes(self, scopes): 32 | "set the service scopes" 33 | self._scopes = scopes 34 | 35 | def getXAddrs(self): 36 | "get service network address" 37 | ret = [] 38 | ipAddrs = None 39 | for xAddr in self._xAddrs: 40 | if '{ip}' in xAddr: 41 | if ipAddrs is None: 42 | ipAddrs = _getNetworkAddrs(socket.AF_INET) 43 | ipAddrs.append(_getNetworkAddrs(socket.AF_INET6)) 44 | for ipAddr in ipAddrs: 45 | if not ipAddr.is_loopback: 46 | ret.append(xAddr.format(ip=str(ipAddr))) 47 | else: 48 | ret.append(xAddr) 49 | return ret 50 | 51 | def setXAddrs(self, xAddrs): 52 | "set service network address" 53 | self._xAddrs = xAddrs 54 | 55 | def getEPR(self): 56 | "get endpoint reference" 57 | return self._epr 58 | 59 | def setEPR(self, epr): 60 | "set endpoint reference" 61 | self._epr = epr 62 | 63 | def getInstanceId(self): 64 | return self._instanceId 65 | 66 | def setInstanceId(self, instanceId): 67 | self._instanceId = instanceId 68 | 69 | def getMessageNumber(self): 70 | return self._messageNumber 71 | 72 | def setMessageNumber(self, messageNumber): 73 | self._messageNumber = messageNumber 74 | 75 | def getMetadataVersion(self): 76 | return self._metadataVersion 77 | 78 | def setMetadataVersion(self, metadataVersion): 79 | self._metadataVersion = metadataVersion 80 | 81 | def incrementMessageNumber(self): 82 | self._messageNumber = self._messageNumber + 1 83 | 84 | 85 | -------------------------------------------------------------------------------- /wsdiscovery/actions/probe.py: -------------------------------------------------------------------------------- 1 | "Serialize & parse WS-Discovery Probe SOAP messages" 2 | 3 | import uuid 4 | from xml.dom import minidom 5 | 6 | from ..namespaces import NS_ADDRESSING, NS_DISCOVERY, NS_ACTION_PROBE, NS_ADDRESS_ALL 7 | from ..envelope import SoapEnvelope 8 | from ..util import createSkelSoapMessage, getBodyEl, getHeaderEl, addElementWithText, \ 9 | addTypes, getTypes, addScopes, getDocAsString, getScopes 10 | 11 | 12 | def constructProbe(types, scopes): 13 | "construct an envelope that represents a ``Probe`` message" 14 | 15 | env = SoapEnvelope() 16 | env.setAction(NS_ACTION_PROBE) 17 | env.setTo(NS_ADDRESS_ALL) 18 | env.setMessageId(uuid.uuid4().urn) 19 | env.setTypes(types) 20 | env.setScopes(scopes) 21 | return env 22 | 23 | 24 | def createProbeMessage(env): 25 | "serialize a SOAP envelope object into a string" 26 | 27 | doc = createSkelSoapMessage(NS_ACTION_PROBE) 28 | 29 | bodyEl = getBodyEl(doc) 30 | headerEl = getHeaderEl(doc) 31 | 32 | addElementWithText(doc, headerEl, "a:MessageID", NS_ADDRESSING, env.getMessageId()) 33 | addElementWithText(doc, headerEl, "a:To", NS_ADDRESSING, env.getTo()) 34 | 35 | if len(env.getReplyTo()) > 0: 36 | addElementWithText(doc, headerEl, "a:ReplyTo", NS_ADDRESSING, env.getReplyTo()) 37 | 38 | probeEl = doc.createElementNS(NS_DISCOVERY, "d:Probe") 39 | bodyEl.appendChild(probeEl) 40 | 41 | addTypes(doc, probeEl, env.getTypes()) 42 | addScopes(doc, probeEl, env.getScopes()) 43 | 44 | return getDocAsString(doc) 45 | 46 | 47 | def parseProbeMessage(dom): 48 | "parse a XML message into a SOAP envelope object" 49 | 50 | env = SoapEnvelope() 51 | env.setAction(NS_ACTION_PROBE) 52 | env.setMessageId(dom.getElementsByTagNameNS(NS_ADDRESSING, "MessageID")[0].firstChild.data.strip()) 53 | 54 | replyToNodes = dom.getElementsByTagNameNS(NS_ADDRESSING, "ReplyTo") 55 | if len(replyToNodes) > 0 and \ 56 | isinstance(replyToNodes[0].firstChild, minidom.Text): 57 | env.setReplyTo(replyToNodes[0].firstChild.data.strip()) 58 | 59 | env.setTo(dom.getElementsByTagNameNS(NS_ADDRESSING, "To")[0].firstChild.data.strip()) 60 | 61 | typeNodes = dom.getElementsByTagNameNS(NS_DISCOVERY, "Types") 62 | if len(typeNodes) > 0: 63 | env.getTypes().extend(getTypes(typeNodes[0])) 64 | 65 | scopeNodes = dom.getElementsByTagNameNS(NS_DISCOVERY, "Scopes") 66 | if len(scopeNodes) > 0: 67 | env.getScopes().extend(getScopes(scopeNodes[0])) 68 | 69 | return env 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /wsdiscovery/udp.py: -------------------------------------------------------------------------------- 1 | """:abbr:`UDP (User Datagram Protocol)` message implementation that helps with 2 | management of unicast/multicast semantics and repeat & delay handling. 3 | 4 | WS-Discovery spec dictates that the example algorithm provided in SOAP-over-UDP spec 5 | is to be used; see 6 | 7 | http://docs.oasis-open.org/ws-dd/soapoverudp/1.1/os/wsdd-soapoverudp-1.1-spec-os.html#_Toc229451838 8 | """ 9 | 10 | import random 11 | import time 12 | 13 | # delays are in milliseconds 14 | 15 | UNICAST_UDP_REPEAT=2 16 | UNICAST_UDP_MIN_DELAY=50 17 | UNICAST_UDP_MAX_DELAY=250 18 | UNICAST_UDP_UPPER_DELAY=500 19 | 20 | MULTICAST_UDP_REPEAT=4 21 | MULTICAST_UDP_MIN_DELAY=50 22 | MULTICAST_UDP_MAX_DELAY=250 23 | MULTICAST_UDP_UPPER_DELAY=500 24 | 25 | 26 | class UDPMessage: 27 | "UDP message management implementation" 28 | 29 | UNICAST = 'unicast' 30 | MULTICAST = 'multicast' 31 | 32 | def __init__(self, env, addr, port, msgType, initialDelay=0, 33 | unicast_num=UNICAST_UDP_REPEAT, 34 | multicast_num=MULTICAST_UDP_REPEAT): 35 | """msgType shall be UDPMessage.UNICAST or UDPMessage.MULTICAST""" 36 | self._env = env 37 | self._addr = addr 38 | self._port = port 39 | self._msgType = msgType 40 | 41 | if msgType == self.UNICAST: 42 | udpRepeat, udpMinDelay, udpMaxDelay, udpUpperDelay = \ 43 | unicast_num, \ 44 | UNICAST_UDP_MIN_DELAY, \ 45 | UNICAST_UDP_MAX_DELAY, \ 46 | UNICAST_UDP_UPPER_DELAY 47 | else: 48 | udpRepeat, udpMinDelay, udpMaxDelay, udpUpperDelay = \ 49 | multicast_num, \ 50 | MULTICAST_UDP_MIN_DELAY, \ 51 | MULTICAST_UDP_MAX_DELAY, \ 52 | MULTICAST_UDP_UPPER_DELAY 53 | 54 | self._udpRepeat = udpRepeat 55 | self._udpUpperDelay = udpUpperDelay 56 | self._t = (udpMinDelay + ((udpMaxDelay - udpMinDelay) * random.random())) / 2 57 | self._nextTime = int(time.time() * 1000) + initialDelay 58 | 59 | def getEnv(self): 60 | return self._env 61 | 62 | def getAddr(self): 63 | return self._addr 64 | 65 | def getPort(self): 66 | return self._port 67 | 68 | def msgType(self): 69 | return self._msgType 70 | 71 | def isFinished(self): 72 | return self._udpRepeat <= 0 73 | 74 | def canSend(self): 75 | ct = int(time.time() * 1000) 76 | return self._nextTime < ct 77 | 78 | def refresh(self): 79 | self._t = self._t * 2 80 | if self._t > self._udpUpperDelay: 81 | self._t = self._udpUpperDelay 82 | self._nextTime = int(time.time() * 1000) + self._t 83 | self._udpRepeat = self._udpRepeat - 1 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation Status](https://readthedocs.org/projects/python-ws-discovery/badge/?version=latest)](https://python-ws-discovery.readthedocs.io/en/latest) 2 | 3 | WS-Discovery in Python 4 | ====================== 5 | This is WS-Discovery implementation for Python 3. It allows to both discover 6 | services and publish discoverable services. For Python 2 support, use the 7 | latest 1.x version of this package. 8 | 9 | Extensive [package documentation is available at ReadTheDocs](https://python-ws-discovery.readthedocs.io). 10 | 11 | Install from PyPI: `pip install WSDiscovery`. 12 | 13 | Basic usage 14 | ------------ 15 | 16 | A simple `wsdiscover` command-line client is provided for discovering 17 | WS-Discovery compliant devices and systems. Run `wsdiscover --help` for 18 | usage instructions. 19 | 20 | Here's an example of how to use the package in your Python code. The following 21 | code first publishes a service and then discovers it: 22 | 23 | ```python 24 | from wsdiscovery.discovery import ThreadedWSDiscovery as WSDiscovery 25 | from wsdiscovery.publishing import ThreadedWSPublishing as WSPublishing 26 | from wsdiscovery import QName, Scope 27 | 28 | # Define type, scope & address of service 29 | ttype1 = QName("http://www.onvif.org/ver10/device/wsdl", "Device") 30 | scope1 = Scope("onvif://www.onvif.org/Model") 31 | xAddr1 = "localhost:8080/abc" 32 | 33 | # Publish the service 34 | wsp = WSPublishing() 35 | wsp.start() 36 | wsp.publishService(types=[ttype1], scopes=[scope1], xAddrs=[xAddr1]) 37 | 38 | # Discover it (along with any other service out there) 39 | wsd = WSDiscovery() 40 | wsd.start() 41 | services = wsd.searchServices() 42 | for service in services: 43 | print(service.getEPR() + ":" + service.getXAddrs()[0]) 44 | wsd.stop() 45 | ``` 46 | 47 | Development notes 48 | ----------------- 49 | To fix a bug or develop this package, it is recommended to use a virtual 50 | environment and set up a development environment. For example: 51 | ``` 52 | $ python3 -m venv venv 53 | $ . venv/bin/activate 54 | (venv) $ pip3 install -e . 55 | ``` 56 | Then you can call `wsdiscover` and `wspublish` from within this virtual 57 | environment. Any code changes to the package will be available and testable 58 | immediately. 59 | 60 | Development state 61 | ----------------- 62 | This is not 100% complete and correct WS-Discovery implementation. It doesn't 63 | verify data received from the network. It may crash, and might contain security 64 | holes. No guarantees - test it carefully for your use case. 65 | 66 | Authors and maintaining 67 | ----------------------- 68 | Original version created by L.A. Fernando. 69 | 70 | Code was then forked and maintained by Andrei Kopats. 71 | 72 | Python2 support fixes by Michael Leinartas. 73 | 74 | Python3 port done by Pieter Jordaan. 75 | 76 | Packaging, major refactoring & command-line clients and 77 | reStructuredText package documentation by Petri Savolainen. 78 | 79 | See full list of contributors on the [GitHub page](https://github.com/andreikop/python-ws-discovery). 80 | -------------------------------------------------------------------------------- /wsdiscovery/envelope.py: -------------------------------------------------------------------------------- 1 | """SOAP envelope implementation.""" 2 | 3 | 4 | class SoapEnvelope: 5 | "envelope implementation" 6 | 7 | def __init__(self): 8 | self._action = "" 9 | self._messageId = "" 10 | self._relatesTo = "" 11 | self._relationshipType = None 12 | self._to = "" 13 | self._replyTo = "" 14 | self._instanceId = "" 15 | self._sequenceId = "" 16 | self._messageNumber = "" 17 | self._epr = "" 18 | self._types = [] 19 | self._scopes = [] 20 | self._xAddrs = [] 21 | self._metadataVersion = "1" 22 | self._probeResolveMatches = [] 23 | 24 | def getAction(self): 25 | return self._action 26 | 27 | def setAction(self, action): 28 | self._action = action 29 | 30 | def getMessageId(self): 31 | return self._messageId 32 | 33 | def setMessageId(self, messageId): 34 | self._messageId = messageId 35 | 36 | def getRelatesTo(self): 37 | return self._relatesTo 38 | 39 | def setRelatesTo(self, relatesTo): 40 | self._relatesTo = relatesTo 41 | 42 | def getRelationshipType(self): 43 | return self._relationshipType 44 | 45 | def setRelationshipType(self, relationshipType): 46 | self._relationshipType = relationshipType 47 | 48 | def getTo(self): 49 | return self._to 50 | 51 | def setTo(self, to): 52 | self._to = to 53 | 54 | def getReplyTo(self): 55 | return self._replyTo 56 | 57 | def setReplyTo(self, replyTo): 58 | self._replyTo = replyTo 59 | 60 | def getInstanceId(self): 61 | return self._instanceId 62 | 63 | def setInstanceId(self, instanceId): 64 | self._instanceId = instanceId 65 | 66 | def getSequenceId(self): 67 | return self._sequenceId 68 | 69 | def setSequenceId(self, sequenceId): 70 | self._sequenceId = sequenceId 71 | 72 | def getEPR(self): 73 | return self._epr 74 | 75 | def setEPR(self, epr): 76 | self._epr = epr 77 | 78 | def getMessageNumber(self): 79 | return self._messageNumber 80 | 81 | def setMessageNumber(self, messageNumber): 82 | self._messageNumber = messageNumber 83 | 84 | def getTypes(self): 85 | return self._types 86 | 87 | def setTypes(self, types): 88 | self._types = types 89 | 90 | def getScopes(self): 91 | return self._scopes 92 | 93 | def setScopes(self, scopes): 94 | self._scopes = scopes 95 | 96 | def getXAddrs(self): 97 | return self._xAddrs 98 | 99 | def setXAddrs(self, xAddrs): 100 | self._xAddrs = xAddrs 101 | 102 | def getMetadataVersion(self): 103 | return self._metadataVersion 104 | 105 | def setMetadataVersion(self, metadataVersion): 106 | self._metadataVersion = metadataVersion 107 | 108 | def getProbeResolveMatches(self): 109 | return self._probeResolveMatches 110 | 111 | def setProbeResolveMatches(self, probeResolveMatches): 112 | self._probeResolveMatches = probeResolveMatches 113 | -------------------------------------------------------------------------------- /wsdiscovery/daemon.py: -------------------------------------------------------------------------------- 1 | """Generic networking-agnostic WS-Discovery messaging daemon mixin implementation.""" 2 | 3 | import random 4 | import time 5 | import uuid 6 | import logging 7 | 8 | from .actions import * 9 | from .uri import URI 10 | from .service import Service 11 | from .envelope import SoapEnvelope 12 | from .udp import UNICAST_UDP_REPEAT, MULTICAST_UDP_REPEAT 13 | 14 | APP_MAX_DELAY = 500 # miliseconds 15 | 16 | logger = logging.getLogger("daemon") 17 | 18 | 19 | class Daemon: 20 | "generic WS-Discovery messaging daemon implementation" 21 | 22 | def __init__(self, uuid_=None, capture=None, ttl=1, **kwargs): 23 | 24 | # track existence of a possible discovery proxy 25 | self._dpActive = False 26 | self._dpAddr = None 27 | self._dpEPR = None 28 | 29 | if uuid_ is not None: 30 | self.uuid = uuid_ 31 | else: 32 | self.uuid = uuid.uuid4().urn 33 | 34 | self._capture = capture 35 | self.ttl = ttl 36 | self._unicast_num = kwargs.get('unicast_num', UNICAST_UDP_REPEAT) 37 | self._multicast_num = kwargs.get('multicast_num', MULTICAST_UDP_REPEAT) 38 | 39 | super().__init__(**kwargs) 40 | 41 | def envReceived(self, env, addr): 42 | action = env.getAction() 43 | action_name = '_handle_' + action[action.rfind('/')+1:].lower() 44 | try: 45 | handler = getattr(self, action_name) 46 | except AttributeError: 47 | logger.warning("could not find handler for: %s" % action_name) 48 | else: 49 | handler(env, addr) 50 | 51 | def _sendResolveMatch(self, service, relatesTo, addr): 52 | env = constructResolveMatch(service, relatesTo) 53 | self.sendUnicastMessage(env, addr[0], addr[1], unicast_num=self._unicast_num) 54 | 55 | def _sendProbeMatch(self, services, relatesTo, addr): 56 | env = constructProbeMatch(services, relatesTo) 57 | self.sendUnicastMessage(env, addr[0], addr[1], random.randint(0, APP_MAX_DELAY), 58 | unicast_num=self._unicast_num) 59 | 60 | def _sendProbe(self, types=None, scopes=None, address=None, port=None): 61 | env = constructProbe(types, scopes) 62 | if self._dpActive: 63 | self.sendUnicastMessage(env, self._dpAddr[0], self._dpAddr[1], 64 | unicast_num=self._unicast_num) 65 | elif address and port: 66 | self.sendUnicastMessage(env, address, port, 67 | unicast_num=self._unicast_num) 68 | else: 69 | self.sendMulticastMessage(env, multicast_num=self._multicast_num) 70 | 71 | def _sendResolve(self, epr): 72 | env = constructResolve(epr) 73 | if self._dpActive: 74 | self.sendUnicastMessage(env, self._dpAddr[0], self._dpAddr[1], 75 | unicast_num=self._unicast_num) 76 | else: 77 | self.sendMulticastMessage(env, multicast_num=self._multicast_num) 78 | 79 | def _sendHello(self, service): 80 | env = constructHello(service) 81 | random.seed((int)(time.time() * 1000000)) 82 | self.sendMulticastMessage(env,initialDelay=random.randint(0, APP_MAX_DELAY), 83 | multicast_num=self._multicast_num) 84 | 85 | def _sendBye(self, service): 86 | env = constructBye(service) 87 | service.incrementMessageNumber() 88 | self.sendMulticastMessage(env, multicast_num=self._multicast_num) 89 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Search — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 | 35 | 36 |
37 | 38 |

Search

39 |
40 | 41 |

42 | Please activate JavaScript to enable the search 43 | functionality. 44 |

45 |
46 |

47 | Searching for multiple words only shows matches that contain 48 | all words. 49 |

50 |
51 | 52 | 53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 |
64 | 100 |
101 |
102 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /wsdiscovery/actions/hello.py: -------------------------------------------------------------------------------- 1 | "Serialize & parse WS-Discovery Hello SOAP messages" 2 | 3 | import uuid 4 | from ..namespaces import NS_ADDRESSING, NS_DISCOVERY, NS_ACTION_HELLO, NS_ADDRESS_ALL 5 | from ..envelope import SoapEnvelope 6 | from ..util import createSkelSoapMessage, getBodyEl, getHeaderEl, addElementWithText, \ 7 | addTypes, addScopes, getDocAsString, getScopes, getQNameFromValue, \ 8 | addEPR, addXAddrs, _parseAppSequence, getTypes, getXAddrs 9 | 10 | 11 | def constructHello(service): 12 | "construct an envelope that represents a ``Hello`` message" 13 | 14 | service.incrementMessageNumber() 15 | 16 | env = SoapEnvelope() 17 | env.setAction(NS_ACTION_HELLO) 18 | env.setTo(NS_ADDRESS_ALL) 19 | env.setMessageId(uuid.uuid4().urn) 20 | env.setInstanceId(str(service.getInstanceId())) 21 | env.setMessageNumber(str(service.getMessageNumber())) 22 | env.setTypes(service.getTypes()) 23 | env.setScopes(service.getScopes()) 24 | env.setXAddrs(service.getXAddrs()) 25 | env.setEPR(service.getEPR()) 26 | return env 27 | 28 | 29 | def createHelloMessage(env): 30 | "serialize a SOAP envelope object into a string" 31 | doc = createSkelSoapMessage(NS_ACTION_HELLO) 32 | 33 | bodyEl = getBodyEl(doc) 34 | headerEl = getHeaderEl(doc) 35 | 36 | addElementWithText(doc, headerEl, "a:MessageID", NS_ADDRESSING, env.getMessageId()) 37 | 38 | if len(env.getRelatesTo()) > 0: 39 | addElementWithText(doc, headerEl, "a:RelatesTo", NS_ADDRESSING, env.getRelatesTo()) 40 | relatesToEl = headerEl.getElementsByTagNameNS(NS_ADDRESSING, "RelatesTo")[0] 41 | relatesToEl.setAttribute("RelationshipType", "d:Suppression") 42 | 43 | addElementWithText(doc, headerEl, "a:To", NS_ADDRESSING, env.getTo()) 44 | 45 | appSeqEl = doc.createElementNS(NS_DISCOVERY, "d:AppSequence") 46 | appSeqEl.setAttribute("InstanceId", env.getInstanceId()) 47 | appSeqEl.setAttribute("MessageNumber", env.getMessageNumber()) 48 | headerEl.appendChild(appSeqEl) 49 | 50 | helloEl = doc.createElementNS(NS_DISCOVERY, "d:Hello") 51 | addEPR(doc, helloEl, env.getEPR()) 52 | addTypes(doc, helloEl, env.getTypes()) 53 | addScopes(doc, helloEl, env.getScopes()) 54 | addXAddrs(doc, helloEl, env.getXAddrs()) 55 | addElementWithText(doc, helloEl, "d:MetadataVersion", NS_DISCOVERY, env.getMetadataVersion()) 56 | 57 | bodyEl.appendChild(helloEl) 58 | 59 | return getDocAsString(doc) 60 | 61 | 62 | def parseHelloMessage(dom): 63 | "parse a XML message into a SOAP envelope object" 64 | env = SoapEnvelope() 65 | env.setAction(NS_ACTION_HELLO) 66 | 67 | env.setMessageId(dom.getElementsByTagNameNS(NS_ADDRESSING, "MessageID")[0].firstChild.data.strip()) 68 | env.setTo(dom.getElementsByTagNameNS(NS_ADDRESSING, "To")[0].firstChild.data.strip()) 69 | 70 | _parseAppSequence(dom, env) 71 | 72 | relatesToNodes = dom.getElementsByTagNameNS(NS_ADDRESSING, "RelatesTo") 73 | if len(relatesToNodes) > 0: 74 | env.setRelatesTo(relatesToNodes[0].firstChild.data.strip()) 75 | env.setRelationshipType(getQNameFromValue( \ 76 | relatesToNodes[0].getAttribute("RelationshipType"), relatesToNodes[0])) 77 | 78 | env.setEPR(dom.getElementsByTagNameNS(NS_ADDRESSING, "Address")[0].firstChild.data.strip()) 79 | 80 | typeNodes = dom.getElementsByTagNameNS(NS_DISCOVERY, "Types") 81 | if len(typeNodes) > 0: 82 | env.setTypes(getTypes(typeNodes[0])) 83 | 84 | scopeNodes = dom.getElementsByTagNameNS(NS_DISCOVERY, "Scopes") 85 | if len(scopeNodes) > 0: 86 | env.setScopes(getScopes(scopeNodes[0])) 87 | 88 | xNodes = dom.getElementsByTagNameNS(NS_DISCOVERY, "XAddrs") 89 | if len(xNodes) > 0: 90 | env.setXAddrs(getXAddrs(xNodes[0])) 91 | 92 | env.setMetadataVersion(dom.getElementsByTagNameNS(NS_DISCOVERY, "MetadataVersion")[0].firstChild.data.strip()) 93 | 94 | return env 95 | -------------------------------------------------------------------------------- /wsdiscovery/actions/resolvematch.py: -------------------------------------------------------------------------------- 1 | "Serialize & parse WS-Discovery Resolve Match SOAP messages" 2 | 3 | import uuid 4 | 5 | from ..namespaces import NS_ADDRESSING, NS_DISCOVERY, NS_ACTION_RESOLVE_MATCH, NS_ADDRESS_UNKNOWN 6 | from ..envelope import SoapEnvelope 7 | from ..util import createSkelSoapMessage, getBodyEl, getHeaderEl, addElementWithText, \ 8 | addTypes, getTypes, addScopes, getDocAsString, getScopes, addEPR, \ 9 | addXAddrs, getXAddrs, _parseAppSequence 10 | 11 | from .probematch import ProbeResolveMatch 12 | 13 | 14 | def constructResolveMatch(service, relatesTo): 15 | "construct an envelope that represents a ``Resolve Match`` message" 16 | 17 | service.incrementMessageNumber() 18 | 19 | env = SoapEnvelope() 20 | env.setAction(NS_ACTION_RESOLVE_MATCH) 21 | env.setTo(NS_ADDRESS_UNKNOWN) 22 | env.setMessageId(uuid.uuid4().urn) 23 | env.setInstanceId(str(service.getInstanceId())) 24 | env.setMessageNumber(str(service.getMessageNumber())) 25 | env.setRelatesTo(relatesTo) 26 | 27 | prb = ProbeResolveMatch(service.getEPR(), service.getTypes(), service.getScopes(), \ 28 | service.getXAddrs(), str(service.getMetadataVersion())) 29 | env.getProbeResolveMatches().append(prb) 30 | return env 31 | 32 | 33 | def createResolveMatchMessage(env): 34 | "serialize a SOAP envelope object into a string" 35 | 36 | doc = createSkelSoapMessage(NS_ACTION_RESOLVE_MATCH) 37 | 38 | bodyEl = getBodyEl(doc) 39 | headerEl = getHeaderEl(doc) 40 | 41 | addElementWithText(doc, headerEl, "a:MessageID", NS_ADDRESSING, env.getMessageId()) 42 | addElementWithText(doc, headerEl, "a:RelatesTo", NS_ADDRESSING, env.getRelatesTo()) 43 | addElementWithText(doc, headerEl, "a:To", NS_ADDRESSING, env.getTo()) 44 | 45 | appSeqEl = doc.createElementNS(NS_DISCOVERY, "d:AppSequence") 46 | appSeqEl.setAttribute("InstanceId", env.getInstanceId()) 47 | appSeqEl.setAttribute("MessageNumber", env.getMessageNumber()) 48 | headerEl.appendChild(appSeqEl) 49 | 50 | resolveMatchesEl = doc.createElementNS(NS_DISCOVERY, "d:ResolveMatches") 51 | if len(env.getProbeResolveMatches()) > 0: 52 | resolveMatch = env.getProbeResolveMatches()[0] 53 | resolveMatchEl = doc.createElementNS(NS_DISCOVERY, "d:ResolveMatch") 54 | addEPR(doc, resolveMatchEl, resolveMatch.getEPR()) 55 | addTypes(doc, resolveMatchEl, resolveMatch.getTypes()) 56 | addScopes(doc, resolveMatchEl, resolveMatch.getScopes()) 57 | addXAddrs(doc, resolveMatchEl, resolveMatch.getXAddrs()) 58 | addElementWithText(doc, resolveMatchEl, "d:MetadataVersion", NS_DISCOVERY, resolveMatch.getMetadataVersion()) 59 | 60 | resolveMatchesEl.appendChild(resolveMatchEl) 61 | 62 | bodyEl.appendChild(resolveMatchesEl) 63 | 64 | return getDocAsString(doc) 65 | 66 | 67 | def parseResolveMatchMessage(dom): 68 | "parse a XML message into a SOAP envelope object" 69 | 70 | env = SoapEnvelope() 71 | env.setAction(NS_ACTION_RESOLVE_MATCH) 72 | 73 | env.setMessageId(dom.getElementsByTagNameNS(NS_ADDRESSING, "MessageID")[0].firstChild.data.strip()) 74 | env.setRelatesTo(dom.getElementsByTagNameNS(NS_ADDRESSING, "RelatesTo")[0].firstChild.data.strip()) 75 | env.setTo(dom.getElementsByTagNameNS(NS_ADDRESSING, "To")[0].firstChild.data.strip()) 76 | 77 | _parseAppSequence(dom, env) 78 | 79 | nodes = dom.getElementsByTagNameNS(NS_DISCOVERY, "ResolveMatch") 80 | if len(nodes) > 0: 81 | node = nodes[0] 82 | epr = node.getElementsByTagNameNS(NS_ADDRESSING, "Address")[0].firstChild.data.strip() 83 | 84 | typeNodes = node.getElementsByTagNameNS(NS_DISCOVERY, "Types") 85 | types = [] 86 | if len(typeNodes) > 0: 87 | types = getTypes(typeNodes[0]) 88 | 89 | scopeNodes = node.getElementsByTagNameNS(NS_DISCOVERY, "Scopes") 90 | scopes = [] 91 | if len(scopeNodes) > 0: 92 | scopes = getScopes(scopeNodes[0]) 93 | 94 | xAddrs = getXAddrs(node.getElementsByTagNameNS(NS_DISCOVERY, "XAddrs")[0]) 95 | mdv = node.getElementsByTagNameNS(NS_DISCOVERY, "MetadataVersion")[0].firstChild.data.strip() 96 | env.getProbeResolveMatches().append(ProbeResolveMatch(epr, types, scopes, xAddrs, mdv)) 97 | 98 | return env 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/wsdiscovery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WS-Discovery in a nutshell — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

WS-Discovery in a nutshell

37 |

WS-Discovery (Web Services Discovery) is a standard widely used by network cameras and other connected 38 | devices such as printers. It is based on uni- and multicast SOAP over UDP.

39 |
40 |

Short introductory materials:

41 | 45 |
46 |

For details, see the full WS-Discovery specification.

47 |
48 | 49 | 50 |
51 | 52 |
53 |
54 | 106 |
107 |
108 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #f8f8f8; } 3 | .highlight .c { color: #8f5902; font-style: italic } /* Comment */ 4 | .highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ 5 | .highlight .g { color: #000000 } /* Generic */ 6 | .highlight .k { color: #004461; font-weight: bold } /* Keyword */ 7 | .highlight .l { color: #000000 } /* Literal */ 8 | .highlight .n { color: #000000 } /* Name */ 9 | .highlight .o { color: #582800 } /* Operator */ 10 | .highlight .x { color: #000000 } /* Other */ 11 | .highlight .p { color: #000000; font-weight: bold } /* Punctuation */ 12 | .highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #8f5902 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #a40000 } /* Generic.Deleted */ 19 | .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ 20 | .highlight .gr { color: #ef2929 } /* Generic.Error */ 21 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 22 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 23 | .highlight .go { color: #888888 } /* Generic.Output */ 24 | .highlight .gp { color: #745334 } /* Generic.Prompt */ 25 | .highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ 26 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 27 | .highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ 28 | .highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */ 29 | .highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */ 30 | .highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */ 31 | .highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */ 32 | .highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */ 33 | .highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */ 34 | .highlight .ld { color: #000000 } /* Literal.Date */ 35 | .highlight .m { color: #990000 } /* Literal.Number */ 36 | .highlight .s { color: #4e9a06 } /* Literal.String */ 37 | .highlight .na { color: #c4a000 } /* Name.Attribute */ 38 | .highlight .nb { color: #004461 } /* Name.Builtin */ 39 | .highlight .nc { color: #000000 } /* Name.Class */ 40 | .highlight .no { color: #000000 } /* Name.Constant */ 41 | .highlight .nd { color: #888888 } /* Name.Decorator */ 42 | .highlight .ni { color: #ce5c00 } /* Name.Entity */ 43 | .highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ 44 | .highlight .nf { color: #000000 } /* Name.Function */ 45 | .highlight .nl { color: #f57900 } /* Name.Label */ 46 | .highlight .nn { color: #000000 } /* Name.Namespace */ 47 | .highlight .nx { color: #000000 } /* Name.Other */ 48 | .highlight .py { color: #000000 } /* Name.Property */ 49 | .highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */ 50 | .highlight .nv { color: #000000 } /* Name.Variable */ 51 | .highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */ 52 | .highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ 53 | .highlight .mb { color: #990000 } /* Literal.Number.Bin */ 54 | .highlight .mf { color: #990000 } /* Literal.Number.Float */ 55 | .highlight .mh { color: #990000 } /* Literal.Number.Hex */ 56 | .highlight .mi { color: #990000 } /* Literal.Number.Integer */ 57 | .highlight .mo { color: #990000 } /* Literal.Number.Oct */ 58 | .highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ 59 | .highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ 60 | .highlight .sc { color: #4e9a06 } /* Literal.String.Char */ 61 | .highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */ 62 | .highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ 63 | .highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ 64 | .highlight .se { color: #4e9a06 } /* Literal.String.Escape */ 65 | .highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ 66 | .highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ 67 | .highlight .sx { color: #4e9a06 } /* Literal.String.Other */ 68 | .highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ 69 | .highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ 70 | .highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ 71 | .highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ 72 | .highlight .fm { color: #000000 } /* Name.Function.Magic */ 73 | .highlight .vc { color: #000000 } /* Name.Variable.Class */ 74 | .highlight .vg { color: #000000 } /* Name.Variable.Global */ 75 | .highlight .vi { color: #000000 } /* Name.Variable.Instance */ 76 | .highlight .vm { color: #000000 } /* Name.Variable.Magic */ 77 | .highlight .il { color: #990000 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /wsdiscovery/actions/probematch.py: -------------------------------------------------------------------------------- 1 | "Serialize & parse WS-Discovery Probe Match SOAP messages" 2 | 3 | import uuid 4 | import random 5 | import time 6 | 7 | from ..namespaces import NS_ADDRESSING, NS_DISCOVERY, NS_ACTION_PROBE_MATCH, NS_ADDRESS_UNKNOWN 8 | from ..envelope import SoapEnvelope 9 | from ..util import createSkelSoapMessage, getBodyEl, getHeaderEl, addElementWithText, \ 10 | addTypes, addScopes, getDocAsString, getScopes, _parseAppSequence, \ 11 | addEPR, getXAddrs, addXAddrs, getTypes, _generateInstanceId 12 | 13 | 14 | def constructProbeMatch(services, relatesTo): 15 | "construct an envelope that represents a ``Probe Match`` message" 16 | 17 | env = SoapEnvelope() 18 | env.setAction(NS_ACTION_PROBE_MATCH) 19 | env.setTo(NS_ADDRESS_UNKNOWN) 20 | env.setMessageId(uuid.uuid4().urn) 21 | random.seed((int)(time.time() * 1000000)) 22 | env.setInstanceId(_generateInstanceId()) 23 | env.setMessageNumber("1") 24 | env.setRelatesTo(relatesTo) 25 | 26 | prbs = env.getProbeResolveMatches() 27 | for srv in services: 28 | prb = ProbeResolveMatch(srv.getEPR(), srv.getTypes(), srv.getScopes(), \ 29 | srv.getXAddrs(), str(srv.getMetadataVersion())) 30 | prbs.append(prb) 31 | return env 32 | 33 | 34 | def createProbeMatchMessage(env): 35 | "serialize a SOAP envelope object into a string" 36 | 37 | doc = createSkelSoapMessage(NS_ACTION_PROBE_MATCH) 38 | 39 | bodyEl = getBodyEl(doc) 40 | headerEl = getHeaderEl(doc) 41 | 42 | addElementWithText(doc, headerEl, "a:MessageID", NS_ADDRESSING, env.getMessageId()) 43 | addElementWithText(doc, headerEl, "a:RelatesTo", NS_ADDRESSING, env.getRelatesTo()) 44 | addElementWithText(doc, headerEl, "a:To", NS_ADDRESSING, env.getTo()) 45 | 46 | appSeqEl = doc.createElementNS(NS_DISCOVERY, "d:AppSequence") 47 | appSeqEl.setAttribute("InstanceId", env.getInstanceId()) 48 | appSeqEl.setAttribute("MessageNumber", env.getMessageNumber()) 49 | headerEl.appendChild(appSeqEl) 50 | 51 | probeMatchesEl = doc.createElementNS(NS_DISCOVERY, "d:ProbeMatches") 52 | probeMatches = env.getProbeResolveMatches() 53 | for probeMatch in probeMatches: 54 | probeMatchEl = doc.createElementNS(NS_DISCOVERY, "d:ProbeMatch") 55 | addEPR(doc, probeMatchEl, probeMatch.getEPR()) 56 | addTypes(doc, probeMatchEl, probeMatch.getTypes()) 57 | addScopes(doc, probeMatchEl, probeMatch.getScopes()) 58 | addXAddrs(doc, probeMatchEl, probeMatch.getXAddrs()) 59 | addElementWithText(doc, probeMatchEl, "d:MetadataVersion", NS_DISCOVERY, probeMatch.getMetadataVersion()) 60 | probeMatchesEl.appendChild(probeMatchEl) 61 | 62 | 63 | bodyEl.appendChild(probeMatchesEl) 64 | 65 | return getDocAsString(doc) 66 | 67 | 68 | def parseProbeMatchMessage(dom): 69 | "parse a XML message into a SOAP envelope object" 70 | 71 | env = SoapEnvelope() 72 | env.setAction(NS_ACTION_PROBE_MATCH) 73 | 74 | env.setMessageId(dom.getElementsByTagNameNS(NS_ADDRESSING, "MessageID")[0].firstChild.data.strip()) 75 | env.setRelatesTo(dom.getElementsByTagNameNS(NS_ADDRESSING, "RelatesTo")[0].firstChild.data.strip()) 76 | # Even though To is required in WS-Discovery, some devices omit it 77 | elem = dom.getElementsByTagNameNS(NS_ADDRESSING, "To").item(0) 78 | if elem: 79 | env.setTo(elem.firstChild.data.strip()) 80 | 81 | _parseAppSequence(dom, env) 82 | 83 | pmNodes = dom.getElementsByTagNameNS(NS_DISCOVERY, "ProbeMatch") 84 | for node in pmNodes: 85 | epr = node.getElementsByTagNameNS(NS_ADDRESSING, "Address")[0].firstChild.data.strip() 86 | 87 | types = [] 88 | typeNodes = node.getElementsByTagNameNS(NS_DISCOVERY, "Types") 89 | if len(typeNodes) > 0: 90 | types = getTypes(typeNodes[0]) 91 | 92 | scopes = [] 93 | scopeNodes = node.getElementsByTagNameNS(NS_DISCOVERY, "Scopes") 94 | if len(scopeNodes) > 0: 95 | scopes = getScopes(scopeNodes[0]) 96 | 97 | xAddrs = [] 98 | xAddrNodes = node.getElementsByTagNameNS(NS_DISCOVERY, "XAddrs") 99 | if len(xAddrNodes) > 0: 100 | xAddrs = getXAddrs(xAddrNodes[0]) 101 | 102 | mdv = node.getElementsByTagNameNS(NS_DISCOVERY, "MetadataVersion")[0].firstChild.data.strip() 103 | env.getProbeResolveMatches().append(ProbeResolveMatch(epr, types, scopes, xAddrs, mdv)) 104 | 105 | return env 106 | 107 | 108 | 109 | class ProbeResolveMatch: 110 | 111 | def __init__(self, epr, types, scopes, xAddrs, metadataVersion): 112 | self._epr = epr 113 | self._types = types 114 | self._scopes = scopes 115 | self._xAddrs = xAddrs 116 | self._metadataVersion = metadataVersion 117 | 118 | def getEPR(self): 119 | return self._epr 120 | 121 | def getTypes(self): 122 | return self._types 123 | 124 | def getScopes(self): 125 | return self._scopes 126 | 127 | def getXAddrs(self): 128 | return self._xAddrs 129 | 130 | def getMetadataVersion(self): 131 | return self._metadataVersion 132 | 133 | def __repr__(self): 134 | return "EPR: %s\nTypes: %s\nScopes: %s\nXAddrs: %s\nMetadata Version: %s" % \ 135 | (self.getEPR(), self.getTypes(), self.getScopes(), 136 | self.getXAddrs(), self.getMetadataVersion()) 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/semantics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WS-Discovery message semantics — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

WS-Discovery message semantics

37 |

WS-Discovery defines the following message semantics for managing and discovering 38 | the availability of networked services.

39 |

See WS-Discovery terms for explanation of some common terms.

40 |
41 |
Hello

A service must send a one-way multicast Hello message when it joins a network, 42 | or its metadata changes.

43 |
44 |
Probe

To discover services, optionally limited to a particular service type or scope, 45 | a client sends a Probe message. Probe can be unicast or multicast.

46 |
47 |
Probe match

When a service receives a matching Probe, it must respond with a Probe Match message.

48 |
49 |
Resolve

A client may send a one-way multicast Resolve message to locate service 50 | address(es).

51 |
52 |
Resolve match

When a service matches a Resolve message, it must respond with a unicast 53 | Resolve Match message.

54 |
55 |
Bye

A service should send a one-way multicast Bye message when preparing to 56 | leave a network.

57 |
58 |
59 |
60 | 61 | 62 |
63 | 64 |
65 |
66 | 120 |
121 |
122 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /docs/networking.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Networking daemon base classes — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Networking daemon base classes

37 |

The framework decouples WS-Discovery messaging functionality 38 | from the actual networking system implementation.

39 |

A set of base classes are provided for creating networked service 40 | discovery and/or publishing implementations:

41 | 47 |
48 | 49 | 50 |
51 | 52 |
53 |
54 | 116 |
117 |
118 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /wsdiscovery/cmdline.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | from contextlib import contextmanager 4 | from urllib.parse import urlparse 5 | import click 6 | 7 | from wsdiscovery.discovery import ThreadedWSDiscovery as WSDiscovery 8 | from wsdiscovery.publishing import ThreadedWSPublishing as WSPublishing 9 | from wsdiscovery.scope import Scope 10 | from wsdiscovery.qname import QName 11 | from wsdiscovery.discovery import DEFAULT_DISCOVERY_TIMEOUT 12 | from wsdiscovery.udp import UNICAST_UDP_REPEAT, MULTICAST_UDP_REPEAT 13 | 14 | DEFAULT_LOGLEVEL = "INFO" 15 | 16 | CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) 17 | 18 | 19 | @contextmanager 20 | def discovery(capture=None, unicast_num=UNICAST_UDP_REPEAT, 21 | multicast_num=MULTICAST_UDP_REPEAT, relates_to=False): 22 | wsd = WSDiscovery(capture=capture, unicast_num=unicast_num, 23 | multicast_num=multicast_num, relates_to=relates_to) 24 | wsd.start() 25 | yield wsd 26 | wsd.stop() 27 | 28 | @contextmanager 29 | def publishing(capture=None): 30 | wsd = WSPublishing(capture=capture) 31 | wsd.start() 32 | yield wsd 33 | wsd.stop() 34 | 35 | 36 | def setup_logger(name, loglevel): 37 | level = getattr(logging, loglevel, None) 38 | if not level: 39 | print("Invalid log level '%s'" % loglevel) 40 | sys.exit() 41 | 42 | logging.basicConfig(level=level) 43 | return logging.getLogger(name) 44 | 45 | 46 | @click.command(context_settings=CONTEXT_SETTINGS) 47 | @click.option('--scope', '-s', help='Probe scope URI, e.g. onvif://www.onvif.org/Model/') 48 | @click.option('--type', '-y', 'ptype', nargs=3, type=(str, str, str), multiple=True, 49 | help='Probe type in this order: NS_URI NS_PREFIX LOCAL_NAME,' 50 | ' e.g. http://www.onvif.org/ver10/network/wsdl dp0 NetworkVideoTransmitter' 51 | ' -- this option can be specified multiple times') 52 | @click.option('--address', '-a', help='Service address') 53 | @click.option('--port', '-p', type=int, help='Service port') 54 | @click.option('--loglevel', '-l', default=DEFAULT_LOGLEVEL, show_default=True, 55 | type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), 56 | help='Log level') 57 | @click.option('--capture', '-c', nargs=1, type=click.File('w'), help='Capture messages to a file') 58 | @click.option('--timeout', '-t', default=DEFAULT_DISCOVERY_TIMEOUT, show_default=True, 59 | type=int, help='Discovery timeout in seconds') 60 | @click.option('--unicast-num', '-un', type=int, default=UNICAST_UDP_REPEAT, 61 | show_default=True, help='Number of Unicast messages to send') 62 | @click.option('--multicast-num', '-mn', type=int, default=MULTICAST_UDP_REPEAT, 63 | show_default=True, help='Number of Multicast messages to send') 64 | @click.option('--relates-to', '-rt', is_flag=True, 65 | help='Also use RelatesTo tag to recognize incoming messages.') 66 | def discover(scope, ptype, address, port, loglevel, capture, timeout, 67 | unicast_num, multicast_num, relates_to): 68 | "Discover services using WS-Discovery" 69 | 70 | logger = setup_logger("ws-discovery", loglevel) 71 | 72 | probe_types = [] 73 | for type_tuple in ptype: 74 | probe_types.append(QName(type_tuple[0], type_tuple[2], type_tuple[1])) 75 | if len(probe_types) == 0: 76 | probe_types = None 77 | 78 | with discovery(capture, unicast_num, multicast_num, relates_to) as wsd: 79 | scopes = [Scope(scope)] if scope else [] 80 | svcs = wsd.searchServices(types=probe_types, scopes=scopes, 81 | address=address, port=port, timeout=timeout) 82 | print("\nDiscovered:\n") 83 | for service in svcs: 84 | url = urlparse(service.getXAddrs()[0]) 85 | print(" address: %s" % url.netloc) 86 | print(" - %s\n" % "\n - ".join([str(s) for s in service.getScopes()])) 87 | 88 | 89 | @click.command(context_settings=CONTEXT_SETTINGS) 90 | @click.option('--scope', '-s', help='Full scope URI, eg. onvif://www.onvif.org/Model/') 91 | @click.option('--typename', '-t', help='Qualified type name, eg. https://myservicesns:myservice_type') 92 | @click.option('--address', '-a', help='Service IP address') 93 | @click.option('--port', '-p', type=int, help='Service port') 94 | @click.option('--loglevel', '-l', default=DEFAULT_LOGLEVEL, show_default=True, 95 | type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), 96 | help='Log level') 97 | @click.option('--capture', '-c', nargs=1, type=click.File('w'), help='Capture messages to a file') 98 | @click.option('--unicast-num', '-un', type=int, default=UNICAST_UDP_REPEAT, 99 | show_default=True, help='Number of Unicast messages to send') 100 | @click.option('--multicast-num', '-mn', type=int, default=MULTICAST_UDP_REPEAT, 101 | show_default=True, help='Number of Multicast messages to send') 102 | def publish(scope, typename, address, port, loglevel, capture, unicast_num, 103 | multicast_num): 104 | "Publish services using WS-Discovery" 105 | 106 | logger = setup_logger("ws-publishing", loglevel) 107 | 108 | with publishing(capture) as wsp: 109 | scopes = [Scope(scope)] if scope else [] 110 | 111 | try: 112 | proto, ns, name = typename.split(':') 113 | except: 114 | types = [] 115 | else: 116 | ns = ns[2:] 117 | types = [QName(proto, ns)] 118 | 119 | xAddrs = ["%s:%i" % (address, port)] if address else ['127.0.0.1'] 120 | svc = wsp.publishService(types, scopes, xAddrs) 121 | -------------------------------------------------------------------------------- /docs/terms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WS-Discovery terms — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

WS-Discovery terms

37 |
38 |
Action

In WS-Addressing, Action (URI) identifies the semantics of the message.

39 |
40 |
QName

A name used in XML is uniquely qualified when it’s associated with a 41 | namespace (URI) it belongs to. This package provides a QName representation 42 | implementation.

43 |
44 |
Scope

Scopes are common identifiers used for organizing web services into 45 | logical groups, serving a purpose similar to that of categories or tags. 46 | This package provides a Service Scope representation implementation.

47 |
48 |
EPR

An Endpoint reference is an URI 49 | that identifies a SOAP resource such as a WS-Discovery service. A 50 | EPR is included in a Probe match message. A Resolve 51 | message can then be used to retrieve actual network address for service.

52 |
53 |
Envelope

SOAP messages are wrapped in so-called envelopes. This package provides 54 | a WS-Discovery message envelope & factories implementation.

55 |
56 |
57 |
58 | 59 | 60 |
61 | 62 |
63 |
64 | 118 |
119 |
120 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /wsdiscovery/discovery.py: -------------------------------------------------------------------------------- 1 | """Discovery application.""" 2 | 3 | import time 4 | import uuid 5 | 6 | from .actions import * 7 | from .uri import URI 8 | from .util import matchesFilter, filterServices, extractSoapUdpAddressFromURI 9 | from .service import Service 10 | from .namespaces import NS_DISCOVERY 11 | from .threaded import ThreadedNetworking 12 | from .daemon import Daemon 13 | 14 | DEFAULT_DISCOVERY_TIMEOUT = 3 15 | 16 | 17 | class Discovery: 18 | """networking-agnostic generic remote service discovery mixin""" 19 | 20 | def __init__(self, **kwargs): 21 | self._remoteServices = {} 22 | self._remoteServiceHelloCallback = None 23 | self._remoteServiceHelloCallbackTypesFilter = None 24 | self._remoteServiceHelloCallbackScopesFilter = None 25 | self._remoteServiceByeCallback = None 26 | super().__init__(**kwargs) 27 | 28 | def setRemoteServiceHelloCallback(self, cb, types=None, scopes=None): 29 | """Set callback, which will be called when new service appeared online 30 | and sent Hi message 31 | 32 | typesFilter and scopesFilter might be list of types and scopes. 33 | If filter is set, callback is called only for Hello messages, 34 | which match filter 35 | 36 | Set None to disable callback 37 | """ 38 | self._remoteServiceHelloCallback = cb 39 | self._remoteServiceHelloCallbackTypesFilter = types 40 | self._remoteServiceHelloCallbackScopesFilter = scopes 41 | 42 | def setRemoteServiceByeCallback(self, cb): 43 | """Set callback, which will be called when new service appeared online 44 | and sent Hi message 45 | Service is passed as a parameter to the callback 46 | Set None to disable callback 47 | """ 48 | self._remoteServiceByeCallback = cb 49 | 50 | def setRemoteServiceDisappearedCallback(self, cb): 51 | """Set callback, which will be called when new service disappears 52 | Service uuid is passed as a parameter to the callback 53 | Set None to disable callback 54 | """ 55 | self._remoteServiceDisppearedCallback = cb 56 | 57 | # discovery-related message handlers: 58 | 59 | def _handle_probematches(self, env, addr): 60 | for match in env.getProbeResolveMatches(): 61 | self._addRemoteService(Service(match.getTypes(), match.getScopes(), match.getXAddrs(), match.getEPR(), 0)) 62 | if match.getXAddrs() is None or len(match.getXAddrs()) == 0: 63 | self._sendResolve(match.getEPR()) 64 | 65 | def _handle_resolvematches(self, env, addr): 66 | for match in env.getProbeResolveMatches(): 67 | self._addRemoteService(Service(match.getTypes(), match.getScopes(), match.getXAddrs(), match.getEPR(), 0)) 68 | 69 | def _handle_hello(self, env, addr): 70 | #check if it is from a discovery proxy 71 | rt = env.getRelationshipType() 72 | if rt is not None and rt.getLocalname() == "Suppression" and rt.getNamespace() == NS_DISCOVERY: 73 | xAddr = env.getXAddrs()[0] 74 | #only support 'soap.udp' 75 | if xAddr.startswith("soap.udp:"): 76 | self._dpActive = True 77 | self._dpAddr = extractSoapUdpAddressFromURI(URI(xAddr)) 78 | self._dpEPR = env.getEPR() 79 | 80 | service = Service(env.getTypes(), env.getScopes(), env.getXAddrs(), env.getEPR(), 0) 81 | self._addRemoteService(service) 82 | if self._remoteServiceHelloCallback is not None: 83 | if matchesFilter(service, 84 | self._remoteServiceHelloCallbackTypesFilter, 85 | self._remoteServiceHelloCallbackScopesFilter): 86 | self._remoteServiceHelloCallback(service) 87 | 88 | def _handle_bye(self, env, addr): 89 | #if the bye is from discovery proxy... revert back to multicasting 90 | if self._dpActive and self._dpEPR == env.getEPR(): 91 | self._dpActive = False 92 | self._dpAddr = None 93 | self._dpEPR = None 94 | 95 | self._removeRemoteService(env.getEPR()) 96 | if self._remoteServiceByeCallback is not None: 97 | self._remoteServiceByeCallback(env.getEPR()) 98 | 99 | 100 | # handle local address changes: 101 | 102 | def _networkAddressAdded(self, addr): 103 | self.addSourceAddr(addr) 104 | 105 | def _networkAddressRemoved(self, addr): 106 | self.removeSourceAddr(addr) 107 | 108 | # search for & keep track of discovered remote services: 109 | 110 | def _addRemoteService(self, service): 111 | self._remoteServices[service.getEPR()] = service 112 | 113 | def _removeRemoteService(self, epr): 114 | if epr in self._remoteServices: 115 | del self._remoteServices[epr] 116 | 117 | def clearRemoteServices(self): 118 | 'clears remotely discovered services' 119 | 120 | self._remoteServices.clear() 121 | 122 | def searchServices(self, types=None, scopes=None, address=None, port=None, 123 | timeout=DEFAULT_DISCOVERY_TIMEOUT): 124 | 'search for services given the TYPES and SCOPES within a given TIMEOUT' 125 | try: 126 | self._sendProbe(types, scopes, address, port) 127 | except: 128 | raise Exception("Server not started") 129 | 130 | time.sleep(timeout) 131 | 132 | return filterServices(list(self._remoteServices.values()), types, scopes) 133 | 134 | def stop(self): 135 | self.clearRemoteServices() 136 | super().stop() 137 | 138 | 139 | class ThreadedWSDiscovery(Daemon, Discovery, ThreadedNetworking): 140 | "Full threaded service discovery implementation" 141 | 142 | def __init__(self, **kwargs): 143 | super().__init__(**kwargs) 144 | 145 | def stop(self): 146 | super().stop() 147 | 148 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Package documentation — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 | 32 |
33 | 34 |
35 |

Package documentation

36 |

This package provides a short introduction to WS-Discovery, simple 37 | command-line tools for discovering and publishing services, and a 38 | framework for building WS-Discovery service publishing or discovery 39 | applications.

40 | 68 |
69 |
70 |

Indices and tables

71 | 76 |
77 | 78 | 79 |
80 | 81 |
82 |
83 | 130 |
131 |
132 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /docs/message.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WS-Discovery message facilities — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 | 56 | 57 |
58 |
59 | 121 |
122 |
123 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /docs/scope.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Service Scope representation — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Service Scope representation

37 |

Service scopes are used to constrain service discovery.

38 |
39 |
40 | class wsdiscovery.scope.Scope(value, matchBy=None)
41 |

Service scope implementation.

42 |
43 |
44 | getMatchBy()
45 |
46 | 47 |
48 |
49 | getQuotedValue()
50 |
51 | 52 |
53 |
54 | getValue()
55 |
56 | 57 |
58 | 59 |
60 | 61 | 62 |
63 | 64 |
65 |
66 | 128 |
129 |
130 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /docs/uri.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | URI parser for scope matching — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

URI parser for scope matching

37 |

Module with URI implementation that supports service scope matching

38 |
39 |
40 | class wsdiscovery.uri.URI(uri)
41 |

URI implementation with additional functionality useful for service scope matching

42 |
43 |
44 | getAuthority()
45 |
46 | 47 |
48 |
49 | getPath()
50 |
51 | 52 |
53 |
54 | getPathExQueryFragment()
55 |
56 | 57 |
58 |
59 | getScheme()
60 |
61 | 62 |
63 | 64 |
65 | 66 | 67 |
68 | 69 |
70 |
71 | 133 |
134 |
135 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /docs/appsupport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Framework for discovery applications — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Framework for discovery applications

37 |

Reusable facilities for implementing networked service 38 | publishing and discovery applications.

39 |
40 |

Full publish & discovery implementations:

41 | 45 |
46 | 67 |
68 | 69 | 70 |
71 | 72 |
73 |
74 | 134 |
135 |
136 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /docs/qname.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | QName representation — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

QName representation

37 |

Qualified name support; see e.g. https://en.wikipedia.org/wiki/QName

38 |
39 |
40 | class wsdiscovery.qname.QName(namespace, localname, namespace_prefix=None)
41 |

Qualified name implementation

42 |
43 |
44 | getFullname()
45 |
46 | 47 |
48 |
49 | getLocalname()
50 |
51 | 52 |
53 |
54 | getNamespace()
55 |
56 | 57 |
58 |
59 | getNamespacePrefix()
60 |
61 | 62 |
63 | 64 |
65 | 66 | 67 |
68 | 69 |
70 |
71 | 133 |
134 |
135 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /docs/actions/bye.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bye message (de)serialization — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Bye message (de)serialization

37 |

Serialize & parse WS-Discovery Bye SOAP messages

38 |
39 |
40 | wsdiscovery.actions.bye.constructBye(service)
41 |

construct an envelope that represents a Bye message

42 |
43 | 44 |
45 |
46 | wsdiscovery.actions.bye.createByeMessage(env)
47 |

serialize a SOAP envelope object into a string

48 |
49 | 50 |
51 |
52 | wsdiscovery.actions.bye.parseByeMessage(dom)
53 |

parse a XML message into a SOAP envelope object

54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 129 |
130 |
131 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/actions/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello message (de)serialization — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Hello message (de)serialization

37 |

Serialize & parse WS-Discovery Hello SOAP messages

38 |
39 |
40 | wsdiscovery.actions.hello.constructHello(service)
41 |

construct an envelope that represents a Hello message

42 |
43 | 44 |
45 |
46 | wsdiscovery.actions.hello.createHelloMessage(env)
47 |

serialize a SOAP envelope object into a string

48 |
49 | 50 |
51 |
52 | wsdiscovery.actions.hello.parseHelloMessage(dom)
53 |

parse a XML message into a SOAP envelope object

54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 129 |
130 |
131 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/actions/probe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Probe message (de)serialization — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Probe message (de)serialization

37 |

Serialize & parse WS-Discovery Probe SOAP messages

38 |
39 |
40 | wsdiscovery.actions.probe.constructProbe(types, scopes)
41 |

construct an envelope that represents a Probe message

42 |
43 | 44 |
45 |
46 | wsdiscovery.actions.probe.createProbeMessage(env)
47 |

serialize a SOAP envelope object into a string

48 |
49 | 50 |
51 |
52 | wsdiscovery.actions.probe.parseProbeMessage(dom)
53 |

parse a XML message into a SOAP envelope object

54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 129 |
130 |
131 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/actions/resolve.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Resolve message (de)serialization — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Resolve message (de)serialization

37 |

Serialize & parse WS-Discovery Resolve SOAP messages

38 |
39 |
40 | wsdiscovery.actions.resolve.constructResolve(epr)
41 |

construct an envelope that represents a Resolve message

42 |
43 | 44 |
45 |
46 | wsdiscovery.actions.resolve.createResolveMessage(env)
47 |

serialize a SOAP envelope object into a string

48 |
49 | 50 |
51 |
52 | wsdiscovery.actions.resolve.parseResolveMessage(dom)
53 |

parse a XML message into a SOAP envelope object

54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 129 |
130 |
131 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/actions/probematch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Probe match message (de)serialization — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Probe match message (de)serialization

37 |

Serialize & parse WS-Discovery Probe Match SOAP messages

38 |
39 |
40 | wsdiscovery.actions.probematch.constructProbeMatch(services, relatesTo)
41 |

construct an envelope that represents a Probe Match message

42 |
43 | 44 |
45 |
46 | wsdiscovery.actions.probematch.createProbeMatchMessage(env)
47 |

serialize a SOAP envelope object into a string

48 |
49 | 50 |
51 |
52 | wsdiscovery.actions.probematch.parseProbeMatchMessage(dom)
53 |

parse a XML message into a SOAP envelope object

54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 129 |
130 |
131 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/actions/resolvematch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Resolve match message (de)serialization — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

Resolve match message (de)serialization

37 |

Serialize & parse WS-Discovery Resolve Match SOAP messages

38 |
39 |
40 | wsdiscovery.actions.resolvematch.constructResolveMatch(service, relatesTo)
41 |

construct an envelope that represents a Resolve Match message

42 |
43 | 44 |
45 |
46 | wsdiscovery.actions.resolvematch.createResolveMatchMessage(env)
47 |

serialize a SOAP envelope object into a string

48 |
49 | 50 |
51 |
52 | wsdiscovery.actions.resolvematch.parseResolveMatchMessage(dom)
53 |

parse a XML message into a SOAP envelope object

54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 129 |
130 |
131 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Python Module Index — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 33 | 34 |
35 | 36 | 37 |

Python Module Index

38 | 39 |
40 | w 41 |
42 | 43 | 44 | 45 | 47 | 48 | 50 | 53 | 54 | 55 | 58 | 59 | 60 | 63 | 64 | 65 | 68 | 69 | 70 | 73 | 74 | 75 | 78 | 79 | 80 | 83 | 84 | 85 | 88 | 89 | 90 | 93 | 94 | 95 | 98 | 99 | 100 | 103 | 104 | 105 | 108 | 109 | 110 | 113 | 114 | 115 | 118 | 119 | 120 | 123 | 124 | 125 | 128 | 129 | 130 | 133 |
 
46 | w
51 | wsdiscovery 52 |
    56 | wsdiscovery.actions.bye 57 |
    61 | wsdiscovery.actions.hello 62 |
    66 | wsdiscovery.actions.probe 67 |
    71 | wsdiscovery.actions.probematch 72 |
    76 | wsdiscovery.actions.resolve 77 |
    81 | wsdiscovery.actions.resolvematch 82 |
    86 | wsdiscovery.daemon 87 |
    91 | wsdiscovery.envelope 92 |
    96 | wsdiscovery.message 97 |
    101 | wsdiscovery.namespaces 102 |
    106 | wsdiscovery.qname 107 |
    111 | wsdiscovery.scope 112 |
    116 | wsdiscovery.service 117 |
    121 | wsdiscovery.threaded 122 |
    126 | wsdiscovery.udp 127 |
    131 | wsdiscovery.uri 132 |
134 | 135 | 136 |
137 | 138 |
139 |
140 | 186 |
187 |
188 | 196 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /docs/serialize_deserialize.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WS-Discovery messages (de)serialization — WSDiscovery 2.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |

WS-Discovery messages (de)serialization

37 |

Functions to serialize and deserialize messages between SOAP envelope & string representations

38 |
39 |
40 | wsdiscovery.message.createSOAPMessage(env)
41 |

serialize SOAP envelopes into XML strings

42 |
43 | 44 |
45 |
46 | wsdiscovery.message.parseSOAPMessage(data, ipAddr)
47 |

deserialize XML message strings into SOAP envelope objects

48 |
49 | 50 |

Serialization & deserialization functions for each message:

51 | 61 | 65 |
66 | 67 | 68 |
69 | 70 |
71 |
72 | 136 |
137 |
138 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | --------------------------------------------------------------------------------