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