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 33import time 34 35 36class GapSecSemTest(BaseTestClass): 37 gatt_connect_err_message = "Gatt connection failed with: {}" 38 gatt_disconnect_err_message = "Gatt disconnection failed with: {}" 39 ble_advertise_interval = 50 40 scan_timeout_seconds = 60 41 42 def setup_class(self): 43 super().setup_class() 44 self.pri_dut = self.fuchsia_devices[0] 45 # TODO: fxb/57968 Provide Facade for setting secure connections only mode, 46 # for the interim set this manually in the build. 47 self.sec_dut = self.fuchsia_devices[1] 48 for fd in self.fuchsia_devices: 49 fd.bts_lib.initBluetoothSys() 50 # Optional user param for collecting enough information for 51 # certification on pass results. 52 self.collect_detailed_pass_logs = self.user_params.get( 53 "collect_detailed_pass_logs", False) 54 55 def on_fail(self, test_name, begin_time): 56 for fd in self.fuchsia_devices: 57 fd.take_bug_report(test_name, begin_time) 58 59 def teardown_test(self): 60 # Stop scanning and advertising on all devices at the end of a test. 61 with suppress(Exception): 62 for fd in self.fuchsia_devices: 63 fd.ble_lib.bleStopBleAdvertising() 64 fd.bleStopBleScan() 65 for fd in self.fuchsia_devices: 66 unbond_all_known_devices(fd, self.log) 67 68 def teardown_class(self): 69 for fd in self.fuchsia_devices: 70 fd.bts_lib.requestDiscovery(False) 71 72 def on_pass(self, test_name, begin_time): 73 if self.collect_detailed_pass_logs == True: 74 for fd in self.fuchsia_devices: 75 fd.take_bt_snoop_log(test_name) 76 fd.take_bug_report(test_name, begin_time) 77 78 def _orchestrate_gatt_connection(self, central, peripheral): 79 """ Orchestrate a GATT connetion from the input Central 80 Fuchsia device to the Peripheral Fuchsia device. 81 Args: 82 central: The central Fuchsia device 83 peripheral: The peripheral Fuchsia device 84 peripheral: The peripheral role Fuchsia device 85 Returns: 86 Dictionary of device info if connection successful. 87 """ 88 adv_name = generate_id_by_size(10) 89 adv_data = { 90 "name": adv_name, 91 "appearance": None, 92 "service_data": None, 93 "tx_power_level": None, 94 "service_uuids": None, 95 "manufacturer_data": None, 96 "uris": None, 97 } 98 scan_response = None 99 connectable = True 100 101 peripheral.ble_lib.bleStartBleAdvertising(adv_data, scan_response, 102 self.ble_advertise_interval, 103 connectable) 104 scan_filter = {"name_substring": adv_name} 105 central.gattc_lib.bleStartBleScan(scan_filter) 106 device = le_scan_for_device_by_name(central, 107 self.log, 108 adv_name, 109 self.scan_timeout_seconds, 110 partial_match=False, 111 self_manage_scan=False) 112 if device is None: 113 raise signals.TestFailure("Scanner unable to find advertisement.") 114 connect_result = central.gattc_lib.bleConnectToPeripheral(device["id"]) 115 if connect_result.get("error") is not None: 116 raise signals.TestFailure( 117 self.gatt_connect_err_message.format( 118 connect_result.get("error"))) 119 self.log.info("Connection Successful...") 120 121 return device 122 123 def _orchestrate_gap_sec_sem_37_to_44_test(self, test_name, central, 124 peripheral, 125 is_central_pairing_initiator, 126 security_level): 127 """ Performs GAP/SEC/SEM/BV-37 to 44 tests. 128 These set of tests deal with varying modes and directions of 129 service level connections with LE secure Connections. 130 131 Args: 132 test_name: The name of the test for logging purposes 133 central: The central role Fuchsia device 134 peripheral: The peripheral role Fuchsia device 135 is_central_pairing_initiator: True if Central should initiate 136 the pairing. False if Peripheral should initiate. 137 security_level: 1 for Encrypted, 2 for Authenticated 138 Orchestration Steps: 139 1. Perform GATT connection from Central to Peripheral 140 2. Pair with specified security_level in direction specified 141 by is_central_pairing_initiator. 142 3. Exchange pairing pins 143 4. Collect link keys if applicable 144 5. Disconnect device 145 6. Forget pairing. 146 """ 147 input_capabilities = "NONE" 148 output_capabilities = "NONE" 149 150 central.bts_lib.acceptPairing("KEYBOARD", "DISPLAY") 151 152 peripheral.bts_lib.acceptPairing("KEYBOARD", "DISPLAY") 153 154 device = self._orchestrate_gatt_connection(central, peripheral) 155 # TODO: fxb/71289 Change once all peer IDs are ints and not strings 156 identifier = int("0x{}".format(device["id"]), 0) 157 bondable = True 158 transport = 2 #LE 159 if is_central_pairing_initiator: 160 pair_result = central.bts_lib.pair(identifier, security_level, 161 bondable, transport) 162 if not is_central_pairing_initiator: 163 device_list = peripheral.bts_lib.getKnownRemoteDevices()['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.bts_lib.pair(did, security_level, 171 bondable, transport) 172 173 pins_transferred = False 174 pairing_pin = central.bts_lib.getPairingPin()['result'] 175 if pairing_pin != "0" and pairing_pin is not None: 176 peripheral.bts_lib.inputPairingPin(pairing_pin) 177 pins_transferred = True 178 if not pins_transferred: 179 pairing_pin = peripheral.bts_lib.getPairingPin()['result'] 180 if pairing_pin != "0": 181 central.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.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.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