# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import base64
import json
from autotest_lib.client.cros import constants
from autotest_lib.server import autotest
from autotest_lib.server import hosts
from autotest_lib.server.cros import dnsname_mangler
class BluetoothTester(object):
"""BluetoothTester is a thin layer of logic over a remote tester.
The Autotest host object representing the remote tester, passed to this
class on initialization, can be accessed from its host property.
"""
XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
XMLRPC_LOG_PATH = '/var/log/bluetooth_xmlrpc_tester.log'
def __init__(self, tester_host):
"""Construct a BluetoothTester.
@param tester_host: host object representing a remote host.
"""
self.host = tester_host
# Make sure the client library is on the device so that the proxy code
# is there when we try to call it.
client_at = autotest.Autotest(self.host)
client_at.install()
# Start up the XML-RPC proxy on the tester.
self._proxy = self.host.rpc_server_tracker.xmlrpc_connect(
constants.BLUETOOTH_TESTER_XMLRPC_SERVER_COMMAND,
constants.BLUETOOTH_TESTER_XMLRPC_SERVER_PORT,
command_name=
constants.BLUETOOTH_TESTER_XMLRPC_SERVER_CLEANUP_PATTERN,
ready_test_name=
constants.BLUETOOTH_TESTER_XMLRPC_SERVER_READY_METHOD,
timeout_seconds=self.XMLRPC_BRINGUP_TIMEOUT_SECONDS,
logfile=self.XMLRPC_LOG_PATH)
def setup(self, profile):
"""Set up the tester with the given profile.
@param profile: Profile to use for this test, valid values are:
computer - a standard computer profile
@return True on success, False otherwise.
"""
return self._proxy.setup(profile)
def set_discoverable(self, discoverable, timeout=0):
"""Set the discoverable state of the controller.
@param discoverable: Whether controller should be discoverable.
@param timeout: Timeout in seconds before disabling discovery again,
ignored when discoverable is False, must not be zero when
discoverable is True.
@return True on success, False otherwise.
"""
return self._proxy.set_discoverable(discoverable, timeout)
def read_info(self):
"""Read the adapter information from the Kernel.
@return the information as a JSON-encoded tuple of:
( address, bluetooth_version, manufacturer_id,
supported_settings, current_settings, class_of_device,
name, short_name )
"""
return json.loads(self._proxy.read_info())
def set_advertising(self, advertising):
"""Set the whether the controller is advertising via LE.
@param advertising: Whether controller should advertise via LE.
@return True on success, False otherwise.
"""
return self._proxy.set_advertising(advertising)
def discover_devices(self, br_edr=True, le_public=True, le_random=True):
"""Discover remote devices.
Activates device discovery and collects the set of devices found,
returning them as a list.
@param br_edr: Whether to detect BR/EDR devices.
@param le_public: Whether to detect LE Public Address devices.
@param le_random: Whether to detect LE Random Address devices.
@return List of devices found as tuples with the format
(address, address_type, rssi, flags, base64-encoded eirdata),
or False if discovery could not be started.
"""
devices = self._proxy.discover_devices(br_edr, le_public, le_random)
if devices == False:
return False
return (
(address, address_type, rssi, flags,
base64.decodestring(eirdata))
for address, address_type, rssi, flags, eirdata
in json.loads(devices)
)
def copy_logs(self, destination):
"""Copy the logs generated by this tester to a given location.
@param destination: destination directory for the logs.
"""
self.host.collect_logs(self.XMLRPC_LOG_PATH, destination)
def close(self):
"""Tear down state associated with the client."""
# This kills the RPC server.
self.host.close()
def connect(self, address):
"""Connect to device with the given address
@param address: Bluetooth address.
"""
self._proxy.connect(address)
def service_search_request(self, uuids, max_rec_cnt, preferred_size=32,
forced_pdu_size=None, invalid_request=False):
"""Send a Service Search Request
@param uuids: List of UUIDs (as integers) to look for.
@param max_rec_cnt: Maximum count of returned service records.
@param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
@param forced_pdu_size: Use certain PDU size parameter instead of
calculating actual length of sequence.
@param invalid_request: Whether to send request with intentionally
invalid syntax for testing purposes (bool flag).
@return list of found services' service record handles or Error Code
"""
return json.loads(
self._proxy.service_search_request(
uuids, max_rec_cnt, preferred_size, forced_pdu_size,
invalid_request)
)
def service_attribute_request(self, handle, max_attr_byte_count, attr_ids,
forced_pdu_size=None, invalid_request=None):
"""Send a Service Attribute Request
@param handle: service record from which attribute values are to be
retrieved.
@param max_attr_byte_count: maximum number of bytes of attribute data to
be returned in the response to this request.
@param attr_ids: a list, where each element is either an attribute ID
or a range of attribute IDs.
@param forced_pdu_size: Use certain PDU size parameter instead of
calculating actual length of sequence.
@param invalid_request: Whether to send request with intentionally
invalid syntax for testing purposes (string with raw request).
@return list of found attributes IDs and their values or Error Code
"""
return json.loads(
self._proxy.service_attribute_request(
handle, max_attr_byte_count, attr_ids, forced_pdu_size,
invalid_request)
)
def service_search_attribute_request(self, uuids, max_attr_byte_count,
attr_ids, preferred_size=32,
forced_pdu_size=None,
invalid_request=None):
"""Send a Service Search Attribute Request
@param uuids: list of UUIDs (as integers) to look for.
@param max_attr_byte_count: maximum number of bytes of attribute data to
be returned in the response to this request.
@param attr_ids: a list, where each element is either an attribute ID
or a range of attribute IDs.
@param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
@param forced_pdu_size: Use certain PDU size parameter instead of
calculating actual length of sequence.
@param invalid_request: Whether to send request with intentionally
invalid syntax for testing purposes (string to be prepended
to correct request).
@return list of found attributes IDs and their values or Error Code
"""
return json.loads(
self._proxy.service_search_attribute_request(
uuids, max_attr_byte_count, attr_ids, preferred_size,
forced_pdu_size, invalid_request)
)
def create_host_from(device_host, args=None):
"""Creates a host object for the Tester associated with a DUT.
The IP address or the hostname can be specified in the 'tester' member of
the argument dictionary. When not present it is derived from the hostname
of the DUT by appending '-router' to the first part.
Will raise an exception if there isn't a tester for the DUT, or if the DUT
is specified as an IP address and thus the hostname cannot be derived.
@param device_host: Autotest host object for the DUT.
@param args: Dictionary of arguments passed to the test.
@return Autotest host object for the Tester.
"""
cmdline_override = args.get('tester', None)
hostname = dnsname_mangler.get_tester_addr(
device_host.hostname,
cmdline_override=cmdline_override)
return hosts.create_host(hostname)