• 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
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