14dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant#!/usr/bin/env python
24dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
34dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
44dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant# Use of this source code is governed by a BSD-style license that can be
54dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant# found in the LICENSE file.
64dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
7da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnantimport base64
8da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnantimport json
94dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnantimport logging
104dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnantimport logging.handlers
114dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
124dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnantimport common
13448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhovfrom autotest_lib.client.common_lib.cros.bluetooth import bluetooth_sdp_socket
146946f948018ca7ce9263e9a9b5ff0eee676c1f68Christopher Wileyfrom autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket
15e0b08e6170b57f90262726eb7f04e059cb47419cHsinyu Chaofrom autotest_lib.client.cros import constants
16ec0b71daa4d20f466724523821e4f4908d7e7d5aChristopher Wileyfrom autotest_lib.client.cros import xmlrpc_server
174dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
184dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
194dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnantclass BluetoothTesterXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
20448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov    """Exposes Tester methods called remotely during Bluetooth autotests.
214dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
224dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    All instance methods of this object without a preceding '_' are exposed via
234dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    an XML-RPC server. This is not a stateless handler object, which means that
24aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant    if you store state inside the delegate, that state will remain around for
254dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    future calls.
264dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    """
274dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
28da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    BR_EDR_LE_PROFILE = (
29da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            bluetooth_socket.MGMT_SETTING_POWERED |
30da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            bluetooth_socket.MGMT_SETTING_CONNECTABLE |
31da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            bluetooth_socket.MGMT_SETTING_PAIRABLE |
32da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            bluetooth_socket.MGMT_SETTING_SSP |
33da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            bluetooth_socket.MGMT_SETTING_BREDR |
34da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            bluetooth_socket.MGMT_SETTING_LE)
35da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
36f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant    LE_PROFILE = (
37f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            bluetooth_socket.MGMT_SETTING_POWERED |
38f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            bluetooth_socket.MGMT_SETTING_CONNECTABLE |
39f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            bluetooth_socket.MGMT_SETTING_PAIRABLE |
40f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            bluetooth_socket.MGMT_SETTING_LE)
41f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant
42da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    PROFILE_SETTINGS = {
43da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        'computer': BR_EDR_LE_PROFILE,
44f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        'peripheral': LE_PROFILE
45da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    }
46da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
47da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    PROFILE_CLASS = {
48da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        'computer': 0x000104,
49f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        'peripheral': None
50da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    }
51da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
52da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    PROFILE_NAMES = {
53da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        'computer': ('ChromeOS Bluetooth Tester', 'Tester'),
54f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        'peripheral': ('ChromeOS Bluetooth Tester', 'Tester')
55da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    }
56da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
57da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
584dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    def __init__(self):
59da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        super(BluetoothTesterXmlRpcDelegate, self).__init__()
60da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
61da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        # Open the Bluetooth Control socket to the kernel which provides us
62da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        # the needed raw management access to the Bluetooth Host Subsystem.
63da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        self._control = bluetooth_socket.BluetoothControlSocket()
64448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        # Open the Bluetooth SDP socket to the kernel which provides us the
65448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        # needed interface to use SDP commands.
66448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        self._sdp = bluetooth_sdp_socket.BluetoothSDPSocket()
67da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        # This is almost a constant, but it might not be forever.
68da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        self.index = 0
69da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
70da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
71da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    def setup(self, profile):
72da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        """Set up the tester with the given profile.
73da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
74da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        @param profile: Profile to use for this test, valid values are:
75da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                computer - a standard computer profile
76da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
77da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        @return True on success, False otherwise.
78da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
79da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        """
80da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        profile_settings = self.PROFILE_SETTINGS[profile]
81c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant        profile_class = self.PROFILE_CLASS[profile]
82c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant        (profile_name, profile_short_name) = self.PROFILE_NAMES[profile]
83c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant
84e56271c1898aaae33231a206b1ea28c5e6afb529Scott James Remnant        # Make sure the controller actually exists.
85e56271c1898aaae33231a206b1ea28c5e6afb529Scott James Remnant        if self.index not in self._control.read_index_list():
86e56271c1898aaae33231a206b1ea28c5e6afb529Scott James Remnant            logging.warning('Bluetooth Controller missing on tester')
87e56271c1898aaae33231a206b1ea28c5e6afb529Scott James Remnant            return False
88e56271c1898aaae33231a206b1ea28c5e6afb529Scott James Remnant
89da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        # Make sure all of the settings are supported by the controller.
90da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        ( address, bluetooth_version, manufacturer_id,
91da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant          supported_settings, current_settings, class_of_device,
92da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant          name, short_name ) = self._control.read_info(self.index)
93da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        if profile_settings & supported_settings != profile_settings:
94da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            logging.warning('Controller does not support requested settings')
95c026987fe3ef3f5d16f956176d4f52c464b46856Katherine Threlkeld            logging.debug('Supported: %b; Requested: %b', supported_settings,
96c026987fe3ef3f5d16f956176d4f52c464b46856Katherine Threlkeld                          profile_settings)
97da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            return False
98da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
99589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant        # Before beginning, force the adapter power off, even if it's already
100589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant        # off; this is enough to persuade an AP-mode Intel chip to accept
101589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant        # settings.
102589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant        if not self._control.set_powered(self.index, False):
103589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant            logging.warning('Failed to power off adapter to accept settings')
104589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant            return False
1058316318f409a01e1b7d1e96b447bb6942a2d7752Scott James Remnant
106f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # Set the controller up as either BR/EDR only, LE only or Dual Mode.
107f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # This is a bit tricky because it rejects commands outright unless
108f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # it's in dual mode, so we actually have to figure out what changes
109f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # we have to make, and we have to turn things on before we turn them
110f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # off.
111f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        turn_on = (current_settings ^ profile_settings) & profile_settings
112f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        if turn_on & bluetooth_socket.MGMT_SETTING_BREDR:
113f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            if self._control.set_bredr(self.index, True) is None:
114f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                logging.warning('Failed to enable BR/EDR')
115f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                return False
116f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        if turn_on & bluetooth_socket.MGMT_SETTING_LE:
117f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            if self._control.set_le(self.index, True) is None:
118f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                logging.warning('Failed to enable LE')
119f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                return False
120f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant
121f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        turn_off = (current_settings ^ profile_settings) & current_settings
122f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        if turn_off & bluetooth_socket.MGMT_SETTING_BREDR:
123f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            if self._control.set_bredr(self.index, False) is None:
124f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                logging.warning('Failed to disable BR/EDR')
125f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                return False
126f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        if turn_off & bluetooth_socket.MGMT_SETTING_LE:
127f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            if self._control.set_le(self.index, False) is None:
128f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                logging.warning('Failed to disable LE')
129f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                return False
130f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant
131f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # Adjust settings that are BR/EDR specific that we need to set before
132f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # powering on the adapter, and would be rejected otherwise.
133f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        if profile_settings & bluetooth_socket.MGMT_SETTING_BREDR:
134f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            if (self._control.set_link_security(
135f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    self.index,
136f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    (profile_settings &
137f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                            bluetooth_socket.MGMT_SETTING_LINK_SECURITY))
138f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                        is None):
139f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                logging.warning('Failed to set link security setting')
140f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                return False
141f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            if (self._control.set_ssp(
142f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    self.index,
143f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    profile_settings & bluetooth_socket.MGMT_SETTING_SSP)
144f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                        is None):
145f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                logging.warning('Failed to set SSP setting')
146f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                return False
147f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            if (self._control.set_hs(
148f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    self.index,
149f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    profile_settings & bluetooth_socket.MGMT_SETTING_HS)
150f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                        is None):
151f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                logging.warning('Failed to set High Speed setting')
152f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                return False
153f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant
154c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            # Split our the major and minor class; it's listed as a kernel bug
155c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            # that we supply these to the kernel without shifting the bits over
156c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            # to take out the CoD format field, so this might have to change
157c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            # one day.
158c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            major_class = (profile_class & 0x00ff00) >> 8
159c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            minor_class = profile_class & 0x0000ff
160c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            if (self._control.set_device_class(
161c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                    self.index, major_class, minor_class)
162c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                        is None):
163c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                logging.warning('Failed to set device class')
164c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                return False
165c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant
166f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # Setup generic settings that apply to either BR/EDR, LE or dual-mode
167f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # that still require the power to be off.
168589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant        if (self._control.set_connectable(
169da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                self.index,
170589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant                profile_settings & bluetooth_socket.MGMT_SETTING_CONNECTABLE)
171da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                    is None):
172589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant            logging.warning('Failed to set connectable setting')
173da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            return False
174589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant        if (self._control.set_pairable(
175da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                self.index,
176589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant                profile_settings & bluetooth_socket.MGMT_SETTING_PAIRABLE)
177da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                    is None):
178589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant            logging.warning('Failed to set pairable setting')
179da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            return False
180589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant
181c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant        if (self._control.set_local_name(
182c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                    self.index, profile_name, profile_short_name)
183c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                    is None):
184c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            logging.warning('Failed to set local name')
185c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            return False
186c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant
187589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant        # Now the settings have been set, power up the adapter.
188589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant        if not self._control.set_powered(
189da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                self.index,
190589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant                profile_settings & bluetooth_socket.MGMT_SETTING_POWERED):
191589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant            logging.warning('Failed to set powered setting')
192589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant            return False
193589b7fff8391bc20acc748c2a5de63c773599077Scott James Remnant
194f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # Fast connectable can only be set once the controller is powered,
195f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        # and only when BR/EDR is enabled.
196f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant        if profile_settings & bluetooth_socket.MGMT_SETTING_BREDR:
197c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            # Wait for the device class set event, this happens after the
198c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            # power up "command complete" event when we've pre-set the class
199c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            # even though it's a side-effect of doing that.
200c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant            self._control.wait_for_events(
201c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                    self.index,
202c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                    ( bluetooth_socket.MGMT_EV_CLASS_OF_DEV_CHANGED, ))
203c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant
204f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant            if (self._control.set_fast_connectable(
205f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    self.index,
206f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    profile_settings &
207f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                    bluetooth_socket.MGMT_SETTING_FAST_CONNECTABLE)
208f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                        is None):
209f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                logging.warning('Failed to set fast connectable setting')
210f719e2d8dfc2610974c44186fac641cd215b7460Scott James Remnant                return False
211da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
212da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        # Fetch the settings again and make sure they're all set correctly,
213da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        # including the BR/EDR flag.
214da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        ( address, bluetooth_version, manufacturer_id,
215da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant          supported_settings, current_settings, class_of_device,
216da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant          name, short_name ) = self._control.read_info(self.index)
2173988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov
2183988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov        # Check generic settings.
219da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        if profile_settings != current_settings:
220da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            logging.warning('Controller settings did not match those set: '
221da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                            '%x != %x', current_settings, profile_settings)
222da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            return False
223c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant        if name != profile_name:
224da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            logging.warning('Local name did not match that set: "%s" != "%s"',
225c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                            name, profile_name)
226da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            return False
227c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant        elif short_name != profile_short_name:
228da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            logging.warning('Short name did not match that set: "%s" != "%s"',
229c9864b65a425f8806d2400264e52899d2bcdad90Scott James Remnant                            short_name, profile_short_name)
230da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            return False
231da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
2323988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov        # Check BR/EDR specific settings.
2333988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov        if profile_settings & bluetooth_socket.MGMT_SETTING_BREDR:
2343988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov            if class_of_device != profile_class:
2353988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                if class_of_device & 0x00ffff == profile_class & 0x00ffff:
2363988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                    logging.warning('Class of device matched that set, but '
2373988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                                    'Service Class field did not: %x != %x '
2383988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                                    'Reboot Tester? ',
2393988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                                    class_of_device, profile_class)
2403988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                else:
2413988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                    logging.warning('Class of device did not match that set: '
2423988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                                    '%x != %x', class_of_device, profile_class)
2433988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov                return False
2443988665743edec3f0f709e9be7928120fdcf4a47Artem Rakhov
245da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        return True
246da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
247da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
248aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant    def set_discoverable(self, discoverable, timeout=0):
249aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        """Set the discoverable state of the controller.
250aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
251aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        @param discoverable: Whether controller should be discoverable.
252aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        @param timeout: Timeout in seconds before disabling discovery again,
253aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant                ignored when discoverable is False, must not be zero when
254aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant                discoverable is True.
255aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
256aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        @return True on success, False otherwise.
257aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
258aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        """
259aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        settings = self._control.set_discoverable(self.index,
260aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant                                                  discoverable, timeout)
261aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        return settings & bluetooth_socket.MGMT_SETTING_DISCOVERABLE
262aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
263aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
264aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant    def read_info(self):
265aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        """Read the adapter information from the Kernel.
266aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
267aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        @return the information as a JSON-encoded tuple of:
268aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant          ( address, bluetooth_version, manufacturer_id,
269aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant            supported_settings, current_settings, class_of_device,
270aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant            name, short_name )
271aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
272aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        """
273aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant        return json.dumps(self._control.read_info(self.index))
274aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
275aec4edd340e914885cb1fe26e9b8766788009f2fScott James Remnant
276e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant    def set_advertising(self, advertising):
277e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant        """Set the whether the controller is advertising via LE.
278e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant
279e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant        @param advertising: Whether controller should advertise via LE.
280e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant
281e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant        @return True on success, False otherwise.
282e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant
283e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant        """
284e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant        settings = self._control.set_advertising(self.index, advertising)
285e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant        return settings & bluetooth_socket.MGMT_SETTING_ADVERTISING
286e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant
287e686dcc4914aaf44164ce0cb9bd5dd243477fa76Scott James Remnant
288da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant    def discover_devices(self, br_edr=True, le_public=True, le_random=True):
289da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        """Discover remote devices.
290da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
291da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        Activates device discovery and collects the set of devices found,
292da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        returning them as a list.
293da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
294da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        @param br_edr: Whether to detect BR/EDR devices.
295da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        @param le_public: Whether to detect LE Public Address devices.
296da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        @param le_random: Whether to detect LE Random Address devices.
297da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
298da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        @return List of devices found as JSON-encoded tuples with the format
299da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                (address, address_type, rssi, flags, base64-encoded eirdata),
300da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                or False if discovery could not be started.
301da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
302da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        """
303da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        address_type = 0
304da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        if br_edr:
305da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            address_type |= 0x1
306da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        if le_public:
307da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            address_type |= 0x2
308da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        if le_random:
309da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            address_type |= 0x4
310da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
311da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        set_type = self._control.start_discovery(self.index, address_type)
312da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        if set_type != address_type:
313da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            logging.warning('Discovery address type did not match that set: '
314da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                            '%x != %x', set_type, address_type)
315da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant            return False
316da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant
317da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        devices = self._control.get_discovered_devices(self.index)
318da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        return json.dumps([
319da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                (address, address_type, rssi, flags,
320da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                 base64.encodestring(eirdata))
321da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant                for address, address_type, rssi, flags, eirdata in devices
322da9f43cc5c84691d82b1dd9222fea37234cb9a0aScott James Remnant        ])
3234dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
3244dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant
325448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov    def connect(self, address):
326448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        """Connect to device with the given address
327448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
328448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        @param address: Bluetooth address.
329448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
330448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        """
331448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        self._sdp.connect(address)
332448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        return True
333448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
334448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
335ba130384b49108dcfa91ad7751b825c0d0128963Artem Rakhov    def service_search_request(self, uuids, max_rec_cnt, preferred_size=32,
336f42dc83292497e95cf4f41c6463f697b840826e2Artem Rakhov                               forced_pdu_size=None, invalid_request=False):
337448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        """Send a Service Search Request
338448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
339ba130384b49108dcfa91ad7751b825c0d0128963Artem Rakhov        @param uuids: List of UUIDs (as integers) to look for.
340448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        @param max_rec_cnt: Maximum count of returned service records.
341ca443b518bfe8457c73d9a0cddadf8f6972a4c64Artem Rakhov        @param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
342ba130384b49108dcfa91ad7751b825c0d0128963Artem Rakhov        @param forced_pdu_size: Use certain PDU size parameter instead of
343ba130384b49108dcfa91ad7751b825c0d0128963Artem Rakhov               calculating actual length of sequence.
344f42dc83292497e95cf4f41c6463f697b840826e2Artem Rakhov        @param invalid_request: Whether to send request with intentionally
345f42dc83292497e95cf4f41c6463f697b840826e2Artem Rakhov               invalid syntax for testing purposes (bool flag).
346448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
347ba130384b49108dcfa91ad7751b825c0d0128963Artem Rakhov        @return list of found services' service record handles or Error Code
348448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
349448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov        """
350275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov        return json.dumps(
351275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                self._sdp.service_search_request(
352275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                 uuids, max_rec_cnt, preferred_size, forced_pdu_size,
353275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                 invalid_request)
354275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov        )
355448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
356448d52cd25de24ffd96a882a4445fc7c8326e413Artem Rakhov
357af4e4a6932ecbb6744e48f4f9e2dcc12f3ba9d50Artem Rakhov    def service_attribute_request(self, handle, max_attr_byte_count, attr_ids,
358af4e4a6932ecbb6744e48f4f9e2dcc12f3ba9d50Artem Rakhov                                  forced_pdu_size=None, invalid_request=None):
359ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov        """Send a Service Attribute Request
360ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov
361ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov        @param handle: service record from which attribute values are to be
362ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov               retrieved.
363ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov        @param max_attr_byte_count: maximum number of bytes of attribute data to
364ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov               be returned in the response to this request.
365ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov        @param attr_ids: a list, where each element is either an attribute ID
366ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov               or a range of attribute IDs.
367af4e4a6932ecbb6744e48f4f9e2dcc12f3ba9d50Artem Rakhov        @param forced_pdu_size: Use certain PDU size parameter instead of
368af4e4a6932ecbb6744e48f4f9e2dcc12f3ba9d50Artem Rakhov               calculating actual length of sequence.
369af4e4a6932ecbb6744e48f4f9e2dcc12f3ba9d50Artem Rakhov        @param invalid_request: Whether to send request with intentionally
370af4e4a6932ecbb6744e48f4f9e2dcc12f3ba9d50Artem Rakhov               invalid syntax for testing purposes (string with raw request).
371ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov
372ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov        @return list of found attributes IDs and their values or Error Code
373ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov
374ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov        """
375275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov        return json.dumps(
376275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                self._sdp.service_attribute_request(
377275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                 handle, max_attr_byte_count, attr_ids, forced_pdu_size,
378275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                 invalid_request)
379275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov        )
380ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov
381ebf3df32a3bfa32782ffdc8e0ce5bf6d65734d5dArtem Rakhov
382f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov    def service_search_attribute_request(self, uuids, max_attr_byte_count,
383ef5a458439a763afa1937837cdf5b6d7065be74dArtem Rakhov                                         attr_ids, preferred_size=32,
384ef5a458439a763afa1937837cdf5b6d7065be74dArtem Rakhov                                         forced_pdu_size=None,
385ef5a458439a763afa1937837cdf5b6d7065be74dArtem Rakhov                                         invalid_request=None):
386f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov        """Send a Service Search Attribute Request
387f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov
388f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov        @param uuids: list of UUIDs (as integers) to look for.
389f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov        @param max_attr_byte_count: maximum number of bytes of attribute data to
390f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov               be returned in the response to this request.
391f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov        @param attr_ids: a list, where each element is either an attribute ID
392f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov               or a range of attribute IDs.
393f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov        @param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
394ef5a458439a763afa1937837cdf5b6d7065be74dArtem Rakhov        @param forced_pdu_size: Use certain PDU size parameter instead of
395ef5a458439a763afa1937837cdf5b6d7065be74dArtem Rakhov               calculating actual length of sequence.
396ef5a458439a763afa1937837cdf5b6d7065be74dArtem Rakhov        @param invalid_request: Whether to send request with intentionally
397ef5a458439a763afa1937837cdf5b6d7065be74dArtem Rakhov               invalid syntax for testing purposes (string to be prepended
398ef5a458439a763afa1937837cdf5b6d7065be74dArtem Rakhov               to correct request).
399f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov
400f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov        @return list of found attributes IDs and their values or Error Code
401f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov
402f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov        """
403275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov        return json.dumps(
404275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                self._sdp.service_search_attribute_request(
405275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                 uuids, max_attr_byte_count, attr_ids, preferred_size,
406275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov                 forced_pdu_size, invalid_request)
407275ecaf0baf905cf69b0c144aac1ae905857cd9cArtem Rakhov        )
408f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov
409f53eeec1740214b1838f577559ba0acd856e996cArtem Rakhov
4104dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnantif __name__ == '__main__':
4114dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    logging.basicConfig(level=logging.DEBUG)
4124dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    handler = logging.handlers.SysLogHandler(address = '/dev/log')
4139fd7f46552205dc17e1d888632658bdf1a97d603Christopher Wiley    formatter = logging.Formatter(
4149fd7f46552205dc17e1d888632658bdf1a97d603Christopher Wiley            'bluetooth_tester_xmlrpc_server: [%(levelname)s] %(message)s')
4159fd7f46552205dc17e1d888632658bdf1a97d603Christopher Wiley    handler.setFormatter(formatter)
4164dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    logging.getLogger().addHandler(handler)
4174dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    logging.debug('bluetooth_tester_xmlrpc_server main...')
4184dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    server = xmlrpc_server.XmlRpcServer(
4194dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant            'localhost',
4204dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant            constants.BLUETOOTH_TESTER_XMLRPC_SERVER_PORT)
4214dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    server.register_delegate(BluetoothTesterXmlRpcDelegate())
4224dcd73f7d38d89063911c74796db9aa68ff8b064Scott James Remnant    server.run()
423