1#!/usr/bin/python
2
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import dbus
8import logging
9import logging.handlers
10import multiprocessing
11
12import common
13from autotest_lib.client.common_lib import utils
14from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
15from autotest_lib.client.cros import xmlrpc_server
16from autotest_lib.client.cros import constants
17from autotest_lib.client.cros import cros_ui
18from autotest_lib.client.cros import sys_power
19from autotest_lib.client.cros import tpm_store
20from autotest_lib.client.cros.networking import shill_proxy
21from autotest_lib.client.cros.networking import wifi_proxy
22
23
24class ShillXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
25    """Exposes methods called remotely during WiFi autotests.
26
27    All instance methods of this object without a preceding '_' are exposed via
28    an XMLRPC server.  This is not a stateless handler object, which means that
29    if you store state inside the delegate, that state will remain around for
30    future calls.
31
32    """
33
34    DEFAULT_TEST_PROFILE_NAME = 'test'
35    DBUS_DEVICE = 'Device'
36
37    def __init__(self):
38        self._wifi_proxy = wifi_proxy.WifiProxy()
39        self._tpm_store = tpm_store.TPMStore()
40
41
42    def __enter__(self):
43        super(ShillXmlRpcDelegate, self).__enter__()
44        if not cros_ui.stop(allow_fail=True):
45            logging.error('UI did not stop, there could be trouble ahead.')
46        self._tpm_store.__enter__()
47
48
49    def __exit__(self, exception, value, traceback):
50        super(ShillXmlRpcDelegate, self).__exit__(exception, value, traceback)
51        self._tpm_store.__exit__(exception, value, traceback)
52        self.enable_ui()
53
54
55    @xmlrpc_server.dbus_safe(False)
56    def create_profile(self, profile_name):
57        """Create a shill profile.
58
59        @param profile_name string name of profile to create.
60        @return True on success, False otherwise.
61
62        """
63        self._wifi_proxy.manager.CreateProfile(profile_name)
64        return True
65
66
67    @xmlrpc_server.dbus_safe(False)
68    def push_profile(self, profile_name):
69        """Push a shill profile.
70
71        @param profile_name string name of profile to push.
72        @return True on success, False otherwise.
73
74        """
75        self._wifi_proxy.manager.PushProfile(profile_name)
76        return True
77
78
79    @xmlrpc_server.dbus_safe(False)
80    def pop_profile(self, profile_name):
81        """Pop a shill profile.
82
83        @param profile_name string name of profile to pop.
84        @return True on success, False otherwise.
85
86        """
87        if profile_name is None:
88            self._wifi_proxy.manager.PopAnyProfile()
89        else:
90            self._wifi_proxy.manager.PopProfile(profile_name)
91        return True
92
93
94    @xmlrpc_server.dbus_safe(False)
95    def remove_profile(self, profile_name):
96        """Remove a profile from disk.
97
98        @param profile_name string name of profile to remove.
99        @return True on success, False otherwise.
100
101        """
102        self._wifi_proxy.manager.RemoveProfile(profile_name)
103        return True
104
105
106    @xmlrpc_server.dbus_safe(False)
107    def clean_profiles(self):
108        """Pop and remove shill profiles above the default profile.
109
110        @return True on success, False otherwise.
111
112        """
113        while True:
114            active_profile = self._wifi_proxy.get_active_profile()
115            profile_name = self._wifi_proxy.dbus2primitive(
116                    active_profile.GetProperties(utf8_strings=True)['Name'])
117            if profile_name == 'default':
118                return True
119            self._wifi_proxy.manager.PopProfile(profile_name)
120            self._wifi_proxy.manager.RemoveProfile(profile_name)
121
122
123    @xmlrpc_server.dbus_safe(False)
124    def configure_service_by_guid(self, raw_params):
125        """Configure a service referenced by a GUID.
126
127        @param raw_params serialized ConfigureServiceParameters.
128
129        """
130        params = xmlrpc_datatypes.deserialize(raw_params)
131        shill = self._wifi_proxy
132        properties = {}
133        if params.autoconnect is not None:
134            properties[shill.SERVICE_PROPERTY_AUTOCONNECT] = params.autoconnect
135        if params.passphrase is not None:
136            properties[shill.SERVICE_PROPERTY_PASSPHRASE] = params.passphrase
137        if properties:
138            self._wifi_proxy.configure_service_by_guid(params.guid, properties)
139        return True
140
141
142    @xmlrpc_server.dbus_safe(False)
143    def configure_wifi_service(self, raw_params):
144        """Configure a WiFi service
145
146        @param raw_params serialized AssociationParameters.
147        @return True on success, False otherwise.
148
149        """
150        params = xmlrpc_datatypes.deserialize(raw_params)
151        return self._wifi_proxy.configure_wifi_service(
152                params.ssid,
153                params.security,
154                params.security_parameters,
155                save_credentials=params.save_credentials,
156                station_type=params.station_type,
157                hidden_network=params.is_hidden,
158                guid=params.guid,
159                autoconnect=params.autoconnect)
160
161
162    def connect_wifi(self, raw_params):
163        """Block and attempt to connect to wifi network.
164
165        @param raw_params serialized AssociationParameters.
166        @return serialized AssociationResult
167
168        """
169        logging.debug('connect_wifi()')
170        params = xmlrpc_datatypes.deserialize(raw_params)
171        params.security_config.install_client_credentials(self._tpm_store)
172        wifi_if = params.bgscan_config.interface
173        if wifi_if is None:
174            logging.info('Using default interface for bgscan configuration')
175            interfaces = self.list_controlled_wifi_interfaces()
176            if not interfaces:
177                return xmlrpc_datatypes.AssociationResult(
178                        failure_reason='No wifi interfaces found?')
179
180            if len(interfaces) > 1:
181                logging.error('Defaulting to first interface of %r', interfaces)
182            wifi_if = interfaces[0]
183        if not self._wifi_proxy.configure_bgscan(
184                wifi_if,
185                method=params.bgscan_config.method,
186                short_interval=params.bgscan_config.short_interval,
187                long_interval=params.bgscan_config.long_interval,
188                signal=params.bgscan_config.signal):
189            return xmlrpc_datatypes.AssociationResult(
190                    failure_reason='Failed to configure bgscan')
191
192        raw = self._wifi_proxy.connect_to_wifi_network(
193                params.ssid,
194                params.security,
195                params.security_parameters,
196                params.save_credentials,
197                station_type=params.station_type,
198                hidden_network=params.is_hidden,
199                guid=params.guid,
200                discovery_timeout_seconds=params.discovery_timeout,
201                association_timeout_seconds=params.association_timeout,
202                configuration_timeout_seconds=params.configuration_timeout)
203        result = xmlrpc_datatypes.AssociationResult.from_dbus_proxy_output(raw)
204        return result
205
206
207    @xmlrpc_server.dbus_safe(False)
208    def delete_entries_for_ssid(self, ssid):
209        """Delete a profile entry.
210
211        @param ssid string of WiFi service for which to delete entries.
212        @return True on success, False otherwise.
213
214        """
215        shill = self._wifi_proxy
216        for profile in shill.get_profiles():
217            profile_properties = shill.dbus2primitive(
218                    profile.GetProperties(utf8_strings=True))
219            entry_ids = profile_properties[shill.PROFILE_PROPERTY_ENTRIES]
220            for entry_id in entry_ids:
221                entry = profile.GetEntry(entry_id)
222                if shill.dbus2primitive(entry[shill.ENTRY_FIELD_NAME]) == ssid:
223                    profile.DeleteEntry(entry_id)
224        return True
225
226
227    def init_test_network_state(self):
228        """Create a clean slate for tests with respect to remembered networks.
229
230        For shill, this means popping and removing profiles, removing all WiFi
231        entries from the default profile, and pushing a 'test' profile.
232
233        @return True iff operation succeeded, False otherwise.
234
235        """
236        self.clean_profiles()
237        self._wifi_proxy.remove_all_wifi_entries()
238        self.remove_profile(self.DEFAULT_TEST_PROFILE_NAME)
239        worked = self.create_profile(self.DEFAULT_TEST_PROFILE_NAME)
240        if worked:
241            worked = self.push_profile(self.DEFAULT_TEST_PROFILE_NAME)
242        return worked
243
244
245    @xmlrpc_server.dbus_safe(None)
246    def list_controlled_wifi_interfaces(self):
247        """List WiFi interfaces controlled by shill.
248
249        @return list of string WiFi device names (e.g. ['mlan0'])
250
251        """
252        ret = []
253        devices = self._wifi_proxy.get_devices()
254        for device in devices:
255            properties = self._wifi_proxy.dbus2primitive(
256                    device.GetProperties(utf8_strings=True))
257            if properties[self._wifi_proxy.DEVICE_PROPERTY_TYPE] != 'wifi':
258                continue
259            ret.append(properties[self._wifi_proxy.DEVICE_PROPERTY_NAME])
260        return ret
261
262
263    @xmlrpc_server.dbus_safe(False)
264    def disconnect(self, ssid):
265        """Attempt to disconnect from the given ssid.
266
267        Blocks until disconnected or operation has timed out.  Returns True iff
268        disconnect was successful.
269
270        @param ssid string network to disconnect from.
271        @return bool True on success, False otherwise.
272
273        """
274        logging.debug('disconnect()')
275        result = self._wifi_proxy.disconnect_from_wifi_network(ssid)
276        successful, duration, message = result
277        if successful:
278            level = logging.info
279        else:
280            level = logging.error
281        level('Disconnect result: %r, duration: %d, reason: %s',
282              successful, duration, message)
283        return successful is True
284
285
286    def wait_for_service_states(self, ssid, states, timeout_seconds):
287        """Wait for service to achieve one state out of a list of states.
288
289        @param ssid string the network to connect to (e.g. 'GoogleGuest').
290        @param states tuple the states for which to wait
291        @param timeout_seconds int seconds to wait for a state
292
293        """
294        return self._wifi_proxy.wait_for_service_states(
295                ssid, states, timeout_seconds)
296
297
298    @xmlrpc_server.dbus_safe(None)
299    def get_service_order(self):
300        """Get the shill service order.
301
302        @return string service order on success, None otherwise.
303
304        """
305        return str(self._wifi_proxy.manager.GetServiceOrder())
306
307
308    @xmlrpc_server.dbus_safe(False)
309    def set_service_order(self, order):
310        """Set the shill service order.
311
312        @param order string comma-delimited service order (eg. 'ethernet,wifi')
313        @return bool True on success, False otherwise.
314
315        """
316        self._wifi_proxy.manager.SetServiceOrder(dbus.String(order))
317        return True
318
319
320    @xmlrpc_server.dbus_safe(None)
321    def get_service_properties(self, ssid):
322        """Get a dict of properties for a service.
323
324        @param ssid string service to get properties for.
325        @return dict of Python friendly native types or None on failures.
326
327        """
328        discovery_params = {self._wifi_proxy.SERVICE_PROPERTY_TYPE: 'wifi',
329                            self._wifi_proxy.SERVICE_PROPERTY_NAME: ssid}
330        service_path = self._wifi_proxy.manager.FindMatchingService(
331                discovery_params)
332        service_object = self._wifi_proxy.get_dbus_object(
333                self._wifi_proxy.DBUS_TYPE_SERVICE, service_path)
334        service_properties = service_object.GetProperties(
335                utf8_strings=True)
336        return self._wifi_proxy.dbus2primitive(service_properties)
337
338
339    @xmlrpc_server.dbus_safe(False)
340    def get_active_wifi_SSIDs(self):
341        """@return list of string SSIDs with at least one BSS we've scanned."""
342        return self._wifi_proxy.get_active_wifi_SSIDs()
343
344
345    @xmlrpc_server.dbus_safe(False)
346    def set_sched_scan(self, enable):
347        """Configure scheduled scan.
348
349        @param enable bool flag indicating to enable/disable scheduled scan.
350        @return True on success, False otherwise.
351
352        """
353        self._wifi_proxy.manager.set_sched_scan(enable)
354        return True
355
356
357    def enable_ui(self):
358        """@return True iff the UI was successfully started."""
359        return cros_ui.start(allow_fail=True, wait_for_login_prompt=False) == 0
360
361
362    def sync_time_to(self, epoch_seconds):
363        """Sync time on the DUT to |epoch_seconds| from the epoch.
364
365        @param epoch_seconds: float number of seconds from the epoch.
366
367        """
368        utils.run('date -u --set=@%f' % epoch_seconds)
369        return True
370
371
372    @staticmethod
373    def do_suspend(seconds):
374        """Suspend DUT using the power manager.
375
376        @param seconds: The number of seconds to suspend the device.
377
378        """
379        return sys_power.do_suspend(seconds)
380
381
382    @staticmethod
383    def do_suspend_bg(seconds):
384        """Suspend DUT using the power manager - non-blocking.
385
386        @param seconds int The number of seconds to suspend the device.
387
388        """
389        process = multiprocessing.Process(target=sys_power.do_suspend,
390                                          args=(seconds, 1))
391        process.start()
392        return True
393
394
395    @xmlrpc_server.dbus_safe(None)
396    def get_dbus_property_on_device(self, wifi_interface, prop_name):
397        """Get a property for the given WiFi device.
398
399        @param wifi_interface: string name of interface being queried.
400        @param prop_name: the name of the property.
401        @return the current value of the property.
402
403        """
404        dbus_object = self._wifi_proxy.find_object(
405                self.DBUS_DEVICE, {'Name': wifi_interface})
406        if dbus_object is None:
407            return None
408
409        object_properties = dbus_object.GetProperties(utf8_strings=True)
410        if prop_name not in object_properties:
411            return None
412
413        return self._wifi_proxy.dbus2primitive(
414                object_properties[prop_name])
415
416
417    @xmlrpc_server.dbus_safe(False)
418    def set_dbus_property_on_device(self, wifi_interface, prop_name, value):
419        """Set a property on the given WiFi device.
420
421        @param wifi_interface: the device to set a property for.
422        @param prop_name: the name of the property.
423        @param value: the desired value of the property.
424        @return True if successful, False otherwise.
425
426        """
427        device_object = self._wifi_proxy.find_object(
428                self.DBUS_DEVICE, {'Name': wifi_interface})
429        if device_object is None:
430            return False
431
432        shill_proxy.ShillProxy.set_dbus_property(device_object,
433                                                 prop_name,
434                                                 value)
435        return True
436
437
438    @xmlrpc_server.dbus_safe(False)
439    def request_roam_dbus(self, bssid, interface):
440        """Request that we roam to the specified BSSID.
441
442        Note that this operation assumes that:
443
444        1) We're connected to an SSID for which |bssid| is a member.
445        2) There is a BSS with an appropriate ID in our scan results.
446
447        @param bssid: string BSSID of BSS to roam to.
448
449        """
450
451        device_object = self._wifi_proxy.find_object(
452                self.DBUS_DEVICE, {'Name': interface})
453        if device_object is None:
454            return False
455        device_object.RequestRoam(bssid)
456        return True
457
458
459    @xmlrpc_server.dbus_safe(False)
460    def set_device_enabled(self, wifi_interface, enabled):
461        """Enable or disable the WiFi device.
462
463        @param wifi_interface: string name of interface being modified.
464        @param enabled: boolean; true if this device should be enabled,
465                false if this device should be disabled.
466        @return True if it worked; false, otherwise
467
468        """
469        interface = {'Name': wifi_interface}
470        dbus_object = self._wifi_proxy.find_object(self.DBUS_DEVICE,
471                                                   interface)
472        if dbus_object is None:
473            return False
474
475        if enabled:
476            dbus_object.Enable()
477        else:
478            dbus_object.Disable()
479        return True
480
481
482    def discover_tdls_link(self, wifi_interface, peer_mac_address):
483        """Send a TDLS Discover to |peer_mac_address| on |wifi_interface|.
484
485        @param wifi_interface: string name of interface to send the discover on.
486        @param peer_mac_address: string mac address of the TDLS peer device.
487
488        @return True if it the operation was initiated; False otherwise
489
490        """
491        device_object = self._wifi_proxy.find_object(
492                self.DBUS_DEVICE, {'Name': wifi_interface})
493        if device_object is None:
494            return False
495        device_object.PerformTDLSOperation('Discover', peer_mac_address)
496        return True
497
498
499    def establish_tdls_link(self, wifi_interface, peer_mac_address):
500        """Establish a TDLS link with |peer_mac_address| on |wifi_interface|.
501
502        @param wifi_interface: string name of interface to establish a link on.
503        @param peer_mac_address: string mac address of the TDLS peer device.
504
505        @return True if it the operation was initiated; False otherwise
506
507        """
508        device_object = self._wifi_proxy.find_object(
509                self.DBUS_DEVICE, {'Name': wifi_interface})
510        if device_object is None:
511            return False
512        device_object.PerformTDLSOperation('Setup', peer_mac_address)
513        return True
514
515
516    @xmlrpc_server.dbus_safe(False)
517    def query_tdls_link(self, wifi_interface, peer_mac_address):
518        """Query the TDLS link with |peer_mac_address| on |wifi_interface|.
519
520        @param wifi_interface: string name of interface to establish a link on.
521        @param peer_mac_address: string mac address of the TDLS peer device.
522
523        @return string indicating the current TDLS link status.
524
525        """
526        device_object = self._wifi_proxy.find_object(
527                self.DBUS_DEVICE, {'Name': wifi_interface})
528        if device_object is None:
529            return None
530        return self._wifi_proxy.dbus2primitive(
531                device_object.PerformTDLSOperation('Status', peer_mac_address))
532
533
534    @xmlrpc_server.dbus_safe(False)
535    def add_wake_packet_source(self, wifi_interface, source_ip):
536        """Set up the NIC to wake on packets from the given source IP.
537
538        @param wifi_interface: string name of interface to establish WoWLAN on.
539        @param source_ip: string IP address of packet source, i.e. "127.0.0.1"
540
541        @return True on success, False otherwise.
542
543        """
544        device_object = self._wifi_proxy.find_object(
545                self.DBUS_DEVICE, {'Name': wifi_interface})
546        if device_object is None:
547            return False
548        device_object.AddWakeOnPacketConnection(source_ip)
549        return True
550
551
552    @xmlrpc_server.dbus_safe(False)
553    def remove_wake_packet_source(self, wifi_interface, source_ip):
554        """Stop waking on packets from the given source IP.
555
556        @param wifi_interface: string name of interface to establish WoWLAN on.
557        @param source_ip: string IP address of packet source, i.e. "127.0.0.1"
558
559        @return True on success, False otherwise.
560
561        """
562        device_object = self._wifi_proxy.find_object(
563                self.DBUS_DEVICE, {'Name': wifi_interface})
564        if device_object is None:
565            return False
566        device_object.RemoveWakeOnPacketConnection(source_ip)
567        return True
568
569
570    @xmlrpc_server.dbus_safe(False)
571    def remove_all_wake_packet_sources(self, wifi_interface):
572        """Stop waking on packets from any IP.
573
574        @param wifi_interface: string name of interface to establish WoWLAN on.
575
576        @return True on success, False otherwise.
577
578        """
579        device_object = self._wifi_proxy.find_object(
580                self.DBUS_DEVICE, {'Name': wifi_interface})
581        if device_object is None:
582            return False
583        device_object.RemoveAllWakeOnPacketConnections()
584        return True
585
586
587
588if __name__ == '__main__':
589    logging.basicConfig(level=logging.DEBUG)
590    handler = logging.handlers.SysLogHandler(address = '/dev/log')
591    formatter = logging.Formatter(
592            'shill_xmlrpc_server: [%(levelname)s] %(message)s')
593    handler.setFormatter(formatter)
594    logging.getLogger().addHandler(handler)
595    logging.debug('shill_xmlrpc_server main...')
596    server = xmlrpc_server.XmlRpcServer('localhost',
597                                         constants.SHILL_XMLRPC_SERVER_PORT)
598    server.register_delegate(ShillXmlRpcDelegate())
599    server.run()
600