1#!/usr/bin/env 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 base64
8import json
9import logging
10import logging.handlers
11
12import common
13from autotest_lib.client.common_lib.cros.bluetooth import bluetooth_sdp_socket
14from autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket
15from autotest_lib.client.cros import constants
16from autotest_lib.client.cros import xmlrpc_server
17
18
19class BluetoothTesterXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
20    """Exposes Tester methods called remotely during Bluetooth autotests.
21
22    All instance methods of this object without a preceding '_' are exposed via
23    an XML-RPC server. This is not a stateless handler object, which means that
24    if you store state inside the delegate, that state will remain around for
25    future calls.
26    """
27
28    BR_EDR_LE_PROFILE = (
29            bluetooth_socket.MGMT_SETTING_POWERED |
30            bluetooth_socket.MGMT_SETTING_CONNECTABLE |
31            bluetooth_socket.MGMT_SETTING_PAIRABLE |
32            bluetooth_socket.MGMT_SETTING_SSP |
33            bluetooth_socket.MGMT_SETTING_BREDR |
34            bluetooth_socket.MGMT_SETTING_LE)
35
36    LE_PROFILE = (
37            bluetooth_socket.MGMT_SETTING_POWERED |
38            bluetooth_socket.MGMT_SETTING_CONNECTABLE |
39            bluetooth_socket.MGMT_SETTING_PAIRABLE |
40            bluetooth_socket.MGMT_SETTING_LE)
41
42    PROFILE_SETTINGS = {
43        'computer': BR_EDR_LE_PROFILE,
44        'peripheral': LE_PROFILE
45    }
46
47    PROFILE_CLASS = {
48        'computer': 0x000104,
49        'peripheral': None
50    }
51
52    PROFILE_NAMES = {
53        'computer': ('ChromeOS Bluetooth Tester', 'Tester'),
54        'peripheral': ('ChromeOS Bluetooth Tester', 'Tester')
55    }
56
57
58    def __init__(self):
59        super(BluetoothTesterXmlRpcDelegate, self).__init__()
60
61        # Open the Bluetooth Control socket to the kernel which provides us
62        # the needed raw management access to the Bluetooth Host Subsystem.
63        self._control = bluetooth_socket.BluetoothControlSocket()
64        # Open the Bluetooth SDP socket to the kernel which provides us the
65        # needed interface to use SDP commands.
66        self._sdp = bluetooth_sdp_socket.BluetoothSDPSocket()
67        # This is almost a constant, but it might not be forever.
68        self.index = 0
69
70
71    def setup(self, profile):
72        """Set up the tester with the given profile.
73
74        @param profile: Profile to use for this test, valid values are:
75                computer - a standard computer profile
76
77        @return True on success, False otherwise.
78
79        """
80        profile_settings = self.PROFILE_SETTINGS[profile]
81        profile_class = self.PROFILE_CLASS[profile]
82        (profile_name, profile_short_name) = self.PROFILE_NAMES[profile]
83
84        # Make sure the controller actually exists.
85        if self.index not in self._control.read_index_list():
86            logging.warning('Bluetooth Controller missing on tester')
87            return False
88
89        # Make sure all of the settings are supported by the controller.
90        ( address, bluetooth_version, manufacturer_id,
91          supported_settings, current_settings, class_of_device,
92          name, short_name ) = self._control.read_info(self.index)
93        if profile_settings & supported_settings != profile_settings:
94            logging.warning('Controller does not support requested settings')
95            logging.debug('Supported: %b; Requested: %b', supported_settings,
96                          profile_settings)
97            return False
98
99        # The high 8 bits of class_of_device is part of Class of Service field
100        # which are not actually updated in kernel with
101        # self._control.set_device_class() below due to a bug in kernel
102        # mgmt.c: set_dev_class(). Hence, we should keep these bits in
103        # profile_class to make the test setup correctly.
104        # Refer to this link about Class of Device/Service bits.
105        #   https://www.bluetooth.com/specifications/assigned-numbers/baseband
106        # Since the class of device is used as an indication only and is not
107        # practically useful in autotest, the service class bits are just
108        # copied from previous self._control.read_info() request.
109        # Refer to Bluetooth Spec. 4.2, "Vol 3, Part C, 3.2.4.4 Usage" about
110        # why it is not actually important.
111        # Refer to "Vol 2. Part E, 7.3.26 Write Class of Device Command" about
112        # the correct parameters to pass to set_device_class() which require
113        # 3 bytes instead of 2 bytes.
114        # Remove the following statement which modifies profile_class only
115        # when kernel is fixed and 3 bytes are passed in set_dev_class().
116        # However, this is of very low priority.
117        profile_class = ((class_of_device & 0xFF0000) |
118                         (profile_class & 0x00FFFF))
119
120        # Before beginning, force the adapter power off, even if it's already
121        # off; this is enough to persuade an AP-mode Intel chip to accept
122        # settings.
123        if not self._control.set_powered(self.index, False):
124            logging.warning('Failed to power off adapter to accept settings')
125            return False
126
127        # Set the controller up as either BR/EDR only, LE only or Dual Mode.
128        # This is a bit tricky because it rejects commands outright unless
129        # it's in dual mode, so we actually have to figure out what changes
130        # we have to make, and we have to turn things on before we turn them
131        # off.
132        turn_on = (current_settings ^ profile_settings) & profile_settings
133        if turn_on & bluetooth_socket.MGMT_SETTING_BREDR:
134            if self._control.set_bredr(self.index, True) is None:
135                logging.warning('Failed to enable BR/EDR')
136                return False
137        if turn_on & bluetooth_socket.MGMT_SETTING_LE:
138            if self._control.set_le(self.index, True) is None:
139                logging.warning('Failed to enable LE')
140                return False
141
142        turn_off = (current_settings ^ profile_settings) & current_settings
143        if turn_off & bluetooth_socket.MGMT_SETTING_BREDR:
144            if self._control.set_bredr(self.index, False) is None:
145                logging.warning('Failed to disable BR/EDR')
146                return False
147        if turn_off & bluetooth_socket.MGMT_SETTING_LE:
148            if self._control.set_le(self.index, False) is None:
149                logging.warning('Failed to disable LE')
150                return False
151        if turn_off & bluetooth_socket.MGMT_SETTING_SECURE_CONNECTIONS:
152            if self._control.set_secure_connections(self.index, False) is None:
153                logging.warning('Failed to disable secure connections')
154                return False
155
156        # Adjust settings that are BR/EDR specific that we need to set before
157        # powering on the adapter, and would be rejected otherwise.
158        if profile_settings & bluetooth_socket.MGMT_SETTING_BREDR:
159            if (self._control.set_link_security(
160                    self.index,
161                    (profile_settings &
162                            bluetooth_socket.MGMT_SETTING_LINK_SECURITY))
163                        is None):
164                logging.warning('Failed to set link security setting')
165                return False
166            if (self._control.set_ssp(
167                    self.index,
168                    profile_settings & bluetooth_socket.MGMT_SETTING_SSP)
169                        is None):
170                logging.warning('Failed to set SSP setting')
171                return False
172            if (self._control.set_hs(
173                    self.index,
174                    profile_settings & bluetooth_socket.MGMT_SETTING_HS)
175                        is None):
176                logging.warning('Failed to set High Speed setting')
177                return False
178
179            # Split our the major and minor class; it's listed as a kernel bug
180            # that we supply these to the kernel without shifting the bits over
181            # to take out the CoD format field, so this might have to change
182            # one day.
183            major_class = (profile_class & 0x00ff00) >> 8
184            minor_class = profile_class & 0x0000ff
185            if (self._control.set_device_class(
186                    self.index, major_class, minor_class)
187                        is None):
188                logging.warning('Failed to set device class')
189                return False
190
191        # Setup generic settings that apply to either BR/EDR, LE or dual-mode
192        # that still require the power to be off.
193        if (self._control.set_connectable(
194                self.index,
195                profile_settings & bluetooth_socket.MGMT_SETTING_CONNECTABLE)
196                    is None):
197            logging.warning('Failed to set connectable setting')
198            return False
199        if (self._control.set_pairable(
200                self.index,
201                profile_settings & bluetooth_socket.MGMT_SETTING_PAIRABLE)
202                    is None):
203            logging.warning('Failed to set pairable setting')
204            return False
205
206        if (self._control.set_local_name(
207                    self.index, profile_name, profile_short_name)
208                    is None):
209            logging.warning('Failed to set local name')
210            return False
211
212        # Check and set discoverable property
213        if ((profile_settings ^ current_settings) &
214                    bluetooth_socket.MGMT_SETTING_DISCOVERABLE):
215            logging.debug('Set discoverable to %x ',
216                          profile_settings &
217                          bluetooth_socket.MGMT_SETTING_DISCOVERABLE)
218            if self._control.set_discoverable(
219                   self.index,
220                   profile_settings &
221                   bluetooth_socket.MGMT_SETTING_DISCOVERABLE) is None:
222                logging.warning('Failed to set discoverable setting')
223                return False
224
225        # Now the settings have been set, power up the adapter.
226        if not self._control.set_powered(
227                self.index,
228                profile_settings & bluetooth_socket.MGMT_SETTING_POWERED):
229            logging.warning('Failed to set powered setting')
230            return False
231
232        # Fast connectable can only be set once the controller is powered,
233        # and only when BR/EDR is enabled.
234        if profile_settings & bluetooth_socket.MGMT_SETTING_BREDR:
235            # Wait for the device class set event, this happens after the
236            # power up "command complete" event when we've pre-set the class
237            # even though it's a side-effect of doing that.
238            self._control.wait_for_events(
239                    self.index,
240                    ( bluetooth_socket.MGMT_EV_CLASS_OF_DEV_CHANGED, ))
241
242            if (self._control.set_fast_connectable(
243                    self.index,
244                    profile_settings &
245                    bluetooth_socket.MGMT_SETTING_FAST_CONNECTABLE)
246                        is None):
247                logging.warning('Failed to set fast connectable setting')
248                return False
249
250        # Fetch the settings again and make sure they're all set correctly,
251        # including the BR/EDR flag.
252        ( address, bluetooth_version, manufacturer_id,
253          supported_settings, current_settings, class_of_device,
254          name, short_name ) = self._control.read_info(self.index)
255
256        # Check generic settings.
257        if profile_settings != current_settings:
258            logging.warning('Controller settings did not match those set: '
259                            '%x != %x', current_settings, profile_settings)
260            return False
261        if name != profile_name:
262            logging.warning('Local name did not match that set: "%s" != "%s"',
263                            name, profile_name)
264            return False
265        elif short_name != profile_short_name:
266            logging.warning('Short name did not match that set: "%s" != "%s"',
267                            short_name, profile_short_name)
268            return False
269
270        # Check BR/EDR specific settings.
271        if profile_settings & bluetooth_socket.MGMT_SETTING_BREDR:
272            if class_of_device != profile_class:
273                if class_of_device & 0x00ffff == profile_class & 0x00ffff:
274                    logging.warning('Class of device matched that set, but '
275                                    'Service Class field did not: %x != %x '
276                                    'Reboot Tester? ',
277                                    class_of_device, profile_class)
278                else:
279                    logging.warning('Class of device did not match that set: '
280                                    '%x != %x', class_of_device, profile_class)
281                return False
282
283        return True
284
285
286    def set_discoverable(self, discoverable, timeout=0):
287        """Set the discoverable state of the controller.
288
289        @param discoverable: Whether controller should be discoverable.
290        @param timeout: Timeout in seconds before disabling discovery again,
291                ignored when discoverable is False, must not be zero when
292                discoverable is True.
293
294        @return True on success, False otherwise.
295
296        """
297        settings = self._control.set_discoverable(self.index,
298                                                  discoverable, timeout)
299        return settings & bluetooth_socket.MGMT_SETTING_DISCOVERABLE
300
301
302    def read_info(self):
303        """Read the adapter information from the Kernel.
304
305        @return the information as a JSON-encoded tuple of:
306          ( address, bluetooth_version, manufacturer_id,
307            supported_settings, current_settings, class_of_device,
308            name, short_name )
309
310        """
311        return json.dumps(self._control.read_info(self.index))
312
313
314    def set_advertising(self, advertising):
315        """Set the whether the controller is advertising via LE.
316
317        @param advertising: Whether controller should advertise via LE.
318
319        @return True on success, False otherwise.
320
321        """
322        settings = self._control.set_advertising(self.index, advertising)
323        return settings & bluetooth_socket.MGMT_SETTING_ADVERTISING
324
325
326    def discover_devices(self, br_edr=True, le_public=True, le_random=True):
327        """Discover remote devices.
328
329        Activates device discovery and collects the set of devices found,
330        returning them as a list.
331
332        @param br_edr: Whether to detect BR/EDR devices.
333        @param le_public: Whether to detect LE Public Address devices.
334        @param le_random: Whether to detect LE Random Address devices.
335
336        @return List of devices found as JSON-encoded tuples with the format
337                (address, address_type, rssi, flags, base64-encoded eirdata),
338                or False if discovery could not be started.
339
340        """
341        address_type = 0
342        if br_edr:
343            address_type |= 0x1
344        if le_public:
345            address_type |= 0x2
346        if le_random:
347            address_type |= 0x4
348
349        set_type = self._control.start_discovery(self.index, address_type)
350        if set_type != address_type:
351            logging.warning('Discovery address type did not match that set: '
352                            '%x != %x', set_type, address_type)
353            return False
354
355        devices = self._control.get_discovered_devices(self.index)
356        return json.dumps([
357                (address, address_type, rssi, flags,
358                 base64.encodestring(eirdata))
359                for address, address_type, rssi, flags, eirdata in devices
360        ])
361
362
363    def connect(self, address):
364        """Connect to device with the given address
365
366        @param address: Bluetooth address.
367
368        """
369        self._sdp.connect(address)
370        return True
371
372
373    def service_search_request(self, uuids, max_rec_cnt, preferred_size=32,
374                               forced_pdu_size=None, invalid_request=False):
375        """Send a Service Search Request
376
377        @param uuids: List of UUIDs (as integers) to look for.
378        @param max_rec_cnt: Maximum count of returned service records.
379        @param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
380        @param forced_pdu_size: Use certain PDU size parameter instead of
381               calculating actual length of sequence.
382        @param invalid_request: Whether to send request with intentionally
383               invalid syntax for testing purposes (bool flag).
384
385        @return list of found services' service record handles or Error Code
386
387        """
388        return json.dumps(
389                self._sdp.service_search_request(
390                 uuids, max_rec_cnt, preferred_size, forced_pdu_size,
391                 invalid_request)
392        )
393
394
395    def service_attribute_request(self, handle, max_attr_byte_count, attr_ids,
396                                  forced_pdu_size=None, invalid_request=None):
397        """Send a Service Attribute Request
398
399        @param handle: service record from which attribute values are to be
400               retrieved.
401        @param max_attr_byte_count: maximum number of bytes of attribute data to
402               be returned in the response to this request.
403        @param attr_ids: a list, where each element is either an attribute ID
404               or a range of attribute IDs.
405        @param forced_pdu_size: Use certain PDU size parameter instead of
406               calculating actual length of sequence.
407        @param invalid_request: Whether to send request with intentionally
408               invalid syntax for testing purposes (string with raw request).
409
410        @return list of found attributes IDs and their values or Error Code
411
412        """
413        return json.dumps(
414                self._sdp.service_attribute_request(
415                 handle, max_attr_byte_count, attr_ids, forced_pdu_size,
416                 invalid_request)
417        )
418
419
420    def service_search_attribute_request(self, uuids, max_attr_byte_count,
421                                         attr_ids, preferred_size=32,
422                                         forced_pdu_size=None,
423                                         invalid_request=None):
424        """Send a Service Search Attribute Request
425
426        @param uuids: list of UUIDs (as integers) to look for.
427        @param max_attr_byte_count: maximum number of bytes of attribute data to
428               be returned in the response to this request.
429        @param attr_ids: a list, where each element is either an attribute ID
430               or a range of attribute IDs.
431        @param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
432        @param forced_pdu_size: Use certain PDU size parameter instead of
433               calculating actual length of sequence.
434        @param invalid_request: Whether to send request with intentionally
435               invalid syntax for testing purposes (string to be prepended
436               to correct request).
437
438        @return list of found attributes IDs and their values or Error Code
439
440        """
441        return json.dumps(
442                self._sdp.service_search_attribute_request(
443                 uuids, max_attr_byte_count, attr_ids, preferred_size,
444                 forced_pdu_size, invalid_request)
445        )
446
447
448if __name__ == '__main__':
449    logging.basicConfig(level=logging.DEBUG)
450    handler = logging.handlers.SysLogHandler(address = '/dev/log')
451    formatter = logging.Formatter(
452            'bluetooth_tester_xmlrpc_server: [%(levelname)s] %(message)s')
453    handler.setFormatter(formatter)
454    logging.getLogger().addHandler(handler)
455    logging.debug('bluetooth_tester_xmlrpc_server main...')
456    server = xmlrpc_server.XmlRpcServer(
457            'localhost',
458            constants.BLUETOOTH_TESTER_XMLRPC_SERVER_PORT)
459    server.register_delegate(BluetoothTesterXmlRpcDelegate())
460    server.run()
461