266 lines
11 KiB
Python
266 lines
11 KiB
Python
# Lint as: python2, python3
|
|
# Copyright 2020 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.
|
|
|
|
"""A class provides most of the test logic for Bluetooth Better Together"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import logging
|
|
import base64
|
|
import json
|
|
|
|
import common
|
|
from autotest_lib.server.cros.bluetooth.bluetooth_adapter_quick_tests import (
|
|
BluetoothAdapterQuickTests)
|
|
from autotest_lib.server.cros.bluetooth.bluetooth_adapter_pairing_tests import (
|
|
BluetoothAdapterPairingTests)
|
|
from autotest_lib.server.cros.bluetooth.bluetooth_adapter_tests import (
|
|
test_retry_and_log)
|
|
from six.moves import range
|
|
|
|
class BluetoothAdapterBetterTogether(BluetoothAdapterQuickTests,
|
|
BluetoothAdapterPairingTests):
|
|
"""A Batch of Bluetooth LE tests for Better Together. This test is written
|
|
as a batch of tests in order to reduce test time, since auto-test ramp up
|
|
time is costly. The batch is using BluetoothAdapterQuickTests wrapper
|
|
methods to start and end a test and a batch of tests.
|
|
|
|
This class can be called to run the entire test batch or to run a
|
|
specific test only
|
|
"""
|
|
|
|
BETTER_TOGETHER_SERVICE_UUID = 'b3b7e28e-a000-3e17-bd86-6e97b9e28c11'
|
|
CLIENT_RX_CHARACTERISTIC_UUID = '00000100-0004-1000-8000-001a11000102'
|
|
CLIENT_TX_CHARACTERISTIC_UUID = '00000100-0004-1000-8000-001a11000101'
|
|
CCCD_VALUE_INDICATION = 0x02
|
|
TEST_ITERATION = 3
|
|
|
|
# The following messages were captured during a smart unlock process. These
|
|
# are the messages exchanged between the phone and the chromebook to
|
|
# authorize each other in order to unlock the chromebook.
|
|
CONNECTION_OPEN_MESSAGE = b'\x80\x00\x01\x00\x01\x00\x00'
|
|
MESSAGE_1 = b'\x1c\x03\x00\xc7\x7b\x22\x66\x65\x61\x74\x75\x72' \
|
|
b'\x65\x22\x3a\x22\x61\x75\x74\x68\x22\x2c\x22\x70' \
|
|
b'\x61\x79\x6c\x6f\x61\x64\x22\x3a\x22\x43\x6c\x6b' \
|
|
b'\x4b\x56\x41\x67\x42\x45\x41\x45\x79\x54\x67\x70' \
|
|
b'\x4b\x43\x41\x45\x53\x52\x67\x6f\x68\x41\x49\x37' \
|
|
b'\x6e\x78\x63\x34\x6b\x44\x4d\x68\x6d\x68\x6e\x4d' \
|
|
b'\x75\x69\x7a\x79\x62\x47\x54\x5f\x31\x4e\x6c\x52' \
|
|
b'\x4c\x6c\x6d\x32\x59\x34\x72\x35\x78\x6f\x6e\x69' \
|
|
b'\x47\x51\x35\x49\x6a\x45\x69\x45\x41\x75\x45\x64' \
|
|
b'\x43\x4c\x37\x36\x44\x70\x30\x70\x6a\x6a\x75\x74' \
|
|
b'\x56\x6a\x47\x35\x38\x62\x55\x49\x57\x42\x57\x49' \
|
|
b'\x41\x4f\x5a\x39\x38\x46\x43\x49\x4f\x79\x58\x68' \
|
|
b'\x2d\x6b\x66\x6f\x51\x41\x52\x49\x42\x72\x68\x49' \
|
|
b'\x67\x7a\x35\x62\x49\x37\x53\x6f\x59\x47\x56\x36' \
|
|
b'\x6b\x4e\x4e\x74\x6d\x73\x59\x53\x6a\x75\x68\x4d' \
|
|
b'\x68\x61\x66\x51\x6c\x4d\x44\x55\x68\x78\x42\x36' \
|
|
b'\x44\x50\x52\x79\x4c\x5a\x62\x30\x3d\x22\x7d'
|
|
|
|
MESSAGE_2 = b'\x2c\x03\x00\xfb\x7b\x22\x66\x65\x61\x74\x75\x72' \
|
|
b'\x65\x22\x3a\x22\x61\x75\x74\x68\x22\x2c\x22\x70' \
|
|
b'\x61\x79\x6c\x6f\x61\x64\x22\x3a\x22\x43\x6f\x41' \
|
|
b'\x42\x43\x68\x77\x49\x41\x52\x41\x43\x4b\x68\x43' \
|
|
b'\x34\x69\x38\x45\x47\x73\x76\x71\x6e\x78\x5f\x66' \
|
|
b'\x66\x4c\x33\x59\x42\x61\x6b\x35\x59\x4d\x67\x51' \
|
|
b'\x49\x44\x52\x41\x42\x45\x6d\x42\x41\x71\x5f\x47' \
|
|
b'\x62\x72\x49\x42\x6c\x42\x42\x76\x73\x6a\x73\x32' \
|
|
b'\x67\x47\x56\x59\x70\x76\x73\x50\x6f\x64\x57\x6e' \
|
|
b'\x70\x50\x42\x6e\x5a\x71\x41\x61\x46\x6b\x30\x59' \
|
|
b'\x54\x48\x76\x79\x6b\x55\x76\x6b\x4a\x79\x6c\x35' \
|
|
b'\x56\x54\x7a\x48\x53\x35\x43\x6e\x41\x65\x56\x4d' \
|
|
b'\x79\x38\x49\x76\x44\x5f\x67\x65\x6d\x4e\x65\x42' \
|
|
b'\x61\x76\x58\x70\x70\x52\x62\x4b\x43\x42\x34\x5f' \
|
|
b'\x35\x35\x79\x4a\x50\x4e\x6b\x5f\x76\x45\x66\x53' \
|
|
b'\x38\x57\x5a\x7a\x6d\x58\x64\x5a\x4d\x68\x72\x74' \
|
|
b'\x31\x61\x6d\x67\x46\x7a\x6e\x35\x4b\x65\x61\x2d' \
|
|
b'\x77\x5f\x58\x55\x53\x49\x47\x54\x6e\x55\x33\x6d' \
|
|
b'\x68\x45\x36\x67\x63\x5a\x4c\x4b\x49\x52\x79\x4d' \
|
|
b'\x61\x39\x68\x78\x41\x4f\x30\x4b\x6f\x7a\x6d\x68' \
|
|
b'\x43\x71\x6d\x4d\x42\x54\x4a\x6e\x57\x4e\x6f\x52' \
|
|
b'\x62\x22\x7d'
|
|
|
|
MESSAGE_3 = b'\x3c\x03\x00\xae\x7b\x22\x66\x65\x61\x74\x75\x72' \
|
|
b'\x65\x22\x3a\x22\x65\x61\x73\x79\x5f\x75\x6e\x6c' \
|
|
b'\x6f\x63\x6b\x22\x2c\x22\x70\x61\x79\x6c\x6f\x61' \
|
|
b'\x64\x22\x3a\x22\x43\x6b\x41\x4b\x48\x41\x67\x42' \
|
|
b'\x45\x41\x49\x71\x45\x44\x30\x72\x52\x69\x54\x77' \
|
|
b'\x32\x41\x6e\x6e\x5f\x5f\x5f\x59\x33\x6c\x68\x55' \
|
|
b'\x53\x65\x45\x79\x42\x41\x67\x4e\x45\x41\x45\x53' \
|
|
b'\x49\x46\x59\x61\x64\x36\x33\x43\x75\x52\x34\x67' \
|
|
b'\x37\x77\x4d\x41\x4b\x61\x65\x76\x2d\x72\x7a\x38' \
|
|
b'\x66\x64\x6f\x47\x46\x2d\x67\x44\x4c\x6a\x37\x50' \
|
|
b'\x47\x62\x43\x6f\x57\x54\x66\x6f\x45\x69\x43\x38' \
|
|
b'\x4a\x56\x62\x4c\x48\x75\x49\x35\x42\x76\x38\x43' \
|
|
b'\x4c\x74\x73\x53\x51\x35\x50\x4c\x6e\x72\x5a\x41' \
|
|
b'\x70\x68\x6b\x75\x4c\x78\x2d\x56\x59\x64\x6c\x32' \
|
|
b'\x53\x4d\x71\x5f\x36\x41\x3d\x3d\x22\x7d'
|
|
|
|
MESSAGE_4 = b'\x4c\x03\x00\xc2\x7b\x22\x66\x65\x61\x74\x75\x72' \
|
|
b'\x65\x22\x3a\x22\x65\x61\x73\x79\x5f\x75\x6e\x6c' \
|
|
b'\x6f\x63\x6b\x22\x2c\x22\x70\x61\x79\x6c\x6f\x61' \
|
|
b'\x64\x22\x3a\x22\x43\x6c\x41\x4b\x48\x41\x67\x42' \
|
|
b'\x45\x41\x49\x71\x45\x4d\x61\x4e\x6c\x75\x43\x56' \
|
|
b'\x63\x72\x59\x37\x68\x70\x34\x68\x46\x74\x69\x6f' \
|
|
b'\x45\x4d\x6b\x79\x42\x41\x67\x4e\x45\x41\x45\x53' \
|
|
b'\x4d\x47\x6c\x68\x33\x55\x68\x44\x77\x58\x4b\x70' \
|
|
b'\x58\x46\x5f\x5a\x6e\x4f\x61\x30\x36\x4a\x48\x4b' \
|
|
b'\x69\x6b\x56\x68\x51\x62\x32\x50\x4d\x36\x62\x62' \
|
|
b'\x76\x78\x51\x6a\x47\x50\x47\x73\x66\x79\x42\x5a' \
|
|
b'\x74\x52\x67\x39\x5f\x65\x36\x6f\x42\x5a\x79\x5f' \
|
|
b'\x74\x35\x42\x45\x74\x78\x49\x67\x51\x75\x37\x42' \
|
|
b'\x62\x6e\x75\x4e\x57\x64\x67\x6d\x42\x36\x4d\x47' \
|
|
b'\x75\x68\x54\x79\x34\x33\x43\x37\x32\x2d\x58\x44' \
|
|
b'\x58\x35\x4b\x4f\x6a\x67\x6d\x56\x74\x48\x58\x41' \
|
|
b'\x68\x4e\x67\x3d\x22\x7d'
|
|
|
|
CONNECTION_CLOSE_MESSAGE = b'\xd2\x00\x00'
|
|
|
|
def test_smart_unlock(self, address):
|
|
"""Simulate the Smart Unlock flow, here are the steps that involve
|
|
1. Set the discovery filter to match LE devices only.
|
|
2. Start the discovery.
|
|
3. Stop the discovery after the device was found.
|
|
4. Set the LE connection parameters to reduce the min and max
|
|
connection intervals to 6.
|
|
5. Connect the device.
|
|
6. Set the Trusted property of the device to true.
|
|
7. Verify all the services were resolved.
|
|
8. Start notification on the RX characteristic of the
|
|
Proximity Service.
|
|
9. Exchange some messages with the peer device to authorize it.
|
|
10. Stop the notification.
|
|
11. Disconnect the device.
|
|
"""
|
|
|
|
filter = {'Transport':'le'}
|
|
parameters = {'MinimumConnectionInterval':6,
|
|
'MaximumConnectionInterval':6}
|
|
|
|
# We don't use the control file for iteration since it will involve the
|
|
# device setup steps which don't reflect the real user scenario.
|
|
for i in range(self.TEST_ITERATION):
|
|
logging.debug("Test iteration %d", i)
|
|
self.test_set_discovery_filter(filter)
|
|
self.test_discover_device(address)
|
|
|
|
self.test_set_le_connection_parameters(address, parameters)
|
|
self.test_connection_by_adapter(address)
|
|
|
|
self.test_set_trusted(address)
|
|
self.test_service_resolved(address)
|
|
self.test_find_object_path(address)
|
|
|
|
self.test_start_notify(self.rx_object_path,
|
|
self.CCCD_VALUE_INDICATION)
|
|
self.test_messages_exchange(
|
|
self.rx_object_path, self.tx_object_path, address)
|
|
self.test_stop_notify(self.rx_object_path)
|
|
self.test_disconnection_by_adapter(address)
|
|
|
|
self.test_remove_device_object(address)
|
|
|
|
return True
|
|
|
|
|
|
@test_retry_and_log(False)
|
|
def test_remove_device_object(self, address):
|
|
"""Test the device object can be removed from the adapter"""
|
|
return self.bluetooth_facade.remove_device_object(address)
|
|
|
|
|
|
@test_retry_and_log(False)
|
|
def test_find_object_path(self, address):
|
|
"""Test the object path can be found for a device"""
|
|
self.tx_object_path = None
|
|
self.rx_object_path = None
|
|
attr_map = self.bluetooth_facade.get_gatt_attributes_map(address)
|
|
attr_map_json = json.loads(attr_map)
|
|
servs_json = attr_map_json['services']
|
|
for uuid_s in servs_json:
|
|
if uuid_s == self.BETTER_TOGETHER_SERVICE_UUID:
|
|
chrcs_json = servs_json[uuid_s]['characteristics']
|
|
for uuid_c in chrcs_json:
|
|
if uuid_c == self.CLIENT_TX_CHARACTERISTIC_UUID:
|
|
self.tx_object_path = chrcs_json[uuid_c]['path']
|
|
if uuid_c == self.CLIENT_RX_CHARACTERISTIC_UUID:
|
|
self.rx_object_path = chrcs_json[uuid_c]['path']
|
|
|
|
return self.tx_object_path and self.rx_object_path
|
|
|
|
|
|
@test_retry_and_log(False)
|
|
def test_messages_exchange(
|
|
self, rx_object_path, tx_object_path, address):
|
|
"""Test message exchange with the peer, Better Together performs the
|
|
messages exchange for authorizing the peer device before unlocking
|
|
the Chromebook.
|
|
"""
|
|
if rx_object_path is None or tx_object_path is None:
|
|
logging.error('Invalid object path')
|
|
return False
|
|
|
|
self.test_message_exchange(
|
|
rx_object_path,
|
|
tx_object_path,
|
|
self.CONNECTION_OPEN_MESSAGE,
|
|
'connection open message')
|
|
|
|
self.test_message_exchange(
|
|
rx_object_path,
|
|
tx_object_path,
|
|
self.MESSAGE_1,
|
|
'message 1')
|
|
|
|
self.test_message_exchange(
|
|
rx_object_path,
|
|
tx_object_path,
|
|
self.MESSAGE_2,
|
|
'message 2')
|
|
|
|
# Better Together gets connection info multiple times to ensure
|
|
# the device is close enough by checking the RSSI, here we simulate it.
|
|
for i in range(9):
|
|
self.test_get_connection_info(address)
|
|
|
|
self.test_message_exchange(
|
|
rx_object_path,
|
|
tx_object_path,
|
|
self.MESSAGE_3,
|
|
'message 3')
|
|
|
|
self.test_message_exchange(
|
|
rx_object_path,
|
|
tx_object_path,
|
|
self.MESSAGE_4,
|
|
'message 4')
|
|
|
|
for i in range(2):
|
|
self.test_get_connection_info(address)
|
|
|
|
self.test_message_exchange(
|
|
rx_object_path,
|
|
tx_object_path,
|
|
self.CONNECTION_CLOSE_MESSAGE,
|
|
'connection close message')
|
|
|
|
return True
|
|
|
|
|
|
@test_retry_and_log(False)
|
|
def test_message_exchange(
|
|
self, rx_object_path, tx_object_path, message, message_type):
|
|
"""Test message exchange between DUT and the peer device"""
|
|
ret_msg = self.bluetooth_facade.exchange_messages(
|
|
tx_object_path, rx_object_path, message)
|
|
|
|
if not ret_msg:
|
|
logging.error("Failed to write message: %s", message_type)
|
|
return False
|
|
|
|
return base64.standard_b64decode(ret_msg) == message
|