1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import collections
6import dbus
7import dbus.mainloop.glib
8import gobject
9import time
10
11from autotest_lib.client.cros import dbus_util
12
13
14class ShillProxyError(Exception):
15    """Exceptions raised by ShillProxy and it's children."""
16    pass
17
18
19class ShillProxy(object):
20    """A wrapper around a DBus proxy for shill."""
21
22    # Core DBus error names
23    DBUS_ERROR_UNKNOWN_OBJECT = 'org.freedesktop.DBus.Error.UnknownObject'
24    # Shill error names
25    ERROR_ALREADY_CONNECTED = 'org.chromium.flimflam.Error.AlreadyConnected'
26    ERROR_FAILURE = 'org.chromium.flimflam.Error.Failure'
27    ERROR_INCORRECT_PIN = 'org.chromium.flimflam.Error.IncorrectPin'
28    ERROR_IN_PROGRESS = 'org.chromium.flimflam.Error.InProgress'
29    ERROR_NOT_CONNECTED = 'org.chromium.flimflam.Error.NotConnected'
30    ERROR_NOT_SUPPORTED = 'org.chromium.flimflam.Error.NotSupported'
31    ERROR_PIN_BLOCKED = 'org.chromium.flimflam.Error.PinBlocked'
32
33
34    DBUS_INTERFACE = 'org.chromium.flimflam'
35    DBUS_SERVICE_UNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
36    DBUS_TYPE_DEVICE = 'org.chromium.flimflam.Device'
37    DBUS_TYPE_IPCONFIG = 'org.chromium.flimflam.IPConfig'
38    DBUS_TYPE_MANAGER = 'org.chromium.flimflam.Manager'
39    DBUS_TYPE_PROFILE = 'org.chromium.flimflam.Profile'
40    DBUS_TYPE_SERVICE = 'org.chromium.flimflam.Service'
41
42    ENTRY_FIELD_NAME = 'Name'
43    ENTRY_FIELD_TYPE = 'Type'
44
45    MANAGER_PROPERTY_ACTIVE_PROFILE = 'ActiveProfile'
46    MANAGER_PROPERTY_DEVICES = 'Devices'
47    MANAGER_PROPERTY_NO_AUTOCONNECT_TECHNOLOGIES = 'NoAutoConnectTechnologies'
48    MANAGER_PROPERTY_PROFILES = 'Profiles'
49    MANAGER_PROPERTY_SERVICES = 'Services'
50    MANAGER_PROPERTY_ALL_SERVICES = 'ServiceCompleteList'
51
52    PROFILE_PROPERTY_ENTRIES = 'Entries'
53    PROFILE_PROPERTY_NAME = 'Name'
54
55    OBJECT_TYPE_PROPERTY_MAP = {
56        'Device': ( DBUS_TYPE_DEVICE, MANAGER_PROPERTY_DEVICES ),
57        'Profile': ( DBUS_TYPE_PROFILE, MANAGER_PROPERTY_PROFILES ),
58        'Service': ( DBUS_TYPE_SERVICE, MANAGER_PROPERTY_SERVICES ),
59        'AnyService': ( DBUS_TYPE_SERVICE, MANAGER_PROPERTY_ALL_SERVICES )
60    }
61
62    DEVICE_ENABLE_DISABLE_TIMEOUT = 60
63    SERVICE_DISCONNECT_TIMEOUT = 5
64
65    SERVICE_PROPERTY_AUTOCONNECT = 'AutoConnect'
66    SERVICE_PROPERTY_DEVICE = 'Device'
67    SERVICE_PROPERTY_GUID = 'GUID'
68    SERVICE_PROPERTY_HEX_SSID = 'WiFi.HexSSID'
69    SERVICE_PROPERTY_HIDDEN = 'WiFi.HiddenSSID'
70    SERVICE_PROPERTY_MODE = 'Mode'
71    SERVICE_PROPERTY_NAME = 'Name'
72    SERVICE_PROPERTY_PASSPHRASE = 'Passphrase'
73    SERVICE_PROPERTY_PROFILE = 'Profile'
74    SERVICE_PROPERTY_SAVE_CREDENTIALS = 'SaveCredentials'
75    # Unless you really care whether a network is WPA (TSN) vs. WPA-2
76    # (RSN), you should use SERVICE_PROPERTY_SECURITY_CLASS.
77    SERVICE_PROPERTY_SECURITY_RAW = 'Security'
78    SERVICE_PROPERTY_SECURITY_CLASS = 'SecurityClass'
79    SERVICE_PROPERTY_SSID = 'SSID'
80    SERVICE_PROPERTY_STRENGTH = 'Strength'
81    SERVICE_PROPERTY_STATE = 'State'
82    SERVICE_PROPERTY_TYPE = 'Type'
83
84    # EAP related properties.
85    SERVICE_PROPERTY_EAP_EAP = 'EAP.EAP'
86    SERVICE_PROPERTY_EAP_INNER_EAP = 'EAP.InnerEAP'
87    SERVICE_PROPERTY_EAP_IDENTITY = 'EAP.Identity'
88    SERVICE_PROPERTY_EAP_PASSWORD = 'EAP.Password'
89    SERVICE_PROPERTY_EAP_CA_CERT_PEM = 'EAP.CACertPEM'
90
91    # OpenVPN related properties.
92    SERVICE_PROPERTY_OPENVPN_CA_CERT_PEM = 'OpenVPN.CACertPEM'
93    SERVICE_PROPERTY_OPENVPN_PASSWORD = 'OpenVPN.Password'
94    SERVICE_PROPERTY_OPENVPN_PKCS11_ID = 'OpenVPN.Pkcs11.ID'
95    SERVICE_PROPERTY_OPENVPN_PKCS11_PIN = 'OpenVPN.Pkcs11.PIN'
96    SERVICE_PROPERTY_OPENVPN_PROVIDER_HOST = 'Provider.Host'
97    SERVICE_PROPERTY_OPENVPN_PROVIDER_TYPE = 'Provider.Type'
98    SERVICE_PROPERTY_OPENVPN_REMOTE_CERT_EKU = 'OpenVPN.RemoteCertEKU'
99    SERVICE_PROPERTY_OPENVPN_USER = 'OpenVPN.User'
100    SERVICE_PROPERTY_OPENVPN_VERB = 'OpenVPN.Verb'
101    SERVICE_PROPERTY_OPENVPN_VERIFY_HASH = 'OpenVPN.VerifyHash'
102    SERVICE_PROPERTY_OPENVPN_VERIFY_X509_NAME = 'OpenVPN.VerifyX509Name'
103    SERVICE_PROPERTY_OPENVPN_VERIFY_X509_TYPE = 'OpenVPN.VerifyX509Type'
104    SERVICE_PROPERTY_OPENVPN_VPN_DOMAIN = 'VPN.Domain'
105
106    # L2TP VPN related properties.
107    SERVICE_PROPERTY_L2TP_CA_CERT_PEM = 'L2TPIPsec.CACertPEM'
108    SERVICE_PROPERTY_L2TP_CLIENT_CERT_ID = 'L2TPIPsec.ClientCertID'
109    SERVICE_PROPERTY_L2TP_CLIENT_CERT_SLOT = 'L2TPIPsec.ClientCertSlot'
110    SERVICE_PROPERTY_L2TP_PASSWORD = 'L2TPIPsec.Password'
111    SERVICE_PROPERTY_L2TP_PIN = 'L2TPIPsec.PIN'
112    SERVICE_PROPERTY_L2TP_PSK = 'L2TPIPsec.PSK'
113    SERVICE_PROPERTY_L2TP_USER = 'L2TPIPsec.User'
114    SERVICE_PROPERTY_L2TP_XAUTH_PASSWORD = 'L2TPIPsec.XauthPassword'
115    SERVICE_PROPERTY_L2TP_XAUTH_USER = 'L2TPIPsec.XauthUser'
116
117    # Mapping of service property to its dbus type.
118    SERVICE_PROPERTY_MAP = {
119        SERVICE_PROPERTY_AUTOCONNECT: dbus.Boolean,
120        SERVICE_PROPERTY_DEVICE: dbus.ObjectPath,
121        SERVICE_PROPERTY_GUID: dbus.String,
122        SERVICE_PROPERTY_HEX_SSID: dbus.String,
123        SERVICE_PROPERTY_HIDDEN: dbus.Boolean,
124        SERVICE_PROPERTY_MODE: dbus.String,
125        SERVICE_PROPERTY_NAME: dbus.String,
126        SERVICE_PROPERTY_PASSPHRASE: dbus.String,
127        SERVICE_PROPERTY_PROFILE: dbus.ObjectPath,
128        SERVICE_PROPERTY_SAVE_CREDENTIALS: dbus.Boolean,
129        SERVICE_PROPERTY_SECURITY_RAW: dbus.String,
130        SERVICE_PROPERTY_SECURITY_CLASS: dbus.String,
131        SERVICE_PROPERTY_SSID: dbus.String,
132        SERVICE_PROPERTY_STRENGTH: dbus.Byte,
133        SERVICE_PROPERTY_STATE: dbus.String,
134        SERVICE_PROPERTY_TYPE: dbus.String,
135
136        SERVICE_PROPERTY_EAP_EAP: dbus.String,
137        SERVICE_PROPERTY_EAP_INNER_EAP: dbus.String,
138        SERVICE_PROPERTY_EAP_IDENTITY: dbus.String,
139        SERVICE_PROPERTY_EAP_PASSWORD: dbus.String,
140        SERVICE_PROPERTY_EAP_CA_CERT_PEM: dbus.Array,
141
142        SERVICE_PROPERTY_OPENVPN_CA_CERT_PEM: dbus.Array,
143        SERVICE_PROPERTY_OPENVPN_PASSWORD: dbus.String,
144        SERVICE_PROPERTY_OPENVPN_PKCS11_ID: dbus.String,
145        SERVICE_PROPERTY_OPENVPN_PKCS11_PIN: dbus.String,
146        SERVICE_PROPERTY_OPENVPN_PROVIDER_HOST: dbus.String,
147        SERVICE_PROPERTY_OPENVPN_PROVIDER_TYPE: dbus.String,
148        SERVICE_PROPERTY_OPENVPN_REMOTE_CERT_EKU: dbus.String,
149        SERVICE_PROPERTY_OPENVPN_USER: dbus.String,
150        SERVICE_PROPERTY_OPENVPN_VERB: dbus.String,
151        SERVICE_PROPERTY_OPENVPN_VERIFY_HASH: dbus.String,
152        SERVICE_PROPERTY_OPENVPN_VERIFY_X509_NAME: dbus.String,
153        SERVICE_PROPERTY_OPENVPN_VERIFY_X509_TYPE: dbus.String,
154        SERVICE_PROPERTY_OPENVPN_VPN_DOMAIN: dbus.String,
155
156        SERVICE_PROPERTY_L2TP_CA_CERT_PEM: dbus.Array,
157        SERVICE_PROPERTY_L2TP_CLIENT_CERT_ID: dbus.String,
158        SERVICE_PROPERTY_L2TP_CLIENT_CERT_SLOT: dbus.String,
159        SERVICE_PROPERTY_L2TP_PASSWORD: dbus.String,
160        SERVICE_PROPERTY_L2TP_PIN: dbus.String,
161        SERVICE_PROPERTY_L2TP_PSK: dbus.String,
162        SERVICE_PROPERTY_L2TP_USER: dbus.String,
163        SERVICE_PROPERTY_L2TP_XAUTH_PASSWORD: dbus.String,
164        SERVICE_PROPERTY_L2TP_XAUTH_USER: dbus.String
165    }
166
167    SERVICE_CONNECTED_STATES = ['portal', 'online']
168
169    SUPPORTED_WIFI_STATION_TYPES = {'managed': 'managed',
170                                    'ibss': 'adhoc',
171                                    None: 'managed'}
172
173    DEVICE_PROPERTY_ADDRESS = 'Address'
174    DEVICE_PROPERTY_EAP_AUTHENTICATION_COMPLETED = 'EapAuthenticationCompleted'
175    DEVICE_PROPERTY_EAP_AUTHENTICATOR_DETECTED = 'EapAuthenticatorDetected'
176    DEVICE_PROPERTY_IP_CONFIG = 'IpConfig'
177    DEVICE_PROPERTY_INTERFACE = 'Interface'
178    DEVICE_PROPERTY_NAME = 'Name'
179    DEVICE_PROPERTY_POWERED = 'Powered'
180    DEVICE_PROPERTY_RECEIVE_BYTE_COUNT = 'ReceiveByteCount'
181    DEVICE_PROPERTY_SCANNING = 'Scanning'
182    DEVICE_PROPERTY_TRANSMIT_BYTE_COUNT = 'TransmitByteCount'
183    DEVICE_PROPERTY_TYPE = 'Type'
184
185    TECHNOLOGY_CELLULAR = 'cellular'
186    TECHNOLOGY_ETHERNET = 'ethernet'
187    TECHNOLOGY_VPN = 'vpn'
188    TECHNOLOGY_WIFI = 'wifi'
189    TECHNOLOGY_WIMAX = 'wimax'
190
191    VALUE_POWERED_ON = True
192    VALUE_POWERED_OFF = False
193
194    POLLING_INTERVAL_SECONDS = 0.2
195
196    # Default log level used in connectivity tests.
197    LOG_LEVEL_FOR_TEST = -4
198
199    # Default log scopes used in connectivity tests.
200    LOG_SCOPES_FOR_TEST_COMMON = [
201        'connection',
202        'dbus',
203        'device',
204        'link',
205        'manager',
206        'portal',
207        'service'
208    ]
209
210    # Default log scopes used in connectivity tests for specific technologies.
211    LOG_SCOPES_FOR_TEST = {
212        TECHNOLOGY_CELLULAR: LOG_SCOPES_FOR_TEST_COMMON + ['cellular'],
213        TECHNOLOGY_ETHERNET: LOG_SCOPES_FOR_TEST_COMMON + ['ethernet'],
214        TECHNOLOGY_VPN: LOG_SCOPES_FOR_TEST_COMMON + ['vpn'],
215        TECHNOLOGY_WIFI: LOG_SCOPES_FOR_TEST_COMMON + ['wifi'],
216        TECHNOLOGY_WIMAX: LOG_SCOPES_FOR_TEST_COMMON + ['wimax']
217    }
218
219    UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
220
221
222    @staticmethod
223    def str2dbus(dbus_class, value):
224        """Typecast string property values to dbus types.
225
226        This mostly makes it easy to special case Boolean constructors
227        to interpret strings like 'false' and '0' as False.
228
229        @param dbus_class: DBus class object.
230        @param value: value to pass to constructor.
231
232        """
233        if isinstance(dbus_class, dbus.Boolean):
234            return dbus_class(value.lower() in ('true','1'))
235        else:
236            return dbus_class(value)
237
238
239    @staticmethod
240    def service_properties_to_dbus_types(in_dict):
241        """Convert service properties to dbus types.
242
243        @param in_dict: Dictionary containing service properties.
244        @return DBus variant dictionary containing service properties.
245
246        """
247        dbus_dict = {}
248        for key, value in in_dict.iteritems():
249                if key not in ShillProxy.SERVICE_PROPERTY_MAP:
250                        raise ShillProxyError('Unsupported property %s' % (key))
251                dbus_dict[key] = ShillProxy.SERVICE_PROPERTY_MAP[key](
252                        value, variant_level=1)
253        return dbus_dict
254
255
256    @classmethod
257    def dbus2primitive(cls, value):
258        """Typecast values from dbus types to python types.
259
260        @param value: dbus object to convert to a primitive.
261
262        """
263        return dbus_util.dbus2primitive(value)
264
265
266    @staticmethod
267    def get_dbus_property(interface, property_key):
268        """get property on a dbus Interface
269
270        @param interface dbus Interface to receive new setting
271        @param property_key string name of property on interface
272        @return python typed object representing property value or None
273
274        """
275        properties = interface.GetProperties(utf8_strings=True)
276        if property_key in properties:
277            return ShillProxy.dbus2primitive(properties[property_key])
278        else:
279            return None
280
281
282    @staticmethod
283    def set_dbus_property(interface, property_key, value):
284        """set property on a dbus Interface
285
286        @param interface dbus Interface to receive new setting
287        @param property_key string name of property on interface
288        @param value string value to set for property on interface from string
289
290        """
291        properties = interface.GetProperties(utf8_strings=True)
292        if property_key not in properties:
293            raise ShillProxyError('No property %s found in %s' %
294                    (property_key, interface.object_path))
295        else:
296            dbus_class = properties[property_key].__class__
297            interface.SetProperty(property_key,
298                    ShillProxy.str2dbus(dbus_class, value))
299
300
301    @classmethod
302    def get_proxy(cls, bus=None, timeout_seconds=10):
303        """Create a Proxy, retrying if necessary.
304
305        This method creates a proxy object of the required subclass of
306        ShillProxy. A call to SomeSubclassOfShillProxy.get_proxy() will return
307        an object of type SomeSubclassOfShillProxy.
308
309        Connects to shill over D-Bus. If shill is not yet running,
310        retry until it is, or until |timeout_seconds| expires.
311
312        After connecting to shill, this method will verify that shill
313        is answering RPCs. No timeout is applied to the test RPC, so
314        this method _may_ block indefinitely.
315
316        @param bus D-Bus bus to use, or specify None and this object will
317            create a mainloop and bus.
318        @param timeout_seconds float number of seconds to try connecting
319            A value <= 0 will cause the method to return immediately,
320            without trying to connect.
321        @return a ShillProxy instance if we connected, or None otherwise
322
323        """
324        end_time = time.time() + timeout_seconds
325        connection = None
326        while connection is None and time.time() < end_time:
327            try:
328                # We create instance of class on which this classmethod was
329                # called. This way, calling SubclassOfShillProxy.get_proxy()
330                # will get a proxy of the right type.
331                connection = cls(bus=bus)
332            except dbus.exceptions.DBusException as e:
333                if e.get_dbus_name() != ShillProxy.DBUS_SERVICE_UNKNOWN:
334                    raise ShillProxyError('Error connecting to shill')
335                else:
336                    # Wait a moment before retrying
337                    time.sleep(ShillProxy.POLLING_INTERVAL_SECONDS)
338
339        if connection is None:
340            return None
341
342        # Although shill is connected to D-Bus at this point, it may
343        # not have completed initialization just yet. Call into shill,
344        # and wait for the response, to make sure that it is truly up
345        # and running. (Shill will not service D-Bus requests until
346        # initialization is complete.)
347        connection.get_profiles()
348        return connection
349
350
351    def __init__(self, bus=None):
352        if bus is None:
353            dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
354            bus = dbus.SystemBus()
355        self._bus = bus
356        self._manager = self.get_dbus_object(self.DBUS_TYPE_MANAGER, '/')
357
358
359    def configure_service_by_guid(self, guid, properties={}):
360        """Configure a service identified by its GUID.
361
362        @param guid string unique identifier of service.
363        @param properties dictionary of service property:value pairs.
364
365        """
366        config = properties.copy()
367        config[self.SERVICE_PROPERTY_GUID] = guid
368        self.configure_service(config)
369
370
371    def configure_service(self, config):
372        """Configure a service with given properties.
373
374        @param config dictionary of service property:value pairs.
375
376        """
377        # Convert configuration values to dbus variant typed values.
378        dbus_config = ShillProxy.service_properties_to_dbus_types(config)
379        self.manager.ConfigureService(dbus_config)
380
381
382    def set_logging(self, level, scopes):
383        """Set the logging in shill to the specified |level| and |scopes|.
384
385        @param level int log level to set to in shill.
386        @param scopes list of strings of log scopes to set to in shill.
387
388        """
389        self.manager.SetDebugLevel(level)
390        self.manager.SetDebugTags('+'.join(scopes))
391
392
393    def set_logging_for_test(self, technology):
394        """Set the logging in shill for a test of the specified |technology|.
395
396        Set the log level to |LOG_LEVEL_FOR_TEST| and the log scopes to the
397        ones defined in |LOG_SCOPES_FOR_TEST| for |technology|. If |technology|
398        is not found in |LOG_SCOPES_FOR_TEST|, the log scopes are set to
399        |LOG_SCOPES_FOR_TEST_COMMON|.
400
401        @param technology string representing the technology type of a test
402            that the logging in shill is to be customized for.
403
404        """
405        scopes = self.LOG_SCOPES_FOR_TEST.get(technology,
406                                              self.LOG_SCOPES_FOR_TEST_COMMON)
407        self.set_logging(self.LOG_LEVEL_FOR_TEST, scopes)
408
409
410    def wait_for_property_in(self, dbus_object, property_name,
411                             expected_values, timeout_seconds):
412        """Wait till a property is in a list of expected values.
413
414        Block until the property |property_name| in |dbus_object| is in
415        |expected_values|, or |timeout_seconds|.
416
417        @param dbus_object DBus proxy object as returned by
418            self.get_dbus_object.
419        @param property_name string property key in dbus_object.
420        @param expected_values iterable set of values to return successfully
421            upon seeing.
422        @param timeout_seconds float number of seconds to return if we haven't
423            seen the appropriate property value in time.
424        @return tuple(successful, final_value, duration)
425            where successful is True iff we saw one of |expected_values| for
426            |property_name|, final_value is the member of |expected_values| we
427            saw, and duration is how long we waited to see that value.
428
429        """
430        start_time = time.time()
431        duration = lambda: time.time() - start_time
432
433        update_queue = collections.deque()
434        signal_receiver = lambda key, value: update_queue.append((key, value))
435        receiver_ref = self._bus.add_signal_receiver(
436                signal_receiver,
437                signal_name='PropertyChanged',
438                dbus_interface=dbus_object.dbus_interface,
439                path=dbus_object.object_path)
440        try:
441            # Check to make sure we're not already in a target state.
442            try:
443                properties = self.dbus2primitive(
444                        dbus_object.GetProperties(utf8_strings=True))
445                last_value = properties.get(property_name, '(no value found)')
446                if last_value in expected_values:
447                    return True, last_value, duration()
448
449            except dbus.exceptions.DBusException:
450                return False, '(object reference became invalid)', duration()
451
452            context = gobject.MainLoop().get_context()
453            while duration() < timeout_seconds:
454                # Dispatch all pending events.
455                while context.iteration(False):
456                    pass
457
458                while update_queue:
459                    updated_property, value = map(self.dbus2primitive,
460                                                  update_queue.popleft())
461                    if property_name != updated_property:
462                        continue
463
464                    last_value = value
465                    if not last_value in expected_values:
466                        continue
467
468                    return True, last_value, duration()
469
470                time.sleep(0.2)  # Give that CPU a break.  CPUs love breaks.
471        finally:
472            receiver_ref.remove()
473
474        return False, last_value, duration()
475
476
477    @property
478    def manager(self):
479        """ @return DBus proxy object representing the shill Manager. """
480        return self._manager
481
482
483    def get_active_profile(self):
484        """Get the active profile in shill.
485
486        @return dbus object representing the active profile.
487
488        """
489        properties = self.manager.GetProperties(utf8_strings=True)
490        return self.get_dbus_object(
491                self.DBUS_TYPE_PROFILE,
492                properties[self.MANAGER_PROPERTY_ACTIVE_PROFILE])
493
494
495    def get_dbus_object(self, type_str, path):
496        """Return the DBus object of type |type_str| at |path| in shill.
497
498        @param type_str string (e.g. self.DBUS_TYPE_SERVICE).
499        @param path path to object in shill (e.g. '/service/12').
500        @return DBus proxy object.
501
502        """
503        return dbus.Interface(
504                self._bus.get_object(self.DBUS_INTERFACE, path),
505                type_str)
506
507
508    def get_devices(self):
509        """Return the list of devices as dbus Interface objects"""
510        properties = self.manager.GetProperties(utf8_strings=True)
511        return [self.get_dbus_object(self.DBUS_TYPE_DEVICE, path)
512                for path in properties[self.MANAGER_PROPERTY_DEVICES]]
513
514
515    def get_profiles(self):
516        """Return the list of profiles as dbus Interface objects"""
517        properties = self.manager.GetProperties(utf8_strings=True)
518        return [self.get_dbus_object(self.DBUS_TYPE_PROFILE, path)
519                for path in properties[self.MANAGER_PROPERTY_PROFILES]]
520
521
522    def get_service(self, params):
523        """
524        Get the shill service that matches |params|.
525
526        @param params dict of strings understood by shill to describe
527            a service.
528        @return DBus object interface representing a service.
529
530        """
531        dbus_params = self.service_properties_to_dbus_types(params)
532        path = self.manager.GetService(dbus_params)
533        return self.get_dbus_object(self.DBUS_TYPE_SERVICE, path)
534
535
536    def get_service_for_device(self, device):
537        """Attempt to find a service that manages |device|.
538
539        @param device a dbus object interface representing a device.
540        @return Dbus object interface representing a service if found. None
541                otherwise.
542
543        """
544        properties = self.manager.GetProperties(utf8_strings=True)
545        all_services = properties.get(self.MANAGER_PROPERTY_ALL_SERVICES,
546                                      None)
547        if not all_services:
548            return None
549
550        for service_path in all_services:
551            service = self.get_dbus_object(self.DBUS_TYPE_SERVICE,
552                                           service_path)
553            properties = service.GetProperties(utf8_strings=True)
554            device_path = properties.get(self.SERVICE_PROPERTY_DEVICE, None)
555            if device_path == device.object_path:
556                return service
557
558        return None
559
560
561    def find_object(self, object_type, properties):
562        """Find a shill object with the specified type and properties.
563
564        Return the first shill object of |object_type| whose properties match
565        all that of |properties|.
566
567        @param object_type string representing the type of object to be
568            returned. Valid values are those object types defined in
569            |OBJECT_TYPE_PROPERTY_MAP|.
570        @param properties dict of strings understood by shill to describe
571            a service.
572        @return DBus object interface representing the object found or None
573            if no matching object is found.
574
575        """
576        if object_type not in self.OBJECT_TYPE_PROPERTY_MAP:
577            return None
578
579        dbus_type, manager_property = self.OBJECT_TYPE_PROPERTY_MAP[object_type]
580        manager_properties = self.manager.GetProperties(utf8_strings=True)
581        for path in manager_properties[manager_property]:
582            try:
583                test_object = self.get_dbus_object(dbus_type, path)
584                object_properties = test_object.GetProperties(utf8_strings=True)
585                for name, value in properties.iteritems():
586                    if (name not in object_properties or
587                        self.dbus2primitive(object_properties[name]) != value):
588                        break
589                else:
590                    return test_object
591
592            except dbus.exceptions.DBusException, e:
593                # This could happen if for instance, you're enumerating services
594                # and test_object was removed in shill between the call to get
595                # the manager properties and the call to get the service
596                # properties.  This causes failed method invocations.
597                continue
598        return None
599
600
601    def find_matching_service(self, properties):
602        """Find a service object that matches the given properties.
603
604        This re-implements the manager DBus method FindMatchingService.
605        The advantage of doing this here is that FindMatchingServices does
606        not exist on older images, which will cause tests to fail.
607
608        @param properties dict of strings understood by shill to describe
609            a service.
610
611        """
612        return self.find_object('Service', properties)
613
614
615    def connect_service_synchronous(self, service, timeout_seconds):
616        """Connect a service and wait for its state to become connected.
617
618        @param service DBus service object to connect.
619        @param timeout_seconds number of seconds to wait for service to go
620            enter a connected state.
621        @return True if the service connected successfully.
622
623        """
624        try:
625            service.Connect()
626        except dbus.exceptions.DBusException as e:
627            if e.get_dbus_name() != self.ERROR_ALREADY_CONNECTED:
628                raise e
629        success, _, _ = self.wait_for_property_in(
630                service, self.SERVICE_PROPERTY_STATE,
631                self.SERVICE_CONNECTED_STATES,
632                timeout_seconds=timeout_seconds)
633        return success
634
635
636    def disconnect_service_synchronous(self, service, timeout_seconds):
637        """Disconnect a service and wait for its state to go idle.
638
639        @param service DBus service object to disconnect.
640        @param timeout_seconds number of seconds to wait for service to go idle.
641        @return True if the service disconnected successfully.
642
643        """
644        try:
645            service.Disconnect()
646        except dbus.exceptions.DBusException as e:
647            if e.get_dbus_name() not in [self.ERROR_IN_PROGRESS,
648                                         self.ERROR_NOT_CONNECTED]:
649                raise e
650        success, _, _ = self.wait_for_property_in(
651                service, self.SERVICE_PROPERTY_STATE, ['idle'],
652                timeout_seconds=timeout_seconds)
653        return success
654