1#!/usr/bin/env python3 2# 3# Copyright (C) 2021 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16""" 17PTS GAP/SEC/SEM Automation 18 19Optional custom parameter "collect_detailed_pass_logs" 20 Used to collect link keys and extra logs on pass results 21 to be used for certification purposes. 22""" 23 24from acts import signals 25from acts.base_test import BaseTestClass 26from acts.test_decorators import test_tracker_info 27from acts_contrib.test_utils.bt.bt_test_utils import generate_id_by_size 28from acts_contrib.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name 29from acts_contrib.test_utils.fuchsia.bt_test_utils import get_link_keys 30from acts_contrib.test_utils.fuchsia.bt_test_utils import unbond_all_known_devices 31from contextlib import suppress 32import inspect 33 34 35class GapSecSemTest(BaseTestClass): 36 gatt_connect_err_message = "Gatt connection failed with: {}" 37 gatt_disconnect_err_message = "Gatt disconnection failed with: {}" 38 ble_advertise_interval = 50 39 scan_timeout_seconds = 60 40 41 def setup_class(self): 42 super().setup_class() 43 self.pri_dut = self.fuchsia_devices[0] 44 # TODO: fxb/57968 Provide Facade for setting secure connections only mode, 45 # for the interim set this manually in the build. 46 self.sec_dut = self.fuchsia_devices[1] 47 for fd in self.fuchsia_devices: 48 fd.sl4f.bts_lib.initBluetoothSys() 49 # Optional user param for collecting enough information for 50 # certification on pass results. 51 self.collect_detailed_pass_logs = self.user_params.get( 52 "collect_detailed_pass_logs", False) 53 54 def on_fail(self, test_name, begin_time): 55 for fd in self.fuchsia_devices: 56 fd.take_bug_report(test_name, begin_time) 57 58 def teardown_test(self): 59 # Stop scanning and advertising on all devices at the end of a test. 60 with suppress(Exception): 61 for fd in self.fuchsia_devices: 62 fd.sl4f.ble_lib.bleStopBleAdvertising() 63 fd.bleStopBleScan() 64 for fd in self.fuchsia_devices: 65 unbond_all_known_devices(fd, self.log) 66 67 def teardown_class(self): 68 for fd in self.fuchsia_devices: 69 fd.sl4f.bts_lib.requestDiscovery(False) 70 71 def on_pass(self, test_name, begin_time): 72 if self.collect_detailed_pass_logs == True: 73 for fd in self.fuchsia_devices: 74 fd.take_bt_snoop_log(test_name) 75 fd.take_bug_report(test_name, begin_time) 76 77 def _orchestrate_gatt_connection(self, central, peripheral): 78 """ Orchestrate a GATT connetion from the input Central 79 Fuchsia device to the Peripheral Fuchsia device. 80 Args: 81 central: The central Fuchsia device 82 peripheral: The peripheral Fuchsia device 83 peripheral: The peripheral role Fuchsia device 84 Returns: 85 Dictionary of device info if connection successful. 86 """ 87 adv_name = generate_id_by_size(10) 88 adv_data = { 89 "name": adv_name, 90 "appearance": None, 91 "service_data": None, 92 "tx_power_level": None, 93 "service_uuids": None, 94 "manufacturer_data": None, 95 "uris": None, 96 } 97 scan_response = None 98 connectable = True 99 100 peripheral.sl4f.ble_lib.bleStartBleAdvertising( 101 adv_data, scan_response, self.ble_advertise_interval, connectable) 102 scan_filter = {"name_substring": adv_name} 103 central.sl4f.gattc_lib.bleStartBleScan(scan_filter) 104 device = le_scan_for_device_by_name(central, 105 self.log, 106 adv_name, 107 self.scan_timeout_seconds, 108 partial_match=False, 109 self_manage_scan=False) 110 if device is None: 111 raise signals.TestFailure("Scanner unable to find advertisement.") 112 connect_result = central.sl4f.gattc_lib.bleConnectToPeripheral( 113 device["id"]) 114 if connect_result.get("error") is not None: 115 raise signals.TestFailure( 116 self.gatt_connect_err_message.format( 117 connect_result.get("error"))) 118 self.log.info("Connection Successful...") 119 120 return device 121 122 def _orchestrate_gap_sec_sem_37_to_44_test(self, test_name, central, 123 peripheral, 124 is_central_pairing_initiator, 125 security_level): 126 """ Performs GAP/SEC/SEM/BV-37 to 44 tests. 127 These set of tests deal with varying modes and directions of 128 service level connections with LE secure Connections. 129 130 Args: 131 test_name: The name of the test for logging purposes 132 central: The central role Fuchsia device 133 peripheral: The peripheral role Fuchsia device 134 is_central_pairing_initiator: True if Central should initiate 135 the pairing. False if Peripheral should initiate. 136 security_level: 1 for Encrypted, 2 for Authenticated 137 Orchestration Steps: 138 1. Perform GATT connection from Central to Peripheral 139 2. Pair with specified security_level in direction specified 140 by is_central_pairing_initiator. 141 3. Exchange pairing pins 142 4. Collect link keys if applicable 143 5. Disconnect device 144 6. Forget pairing. 145 """ 146 input_capabilities = "NONE" 147 output_capabilities = "NONE" 148 149 central.sl4f.bts_lib.acceptPairing("KEYBOARD", "DISPLAY") 150 151 peripheral.sl4f.bts_lib.acceptPairing("KEYBOARD", "DISPLAY") 152 153 device = self._orchestrate_gatt_connection(central, peripheral) 154 # TODO: fxb/71289 Change once all peer IDs are ints and not strings 155 identifier = int("0x{}".format(device["id"]), 0) 156 bondable = True 157 transport = 2 #LE 158 if is_central_pairing_initiator: 159 pair_result = central.sl4f.bts_lib.pair(identifier, security_level, 160 bondable, transport) 161 if not is_central_pairing_initiator: 162 device_list = peripheral.sl4f.bts_lib.getKnownRemoteDevices( 163 )['result'] 164 print(device_list) 165 for id_dict in device_list: 166 d = device_list[id_dict] 167 name = None 168 if d['connected'] is True: 169 did = d['id'] 170 pair_result = peripheral.sl4f.bts_lib.pair(did, security_level, 171 bondable, transport) 172 173 pins_transferred = False 174 pairing_pin = central.sl4f.bts_lib.getPairingPin()['result'] 175 if pairing_pin != "0" and pairing_pin is not None: 176 peripheral.sl4f.bts_lib.inputPairingPin(pairing_pin) 177 pins_transferred = True 178 if not pins_transferred: 179 pairing_pin = peripheral.sl4f.bts_lib.getPairingPin()['result'] 180 if pairing_pin != "0": 181 central.sl4f.bts_lib.inputPairingPin(pairing_pin) 182 183 if self.collect_detailed_pass_logs == True: 184 save_path = f"{central.log_path}/{test_name}_stash_secure.store" 185 self.log.info( 186 f"Known Link Keys: {get_link_keys(central, save_path)}") 187 save_path = f"{peripheral.log_path}/{test_name}_stash_secure.store" 188 self.log.info( 189 f"Known Link Keys: {get_link_keys(peripheral, save_path)}") 190 191 disconnect_result = central.sl4f.gattc_lib.bleDisconnectPeripheral( 192 device["id"]) 193 if disconnect_result.get("error") is not None: 194 raise signals.TestFailure( 195 self.gatt_disconnect_err_message.format( 196 disconnect_result.get("error"))) 197 self.log.info("Disconnection Successful...") 198 199 central.sl4f.bts_lib.forgetDevice(identifier) 200 201 raise signals.TestPass("Success") 202 203 def test_gap_sec_sem_bv_37_c(self): 204 central = self.pri_dut 205 peripheral = self.sec_dut 206 is_central_pairing_initiator = True 207 security_level = 1 # Encrypted 208 test_name = inspect.currentframe().f_code.co_name 209 self._orchestrate_gap_sec_sem_37_to_44_test( 210 test_name, central, peripheral, is_central_pairing_initiator, 211 security_level) 212 213 def test_gap_sec_sem_bv_38_c(self): 214 central = self.pri_dut 215 peripheral = self.sec_dut 216 is_central_pairing_initiator = True 217 security_level = 2 # Authenticated 218 test_name = inspect.currentframe().f_code.co_name 219 self._orchestrate_gap_sec_sem_37_to_44_test( 220 test_name, central, peripheral, is_central_pairing_initiator, 221 security_level) 222 223 def test_gap_sec_sem_bv_39_c(self): 224 central = self.pri_dut 225 peripheral = self.sec_dut 226 is_central_pairing_initiator = False 227 security_level = 1 # Encrypted 228 test_name = inspect.currentframe().f_code.co_name 229 self._orchestrate_gap_sec_sem_37_to_44_test( 230 test_name, central, peripheral, is_central_pairing_initiator, 231 security_level) 232 233 def test_gap_sec_sem_bv_40_c(self): 234 central = self.pri_dut 235 peripheral = self.sec_dut 236 is_central_pairing_initiator = False 237 security_level = 2 # Authenticated 238 test_name = inspect.currentframe().f_code.co_name 239 self._orchestrate_gap_sec_sem_37_to_44_test( 240 test_name, central, peripheral, is_central_pairing_initiator, 241 security_level) 242 243 def test_gap_sec_sem_bv_41_c(self): 244 central = self.sec_dut 245 peripheral = self.pri_dut 246 is_central_pairing_initiator = True 247 security_level = 1 # Encrypted 248 test_name = inspect.currentframe().f_code.co_name 249 self._orchestrate_gap_sec_sem_37_to_44_test( 250 test_name, central, peripheral, is_central_pairing_initiator, 251 security_level) 252 253 def test_gap_sec_sem_bv_42_c(self): 254 central = self.sec_dut 255 peripheral = self.pri_dut 256 is_central_pairing_initiator = True 257 security_level = 2 # Authenticated 258 test_name = inspect.currentframe().f_code.co_name 259 self._orchestrate_gap_sec_sem_37_to_44_test( 260 test_name, central, peripheral, is_central_pairing_initiator, 261 security_level) 262 263 def test_gap_sec_sem_bv_43_c(self): 264 central = self.sec_dut 265 peripheral = self.pri_dut 266 is_central_pairing_initiator = False 267 security_level = 1 # Encrypted 268 test_name = inspect.currentframe().f_code.co_name 269 self._orchestrate_gap_sec_sem_37_to_44_test( 270 test_name, central, peripheral, is_central_pairing_initiator, 271 security_level) 272 273 def test_gap_sec_sem_bv_44_c(self): 274 central = self.sec_dut 275 peripheral = self.pri_dut 276 is_central_pairing_initiator = False 277 security_level = 2 # Authenticated 278 test_name = inspect.currentframe().f_code.co_name 279 self._orchestrate_gap_sec_sem_37_to_44_test( 280 test_name, central, peripheral, is_central_pairing_initiator, 281 security_level) 282