pax_global_header00006660000000000000000000000064131330142000014475gustar00rootroot0000000000000052 comment=6221363fc20b10f9ad9396db9bea6b43cb555212 txdbus-1.1.0/000077500000000000000000000000001313301420000130055ustar00rootroot00000000000000txdbus-1.1.0/.gitignore000066400000000000000000000001231313301420000147710ustar00rootroot00000000000000*\.pyc *\.egg-info _trial_temp* *\.pdf *\.html dist MANIFEST .tox *.egg* .coverage txdbus-1.1.0/.travis.yml000066400000000000000000000007531313301420000151230ustar00rootroot00000000000000sudo: false dist: trusty language: python cache: pip python: - 2.7 - 3.3 - 3.4 - 3.5 - 3.6 - 3.7-dev env: matrix: - TOXENV=py global: - LOGNAME=travis matrix: allow_failures: - python: 3.7-dev include: - python: 3.6 env: TOXENV=flake8 addons: apt: packages: - dbus - dbus-x11 install: - pip install tox before_script: - dbus-launch --version script: - dbus-launch -- tox after_script: - pip install codecov - codecov txdbus-1.1.0/CHANGELOG.rst000066400000000000000000000006321313301420000150270ustar00rootroot00000000000000Changelog ========= Version 1.1.0 ------------- * Preliminary support for Python 3. * Dropped support for Python 2.6. * Added support for the DBus ``h`` (UNIX_FD) argument types. * Fixed ``path_namespace`` based routing rule handling. * Moved tests from txdbus/ package to the root of the project. * Added flake8 checks to automated tests. * Added automated code coverage tracking. * Created CHANGELOG.rst. txdbus-1.1.0/LICENSE000066400000000000000000000020661313301420000140160ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2012 Tom Cocagne Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. txdbus-1.1.0/MANIFEST.in000066400000000000000000000001061313301420000145400ustar00rootroot00000000000000 include setup.py include txdbus global-exclude _trial_temp graft doc txdbus-1.1.0/README.rst000066400000000000000000000045231313301420000145000ustar00rootroot00000000000000TxDBus ====== .. image:: https://travis-ci.org/cocagne/txdbus.svg?branch=master :target: https://travis-ci.org/cocagne/txdbus :alt: Travis CI build status .. image:: https://codecov.io/gh/cocagne/txdbus/branch/master/graph/badge.svg :target: https://codecov.io/gh/cocagne/txdbus :alt: Codecov coverage report .. image:: https://img.shields.io/pypi/v/txdbus.svg :target: https://pypi.python.org/pypi/txdbus :alt: version on pypi .. image:: https://img.shields.io/pypi/l/txdbus.svg :target: https://github.com/cocagne/txdbus/blob/master/LICENCE :alt: licence Tom Cocagne v1.1.0, July 2017 Introduction ------------ TxDBus is a native Python implementation of the `DBus protocol`_ for the Twisted_ networking framework. In addition to a tutorial_, and collection of examples_ the documentation for this project also includes `An Overview of the DBus Protocol`_. *License*: MIT_ .. _DBus Protocol: https://dbus.freedesktop.org/doc/dbus-specification.html .. _Twisted: https://twistedmatrix.com/trac/ .. _Tutorial: https://packages.python.org/txdbus .. _Examples: https://github.com/cocagne/txdbus/tree/master/doc/tutorial_examples .. _An Overview of the DBus Protocol: https://packages.python.org/txdbus/dbus_overview.html .. _MIT: https://choosealicense.com/licenses/mit/ Usage Example ------------- .. code-block:: python #!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import error, client @defer.inlineCallbacks def show_desktop_notification(): ''' Displays "Hello World!" in a desktop notification window for 3 seconds ''' con = yield client.connect(reactor, 'session') notifier = yield con.getRemoteObject('org.freedesktop.Notifications', '/org/freedesktop/Notifications') nid = yield notifier.callRemote('Notify', 'Example Application', 0, '', 'Tx DBus Example', 'Hello World!', [], dict(), 3000) reactor.stop() reactor.callWhenRunning(show_desktop_notification) reactor.run() txdbus-1.1.0/doc/000077500000000000000000000000001313301420000135525ustar00rootroot00000000000000txdbus-1.1.0/doc/dbus_overview.asciidoc000066400000000000000000000707311313301420000201450ustar00rootroot00000000000000DBus Overview ============= Tom Cocagne v1.0.1, August 2012 D-Bus is an Inter-Process Communication (IPC) and Remote Procedure Calling (RPC) mechanism specifically designed for efficient and easy-to-use communication between processes running on the same machine. It is intended to replace the amalgam of competing IPC frameworks in this domain with a single, unified protocol that is tailored specifically for meeting the needs of secure, intra-system IPC. There are two primary use-cases for which D-Bus is designed: * As a "system bus" for communicating between system applications and user sessions * As a "session bus" for exhanging data between applications in a desktop environments As the name implies, D-Bus uses a logical "Bus" over which connected applications may communicate. For ease of use, this communication takes place via a simple object model that supports both RPC and publish-subscribe mechanisms. Applications connected to the bus may query for the availability of objects, call remote methods on them, and request notification for the signals they emit. The APIs for objects exported over D-Bus are defined by the interfaces they support. Interfaces in D-Bus contain a comprehensive list of all methods and signals supported by the interface as well as the exact type an number of arguments they require. Any number of interfaces may be simultaneously supported by objects. This feature may be used to achieve a similar effect to the inheritance mechanism which is available in many programming languages but not directly supported by D-Bus itself. To support a dynamic execution environment and ease of integration, D-Bus includes a standard introspection mechanism for the run-time querying of object interfaces. The D-Bus bindings for many dynamic languages make use of this feature to prevent the user from needing to explicitly specify the remote interfaces being used. Objects in D-Bus are uniquely identified through the combination of a *bus name* and *object path*. The *bus name* uniquely identifies a client application connected to the bus and the *object path* uniquely identifies the object within that application. Each application is automatically assigned a unique and effectively random bus name upon connection. Applications wishing to export objects over D-Bus can, and typically do, request ownership of additional "well known" bus names to provide a simple and consistent location for other applications to access at run time. The bus name mechanism in D-Bus is similar to dynamic IP address allocation paired with DNS name resolution services. Newly connected D-Bus applications are assinged a random bus name (similar to random IP address allocation). To prevent potential clients from needing to somehow discover that random bus name in order to access the application's exported objects, the providing application may instead request additional "well-known" bus names to serve as aliases for its unique name (similar to multiple DNS entries resolving to the same IP address). For example, the Gnome NetworkManager uses the bus name "org.freedesktop.NetworkManager" to export its objects. Rather than attempting to determine the unique, random name assigned to the network manager at connection time, applications wishing to make use of the network manager will may simply use the well-known name. Key Components -------------- Object Paths ~~~~~~~~~~~~ D-Bus objects are identified within an application via their *object path*. The *object path* intentionally looks just like a standard Unix file system path. The primary difference is that the path may contain only numbers, letters, underscores, and the '/' character. From a functional standpoint, the primary purpose of object paths is simply to be a unique identifier for an object. The "hierarchy" implied the path structure is almost purely conventional. Applications with a naturally hierarchical structure will likely take advantage of this feature while others may choose to ignore it completely. Interfaces ~~~~~~~~~~ D-Bus interfaces define the methods and signals supported by D-Bus objects. In order to make use of a D-Bus interface it must be known to remote users. This interface definition may be hard coded into an application or may be queried at run time through the D-Bus introspection mechanism. Although technically optional, most D-Bus implementations automatically provide introspection support for the objects they export. The naming convention for D-Bus interfaces is similar to that of well-known bus names. To reduce the chance of name clashes, the accepted convention is to prefix the interface name with a reversed DNS domain name. For example, the standard "Introspection" interface is "org.freedesktop.DBus.Introspectable" The constraints on methods and signals names contained within D-Bus interfaces are similar to those employed by many popular programming languages. They must begin with a letter and may consist of only letters, numbers, and underscores. Each method and signal explicitly defines the number and types of arguments they accept. These are encoded as D-Bus Signature strings. Signature Strings ~~~~~~~~~~~~~~~~~ D-Bus uses a string-based type encoding mechanism called *Signatures* to describe the number and types of arguments requried by methods and signals. Signatures are used for interface declaration/documentation, data marshalling, and validity checking. Their string encoding uses a simple, though expressive, format and a basic understanding of it is required for effective D-Bus use. The table below lists the fundamental types and their encoding characters. .Signature Encoding [width="40%",cols="1m,10",options="header"] |======================================================== |Character |Code Data Type |y |8-bit unsigned integer |b |boolean value |n |16-bit signed integer |q |16-bit unsigned integer |i |32-bit signed integer |u |32-bit unsigned integer |x |64-bit signed integer |t |64-bit unsigned integer |d |double-precision floating point (IEEE 754) |s |UTF-8 string (no embedded nul characters) |o |D-Bus Object Path string |g |D-Bus Signature string |a |Array |( |Structure start |) |Structure end |v |Variant type (described below) |{ |Dictionary/Map begin |} |Dictionary/Map end |h |Unix file descriptor |======================================================== No spacing is allowed within signature strings. When defining signatures for multi-argument methods and signatures, the types of each argument are concatenated into a single string. For example, the signature for a method that accepts two integers followed by a string would be "iis". Container Types ^^^^^^^^^^^^^^^ There are four container types: Structs, Arrays, Variants, and Dictionaries. Structs +++++++ Structures are enclosed by parentheses and may contain any valid D-Bus signature. For example, +(ii)+ defines a structure containing two integers and +((ii)s)+ defines a structure containing a structure of two integers followed by a string. Empty structures are not permitted. Arrays ++++++ Arrays define a list consisting of members with a fixed type. The array charater +a+ must be immediately followed by the type of data in the array. This must be a *single, complete type*. .Examples * +ai+ - Array of 32-bit integers * +a(ii)+ - Array of structures * +aai+ - Array of array of integers Variants ++++++++ Variants may contain a value of any type. The marshalled value of the variant includes the D-Bus signature defining the type of data it contains. Dictionaries ++++++++++++ Dictionaries work in a manner similar to that of structures but are restricted to arrays of key = value pairs. The key must be a basic, non-container type and the value may be any single, complete type. Dictionaries are defined in terms of arrays using +{}+ to surround the key and value types. Examples: * +$$a{ss}$$+ - string => string * +$$a{is}$$+ - 32-bit signed integer => string * +$$a{s(ii)}$$+ - string => structure containing two integers * +$$a{sa{ss}}$$+ - string => dictionary of string to string Methods ~~~~~~~ D-Bus methods may accept any number of arguments and may return any number of values, including none. When method calls specify no return value, a "method return" message is still sent to the calling application. This allows applications using the remote API to know that the remote method invocation has completed even if no useful result is returned. The only case in which a return message is not sent in acknowledgement to a D-Bus method call is if the "no reply expected" flag is sent as part of the method invocation. This is an optional D-Bus implementation feature that, if supported, may be used to suppress the generation of the reply message. The signature strings for method argument lists and method return values may contain multiple types. For each argument accepted and each value returned, the type of the argument/return value is simply appended, in order, to the signature string. For example, a method accepting two unsigned 32-bit integers and returning two strings would use "uu" for the argument signature and "ss" for the return value signature. If the method accepts no arguments or returns no values, the signatures for those attributes are empty strings. Signals ~~~~~~~ D-Bus signals provide a 1-to-many, publish-subscribe mechanism. Similar to method return values, D-Bus signals may contain an arbitrary ammount of data. Unlike methods however, signals are entirely asynchronous and may be emitted by D-Bus objects at any time. By default, signals emitted from D-Bus objects will not be sent to any clients. To receive signals, client applications must explicitly register their interest in the specific signals emitted by D-Bus objects. Bus Names ~~~~~~~~~ There are two types of bus names: *unique* and *well-known*. Unique bus names are assigned by the bus to each client connection. They begin with a ':', and they are guaranteed never to be reused during the life of the bus. Unlike process ids which can roll-over and be reused, unique bus names are guaranteed to be truly unique. D-Bus clients may request additional, "well-known" bus names in order to offer their services under names that are agreed upon by convention. This allows applications to easily find the offered services at a known location. For example, the Gnome Network Manager offers its services on the well-known bus name of 'org.freedesktop.NetworkManager' to prevent prospective users from needing to determine the network manager's unique bus name. Well known bus names have essentially the same naming requirements as DNS domain names (though bus names may include the '_' character). As the whole point of "well-known" bus names is simplicity of resource naming, the accepted convention for avoiding unintentional name clashes is to prefix the well known bus names with a reversed DNS domain name: *org.frobozz.Zork* D-Bus imposes a simple arbitration mechanism on the ownership of bus names. If a client requests an unused name, it is immediately granted. If the name is currently owned, however, the client has two options for obtaining the name. It can attempt to steal ownership or it can place itself in a queue for eventual ownership. To steal ownership, two conditions must be met. The application currently owning the name must have informed the bus that it is willing to relinquish ownership of the name and the stealing application must set the flag enabling the ownership request to steal the name from the owning application. If both of these conditions are not met, the request for the name will simply fail and return an error code stating that the name is currently unavailable. To queue for ownership, the requesting application sets a flag in the ownership request indicating that if the name is not currently available, the request should be queued. If, at some point in the future, ownership is granted to the client, it will be informed of this fact by way of a signal. Message Routing ~~~~~~~~~~~~~~~ Messages are routed to client connections by destination address and match rules. Destination address routing is used when a message's destination parameter contains a unique or well-known bus name. This is typically the case with method call and return messages which inherently require 1-to-1 communication. Signals, on the other hand, are broadcast messages with no specific destination. For these, client applications must register match rules in order to receive the signals they are interested in. Although signal registration is the most common use for message matching rules, DBus message matching rules can be used to request delivery of any messages transmitted over the bus; including the method call and return messages between arbitrary bus clients. The messages delivered via match rules are always copies so it is not possible to use this mechanism to redirect messages away from their intended targets. Message match rules are set via the 'org.freedesktop.DBus.AddMatch' method and are formatted as a series of comma-separated, key=value paris contained within a single string. Excluded keys indicate wildcard matches that match every message. If all components of the match rule match a message, it will be delivered to the requesting application. The following table provides a terse description of the keys and values that may be specified in match rules. For full details, please refer to the DBus specification. .Rule Elements [width="90%",cols="1,2,5",options="header"] |============================================================================== |key |possible values | Description |type |'signal', 'method_call', 'method_return', 'error'| Matches specific message type |sender |Unique or well-known bus name | Matches messages sent by a particular sender |interface |Interface name | Matches messages sent to or from the specified interface |member |Any valid method or signal name | Matches method and signal names |path |Any valid object path | Matches messages sent to or from the specified object |path_namespace |Any valid object path | Matches messages sent to or from all objects at or below the specified path |destination |Unique or well-known bus name | Matches messages sent to the specified destination |arg[0,1,2...] |Any string | Matches messages based on the content of their arguments. Only arguments of string type may be matched. |arg[0,1,2...]path |Any string | Specialized matching for path-like arguments. Ex: arg0path='/aa/bb' will match '/', '/aa/', '/aa/bb/cc' but not '/aa', '/aa/b', or '/aa/bb' |arg0namespace |Any string | Specialized matching for partial bus names. Primarily intended for monitoring NameOwnerChanged for a group of related bus names. Ex: 'member="NameOwnerChanged", arg0namespace="com.foo.bar"' will match "com.foo.bar.baz" and "com.foo.bar.quux" |============================================================================== Bus Addresses ~~~~~~~~~~~~~ Bus addresses specify the connection mechanism (TCP, Unix socket, etc) and any extra information required for successful connection and authentication. The format for bus addresses is a transport name followed by a colon followed by an optional, comma-separated set of key=value pairs. Multiple addresses may be specified in the same string, separated by semicolons. Unix Addresses ^^^^^^^^^^^^^^ Unix addresses use the 'unix' transport and support the following key/value pairs. Each of the key/value paris are mutually exclusive with the others so only one may be used. [width="90%",cols="1m,10",options="header"] |============================================================================== |Key |Values |path |Filesystem path of the socket |tmpdir | Temporary directory in which a randomly named socket file with a 'dbus-' prefix will be created. |abstract |Name of an abstract unix socket |============================================================================== TCP Addresses ^^^^^^^^^^^^^ TCP addresses use the 'tcp' transport and support the following key/value pairs. TCP sockets provide very poor security over an insecure network. [width="90%",cols="1m,10",options="header"] |============================================================================== |Key |Values |host |DNS name or IP address |port |Port number |family |"ipv4" or "ipv6" |============================================================================== Nonce-secured TCP Addresses ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Nonce-secured TCP sockets provide 'slightly' better security than raw TCP (though still very poor on an unsecure network). This transport requires the user to read a file and send the contents of that file over the connection immediately after connection. It provides little protection against a man-in-the-middle attack over a network but is reasonably secure for use over the local-loopback IP address on a multi-user system. [width="90%",cols="1m,10",options="header"] |============================================================================== |Key |Values |host |DNS name or IP address |port |Port number |family |"ipv4" or "ipv6" |noncefile |Path to file containing the nonce |============================================================================== Standard Interfaces ------------------- org.freedesktop.DBus.Peer ~~~~~~~~~~~~~~~~~~~~~~~~~ Ping() ^^^^^^ Used to test for liveliness of a connection. The object path is ignored. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments v|   v|   |returns v|   v|   |============================================================================== GetMachineId() ^^^^^^^^^^^^^^ Returns a hex-coded UUID representing the machine the process is running on. This id should be identical for all processes running on the same machine. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments v|   v|   |returns | s |The hex-coded UUID |============================================================================== org.freedesktop.DBus.Introspectable ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Introspect() ^^^^^^^^^^^^ Returns a string containing an XML description of the object's supported interfaces. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments v|   v|   |returns | s |The XML interface definitions |============================================================================== org.freedesktop.DBus.Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Get(interface_name, property_name) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Returns a Variant containing the requested property. The interface name may be '', in which case, the implementation will use the first interface it finds that supports a property with the given name. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |ss v|   |returns |v v|   |============================================================================== Set(interface_name, property_name, value) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sets the specified property. Similar to Get, the interface_name argument may be ''. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |ssv v|   |returns v|   v|   |============================================================================== GetAll(interface_name) ^^^^^^^^^^^^^^^^^^^^^^ Return a dictionary of string property names to variant values. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |s v|   |returns |$$a{sv}$$ v|   |============================================================================== PropertiesChanged ^^^^^^^^^^^^^^^^^ Signal indicating property values have changed * parameters: +$$sa{sv}as$$+ . Name of interface with changed properties . Dictionary of string names to variant values of the changed properties . Array of string names indicating properties that have changed but for which values are not conveyed. org.freedesktop.DBus.ObjectManager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GetManagedObjects() ^^^^^^^^^^^^^^^^^^^ Returns the subordinate objects paths and their supported interfaces and properties. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments v|   v|   |returns |$$a{oa{sa{sv}}}$$ v|   |============================================================================== InterfacesAdded ^^^^^^^^^^^^^^^ Signal indicating interfaces have been added to an object * parameters: +$$oa{sa{sv}}$$+ . Object path gaining the new interfaces . Dictionary of interfaces names and their properties InterfacesRemoved ^^^^^^^^^^^^^^^^^ Signal indicating interfaces have been removed from an object * parameters: +$$oas$$+ . Object path loosing the interfaces . List of interface names Bus Services ~~~~~~~~~~~~ RequestName(new_bus_name, flags) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Requests the specified bus name be assigned to the client connection. The flags argument and return value is described below. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |su | flags:: Bit mask with the following fields * *0x1* - "Allow Replacement" flag. If specified, a subsequent request for the bus name by another application that specifies the "Replace Existing" flag will steal ownership of the bus name from the current owner. When the name is stolen, the original owning application will automatically be placed at the end of the name ownership queue unless the "Do not queue" flag is specified. * *0x2* - "Replace Existing" flag. If specified and the bus name is currently owned by an application that set the "Allow Replacement" flag the name will be stolen from the current owner and assigned to the calling client. * *0x4* - "Do not queue" flag. If specified and the bus name is currently owned by another application, the request will simply fail. Without this flag, the requesting application will be queued for eventual ownership. |returns |u | 1. The name was successfully acquired 2. Failed to acquire ownership but queued for eventual ownership 3. Failed to acquire ownership and not queued for eventual ownership 4. The calling application already owns the bus name |============================================================================== ReleaseName(bus_name) ^^^^^^^^^^^^^^^^^^^^^ Releases ownership of the specified bus name [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |s |Bus name to release |returns |u |1 = Success, 2 = Name does not exist, 3 = Not owner |============================================================================== ListQueuedOwners(bus_name) ^^^^^^^^^^^^^^^^^^^^^^^^^^ Lists the connections queued for ownership of a well-known bus name (unique connection names do not have queues). [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |s |Well-known bus name to query |returns |as |List of queued connection names |============================================================================== GetNameOwner(bus_name) ^^^^^^^^^^^^^^^^^^^^^^ Returns the current owner of the well-known bus name. If the name currently has no owner, this method will return a org.freedesktop.DBus.Error.NameHasNoOwner error. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |s v|   |returns |s v|   |============================================================================== NameHasOwner(name) ^^^^^^^^^^^^^^^^^^ Checks if the specified currently has an owner [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |s |Bus name to query |returns |b |True if the name is currently in-use |============================================================================== NameOwnerChanged ^^^^^^^^^^^^^^^^ Signals the change in ownership of a bus name. This signal may be used to detect the arrival of new bus names as well as the ownership changes for existing bus names. * parameters: +$$sss$$+ . Bus name for which ownership has changed . Unique connection id for the old owner or empty string for none . Unique connection id for the new owner or empty string for none NameLost ^^^^^^^^ Signal sent to the specific application when it loses ownership of a bus name. * parameters: +$$s$$+ ** The name which was lost NameAcquired ^^^^^^^^^^^^ Signal sent to the specific application when it gains ownership of a bus name * parameters: +$$s$$+ ** The name which was acquired ListNames() ^^^^^^^^^^^ Returns the list of all currently owned bus names. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments v|   v|   |returns |as v|   |============================================================================== ListActivatableNames() ^^^^^^^^^^^^^^^^^^^^^^ Returns list of all names that can be activated on the bus. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments v|   v|   |returns |as v|   |============================================================================== StartServiceByName(name, flags) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Requests the specified service be started. The flags argument is currently not used. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |su |The name of the service to start, unused flags |returns |u |1 = success, 2 = service already running |============================================================================== UpdateActivationEnvironment( environment ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Modifies the environment passed to services started by the bus. Some busses may disable this method for some or all users for security reasons. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |$$a{ss}$$ |Dictionary of key = value pairs |returns v|   v|   |============================================================================== GetConnectionUnixUser(bus_name) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Returns the uid of the process owning the specified bus name. If the call fails for any reason, an error is returned. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |s v|   |returns |u v|   |============================================================================== GetConnectionUnixProcessId(bus_name) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Returns the process id of the process owning the specified bus name. If the call fails for any reason, an error is returned [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |s v|   |returns |u v|   |============================================================================== AddMatch(match_rule) ^^^^^^^^^^^^^^^^^^^^ Adds a message matching rule for the current connection. See the "Message Matching" section for details [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments |s v|   |returns v|   v|   |============================================================================== RemoveMatch(match_rule) ^^^^^^^^^^^^^^^^^^^^^^^ Removes a previously registered match rule [cols="1e,^1m,10a",frame="none"] |============================================================================== |argumens |s v|   |returns v|   v|   |============================================================================== GetId() ^^^^^^^ Returns the unique ID for the bus. [cols="1e,^1m,10a",frame="none"] |============================================================================== |arguments v|   v|   |returns |s v|   |============================================================================== txdbus-1.1.0/doc/epydoc.conf000066400000000000000000000001121313301420000156760ustar00rootroot00000000000000[epydoc] name: Twisted DBus modules: tx output: html target: doc/apidocstxdbus-1.1.0/doc/examples/000077500000000000000000000000001313301420000153705ustar00rootroot00000000000000txdbus-1.1.0/doc/examples/avahi_browse000077500000000000000000000047451313301420000200010ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import error, client def resolved(*args): print args def error(*args): print args class AvahiBrowser (object): avahi_bus_name = 'org.freedesktop.Avahi' avahi_object_path = '/' @defer.inlineCallbacks def start(self, service_type, domain=''): con = yield client.connect(reactor, 'system') # Get a handle to the server self.server = yield con.getRemoteObject( self.avahi_bus_name, self.avahi_object_path ) # Create new service browser -> Get back path to object browser_path = yield self.server.callRemote( 'ServiceBrowserNew', -1, # interface: -1 is IF_UNSPEC -1, # protocol: -1 is PROTO_UNSPEC service_type, domain, 0 ) # Get service browser object self.browser = yield con.getRemoteObject( self.avahi_bus_name, browser_path ) # Setup listeners self.browser.notifyOnSignal('ItemNew', self.new_service) self.browser.notifyOnSignal('ItemRemove', self.remove_service) print "Now browsing for avahi services ..." def new_service(self, interface, protocol, name, stype, domain, flags): """Add servivce""" print "New service found:" print interface, protocol, name, stype, domain, flags print "Browsing new-found service:" self.server.callRemote( "ResolveService", interface, protocol, name, stype, domain, -1, 0 ).addCallbacks( resolved, error ) def remove_service(self, interface, protocol, name, stype, domain, flags): """Remove service""" print "Service was removed..." print interface, protocol, name, stype, domain, flags ab = AvahiBrowser() reactor.callWhenRunning(ab.start, '_avahidemo._tcp') reactor.run() txdbus-1.1.0/doc/examples/avahi_publish000077500000000000000000000045621313301420000201430ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client class AvahiServicePublisher (object): avahi_bus_name = 'org.freedesktop.Avahi' avahi_object_path = '/' def __init__(self): self._services = dict() self._client = None self._server = None @defer.inlineCallbacks def publish_service(self, name, service_type, port, txt_data=[], domain='', host=''): if self._client is None: self._client = yield client.connect(reactor, 'system') if self._server is None: self._server = yield self._client.getRemoteObject( self.avahi_bus_name, self.avahi_object_path ) # Create a new entry group and retrieve the path to the new object group_path = yield self._server.callRemote('EntryGroupNew') # Get entry group object group = yield self._client.getRemoteObject( self.avahi_bus_name, group_path ) # The D-Bus interface requires the text strings to be arrays of arrays of bytes encoded_txt = [ [ord(c) for c in s] for s in txt_data ] # Use the group's AddService method to register the service yield group.callRemote('AddService', -1, # interface: -1 is IF_UNSPEC -1, # protocol: -1 is PROTO_UNSPEC 0, # flags name, service_type, domain, host, port, encoded_txt) # Commit the changes yield group.callRemote('Commit') self._services[ name ] = group @defer.inlineCallbacks def stop_publishing(self, name): if name in self._services: yield self._services[name].callRemote('Reset') @defer.inlineCallbacks def demo(): name = 'Avahi Publish Example' service_type = '_avahidemo._tcp' port = 12345 txt_data = ['hello=world'] asp = AvahiServicePublisher() yield asp.publish_service(name, service_type, port, txt_data) print "Publishing Avahi service:", name reactor.addSystemEventTrigger('before', 'shutdown', lambda : asp.stop_publishing(name)) reactor.callWhenRunning(demo) reactor.run() txdbus-1.1.0/doc/examples/err_client000077500000000000000000000011551313301420000174460ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client def onReply( rep ): print 'Wont see this' def onFailed(err): print 'Remote error raised: ', err.getErrorMessage() print 'DBus Error Name: ', err.value.errName print 'Message: ', err.value.message dc = client.connect(reactor) dc.addCallback(lambda cli: cli.getRemoteObject( 'org.example', '/MyObjPath' )) dc.addCallback(lambda ro: ro.callRemote('throwError')) dc.addCallbacks(onReply, onFailed) dc.addBoth( lambda _: reactor.stop() ) reactor.run() txdbus-1.1.0/doc/examples/err_server000077500000000000000000000015011313301420000174710ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client, objects from txdbus.interface import DBusInterface, Method, Signal class ExampleException (Exception): dbusErrorName = 'org.example.ExampleException' class MyObj (objects.DBusObject): count = 0 iface = DBusInterface('org.example.MyIFace', Method('throwError')) dbusInterfaces = [iface] def __init__(self, objectPath): objects.DBusObject.__init__(self, objectPath) def dbus_throwError(self): self.count += 1 raise ExampleException('Exception count %d' % self.count) def onConnected(conn): conn.exportObject( MyObj('/MyObjPath') ) return conn.requestBusName('org.example') d = client.connect(reactor) d.addCallback(onConnected) reactor.run() txdbus-1.1.0/doc/examples/fd_client.py000077500000000000000000000036561313301420000177060ustar00rootroot00000000000000#!/usr/bin/env python """ fd_client.py Grabs '/path/to/FDObject' on 'org.example' bus and demonstrates calling remote methods with UNIX open file descriptors as arguments (type 'h' as per the dbus spec). NOTE: Passing open UNIX filedescriptors accross RPC / ICP mechanisms such as dbus requires the underlying transport to be a UNIX domain socket. """ from __future__ import print_function from twisted.internet import defer, task from txdbus import client @defer.inlineCallbacks def call_remote_verbose(obj, method, *args, **kwargs): print('calling %s%s' % (method, args), end=' = ') result = yield obj.callRemote(method, *args, **kwargs) print(repr(result)) defer.returnValue(result) @defer.inlineCallbacks def main(reactor): PATH = '/path/to/FDObject' BUSN = 'org.example' try: bus = yield client.connect(reactor) print('connected to dbus') object = yield bus.getRemoteObject(BUSN, PATH) print('obtained remote object') except Exception as e: print('failed obtaining remote object: %s' % (e,)) defer.returnValue(None) # Open this source file. Ask remote to read it and return byte count. with open(__file__, 'rb') as f: yield call_remote_verbose(object, 'lenFD', f.fileno()) # Open this source file. Ask remote to read 10 bytes from it. with open(__file__, 'rb') as f: yield call_remote_verbose(object, 'readBytesFD', f.fileno(), 10) # Like before, now exercise passing two open UNIX FDs. # (will not be available under Twisted < 17.1.0) with open(__file__, 'rb') as f1, open(__file__, 'rb') as f2: fd1 = f1.fileno() fd2 = f2.fileno() try: yield call_remote_verbose(object, 'readBytesTwoFDs', fd1, fd2, 5) except Exception as e: print('remote call failed: %s' % (e,)) bus.disconnect() print('disconnected') if __name__ == '__main__': task.react(main) txdbus-1.1.0/doc/examples/fd_server.py000077500000000000000000000057321313301420000177330ustar00rootroot00000000000000#!/usr/bin/env python """ fd_server.py Publishes FDObject at PATH on BUSN (see code), demonstrating server side implementation of methods with open UNIX file descriptors as arguments (type 'h' as per the dbus spec). NOTE:: Passing open UNIX filedescriptors accross RPC / ICP mechanisms such as dbus requires the underlying transport to be a UNIX domain socket. """ from __future__ import print_function import os import twisted from twisted.internet import defer, reactor from txdbus import client, interface, objects def trace_method_call(method): def wrapper(*args, **kwargs): print('handling %s%r' % (method.__name__, args[1:]), end=' = ') result = method(*args, **kwargs) print(repr(result)) return result return wrapper class FDObject(objects.DBusObject): _methods = [ 'org.example.FDInterface', interface.Method('lenFD', arguments='h', returns='t'), interface.Method('readBytesFD', arguments='ht', returns='ay'), ] @trace_method_call def dbus_lenFD(self, fd): """ Returns the byte count after reading till EOF. """ f = os.fdopen(fd, 'rb') result = len(f.read()) f.close() return result @trace_method_call def dbus_readBytesFD(self, fd, byte_count): """ Reads byte_count bytes from fd and returns them. """ f = os.fdopen(fd, 'rb') result = f.read(byte_count) f.close() return bytearray(result) @trace_method_call def dbus_readBytesTwoFDs(self, fd1, fd2, byte_count): """ Reads byte_count from fd1 and fd2. Returns concatenation. """ result = bytearray() for fd in (fd1, fd2): f = os.fdopen(fd, 'rb') result.extend(f.read(byte_count)) f.close() return result # Only export 'readBytesTwoFDs' if we're running Twisted >= 17.1.0 which # is required to handle multiple UNIX FD arguments. _minTxVersion = type(twisted.version)('twisted', 17, 1, 0) if twisted.version >= _minTxVersion: _methods.append( interface.Method('readBytesTwoFDs', arguments='hht', returns='ay') ) else: print('Twisted version < %s, not exposing %r' % ( _minTxVersion.base(), 'readBytesTwoFDs' )) del _minTxVersion dbusInterfaces = [interface.DBusInterface(*_methods)] @defer.inlineCallbacks def main(reactor): PATH = '/path/to/FDObject' BUSN = 'org.example' try: bus = yield client.connect(reactor) except Exception as e: print('failed connecting to dbus: %s' % (e,)) reactor.stop() defer.returnValue(None) print('connected to dbus') object = FDObject(PATH) bus.exportObject(object) yield bus.requestBusName(BUSN) print('exported %r on %r at %r' % (object.__class__.__name__, BUSN, PATH)) if __name__ == '__main__': reactor.callWhenRunning(main, reactor) reactor.run() txdbus-1.1.0/doc/examples/object_arg_client000077500000000000000000000014261313301420000207560ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client def onReply( rep ): print 'Remote method call result: ', rep def onFailed(err): print 'Failed: ', err.getErrorMessage() dc = client.connect(reactor) dc.addCallback(lambda cli: cli.getRemoteObject( 'org.example', '/MyObjPath' )) class DBSerializeable(object): dbusOrder = ['text', 'number'] def __init__(self, txt, num): self.text = txt self.number = num serialObj = DBSerializeable( 'Foobar', 1 ) dc.addCallback( lambda ro: ro.callRemote('exampleMethod', serialObj)) dc.addCallbacks(onReply, onFailed) dc.addBoth( lambda _: reactor.stop() ) reactor.run() txdbus-1.1.0/doc/examples/object_arg_server000077500000000000000000000017571313301420000210150ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client, objects from txdbus.interface import DBusInterface, Method, Signal class MyObj (objects.DBusObject): iface = DBusInterface('org.example.MyIFace', Method('exampleMethod', arguments='(si)', returns='s' )) dbusInterfaces = [iface] def __init__(self, objectPath): objects.DBusObject.__init__(self, objectPath) def dbus_exampleMethod(self, arg): print 'Received remote call. Argument: ', arg return 'You sent (%s,%d)' % (arg[0], arg[1]) def onConnected(conn): conn.exportObject( MyObj('/MyObjPath') ) return conn.requestBusName('org.example') def onReady(ignore): print 'Exporting object /MyObjPath on bus name "org.exmaple"' def onErr(err): print 'Failed to export object: ', err.getErrorMessage() d = client.connect(reactor) d.addCallback(onConnected) d.addCallback(onReady) d.addErrback(onErr) reactor.run() txdbus-1.1.0/doc/examples/ping_client000077500000000000000000000016431313301420000176150ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client from txdbus.interface import DBusInterface, Method iface = DBusInterface( 'org.freedesktop.DBus.Peer', Method('Ping') ) def onConnected(cli): d = cli.getRemoteObject( 'org.example', '/MyObjPath', iface ) def gotObject( ro ): def onReply( rep ): print 'Object is available' reactor.stop() def onErr( err ): print 'Object does not exist. Retrying...' def ping(): reactor.callLater(1, ping) ro.callRemote('Ping').addCallbacks( onReply, onErr ) ping() d.addCallbacks( gotObject ) return d def onFailed(err): print 'Failed: ', err.getErrorMessage() dc = client.connect(reactor) dc.addCallbacks(onConnected) dc.addErrback(onFailed) reactor.run() txdbus-1.1.0/doc/examples/ping_client2000077500000000000000000000014711313301420000176760ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client from txdbus.interface import DBusInterface, Method iface = DBusInterface( 'org.freedesktop.DBus.Peer', Method('Ping') ) def onConnected(cli): d = cli.getRemoteObject( 'org.example', '/MyObjPath', iface ) def gotObject( ro ): def onReply( rep ): print 'Ping Success' dp = ro.callRemote('Ping') dp.addCallback( onReply ) return dp d.addCallbacks( gotObject ) return d def onFailed(err): print 'Failed: ', err.getErrorMessage() dc = client.connect(reactor) dc.addCallbacks(onConnected) dc.addErrback(onFailed) dc.addBoth( lambda _: reactor.stop() ) reactor.run() txdbus-1.1.0/doc/examples/ping_direct000077500000000000000000000011231313301420000176020ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client def onConnected(cli): d = cli.callRemote( '/MyObjPath', 'Ping', interface = 'org.freedesktop.DBus.Peer', destination = 'org.example' ) def onReply( rep ): print 'Ping Success' d.addCallback( onReply ) return d def onFailed(err): print 'Failed: ', err.getErrorMessage() dc = client.connect(reactor) dc.addCallbacks(onConnected) dc.addErrback(onFailed) dc.addBoth( lambda _: reactor.stop() ) reactor.run() txdbus-1.1.0/doc/examples/ping_test000077500000000000000000000011571313301420000173160ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client def onConnected(cli): d = cli.getRemoteObject( 'org.example', '/MyObjPath' ) def gotObject( ro ): def onReply( rep ): print 'Ping Success' dp = ro.callRemote('Ping') dp.addCallback( onReply ) return dp d.addCallbacks( gotObject ) return d def onFailed(err): print 'Failed: ', err.getErrorMessage() dc = client.connect(reactor) dc.addCallbacks(onConnected) dc.addErrback(onFailed) dc.addBoth( lambda _: reactor.stop() ) reactor.run() txdbus-1.1.0/doc/examples/signal_client000077500000000000000000000006741313301420000201400ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client def onSignal( tickCount ): print 'Got tick signal: ', tickCount def onErr(err): print 'Error: ', err.getErrorMessage() d = client.connect(reactor) d.addCallback( lambda cli: cli.getRemoteObject( 'org.example', '/Signaller' ) ) d.addCallback( lambda ro: ro.notifyOnSignal( 'tick', onSignal ) ) d.addErrback( onErr ) reactor.run() txdbus-1.1.0/doc/examples/signal_client2000077500000000000000000000021431313301420000202130ustar00rootroot00000000000000#!/usr/bin/env python # Manually defining the interface and passing it to getRemoteObject() # prevents the need for DBus object introspection. The primary # advantage of this is that the proxy object can be successfully # created even if the remote object does not actually exist. As signal # registration is a function of the bus itself and not of the actual # object, this script may be run prior launching signal_server. # from twisted.internet import reactor from txdbus import client from txdbus.interface import DBusInterface, Signal iface = DBusInterface( 'org.example.SignalSender', Signal('tick', 'u') ) def onSignal( tickCount ): print 'Got tick signal: ', tickCount def onErr(err): print 'Error: ', err.getErrorMessage() d = client.connect(reactor) d.addCallback( lambda cli: cli.getRemoteObject( 'org.example', '/Signaller', iface) ) d.addCallback( lambda ro: ro.notifyOnSignal( 'tick', onSignal ) ) d.addErrback( onErr ) reactor.run() txdbus-1.1.0/doc/examples/signal_server000077500000000000000000000022171313301420000201630ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client, objects from txdbus.interface import DBusInterface, Method, Signal class SignalSender (objects.DBusObject): iface = DBusInterface( 'org.example.SignalSender', Signal('tick', 'u') ) dbusInterfaces = [iface] def __init__(self, objectPath): objects.DBusObject.__init__(self, objectPath) self.count = 0 def sendTick(self): self.emitSignal('tick', self.count) self.count += 1 reactor.callLater(1, self.sendTick) def onErr(err): print 'Failed: ', err.getErrorMessage() reactor.stop() def onConnected(conn): s = SignalSender('/Signaller') conn.exportObject( s ) dn = conn.requestBusName('org.example') def onReady(_): print 'Emitting periodic "tick" signals. Bus name: org.example, Object Path /Signaller' s.sendTick() dn.addCallback( onReady ) return dn dconnect = client.connect(reactor) dconnect.addCallback(onConnected) dconnect.addErrback(onErr) reactor.run() txdbus-1.1.0/doc/examples/simple_client000077500000000000000000000011651313301420000201500ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client def onConnected(cli): d = cli.getRemoteObject( 'org.example', '/MyObjPath' ) def gotObject( ro ): return ro.callRemote('exampleMethod', "Hello World!") def gotReply( rep ): print 'Remote method call result: ', rep d.addCallbacks( gotObject ) d.addCallback( gotReply ) return d def onFailed(err): print 'Failed: ', err.getErrorMessage() dc = client.connect(reactor) dc.addCallbacks(onConnected) dc.addErrback(onFailed) dc.addBoth( lambda _: reactor.stop() ) reactor.run() txdbus-1.1.0/doc/examples/simple_client2000077500000000000000000000011241313301420000202250ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client def onReply( rep ): print 'Remote method call result: ', rep def onFailed(err): print 'Failed: ', err.getErrorMessage() dc = client.connect(reactor) dc.addCallback(lambda cli: cli.getRemoteObject( 'org.example', '/MyObjPath' )) dc.addCallback(lambda ro: ro.callRemote('exampleMethod', "Hello World!")) dc.addCallbacks(onReply, onFailed) dc.addBoth( lambda _: reactor.stop() ) reactor.run() txdbus-1.1.0/doc/examples/simple_server000077500000000000000000000017341313301420000202020ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client, objects from txdbus.interface import DBusInterface, Method, Signal class MyObj (objects.DBusObject): iface = DBusInterface('org.example.MyIFace', Method('exampleMethod', arguments='s', returns='s' )) dbusInterfaces = [iface] def __init__(self, objectPath): objects.DBusObject.__init__(self, objectPath) def dbus_exampleMethod(self, arg): print 'Received remote call. Argument: ', arg return 'You sent (%s)' % arg def onConnected(conn): conn.exportObject( MyObj('/MyObjPath') ) return conn.requestBusName('org.example') def onReady(ignore): print 'Exporting object /MyObjPath on bus name "org.exmaple"' def onErr(err): print 'Failed to export object: ', err.getErrorMessage() d = client.connect(reactor) d.addCallback(onConnected) d.addCallback(onReady) d.addErrback(onErr) reactor.run() txdbus-1.1.0/doc/examples/system_server000077500000000000000000000020171313301420000202300ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor from txdbus import client, objects from txdbus.interface import DBusInterface, Method, Signal class MyObj (objects.DBusObject): iface = DBusInterface('org.example.MyIFace', Method('exampleMethod', arguments='s', returns='s' )) dbusInterfaces = [iface] def __init__(self, objectPath): objects.DBusObject.__init__(self, objectPath) def dbus_exampleMethod(self, arg): print 'Received remote call. Argument: ', arg return 'You sent (%s)' % arg def onConnected(conn): print 'Connected to the system bus!' conn.exportObject( MyObj('/MyObjPath') ) return conn.requestBusName('org.example') def onReady(ignore): print 'Exporting object /MyObjPath on bus name "org.exmaple"' def onErr(err): print 'Failed to export object: ', err.getErrorMessage() d = client.connect(reactor, 'system') d.addCallback(onConnected) d.addCallback(onReady) d.addErrback(onErr) reactor.run() txdbus-1.1.0/doc/tutorial.asciidoc000066400000000000000000001017171313301420000171240ustar00rootroot00000000000000Tx DBus Tutorial ================ Tom Cocagne v1.0.3, May 2013 Introduction ------------ Tx DBus is a native-python implementation of the DBus protocol on top of the Twisted networking engine. The purpose of this tutorial is to provide an introduction to the use of Tx DBus and demonstrate the main APIs necessary to successfully incorproate it within Twisted applications. This tutorial assumes a basic understanding of both Twisted and DBus. The Twisted project provides excellent http://twistedmatrix.com/documents/current/core/howto/index.html[documentation] for quickly getting up to speed with the framework. As for DBus, several good resources are available: * link:dbus_overview.html[DBus Overview] (part of this project) * http://dbus.freedesktop.org/doc/dbus-tutorial.html[DBus Tutorial] * http://dbus.freedesktop.org/doc/dbus-specification.html[DBus Specification] Inline Callbacks ~~~~~~~~~~~~~~~~ This tutorial leverages the +defer.inlineCallbacks+ function decorator in an attempt to improve readability of the example code. As inline callbacks are not well described in the Twisted documentation, this section provides a quick overview of the feature. Inline callbacks is an alternative callback mechanism that may be used in Twisted applications running on Python 2.5+. They assist in writing Deferred-using code that looks similar to a regular, sequential function. Through the magic of Python's generator mechanism, this sequential-looking code is, in fact, fully asynchronous and functionally equivalent to the traditional Deferred plus explicit callbacks and errbacks mechanism. Although the inline callbacks mechanism is not quite as flexible as explicit callbacks and errbacks, it often results in simpler and more compact code. Aside from the use of the +defer.inlineCallbacks+ function decorator, the key to inline callbacks is to +yield+ all deferreds. This will pause the generator function at the point of the +yield+ until the asynchronous operation completes. On completion, the generator will be resumed and the result of the operator will be returned from the +yield+ statement. Alternatively, if the operation resulted in an exception, the exception will be re-thrown from the +yield+ statement. The return value of functions decorated with +defer.inlineCallbacks+ is a deferred. Due to the nature of generator functions, inline callbacks methods cannot use the traditional +return+ keyword to return the result of the asynchronous operation. Instead, they must use +defer.returnValue()+ to return the result. If +defer.returnValue()+ is not used, the deferred returned by the decorated function will result in +None+ .Inline Callbacks Example [source,python] ---------------------------------------------------------------------- from twisted.internet import defer, utils @defer.inlineCallbacks def checkIPUsage( ip_addr ): ip_txt = yield utils.getProcessOutput('/sbin/ip', ['addr', 'list']) if ip_addr in ip_txt: # True will become the result of the Deferred defer.returnValue( True ) else: # Will trigger an errback raise Exception('IP NOT FOUND') ---------------------------------------------------------------------- Quick Real-World Example for the Impatient ------------------------------------------ The following example displays a notification popup on the desktop via the the +org.freedesktop.Notifications+ DBus API [source,python] ---------------------------------------------------------------------- #!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import error, client @defer.inlineCallbacks def show_desktop_notification( duration, message ): ''' Shows the message as a desktop notification for the specified number of seconds ''' con = yield client.connect(reactor, 'session') notifier = yield con.getRemoteObject('org.freedesktop.Notifications', '/org/freedesktop/Notifications') nid = yield notifier.callRemote('Notify', 'Example Application', 0, '', 'Example Notification Summary', message, [], dict(), duration * 1000) def main(): d = show_desktop_notification( 5, "Hello World!" ) d.addCallback( lambda _: reactor.stop() ) reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- DBus Connections ---------------- In order to make any use of DBus, a connection to the bus must first be established. This is accomplished through the +txdbus.client.connect(reactor, busAddress="session")+ method which returns a Deferred to a +txdbus.client.DBusClientConnection+ instance. The +busAddress+ parameter supports two special-case addresses in addition to the standard server addresses as defined by the DBus specification. If 'session' (the default) or 'system' is passed, the client will attempt to connect to the local session or system busses, respectively. For typical usage, these special-case addresses will likely suffice. Tx DBus currently supports the +unix+, +tcp+, and +nonce-tcp+ connection types. .Bus Connection [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor from txdbus import client def onConnected(cli): print 'Connected to the session bus!' dconnect = client.connect(reactor) dconnect.addCallback(onConnected) reactor.run() ---------------------------------------------------------------------- DBus Clients ------------ Tx DBus provides two APIs for interacting with remote objects. The generally preferred and significantly easier to use mechanism involves creating local proxies to represent remote objects. Signal registration and remote method invocation is then done by way of the proxy instances which hide most of the low-level details. Alternatively, low-level APIs exist for direct remote method invocation and message matching registration. Although generally much less convenient, the low-level APIs provide full access to the DBus internals. As with most dynamic language bindings, Tx DBus will automatically use the DBus introspection mechanism to obtain interface definitions for remote objects if they are not explicitly provided. While introspection is certainly a convenient mechanism and appropriate for many use cases, there are some advantages to explicitly specifying the interfaces. The primary benefit is that it allows for signal registration and local proxy object creation irrespective of whether or not the target bus name is currently in use. [[remote_methods]] Remote Methods ~~~~~~~~~~~~~~ As there is a delay involved in remote method invocation, remote calls always result in a Deferred instance. When the results eventually become available, the deferred will be callbacked/errbacked with the returned value. The format of the return value depends on the interface specification for the remote method. If the interface does not specify any return values, the return value will be +None+. If only one value is returned (structures and arrays are considered single values), that value will be returned as the result. Otherwise, if multiple values are returned, the result will be a Python list containing the returned values in the order specified by the DBus signature. There are two mechanisms for invoking remote methods. The easier of the two is to invoke the remote method through a local proxy object. This has the advantage of hiding many of the low-level DBus details and provides a simpler interface. Alternatively, the methods may be invoked directly without the use of proxy objects. In this case, however, all required parameters for the method invocation must be specified manually. Both mechanisms use a function called +callRemote()+ to effect the remote method invocation. The low-level +callRemote()+ is provided by the +txdbus.client.DBusClientConnection+ class and requires a large number of arguments. The proxy object's +callRemote()+ method wraps the low-level method and hides most of the details. In addition to accepting the name of the method to invoke and a list of positional arguments, both interfaces also accept the following optional keyword arguments that may be used to augment the remote method invocation. .callRemote() Optional Keyword Arguments [width="90%",cols="1m,10",options="header"] |======================================================== |Keyword |Description |expectReply | By default, the returned Deferred will callback/errback when the result of the remote invocation becomes available. If this parameter is set to +True+ (defaults to +False+), defer.suceed(None) will be returned immediately and no DBus MethodReturn message will be sent over the bus in response to the invocation. |autoStart | If set to +True+ (the default), the DBus daemon will attempt to auto-start a service to handle the remote call if the service is not already running. |timeout | If specified, the returned Deferred will be errbacked with a +txdbus.error.TimeOut+ instance if the remote call does not return before the timeout elapses (defaults to infinity). |interface | If specified, the remote call will invoke the method on the named interface. If left unspecified and more than one interface provides a method with the same name it is "implementation" defined as to which will be invoked. |======================================================== Proxy Objects ~~~~~~~~~~~~~ Remote DBus objects are generally interacted with by way of local proxy objects. The following example demonstrates the creation of a proxy object and a remote method invocation. [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MyObjPath' ) yield robj.callRemote('Ping') print 'Ping Succeeded. org.example is available' except error.DBusException, e: print 'Ping Failed. org.example is not available' reactor.stop() reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- The local proxy object uses the remote object's interface definition to provide a local representation of the remote object's API. As no explicit interface description was provided in the +getRemoteObject()+ call, the interfaces must be introspected prior to creation of the local proxy object. Remote method invocation on proxy objects is done through their +callRemote()+ method. The first argument is the name of the method to be invoked and the subsequent positional arguments are the arguments to be passed to the remote method. The optional keyword arguments described in the <> section may be used to augment the call as desired. Low Level Method Invocation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ In addition to method invocation through proxy objects, the +txdbus.client.DBusClientConnection+ class provides a low-level +callRemote()+ function that may be used to directly invoke remote methods. However, all parameters typically hidden by the proxy objects such as signature strings, destination bus addresses, and the like must be explicitly specified. As with the proxy object's +callRemote()+, this method also accepts the optional keyword arguments listed in the <> section. The following example is equivalent to the previous one but uses the low-level API to invoke the +Ping+ method without the use of a proxy object. [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) yield cli.callRemote( '/AnyValidObjPath', 'Ping', interface = 'org.freedesktop.DBus.Peer', destination = 'org.example' ) print 'Ping Succeeded. org.example is available' except error.DBusException, e: print 'Ping Failed. org.example is not available' reactor.stop() reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- NOTE: The +Ping+ function is used here because it's a standard interface that's guaranteed to exist. However, it's worth mentioning that +Ping+ is handled specially and can be somewhat misleading. Although it would appear the remote object referred to by the object path is the target of the +Ping+ function, it is in fact just the bus name that is being pinged. The object path is ignored. Consequently, this function cannot be used to test for the availability of a specific object. Explicit Interface Specification ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following example extends the previous two by demonstrating explicit interface specification for a remote object. [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error from txdbus.interface import DBusInterface, Method peer_iface = DBusInterface( 'org.freedesktop.DBus.Peer', Method('Ping') ) @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MyObjPath', peer_iface ) yield robj.callRemote('Ping') print 'Ping Succeeded. org.example is available' except error.DBusException, e: print 'Ping Failed. org.example is not available' reactor.stop() reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- Of course, the +org.freedesktop.DBus.Peer+ interface is rather simplistic. To better demonstrate DBus interface definition, consider the following code [source,python] ---------------------------------------------------------------------- from txdbus.interface import DBusInterface, Method, Signal # Method( method_name, arguments='', returns='') # Signal( signal_name, arguments='' ) # # The arguments and returns parameters must be empty strings for # no arguments/return values or a valid DBus signature string # iface = DBusInterface( 'org.example', Method('simple'), Method('full', 's', 'i'), Method('retOnly', returns='s'), Method('argOnly', 's'), Signal('noDataSignal'), Signal('DataSignal', 'as') ) ---------------------------------------------------------------------- Exporting Objects Over DBus --------------------------- In order to export an object over DBus, it must support the +txdbus.objects.IDBusObject+ interface. While this interface may be directly supported by applications, it will typically be easier to derive from the default implementation provided by the +txdbus.objects.DBusObject+ class. The easiest way to explain its use is by way of example. The following code demonstrates a simple object exported over DBus. .Example Exported Object [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Method class MyObj (objects.DBusObject): iface = DBusInterface('org.example.MyIFace', Method('exampleMethod', arguments='s', returns='s' )) dbusInterfaces = [iface] def __init__(self, objectPath): super(MyObj, self).__init__(objectPath) def dbus_exampleMethod(self, arg): print 'Received remote call. Argument: ', arg return 'You sent (%s)' % arg @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( MyObj('/MyObjPath') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /MyObjPath' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() ---------------------------------------------------------------------- This example demonstrates several key issues for subclasses of +DBusObject+. The DBus interfaces supported by an object are declared by way of a class-level variable named +dbusInterfaces+. This variable contains a list of +DBusInterface+ instances which define an interface's API. When class inheritance is used, the +dbusInterfaces+ variables of all superclasses are conjoined to determine the full set of APIs supported by the object. Supporting the methods declared in the DBus interfaces is as simple as creating methods named +dbus_+. These methods may return Deferreds to the final results if those results are not immediately available. The following code demonstrates the use of the exported object. .Use of the Exported Object [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MyObjPath' ) reply = yield robj.callRemote('exampleMethod', 'Hello World!') print 'Reply from server: ', reply except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- DBus Properties ~~~~~~~~~~~~~~~ Tx DBus supports DBus Properties through the +txdbus.objects.DBusProperty+ class. This class leverages Python's descriptor capabilities to provide near-transparent support for DBus Properties. If the +Property+ in the +DBusInterface+ class set +emitsOnChanged+ to +True+, an +org.freedesktop.DBus.Properties.PropertiesChanged+ signal will be generated each time the value is assigned to (defaults to True). .Server Properties Example [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Property from txdbus.objects import DBusProperty class MyObj (objects.DBusObject): iface = DBusInterface('org.example.MyIFace', Property('foo', 's', writeable=True)) dbusInterfaces = [iface] foo = DBusProperty('foo') def __init__(self, objectPath): super(MyObj, self).__init__(objectPath) self.foo = 'bar' @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( MyObj('/MyObjPath') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /MyObjPath' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() ---------------------------------------------------------------------- Client-side property use: .Client-side Properties Example [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MyObjPath' ) # Use the standard org.freedesktop.DBus.Properties.Get function to # obtain the value of 'foo'. Only one interface on the remote object # declares 'foo' so the interface name (the second function argument) # may be omitted. foo = yield robj.callRemote('Get', '', 'foo') # prints "bar" print foo yield robj.callRemote('Set', '', 'foo', 'baz') foo = yield robj.callRemote('Get', '', 'foo') # prints "baz" print foo except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- Caller Identity ~~~~~~~~~~~~~~~ The identity of the calling DBus connection can be reliably determined in DBus. Methods wishing to know the identity of the connection invoking them may add a +dbusCaller=None+ key-word argument. Methods supporting this argument will be supplied with the unique bus name of the calling connection. [source,python] ---------------------------------------------------------------------- def dbus_identityExample(dbusCaller=None): print 'Calling connection: ', dbusCaller ---------------------------------------------------------------------- Although the unique bus name of the caller is often not very useful in and of itself it can be reliably converted into a Unix user id with the +getConnectionUnixUser()+ method of +txdbus.client.DBusClientConnection+: .Determining Unix User Id of the caller [source,python] ---------------------------------------------------------------------- def dbus_identityExample(dbusCaller=None): d = self.getConnection().getConnectionUnixUser( dbusCaller ) d.addCallback( lambda uid : 'Your Unix User Id is: %d' % uid ) return d ---------------------------------------------------------------------- Resolving Conflicting Interface Declarations -------------------------------------------- Mapping DBus method calls to methods named +dbus_+ is generally a convenient mechanism. However, it can result in confusion when multiple supported interfaces define methods with the same name. To resolve this situation, the +dbusMethod()+ decorator may be used to explicitly bind a method to the desired interface. .Resolving Conflicting Interface Method Declarations - Server Side [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Method from txdbus.objects import dbusMethod class MyObj (objects.DBusObject): iface1 = DBusInterface('org.example.MyIFace1', Method('common')) iface2 = DBusInterface('org.example.MyIFace2', Method('common')) dbusInterfaces = [iface1, iface2] def __init__(self, objectPath): super(MyObj, self).__init__(objectPath) @dbusMethod('org.example.MyIFace1', 'common') def dbus_common1(self): print 'iface1 common called!' @dbusMethod('org.example.MyIFace2', 'common') def dbus_common2(self): print 'iface2 common called!' @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( MyObj('/MultiInterfaceObject') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /MultiInterfaceObject' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() ---------------------------------------------------------------------- Similarly, action must be taken on the client side to ensure that the appropriate function is executed when multiple interfaces support methods of the same name. The +interface+ key-word argument to the +callRemote()+ function may be used to identify the desired interface. If the +interfaces+ argument is not used in this situation, it is "implementation defined" as to which interface's method will be invoked. .Resolving Conflicting Interface Method Declarations - Client Side [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MultiInterfaceObject' ) yield robj.callRemote('common', interface='org.example.MyIFace1') yield robj.callRemote('common', interface='org.example.MyIFace2') except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- Signals ------- Signals are emitted by subclasses of +DBusObject+ using the +emitSignal()+ method [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Signal class SignalSender (objects.DBusObject): iface = DBusInterface( 'org.example.SignalSender', Signal('tick', 'u') ) dbusInterfaces = [iface] def __init__(self, objectPath): super(SignalSender, self).__init__(objectPath) self.count = 0 def sendTick(self): self.emitSignal('tick', self.count) self.count += 1 reactor.callLater(1, self.sendTick) @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) s = SignalSender('/Signaller') conn.exportObject( s ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /Signaller' print 'Emitting "tick" signals every second' s.sendTick() # begin looping except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() ---------------------------------------------------------------------- The corresponding client code to receive the emitted signals is: .Signal Reception Example [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error def onSignal( tickCount ): print 'Got tick signal: ', tickCount @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/Signaller' ) robj.notifyOnSignal( 'tick', onSignal ) except error.DBusException, e: print 'DBus Error:', e reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- Note that this client code uses introspection to obtain the API of the remote object emitting the signals. Consequently, the server application must be up and running when the client application starts or an error will be thrown from +getRemoteObject()+ when the introspection fails. Were the interface specified explicitly, the signal registration would succeed even if the emitting application were entirely disconnected from the bus. The following code can be run at any time and, if launched before the signal-emitting application, it will never miss any messages. .Signal Reception With Explicit Interface Specification [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error from txdbus.interface import DBusInterface, Signal signal_iface = DBusInterface( 'org.example.SignalSender', Signal('tick', 'u') ) def onSignal( tickCount ): print 'Got tick signal: ', tickCount @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/Signaller', signal_iface ) robj.notifyOnSignal( 'tick', onSignal ) except error.DBusException, e: print 'DBus Error:', e reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- DBus Structure Handling and Object Serialization ------------------------------------------------ When calling methods that accept structures as arguments, such as +(si)+ (a structure containing a string and 32-bit signed integer) the argument passed to the callRemote() method should be 2-element list containing the desired string and integer [source,python] ---------------------------------------------------------------------- # -- Server Snippet -- ... Method('structArg', '(si)', 's') ... def dbus_structArg(self, arg): return 'You sent (%s, %d)' % (arg[0], arg[1]) # -- Client Snippet -- remoteObj.callRemote('structArg', ['Foobar', 1]) ---------------------------------------------------------------------- It is also possible to pass Python objects instead of lists to arguments requiring a structure type. If the object contains a +dbusOrder+ member variable, it will be used as an ordered list of attribute names by the serialization process. For example, the client portion of the previous code snippet could be equivalently written as [source,python] ---------------------------------------------------------------------- class DBSerializeable(object): dbusOrder = ['text', 'number'] def __init__(self, txt, num): self.text = txt self.number = num serialObj = DBSerializeable( 'Foobar', 1 ) remoteObj.callRemote('structArg', serialObj) ---------------------------------------------------------------------- Error Handling -------------- DBus reports errors with dedicated error messages. Some of these messages are generated by the bus itself, such as when a remote method call is sent to bus name that does not exist, others are generated within client applications, such as when invalid argument values are detected. Any exception raised during the invocation of a +dbus_*+ method will be converted into a proper DBus error message. The name of the DBus error message will default to +org.txdbus.PythonException.+. If the exception object has a +dbusErrorName+ member variable, that value will be used instead. All error messages sent by this implementation include a single string parameter that is obtained by converting the exception instance to a string. .Error Generation Example [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Method class ExampleException (Exception): dbusErrorName = 'org.example.ExampleException' class ErrObj (objects.DBusObject): iface = DBusInterface('org.example.ErrorExample', Method('throwError')) dbusInterfaces = [iface] def __init__(self, objectPath): super(ErrObj, self).__init__(objectPath) def dbus_throwError(self): raise ExampleException('Uh oh') @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( ErrObj('/ErrorObject') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /ErrorObject' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() ---------------------------------------------------------------------- Failures occuring during remote method invocation are reported to the calling code as instances of +txdbus.error.RemoteError+. Instances of this object have two fields +errName+ which is the textual name of the DBus error and an optional +message+. DBus does not formally define the content of error messages. However, if the DBus error message contains a single string parameter (which is often the case in practice), it will be assigned to the +message+ field of the +RemoteError+ instance. [source,python] ---------------------------------------------------------------------- from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/ErrorObject' ) try: yield robj.callRemote('throwError') print 'Not Reached' except error.RemoteError, e: print 'Client threw an error named: ', e.errName print 'Error message: ', e.message except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() ---------------------------------------------------------------------- txdbus-1.1.0/doc/tutorial_examples/000077500000000000000000000000001313301420000173135ustar00rootroot00000000000000txdbus-1.1.0/doc/tutorial_examples/desktop_notification000077500000000000000000000017561313301420000234710ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import error, client @defer.inlineCallbacks def show_desktop_notification( duration, message ): ''' Shows the message as a desktop notification for the specified number of seconds ''' con = yield client.connect(reactor, 'session') notifier = yield con.getRemoteObject('org.freedesktop.Notifications', '/org/freedesktop/Notifications') nid = yield notifier.callRemote('Notify', 'Example Application', 0, '', 'Example Notification Summary', message, [], dict(), duration * 1000) def main(): d = show_desktop_notification( 5, "Hello World!" ) d.addCallback( lambda _: reactor.stop() ) reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/error_client000077500000000000000000000012251313301420000217300ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/ErrorObject' ) try: yield robj.callRemote('throwError') print 'Not Reached' except error.RemoteError, e: print 'Client threw an error named: ', e.errName print 'Error message: ', e.message except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/error_server000077500000000000000000000020161313301420000217570ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Method class ExampleException (Exception): dbusErrorName = 'org.example.ExampleException' class ErrObj (objects.DBusObject): iface = DBusInterface('org.example.ErrorExample', Method('throwError')) dbusInterfaces = [iface] def __init__(self, objectPath): super(ErrObj, self).__init__(objectPath) def dbus_throwError(self): raise ExampleException('Uh oh') @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( ErrObj('/ErrorObject') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /ErrorObject' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() txdbus-1.1.0/doc/tutorial_examples/multi_iface_client000077500000000000000000000011011313301420000230510ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MultiInterfaceObject' ) yield robj.callRemote('common', interface='org.example.MyIFace1') yield robj.callRemote('common', interface='org.example.MyIFace2') except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/multi_iface_server000077500000000000000000000023511313301420000231110ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Method from txdbus.objects import dbusMethod class MyObj (objects.DBusObject): iface1 = DBusInterface('org.example.MyIFace1', Method('common')) iface2 = DBusInterface('org.example.MyIFace2', Method('common')) dbusInterfaces = [iface1, iface2] def __init__(self, objectPath): super(MyObj, self).__init__(objectPath) @dbusMethod('org.example.MyIFace1', 'common') def dbus_common1(self): print 'iface1 common called!' @dbusMethod('org.example.MyIFace2', 'common') def dbus_common2(self): print 'iface2 common called!' @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( MyObj('/MultiInterfaceObject') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /MultiInterfaceObject' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() txdbus-1.1.0/doc/tutorial_examples/ping_low_level000077500000000000000000000011431313301420000222450ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) yield cli.callRemote( '/AnyValidObjectPath', 'Ping', interface = 'org.freedesktop.DBus.Peer', destination = 'org.example' ) print 'Ping Succeeded. org.example is available' except error.DBusException, e: print 'Ping Failed. org.example is not available' reactor.stop() reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/ping_proxy_object000077500000000000000000000010201313301420000227560ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MyObjPath' ) yield robj.callRemote('Ping') print 'Ping Succeeded. org.example is available' except error.DBusException, e: print 'Ping Failed. org.example is not available' reactor.stop() reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/ping_with_interface000077500000000000000000000013331313301420000232510ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error from txdbus.interface import DBusInterface, Method peer_iface = DBusInterface( 'org.freedesktop.DBus.Peer', Method('Ping') ) @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MyObjPath', peer_iface ) yield robj.callRemote('Ping') print 'Ping Succeeded. org.example is available' except error.DBusException, e: print 'Ping Failed. org.example is not available' reactor.stop() reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/properties_client000077500000000000000000000016501313301420000227750ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MyObjPath' ) # Use the standard org.freedesktop.DBus.Properties.Get function to # obtain the value of 'foo'. Only one interface on the remote object # declares 'foo' so the interface name (the second function argument) # may be omitted. foo = yield robj.callRemote('Get', '', 'foo') # prints "bar" print foo yield robj.callRemote('Set', '', 'foo', 'baz') foo = yield robj.callRemote('Get', '', 'foo') # prints "baz" print foo except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/properties_server000077500000000000000000000017211313301420000230240ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Property from txdbus.objects import DBusProperty class MyObj (objects.DBusObject): iface = DBusInterface('org.example.MyIFace', Property('foo', 's', writeable=True)) dbusInterfaces = [iface] foo = DBusProperty('foo') def __init__(self, objectPath): super(MyObj, self).__init__(objectPath) self.foo = 'bar' @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( MyObj('/MyObjPath') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /MyObjPath' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() txdbus-1.1.0/doc/tutorial_examples/signal_client000077500000000000000000000010201313301420000220450ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error def onSignal( tickCount ): print 'Got tick signal: ', tickCount @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/Signaller' ) robj.notifyOnSignal( 'tick', onSignal ) except error.DBusException, e: print 'DBus Error:', e reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/signal_client_iface000077500000000000000000000013471313301420000232100ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error from txdbus.interface import DBusInterface, Signal signal_iface = DBusInterface( 'org.example.SignalSender', Signal('tick', 'u') ) def onSignal( tickCount ): print 'Got tick signal: ', tickCount @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/Signaller', signal_iface ) robj.notifyOnSignal( 'tick', onSignal ) except error.DBusException, e: print 'DBus Error:', e reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/signal_server000077500000000000000000000022531313301420000221060ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Signal class SignalSender (objects.DBusObject): iface = DBusInterface( 'org.example.SignalSender', Signal('tick', 'u') ) dbusInterfaces = [iface] def __init__(self, objectPath): super(SignalSender, self).__init__(objectPath) self.count = 0 def sendTick(self): self.emitSignal('tick', self.count) self.count += 1 reactor.callLater(1, self.sendTick) @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) s = SignalSender('/Signaller') conn.exportObject( s ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /Signaller' print 'Emitting "tick" signals every second' s.sendTick() # begin looping except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() txdbus-1.1.0/doc/tutorial_examples/simple_client000077500000000000000000000010251313301420000220660ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) robj = yield cli.getRemoteObject( 'org.example', '/MyObjPath' ) reply = yield robj.callRemote('exampleMethod', 'Hello World!') print 'Reply from server: ', reply except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/simple_client_id000077500000000000000000000007121313301420000225440ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, error @defer.inlineCallbacks def main(): try: cli = yield client.connect(reactor) userid = yield cli.getConnectionUnixUser( 'org.example' ) print 'User of org.example: ', userid except error.DBusException, e: print 'DBus Error:', e reactor.stop() reactor.callWhenRunning(main) reactor.run() txdbus-1.1.0/doc/tutorial_examples/simple_server000077500000000000000000000020051313301420000221150ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Method class MyObj (objects.DBusObject): iface = DBusInterface('org.example.MyIFace', Method('exampleMethod', arguments='s', returns='s' )) dbusInterfaces = [iface] def __init__(self, objectPath): super(MyObj, self).__init__(objectPath) def dbus_exampleMethod(self, arg): print 'Received remote call. Argument: ', arg return 'You sent (%s)' % arg @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( MyObj('/MyObjPath') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /MyObjPath' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() txdbus-1.1.0/doc/tutorial_examples/simple_server_id000077500000000000000000000021141313301420000225720ustar00rootroot00000000000000#!/usr/bin/env python from twisted.internet import reactor, defer from txdbus import client, objects, error from txdbus.interface import DBusInterface, Method class MyObj (objects.DBusObject): iface = DBusInterface('org.example.MyIFace', Method('exampleMethod', arguments='s', returns='s' )) def __init__(self, objectPath): super(MyObj, self).__init__(objectPath, self.iface) def dbus_exampleMethod(self, arg, dbusCaller=None): d = self.getConnection().getConnectionUnixUser( dbusCaller ) d.addCallback( lambda uid : 'Your Unix User Id is: %d' % uid ) return d @defer.inlineCallbacks def main(): try: conn = yield client.connect(reactor) conn.exportObject( MyObj('/MyObjPath') ) yield conn.requestBusName('org.example') print 'Object exported on bus name "org.example" with path /MyObjPath' except error.DBusException, e: print 'Failed to export object: ', e reactor.stop() reactor.callWhenRunning( main ) reactor.run() txdbus-1.1.0/requirements.txt000066400000000000000000000000221313301420000162630ustar00rootroot00000000000000twisted>=10.1 six txdbus-1.1.0/setup.cfg000066400000000000000000000002001313301420000146160ustar00rootroot00000000000000[flake8] exclude=.tox,build,.eggs application-import-names=txdbus,tests import-order-style=smarkets [bdist_wheel] universal = 1txdbus-1.1.0/setup.py000077500000000000000000000022661313301420000145300ustar00rootroot00000000000000#!/usr/bin/env python VERSION = '1.1.0' DESCRIPTION = ( 'A native Python implementation of the DBus protocol for Twisted ' 'applications.' ) try: from setuptools import setup except ImportError: from distutils.core import setup setup( name='txdbus', version=VERSION, description=DESCRIPTION, license='MIT', long_description=open('README.rst').read(), url='https://github.com/cocagne/txdbus', author='Tom Cocagne', author_email='tom.cocagne@gmail.com', install_requires=['twisted>=10.1', 'six'], provides=['txdbus'], packages=['txdbus'], keywords=['dbus', 'twisted'], classifiers=[ 'Development Status :: 4 - Beta', 'Framework :: Twisted', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Topic :: Software Development :: Libraries', 'Topic :: System :: Networking', ], ) txdbus-1.1.0/tests/000077500000000000000000000000001313301420000141475ustar00rootroot00000000000000txdbus-1.1.0/tests/__init__.py000066400000000000000000000000001313301420000162460ustar00rootroot00000000000000txdbus-1.1.0/tests/client_tests.py000066400000000000000000002026261313301420000172310ustar00rootroot00000000000000# # This module is structured somewhat unusually in order to allow the # code to be shared between two different modules that run these # tests against the internally implemented bus and the OS native # bus if it's available # # Those modules dynamically create subclasses of all classes # deriving from ServerObjectTester, unittest.TestClass, and # an optional mixin class for setting up the internal bus. # import os import tempfile from unittest import SkipTest import twisted from twisted.internet import defer, reactor from txdbus import client, endpoints, error, introspection, objects from txdbus.interface import DBusInterface, Method, Property, Signal from txdbus.objects import dbusMethod, DBusProperty def delay(arg): d = defer.Deferred() reactor.callLater(0.05, lambda: d.callback(arg)) return d class TestException (Exception): dbusErrorName = 'org.txdbus.trial.TestException' class ServerObjectTester(object): tst_path = '/test/TestObj' tst_bus = 'org.txdbus.trial.bus%d' % os.getpid() TestClass = None @defer.inlineCallbacks def setUp(self): yield self._setup() yield self._client_connect() def _client_connect(self): self.t = self.TestClass(self.tst_path) self.server_conn = None self.client_conn = None f = client.DBusClientFactory() point = endpoints.getDBusEnvEndpoints(reactor)[0] point.connect(f) d = f.getConnection() d.addCallback(self._connected) d.addCallback(lambda _: self.get_client_connection()) return d def _connected(self, conn): self.server_conn = conn conn.exportObject(self.t) return conn.requestBusName(self.tst_bus) def tearDown(self): if self.client_conn: self.client_conn.disconnect() if self.server_conn: self.server_conn.disconnect() return self._teardown() def get_client_connection(self): if self.client_conn: return defer.succeed(self.client_conn) else: f = client.DBusClientFactory() point = endpoints.getDBusEnvEndpoints(reactor)[0] point.connect(f) d = f.getConnection() def got_connection(c): self.client_conn = c return c d.addCallback(got_connection) return d def get_proxy(self, interfaces=None): d = self.get_client_connection() def gotit(conn): return conn.getRemoteObject( self.tst_bus, self.tst_path, interfaces=interfaces) d.addCallback(gotit) return d def proxy_chain(self, *args): d = self.get_proxy() for a in args: d.addCallback(a) return d class SimpleObjectTester(ServerObjectTester): class TestClass (objects.DBusObject): tif = DBusInterface('org.txdbus.trial.Simple', Method('testMethod', arguments='s', returns='s'), Signal('tsig', 's') ) dbusInterfaces = [tif] def __init__(self, object_path): objects.DBusObject.__init__(self, object_path) def dbus_testMethod(self, arg): return arg + 'bar' class ConnectionTest(SimpleObjectTester): def test_client_connect(self): d = client.connect(reactor) def ok(conn): conn.disconnect() self.assertTrue(True) d.addCallback(ok) return d def test_fallback_connect(self): bad1 = 'unix:abstract=/tmp/FOOBARBAZBLICK,guid=5' bad2 = 'tcp:host=127.0.0.99,port=0,family="ipv4",guid=5' good = os.environ['DBUS_SESSION_BUS_ADDRESS'] d = client.connect(reactor, '%s;%s;%s' % (bad1, bad2, good)) def ok(conn): conn.disconnect() self.assertTrue(True) d.addCallback(ok) return d def test_failed_connect(self): bad1 = 'unix:abstract=/tmp/FOOBARBAZBLICK,guid=5' bad2 = 'tcp:host=127.0.0.99,port=0,family="ipv4",guid=5' d = client.connect(reactor, '%s;%s' % (bad1, bad2)) def ok(conn): conn.disconnect() self.assertTrue(False, 'Connect should not have succeeded') d.addCallback(ok) d.addErrback(lambda _: self.assertTrue(True)) return d def test_no_valid_endpoints(self): d = client.connect(reactor, '') def ok(conn): conn.disconnect() self.assertTrue(False, 'Connect should not have succeeded') d.addCallback(ok) d.addErrback(lambda _: self.assertTrue(True)) return d class InheritiedInterfaceTest(SimpleObjectTester): class TestClass (SimpleObjectTester.TestClass): tif = DBusInterface('org.txdbus.trial.SimpleSub', Method( 'testMethodSub', arguments='s', returns='s'), Signal('tsig', 's') ) dbusInterfaces = [tif] def __init__(self, object_path): objects.DBusObject.__init__(self, object_path) def dbus_testMethodSub(self, arg): return arg + 'barSub' def test_subclass_method(self): def got_object(ro): return ro.callRemote('testMethodSub', 'foo') def got_reply(reply): self.assertEquals(reply, 'foobarSub') return self.proxy_chain(got_object, got_reply) def test_superclass_method(self): def got_object(ro): return ro.callRemote('testMethod', 'foo') def got_reply(reply): self.assertEquals(reply, 'foobar') return self.proxy_chain(got_object, got_reply) class ObjectManagerTest(SimpleObjectTester): class TestClass (SimpleObjectTester.TestClass): tif = DBusInterface('org.txdbus.trial.SimpleSub', Method( 'testMethodSub', arguments='s', returns='s'), Method('foo', returns='s'), Signal('tsig', 's'), Property('prop', 'i') ) dbusInterfaces = [tif] prop = DBusProperty('prop') def __init__(self, object_path, arg=-1): objects.DBusObject.__init__(self, object_path) self.prop = arg def dbus_foo(self): return 'foo' def test_get_managed_objects(self): t1 = self.TestClass('/org/test/Foo', 0) t2 = self.TestClass('/org/test/Foo/Bar', 1) t3 = self.TestClass('/org/test/Foo/Baz', 2) self.server_conn.exportObject(t1) self.server_conn.exportObject(t2) self.server_conn.exportObject(t3) def get_proxy(path): d = self.get_client_connection() def gotit(conn): return conn.getRemoteObject(self.tst_bus, path) d.addCallback(gotit) return d def got_object(ro): return ro.callRemote('GetManagedObjects') def got_reply(reply): self.assertEquals(reply, { '/org/test/Foo/Bar': { 'org.freedesktop.DBus.Properties': {}, 'org.txdbus.trial.Simple': {}, 'org.txdbus.trial.SimpleSub': {'prop': 1}, }, '/org/test/Foo/Baz': { 'org.freedesktop.DBus.Properties': {}, 'org.txdbus.trial.Simple': {}, 'org.txdbus.trial.SimpleSub': {'prop': 2}, }, }) dp = get_proxy('/org/test/Foo') dp.addCallback(got_object) dp.addCallback(got_reply) return dp @defer.inlineCallbacks def test_unexport_objects(self): t1 = self.TestClass('/org/test/Foo', 0) t2 = self.TestClass('/org/test/Foo/Bar', 1) t3 = self.TestClass('/org/test/Foo/Baz', 2) self.server_conn.exportObject(t1) self.server_conn.exportObject(t2) self.server_conn.exportObject(t3) conn = yield self.get_client_connection() ro1 = yield conn.getRemoteObject(self.tst_bus, '/org/test/Foo') ro2 = yield conn.getRemoteObject(self.tst_bus, '/org/test/Foo/Bar') f1 = yield ro1.callRemote('foo') f2 = yield ro2.callRemote('foo') self.assertEquals(f1, 'foo') self.assertEquals(f2, 'foo') self.server_conn.unexportObject('/org/test/Foo') f2 = yield ro2.callRemote('foo') self.assertEquals(f2, 'foo') try: f1 = yield ro1.callRemote('foo') self.fail('failed throw exception') except error.RemoteError as e: self.assertEquals( e.message, '/org/test/Foo is not an object provided by this process.') except Exception: self.fail('Threw wrong exception') def test_interface_added_signal(self): dsig = defer.Deferred() def on_signal(m): dsig.callback(m) def check_results(m): self.assertEquals(m.interface, 'org.freedesktop.DBus.ObjectManager') self.assertEquals(m.member, 'InterfacesAdded') self.assertEquals(m.body, [ '/org/test/Foo', { 'org.txdbus.trial.SimpleSub': {'prop': 0}, 'org.txdbus.trial.Simple': {}, 'org.freedesktop.DBus.Properties': {} } ]) def on_proxy(ro): return self.client_conn.addMatch(on_signal, mtype='signal', sender=self.tst_bus) # path_namespace is available in dbus 1.5+ # path_namespace = '/org/test' ) def sendit(ro): t = self.TestClass('/org/test/Foo', 0) self.server_conn.exportObject(t) d = self.get_proxy() d.addCallback(on_proxy) d.addCallback(sendit) d.addCallback(lambda _: dsig) d.addCallback(check_results) return d def test_interface_removed_signal(self): dsig = defer.Deferred() t = self.TestClass('/org/test/Foo', 0) def on_signal(m): dsig.callback(m) def check_results(m): self.assertEquals(m.interface, 'org.freedesktop.DBus.ObjectManager') self.assertEquals(m.member, 'InterfacesRemoved') self.assertEquals(m.body, [ '/org/test/Foo', [ 'org.txdbus.trial.SimpleSub', 'org.txdbus.trial.Simple', 'org.freedesktop.DBus.Properties' ] ]) def on_proxy(ro): self.server_conn.exportObject(t) return self.client_conn.addMatch(on_signal, mtype='signal', sender=self.tst_bus, member='InterfacesRemoved') # path_namespace is available in dbus 1.5+ # path_namespace = '/org/test' ) def sendit(ro): self.server_conn.unexportObject('/org/test/Foo') d = self.get_proxy() d.addCallback(on_proxy) d.addCallback(sendit) d.addCallback(lambda _: dsig) d.addCallback(check_results) return d class SimpleTest(SimpleObjectTester): def test_bad_remote_method_call(self): d = self.client_conn.callRemote(self.tst_path, '0badmember', interface='org.freedesktop.DBus.Peer', destination=self.tst_bus, body=1) d.addCallback( lambda _: self.assertTrue( False, 'Remote call should have errbacked')) d.addErrback(lambda _: self.assertTrue(True)) return d def test_simple(self): def got_object(ro): return ro.callRemote('testMethod', 'foo') def got_reply(reply): self.assertEquals(reply, 'foobar') return self.proxy_chain(got_object, got_reply) def test_get_connection(self): def got_object(ro): self.assertTrue(self.t.getConnection() is not None) return self.proxy_chain(got_object) def test_get_name_owner(self): d = self.server_conn.getNameOwner(self.tst_bus) def got_reply(reply): self.assertEquals(reply, self.server_conn.busName) d.addCallback(got_reply) return d def test_manual_interface(self): def on_proxy(ro): return ro.callRemote('testMethod', 'foo') def on_reply(reply): self.assertEquals(reply, 'foobar') d = self.get_proxy(SimpleObjectTester.TestClass.tif) d.addCallback(on_proxy) d.addCallback(on_reply) return d def test_extra_arguments(self): def on_proxy(ro): return ro.callRemote('testMethod', 'foo', expectReply=False, autoStart=False, timeout=5) def on_reply(reply): self.assertTrue(reply is None) def on_error(err): print('***** GOT TIMEOUT ******', err.getErrorMessage()) print(' ', err.value) self.assertTrue( isinstance( err.value, error.TimeOut), 'Did not receive a timeout') d = self.get_proxy() d.addCallback(on_proxy) d.addCallbacks(on_reply, on_error) return d def test_ping(self): d = self.client_conn.callRemote(self.tst_path, 'Ping', interface='org.freedesktop.DBus.Peer', destination=self.tst_bus) d.addCallback(lambda _: self.assertTrue(True)) return d def test_hello_hello(self): def on_proxy(ro): return self.client_conn.callRemote( '/org/freedesktop/DBus', 'Hello', interface='org.freedesktop.DBus', destination='org.freedesktop.DBus', ) def on_err(e): self.assertEquals( 'org.freedesktop.DBus.Error.Failed: Already handled an Hello ' 'message', str(e.value), ) d = self.get_proxy() d.addCallback(on_proxy) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d class DisconnectTest(SimpleObjectTester): def test_notify_on_disconnect(self): dresult = defer.Deferred() def fail(): dresult.callback(None) self.fail( 'Failed to receive disconnect notification on remote object') fail_cb = reactor.callLater(5, fail) def on_disconnect(robj, reason): fail_cb.cancel() self.assertTrue(True) dresult.callback(None) def on_proxy(ro): self.proxy = ro # ensure proxy object doesn't get GC-ed ro.notifyOnDisconnect(on_disconnect) self.client_conn.disconnect() self.client_conn = None d = self.get_proxy() d.addCallback(on_proxy) return dresult def test_cancel_notify_on_disconnect(self): dresult = defer.Deferred() def ok(): self.assertTrue(True) dresult.callback(None) ok_cb = reactor.callLater(0.1, ok) def on_disconnect(robj, reason): ok_cb.cancel() self.fail( 'Should not have received disconnect notification on remote ' 'object' ) dresult.callback(None) def on_proxy(ro): self.proxy = ro # ensure proxy object doesn't get GC-ed ro.notifyOnDisconnect(on_disconnect) ro.cancelNotifyOnDisconnect(on_disconnect) self.client_conn.disconnect() self.client_conn = None d = self.get_proxy() d.addCallback(on_proxy) return dresult class IntrospectionTest(SimpleObjectTester): class TestClass (objects.DBusObject): tif = DBusInterface('org.txdbus.trial.Simple', Method('testMethod', arguments='s', returns='s'), Signal('tsig', 's'), Property('foo', 's', emitsOnChange=False), Property('bar', 'i', True, True, True), Property('baz', 's', False, True, 'invalidates') ) dbusInterfaces = [tif] def __init__(self, object_path): objects.DBusObject.__init__(self, object_path) introspection_golden_xml = """ """ # noqa E501: It's okay for this to be over 80-chars def test_introspection(self): d = self.get_client_connection() def cb(c): return c.callRemote( self.tst_path, 'Introspect', interface='org.freedesktop.DBus.Introspectable', destination=self.tst_bus, ) def gotxml(xml): # with open('/tmp/tout', 'w') as f: # f.write(xml) self.assertEquals(self.introspection_golden_xml, xml) d.addCallback(cb) d.addCallback(gotxml) return d def test_introspection_parsing(self): ifaces = introspection.getInterfacesFromXML( self.introspection_golden_xml, True) for iface in ifaces: if iface.name == 'org.txdbus.trial.Simple': break else: self.assertTrue(False) self.assertTrue('testMethod' in iface.methods) self.assertTrue('tsig' in iface.signals) self.assertTrue('foo' in iface.properties) class SignalTester(ServerObjectTester): class TestClass (objects.DBusObject): tif = DBusInterface( 'org.txdbus.trial.Signal', Method('sendSignal', arguments='s'), Method('sendShared1'), Method('sendEmpty'), Method('sendRaw', arguments='s'), Signal('testSignal', 's'), Signal('sharedSignal', 's'), Signal('emptySig') ) tif2 = DBusInterface( 'org.txdbus.trial.Signal2', Method('sendShared2'), Signal('sharedSignal', 's') ) dbusInterfaces = [tif, tif2] def __init__(self, object_path): objects.DBusObject.__init__(self, object_path) def dbus_sendSignal(self, arg): self.emitSignal('testSignal', 'Signal arg: ' + arg) def dbus_sendRaw(self, arg): self.emitSignal('testSignal', arg) def dbus_sendEmpty(self): self.emitSignal('emptySig') def dbus_sendShared1(self): self.emitSignal( 'sharedSignal', 'iface1', interface='org.txdbus.trial.Signal', ) def dbus_sendShared2(self): self.emitSignal( 'sharedSignal', 'iface2', interface='org.txdbus.trial.Signal2', ) def test_signal(self): dsig = defer.Deferred() def on_signal(arg): dsig.callback(arg) def on_proxy(ro): ro.notifyOnSignal('testSignal', on_signal) return ro.callRemote('sendSignal', 'foo') d = self.get_proxy() d.addCallback(on_proxy) def check_result(result): self.assertEquals(result, 'Signal arg: foo') dsig.addCallback(check_result) return defer.DeferredList([d, dsig]) def test_signal_no_parameters(self): dsig = defer.Deferred() def on_signal(): dsig.callback(None) def on_proxy(ro): ro.notifyOnSignal('emptySig', on_signal) return ro.callRemote('sendEmpty') d = self.get_proxy() d.addCallback(on_proxy) return defer.DeferredList([d, dsig]) def test_bad_signal_interface(self): def on_signal(arg): pass def on_proxy(ro): self.assertRaises( AttributeError, ro.notifyOnSignal, 'testSignal', on_signal, 'foo_iface' ) d = self.get_proxy() d.addCallback(on_proxy) return d def test_signal_cancel(self): counts = {'signal_count': 0} def on_signal(_): counts['signal_count'] += 1 def on_proxy(ro): dnotify = ro.notifyOnSignal('testSignal', on_signal) def send_signal(rule_id): dx = ro.callRemote('sendSignal', 'foo') dx.addCallback(lambda _: rule_id) return dx def cancel_reg(rule_id): return ro.cancelSignalNotification(rule_id) def delay(arg): d = defer.Deferred() reactor.callLater(0.1, lambda: d.callback(arg)) return d def check_result(y): self.assertEqual(counts['signal_count'], 1) dnotify.addCallback(send_signal) dnotify.addCallback(cancel_reg) dnotify.addCallback(send_signal) dnotify.addCallback(check_result) return dnotify d = self.get_proxy() d.addCallback(on_proxy) return d def test_shared_signal(self): d1 = defer.Deferred() d2 = defer.Deferred() def on_proxy(ro): ro.notifyOnSignal('sharedSignal', d1.callback, interface='org.txdbus.trial.Signal') ro.notifyOnSignal('sharedSignal', d2.callback, interface='org.txdbus.trial.Signal2') return defer.DeferredList([ ro.callRemote('sendShared1'), ro.callRemote('sendShared2'), ]) def check_signals_sent(result): # both callRemotes successful and returning None self.assertEquals(len(result), 2) for success, returnValue in result: self.assertTrue(success) self.assertIsNone(returnValue) return result signalsSent = self.get_proxy().addCallback(on_proxy) signalsSent.addCallback(check_signals_sent) def check_signals_received(result): # d1 and d2 calledback with the correct signal data self.assertEquals(result[0], (True, 'iface1')) self.assertEquals(result[1], (True, 'iface2')) return result signalsReceived = defer.DeferredList([d1, d2]) signalsReceived.addCallback(check_signals_received) # success is both: # - signals sent, callRemotes checked, connections closed # - signals received, callbacks checked return defer.DeferredList([signalsSent, signalsReceived]) def test_arg_rule_match(self): dsig = defer.Deferred() def on_signal(result): dsig.callback(result) d = self.client_conn.addMatch( on_signal, mtype='signal', sender=self.tst_bus, arg=[(0, 'Signal arg: MATCH')], ) def on_proxy(ro): return ro.callRemote('sendSignal', 'MATCH') d.addCallback(lambda _: self.get_proxy()) d.addCallback(on_proxy) def check_result(result): self.assertEquals(result.body[0], 'Signal arg: MATCH') dsig.addCallback(check_result) return dsig def test_arg_path_rule_match(self): dsig = defer.Deferred() def on_signal(result): dsig.callback(result) d = self.client_conn.addMatch( on_signal, mtype='signal', sender=self.tst_bus, arg_path=[(0, '/aa/bb/')], ) def on_proxy(ro): return ro.callRemote('sendRaw', '/aa/bb/cc') d.addCallback(lambda _: self.get_proxy()) d.addCallback(on_proxy) def check_result(result): self.assertEquals(result.body[0], '/aa/bb/cc') dsig.addCallback(check_result) return dsig def test_arg_rule_remove_match(self): dsig = defer.Deferred() x = {'rule_id': None} def on_signal(result): dsig.callback(result) d = self.client_conn.addMatch( on_signal, mtype='signal', sender=self.tst_bus, arg=[(0, 'Signal arg: MATCH')], ) def added(rule_id): x['rule_id'] = rule_id return self.get_proxy() d.addCallback(added) def on_proxy(ro): return ro.callRemote('sendSignal', 'MATCH') d.addCallback(on_proxy) def check_result(result): self.assertEquals(result.body[0], 'Signal arg: MATCH') dsig.addCallback(check_result) def remove(_): return self.client_conn.delMatch(x['rule_id']) dsig.addCallback(remove) return dsig def test_path_namespace_rule_match(self): dsig = defer.Deferred() def on_signal(result): dsig.callback(result) d = self.client_conn.addMatch( on_signal, mtype='signal', sender=self.tst_bus, path_namespace='/test', ) def on_proxy(ro): return ro.callRemote('sendRaw', 'string payload') d.addCallback(lambda _: self.get_proxy()) d.addCallback(on_proxy) def check_result(result): self.assertEquals(result.body[0], 'string payload') dsig.addCallback(check_result) return dsig class ErrorTester(ServerObjectTester): class TestException (Exception): dbusErrorName = 'org.txdbus.trial.TestException' pass class InvalidErrorName (Exception): dbusErrorName = 'oops' class TestClass (objects.DBusObject): tif = DBusInterface( 'org.txdbus.trial.Oops', Method('errCall', arguments='s'), Method('raiseExpected'), Method('raisePython'), Method('raiseInvalid'), Signal('testSignal', 's') ) dbusInterfaces = [tif] def __init__(self, object_path): objects.DBusObject.__init__(self, object_path) def dbus_errCall(self, arg): self.fail('Should not see this') def dbus_raiseExpected(self): raise ErrorTester.TestException('ExpectedError') def dbus_raisePython(self): d = {} d['Uh oh!'] def dbus_raiseInvalid(self): raise ErrorTester.InvalidErrorName() def test_err_no_method(self): def on_err(e): self.assertEquals( 'org.freedesktop.DBus.Error.UnknownMethod: Method "FooBarBaz" ' 'with signature "" on interface "(null)" doesn\'t exist', str(e.value), ) d = self.client_conn.callRemote(self.tst_path, 'FooBarBaz', destination=self.tst_bus) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_err_no_object(self): def on_err(e): self.assertEquals( 'org.freedesktop.DBus.Error.UnknownObject: ' '/test/TestObjINVALID is not an object provided ' 'by this process.', str(e.value), ) d = self.client_conn.callRemote(self.tst_path + "INVALID", 'FooBarBaz', destination=self.tst_bus) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_err_call_no_arguments(self): def on_err(e): self.assertEquals( str(e.value), 'org.freedesktop.DBus.Error.InvalidArgs: Call to errCall has ' 'wrong args (, expected s)', ) d = self.client_conn.callRemote(self.tst_path, 'errCall', destination=self.tst_bus) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_err_call_bad_arguments(self): def on_err(e): self.assertEquals( str(e.value), 'org.freedesktop.DBus.Error.InvalidArgs: Call to errCall has ' 'wrong args (i, expected s)', ) self.assertTrue(True) d = self.client_conn.callRemote( self.tst_path, 'errCall', signature='i', body=[5], destination=self.tst_bus ) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_raise_expected(self): def on_err(e): self.assertEquals( str(e.value), 'org.txdbus.trial.TestException: ExpectedError', ) d = self.client_conn.callRemote( self.tst_path, 'raiseExpected', destination=self.tst_bus, ) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_raise_python(self): def on_err(e): self.assertEquals( str(e.value), "org.txdbus.PythonException.KeyError: 'Uh oh!'", ) d = self.client_conn.callRemote( self.tst_path, 'raisePython', destination=self.tst_bus, ) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_raise_invalid(self): def on_err(e): self.assertEquals( str(e.value), 'org.txdbus.InvalidErrorName: !!(Invalid error name "oops")!! ' ) d = self.client_conn.callRemote( self.tst_path, 'raiseInvalid', destination=self.tst_bus, ) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def _test_bad(self): def on_err(e): self.assertTrue(True) d = self.client_conn.callRemote( '/org/freedesktop/DBus', 'RequestName', interface='org.freedesktop.DBus', signature='i', body=[5], destination='org.freedesktop.DBus', ) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d class ComplexObjectTester(ServerObjectTester): class TestClass (objects.DBusObject): tif = DBusInterface( 'org.txdbus.trial.Complex', Method( 'testComplexArgs', arguments='s(ii(si)i)', returns='s', ), Method( 'testDictToTuples', arguments='a{ss}', returns='a(ss)', ), Method( 'testDictToTuples2', arguments='a{ss}', returns='a(ss)y', ), Method( 'testDictToTuples3', arguments='a{ss}', returns='ia(ss)y', ), Method( 'testDictToTuples4', arguments='(a{ss})', returns='(ia(ss)y)', ), Method('testCaller'), Method('testTimeOut'), Method('testTimeOutNotNeeded', arguments='s'), Method('notImplemented'), Signal('tsig', 's') ) dbusInterfaces = [tif] def __init__(self, object_path): objects.DBusObject.__init__(self, object_path) def dbus_testComplexArgs(self, arg1, arg2): return repr(arg1) + ' # ' + repr(arg2) def dbus_testDictToTuples(self, d): k = sorted(d.keys()) r = [(x, d[x]) for x in k] return r def dbus_testDictToTuples2(self, d): k = sorted(d.keys()) r = [(x, d[x]) for x in k] return (r, 6) def dbus_testDictToTuples3(self, d): k = sorted(d.keys()) r = [(x, d[x]) for x in k] return (2, r, 6) def dbus_testDictToTuples4(self, tpl): d = tpl[0] k = sorted(d.keys()) r = [(x, d[x]) for x in k] return (2, r, 6) def dbus_testCaller(self, dbusCaller=None): if dbusCaller is None: raise Exception('dbusCaller should not be none') def dbus_testTimeOut(self): d = defer.Deferred() reactor.callLater(0.02, lambda: d.callback(None)) return d def dbus_testTimeOutNotNeeded(self, arg): if arg == 'err': raise Exception('err') return 'foo' def test_comlex_args(self): class Sub: dbusOrder = ['substr', 'subint'] def __init__(self): self.substr = 'substring' self.subint = 10 class Foo: dbusOrder = ['one', 'two', 'sub', 'four'] def __init__(self): self.one = 1 self.two = 2 self.sub = Sub() self.four = 4 def got_object(ro): return ro.callRemote('testComplexArgs', 'foo', Foo()) def got_reply(reply): expected = repr(u'foo') + ' # ' + \ repr([1, 2, [u'substring', 10], 4]) self.assertEquals(reply, expected) return self.proxy_chain(got_object, got_reply) def test_dict_to_tuples(self): d = {'foo': 'bar', 'baz': 'quux', 'william': 'wallace'} def got_object(ro): return ro.callRemote('testDictToTuples', d) def got_reply(reply): self.assertEquals( reply, [['baz', 'quux'], ['foo', 'bar'], ['william', 'wallace']], ) return self.proxy_chain(got_object, got_reply) def test_dict_to_tuples2(self): d = {'foo': 'bar', 'baz': 'quux', 'william': 'wallace'} def got_object(ro): return ro.callRemote('testDictToTuples2', d) def got_reply(reply): self.assertEquals( reply[0], [['baz', 'quux'], ['foo', 'bar'], ['william', 'wallace']], ) self.assertEquals(reply[1], 6) return self.proxy_chain(got_object, got_reply) def test_dict_to_tuples3(self): d = {'foo': 'bar', 'baz': 'quux', 'william': 'wallace'} def got_object(ro): return ro.callRemote('testDictToTuples3', d) def got_reply(reply): self.assertEquals(reply[0], 2) self.assertEquals( reply[1], [['baz', 'quux'], ['foo', 'bar'], ['william', 'wallace']], ) self.assertEquals(reply[2], 6) return self.proxy_chain(got_object, got_reply) def test_dict_to_tuples4(self): d = {'foo': 'bar', 'baz': 'quux', 'william': 'wallace'} def got_object(ro): return ro.callRemote('testDictToTuples4', [d]) def got_reply(reply_obj): reply = reply_obj[0] self.assertEquals(reply[0], 2) self.assertEquals( reply[1], [['baz', 'quux'], ['foo', 'bar'], ['william', 'wallace']], ) self.assertEquals(reply[2], 6) return self.proxy_chain(got_object, got_reply) def test_caller(self): def got_object(ro): return ro.callRemote('testCaller') def got_reply(reply): self.assertTrue(True) return self.proxy_chain(got_object, got_reply) def test_time_out(self): def on_proxy(ro): return ro.callRemote('testTimeOut', timeout=0.01) def on_err(err): self.assertTrue( isinstance(err.value, error.TimeOut), 'Did not receive a timeout', ) d = self.get_proxy() d.addCallback(on_proxy) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) d.addBoth(delay) return d def test_time_out_not_needed(self): def on_proxy(ro): return ro.callRemote('testTimeOutNotNeeded', '', timeout=5) def on_err(err): self.assertTrue( not isinstance(err.value, error.TimeOut), 'Should not have received a timeout', ) d = self.get_proxy() d.addCallback(on_proxy) d.addErrback(on_err) return d def test_time_out_not_needed_on_error(self): def on_proxy(ro): return ro.callRemote('testTimeOutNotNeeded', 'err', timeout=5) def on_err(err): self.assertTrue( not isinstance(err.value, error.TimeOut), 'Should not have received a timeout', ) d = self.get_proxy() d.addCallback(on_proxy) d.addErrback(on_err) return d def test_not_implemented_dbus_method(self): def on_proxy(ro): return ro.callRemote('notImplemented') def on_err(err): self.assertEquals( err.getErrorMessage(), 'org.txdbus.PythonException.NotImplementedError', ) d = self.get_proxy() d.addCallback(on_proxy) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) d.addBoth(delay) return d def test_connection_lost_callbacks(self): x = {'hit': False} def cb(conn, reason): x['hit'] = True self.client_conn.notifyOnDisconnect(cb) self.client_conn.disconnect() self.client_conn = None d = delay(0.01) d.addCallback(lambda _: self.assertTrue(x['hit'])) return d def test_connection_lost_callbacks_canceled(self): x = {'hit1': False, 'hit2': False} def cb1(conn, reason): x['hit1'] = True def cb2(conn, reason): x['hit2'] = True self.client_conn.notifyOnDisconnect(cb2) self.client_conn.notifyOnDisconnect(cb1) self.client_conn.cancelNotifyOnDisconnect(cb2) self.client_conn.disconnect() self.client_conn = None d = delay(0.01) d.addCallback(lambda _: self.assertTrue(x['hit1'])) d.addCallback(lambda _: self.assertTrue(not x['hit2'])) return d def test_connection_lost_with_pending_calls(self): x = {'hit': False} def cb(conn, reason): x['hit'] = True pending = reactor.callLater(0.1, lambda: self.assertTrue( False, 'Failed to cancel pending call' )) dcancel = defer.Deferred() dcancel.addCallback(lambda _: self.assertTrue(False)) dcancel.addErrback(lambda _: self.assertTrue(True)) # Directly inject self.client_conn._pendingCalls['foo'] = (dcancel, pending) self.client_conn.notifyOnDisconnect(cb) self.client_conn.disconnect() self.client_conn = None d = delay(0.01) d.addCallback(lambda _: dcancel) return d class InterfaceTester(ServerObjectTester): class TestClass (objects.DBusObject): tif1 = DBusInterface( 'org.txdbus.trial.IFace1', Method('testMethod', arguments='s', returns='s'), Method('foo'), Method('baz', returns='s'), Method('blah', returns='vv'), Method('onlyOneImpl'), Property('prop_if1', 's', writeable=True), Property('common', 's') ) tif2 = DBusInterface( 'org.txdbus.trial.IFace2', Method('testMethod', arguments='s', returns='s'), Method('bar'), Method('baz', returns='s'), Method('renamedMethod', returns='s'), Method('onlyOneImpl'), Property('prop1', 's'), Property('prop2', 'i'), Property('pwrite', 's', writeable=True), Property('wronly', 's', readable=False, writeable=True), Property('common', 's') ) dbusInterfaces = [tif1, tif2] pif1 = DBusProperty('prop_if1') prop_attr = DBusProperty('prop1', 'org.txdbus.trial.IFace2') prop2_attr = DBusProperty('prop2') propw = DBusProperty('pwrite') pwr = DBusProperty('wronly') common1 = DBusProperty('common', 'org.txdbus.trial.IFace1') common2 = DBusProperty('common', 'org.txdbus.trial.IFace2') def __init__(self, object_path): self.pif1 # test property access prior to object construction objects.DBusObject.__init__(self, object_path) self.prop_attr = 'foobar' self.prop2_attr = 5 self.propw = 'orig' self.pwr = 'blah' self.pif1 = 'pif1' self.common1 = 'common1' self.common2 = 'common2' def dbus_testMethod(self, arg): return arg + 'bar' def dbus_foo(self): return None def dbus_bar(self): return None def dbus_blah(self): return ('foo', 'bar') @dbusMethod('org.txdbus.trial.IFace1', 'baz') def dbus_baz1(self): return 'iface1' @dbusMethod('org.txdbus.trial.IFace2', 'baz') def dbus_baz2(self): return 'iface2' @dbusMethod('org.txdbus.trial.IFace2', 'renamedMethod') def dbus_sneaky(self): return 'sneaky' @dbusMethod('org.txdbus.trial.IFace1', 'onlyOneImpl') def dbus_onlyOneImpl(self): pass def test_property_emitsOnChange_validity(self): def c(): Property('foo', 's', emitsOnChange='foo') self.assertRaises(TypeError, c) def test_interface_invalid_constructor_argument(self): def c(): DBusInterface('foo', 1) self.assertRaises(TypeError, c) def test_delete_iterface_method(self): i = DBusInterface('foo', Method('methA'), Method('methB')) self.assertTrue('methA' in i.introspectionXml) i.delMethod('methA') self.assertTrue('methA' not in i.introspectionXml) def test_delete_iterface_signal(self): i = DBusInterface('foo', Signal('sigA'), Signal('sigB')) self.assertTrue('sigA' in i.introspectionXml) i.delSignal('sigA') self.assertTrue('sigA' not in i.introspectionXml) def test_delete_iterface_property(self): i = DBusInterface( 'foo', Property( 'propA', 'i'), Property( 'propB', 'i')) self.assertTrue('propA' in i.introspectionXml) i.delProperty('propA') self.assertTrue('propA' not in i.introspectionXml) def test_renamed_server_method(self): def got_object(ro): return ro.callRemote('renamedMethod') def got_reply(reply): self.assertEquals(reply, 'sneaky') return self.proxy_chain(got_object, got_reply) def test_get_proxy_with_string_interface_name(self): def got_object(ro): return self.client_conn.getRemoteObject( self.tst_bus, self.tst_path, interfaces='org.txdbus.trial.IFace1', ) def got_object2(ro2): return ro2.callRemote('baz') def got_reply(reply): self.assertEquals(reply, 'iface1') return self.proxy_chain(got_object, got_object2, got_reply) def test_get_proxy_with_bad_interface_name(self): def got_object(ro): return self.client_conn.getRemoteObject( self.tst_bus, self.tst_path, interfaces='org.txdbus.INVALID_INTERFACE', ) def on_err(err): self.assertEquals( err.getErrorMessage(), 'Introspection failed to find interfaces: org.txdbus.' 'INVALID_INTERFACE', ) def got(v): print('GOT: ', v) d = self.get_proxy() d.addCallback(got_object) d.addCallback(got) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_missing_implementation(self): def got_object(ro): return ro.callRemote( 'onlyOneImpl', interface='org.txdbus.trial.IFace2', ) def on_err(err): self.assertEquals( err.getErrorMessage(), 'org.txdbus.PythonException.NotImplementedError', ) d = self.get_proxy() d.addCallback(got_object) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_emit_invalid_signal(self): def got_object(ro): try: self.t.emitSignal('InvalidSignalName') self.assertTrue(False, 'Should have raised an exception') except AttributeError as e: self.assertEquals( str(e), 'Signal "InvalidSignalName" not found in any supported ' 'interface.', ) return self.proxy_chain(got_object) def test_baz(self): def got_object(ro): return ro.callRemote('baz') def got_reply(reply): self.assertEquals(reply, 'iface1') return self.proxy_chain(got_object, got_reply) def test_baz2(self): def got_object(ro): return ro.callRemote('baz', interface='org.txdbus.trial.IFace2') def got_reply(reply): self.assertEquals(reply, 'iface2') return self.proxy_chain(got_object, got_reply) def test_foo(self): def got_object(ro): return ro.callRemote('foo') def got_reply(reply): self.assertTrue(True) return self.proxy_chain(got_object, got_reply) def test_bad_remote_method(self): def got_object(ro): self.assertRaises( AttributeError, ro.callRemote, 'INVALID_METHOD_NAME') return self.proxy_chain(got_object) def test_bad_remote_method_argument_number(self): def got_object(ro): self.assertRaises(TypeError, ro.callRemote, 'foo', 'bar') return self.proxy_chain(got_object) def test_bar(self): def got_object(ro): return ro.callRemote('bar') def got_reply(reply): self.assertTrue(True) return self.proxy_chain(got_object, got_reply) def test_foo2(self): def got_object(ro): return ro.callRemote('testMethod', 'foo') def got_reply(reply): self.assertEquals(reply, 'foobar') return self.proxy_chain(got_object, got_reply) def test_muli_variant_return(self): def got_object(ro): return ro.callRemote('blah') def got_reply(reply): self.assertEquals(reply, ['foo', 'bar']) return self.proxy_chain(got_object, got_reply) def test_get_property_with_interface(self): def got_object(ro): return ro.callRemote('Get', 'org.txdbus.trial.IFace2', 'prop1') def got_reply(reply): self.assertEquals(reply, 'foobar') return self.proxy_chain(got_object, got_reply) def test_get_common_property1_with_interface(self): def got_object(ro): return ro.callRemote('Get', 'org.txdbus.trial.IFace1', 'common') def got_reply(reply): self.assertEquals(reply, 'common1') return self.proxy_chain(got_object, got_reply) def test_get_common_property2_with_interface(self): def got_object(ro): return ro.callRemote('Get', 'org.txdbus.trial.IFace2', 'common') def got_reply(reply): self.assertEquals(reply, 'common2') return self.proxy_chain(got_object, got_reply) def test_get_property_without_interface(self): def got_object(ro): return ro.callRemote('Get', '', 'prop1') def got_reply(reply): self.assertEquals(reply, 'foobar') return self.proxy_chain(got_object, got_reply) def test_get_integer_property(self): def got_object(ro): return ro.callRemote('Get', '', 'prop2') def got_reply(reply): self.assertEquals(reply, 5) return self.proxy_chain(got_object, got_reply) def test_get_invalid_property(self): def got_object(ro): return ro.callRemote('Get', '', 'INVALID_PROPERTY') def got_err(err): self.assertEquals( err.getErrorMessage(), 'org.txdbus.PythonException.Exception: Invalid Property') d = self.get_proxy() d.addCallback(got_object) d.addErrback(got_err) return d def test_set_invalid_property(self): def got_object(ro): return ro.callRemote('Set', '', 'INVALID_PROPERTY', 'Whoopsie') def got_err(err): self.assertEquals( err.getErrorMessage(), 'org.txdbus.PythonException.Exception: Invalid Property') d = self.get_proxy() d.addCallback(got_object) d.addErrback(got_err) return d def test_set_read_only_property(self): def got_object(ro): return ro.callRemote('Set', '', 'prop1', 'Whoopsie') def got_err(err): self.assertEquals( err.getErrorMessage(), 'org.txdbus.PythonException.Exception: Property is not ' 'Writeable', ) d = self.get_proxy() d.addCallback(got_object) d.addErrback(got_err) return d def test_get_write_only_property(self): def got_object(ro): return ro.callRemote('Get', '', 'wronly') def got_err(err): self.assertEquals( err.getErrorMessage(), 'org.txdbus.PythonException.Exception: Property is not ' 'readable', ) d = self.get_proxy() d.addCallback(got_object) d.addErrback(got_err) return d def test_set_string_property(self): def got_object(ro): self.assertEquals(self.t.propw, 'orig') return ro.callRemote('Set', '', 'pwrite', 'changed') def got_reply(reply): self.assertEquals(self.t.propw, 'changed') return self.proxy_chain(got_object, got_reply) def test_get_all_properties(self): def got_object(ro): return ro.callRemote('GetAll', '') def got_reply(reply): # GetAll called with no specific interface. # Remote object implements two interfaces with a 'common' # property. The result may include the value of either # interface's property, in this case: self.assertIn(reply['common'], ('common1', 'common2')) # The remaining properties have no possible ambiguity. del reply['common'] self.assertEquals(reply, { 'prop1': 'foobar', 'prop2': 5, 'prop_if1': 'pif1', 'pwrite': 'orig' }) return self.proxy_chain(got_object, got_reply) def test_property_emit_changed(self): dsig = defer.Deferred() def on_signal(*args): dsig.callback(args) def check_results(arg): interface_name, changed, invalidated = arg self.assertEquals(interface_name, 'org.txdbus.trial.IFace2') self.assertEquals(changed, {'pwrite': 'should emit'}) self.assertEquals(invalidated, []) def on_proxy(ro): dnot = ro.notifyOnSignal('PropertiesChanged', on_signal) dnot.addCallback(lambda _: ro) return dnot def setit(ro): ro.callRemote('Set', '', 'pwrite', 'should emit') d = self.get_proxy() d.addCallback(on_proxy) d.addCallback(setit) d.addCallback(lambda _: dsig) d.addCallback(check_results) return d def test_property_delete(self): def d(): del self.pif1 self.assertRaises(AttributeError, d) def test_invalid_property_name(self): class T(objects.DBusObject): prop = DBusProperty('no_interface_property') t = T('/foo') def ii(): for x in t._iterIFaceCaches(): pass self.assertRaises(AttributeError, ii) class BusNameTest(SimpleObjectTester): def test_get_bus_name(self): d = self.client_conn.requestBusName('org.test.foobar') def cb(i): self.assertEquals(i, client.NAME_ACQUIRED) d.addCallback(cb) return d def test_bad_bus_name(self): def on_err(e): self.assertEquals( 'org.freedesktop.DBus.Error.InvalidArgs: Cannot acquire a ' 'service starting with \':\' such as ":1.234"', str(e.value) ) d = self.client_conn.requestBusName(':1.234') d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d def test_already_owner(self): d = self.client_conn.requestBusName('org.test.foobar') def cb(i): self.assertEquals(i, client.NAME_ALREADY_OWNER) d.addCallback( lambda _: self.client_conn.requestBusName('org.test.foobar')) d.addCallback(cb) return d def cli2(self): f = client.DBusClientFactory() point = endpoints.getDBusEnvEndpoints(reactor)[0] point.connect(f) d = f.getConnection() def got_connection(c): self.client_conn2 = c return c d.addCallback(got_connection) return d def test_name_in_use(self): d = self.cli2() d.addCallback( lambda _: self.client_conn.requestBusName('org.test.foobar')) d.addCallback( lambda _: self.client_conn2.requestBusName('org.test.foobar')) def on_err(e): self.assertEquals( 'Failed to acquire bus name "org.test.foobar": Name in use', str(e.value), ) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) def cleanup(x): self.client_conn2.disconnect() return x d.addBoth(cleanup) return d def test_name_replacement(self): d = self.cli2() flags = {'sig': False} def on_name_lost(sig): self.assertEquals(sig.body[0], 'org.test.foobar') flags['sig'] = True d.addCallback( lambda _: self.client_conn.addMatch( on_name_lost, member='NameLost')) d.addCallback(lambda _: self.client_conn.requestBusName( 'org.test.foobar', allowReplacement=True, )) d.addCallback(lambda _: self.client_conn2.requestBusName( 'org.test.foobar', replaceExisting=True, )) d.addCallback(delay) def cb(i): self.assertTrue(flags['sig']) self.assertEquals(i, client.NAME_ACQUIRED) d.addCallback(cb) def cleanup(x): self.client_conn2.disconnect() return x d.addBoth(cleanup) return d def test_queued_name_replacement(self): d = self.cli2() flags = {'sig': False} def on_name_acq(sig): self.assertEquals(sig.body[0], 'org.test.foobar') flags['sig'] = True d.addCallback( lambda _: self.client_conn2.addMatch( on_name_acq, member='NameAcquired')) d.addCallback(lambda _: self.client_conn.requestBusName( 'org.test.foobar', allowReplacement=False, )) d.addCallback(lambda _: self.client_conn2.requestBusName( 'org.test.foobar', replaceExisting=True, doNotQueue=False, errbackUnlessAcquired=False, )) d.addCallback(lambda r: self.assertEquals( r, client.NAME_IN_QUEUE, 'Queue error' )) d.addCallback(lambda _: self.client_conn.releaseBusName( 'org.test.foobar' )) d.addCallback(lambda r: self.assertEquals( r, client.NAME_RELEASED, 'Failed to release name', )) d.addCallback(delay) d.addCallback(lambda _: self.assertTrue( flags['sig'], 'Failed to acquire name after release', )) def cleanup(x): self.client_conn2.disconnect() return x d.addBoth(cleanup) return d def test_queued_name_replacement_with_errback(self): d = self.cli2() flags = {'sig': False} def on_name_acq(sig): self.assertEquals(sig.body[0], 'org.test.foobar') flags['sig'] = True d.addCallback(lambda _: self.client_conn2.addMatch( on_name_acq, member='NameAcquired', )) d.addCallback(lambda _: self.client_conn.requestBusName( 'org.test.foobar', allowReplacement=False, )) d.addCallback(lambda _: self.client_conn2.requestBusName( 'org.test.foobar', replaceExisting=True, doNotQueue=False, )) d.addErrback(lambda e: self.assertEquals( e.getErrorMessage(), 'Failed to acquire bus name "org.test.foobar": Queued for name ' 'acquisition' )) d.addCallback(lambda _: self.client_conn.releaseBusName( 'org.test.foobar' )) d.addCallback(lambda r: self.assertEquals( r, client.NAME_RELEASED, 'Failed to release name', )) d.addCallback(delay) d.addCallback( lambda _: self.assertTrue( flags['sig'], 'Failed to acquire name after release')) def cleanup(x): self.client_conn2.disconnect() return x d.addBoth(cleanup) return d def test_list_queued_owners(self): d = self.cli2() d.addCallback(lambda _: self.client_conn.requestBusName( 'org.test.foobar', allowReplacement=False, )) d.addCallback(lambda _: self.client_conn2.requestBusName( 'org.test.foobar', replaceExisting=True, doNotQueue=False, errbackUnlessAcquired=False, )) d.addCallback(lambda r: self.assertEquals( r, client.NAME_IN_QUEUE, 'Queue error', )) d.addCallback(lambda _: self.client_conn2.listQueuedBusNameOwners( 'org.test.foobar', )) d.addCallback(lambda r: self.assertEquals( r, [self.client_conn.busName, self.client_conn2.busName], 'Bus Name Queue differes from expected value' )) def cleanup(x): self.client_conn2.disconnect() return x d.addBoth(cleanup) return d def test_empty_list_queued_owners(self): d = self.client_conn.listQueuedBusNameOwners('org.test.foobar') def on_err(e): self.assertEquals( 'org.freedesktop.DBus.Error.NameHasNoOwner: Could not get ' 'owners of name \'org.test.foobar\': no such name', str(e.value), ) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) return d # GetConnectionUnixUser def test_get_connection_user(self): try: import pwd # noqa: This is acceptable, since it's kind of a hack. except ImportError: raise SkipTest('This test is for Unix-like systems -only') d = self.cli2() d.addCallback(lambda _: self.client_conn.requestBusName( 'org.test.foobar' )) d.addCallback(lambda _: self.client_conn2.getConnectionUnixUser( 'org.test.foobar', )) d.addCallback(lambda r: self.assertEquals( r, os.getuid(), 'Failed to get connection user id', )) def cleanup(x): self.client_conn2.disconnect() return x d.addBoth(cleanup) return d def test_bad_get_connection_user1(self): try: import pwd # noqa: This is acceptable, since it's kind of a hack. except ImportError: raise SkipTest('This test is for Unix-like systems -only') d = self.cli2() d.addCallback(lambda _: self.client_conn.requestBusName( 'org.test.foobar', )) d.addCallback(lambda _: self.client_conn2.getConnectionUnixUser( 'org.MISSING_BUS_NAME', )) def on_err(e): self.assertEquals( 'org.freedesktop.DBus.Error.NameHasNoOwner: Could not get UID ' 'of name \'org.MISSING_BUS_NAME\': no such name', str(e.value), ) d.addCallbacks(lambda _: self.fail('Call should have failed'), on_err) def cleanup(x): self.client_conn2.disconnect() return x d.addBoth(cleanup) return d # Multiple UNIX_FD arguments require Twisted 17.1.0 or later. _multiFD = twisted.version >= type(twisted.version)('twisted', 17, 1, 0) _multiFDmsg = 'Multi FD arg support requires Twisted 17.1.0 or later.' class UnixFDArgumentsTester(ServerObjectTester): class TestClass(objects.DBusObject): def dbus_readFD(self, fd): f = os.fdopen(fd, 'rb') result = f.read() f.close() return bytearray(result) def dbus_concatReadFDs(self, fd1, fd2): f1 = os.fdopen(fd1, 'rb') f2 = os.fdopen(fd2, 'rb') results = f1.read(), f2.read() f1.close() f2.close() return bytearray(b''.join(results)) _dbusInterfaceArgs = [ 'org.txdbus.trial.UnixFDArgumentsTester', Method('readFD', arguments='h', returns='ay'), ] if _multiFD: _dbusInterfaceArgs.append( Method('concatReadFDs', arguments='hh', returns='ay'), ) dbusInterfaces = [DBusInterface(*_dbusInterfaceArgs)] def _create_temp_file(self, payload): fd, filename = tempfile.mkstemp(prefix='txdbus-test-') self.addCleanup(os.unlink, filename) os.write(fd, payload) os.close(fd) return filename @defer.inlineCallbacks def test_call_with_one_UNIX_FD_arg(self): PAYLOAD = b'file contents' filename = self._create_temp_file(PAYLOAD) ro = yield self.get_proxy() with open(filename, 'rb') as f: result = yield ro.callRemote('readFD', f.fileno()) self.assertEqual(PAYLOAD, bytearray(result)) @defer.inlineCallbacks def test_call_with_two_UNIX_FD_args(self): PAYLOAD_1 = b'0123456789' PAYLOAD_2 = b'abcdefghij' filename1 = self._create_temp_file(PAYLOAD_1) filename2 = self._create_temp_file(PAYLOAD_2) ro = yield self.get_proxy() with open(filename1, 'rb') as f1, open(filename2, 'rb') as f2: result = yield ro.callRemote( 'concatReadFDs', f1.fileno(), f2.fileno(), ) self.assertEqual(PAYLOAD_1 + PAYLOAD_2, bytearray(result)) if not _multiFD: test_call_with_two_UNIX_FD_args.skip = _multiFDmsg txdbus-1.1.0/tests/test_authentication.py000066400000000000000000000441611313301420000206050ustar00rootroot00000000000000import binascii import getpass import os import os.path import shutil import tempfile import time from twisted.internet import defer, interfaces, protocol, reactor from twisted.internet.protocol import Factory from twisted.trial import unittest from zope.interface import implementer from txdbus import authentication, bus, endpoints from txdbus.authentication import DBusAuthenticationFailed class GetPass(object): def getuser(self): return 'testuser' tohex = binascii.hexlify unhex = binascii.unhexlify class ClientAuthenticatorTester(unittest.TestCase): def setUp(self): authentication.getpass = GetPass() # override 'getpass' module self.ca = authentication.ClientAuthenticator() self.reply = None self.ca.beginAuthentication(self) def tearDown(self): authentication.getpass = getpass def sendAuthMessage(self, m): self.reply = m def send(self, msg): self.ca.handleAuthMessage(msg) def ae(self, x, y): self.assertEquals(x, y) def are(self, x): self.assertEquals(self.reply, x) def test_bad_auth_message(self): self.assertRaises(DBusAuthenticationFailed, self.send, b'BAD_LINE') def test_rejection(self): self.ae(self.ca.authMech, b'EXTERNAL') self.are(b'AUTH EXTERNAL') self.send(b'REJECTED') self.ae(self.ca.authMech, b'DBUS_COOKIE_SHA1') self.are(b'AUTH DBUS_COOKIE_SHA1 ' + tohex(b'testuser')) self.send(b'REJECTED') self.ae(self.ca.authMech, b'ANONYMOUS') self.are(b'AUTH ANONYMOUS 747864627573') self.assertRaises(DBusAuthenticationFailed, self.send, b'REJECTED') def test_error(self): self.ae(self.ca.authMech, b'EXTERNAL') self.are(b'AUTH EXTERNAL') self.send(b'ERROR') self.ae(self.ca.authMech, b'DBUS_COOKIE_SHA1') self.are(b'AUTH DBUS_COOKIE_SHA1 ' + tohex(b'testuser')) self.send(b'ERROR') self.ae(self.ca.authMech, b'ANONYMOUS') self.are(b'AUTH ANONYMOUS 747864627573') def test_ok(self): self.assertRaises(DBusAuthenticationFailed, self.send, b'OK') self.assertRaises(DBusAuthenticationFailed, self.send, b'OK foo') self.send(b'OK ' + tohex(b'foo')) self.ae(self.ca.getGUID(), b'foo') self.are(b'BEGIN') self.assertTrue(self.ca.authenticationSucceeded()) def test_unix_fd_disagree(self): self.assertRaises( DBusAuthenticationFailed, self.send, b'AGREE_UNIX_FD', ) def test_unix_fd_agree(self): @implementer(interfaces.IUNIXTransport) class FakeUNIXTransport(object): def write(self, data): pass def writeSequence(self, data): pass def loseConnection(self): pass def getPeer(self): pass def getHost(self): pass def sendFileDescriptor(self, descriptor): pass self.transport = FakeUNIXTransport() # "re-"begin authentication after faking my transport self.ca.beginAuthentication(self) self.send(b'OK ' + tohex(b'foo')) self.are(b'NEGOTIATE_UNIX_FD') self.send(b'AGREE_UNIX_FD') self.are(b'BEGIN') del self.transport def test_data_external(self): self.ca.authMech = b'EXTERNAL' self.send(b'DATA') self.are(b'DATA') def test_get_cookie(self): t = tempfile.mkdtemp() k = os.path.join(t, 'keyring') ctx = b'foo' cid = b'bar' fn = os.path.join(k, ctx.decode('ascii')) try: os.mkdir(k, 0o0777) self.ca.cookie_dir = k self.assertRaises( Exception, self.ca._authGetDBusCookie, None, None, ) os.chmod(k, 0o0700) self.ca.cookie_dir = '/etc' self.assertRaises( Exception, self.ca._authGetDBusCookie, None, None, ) with open(fn, 'wb') as f: f.write(b'abcd 12345 234234234\n') f.write(b'bar 12345 123456\n') self.ca.cookie_dir = k self.ae(self.ca._authGetDBusCookie(ctx, cid), b'123456') finally: shutil.rmtree(t) def test_data_dbus_cookie_sha1_err(self): self.ca.authMech = b'DBUS_COOKIE_SHA1' self.send(b'DATA ACK!') self.are(b'ERROR Non-hexadecimal digit found') class BusCookieAuthenticatorTester(unittest.TestCase): def setUp(self): self.ba = authentication.BusCookieAuthenticator() def ae(self, x, y): self.assertEquals(x, y) def ar(self, x): self.assertEquals(x, ('REJECTED', None)) def s(self, x): return self.ba.step(x) def s1(self, x, y=None): return self.ba._step_one(x, y) def s2(self, x): return self.ba._step_two(x) def test_mech_name(self): self.ae(self.ba.getMechanismName(), 'DBUS_COOKIE_SHA1') def test_step(self): self.ar(self.s(None)) self.ba.step_num = 2 self.ar(self.s('foo')) def test_step1_invalid_username(self): self.ar(self.s1('foobarbazwannabewilliamwallace')) def test_step1_invalid_uid(self): self.ar(self.s1(99999999999)) def test_step1_bad_user_keyring_permissions(self): t = tempfile.mkdtemp() k = os.path.join(t, 'keyring') try: os.mkdir(k, 0o0777) self.ar(self.s1(0, k)) finally: shutil.rmtree(t) def test_step1_create_user_keyring_dir(self): t = tempfile.mkdtemp() k = os.path.join(t, 'keyring') try: self.assertTrue(not os.path.exists(k)) self.ae(self.s1(0, k)[0], 'CONTINUE') self.assertTrue(os.path.exists(k)) finally: shutil.rmtree(t) def test_step2_fail(self): t = tempfile.mkdtemp() k = os.path.join(t, 'keyring') try: self.assertTrue(not os.path.exists(k)) self.ae(self.s1(0, k)[0], 'CONTINUE') self.assertTrue(os.path.exists(k)) self.ar(self.s2('INVALID RESPONSE')) finally: shutil.rmtree(t) def test_lock(self): t = tempfile.mkdtemp() k = os.path.join(t, 'keyring') try: self.assertTrue(not os.path.exists(k)) self.ae(self.s1(0, k)[0], 'CONTINUE') self.assertTrue(os.path.exists(k)) lf = self.ba.cookie_file + '.lock' with open(lf, 'w') as f: f.write('\0') self.ba._get_lock() self.assertTrue(True) finally: shutil.rmtree(t) class DBusCookieAuthenticationTester(unittest.TestCase): def setUp(self): authentication.getpass = GetPass() # override 'getpass' module self.ca = authentication.ClientAuthenticator() self.ba = authentication.BusCookieAuthenticator() self.reply = None self.ca.beginAuthentication(self) def tearDown(self): authentication.getpass = getpass def sendAuthMessage(self, m): self.reply = m def send(self, msg): self.ca.handleAuthMessage(msg) def test_dbus_cookie_authentication(self): self.assertEquals(self.ba.getMechanismName(), 'DBUS_COOKIE_SHA1') while not self.ca.authMech == b'DBUS_COOKIE_SHA1': self.ca.authTryNextMethod() self.assertEquals( self.reply, b'AUTH DBUS_COOKIE_SHA1 ' + tohex(b'testuser'), ) t = tempfile.mkdtemp() k = os.path.join(t, 'keyring') try: self.ca.cookie_dir = k s1 = self.ba._step_one('0', k) self.assertEquals(s1[0], 'CONTINUE') self.send(b'DATA ' + tohex(s1[1])) self.assertTrue(self.reply.startswith(b'DATA')) self.assertEquals( self.ba._step_two(unhex(self.reply.split()[1])), ('OK', None), ) finally: shutil.rmtree(t) class DBusCookieCookieHandlingTester(unittest.TestCase): def setUp(self): self.ba = authentication.BusCookieAuthenticator() self.t = tempfile.mkdtemp() self.ba.cookie_file = os.path.join(self.t, 'nomnomnom') def tearDown(self): shutil.rmtree(self.t) def test_make_cookies(self): def g(t): def tf(): return time.time() - t return tf self.ba._create_cookie(g(31.0)) self.ba._create_cookie(g(31.0)) self.ba._create_cookie(g(20.0)) self.ba._create_cookie(g(21.2)) c = self.ba._get_cookies() self.assertEquals({b'3', b'4'}, {x[0] for x in c}) def test_del_cookie_with_remaining(self): self.ba._create_cookie() self.ba._create_cookie() self.ba._create_cookie() self.ba.cookieId = 2 self.ba._delete_cookie() c = self.ba._get_cookies() self.assertEquals({b'1', b'3'}, {x[0] for x in c}) def test_del_cookie_last(self): self.ba._create_cookie() self.ba.cookieId = 1 self.assertTrue(os.path.exists(self.ba.cookie_file)) self.ba._delete_cookie() self.assertTrue(not os.path.exists(self.ba.cookie_file)) class ExternalAuthMechanismTester(unittest.TestCase): def test_external_auth_logic(self): bea = authentication.BusExternalAuthenticator() self.assertEquals(bea.getMechanismName(), 'EXTERNAL') class T(object): _unix_creds = None bea.init(T()) self.assertEquals( bea.step(''), ('REJECT', 'Unix credentials not available'), ) bea.creds = ('foo', 0) self.assertEquals(bea.step(''), ('CONTINUE', '')) self.assertEquals(bea.step(''), ('OK', None)) self.assertEquals(bea.getUserName(), 'root') bea.cancel() class AnonymousAuthMechanismTester(unittest.TestCase): def test_anonymous_auth_logic(self): baa = authentication.BusAnonymousAuthenticator() self.assertEquals(baa.getMechanismName(), 'ANONYMOUS') baa.init(None) self.assertEquals(baa.step(''), ('OK', None)) self.assertEquals(baa.getUserName(), 'anonymous') baa.cancel() # --------------------------------------------------------------------- # Protocol Level Tests # --------------------------------------------------------------------- # Always use the internal bus for tests if a system bus isn't available # typically the session bus won't exist on Windows # INTERNAL_BUS = 'DBUS_SESSION_BUS_ADDRESS' not in os.environ INTERNAL_BUS = True def delay(arg): d = defer.Deferred() reactor.callLater(0.05, lambda: d.callback(arg)) return d def get_username(): uname = os.environ.get('USERNAME', None) if uname is None: uname = os.environ.get('LOGNAME', None) return uname.encode('ascii') class AuthTestProtocol(protocol.Protocol): _buffer = b'' _sent_null = False def connectionMade(self): self.disconnect_d = None self.disconnect_timeout = None self.fail_exit_d = None self.factory._ok(self) def dataReceived(self, data): lines = (self._buffer + data).split(b'\r\n') self._buffer = lines.pop(-1) for line in lines: self.gotMessage(line) def disconnect(self): self.transport.loseConnection() def setTest(self, test): self.test = test self.assertTrue = self.test.assertTrue self.assertEquals = self.test.assertEquals self.fail = self.test.fail def succeed(self): self.assertTrue(True) def connectionLost(self, reason): if self.disconnect_d: if self.disconnect_timeout: self.disconnect_timeout.cancel() self.disconnect_timeout = None d = self.disconnect_d self.disconnect_d = None d.callback(None) elif self.fail_exit_d: d = self.fail_exit_d self.fail_exit_d = None d.errback(unittest.FailTest('Connection unexpectedly dropped')) def failOnExit(self): self.fail_exit_d = defer.Deferred() def cleanup(_): self.fail_exit_d = None return _ self.fail_exit_d.addCallback(cleanup) return self.fail_exit_d def expectDisconnect(self): self.disconnect_d = defer.Deferred() def timeout(): self.fail() d = self.disconnect_d self.disconnect_d = None d.errback(Exception('Disconnect timed out')) self.disconnect_timeout = reactor.callLater(2, timeout) self.disconnect_d.addCallback(lambda _: self.succeed()) return self.disconnect_d def send(self, msg): if not self._sent_null: self.transport.write(b'\0') self._sent_null = True self.transport.write(msg + b'\r\n') def test_no_null_byte_at_start(self): d = self.expectDisconnect() self.transport.write(b'blah') return d def test_bad_command(self): d = self.failOnExit() self.send(b'FISHY') def recv(msg): self.assertEquals(msg, b'ERROR "Unknown command"') d.callback(None) self.gotMessage = recv return d def test_bad_mech(self): d = self.failOnExit() self.send(b'AUTH FOOBAR') def recv(msg): self.assertTrue(msg.startswith(b'REJECTED')) d.callback(None) self.gotMessage = recv return d def test_bad_mech2(self): d = self.failOnExit() self.send(b'AUTH FOO BAR') def recv(msg): self.assertTrue(msg.startswith(b'REJECTED')) d.callback(None) self.gotMessage = recv return d def test_too_long(self): d = self.expectDisconnect() self.send(b'AUTH ' + b'A' * 17000) return d def test_max_rejects(self): d = self.expectDisconnect() def retry(_=None): dr = defer.Deferred() self.send(b'AUTH FOOBAR') def recv(msg): self.assertTrue(msg.startswith(b'REJECTED')) dr.callback(None) self.gotMessage = recv return dr x = retry() x.addCallback(retry) x.addCallback(retry) x.addCallback(retry) x.addCallback(retry) x.addCallback(retry) return d def test_reject(self): d = self.failOnExit() self.send(b'AUTH DBUS_COOKIE_SHA1') def recv(msg): self.assertTrue(msg.startswith(b'REJECTED')) d.callback(None) self.gotMessage = recv return d def test_retry(self): d = self.failOnExit() self.send(b'AUTH DBUS_COOKIE_SHA1') def recv2(msg): self.assertTrue(msg.startswith(b'DATA')) d.callback(None) def recv1(msg): self.send( b'AUTH DBUS_COOKIE_SHA1 ' + binascii.hexlify( get_username())) self.assertTrue(msg.startswith(b'REJECTED')) self.gotMessage = recv2 self.gotMessage = recv1 return d def test_cancel(self): d = self.failOnExit() self.send(b'AUTH DBUS_COOKIE_SHA1 ' + binascii.hexlify(get_username())) def recv2(msg): self.assertTrue(msg.startswith(b'REJECTED')) d.callback(None) def recv1(msg): self.send(b'CANCEL') self.assertTrue(msg.startswith(b'DATA')) self.gotMessage = recv2 self.gotMessage = recv1 return d class AuthFactory (Factory): """ Factory for DBusClientConnection instances """ protocol = AuthTestProtocol def __init__(self): self.d = defer.Deferred() def _ok(self, proto): self.d.callback(proto) def _failed(self, err): self.d.errback(err) def getConnection(self): """ Returns the fully-connected DBusClientConnection instance. This method should be used to obtain a reference to the DBusClientConnection as it will be called back/error backed after authentication and DBus session registration are complete. """ return self.d class ServerObjectTester(unittest.TestCase): def setUp(self): if INTERNAL_BUS: os.environ['DBUS_SESSION_BUS_ADDRESS'] = ( 'unix:abstract=/tmp/txdbus-test,guid=5' ) bus_obj = bus.Bus() f = Factory() f.protocol = bus.BusProtocol f.bus = bus_obj point = endpoints.getDBusEnvEndpoints(reactor, False)[0] d = point.listen(f) def got_port(port): self.port = port return self._client_connect() d.addCallback(got_port) return d else: return self._client_connect() def _client_connect(self): self.conn = None f = AuthFactory() point = endpoints.getDBusEnvEndpoints(reactor)[0] point.connect(f) d = f.getConnection() d.addCallback(self._connected) return d def _connected(self, conn): self.conn = conn self.conn.setTest(self) def tearDown(self): if self.conn: self.conn.disconnect() if INTERNAL_BUS: return self.port.stopListening() def test_no_null_byte_at_start(self): return self.conn.test_no_null_byte_at_start() def test_bad_command(self): return self.conn.test_bad_command() def test_bad_mech(self): return self.conn.test_bad_mech() def test_bad_mech2(self): return self.conn.test_bad_mech2() def test_too_long(self): return self.conn.test_too_long() def test_reject(self): return self.conn.test_reject() def test_retry(self): return self.conn.test_retry() def test_cancel(self): return self.conn.test_cancel() def test_max_rejects(self): return self.conn.test_max_rejects() txdbus-1.1.0/tests/test_client_against_internal_bus.py000066400000000000000000000027761313301420000233250ustar00rootroot00000000000000import os import sys from unittest import SkipTest import six from twisted.internet import defer, reactor from twisted.internet.protocol import Factory from twisted.trial import unittest from tests import client_tests from txdbus import bus, endpoints # Force the objects test code to use the internal bus rather than the # session bus provided by the operating system. def delay(t): d = defer.Deferred() reactor.callLater(t, lambda: d.callback(None)) return d class InternalBusMixin (object): def _setup(self): raise SkipTest('Internal bus tests are currently broken.') self.orig_env = os.environ['DBUS_SESSION_BUS_ADDRESS'] os.environ['DBUS_SESSION_BUS_ADDRESS'] = ( 'unix:abstract=/tmp/txdbus-test,guid=5' ) bus_obj = bus.Bus() f = Factory() f.protocol = bus.BusProtocol f.bus = bus_obj point = endpoints.getDBusEnvEndpoints(reactor, False)[0] d = point.listen(f) def got_port(port): self.port = port d.addCallback(got_port) return d def _teardown(self): os.environ['DBUS_SESSION_BUS_ADDRESS'] = self.orig_env return self.port.stopListening() # "Copy" the objects unit tests into this module m = sys.modules[__name__] for k, v in six.iteritems(client_tests.__dict__): if ( isinstance(v, type) and issubclass(v, client_tests.ServerObjectTester) ): setattr(m, k, type(k, (InternalBusMixin, v, unittest.TestCase), {})) txdbus-1.1.0/tests/test_client_against_native_bus.py000066400000000000000000000015241313301420000227650ustar00rootroot00000000000000import os import sys import six from twisted.trial import unittest from tests import client_tests # Only test against the native bus if it's available if 'DBUS_SESSION_BUS_ADDRESS' in os.environ: orig_env = os.environ['DBUS_SESSION_BUS_ADDRESS'] class NativeBusMixin (object): def _setup(self): os.environ['DBUS_SESSION_BUS_ADDRESS'] = orig_env def _teardown(self): pass # "Copy" the objects unit tests into this module m = sys.modules[__name__] for k, v in six.iteritems(client_tests.__dict__): if ( isinstance(v, type) and issubclass(v, client_tests.ServerObjectTester) and v is not client_tests.ServerObjectTester ): setattr(m, k, type( k, (NativeBusMixin, v, unittest.TestCase), {} )) txdbus-1.1.0/tests/test_endpoints.py000066400000000000000000000065641313301420000175760ustar00rootroot00000000000000import os from twisted.internet import reactor from twisted.internet.endpoints import UNIXServerEndpoint from twisted.trial import unittest from txdbus import endpoints class EndpointsTester(unittest.TestCase): def setUp(self): self.pre_ses = os.environ['DBUS_SESSION_BUS_ADDRESS'] def tearDown(self): os.environ['DBUS_SESSION_BUS_ADDRESS'] = self.pre_ses def env(self, val): if val: os.environ['DBUS_SESSION_BUS_ADDRESS'] = val else: if 'DBUS_SESSION_BUS_ADDRESS' in os.environ: del os.environ['DBUS_SESSION_BUS_ADDRESS'] def gde(self, addr, client=True): return endpoints.getDBusEndpoints(reactor, addr, client) def test_no_session(self): self.env(None) try: endpoints.getDBusEnvEndpoints(reactor) self.assertTrue(False) except Exception as e: self.assertEquals( str(e), 'DBus Session environment variable not set', ) def xtest_env_session(self): pass def test_unix_address(self): e = self.gde('unix:path=/var/run/dbus/system_bus_socket')[0] self.assertEquals(e._path, '/var/run/dbus/system_bus_socket') e = self.gde('unix:tmpdir=/tmp')[0] self.assertTrue(e._path.startswith('/tmp/dbus-')) e = self.gde( 'unix:abstract=/tmp/dbus-jgAbdgyUH7,' 'guid=6abbe624c672777bd87ab46e00027706' )[0] self.assertEquals(e._path, '\0/tmp/dbus-jgAbdgyUH7') e = self.gde('unix:abstract=/tmp/dbus-jgAbdgyUH7', False)[0] self.assertEquals(e._address, '\0/tmp/dbus-jgAbdgyUH7') self.assertTrue(isinstance(e, UNIXServerEndpoint)) def test_tcp_address(self): e = self.gde('tcp:host=127.0.0.1,port=1234')[0] self.assertEquals(e._host, '127.0.0.1') self.assertEquals(e._port, 1234) e = self.gde('tcp:host=127.0.0.1,port=1234', False)[0] self.assertEquals(e._interface, '127.0.0.1') self.assertEquals(e._port, 1234) def test_nonce_tcp_address(self): e = self.gde('nonce-tcp:host=127.0.0.1,port=1234,noncefile=/foo')[0] self.assertEquals(e._host, '127.0.0.1') self.assertEquals(e._port, 1234) self.assertTrue('noncefile' in e.dbus_args) self.assertEquals(e.dbus_args['noncefile'], '/foo') def test_launchd_address(self): l = self.gde('launchd:env=foo') self.assertEquals(l, []) def test_session(self): self.env( 'unix:abstract=/tmp/dbus-jgAbdgyUH7,' 'guid=6abbe624c672777bd87ab46e00027706' ) e = self.gde('session')[0] self.assertEquals(e._path, '\0/tmp/dbus-jgAbdgyUH7') self.env(None) self.assertRaises(Exception, self.gde, 'session') def test_system(self): e = self.gde('system')[0] self.assertEquals(e._path, '/var/run/dbus/system_bus_socket') def test_multiple_addresses(self): self.env( 'unix:abstract=/tmp/dbus-jgAbdgyUH7,' 'guid=6abbe624c672777bd87ab46e00027706;' 'tcp:host=127.0.0.1,port=1234' ) l = self.gde('session') self.assertTrue(len(l) == 2) e = l[0] self.assertEquals(e._path, '\0/tmp/dbus-jgAbdgyUH7') e = l[1] self.assertEquals(e._host, '127.0.0.1') self.assertEquals(e._port, 1234) txdbus-1.1.0/tests/test_marshal.py000066400000000000000000000315621313301420000172160ustar00rootroot00000000000000import unittest from struct import pack import six import txdbus.marshal as m # dbus_types = [ ('BYTE', 'y', 1), # ('BOOLEAN', 'b', 4), # ('INT16', 'n', 2), # ('UINT16', 'q', 2), # ('INT32', 'i', 4), # ('UINT32', 'u', 4), # ('INT64', 'x', 8), # ('UINT64', 't', 8), # ('DOUBLE', 'd', 8), # ('STRING', 's', 4), # (4-byte align for length) # ('OBJECT_PATH', '4', 4), # (4-byte align for length) # ('SIGNATURE', 'g', 1), # ('ARRAY', 'a', 4), # (4-byte align for length) # ('STRUCT', '(', 8), # ('VARIANT', 'v', 1), # (1-byte align for signature) # ('DICT_ENTRY', '{', 8), # ('UNIX_FD', 'h', 4) # ] class SigFromPyTests(unittest.TestCase): def t(self, p, s): self.assertEquals(m.sigFromPy(p), s) def test_int(self): self.t(1, 'i') def test_bool(self): self.t(True, 'b') @unittest.skipIf(six.PY3, 'Python 3 uses unified integers: no long type.') def test_long(self): self.t(long(1), 'x') # noqa: This test is python2-only def test_float(self): self.t(1.0, 'd') def test_string(self): self.t('foo', 's') def test_list(self): self.t([1], 'ai') def test_bytearray(self): self.t(bytearray(six.b('\xAA\xAA')), 'ay') def test_list_multiple_elements_same_type(self): self.t([1, 2], 'ai') def test_list_of_variants(self): self.t([1, '2'], 'av') def test_tuple(self): self.t(('foo', 1), '(si)') def test_dict(self): self.t({'foo': 1}, 'a{si}') def test_dict_multiple_elements_same_type(self): self.t({'foo': 1, 'bar': 2}, 'a{si}') def test_dict_of_variants(self): self.t({'foo': 1, 'bar': '2'}, 'a{sv}') def test_fail(self): class I(object): pass self.assertRaises(m.MarshallingError, m.sigFromPy, I()) def test_class(self): class V(object): dbusSignature = 'ii' self.t(V(), 'ii') class AlignmentTests(unittest.TestCase): def test_no_padding(self): self.assertEquals(m.pad['y'](1), b'') def test_2align(self): self.assertEquals(m.pad['n'](1), b'\0') def test_8align(self): self.assertEquals(m.pad['t'](1), b'\0' * 7) def test_0align(self): self.assertEquals(m.pad['t'](8), b'') def test_mid_align(self): self.assertEquals(m.pad['t'](4), b'\0' * 4) class SignatureIteratorTests(unittest.TestCase): def ae(self, sig, expected): self.assertEquals(list(m.genCompleteTypes(sig)), expected) def test_one(self): self.ae('i', ['i']) def test_two(self): self.ae('ii', ['i', 'i']) def test_multi(self): self.ae('isydnq', ['i', 's', 'y', 'd', 'n', 'q']) def test_struct(self): self.ae('i(ii)i', ['i', '(ii)', 'i']) def test_embedded_struct(self): self.ae('i(i(ss)i)i', ['i', '(i(ss)i)', 'i']) def test_embedded_array(self): self.ae('i(iaii)i', ['i', '(iaii)', 'i']) def test_array_of_struct(self): self.ae('ia(iii)i', ['i', 'a(iii)', 'i']) def test_array_of_dict(self): self.ae('ia{s(ii)}i', ['i', 'a{s(ii)}', 'i']) class TestMarshal(unittest.TestCase): def check(self, sig, var_list, expected_encoding, little_endian=True): if not isinstance(var_list, list): var_list = [var_list] nbytes, chunks = m.marshal(sig, var_list, 0, little_endian) bin_str = b''.join(chunks) self.assertEquals( nbytes, len(expected_encoding), "Byte length mismatch. Expected %d. Got %d" % ( len(expected_encoding), nbytes, ), ) self.assertEquals( bin_str, expected_encoding, "Binary encoding differs from expected value", ) class TestSimpleMarshal(TestMarshal): def test_byte(self): self.check('y', 1, b'\x01') def test_int16(self): self.check('n', -1024, pack('h', -1024)) def test_uint16(self): self.check('q', 1024, pack('H', 1024)) def test_int32(self): self.check('i', -70000, pack('i', -70000)) def test_uint32(self): self.check('u', 70000, pack('I', 70000)) def test_int64(self): self.check('x', -70000, pack('q', -70000)) def test_uint64(self): self.check('t', 70000, pack('Q', 70000)) def test_double(self): self.check('d', 3.14, pack('d', 3.14)) def test_boolean(self): self.check('b', True, pack('i', 1)) def test_string(self): self.check('s', 'Hello World', pack('i12s', 11, b'Hello World')) def test_string_wrong_type(self): self.assertRaises(m.MarshallingError, self.check, 's', 1, '') def test_string_embedded_null(self): self.assertRaises( m.MarshallingError, self.check, 's', b'Hello\0World', '') def test_signature1(self): self.check('g', 'i', pack('BcB', 1, b'i', 0)) def test_signature2(self): self.check('g', '(ii)', pack('B4sB', 4, b'(ii)', 0)) def test_endian(self): self.check('x', 70000, pack('>q', 70000), False) class TestStructMarshal(TestMarshal): def test_one(self): self.check('(i)', [[1]], pack('i', 1)) def test_two(self): self.check('(ii)', [[2, 3]], pack('ii', 2, 3)) def test_pad(self): self.check('(yx)', [[1, 70000]], pack('Bxxxxxxxq', 1, 70000)) def test_string(self): self.check('(ysy)', [[1, 'foo', 2]], pack( 'Bxxxi3sxB', 1, 3, b'foo', 2)) def test_substruct(self): self.check('(y(ii)y)', [[1, [3, 4], 2]], pack('BxxxxxxxiiB', 1, 3, 4, 2)) def test_substruct_endian(self): self.check('(y(ii)y)', [[1, [3, 4], 2]], pack( '>BxxxxxxxiiB', 1, 3, 4, 2), False) def test_custom(self): class S: dbusOrder = 'a b'.split() def __init__(self): self.a = 1 self.b = 2 self.check('(ii)', [S()], pack('ii', 1, 2)) class TestArrayMarshal(TestMarshal): def test_byte(self): self.check('ay', [[1, 2, 3, 4]], pack('iBBBB', 4, 1, 2, 3, 4)) def test_byte_bytearray(self): self.check( 'ay', bytearray(six.b('\xaa\xaa')), pack('iBB', 2, 170, 170), ) def test_string(self): self.check('as', [['x', 'foo']], pack( 'ii2sxxi4s', 16, 1, b'x', 3, b'foo')) def test_struct(self): self.check('a(ii)', [[[1, 2], [3, 4]]], pack('ixxxxiiii', 16, 1, 2, 3, 4)) def test_struct_padding(self): self.check('a(yy)', [[[1, 2], [3, 4]]], pack( 'ixxxxBBxxxxxxBB', 10, 1, 2, 3, 4)) def test_dict(self): self.check('a{yy}', [{1: 2, 3: 4}], pack( 'ixxxxBBxxxxxxBB', 10, 1, 2, 3, 4)) def test_dict_strings(self): self.check( 'a{ss}', [[('foo', 'bar'), ('x', 'y')]], pack( 'ixxxxi4si4si2sxxi2s', 30, 3, b'foo', 3, b'bar', 1, b'x', 1, b'y' ) ) def test_invalid_array(self): self.assertRaises(m.MarshallingError, self.check, 'a{yy}', 1, '') class TestVariantMarshal(TestMarshal): def test_byte(self): self.check('v', [1], pack('B2si', 1, b'i', 1)) def test_struct(self): class S: dbusSignature = '(ii)' dbusOrder = 'a b'.split() def __init__(self): self.a = 1 self.b = 2 self.check('v', [S()], pack('B5sxxii', 4, b'(ii)', 1, 2)) def test_bytearray(self): self.check( 'v', bytearray( six.b('\xAA\xAA')), pack( 'B2siBB', 2, six.b('ay'), 2, 170, 170)) # ------------------------------------------------------------------------ # Unmarshalling # ------------------------------------------------------------------------ def check_equal(a, b): try: if isinstance(a, list): check_list(a, b) elif isinstance(a, dict): check_dict(a, b) elif not a == b: raise Exception() except BaseException: return False return True def check_list(a, b): if not isinstance(b, list): raise Exception() if len(a) != len(b): raise Exception() for x, y in zip(a, b): check_equal(x, y) def check_dict(a, b): if not isinstance(b, dict): raise Exception() if not len(a.keys()) == len(b.keys()): raise Exception() aset = set(a.keys()) bset = set(b.keys()) if aset - bset: raise Exception() for x in a.keys(): check_equal(a[x], b[x]) class TestUnmarshal(unittest.TestCase): def check(self, sig, expected_value, encoding): nbytes, value = m.unmarshal(sig, encoding, 0) self.assertEquals( nbytes, len(encoding), ( "Unmarshalling length mismatch. Expected %d bytes consumed. " "Got %d" ) % (len(encoding), nbytes), ) self.assertTrue( check_equal( [expected_value], value), 'Value mismatch. Expected: "%s". Got: "%s"' % (repr(expected_value), repr(value))) class TestSimpleUnmarshal(TestUnmarshal): def test_byte(self): self.check('y', 1, b'\1') def test_int16(self): self.check('n', -1024, pack('h', -1024)) def test_uint16(self): self.check('q', 1024, pack('H', 1024)) def test_int32(self): self.check('i', -70000, pack('i', -70000)) def test_uint32(self): self.check('u', 70000, pack('I', 70000)) def test_int64(self): self.check('x', -70000, pack('q', -70000)) def test_uint64(self): self.check('t', 70000, pack('Q', 70000)) def test_double(self): self.check('d', 3.14, pack('d', 3.14)) def test_boolean(self): self.check('b', True, pack('i', 1)) def test_string(self): self.check('s', 'Hello World', pack('i12s', 11, b'Hello World')) def test_signature1(self): self.check('g', 'i', pack('BcB', 1, b'i', 0)) def test_signature2(self): self.check('g', '(ii)', pack('B4sB', 4, b'(ii)', 0)) class TestStructUnmarshal(TestUnmarshal): def test_one(self): self.check('(i)', [[1]], pack('i', 1)) def test_two(self): self.check('(ii)', [[2, 3]], pack('ii', 2, 3)) def test_pad(self): self.check('(yx)', [[1, 70000]], pack('Bxxxxxxxq', 1, 70000)) def test_string(self): self.check( '(ysy)', [[1, 'foo', 2]], pack('Bxxxi3sxB', 1, 3, b'foo', 2), ) def test_substruct(self): self.check( '(y(ii)y)', [[1, [3, 4], 2]], pack('BxxxxxxxiiB', 1, 3, 4, 2), ) class TestArrayUnmarshal(TestUnmarshal): def test_byte(self): self.check('ay', [[1, 2, 3, 4]], pack('iBBBB', 4, 1, 2, 3, 4)) def test_string(self): self.check( 'as', [['x', 'foo']], pack('ii2sxxi4s', 16, 1, b'x', 3, b'foo') ) def test_struct(self): self.check( 'a(ii)', [[[1, 2], [3, 4]]], pack('ixxxxiiii', 16, 1, 2, 3, 4) ) def test_struct_padding(self): self.check( 'a(yy)', [[[1, 2], [3, 4]]], pack('ixxxxBBxxxxxxBB', 10, 1, 2, 3, 4) ) def test_dict(self): self.check( 'a{yy}', [{1: 2, 3: 4}], pack('ixxxxBBxxxxxxBB', 10, 1, 2, 3, 4), ) def test_dict_strings(self): self.check( 'a{ss}', [{'foo': 'bar', 'x': 'y'}], pack( 'ixxxxi4si4si2sxxi2s', 30, 3, b'foo', 3, b'bar', 1, b'x', 1, b'y' ) ) def test_bad_length(self): self.assertRaises( m.MarshallingError, self.check, 'a(ii)', [[[1, 2], [3, 4]]], pack('ixxxxiiii', 15, 1, 2, 3, 4) ) class TestVariantUnmarshal(TestUnmarshal): def test_byte(self): self.check('v', [1], pack('B2si', 1, b'i', 1)) def test_struct(self): self.check('v', [[1, 2]], pack('B5sxxii', 4, b'(ii)', 1, 2)) if __name__ == '__main__': unittest.main() txdbus-1.1.0/tests/test_message.py000066400000000000000000000014351313301420000172070ustar00rootroot00000000000000import unittest from txdbus import error, message class MessageTester(unittest.TestCase): def test_too_long(self): class E(message.ErrorMessage): _maxMsgLen = 1 def c(): E('foo.bar', 5) self.assertRaises(error.MarshallingError, c) def test_reserved_path(self): def c(): message.MethodCallMessage('/org/freedesktop/DBus/Local', 'foo') self.assertRaises(error.MarshallingError, c) def test_invalid_message_type(self): class E(message.ErrorMessage): _messageType = 99 try: message.parseMessage(E('foo.bar', 5).rawMessage, oobFDs=[]) self.assertTrue(False) except Exception as e: self.assertEquals(str(e), 'Unknown Message Type: 99') txdbus-1.1.0/tests/test_objects.py000066400000000000000000000012611313301420000172110ustar00rootroot00000000000000from twisted.trial import unittest from txdbus import objects class ObjectsTester(unittest.TestCase): def test_signature_validity(self): self.assertTrue(objects.isSignatureValid('foo', 'foo')) self.assertTrue(not objects.isSignatureValid('foo', None)) self.assertTrue(not objects.isSignatureValid('foo', 'bar')) self.assertTrue(not objects.isSignatureValid(None, 'foo')) self.assertTrue(objects.isSignatureValid(None, None)) def test_property_deletion(self): class Foo(object): p = objects.DBusProperty('foo') def d(): f = Foo() del f.p self.assertRaises(AttributeError, d) txdbus-1.1.0/tests/test_validators.py000066400000000000000000000044631313301420000177370ustar00rootroot00000000000000from twisted.trial import unittest from txdbus import error, marshal class InterfaceNameValidationTester(unittest.TestCase): def t(self, s): self.assertRaises( error.MarshallingError, marshal.validateInterfaceName, s, ) def test_1(self): self.t('oops') def test_2(self): self.t('foo..bar') def test_3(self): self.t('.'.join(['a' + str(i) for i in range(0, 200)])) def test_4(self): self.t('.foo.bar') def test_5(self): self.t('1foo.bar') def test_6(self): self.t('foo.bar!') def test_7(self): self.t('foo.2bar') class ObjectNameValidationTester (InterfaceNameValidationTester): def t(self, s): self.assertRaises( error.MarshallingError, marshal.validateObjectPath, s, ) def test_1(self): self.t('foo') def test_2(self): self.t('/foo/') def test_3(self): self.t('/foo//bar') def test_4(self): self.t('/foo~bar') def test_5(self): self.assertEquals(marshal.validateObjectPath('/foo/bar'), None) class ErrorNameValidationTester (InterfaceNameValidationTester): def t(self, s): self.assertRaises( error.MarshallingError, marshal.validateErrorName, s, ) class BusNameValidationTester(unittest.TestCase): def t(self, s): self.assertRaises( error.MarshallingError, marshal.validateBusName, s, ) def test_1(self): self.t('oops') def test_2(self): self.t('foo..bar') def test_3(self): self.t('.'.join(['a' + str(i) for i in range(0, 200)])) def test_4(self): self.t('.foo.bar') def test_5(self): self.t('1foo.bar') def test_6(self): self.t('foo.bar!') def test_7(self): self.t('foo.2bar') class MemberNameValidationTester(unittest.TestCase): def t(self, s): self.assertRaises( error.MarshallingError, marshal.validateMemberName, s, ) def test_1(self): self.t('') def test_2(self): self.t('f' * 256) def test_3(self): self.t('1oops') def test_4(self): self.t('foo.bar') txdbus-1.1.0/tox.ini000066400000000000000000000005571313301420000143270ustar00rootroot00000000000000[tox] envlist = py27, py33, py34, py35, py36 skip_missing_interpreters = True [testenv] deps = -rrequirements.txt coverage commands = coverage run --source txdbus -m twisted.trial tests passenv = DBUS_SESSION_BUS_ADDRESS USERNAME LOGNAME [testenv:flake8] skip_install = True deps = flake8 flake8-comprehensions flake8-import-order commands = flake8 txdbus-1.1.0/txdbus/000077500000000000000000000000001313301420000143165ustar00rootroot00000000000000txdbus-1.1.0/txdbus/__init__.py000066400000000000000000000001331313301420000164240ustar00rootroot00000000000000 """ Twisted DBus: Native Python DBus implementation for Twisted @author: Tom Cocagne """ txdbus-1.1.0/txdbus/authentication.py000066400000000000000000000455641313301420000177250ustar00rootroot00000000000000""" This module implements DBus authentication mechanisms @author: Tom Cocagne """ import binascii import getpass import hashlib import os import os.path import time import six from twisted.internet import interfaces from twisted.python import log from zope.interface import implementer, Interface from txdbus.error import DBusAuthenticationFailed from txdbus.protocol import IDBusAuthenticator @implementer(IDBusAuthenticator) class ClientAuthenticator (object): """ Implements the client-side portion of the DBus authentication protocol. @ivar preference: List of authentication mechanisms to try in the preferred order @type preference: List of C{string} """ preference = [b'EXTERNAL', b'DBUS_COOKIE_SHA1', b'ANONYMOUS'] def beginAuthentication(self, protocol): self.authenticated = False self.protocol = protocol self.unixFDSupport = self._usesUnixSocketTransport(self.protocol) self.guid = None self.cookiedir = None # used for testing only self.authOrder = self.preference[:] self.authOrder.reverse() self.authTryNextMethod() def _usesUnixSocketTransport(self, protocol): return ( getattr(protocol, 'transport', None) and interfaces.IUNIXTransport.providedBy(protocol.transport) ) def handleAuthMessage(self, line): if b' ' not in line: cmd = line args = b'' else: cmd, args = line.split(b' ', 1) m = getattr(self, '_auth_' + cmd.decode(), None) if m: m(args) else: raise DBusAuthenticationFailed( 'Invalid DBus authentication protocol message: ' + line.decode("ascii", "replace") ) def authenticationSucceeded(self): return self.authenticated def getGUID(self): return self.guid # ------------------------------------------------- def sendAuthMessage(self, msg): self.protocol.sendAuthMessage(msg) def authTryNextMethod(self): """ Tries the next authentication method or raises a failure if all mechanisms have been tried. """ if not self.authOrder: raise DBusAuthenticationFailed() self.authMech = self.authOrder.pop() if self.authMech == b'DBUS_COOKIE_SHA1': self.sendAuthMessage( b'AUTH ' + self.authMech + b' ' + binascii.hexlify(getpass.getuser().encode('ascii')) ) elif self.authMech == b'ANONYMOUS': self.sendAuthMessage( b'AUTH ' + self.authMech + b' ' + binascii.hexlify(b'txdbus') ) else: self.sendAuthMessage(b'AUTH ' + self.authMech) def _auth_REJECTED(self, line): self.authTryNextMethod() def _auth_OK(self, line): line = line.strip() if not line: raise DBusAuthenticationFailed('Missing guid in OK message') try: self.guid = binascii.unhexlify(line) except BaseException: raise DBusAuthenticationFailed('Invalid guid in OK message') else: if self.unixFDSupport: self.sendAuthMessage(b'NEGOTIATE_UNIX_FD') else: self.sendAuthMessage(b'BEGIN') self.authenticated = True def _auth_AGREE_UNIX_FD(self, line): if self.unixFDSupport: self.sendAuthMessage(b'BEGIN') self.authenticated = True else: raise DBusAuthenticationFailed( 'AGREE_UNIX_FD with no NEGOTIATE_UNIX_FD', ) def _auth_DATA(self, line): if self.authMech == b'EXTERNAL': self.sendAuthMessage(b'DATA') elif self.authMech == b'DBUS_COOKIE_SHA1': try: data = binascii.unhexlify(line.strip()) cookie_context, cookie_id, server_challenge = data.split() server_cookie = self._authGetDBusCookie( cookie_context, cookie_id, ) client_challenge = binascii.hexlify( hashlib.sha1(os.urandom(8)).digest() ) response = b':'.join([ server_challenge, client_challenge, server_cookie ]) response = binascii.hexlify(hashlib.sha1(response).digest()) reply = client_challenge + b' ' + response self.sendAuthMessage(b'DATA ' + binascii.hexlify(reply)) except Exception as e: log.msg('DBUS Cookie authentication failed: ' + str(e)) self.sendAuthMessage( b'ERROR ' + str(e).encode('unicode-escape')) def _auth_ERROR(self, line): log.msg( 'Authentication mechanism failed: ' + line.decode("ascii", "replace") ) self.authTryNextMethod() # ------------------------------------------------- def _authGetDBusCookie(self, cookie_context, cookie_id): """ Reads the requested cookie_id from the cookie_context file """ # XXX Ensure we obtain the correct directory for the # authenticating user and that that user actually # owns the keyrings directory if self.cookie_dir is None: cookie_dir = os.path.expanduser('~/.dbus-keyrings') else: cookie_dir = self.cookie_dir dstat = os.stat(cookie_dir) if dstat.st_mode & 0o066: raise Exception( 'User keyrings directory is writeable by other users. ' 'Aborting authentication', ) import pwd if dstat.st_uid != pwd.getpwuid(os.geteuid()).pw_uid: raise Exception( 'Keyrings directory is not owned by the current user. ' 'Aborting authentication!', ) path = os.path.join(cookie_dir, cookie_context.decode('ascii')) with open(path, 'rb') as f: for line in f: try: k_id, k_time, k_cookie_hex = line.split() if k_id == cookie_id: return k_cookie_hex except BaseException: pass class IBusAuthenticationMechanism (Interface): """ Classes implementing this interface may be used by Bus instances to authenticate users """ def getMechanismName(self): """ @returns: The name of the authentication mechanism """ def init(self, protocol): """ Called to allow authentication mechanism to query protocol state """ def step(self, arg): """ @returns: ('OK' | 'CONTINUE' | 'REJECT', challenge | None) """ def getUserName(self): """ @returns: the name of the user """ def cancel(self): """ Informs the authentication mechanism that the current authentication has been canceled and that cleanup is in order """ @implementer(IBusAuthenticationMechanism) class BusCookieAuthenticator (object): """ Implements the Bus-side portion of the DBUS_COOKIE_SHA1 authentication mechanism """ cookieContext = 'org_twisteddbus_ctx' + str(os.getpid()) def __init__(self): self.step_num = 0 self.username = None self.cookieId = None def cancel(self): if self.cookieId: self._delete_cookie() def getMechanismName(self): return 'DBUS_COOKIE_SHA1' def init(self, protocol): pass def getUserName(self): return self.username def step(self, arg): s = self.step_num self.step_num += 1 if arg is None: return ('REJECTED', None) try: if s == 0: return self._step_one(arg) elif s == 1: return self._step_two(arg) else: raise Exception() except Exception as e: return ('REJECTED', None) def _step_one(self, username, keyring_dir=None): try: uid = int(username) try: import pwd username = pwd.getpwuid(uid).pw_name except BaseException: return ('REJECTED', None) except ValueError: pass self.username = username try: import pwd p = pwd.getpwnam(username) self.uid = p.pw_uid self.gid = p.pw_gid self.homedir = p.pw_dir except (KeyError, ImportError): return ('REJECTED', None) # username not found if keyring_dir is None: dk = os.path.join(self.homedir, '.dbus-keyrings') else: dk = keyring_dir # for testing only self.cookie_file = os.path.join(dk, self.cookieContext) try: s = os.lstat(dk) if not os.path.isdir(dk) or s.st_mode & 0o0066: # Invalid keyrings directory. Something fishy is going on return ('REJECTED', None) except OSError: old_un = os.umask(0o0077) os.mkdir(dk) os.umask(old_un) if os.geteuid() == 0: os.chown(dk, self.uid, self.gid) self._create_cookie() self.challenge_str = binascii.hexlify(hashlib.sha1( os.urandom(8)).digest()) msg = b' '.join([self.cookieContext.encode('ascii'), str(self.cookieId).encode('ascii'), self.challenge_str]) return ('CONTINUE', msg) def _step_two(self, response): self._delete_cookie() hash_str = None shash = 1 try: client_challenge, hash_str = response.split() tohash = ( self.challenge_str + b':' + client_challenge + b':' + self.cookie ) shash = binascii.hexlify(hashlib.sha1(tohash).digest()) except BaseException: pass if shash == hash_str: return ('OK', None) else: return ('REJECTED', None) def _get_lock(self): # # Twisted is single-threaded, our context name includes the # process id, and we wont be using any deferreds here... so # the lock file really isn't needed. We'll go ahead and # include a very simple version of it, however, "just to say # we did" # self.lock_file = self.cookie_file + '.lock' try: lockfd = os.open( self.lock_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o0600, ) except BaseException: time.sleep(0.01) try: lockfd = os.open( self.lock_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o0600, ) except BaseException: os.unlink(self.lock_file) lockfd = os.open( self.lock_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o0600, ) return lockfd def _get_cookies(self, timefunc=time.time): cookies = [] try: with open(self.cookie_file, 'rb') as f: for line in f: k_id, k_time, k_cookie_hex = line.split() if abs(timefunc() - int(k_time)) < 30: cookies.append(line.split()) except BaseException: pass return cookies def _create_cookie(self, timefunc=time.time): lockfd = self._get_lock() cookies = self._get_cookies(timefunc) cookie_id = 1 for tpl in cookies: if int(tpl[0]) >= cookie_id: cookie_id = int(tpl[0]) + 1 cookie = binascii.hexlify(os.urandom(24)) cookies.append(( str(cookie_id).encode('ascii'), str(int(timefunc())).encode('ascii'), cookie, )) for c in cookies: os.write(lockfd, b' '.join(c) + b'\n') os.close(lockfd) if os.geteuid() == 0: os.chown(self.lock_file, self.uid, self.gid) os.rename(self.lock_file, self.cookie_file) self.cookieId = cookie_id self.cookie = cookie def _delete_cookie(self): lockfd = self._get_lock() cookies = self._get_cookies() for i, tpl in enumerate(cookies): if int(tpl[0]) == self.cookieId: del cookies[i] break if not cookies: os.unlink(self.cookie_file) os.close(lockfd) os.unlink(self.lock_file) else: for c in cookies: os.write(lockfd, b' '.join(c) + b'\n') os.close(lockfd) if os.geteuid() == 0: os.chown(self.lock_file, self.uid, self.gid) os.rename(self.lock_file, self.cookie_file) @implementer(IBusAuthenticationMechanism) class BusExternalAuthenticator (object): """ Implements the Bus-side portion of the EXTERNAL authentication mechanism """ def __init__(self): self.ok = False self.creds = None def getMechanismName(self): return 'EXTERNAL' def init(self, protocol): self.creds = protocol._unix_creds def step(self, arg): if not self.creds: return ('REJECT', 'Unix credentials not available') if not self.ok: self.ok = True return ('CONTINUE', '') else: return ('OK', None) def getUserName(self): import pwd return pwd.getpwuid(self.creds[1]).pw_name def cancel(self): pass @implementer(IBusAuthenticationMechanism) class BusAnonymousAuthenticator (object): """ Implements the Bus-side portion of the ANONYMOUS authentication mechanism """ def getMechanismName(self): return 'ANONYMOUS' def init(self, protocol): pass def step(self, arg): return ('OK', None) def getUserName(self): return 'anonymous' def cancel(self): pass @implementer(IDBusAuthenticator) class BusAuthenticator (object): """ Implements the Bus-side portion of the DBus authentication protocol. @ivar authenticators: A dictionary of mechanism names to mechanism implementation classes @type authenticators: C{dict} """ MAX_REJECTS_ALLOWED = 5 authenticators = {b'EXTERNAL': BusExternalAuthenticator, b'DBUS_COOKIE_SHA1': BusCookieAuthenticator, b'ANONYMOUS': BusAnonymousAuthenticator} def __init__(self, server_guid): self.server_guid = server_guid self.authenticated = False self.mechanisms = {} self.protocol = None self.guid = None self.reject_count = 0 self.state = None self.current_mech = None for n, m in six.iteritems(self.authenticators): self.mechanisms[n] = m mechNames = self.authenticators.keys() self.reject_msg = b'REJECTED ' + b' '.join(mechNames) def beginAuthentication(self, protocol): self.protocol = protocol self.state = 'WaitingForAuth' def handleAuthMessage(self, line): # print 'RCV: ', line.rstrip() if b' ' not in line: cmd = line args = b'' else: cmd, args = line.split(b' ', 1) m = getattr(self, '_auth_' + cmd.decode(), None) if m: m(args) else: self.sendError(b'"Unknown command"') def authenticationSucceeded(self): return self.authenticated def getGUID(self): return self.guid # -------------------------------------------------- def reject(self): if self.current_mech: self.current_mech.cancel() self.current_mech = None self.reject_count += 1 if self.reject_count > self.MAX_REJECTS_ALLOWED: raise DBusAuthenticationFailed( 'Client exceeded maximum failed authentication attempts') self.sendAuthMessage(self.reject_msg) self.state = 'WaitingForAuth' def sendAuthMessage(self, msg): # print 'SND: ', msg.rstrip() self.protocol.sendAuthMessage(msg) def sendError(self, msg=None): if msg: self.sendAuthMessage(b'ERROR ' + msg) else: self.sendAuthMessage(b'ERROR') def stepAuth(self, response): if self.current_mech is None: self.reject() return if response: response = binascii.unhexlify(response.strip()).decode('ascii') status, challenge = self.current_mech.step(response) if status == 'OK': self.sendAuthMessage(b'OK ' + self.server_guid) self.state = 'WaitingForBegin' elif status == 'CONTINUE': self.sendAuthMessage(b'DATA ' + binascii.hexlify(challenge)) self.state = 'WaitingForData' else: # print 'REJECT: ', status self.reject() def _auth_AUTH(self, line): if self.state == 'WaitingForAuth': tpl = line.split() if len(tpl) == 0: self.reject() else: mech = tpl[0] initial_response = None if len(tpl) > 1: initial_response = tpl[1] if mech in self.mechanisms: m = self.mechanisms[mech]() self.current_mech = IBusAuthenticationMechanism(m) self.current_mech.init(self.protocol) self.stepAuth(initial_response) else: self.reject() else: self.sendError() def _auth_BEGIN(self, line): if self.state == 'WaitingForBegin': self.authenticated = True self.guid = self.current_mech.getUserName() self.current_mech = None else: raise DBusAuthenticationFailed('Protocol violation') def _auth_ERROR(self, line): if self.state in ('WaitingForAuth', 'WaitingForData', 'WaitingForBegin'): self.reject() def _auth_DATA(self, line): if self.state == 'WaitingForData': self.stepAuth(line) else: self.sendError() def _auth_CANCEL(self, line): if self.state in ('WaitingForData', 'WaitingForBegin'): self.reject() else: self.sendError() def _auth_NEGOTIATE_UNIX_FD(self, line): # Only valid in the 'WaitingForBegin' state self.sendError() txdbus-1.1.0/txdbus/bus.py000066400000000000000000000405171313301420000154700ustar00rootroot00000000000000""" DBus Bus implementation @author: Tom Cocagne """ import binascii import os from twisted.python import log import txdbus.protocol from txdbus import authentication, client, error, message, objects, router from txdbus import marshal from txdbus.interface import DBusInterface, Method, Signal class DError(Exception): """ Used to signal anticipated errors """ def __init__(self, errorName, msg=None): self.dbusErrorName = errorName self.errorName = errorName self.errorMessage = msg def __str__(self): return self.errorMessage class BusProtocol (txdbus.protocol.BasicDBusProtocol): """ Instances of this class handle connections to DBus clients @ivar bus: The L{Bus} instance associated with this connection @type bus: L{Bus} """ _client = False _called_hello = False bus = None authenticator = authentication.BusAuthenticator def connectionAuthenticated(self): self.username = self.guid self.uniqueName = None self.busNames = {} # name => allow_replacement self.bus = self.factory.bus self.matchRules = set() self.isConnected = True def connectionLost(self, reason): self.isConnected = False if self.bus is not None: self.bus.clientDisconnected(self) def rawDBusMessageReceived(self, raw_msg): msg = message.parseMessage(raw_msg) mt = msg._messageType if not self.uniqueName: self.bus.clientConnected(self) if not self._called_hello and mt == 1: if msg.destination == 'org.freedesktop.DBus': if msg.member == 'Hello': r = message.MethodReturnMessage( msg.serial, body=[self.uniqueName], signature='s', ) self._called_hello = True self.sendMessage(r) return else: self.transport.loseConnection() msg.sender = self.uniqueName # re-marshal with the sender set and same serial number msg._marshal(False) self.bus.messageReceived(self, msg) class Bus (objects.DBusObject): """ DBus Bus implementation. @ivar stdIface: L{interface.DBusInterface} containing the standard bus interface @type stdIface: L{interface.DBusInterface} """ stdIface = DBusInterface( 'org.freedesktop.DBus', Method('Hello', arguments='', returns='s'), Method('GetId', arguments='', returns='s'), Method( 'RequestName', arguments='su', returns='u' ), Method('ReleaseName', arguments='s', returns='u'), Method( 'ListQueuedOwners', arguments='s', returns='as' ), Method('AddMatch', arguments='s', returns=''), Method('RemoveMatch', arguments='s', returns=''), Method( 'GetNameOwner', arguments='s', returns='s' ), Method( 'GetConnectionUnixUser', arguments='s', returns='u' ), # Not Implemented Methods Method( 'GetConnectionUnixProcessId', arguments='s', returns='u' ), Method( 'ListActivatableNames', arguments='', returns='as' ), Method( 'UpdateActivationEnvironment', arguments='a{ss}', returns='' ), Method( 'StartServiceByName', arguments='su', returns='u' ), Method( 'GetAdtAuditSessionData', arguments='s', returns='u' ), Method( 'GetConnectionSELinuxSecurityContext', arguments='su', returns='ay' ), Method('ReloadConfig'), Signal('NameAcquired', arguments='s'), Signal('NameLost', arguments='s'), Signal('NameOwnerChanged', arguments='sss') ) dbusInterfaces = [stdIface] def __init__(self): objects.DBusObject.__init__(self, '/org/freedesktop/DBus') self.uuid = binascii.hexlify(os.urandom(16)) self.clients = {} # maps unique_bus_id to client connection self.busNames = {} # maps name to list of queued connections self.router = router.MessageRouter() self.next_id = 1 self.obj_handler = objects.DBusObjectHandler(self) self.obj_handler.exportObject(self) # returns the new unique bus name for the client connection def clientConnected(self, proto): """ Called when a client connects to the bus. This method assigns the new connection a unique bus name. """ proto.uniqueName = ':1.%d' % (self.next_id,) self.next_id += 1 self.clients[proto.uniqueName] = proto def clientDisconnected(self, proto): """ Called when a client disconnects from the bus """ for rule_id in proto.matchRules: self.router.delMatch(rule_id) for busName in proto.busNames.iterkeys(): self.dbus_ReleaseName(busName, proto.uniqueName) if proto.uniqueName: del self.clients[proto.uniqueName] def sendMessage(self, msg): """ Sends the supplied message to the correct destination. The @type msg: L{message.DBusMessage} @param msg: The 'destination' field of the message must be set for method calls and returns """ if msg._messageType in (1, 2): assert msg.destination, 'Failed to specify a message destination' if msg.destination is not None: if msg.destination[0] == ':': p = self.clients.get(msg.destination, None) else: p = self.busNames.get(msg.destination, None) if p: p = p[0] # print 'SND: ', msg._messageType, ' to ', p.uniqueName, 'serial', # msg.serial, if p: p.sendMessage(msg) else: log.msg( 'Invalid bus name in msg.destination: ' + msg.destination ) else: self.router.routeMessage(msg) def messageReceived(self, p, msg): mt = msg._messageType # print 'MSG: ', mt, ' from ', p.uniqueName, ' to ', msg.destination try: if mt == 1: self.methodCallReceived(p, msg) elif mt == 2: self.methodReturnReceived(p, msg) elif mt == 3: self.errorReceived(p, msg) elif mt == 4: self.signalReceived(p, msg) if ( msg.destination and not msg.destination == 'org.freedesktop.DBus' ): self.sendMessage(msg) self.router.routeMessage(msg) except DError as e: sig = None body = None if e.errorMessage: sig = 's' body = [e.errorMessage] r = message.ErrorMessage( e.errorName, msg.serial, signature=sig, body=body, ) p.sendMessage(r) def methodCallReceived(self, p, msg): if msg.destination == 'org.freedesktop.DBus': self.obj_handler.handleMethodCallMessage(msg) def methodReturnReceived(self, p, msg): pass def errorReceived(self, p, msg): pass def signalReceived(self, p, msg): pass def sendSignal(self, p, member, signature=None, body=None, path='/org/freedesktop/DBus', interface='org.freedesktop.DBus'): """ Sends a signal to a specific connection @type p: L{BusProtocol} @param p: L{BusProtocol} instance to send a signal to @type member: C{string} @param member: Name of the signal to send @type path: C{string} @param path: Path of the object emitting the signal. Defaults to 'org/freedesktop/DBus' @type interface: C{string} @param interface: If specified, this specifies the interface containing the desired method. Defaults to 'org.freedesktop.DBus' @type body: None or C{list} @param body: If supplied, this is a list of signal arguments. The contents of the list must match the signature. @type signature: None or C{string} @param signature: If specified, this specifies the DBus signature of the body of the DBus Signal message. This string must be a valid Signature string as defined by the DBus specification. If the body argumnent is supplied, this parameter must be provided. """ if not isinstance(body, (list, tuple)): body = [body] s = message.SignalMessage(path, member, interface, p.uniqueName, signature, body) p.sendMessage(s) def broadcastSignal(self, member, signature=None, body=None, path='/org/freedesktop/DBus', interface='org.freedesktop.DBus'): """ Sends a signal to all connections with registered interest @type member: C{string} @param member: Name of the signal to send @type path: C{string} @param path: Path of the object emitting the signal. Defaults to 'org/freedesktop/DBus' @type interface: C{string} @param interface: If specified, this specifies the interface containing the desired method. Defaults to 'org.freedesktop.DBus' @type body: None or C{list} @param body: If supplied, this is a list of signal arguments. The contents of the list must match the signature. @type signature: None or C{string} @param signature: If specified, this specifies the DBus signature of the body of the DBus Signal message. This string must be a valid Signature string as defined by the DBus specification. If the body argumnent is supplied , this parameter must be provided. """ if not isinstance(body, (list, tuple)): body = [body] s = message.SignalMessage(path, member, interface, None, signature, body) self.router.routeMessage(s) # ---------------------------------------------------------------- # DBus Object Interface # def dbus_Hello(self, dbusCaller=None): raise DError( 'org.freedesktop.DBus.Error.Failed', 'Already handled an Hello message', ) def dbus_GetId(self): return self.uuid def dbus_RequestName(self, name, flags, dbusCaller=None): caller = self.clients[dbusCaller] allow_replacement = bool(flags & 0x1) replace_existing = bool(flags & 0x2) do_not_queue = bool(flags & 0x4) if not name: raise DError( 'org.freedesktop.DBus.Error.InvalidArgs', 'Empty string is not a valid bus name', ) if name[0] == ':': raise DError( 'org.freedesktop.DBus.Error.InvalidArgs', 'Cannot acquire a service starting with \':\' such as "%s"' % (name,), ) try: marshal.validateBusName(name) except error.MarshallingError as e: raise DError('org.freedesktop.DBus.Error.InvalidArgs', str(e)) def signalAcq(old_owner_name): self.sendSignal(caller, 'NameAcquired', 's', name) self.broadcastSignal( 'NameOwnerChanged', 'sss', [name, old_owner_name, caller.uniqueName], ) if name not in self.busNames: self.busNames[name] = [caller, ] caller.busNames[name] = allow_replacement signalAcq('') return client.NAME_ACQUIRED else: queue = self.busNames[name] owner = queue[0] if owner is caller: # Update the replacement flag owner.busNames[name] = allow_replacement return client.NAME_ALREADY_OWNER else: if not replace_existing: return client.NAME_IN_USE if owner.busNames[name]: del queue[0] queue.insert(0, caller) del owner.busNames[name] caller.busNames[name] = allow_replacement self.sendSignal(owner, 'NameLost', 's', name) signalAcq(owner.uniqueName) return client.NAME_ACQUIRED else: if do_not_queue: return client.NAME_IN_USE queue.append(caller) caller.busNames[name] = allow_replacement return client.NAME_IN_QUEUE def dbus_ReleaseName(self, name, dbusCaller=None): caller = self.clients[dbusCaller] queue = self.busNames.get(name, None) if queue is None: return client.NAME_NON_EXISTENT owner = queue[0] if caller is not owner: return client.NAME_NOT_OWNER del queue[0] if caller.isConnected: self.sendSignal(caller, 'NameLost', 's', name) if queue: self.sendSignal(queue[0], 'NameAcquired', 's', name) else: del self.busNames[name] return client.NAME_RELEASED def dbus_ListQueuedOwners(self, name): queue = self.busNames.get(name, None) if queue: return [p.uniqueName for p in queue] else: raise DError( 'org.freedesktop.DBus.Error.NameHasNoOwner', 'Could not get owners of name \'%s\': no such name' % (name,), ) def dbus_AddMatch(self, rule, dbusCaller=None): caller = self.clients[dbusCaller] kwargs = { 'mtype': None, 'sender': None, 'interface': None, 'member': None, 'path': None, 'path_namespace': None, 'destination': None, 'args': None, 'arg_paths': None, 'arg0namespace': None, } for item in rule.split(','): k, v = item.split('=') value = v[1:-1] if k == 'type': k = 'mtype' if k in kwargs: kwargs[k] = value elif k.startswith('arg'): if k.endswith('path'): if kwargs['arg_paths'] is None: kwargs['arg_paths'] = [] kwargs['arg_paths'].append((int(k[3:-4]), value)) else: if kwargs['args'] is None: kwargs['args'] = [] kwargs['args'].append((int(k[3:]), value)) self.router.addMatch(caller.sendMessage, **kwargs) def dbus_GetNameOwner(self, busName): if busName.startswith(':'): conn = self.clients.get(busName, None) else: conn = self.busNames.get(busName, None) if conn: conn = conn[0] if conn is None: raise DError( "org.freedesktop.DBus.Error.NameHasNoOwner", "Could not get UID of name '%s': no such name" % (busName,), ) return conn.uniqueName def dbus_GetConnectionUnixUser(self, busName): if busName.startswith(':'): conn = self.clients.get(busName, None) else: conn = self.busNames.get(busName, None) if conn: conn = conn[0] if conn is None: raise DError( "org.freedesktop.DBus.Error.NameHasNoOwner", "Could not get UID of name '%s': no such name" % (busName,), ) try: import pwd return pwd.getpwnam(conn.username).pw_uid except BaseException: raise DError( 'org.freedesktop.DBus.Error', "Unable to determine unix user for bus '%s'" % (busName,), ) txdbus-1.1.0/txdbus/client.py000066400000000000000000000555021313301420000161550ustar00rootroot00000000000000 """ This is a DBus client connection implementation. It provides the ability to call methods on remote objects, receive signals, and export local objects and methods over the DBus bus. @author: Tom Cocagne """ import six from twisted.internet import defer, reactor from twisted.internet.error import ConnectError from twisted.internet.protocol import Factory import txdbus.protocol from txdbus import ( authentication, error, introspection, message, objects, router, ) # Constant return values for requestBusName NAME_ACQUIRED = 1 NAME_IN_QUEUE = 2 NAME_IN_USE = 3 NAME_ALREADY_OWNER = 4 # Constant return values for releaseBusName NAME_RELEASED = 1 NAME_NON_EXISTENT = 2 NAME_NOT_OWNER = 3 # unique constant for signature checking _NO_CHECK_RETURN = '__DBUS_NO_RETURN_VALUE' class DBusClientConnection (txdbus.protocol.BasicDBusProtocol): """ Client-side implementation of the DBus Protocol. @ivar authenticator: Class to used to authenticate connections @type authenticator: Class implementing L{protocol.IDBusAuthenticator} @ivar busName: Unique name of the connection to the bus @type busName: C{string} """ authenticator = authentication.ClientAuthenticator busName = None def connectionAuthenticated(self): """ Called by L{protocol.BasicDBusProtocol} when the DBus authentication has completed successfully. """ self.router = router.MessageRouter() self.match_rules = {} self.objHandler = objects.DBusObjectHandler(self) # serial_number => (deferred, delayed_timeout_cb | None): self._pendingCalls = {} self._dcCallbacks = [] d = self.callRemote( '/Hello', 'Hello', interface='org.freedesktop.DBus', destination='org.freedesktop.DBus', ) d.addCallbacks( self._cbGotHello, lambda err: self.factory._failed(err), ) def _cbGotHello(self, busName): """ Called in reply to the initial Hello remote method invocation """ self.busName = busName # print 'Connection Bus Name = ', self.busName self.factory._ok(self) def disconnect(self): """ Terminates the connection to the DBus Daemon """ self.transport.loseConnection() def connectionLost(self, reason): """ Called when the transport loses connection to the bus """ if self.busName is None: return for cb in self._dcCallbacks: cb(self, reason) for d, timeout in self._pendingCalls.values(): if timeout: timeout.cancel() d.errback(reason) self._pendingCalls = {} self.objHandler.connectionLost(reason) def notifyOnDisconnect(self, callback): """ @type callback: Callable object a that accepts a L{DBusClientConnection} and L{twisted.python.failure.Failure} @param callback: Function that will be called when the connection to the DBus session is lost. Arguments are the L{DBusClientConnection} that lost connection and the reason for the disconnect. This is the same value passed to L{twisted.internet.protocol.Protocol.connectionLost}) """ self._dcCallbacks.append(callback) def cancelNotifyOnDisconnect(self, callback): """ Cancels a callback previously registered with notifyOnDisconnect """ self._dcCallbacks.remove(callback) def exportObject(self, dbusObject): """ Exports an object over DBus @type dbusObject: An object implementing the L{objects.IDBusObject} interface @param dbusObject: Object to make available for remote access via DBus """ self.objHandler.exportObject(dbusObject) def unexportObject(self, objectPath): """ Stops exporting an object over DBus @type objectPath: C{string} @param objectPath: Object to stop exporting """ self.objHandler.unexportObject(objectPath) def getRemoteObject(self, busName, objectPath, interfaces=None, replaceKnownInterfaces=False): """ Creates a L{objects.RemoteDBusObject} instance to represent the specified DBus object. If explicit interfaces are not supplied, DBus object introspection will be used to obtain them automatically. @param interfaces: May be None, a single value, or a list of string interface names and/or instances of L{interface.DBusInterface}. If None or any of the specified interface names are unknown, full introspection will be attempted. If interfaces consists of solely of L{interface.DBusInterface} instances and/or known interface names, no introspection will be preformed. @rtype: L{twisted.internet.defer.Deferred} @returns: A deferred to a L{objects.RemoteDBusObject} instance representing the remote object """ return self.objHandler.getRemoteObject( busName, objectPath, interfaces, replaceKnownInterfaces, ) def delMatch(self, rule_id): """ Removes a message matching rule previously registered with addMatch """ rule = self.match_rules[rule_id] d = self.callRemote( '/org/freedesktop/DBus', 'RemoveMatch', interface='org.freedesktop.DBus', destination='org.freedesktop.DBus', body=[rule], signature='s', ) def ok(_): del self.match_rules[rule_id] self.router.delMatch(rule_id) d.addCallback(ok) return d def addMatch(self, callback, mtype=None, sender=None, interface=None, member=None, path=None, path_namespace=None, destination=None, arg=None, arg_path=None, arg0namespace=None): """ Creates a message matching rule, associates it with the specified callback function, and sends the match rule to the DBus daemon. The arguments to this function are exactly follow the DBus specification. Refer to the \"Message Bus Message Routing\" section of the DBus specification for details. @rtype: C{int} @returns: a L{Deferred} to an integer id that may be used to unregister the match rule """ l = [] def add(k, v): if v is not None: l.append("%s='%s'" % (k, v)) add('type', mtype) add('sender', sender) add('interface', interface) add('member', member) add('path', path) add('path_namespace', path_namespace) add('destination', destination) if arg: for idx, v in arg: add('arg%d' % (idx,), v) if arg_path: for idx, v in arg_path: add('arg%dpath' % (idx,), v) add('arg0namespace', arg0namespace) rule = ','.join(l) d = self.callRemote( '/org/freedesktop/DBus', 'AddMatch', interface='org.freedesktop.DBus', destination='org.freedesktop.DBus', body=[rule], signature='s', ) def ok(_): rule_id = self.router.addMatch( callback, mtype, sender, interface, member, path, path_namespace, destination, arg, arg_path, arg0namespace, ) self.match_rules[rule_id] = rule return rule_id d.addCallbacks(ok) return d def getNameOwner(self, busName): """ Calls org.freedesktop.DBus.GetNameOwner @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to the unique connection name owning the bus name """ d = self.callRemote( '/org/freedesktop/DBus', 'GetNameOwner', interface='org.freedesktop.DBus', signature='s', body=[busName], destination='org.freedesktop.DBus', ) return d def getConnectionUnixUser(self, busName): """ Calls org.freedesktop.DBus.GetConnectionUnixUser @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to the integer unix user id """ d = self.callRemote( '/org/freedesktop/DBus', 'GetConnectionUnixUser', interface='org.freedesktop.DBus', signature='s', body=[busName], destination='org.freedesktop.DBus', ) return d def listQueuedBusNameOwners(self, busName): """ Calls org.freedesktop.DBus.ListQueuedOwners @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to a list of unique bus names for connections queued for the name """ d = self.callRemote( '/org/freedesktop/DBus', 'ListQueuedOwners', interface='org.freedesktop.DBus', signature='s', body=[busName], destination='org.freedesktop.DBus', ) return d def releaseBusName(self, busName): """ Calls org.freedesktop.DBus.ReleaseName @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to an integer constant which will be one of NAME_RELEASED, NAME_NON_EXISTENT, NAME_NOT_OWNER """ d = self.callRemote( '/org/freedesktop/DBus', 'ReleaseName', interface='org.freedesktop.DBus', signature='s', body=[busName], destination='org.freedesktop.DBus', ) return d def requestBusName(self, newName, allowReplacement=False, replaceExisting=False, doNotQueue=True, errbackUnlessAcquired=True): """ Calls org.freedesktop.DBus.RequestName to request that the specified bus name be associated with the connection. @type newName: C{string} @param newName: Bus name to acquire @type allowReplacement: C{bool} @param allowReplacement: If True (defaults to False) and another application later requests this same name, the new requester will be given the name and this connection will lose ownership. @type replaceExisting: C{bool} @param replaceExisting: If True (defaults to False) and another application owns the name but specified allowReplacement at the time of the name acquisition, this connection will assume ownership of the bus name. @type doNotQueue: C{bool} @param doNotQueue: If True (defaults to True) the name request will fail if the name is currently in use. If False, the request will cause this connection to be queued for ownership of the requested name @type errbackUnlessAcquired: C{bool} @param errbackUnlessAcquired: If True (defaults to True) an L{twisted.python.failure.Failure} will be returned if the name is not acquired. @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to """ flags = 0 if allowReplacement: flags |= 0x1 if replaceExisting: flags |= 0x2 if doNotQueue: flags |= 0x4 d = self.callRemote( '/org/freedesktop/DBus', 'RequestName', interface='org.freedesktop.DBus', signature='su', body=[newName, flags], destination='org.freedesktop.DBus', ) def on_result(r): if errbackUnlessAcquired and not ( r == NAME_ACQUIRED or r == NAME_ALREADY_OWNER): raise error.FailedToAcquireName(newName, r) return r d.addCallback(on_result) return d def introspectRemoteObject(self, busName, objectPath, replaceKnownInterfaces=False): """ Calls org.freedesktop.DBus.Introspectable.Introspect @type busName: C{string} @param busName: Name of the bus containing the object @type objectPath: C{string} @param objectPath: Object Path to introspect @type replaceKnownInterfaces: C{bool} @param replaceKnownInterfaces: If True (defaults to False), the content of the introspected XML will override any pre-existing definitions of the contained interfaces. @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to a list of L{interface.DBusInterface} instances created from the content of the introspected XML description of the object's interface. """ d = self.callRemote( objectPath, 'Introspect', interface='org.freedesktop.DBus.Introspectable', destination=busName, ) def ok(xml_str): return introspection.getInterfacesFromXML( xml_str, replaceKnownInterfaces ) def err(e): raise error.IntrospectionFailed( 'Introspection Failed: ' + e.getErrorMessage() ) d.addCallbacks(ok, err) return d def _cbCvtReply(self, msg, returnSignature): """ Converts a remote method call reply message into an appropriate callback value. """ if msg is None: return None if returnSignature != _NO_CHECK_RETURN: if not returnSignature: if msg.signature: raise error.RemoteError( 'Unexpected return value signature') else: if not msg.signature or msg.signature != returnSignature: msg = 'Expected "%s". Received "%s"' % ( str(returnSignature), str(msg.signature)) raise error.RemoteError( 'Unexpected return value signature: %s' % (msg,)) if msg.body is None or len(msg.body) == 0: return None # if not ( # isinstance(msg.body[0], six.string_types) and # msg.body[0].startswith(' 1: # More than one UNIX_FD argument requires Twisted >= 17.1.0. minTxVersion = '17.1.0' if twisted.version.base() < minTxVersion: raise RuntimeError( 'Method %r with multiple UNIX_FD arguments requires ' 'Twisted >= %s' % (name, minTxVersion) ) class Signal (object): """ Represents a Signal declaration in a DBus Interface @type name: C{string} @param name: Signal name. This must be a valid DBus interface name @type arguments: C{string} @param arguments: DBus signature for signal arguments this must be either an empty string, indicating no arguments, or a valid signature string as defined by the DBus specification. """ __slots__ = ['name', 'nargs', 'sig'] def __init__(self, name, arguments=''): self.name = name self.nargs = -1 self.sig = arguments class Property (object): """ Represents a Property declaration in a DBus Interface. @type name: C{string} @param name: Property name. This must be a valid DBus interface name @type sig: C{string} @param sig: DBus signature for the property data type @type readable: C{bool} @param readable: True if the property is readable (defaults to True) @type writeable: C{bool} @param writeable: True if the property is writeable (defaults to False) @type emitsOnChange: C{bool} @param emitsOnChange: True if changes to the property result in a org.freedesktop.DBus.Properties.PropertiesChanged signal being emitted (defaults to True) """ __slots__ = ['name', 'sig', 'access', 'emits'] def __init__(self, name, sig, readable=True, writeable=False, emitsOnChange=True): self.name = name self.sig = sig if writeable and not readable: self.access = 'write' elif writeable and readable: self.access = 'readwrite' else: self.access = 'read' if emitsOnChange not in (True, False, 'invalidates'): raise TypeError( 'emitsOnChange parameter must be one of True, False, or ' '"invalidates"', ) if isinstance(emitsOnChange, bool): self.emits = 'true' if emitsOnChange else 'false' else: self.emits = emitsOnChange class DBusInterface (object): """ Represents a DBus Interface Definition. The introspectionXml property contains the full XML introspection description of the interface defined by an instance of this class. @type knownInterfaces: C{dict} @cvar knownInterfaces: Dictionary cache of interface name to L{DBusInterface} objects. This cache is used to avoid continual interface re-instrospection @type introspectionXml: C{string} @ivar introspectionXml: XML string containing the interface definition """ knownInterfaces = {} def __init__(self, name, *args, **kwargs): """ Method and fSignal instances to be included in the interface may be passed as additional positional parameters after 'name'. @keyword noRegister: If passed as a keyword argument this prevents the interface definition from being cached for future use """ self.name = name self.methods = {} self.signals = {} self.properties = {} self._xml = None for x in args: if isinstance(x, Method): self.addMethod(x) elif isinstance(x, Signal): self.addSignal(x) elif isinstance(x, Property): self.addProperty(x) else: raise TypeError('Invalid interface argument: %s' % (repr(x),)) if 'noRegister' not in kwargs: self.knownInterfaces[name] = self def addMethod(self, m): """ Adds a L{Method} to the interface """ if m.nargs == -1: m.nargs = len([a for a in marshal.genCompleteTypes(m.sigIn)]) m.nret = len([a for a in marshal.genCompleteTypes(m.sigOut)]) self.methods[m.name] = m self._xml = None def addSignal(self, s): """ Adds a L{Signal} to the interface """ if s.nargs == -1: s.nargs = len([a for a in marshal.genCompleteTypes(s.sig)]) self.signals[s.name] = s self._xml = None def addProperty(self, p): self.properties[p.name] = p self._xml = None def delMethod(self, name): """ Deletes the named method """ del self.methods[name] self._xml = None def delSignal(self, name): """ Deletes the named signal """ del self.signals[name] self._xml = None def delProperty(self, name): """ Deletes the named property """ del self.properties[name] self._xml = None def _getXml(self): # """ # @returns: an XML description of the interface # @rtype: C{string} # """ if self._xml is None: l = [] l.append(' ' % (self.name,)) k = sorted(six.iterkeys(self.methods)) for m in (self.methods[a] for a in k): l.append(' ' % (m.name,)) for arg_sig in marshal.genCompleteTypes(m.sigIn): l.append( ' ' % (arg_sig,)) for arg_sig in marshal.genCompleteTypes(m.sigOut): l.append( ' ' % (arg_sig,)) l.append(' ') k = sorted(six.iterkeys(self.signals)) for s in (self.signals[a] for a in k): l.append(' ' % (s.name,)) for arg_sig in marshal.genCompleteTypes(s.sig): l.append(' ' % (arg_sig,)) l.append(' ') k = list(six.iterkeys(self.properties)) k.sort() for p in (self.properties[a] for a in k): l.append( ' ' % (p.name, p.sig, p.access,)) l.append( ' ' % (p.emits,)) l.append(' ') l.append(' ') self._xml = '\n'.join(l) return self._xml introspectionXml = property(_getXml) # def printSelf(self): # """ # Debugging utility that prints the interface to standard output # """ # print('Interface: ', self.name) # # def sdict(d): # l = d.keys() # l.sort() # return [d[x] for x in l] # # for m in sdict(self.methods): # print(' Method:', m.name, ' in =', m.sigIn, ' out =', m.sigOut) # # for s in sdict(self.signals): # print(' Signals:', s.name, ' sig =', s.sig) txdbus-1.1.0/txdbus/introspection.py000066400000000000000000000133411313301420000175720ustar00rootroot00000000000000""" Provides support for DBus introspection @author: Tom Cocagne """ import xml.sax import xml.sax.handler from six.moves import cStringIO from txdbus import interface _dtd_decl = '''''' # noqa: # Line length is acceptable here ^ _intro = ''' ''' def generateIntrospectionXML(objectPath, exportedObjects): """ Generates the introspection XML for an object path or partial object path that matches exported objects. This allows for browsing the exported objects with tools such as d-feet. @rtype: C{string} """ l = [_dtd_decl] l.append('' % (objectPath,)) obj = exportedObjects.get(objectPath, None) if obj is not None: for i in obj.getInterfaces(): l.append(i.introspectionXml) l.append(_intro) # make sure objectPath ends with '/' to only get partial matches based on # the full path, not a part of a subpath if not objectPath.endswith('/'): objectPath += '/' matches = [] for path in exportedObjects.keys(): if path.startswith(objectPath): path = path[len(objectPath):].partition('/')[0] if path not in matches: matches.append(path) if obj is None and not matches: return None for m in matches: l.append('' % m) l.append('') return '\n'.join(l) # Returns a list of interfaces def getInterfacesFromXML(xmlStr, replaceKnownInterfaces=False): """ Parses the supplied Introspection XML string and returns a list of L{interface.DBusInerface} instances representing the XML interface definitions. @type replaceKnownInterfaces: C{bool} @param replaceKnownInterfaces: If true, pre-existing interface definitions will be replaced by the contents of the interfaces defined within the XML string @rtype: C{list} of L{interface.DBusInerface} """ handler = IntrospectionHandler(replaceKnownInterfaces) xmlStr = xmlStr.strip() if xmlStr.startswith('') + 1:] # xml.sax.parseString( xmlStr, handler ) p = xml.sax.make_parser() p.setFeature(xml.sax.handler.feature_validation, False) p.setFeature(xml.sax.handler.feature_external_ges, False) p.setContentHandler(handler) p.parse(cStringIO(xmlStr)) return handler.interfaces class IntrospectionHandler(xml.sax.handler.ContentHandler): """ XML Interface description handler """ def __init__(self, replaceKnownInterfaces=False): xml.sax.handler.ContentHandler.__init__(self) self.skipKnown = not replaceKnownInterfaces self.interfaces = [] self.member = None self.isMethod = None self.iface = None self.skip = False def startElement(self, name, attrs): if self.skip: return f = getattr(self, 'start_' + name, None) if f: f(attrs) def endElement(self, name): if self.skip and name != 'interface': return f = getattr(self, 'end_' + name, None) if f: f() def start_node(self, attrs): pass # ignore for now def start_interface(self, attrs): iname = str(attrs['name']) if iname in interface.DBusInterface.knownInterfaces and self.skipKnown: self.skip = True self.interfaces.append( interface.DBusInterface.knownInterfaces[iname] ) else: self.iface = interface.DBusInterface(iname) self.interfaces.append(self.iface) def end_interface(self): self.skip = False def start_method(self, attrs): self.member = interface.Method(str(attrs['name'])) self.member.nargs = 0 self.member.nret = 0 self.isMethod = True def end_method(self): self.iface.addMethod(self.member) def start_signal(self, attrs): self.member = interface.Signal(str(attrs['name'])) self.member.nargs = 0 self.isMethod = False def end_signal(self): self.iface.addSignal(self.member) def start_property(self, attrs): name = str(attrs['name']) sig = str(attrs['type']) rw = str(attrs['access']) readable = rw.lower() in ('read', 'readwrite') writeable = rw.lower() in ('write', 'readwrite') self.member = interface.Property(name, sig, readable, writeable) self.isMethod = False def start_annotation(self, attrs): if attrs['name'] == 'org.freedesktop.DBus.Property.EmitsChangedSignal': self.member.emits = str(attrs['value']) in ('true', 'invalidates') def end_property(self): self.iface.addProperty(self.member) def start_arg(self, attrs): t = str(attrs['type']) if self.isMethod: if attrs['direction'] == 'in': self.member.nargs += 1 self.member.sigIn = self.member.sigIn + t else: self.member.nret += 1 self.member.sigOut = self.member.sigOut + t else: self.member.nargs += 1 self.member.sig = self.member.sig + t txdbus-1.1.0/txdbus/marshal.py000066400000000000000000000616101313301420000163230ustar00rootroot00000000000000""" Provides data marshalling to and from the DBus wire format @author: Tom Cocagne """ import codecs import re import struct import six from txdbus.error import MarshallingError invalid_obj_path_re = re.compile('[^a-zA-Z0-9_/]') if_re = re.compile('[^A-Za-z0-9_.]') bus_re = re.compile('[^A-Za-z0-9_.\-:]') mbr_re = re.compile('[^A-Za-z0-9_]') dot_digit_re = re.compile('\.\d') # Name Type code Alignment dbus_types = [('BYTE', 'y', 1), ('BOOLEAN', 'b', 4), ('INT16', 'n', 2), ('UINT16', 'q', 2), ('INT32', 'i', 4), ('UINT32', 'u', 4), ('INT64', 'x', 8), ('UINT64', 't', 8), ('DOUBLE', 'd', 8), ('STRING', 's', 4), # (4-byte align for length) ('OBJECT_PATH', 'o', 4), # (4-byte align for length) ('SIGNATURE', 'g', 1), ('ARRAY', 'a', 4), # (4-byte align for length) ('STRUCT', '(', 8), ('VARIANT', 'v', 1), # (1-byte align for signature) ('DICT_ENTRY', '{', 8), ('UNIX_FD', 'h', 4) ] class Byte(int): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'y' class Boolean(int): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'b' class Int16(int): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'n' class UInt16(int): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'q' class Int32(int): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'i' class UInt32(int): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'u' class Int64(int): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'x' class UInt64(int): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 't' class Signature (str): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'g' class ObjectPath (str): """ Used during Variant serialization to ensure that this type is encoded rather than the generic Python type """ dbusSignature = 'o' variantClassMap = { 'y': Byte, 'b': Boolean, 'n': Int16, 'q': UInt16, 'i': Int32, 'u': UInt32, 'x': Int64, 't': UInt64, 'g': Signature, 'o': ObjectPath, } def validateObjectPath(p): """ Ensures that the provided object path conforms to the DBus standard. Throws a L{error.MarshallingError} if non-conformant @type p: C{string} @param p: A DBus object path """ if not p.startswith('/'): raise MarshallingError('Object paths must begin with a "/"') if len(p) > 1 and p[-1] == '/': raise MarshallingError('Object paths may not end with "/"') if '//' in p: raise MarshallingError('"//" is not allowed in object paths"') if invalid_obj_path_re.search(p): raise MarshallingError('Invalid characters contained in object path') def validateInterfaceName(n): """ Verifies that the supplied name is a valid DBus Interface name. Throws an L{error.MarshallingError} if the format is invalid @type n: C{string} @param n: A DBus interface name """ try: if '.' not in n: raise Exception('At least two components required') if '..' in n: raise Exception('".." not allowed in interface names') if len(n) > 255: raise Exception('Name exceeds maximum length of 255') if n[0] == '.': raise Exception('Names may not begin with a "."') if n[0].isdigit(): raise Exception('Names may not begin with a digit') if if_re.search(n): raise Exception( 'Names contains a character outside the set [A-Za-z0-9_.]') if dot_digit_re.search(n): raise Exception( 'No components of an interface name may begin with a digit') except Exception as e: raise MarshallingError('Invalid interface name "%s": %s' % (n, str(e))) def validateErrorName(n): try: validateInterfaceName(n) except MarshallingError as e: raise MarshallingError(str(e).replace('interface', 'error', 1)) def validateBusName(n): """ Verifies that the supplied name is a valid DBus Bus name. Throws an L{error.MarshallingError} if the format is invalid @type n: C{string} @param n: A DBus bus name """ try: if '.' not in n: raise Exception('At least two components required') if '..' in n: raise Exception('".." not allowed in bus names') if len(n) > 255: raise Exception('Name exceeds maximum length of 255') if n[0] == '.': raise Exception('Names may not begin with a "."') if n[0].isdigit(): raise Exception('Names may not begin with a digit') if bus_re.search(n): raise Exception( 'Names contains a character outside the set [A-Za-z0-9_.\-:]') if not n[0] == ':' and dot_digit_re.search(n): raise Exception( 'No coponents of an interface name may begin with a digit') except Exception as e: raise MarshallingError('Invalid bus name "%s": %s' % (n, str(e))) def validateMemberName(n): """ Verifies that the supplied name is a valid DBus member name. Throws an L{error.MarshallingError} if the format is invalid @type n: C{string} @param n: A DBus member name """ try: if len(n) < 1: raise Exception('Name must be at least one byte in length') if len(n) > 255: raise Exception('Name exceeds maximum length of 255') if n[0].isdigit(): raise Exception('Names may not begin with a digit') if mbr_re.search(n): raise Exception( 'Names contains a character outside the set [A-Za-z0-9_]') except Exception as e: raise MarshallingError('Invalid member name "%s": %s' % (n, str(e))) # XXX: This could be made *much* smarter (handle objects and recursive # structures) def sigFromPy(pobj): """ Returns the DBus signature type for the argument. If the argument is an instance of one of the type wrapper classes, the exact type signature corresponding to the wrapper class will be used. If the object has a variable named 'dbusSignature', the value of that variable will be used. Otherwise, a generic type will be used (i.e "i" for a Python int) @rtype: C{string} @returns: The DBus signature for the supplied Python object """ sig = getattr(pobj, 'dbusSignature', None) if sig is not None: return sig elif isinstance(pobj, bool): return 'b' elif isinstance(pobj, int): return 'i' elif isinstance(pobj, six.integer_types): return 'x' elif isinstance(pobj, float): return 'd' elif isinstance(pobj, six.string_types): return 's' elif isinstance(pobj, bytearray): return 'ay' elif isinstance(pobj, list): vtype = type(pobj[0]) same = True for v in pobj[1:]: if not isinstance(v, vtype): same = False if same: return 'a' + sigFromPy(pobj[0]) else: return 'av' elif isinstance(pobj, tuple): return '(' + ''.join(sigFromPy(e) for e in pobj) + ')' elif isinstance(pobj, dict): same = True vtype = None for k, v in six.iteritems(pobj): if vtype is None: vtype = type(v) elif not isinstance(v, vtype): same = False if same: return 'a{' + sigFromPy(k) + sigFromPy(v) + '}' else: return 'a{' + sigFromPy(k) + 'v}' else: raise MarshallingError( 'Invalid Python type for variant: ' + repr(pobj)) # ------------------------------------------------------------------------ # Marshalling Functions # Padding: # - All data types must be padded to the correct alignment # - All padding bytes must be nul # padding = { 0: b'\0' * 0, 1: b'\0' * 1, 2: b'\0' * 2, 3: b'\0' * 3, 4: b'\0' * 4, 5: b'\0' * 5, 6: b'\0' * 6, 7: b'\0' * 7, } def genpad(align): return lambda x: padding[x % align and (align - x % align) or 0] pad = {} for name, tcode, align in dbus_types: pad[tcode] = genpad(align) pad['header'] = genpad(8) # ------------------------------------------------------------------------ # Signature Generator/Iterator # def genCompleteTypes(compoundSig): """ Generator function used to iterate over each complete, top-level type contained in in a signature. Ex:: "iii" => [ 'i', 'i', 'i' ] "i(ii)i" => [ 'i', '(ii)', 'i' ] "i(i(ii))i" => [ 'i', '(i(ii))', 'i' ] """ i = 0 end = len(compoundSig) def find_end(idx, b, e): depth = 1 while idx < end: subc = compoundSig[idx] if subc == b: depth += 1 elif subc == e: depth -= 1 if depth == 0: return idx idx += 1 while i < end: c = compoundSig[i] if c == '(': x = find_end(i + 1, '(', ')') yield compoundSig[i:x + 1] i = x elif c == '{': x = find_end(i + 1, '{', '}') yield compoundSig[i:x + 1] i = x elif c == 'a': g = genCompleteTypes(compoundSig[i + 1:]) ct = six.next(g) i += len(ct) yield 'a' + ct else: yield c i += 1 # ------------------------------------------------------------------------ # Marshalling Functions # General: # - All values must be padded to proper alignment # - Pad bytes must be zero # # BOOLEAN: # - Only 1 & 0 are valid # # DICT_ENTRY: # - Identical to STRUCT # # Message: # - Max length of header, body and all padding is 2^27 # # Message Header: # - Must be padded to a multiple of 8 bytes # - Fixed signature: "yyyyuua(yv)" # * 1 BYTE: Endian flag. 'l' for little, 'B' for big # * 2 BYTE: Message type enum # * 3 BYTE: Bit Flags # * 4 BYTE: Major protocol version (1 currently) # * 5 UINT32: Body Length (begins after header padding) # * 6 UINT32: Message serial number (must not be zero) # * 7 Array: zero or more header fields. Msg type determines # which entries are required # # Message Body: # - Begins on 8-byte boundary # - Not padded to a required byte alignment # # def marshal_byte(ct, var, start_byte, lendian, oobFDs): return 1, [struct.pack(lendian and 'B', var)] def marshal_boolean(ct, var, start_byte, lendian, oobFDs): return 4, [struct.pack(lendian and 'I', 1 if var else 0)] def marshal_int16(ct, var, start_byte, lendian, oobFDs): return 2, [struct.pack(lendian and 'h', var)] def marshal_uint16(ct, var, start_byte, lendian, oobFDs): return 2, [struct.pack(lendian and 'H', var)] def marshal_int32(ct, var, start_byte, lendian, oobFDs): return 4, [struct.pack(lendian and 'i', var)] def marshal_uint32(ct, var, start_byte, lendian, oobFDs): return 4, [struct.pack(lendian and 'I', var)] def marshal_int64(ct, var, start_byte, lendian, oobFDs): return 8, [struct.pack(lendian and 'q', var)] def marshal_uint64(ct, var, start_byte, lendian, oobFDs): return 8, [struct.pack(lendian and 'Q', var)] def marshal_double(ct, var, start_byte, lendian, oobFDs): return 8, [struct.pack(lendian and 'd', var)] def marshal_unix_fd(ct, var, start_byte, lendian, oobFDs): index = len(oobFDs) oobFDs.append(var) return 4, [struct.pack(lendian and 'I', index)] # STRING: # - *must* be valid UTF-8, nul terminated with no embedded nuls # format: # 1 - UINT32 length in bytes (excluding terminating nul) # 2 - string data (no embedded nuls) # 3 - terminating nul byte # def marshal_string(ct, var, start_byte, lendian, oobFDs): if not isinstance(var, six.string_types): raise MarshallingError('Required string. Received: ' + repr(var)) if var.find('\0') != -1: raise MarshallingError( 'Embedded nul characters are not allowed within DBus strings') var = codecs.encode(var, 'utf-8') return 4 + \ len(var) + \ 1, [struct.pack(lendian and 'I', len(var)), var, b'\0'] # OBJECT_PATH: # - Identical to string # def marshal_object_path(ct, var, start_byte, lendian, oobFDs): validateObjectPath(var) return marshal_string(ct, var, start_byte, lendian, oobFDs) # SIGNATURE: # - Ends with nul byte # - List of complete types. No partial types permitted # - Max signature length is 255 # format: # 1 - Single byte length # 2 - Valid signature string # 3 - terminating nul byte def marshal_signature(ct, var, start_byte, lendian, oobFDs): # XXX validate signature var = codecs.encode(var, 'ascii') return 2 + \ len(var), [struct.pack(lendian and 'B', len(var)), var, b'\0'] # ARRAY: # - Max length is 2^26 # format: # 1 - UINT32 length of array data (does not include alignment padding) # 2 - Padding to required alignment of contained data type # 3 - each array element def marshal_array(ct, var, start_byte, lendian, oobFDs): chunks = [] data_len = 0 tsig = ct[1:] # strip of leading 'a' tcode = tsig[0] # type of array element start_byte += 4 # for array size initial_padding = pad[tcode](start_byte) if initial_padding: start_byte += len(initial_padding) chunks.append(initial_padding) if isinstance(var, (list, tuple, bytearray)): arr_list = var elif isinstance(var, dict): arr_list = [tpl for tpl in six.iteritems(var)] else: raise MarshallingError( 'List, Tuple, Bytearray, or Dictionary required for DBus array. ' ' Received: ' + repr(var) ) for item in arr_list: padding = pad[tcode](start_byte) if padding: start_byte += len(padding) data_len += len(padding) chunks.append(padding) nbytes, vchunks = marshallers[tcode]( tsig, item, start_byte, lendian, oobFDs) start_byte += nbytes data_len += nbytes chunks.extend(vchunks) chunks.insert(0, struct.pack(lendian and 'I', data_len)) return 4 + len(initial_padding) + data_len, chunks # STRUCT: # - Must start on 8 byte boundary # - Content consists of each field marshaled in sequence # def marshal_struct(ct, var, start_byte, lendian, oobFDs): return marshal(ct[1:-1], var, start_byte, lendian, oobFDs) marshal_dictionary = marshal_struct # VARIANT: # - Signature must contain only a single, complete type # format: # 1 - Marshaled SIGNATURE # 2 - Any required padding to align the type specified in the signature # 3 - Marshaled value def marshal_variant(ct, var, start_byte, lendian, oobFDs): # XXX: ensure only a single, complete type is in the siguature bstart = start_byte vsig = sigFromPy(var) nbytes, chunks = marshal_signature( ct, sigFromPy(var), start_byte, lendian, oobFDs) start_byte += nbytes padding = pad[vsig[0]](start_byte) if padding: start_byte += len(padding) chunks.append(padding) rnbytes, rchunks = marshal(vsig, [var], start_byte, lendian) start_byte += rnbytes chunks.extend(rchunks) return start_byte - bstart, chunks marshallers = { 'y': marshal_byte, 'b': marshal_boolean, 'n': marshal_int16, 'q': marshal_uint16, 'i': marshal_int32, 'u': marshal_uint32, 'x': marshal_int64, 't': marshal_uint64, 'd': marshal_double, 's': marshal_string, 'o': marshal_object_path, 'g': marshal_signature, 'a': marshal_array, '(': marshal_struct, 'v': marshal_variant, '{': marshal_dictionary, 'h': marshal_unix_fd, } def marshal(compoundSignature, variableList, startByte=0, lendian=True, oobFDs=None): """ Encodes the Python objects in variableList into the DBus wire-format matching the supplied compoundSignature. This function retuns a list of binary strings is rather than a single string to simplify the recursive marshalling algorithm. A single string may be easily obtained from the result via: ''.join(list_of_binary_strings) Any UNIX_FD 'h' type is encoded per spec and the respective FD appended to oobFDs which should be supplied as an empty list. @type compoundSignature: C{string} @param compoundSignature: DBus signature specifying the types of the variables to encode @type variableList: C{list} @param variableList: List of variables to encode (length of the list must exactly match the number of variables specified in compoundSignature @type startByte: C{int} @param startByte: Used during recursive marshalling to ensure data alignment requirements are met @type lendian: C{bool} @param lendian: True if the data should be serialized in little-endian format @returns: (number_of_encoded_bytes, list_of_binary_strings) """ chunks = [] bstart = startByte if hasattr(variableList, 'dbusOrder'): order = getattr(variableList, 'dbusOrder') variableList = [getattr(variableList, attr_name) for attr_name in order] for ct, var in zip(genCompleteTypes(compoundSignature), variableList): tcode = ct[0] padding = pad[tcode](startByte) if padding: startByte += len(padding) chunks.append(padding) nbytes, vchunks = marshallers[tcode]( ct, var, startByte, lendian, oobFDs) startByte += nbytes chunks.extend(vchunks) return startByte - bstart, chunks # ------------------------------------------------------------------------ # ------------------------------------------------------------------------ # Unmarshalling Functions # ------------------------------------------------------------------------ # ------------------------------------------------------------------------ def unmarshal_byte(ct, data, offset, lendian, oobFDs): return 1, struct.unpack_from(lendian and 'B', data, offset)[0] def unmarshal_boolean(ct, data, offset, lendian, oobFDs): return 4, struct.unpack_from( lendian and 'I', data, offset)[0] != 0 def unmarshal_int16(ct, data, offset, lendian, oobFDs): return 2, struct.unpack_from(lendian and 'h', data, offset)[0] def unmarshal_uint16(ct, data, offset, lendian, oobFDs): return 2, struct.unpack_from(lendian and 'H', data, offset)[0] def unmarshal_int32(ct, data, offset, lendian, oobFDs): return 4, struct.unpack_from(lendian and 'i', data, offset)[0] def unmarshal_uint32(ct, data, offset, lendian, oobFDs): return 4, struct.unpack_from(lendian and 'I', data, offset)[0] def unmarshal_int64(ct, data, offset, lendian, oobFDs): return 8, struct.unpack_from(lendian and 'q', data, offset)[0] def unmarshal_uint64(ct, data, offset, lendian, oobFDs): return 8, struct.unpack_from(lendian and 'Q', data, offset)[0] def unmarshal_double(ct, data, offset, lendian, oobFDs): return 8, struct.unpack_from(lendian and 'd', data, offset)[0] def unmarshal_unix_fd(ct, data, offset, lendian, oobFDs): index = struct.unpack_from(lendian and 'I', data, offset)[0] try: fd = oobFDs[index] except IndexError: fd = None return 4, fd # STRING: # - *must* be valid UTF-8, nul terminated with no embedded nuls # format: # 1 - UINT32 length in bytes (excluding terminating nul) # 2 - string data (no embedded nuls) # 3 - terminating nul byte # def unmarshal_string(ct, data, offset, lendian, oobFDs): slen = struct.unpack_from(lendian and 'I', data, offset)[0] s = codecs.decode(data[offset + 4: offset + 4 + slen], 'utf-8') return 4 + slen + 1, s # OBJECT_PATH: # - Identical to string # unmarshal_object_path = unmarshal_string # SIGNATURE: # - Ends with nul byte # - List of complete types. No partial types permitted # - Max signature length is 255 # format: # 1 - Single byte length # 2 - Valid signature string # 3 - terminating nul byte def unmarshal_signature(ct, data, offset, lendian, oobFDs): slen = struct.unpack_from(lendian and 'B', data, offset)[0] s = codecs.decode(data[offset + 1: offset + 1 + slen], 'ascii') return 1 + slen + 1, s # ARRAY: # - Max length is 2^26 # format: # 1 - UINT32 length of array data (does not include alignment padding) # 2 - Padding to required alignment of contained data type # 3 - each array element def unmarshal_array(ct, data, offset, lendian, oobFDs): start_offset = offset values = [] data_len = struct.unpack_from(lendian and 'I', data, offset)[0] tsig = ct[1:] # strip of leading 'a' tcode = tsig[0] # type of array element offset += 4 # 4-byte data length offset += len(pad[tcode](offset)) # padding length end_offset = offset + data_len while offset < end_offset: offset += len(pad[tcode](offset)) nbytes, value = unmarshallers[tcode]( tsig, data, offset, lendian, oobFDs) offset += nbytes values.append(value) if not offset == end_offset: raise MarshallingError('Invalid array encoding') if tcode == '{': d = {} for item in values: d[item[0]] = item[1] values = d return offset - start_offset, values # STRUCT: # - Must start on 8 byte boundary # - Content consists of each field marshaled in sequence # def unmarshal_struct(ct, data, offset, lendian, oobFDs): return unmarshal(ct[1:-1], data, offset, lendian, oobFDs) unmarshal_dictionary = unmarshal_struct # VARIANT: # - Signature must contain only a single, complete type # format: # 1 - Marshaled SIGNATURE # 2 - Any required padding to align the type specified in the signature # 3 - Marshaled value def unmarshal_variant(ct, data, offset, lendian, oobFDs): # XXX: ensure only a single, complete type is in the siguature start_offset = offset nsig, vsig = unmarshal_signature(ct, data, offset, lendian, oobFDs) offset += nsig offset += len(pad[vsig[0]](offset)) nvar, value = unmarshal(vsig, data, offset, lendian, oobFDs) offset += nvar return offset - start_offset, value[0] unmarshallers = { 'y': unmarshal_byte, 'b': unmarshal_boolean, 'n': unmarshal_int16, 'q': unmarshal_uint16, 'i': unmarshal_int32, 'u': unmarshal_uint32, 'x': unmarshal_int64, 't': unmarshal_uint64, 'd': unmarshal_double, 's': unmarshal_string, 'o': unmarshal_object_path, 'g': unmarshal_signature, 'a': unmarshal_array, '(': unmarshal_struct, 'v': unmarshal_variant, '{': unmarshal_dictionary, 'h': unmarshal_unix_fd, } def unmarshal(compoundSignature, data, offset=0, lendian=True, oobFDs=None): """ Unmarshals DBus encoded data. @type compoundSignature: C{string} @param compoundSignature: DBus signature specifying the encoded value types @type data: C{string} @param data: Binary data @type offset: C{int} @param offset: Offset within data at which data for compoundSignature starts (used during recursion) @type lendian: C{bool} @param lendian: True if data is encoded in little-endian format @returns: (number_of_bytes_decoded, list_of_values) """ values = [] start_offset = offset for ct in genCompleteTypes(compoundSignature): tcode = ct[0] offset += len(pad[tcode](offset)) nbytes, value = unmarshallers[tcode](ct, data, offset, lendian, oobFDs) offset += nbytes values.append(value) return offset - start_offset, values txdbus-1.1.0/txdbus/message.py000066400000000000000000000273041313301420000163220ustar00rootroot00000000000000""" Module to represent DBus Messages @author: Tom Cocagne """ from txdbus import error, marshal _headerFormat = 'yyyyuua(yv)' class DBusMessage (object): """ Abstract base class for DBus messages @ivar _messageType: C{int} DBus message type @ivar expectReply: True if a method return message is expected @ivar autoStart: True if a service should be auto started by this message @ivar signature: C{str} DBus signature describing the body content @ivar endian: C{int} containing endian code: Little endian = ord('l'). Big endian is ord('B'). Defaults to little-endian @ivar bodyLength: Length of the body in bytes @ivar serial: C{int} message serial number @ivar rawMessage: Raw binary message data @ivar rawHeader: Raw binary message header @ivar rawBody: Raw binary message body @ivar interface: C{str} DBus interface name @ivar path: C{str} DBus object path @ivar sender: C{str} DBus bus name for sending connection @ivar destination: C{str} DBus bus name for destination connection """ _maxMsgLen = 2**27 _nextSerial = 1 _protocolVersion = 1 # Overriden by subclasses _messageType = 0 _headerAttrs = None # [(attr_name, code, is_required), ...] # Set prior to marshal or during/after unmarshalling expectReply = True autoStart = True signature = None body = None # Set during marshalling/unmarshalling endian = ord('l') bodyLength = 0 serial = None headers = None rawMessage = None # Required/Optional interface = None path = None # optional sender = None destination = None # def printSelf(self): # mtype = { 1 : 'MethodCall', # 2 : 'MethodReturn', # 3 : 'Error', # 4 : 'Signal' } # print mtype[self._messageType] # keys = self.__dict__.keys() # keys.sort() # for a in keys: # if not a.startswith('raw'): # print ' %s = %s' % (a.ljust(15), str(getattr(self,a))) def _marshal(self, newSerial=True, oobFDs=None): """ Encodes the message into binary format. The resulting binary message is stored in C{self.rawMessage} """ flags = 0 if not self.expectReply: flags |= 0x1 if not self.autoStart: flags |= 0x2 # may be overriden below, depending on oobFDs _headerAttrs = self._headerAttrs # marshal body before headers to know if the 'unix_fd' header is needed if self.signature: binBody = b''.join( marshal.marshal( self.signature, self.body, oobFDs=oobFDs )[1] ) if oobFDs: # copy class based _headerAttrs to add a unix_fds header this # time _headerAttrs = list(self._headerAttrs) _headerAttrs.append(('unix_fds', 9, False)) self.unix_fds = len(oobFDs) else: binBody = b'' self.headers = [] for attr_name, code, is_required in _headerAttrs: hval = getattr(self, attr_name, None) if hval is not None: if attr_name == 'path': hval = marshal.ObjectPath(hval) elif attr_name == 'signature': hval = marshal.Signature(hval) elif attr_name == 'unix_fds': hval = marshal.UInt32(hval) self.headers.append([code, hval]) self.bodyLength = len(binBody) if newSerial: self.serial = DBusMessage._nextSerial DBusMessage._nextSerial += 1 binHeader = b''.join(marshal.marshal( _headerFormat, [ self.endian, self._messageType, flags, self._protocolVersion, self.bodyLength, self.serial, self.headers ], lendian=self.endian == ord('l') )[1]) headerPadding = marshal.pad['header'](len(binHeader)) self.rawHeader = binHeader self.rawPadding = headerPadding self.rawBody = binBody self.rawMessage = b''.join([binHeader, headerPadding, binBody]) if len(self.rawMessage) > self._maxMsgLen: raise error.MarshallingError( 'Marshalled message exceeds maximum message size of %d' % (self._maxMsgLen,), ) class MethodCallMessage (DBusMessage): """ A DBus Method Call Message """ _messageType = 1 _headerAttrs = [ ('path', 1, True), ('interface', 2, False), ('member', 3, True), ('destination', 6, False), ('sender', 7, False), ('signature', 8, False) ] def __init__(self, path, member, interface=None, destination=None, signature=None, body=None, expectReply=True, autoStart=True, oobFDs=None): """ @param path: C{str} DBus object path @param member: C{str} Member name @param interface: C{str} DBus interface name or None @param destination: C{str} DBus bus name for message destination or None @param signature: C{str} DBus signature string for encoding C{self.body} @param body: C{list} of python objects to encode. Objects must match the C{self.signature} @param expectReply: True if a Method Return message should be sent in reply to this message @param autoStart: True if the Bus should auto-start a service to handle this message if the service is not already running. """ marshal.validateMemberName(member) if interface: marshal.validateInterfaceName(interface) if destination: marshal.validateBusName(destination) if path == '/org/freedesktop/DBus/Local': raise error.MarshallingError( '/org/freedesktop/DBus/Local is a reserved path') self.path = path self.member = member self.interface = interface self.destination = destination self.signature = signature self.body = body self.expectReply = expectReply self.autoStart = autoStart self._marshal(oobFDs=oobFDs) class MethodReturnMessage (DBusMessage): """ A DBus Method Return Message """ _messageType = 2 _headerAttrs = [ ('reply_serial', 5, True), ('destination', 6, False), ('sender', 7, False), ('signature', 8, False), ] def __init__(self, reply_serial, body=None, destination=None, signature=None): """ @param reply_serial: C{int} serial number this message is a reply to @param destination: C{str} DBus bus name for message destination or None @param signature: C{str} DBus signature string for encoding C{self.body} @param body: C{list} of python objects to encode. Objects must match the C{self.signature} """ if destination: marshal.validateBusName(destination) self.reply_serial = marshal.UInt32(reply_serial) self.destination = destination self.signature = signature self.body = body self._marshal() class ErrorMessage (DBusMessage): """ A DBus Error Message """ _messageType = 3 _headerAttrs = [ ('error_name', 4, True), ('reply_serial', 5, True), ('destination', 6, False), ('sender', 7, False), ('signature', 8, False), ] def __init__(self, error_name, reply_serial, destination=None, signature=None, body=None, sender=None): """ @param error_name: C{str} DBus error name @param reply_serial: C{int} serial number this message is a reply to @param destination: C{str} DBus bus name for message destination or None @param signature: C{str} DBus signature string for encoding C{self.body} @param body: C{list} of python objects to encode. Objects must match the C{self.signature} @param sender: C{str} name of the originating Bus connection """ if destination: marshal.validateBusName(destination) marshal.validateInterfaceName(error_name) self.error_name = error_name self.reply_serial = marshal.UInt32(reply_serial) self.destination = destination self.signature = signature self.body = body self.sender = sender self._marshal() class SignalMessage (DBusMessage): """ A DBus Signal Message """ _messageType = 4 _headerAttrs = [ ('path', 1, True), ('interface', 2, True), ('member', 3, True), ('destination', 6, False), ('sender', 7, False), ('signature', 8, False), ] def __init__(self, path, member, interface, destination=None, signature=None, body=None): """ @param path: C{str} DBus object path of the object sending the signal @param member: C{str} Member name @param interface: C{str} DBus interface name or None @param destination: C{str} DBus bus name for message destination or None @param signature: C{str} DBus signature string for encoding C{self.body} @param body: C{list} of python objects to encode. Objects must match the C{self.signature} """ marshal.validateMemberName(member) marshal.validateInterfaceName(interface) if destination: marshal.validateBusName(destination) self.path = path self.member = member self.interface = interface self.destination = destination self.signature = signature self.body = body self._marshal() _mtype = { 1: MethodCallMessage, 2: MethodReturnMessage, 3: ErrorMessage, 4: SignalMessage, } _hcode = { 1: 'path', 2: 'interface', 3: 'member', 4: 'error_name', 5: 'reply_serial', 6: 'destination', 7: 'sender', 8: 'signature', 9: 'unix_fds', } def parseMessage(rawMessage, oobFDs): """ Parses the raw binary message and returns a L{DBusMessage} subclass. Unmarshalling DBUS 'h' (UNIX_FD) gets the FDs from the oobFDs list. @type rawMessage: C{str} @param rawMessage: Raw binary message to parse @rtype: L{DBusMessage} subclass @returns: The L{DBusMessage} subclass corresponding to the contained message """ lendian = rawMessage[0] == b'l'[0] nheader, hval = marshal.unmarshal( _headerFormat, rawMessage, 0, lendian, oobFDs, ) messageType = hval[1] if messageType not in _mtype: raise error.MarshallingError( 'Unknown Message Type: ' + str(messageType) ) m = object.__new__(_mtype[messageType]) m.rawHeader = rawMessage[:nheader] npad = nheader % 8 and (8 - nheader % 8) or 0 m.rawPadding = rawMessage[nheader: nheader + npad] m.rawBody = rawMessage[nheader + npad:] m.serial = hval[5] for code, v in hval[6]: try: setattr(m, _hcode[code], v) except KeyError: pass if m.signature: nbytes, m.body = marshal.unmarshal( m.signature, m.rawBody, lendian=lendian, oobFDs=oobFDs, ) return m txdbus-1.1.0/txdbus/objects.py000066400000000000000000000766411313301420000163370ustar00rootroot00000000000000""" This module provides classes for managing local and remote DBus objects @author: Tom Cocagne """ import inspect import weakref import six from twisted.internet import defer from zope.interface import implementer, Interface from txdbus import error, interface, introspection, marshal, message def isSignatureValid(expected, received): """ Verifies that the received signature matches the expected value """ if expected: if not received or expected != received: return False else: if received: return False return True def dbusMethod(interfaceName, methodName): def deco(method): method._dbusInterface = interfaceName method._dbusMethod = methodName return method return deco class DBusProperty(object): def __init__(self, dbusPropertyName, interface=None): self.pname = dbusPropertyName self.key = None self.interface = interface # Interface name self.attr_name = None # set by DBusObject self.iprop = None # points to interface.Property instance def __get__(self, instance, owner): if not hasattr(instance, '_dbusProperties'): instance._dbusProperties = {} if self.interface is None: # Force object to set it instance._getProperty('', self.pname) if self.key is None: self.key = self.interface + self.pname return instance._dbusProperties.get(self.key, None) def __set__(self, instance, value): if not hasattr(instance, '_dbusProperties'): instance._dbusProperties = {} if self.iprop is None: # Force object to set it instance._getProperty('', self.pname) if self.key is None: self.key = self.interface + self.pname instance._dbusProperties[self.key] = value if self.iprop.emits == 'true': instance.emitSignal( 'PropertiesChanged', self.interface, {self.pname: value}, [], ) def __delete__(self, instance): raise AttributeError('DBus properties cannot be deleted') class RemoteDBusObject (object): """ Provides local representation of a remote DBus object. """ _disconnectCBs = None _signalRules = None def __init__(self, objHandler, busName, objectPath, interfaces): """ @type objHandler: L{DBusObjectHandler} @param objHandler: L{DBusObjectHandler} managing this instance @type busName: C{string} @param busName: Name of the bus owning the remote object @type objectPath: C{string} @param objectPath: Path of the remote object @type interfaces: List of C{interface.DBusInterface} @param interfaces: List of interfaces supported by the remote object """ self.objHandler = objHandler self.busName = busName self.objectPath = objectPath self.interfaces = interfaces def notifyOnDisconnect(self, callback): """ Registers a callback that will be called when the DBus connection underlying the remote object is lost @type callback: Callable object accepting a L{RemoteDBusObject} and L{twisted.python.failure.Failure} @param callback: Function that will be called when the connection to the DBus session is lost. Arguments are the L{RemoteDBusObject} instance and reason for the disconnect (the same value passed to L{twisted.internet.protocol.Protocol.connectionLost}) """ if self._disconnectCBs is None: self._disconnectCBs = [] self._disconnectCBs.append(callback) def cancelNotifyOnDisconnect(self, callback): """ Cancels a callback previously registered with notifyOnDisconnect """ if self._disconnectCBs: self._disconnectCBs.remove(callback) def connectionLost(self, reason): """ Called by the L{DBusObjectHandler} when the connection is lost """ if self._disconnectCBs: for cb in self._disconnectCBs: cb(self, reason) def notifyOnSignal(self, signalName, callback, interface=None): """ Informs the DBus daemon of the process's interest in the specified signal and registers the callback function to be called when the signal arrives. Multiple callbacks may be registered. @type signalName: C{string} @param signalName: Name of the signal to register the callback for @type callback: Callable object @param callback: Callback to be called on signal arrival. The callback will be passed signals arguments as positional function arguments. @type interface: C{string} @param interface: Optional DBus interface emitting the signal. This is only needed if more than one interface shares a signal with the same name @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to an integer rule_id that may be passed to cancelSignalNotification to prevent the delivery of future signals to the callback """ iface = None signal = None for i in self.interfaces: if interface and not i.name == interface: continue if signalName in i.signals: signal = i.signals[signalName] iface = i break def callback_caller(sig_msg): if isSignatureValid(signal.sig, sig_msg.signature): if sig_msg.body: callback(*sig_msg.body) else: callback() if iface is None: raise AttributeError( 'Requested signal "%s" is not a member of any of the ' 'supported interfaces' % (signalName,), ) d = self.objHandler.conn.addMatch( callback_caller, mtype='signal', path=self.objectPath, member=signalName, interface=iface.name, ) def on_ok(rule_id): if self._signalRules is None: self._signalRules = set() self._signalRules.add(rule_id) return rule_id d.addCallback(on_ok) return d def cancelSignalNotification(self, rule_id): """ Cancels a callback previously registered with notifyOnSignal """ if self._signalRules and rule_id in self._signalRules: self.objHandler.conn.delMatch(rule_id) self._signalRules.remove(rule_id) def callRemote(self, methodName, *args, **kwargs): """ Calls the remote method and returns a Deferred instance to the result. DBus does not support passing keyword arguments over the wire. The keyword arguments accepted by this method alter the behavior of the remote call as described in the kwargs prameter description. @type methodName: C{string} @param methodName: Name of the method to call @param args: Positional arguments to be passed to the remote method @param kwargs: Three keyword parameters may be passed to alter the behavior of the remote method call. If \"expectReply=False\" is supplied, the returned Deferred will be immediately called back with the value None. If \"autoStart=False\" is supplied the DBus daemon will not attempt to auto-start a service to fulfill the call if the service is not yet running (defaults to True). If \"timeout=VALUE\" is supplied, the returned Deferred will be errbacked with a L{error.TimeOut} instance if the remote call does not return before the timeout elapses. If \"interface\" is specified, the remote call use the method of the named interface. @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to the result of the remote call """ expectReply = kwargs.get('expectReply', True) autoStart = kwargs.get('autoStart', True) timeout = kwargs.get('timeout', None) interface = kwargs.get('interface', None) m = None for i in self.interfaces: if interface and not interface == i.name: continue m = i.methods.get(methodName, None) if m: break if m is None: raise AttributeError( 'Requested method "%s" is not a member of any of the ' 'supported interfaces' % (methodName,), ) if len(args) != m.nargs: raise TypeError( '%s.%s takes %d arguments (%d given)' % (i.name, methodName, m.nargs, len(args)), ) return self.objHandler.conn.callRemote( self.objectPath, methodName, interface=i.name, destination=self.busName, signature=m.sigIn, body=args, expectReply=expectReply, autoStart=autoStart, timeout=timeout, returnSignature=m.sigOut, ) class IDBusObject (Interface): """ Classes implementing this interface may be exported on DBus. """ def getInterfaces(self): """ Returns a iterator of L{interface.DBusInterface} objects implemented by the instance """ def getObjectPath(self): """ Returns the DBus object path for this instance """ def setObjectHandler(self, objectHandler): """ Provides the instance with a reference to its L{DBusObjectHandler} instance @type objectHandler: L{DBusObjectHandler} @param objectHandler: Thte L{DBusObjectHandler} managing this instance """ def executeMethod(self, interfaceObj, methodName, methodArguments, sender): """ Invokes the named method and returns the results. The method may return the result immediately or may return a deferred. @type interfaceObj: L{interface.DBusInterface} @param interfaceObj: The interface object containing the method to be invoked @type methodName: C{string} @param methodName: Name of the method to invoke @type methodArguments: C{list} @param methodArguments: List of method arguments @type sender: C{string} @param sender: Unique bus name of the connection invoking the method @returns: The method return values or a Deferred to the return values """ def getAllProperties(self, interfaceName): """ @type interfaceName: C{string} @param interfaceName: The interface to obtain properties from @returns: a dictionary of DBus property names and their associated values. The values must be marshallable as variants. Consequently, the type-specific wrapper classes within the marshal module may be necessary to ensure the correct encoding type """ class _IfaceCache(object): def __init__(self, interfaceName): self.name = interfaceName self.methods = {} self.properties = {} @implementer(IDBusObject) class DBusObject (object): """ Straight-forward L{IDBusObject} implementation. This implementation provides an API similar to that of L{twisted.spread.pb.Referenceable}. Classes to be exported over DBus may simply derive from L{DBusObject}, specify their object path and supported DBus interfaces in the constructor, and implement methods named 'dbus_' for each method they wish to support. @ivar dbusInterfaces: List of L{interface.DBusInterface} objects this class supports. If one or more superclasses also define dbusInterfaces, the contents of those lists will be added to the total interfaces the object supports. """ _objectHandler = None dbusInterfaces = [interface.DBusInterface( 'org.freedesktop.DBus.Properties', interface.Method('Get', 'ss', 'v'), interface.Method('Set', 'ssv'), interface.Method('GetAll', 's', 'a{sv}'), interface.Signal('PropertiesChanged', 'sa{sv}as') )] def __init__(self, objectPath): """ @type objectPath: C{string} @param objectPath: The DBus path of the object. The format of the path must comply with the DBus specification. """ self._objectPath = objectPath self._objectHandler = None marshal.validateObjectPath(objectPath) def _iterIFaceCaches(self): for base in self.__class__.__mro__: if base is object: return cache = base.__dict__.get('_dbusIfaceCache', None) if cache is None: cache = {} for name, obj in six.iteritems(base.__dict__): self._cacheInterfaces(base, cache, name, obj) setattr(base, '_dbusIfaceCache', cache) yield cache def _cacheInterfaces(self, cls, cache, cls_attr_name, obj): def get_ic(interface_name): if interface_name not in cache: cache[interface_name] = _IfaceCache(interface_name) return cache[interface_name] if inspect.isfunction(obj) and hasattr(obj, '_dbusInterface'): get_ic(obj._dbusInterface).methods[obj._dbusMethod] = getattr( cls, obj.__name__ ) elif isinstance(obj, DBusProperty): if obj.interface is None: for iface in self.getInterfaces(): if obj.pname in iface.properties: obj.interface = iface.name break if obj.interface is None: raise AttributeError( 'No supported DBus interfaces contain a property named ' '"%s"' % obj.pname, ) for iface in self.getInterfaces(): if obj.interface == iface.name: obj.iprop = iface.properties[obj.pname] break get_ic(obj.interface).properties[obj.pname] = obj obj.attr_name = cls_attr_name def _searchCache(self, interfaceName, cacheAttr, key): for cache in self._iterIFaceCaches(): if interfaceName: if interfaceName in cache: d = getattr(cache[interfaceName], cacheAttr) if key in d: return d[key] else: for ic in six.itervalues(cache): d = getattr(ic, cacheAttr) if key in d: return d[key] def _getDecoratedMethod(self, interfaceName, methodName): f = self._searchCache(interfaceName, 'methods', methodName) if f: return getattr(self, f.__name__) def _getProperty(self, interfaceName, propertyName): return self._searchCache(interfaceName, 'properties', propertyName) def getConnection(self): if self._objectHandler: return self._objectHandler.conn def getInterfaces(self): for base in self.__class__.__mro__: if 'dbusInterfaces' in base.__dict__: for iface in base.dbusInterfaces: yield iface def getObjectPath(self): return self._objectPath def setObjectHandler(self, objectHandler): self._objectHandler = objectHandler def _set_method_flags(self, method_obj): """ Sets the \"_dbusCaller\" boolean on the \"dbus_*\" methods. This is a one-time operation used to flag each method with a boolean indicating whether or not they accept the \"dbusCaller\" keyword argument """ args = inspect.getargspec(method_obj)[0] needs_caller = False if len(args) >= 1 and args[-1] == 'dbusCaller': needs_caller = True method_obj.__func__._dbusCaller = needs_caller def executeMethod(self, interfaceObj, methodName, methodArguments, sender): m = getattr(self, 'dbus_' + methodName, None) iname = interfaceObj.name if m is None: m = self._getDecoratedMethod(iname, methodName) if m is None: raise NotImplementedError if hasattr(m, '_dbusInterface') and m._dbusInterface != iname: m = self._getDecoratedMethod(iname, methodName) if m is None: raise NotImplementedError if not hasattr(m, '_dbusCaller'): self._set_method_flags(m) if m._dbusCaller: if methodArguments: return m(*methodArguments, dbusCaller=sender) else: return m(dbusCaller=sender) else: if methodArguments: return m(*methodArguments) else: return m() def emitSignal(self, signalName, *args, **kwargs): """ Emits the specified signal with the supplied arguments @type signalName: C{string} @param signalName: Name of the signal to emit. This must match the name of a signal in one of the objects supported interfaces. @type interface: C{string} @keyword interface: Optional keyword argument specifying the DBus interface to use. This is only needed if more than one interface defines a signal with the same name. @param args: Positional arguments for the signal content """ if self._objectHandler is None: return iface = kwargs.get('interface', None) s = None for i in self.getInterfaces(): if iface and not iface == i.name: continue t = i.signals.get(signalName, None) if isinstance(t, interface.Signal): s = t break if s is None: raise AttributeError( 'Signal "%s" not found in any supported interface.' % (signalName,), ) msig = message.SignalMessage( self._objectPath, signalName, i.name, signature=s.sig, body=args, ) self._objectHandler.conn.sendMessage(msig) def getAllProperties(self, interfaceName): r = {} def addp(p): if p.iprop.access != 'write': v = getattr(self, p.attr_name) if p.iprop.sig in marshal.variantClassMap: v = marshal.variantClassMap[p.iprop.sig](v) r[p.pname] = v if interfaceName: for cache in self._iterIFaceCaches(): ifc = cache.get(interfaceName, None) if ifc: for p in six.itervalues(ifc.properties): addp(p) break else: for cache in self._iterIFaceCaches(): for ifc in six.itervalues(cache): for p in six.itervalues(ifc.properties): addp(p) return r @dbusMethod('org.freedesktop.DBus.Properties', 'Get') def _dbus_PropertyGet(self, interfaceName, propertyName): p = self._getProperty(interfaceName, propertyName) if p is None: raise Exception('Invalid Property') if p.iprop.access == 'write': raise Exception('Property is not readable') v = getattr(self, p.attr_name) if p.iprop.sig in marshal.variantClassMap: return marshal.variantClassMap[p.iprop.sig](v) else: return v @dbusMethod('org.freedesktop.DBus.Properties', 'Set') def _dbus_PropertySet(self, interfaceName, propertyName, value): p = self._getProperty(interfaceName, propertyName) if p is None: raise Exception('Invalid Property') if p.iprop.access not in ('write', 'readwrite'): raise Exception('Property is not Writeable') return setattr(self, p.attr_name, value) @dbusMethod('org.freedesktop.DBus.Properties', 'GetAll') def _dbus_PropertyGetAll(self, interfaceName): return self.getAllProperties(interfaceName) class DBusObjectHandler (object): """ This class manages remote and local DBus objects associated with a DBus connection. Remote DBus objects are represented by instances of RemoteDBusObject. Local objects exported over DBus must implement the IDBusObject interface. """ def __init__(self, connection): """ @type connection: L{client.DBusClientConnection} or L{bus.Bus} @param connection: The connection to manage objects for """ self.conn = connection self.exports = {} # map object paths => obj self._weakProxies = weakref.WeakValueDictionary() def connectionLost(self, reason): """ Called by the DBus Connection object when the connection is lost. @type reason: L{twistd.python.failure.Failure} @param reason: The value passed to the associated connection's connectionLost method. """ for wref in self._weakProxies.valuerefs(): p = wref() if p is not None: p.connectionLost(reason) def exportObject(self, dbusObject): """ Makes the specified object available over DBus @type dbusObject: an object implementing the L{IDBusObject} interface @param dbusObject: The object to export over DBus """ o = IDBusObject(dbusObject) self.exports[o.getObjectPath()] = o o.setObjectHandler(self) i = {} for iface in o.getInterfaces(): i[iface.name] = o.getAllProperties(iface.name) msig = message.SignalMessage( o.getObjectPath(), 'InterfacesAdded', 'org.freedesktop.DBus.ObjectManager', signature='sa{sa{sv}}', body=[o.getObjectPath(), i], ) self.conn.sendMessage(msig) def unexportObject(self, objectPath): """ @type objectPath: C{string} @param objectPath: Object to stop exporting """ o = self.exports[objectPath] del self.exports[objectPath] i = [iface.name for iface in o.getInterfaces()] msig = message.SignalMessage( o.getObjectPath(), 'InterfacesRemoved', 'org.freedesktop.DBus.ObjectManager', signature='sas', body=[o.getObjectPath(), i], ) self.conn.sendMessage(msig) def getManagedObjects(self, objectPath): """ Returns a Python dictionary containing the reply content for org.freedesktop.DBus.ObjectManager.GetManagedObjects """ d = {} for p in sorted(self.exports.keys()): if not p.startswith(objectPath) or p == objectPath: continue o = self.exports[p] i = {} d[p] = i for iface in o.getInterfaces(): i[iface.name] = o.getAllProperties(iface.name) return d def _send_err(self, msg, errName, errMsg): """ Helper method for sending error messages """ r = message.ErrorMessage( errName, msg.serial, body=[errMsg], signature='s', destination=msg.sender, ) self.conn.sendMessage(r) def handleMethodCallMessage(self, msg): """ Handles DBus MethodCall messages on behalf of the DBus Connection and dispatches them to the appropriate exported object """ if ( msg.interface == 'org.freedesktop.DBus.Peer' and msg.member == 'Ping' ): r = message.MethodReturnMessage( msg.serial, destination=msg.sender, ) self.conn.sendMessage(r) return if ( msg.interface == 'org.freedesktop.DBus.Introspectable' and msg.member == 'Introspect' ): xml = introspection.generateIntrospectionXML( msg.path, self.exports, ) if xml is not None: r = message.MethodReturnMessage( msg.serial, body=[xml], destination=msg.sender, signature='s', ) self.conn.sendMessage(r) return # Try to get object from complete object path o = self.exports.get(msg.path, None) if o is None: self._send_err( msg, 'org.freedesktop.DBus.Error.UnknownObject', '%s is not an object provided by this process.' % (msg.path), ) return if ( msg.interface == 'org.freedesktop.DBus.ObjectManager' and msg.member == 'GetManagedObjects' ): i_and_p = self.getManagedObjects(o.getObjectPath()) r = message.MethodReturnMessage( msg.serial, body=[i_and_p], destination=msg.sender, signature='a{oa{sa{sv}}}', ) self.conn.sendMessage(r) return i = None for x in o.getInterfaces(): if msg.interface: if x.name == msg.interface: i = x break else: if msg.member in x.methods: i = x break m = None if i: m = i.methods.get(msg.member, None) if m is None: self._send_err( msg, 'org.freedesktop.DBus.Error.UnknownMethod', ( 'Method "%s" with signature "%s" on interface "%s" ' 'doesn\'t exist' ) % ( msg.member, msg.signature or '', msg.interface or '(null)', ), ) return msig = msg.signature if msg.signature is not None else '' esig = m.sigIn if m.sigIn is not None else '' if esig != msig: self._send_err( msg, 'org.freedesktop.DBus.Error.InvalidArgs', 'Call to %s has wrong args (%s, expected %s)' % (msg.member, msg.signature or '', m.sigIn or '') ) return d = defer.maybeDeferred( o.executeMethod, i, msg.member, msg.body, msg.sender, ) if msg.expectReply: def send_reply(return_values): if isinstance(return_values, (list, tuple)): if m.nret == 1: return_values = [return_values] else: return_values = [return_values] r = message.MethodReturnMessage( msg.serial, body=return_values, destination=msg.sender, signature=m.sigOut, ) self.conn.sendMessage(r) def send_error(err): e = err.value errMsg = err.getErrorMessage() name = None if hasattr(e, 'dbusErrorName'): name = e.dbusErrorName if name is None: name = 'org.txdbus.PythonException.' + e.__class__.__name__ try: marshal.validateErrorName(name) except error.MarshallingError as e: errMsg = ('!!(Invalid error name "%s")!! ' % name) + errMsg name = 'org.txdbus.InvalidErrorName' r = message.ErrorMessage(name, msg.serial, body=[errMsg], signature='s', destination=msg.sender) self.conn.sendMessage(r) d.addCallback(send_reply) d.addErrback(send_error) def getRemoteObject(self, busName, objectPath, interfaces=None, replaceKnownInterfaces=False): """ Creates a L{RemoteDBusObject} instance to represent the specified DBus object. If explicit interfaces are not supplied, DBus object introspection will be used to obtain them automatically. @type busName: C{string} @param busName: Name of the bus exporting the desired object @type objectPath: C{string} @param objectPath: DBus path of the desired object @type interfaces: None, C{string} or L{interface.DBusInterface} or a list of C{string}/L{interface.DBusInterface} @param interfaces: May be None, a single value, or a list of string interface names and/or instances of L{interface.DBusInterface}. If None or any of the specified interface names are unknown, full introspection will be attempted. If interfaces consists of solely of L{interface.DBusInterface} instances and/or known interfacep names, no introspection will be preformed. @type replaceKnownInterfaces: C{bool} @param replaceKnownInterfaces: If True (defaults to False), any interfaces discovered during the introspection process will override any previous, cached values. @rtype: L{twisted.internet.defer.Deferred} @returns: A Deferred to the L{RemoteDBusObject} instance """ weak_id = (busName, objectPath, interfaces) need_introspection = False required_interfaces = set() if interfaces is not None: ifl = [] if not isinstance(interfaces, list): interfaces = [interfaces] for i in interfaces: if isinstance(i, interface.DBusInterface): ifl.append(i) required_interfaces.add(i.name) else: required_interfaces.add(i) if i in interface.DBusInterface.knownInterfaces: ifl.append(interface.DBusInterface.knownInterfaces[i]) else: need_introspection = True if not need_introspection: return defer.succeed( RemoteDBusObject(self, busName, objectPath, ifl) ) d = self.conn.introspectRemoteObject( busName, objectPath, replaceKnownInterfaces, ) def ok(ifaces): missing = required_interfaces - {q.name for q in ifaces} if missing: raise error.IntrospectionFailed( 'Introspection failed to find interfaces: ' + ','.join(missing) ) prox = RemoteDBusObject(self, busName, objectPath, ifaces) self._weakProxies[weak_id] = prox return prox d.addCallback(ok) return d txdbus-1.1.0/txdbus/protocol.py000066400000000000000000000222131313301420000165310ustar00rootroot00000000000000""" This module implements the wire-level DBus protocol. @author: Tom Cocagne """ import os.path import struct import six from twisted.internet import interfaces, protocol from twisted.python import log from zope.interface import implementer, Interface from txdbus import error, message _is_linux = False if os.path.exists('/proc/version'): with open('/proc/version') as f: if f.read().startswith('Linux'): _is_linux = True class IDBusAuthenticator (Interface): """ Classes implementing this interface may be used by L{BasicDBusProtocol} instances to handle authentication """ def beginAuthentication(self, protocol): """ Called immediately after the connection is established. @type protocol: L{BasicDBusProtocol} @param protocol: L{BasicDBusProtocol} instance requiring authentication """ def handleAuthMessage(self, line): """ Handles an authentication message received on the connection. The authentication delimeter will be stripped prior to calling this method. If authentication fails, this method should raise L{error.DBusAuthenticationFailed} with an error message describing the reason. """ def authenticationSucceeded(self): """ @rtype: C{bool} @returns: True if authentication has succeeded """ def getGUID(self): """ @rtype: C{string} @returns: the GUID for the successfully authenticated connection """ @implementer(interfaces.IFileDescriptorReceiver) class BasicDBusProtocol(protocol.Protocol): """ Basic class providing support for converting a stream of bytes into authentication and DBus messages. This class is not inteded for direct use. @ivar authenticator: Class used to authenticate connections @type authenticator: Class implementing L{IDBusAuthenticator} """ _buffer = b'' _receivedFDs = [] _toBeSentFDs = [] _authenticated = False _nextMsgLen = 0 _endian = '<' _client = True _firstByte = True _unix_creds = None # (pid, uid, gid) from UnixSocket credential passing authenticator = None # Class to handle DBus authentication authDelimiter = b'\r\n' MAX_AUTH_LENGTH = 16384 MAX_MSG_LENGTH = 2**27 MSG_HDR_LEN = 16 # including 4-byte padding for array of structure guid = None # Filled in with the GUID of the server (for client protocol) # or the username of the authenticated client (for server protocol) def connectionMade(self): self.guid = None if self._client: # DBus specification requires that clients send a null byte upon # connection to the bus self.transport.write(b'\0') if self._client: self._dbusAuth = IDBusAuthenticator(self.authenticator()) else: self._dbusAuth = IDBusAuthenticator(self.authenticator( self.factory.bus.uuid)) self._dbusAuth.beginAuthentication(self) def dataReceived(self, data): if self._authenticated: self._buffer = self._buffer + data buffer_len = len(self._buffer) if self._nextMsgLen == 0 and buffer_len >= 16: # There would be multiple clients using different endians. # Reset endian every time. if self._buffer[:1] != b'l': self._endian = '>' else: self._endian = '<' body_len = struct.unpack( self._endian + 'I', self._buffer[4:8])[0] harr_len = struct.unpack( self._endian + 'I', self._buffer[12:16])[0] hlen = self.MSG_HDR_LEN + harr_len padlen = hlen % 8 and (8 - hlen % 8) or 0 self._nextMsgLen = ( self.MSG_HDR_LEN + harr_len + padlen + body_len ) if self._nextMsgLen != 0 and buffer_len >= self._nextMsgLen: raw_msg = self._buffer[:self._nextMsgLen] self._buffer = self._buffer[self._nextMsgLen:] self._nextMsgLen = 0 self.rawDBusMessageReceived(raw_msg) if self._buffer: # Recursively process any other complete messages self.dataReceived(b'') else: if not self._client and self._firstByte: if six.byte2int(data) != 0: self.transport.loseConnection() return self._firstByte = False data = data[1:] if _is_linux: import socket cd = self.transport.socket.getsockopt( socket.SOL_SOCKET, 17, # SO_PEERCRED struct.calcsize('3i') ) self._unix_creds = struct.unpack('3i', cd) lines = (self._buffer + data).split(self.authDelimiter) self._buffer = lines.pop(-1) for line in lines: if self.transport.disconnecting: # this is necessary because the transport may be # told to lose the connection by a line within a # larger packet, and it is important to disregard # all the lines in that packet following the one # that told it to close. return if len(line) > self.MAX_AUTH_LENGTH: return self.authMessageLengthExceeded(line) else: try: self._dbusAuth.handleAuthMessage(line) if self._dbusAuth.authenticationSucceeded(): self.guid = self._dbusAuth.getGUID() self._dbusAuth = None self.setAuthenticationSucceeded() if self._buffer: self.dataReceived(b'') except error.DBusAuthenticationFailed as e: log.msg('DBus Authentication failed: ' + str(e)) self.transport.loseConnection() else: if len(self._buffer) > self.MAX_AUTH_LENGTH: return self.authMessageLengthExceeded(self._buffer) def fileDescriptorReceived(self, fd): self._receivedFDs.append(fd) # ------------------------------------------------------------------------- # Authentication Message Handling # def sendAuthMessage(self, msg): """ Sends a message to the other end of the connection. @param msg: The line to send, not including the authDelimiter. @type msg: C{str} """ return self.transport.writeSequence((msg, self.authDelimiter)) def authMessageLengthExceeded(self, line): """ Called when the maximum line length has been reached. By default, this method simply terminates the connection. """ self.transport.loseConnection() def setAuthenticationSucceeded(self): """ Called by subclass when the authentication process completes. This causes the protocol to switch from line-based authentication messaging to binary DBus message handling """ self._authenticated = True self.connectionAuthenticated() def connectionAuthenticated(self): """ Called when the connection has been successfully authenticated """ # ------------------------------------------------------------------------- # DBus Message Handling # def sendMessage(self, msg): """ @type msg: L{message.DBusMessage} @param msg: A L{message.DBusMessage} instance to send over the connection """ assert isinstance(msg, message.DBusMessage) for fd in self._toBeSentFDs: self.transport.sendFileDescriptor(fd) self._toBeSentFDs = [] self.transport.write(msg.rawMessage) def rawDBusMessageReceived(self, rawMsg): """ Called when the raw bytes for a complete DBus message are received @param rawMsg: Byte-string containing the complete message @type rawMsg: C{str} """ m = message.parseMessage(rawMsg, self._receivedFDs) mt = m._messageType self._receivedFDs = [] if mt == 1: self.methodCallReceived(m) elif mt == 2: self.methodReturnReceived(m) elif mt == 3: self.errorReceived(m) elif mt == 4: self.signalReceived(m) def methodCallReceived(self, mcall): """ Called when a DBus METHOD_CALL message is received """ def methodReturnReceived(self, mret): """ Called when a DBus METHOD_RETURN message is received """ def errorReceived(self, merr): """ Called when a DBus ERROR message is received """ def signalReceived(self, msig): """ Called when a DBus METHOD_CALL message is received """ txdbus-1.1.0/txdbus/router.py000066400000000000000000000066411313301420000162170ustar00rootroot00000000000000""" This module implements DBus message matching for routing messages to interested connections. @author: Tom Cocagne """ import six from twisted.python import log # XXX Replace this simple, inefficient implementation with something a bit # smarter _mtypes = {'method_call': 1, 'method_return': 2, 'error': 3, 'signal': 4} class Rule (object): """ Represents a single match rule """ def __init__(self, callback, _id, router): self.callback = callback self.id = _id self.router = router self.simple = [] def add(self, key, value): # if key in ('mtype', 'sender', 'interface', 'member', 'path', # 'destination'): if key in ('mtype', 'interface', 'member', 'path', 'destination'): self.simple.append((key, value)) else: setattr(self, key, value) def cancel(self): self.router.delMatch(self.id) def match(self, m): try: for k, v in self.simple: if getattr(m, k) != v: return if hasattr(self, 'path_namespace'): if ( m.path is None or not m.path.startswith(self.path_namespace) ): return if hasattr(self, 'args') and m.body is not None: for idx, val in self.args: if idx >= len(m.body) or m.body[idx] != val: return if hasattr(self, 'arg_paths') and m.body is not None: for idx, val in self.arg_paths: if idx >= len(m.body) or not m.body[idx].startswith(val): return # XXX arg0namespace -- Not quite sure how this one works # if we get here, we have a match self.callback(m) except BaseException: log.err() class MessageRouter (object): """ Routes DBus messages to callback functions based on match rules as defined by the DBus specificiation """ def __init__(self): self._id = 0 self._rules = {} def addMatch(self, callback, mtype=None, sender=None, interface=None, member=None, path=None, path_namespace=None, destination=None, args=None, arg_paths=None, arg0namespace=None): # print 'ROUTER ADDING MATCH: ', mtype, path, interface, member, 'arg', # arg r = Rule(callback, self._id, self) # Simple if mtype: r.add('_messageType', mtype) if sender: r.add('sender', sender) if interface: r.add('interface', interface) if member: r.add('member', member) if path: r.add('path', path) if destination: r.add('destination', destination) # Complex if path_namespace: r.add('path_namespace', path_namespace) if args: r.add('args', args) if arg_paths: r.add('arg_paths', arg_paths) if arg0namespace: r.add('arg0namespace', arg0namespace) i = self._id self._id += 1 self._rules[i] = r return i def delMatch(self, rule_id): del self._rules[rule_id] def routeMessage(self, m): # print 'ROUTING MSG', m.interface, m.member for r in six.itervalues(self._rules): r.match(m)