├── 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 | [](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 |
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 |
41 |
Table of contents:
42 |
67 |
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 |
34 |
35 |
36 |
WS-Discovery message facilities
37 |
38 |
The following are provided:
39 |
51 |
52 |
53 |
54 |
55 |
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 |
47 |
Facilities provided by the framework:
48 |
66 |
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 |
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 |
42 |
43 |
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 |
--------------------------------------------------------------------------------