• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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