• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import asyncio
2import hci_packets as hci
3import link_layer_packets as ll
4import math
5import random
6import unittest
7from dataclasses import dataclass
8from hci_packets import ErrorCode, FragmentPreference
9from py.bluetooth import Address
10from py.controller import ControllerTest
11from typing import List
12
13
14def make_advertising_event_properties(properties: int) -> hci.AdvertisingEventProperties:
15    return hci.AdvertisingEventProperties(connectable=(properties & 0x1) != 0,
16                                          scannable=(properties & 0x2) != 0,
17                                          directed=(properties & 0x4) != 0,
18                                          high_duty_cycle=(properties & 0x8) != 0,
19                                          legacy=(properties & 0x10) != 0,
20                                          anonymous=(properties & 0x20) != 0,
21                                          tx_power=(properties & 0x40) != 0)
22
23
24@dataclass
25class TestRound:
26    advertising_event_properties: int
27    data_length: int
28    fragment_preference: FragmentPreference
29    duration: int
30    max_extended_advertising_events: int
31
32
33class Test(ControllerTest):
34    # Test parameters.
35    LL_advertiser_advInterval_MIN = 0x200
36    LL_advertiser_advInterval_MAX = 0x200
37    LL_advertiser_Adv_Channel_Map = 0x7
38    LL_initiator_connInterval = 0x200
39    LL_initiator_connPeripheralLatency = 0x200
40    LL_initiator_connSupervisionTimeout = 0x200
41
42    # LL/DDI/ADV/BV-47-C [Extended Advertising, Non-Connectable – LE 1M PHY]
43    async def test(self):
44        controller = self.controller
45
46        # 1. The Upper Tester sends an HCI_LE_Read_Maximum_Advertising_Data_Length command to the
47        # IUT and expects the IUT to return a Maximum_Advertising_Data_Length between 0x001F and
48        # 0x0672. The Upper Tester stores the Maximum_Advertising_Data_Length for future use.
49        controller.send_cmd(hci.LeReadMaximumAdvertisingDataLength())
50
51        event = await self.expect_cmd_complete(hci.LeReadMaximumAdvertisingDataLengthComplete)
52        maximum_advertising_data_length = event.maximum_advertising_data_length
53
54        # Test rounds.
55        test_rounds = [
56            TestRound(0x0, 0, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
57            TestRound(0x0, 31, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
58            TestRound(0x0, 474, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
59            TestRound(0x0, 711, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
60            TestRound(0x0, 948, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
61            TestRound(0x0, maximum_advertising_data_length, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
62            TestRound(0x0, maximum_advertising_data_length, FragmentPreference.CONTROLLER_SHOULD_NOT, 0x0, 0x0),
63            TestRound(0x4, 0, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
64            TestRound(0x4, 251, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
65            TestRound(0x4, maximum_advertising_data_length, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x0),
66            TestRound(0x0, 0, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x1f4, 0x0),
67            TestRound(0x4, 0, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x1f4, 0x0),
68            TestRound(0x0, 0, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x32),
69            TestRound(0x4, 0, FragmentPreference.CONTROLLER_MAY_FRAGMENT, 0x0, 0x32),
70        ]
71
72        # 14. Repeat steps 2–13 for each Round shown in Table 4.6
73        for test_round in test_rounds:
74            await self.steps_2_13(maximum_advertising_data_length, **vars(test_round))
75
76    async def steps_2_13(self, maximum_advertising_data_length: int, advertising_event_properties: int,
77                         data_length: int, fragment_preference: FragmentPreference, duration: int,
78                         max_extended_advertising_events: int):
79        controller = self.controller
80        advertising_event_properties = make_advertising_event_properties(advertising_event_properties)
81
82        # 2. If the Data Length listed in Table 4.6 for the current Round is less than or equal to the
83        # Maximum_Advertising_Data_Length proceed to step 3, otherwise skip to step 14.
84        if data_length > maximum_advertising_data_length:
85            return
86
87        # 3. The Upper Tester sends an HCI_LE_Set_Extended_Advertising_Parameters command to the
88        # IUT using all supported advertising channels and a selected advertising interval between the
89        # minimum and maximum advertising intervals supported. Advertising_Event_Properties parameter
90        # shall be set to the value specified in Table 4.6 for this round. The Primary_Advertising_PHY and
91        # Secondary_Advertising_PHY shall be set to the values specified in Table 4.5. If the
92        # Advertising_Event_Properties value for this Round specifies directed advertising, the
93        # Peer_Address_Type shall be set to 0x00 (Public Device Address), and the Peer_Address shall be
94        # set to the Lower Tester’s address.
95        controller.send_cmd(
96            hci.LeSetExtendedAdvertisingParameters(advertising_handle=0,
97                                                   advertising_event_properties=advertising_event_properties,
98                                                   primary_advertising_interval_min=self.LL_advertiser_advInterval_MIN,
99                                                   primary_advertising_interval_max=self.LL_advertiser_advInterval_MAX,
100                                                   primary_advertising_channel_map=self.LL_advertiser_Adv_Channel_Map,
101                                                   own_address_type=hci.OwnAddressType.PUBLIC_DEVICE_ADDRESS,
102                                                   advertising_filter_policy=hci.AdvertisingFilterPolicy.ALL_DEVICES,
103                                                   primary_advertising_phy=hci.PrimaryPhyType.LE_1M))
104
105        await self.expect_evt(
106            hci.LeSetExtendedAdvertisingParametersComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
107
108        # 4. The Upper Tester sends one or more HCI_LE_Set_Extended_Advertising_Data commands to the
109        # IUT with values according to Table 4.6 and using random octets from 1 to 254 as the payload. If
110        # the Data Length is greater than 251 the Upper Tester shall send multiple commands using one
111        # Operation 0x01 (First fragment) command, followed by zero or more Operation 0x00
112        # (Intermediate Fragment) commands, and a final Operation 0x02 (Last fragment) command.
113        # Otherwise the Upper Tester shall send a single command using Operation 0x03 (Complete Data).
114        advertising_data = [random.randint(1, 254) for n in range(data_length)]
115        num_fragments = math.ceil(data_length / 251) or 1  # Make sure to set the advertising data if it is empty.
116        for n in range(num_fragments):
117            fragment_offset = 251 * n
118            fragment_length = min(251, data_length - fragment_offset)
119            if num_fragments == 1:
120                operation = hci.Operation.COMPLETE_ADVERTISEMENT
121            elif n == 0:
122                operation = hci.Operation.FIRST_FRAGMENT
123            elif n == num_fragments - 1:
124                operation = hci.Operation.LAST_FRAGMENT
125            else:
126                operation = hci.Operation.INTERMEDIATE_FRAGMENT
127
128            controller.send_cmd(
129                hci.LeSetExtendedAdvertisingDataRaw(advertising_handle=0,
130                                                    operation=operation,
131                                                    advertising_data=advertising_data[fragment_offset:fragment_offset +
132                                                                                      fragment_length]))
133
134            await self.expect_evt(
135                hci.LeSetExtendedAdvertisingDataComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
136
137        # 5. The Upper Tester enables advertising using the HCI_LE_Set_Extended_Advertising_Enable
138        # command. The Duration[0] parameter shall be set to the value specified in Table 4.6 for this
139        # round. The Max_Extended_Advertising_Events[0] parameter shall be set to the value specified in
140        # Table 4.6 for this round.
141        controller.send_cmd(
142            hci.LeSetExtendedAdvertisingEnable(enable=hci.Enable.ENABLED,
143                                               enabled_sets=[
144                                                   hci.EnabledSet(
145                                                       advertising_handle=0,
146                                                       duration=duration,
147                                                       max_extended_advertising_events=max_extended_advertising_events)
148                                               ]))
149
150        await self.expect_evt(
151            hci.LeSetExtendedAdvertisingEnableComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
152
153        # 6. The Lower Tester receives an ADV_EXT_IND packet from the IUT with AdvMode set to 00b. The
154        # ADV_EXT_IND PDU shall not include the SuppInfo, SyncInfo, TxPower, ACAD, or AdvData
155        # fields. If advertising data was set in step 4, the ADV_EXT_IND PDU shall include the AuxPtr field;
156        # otherwise, the ADV_EXT_IND PDU may include the AuxPtr field. If the AuxPtr field is included,
157        # the ADV_EXT_IND PDU shall also include the ADI field with the SID set to the value used in step
158        # 3; otherwise that field shall not be included.
159
160        # 7. If the AuxPtr is absent, skip to step 10.
161
162        # 8. The Lower Tester utilizes the AuxPtr field to listen for an AUX_ADV_IND PDU on the secondary
163        # advertising channel with the AdvMode field set to 00b. The AUX_ADV_IND PDU shall not include
164        # the SuppInfo, SyncInfo, or TxPower fields. The AUX_ADV_IND PDU shall include the ADI field
165        # matching the ADI field from step 6. If the AUX_ADV_IND PDU does not contain all the data
166        # submitted in step 4 (if any), it shall include an AuxPtr field.
167
168        # 9. If the AUX_ADV_IND PDU contains an AuxPtr field, the Lower Tester utilizes it to listen for an
169        # AUX_CHAIN_IND PDU with the AdvMode field set to 00b. The AUX_CHAIN_IND PDU shall
170        # include the ADI field matching the ADI field from step 6 and the AdvData field containing
171        # additional data submitted in step 4. The AUX_CHAIN_IND PDU shall not include the AdvA,
172        # TargetA, SuppInfo, TxPower, or SyncInfo fields. If the AUX_CHAIN_IND PDU contains an AuxPtr
173        # field this step is repeated until an AUX_CHAIN_IND PDU is received with no AuxPtr field and all
174        # data has been received.
175        repeat = max_extended_advertising_events or 3
176        for n in range(repeat):
177            await self.expect_ll(
178                ll.LeExtendedAdvertisingPdu(source_address=controller.address,
179                                            advertising_address_type=ll.AddressType.PUBLIC,
180                                            target_address_type=ll.AddressType.PUBLIC,
181                                            connectable=int(advertising_event_properties.connectable),
182                                            scannable=int(advertising_event_properties.scannable),
183                                            directed=int(advertising_event_properties.directed),
184                                            sid=0,
185                                            tx_power=0,
186                                            primary_phy=ll.PrimaryPhyType.LE_1M,
187                                            secondary_phy=ll.SecondaryPhyType.NO_PACKETS,
188                                            advertising_data=advertising_data))
189
190        # 10. If the Max_Extended_Advertising_Events was set to a value different than 0, repeat steps 6–9
191        # until the IUT stops advertising. Afterwards, the Lower Tester confirms that the IUT did not send
192        # more than Max_Extended_Advertising_Events advertising events. Upper Tester shall receive LE
193        # Advertising Set Terminated event with ErrorCode 0x43. Skip to step 13.
194        if max_extended_advertising_events > 0:
195            try:
196                # Note: The test should timeout waiting for an advertising event
197                # past Max Extended Advertising Events count.
198                await asyncio.wait_for(self.controller.receive_ll(), timeout=1)
199                self.assertTrue(False)
200            except asyncio.exceptions.TimeoutError:
201                pass
202
203            await self.expect_evt(
204                hci.LeAdvertisingSetTerminated(
205                    status=ErrorCode.ADVERTISING_TIMEOUT,
206                    advertising_handle=0,
207                    connection_handle=0,
208                    num_completed_extended_advertising_events=max_extended_advertising_events))
209
210        # 11. Otherwise if Duration was set to a value different than 0, repeat steps 6–9 until the amount of
211        # time specified for Duration has elapsed. Afterwards, the Lower Tester confirms that the IUT does
212        # not start any additional advertising events. Upper Tester shall receive LE Advertising Set
213        # Terminated event with ErrorCode 0x3C. Skip to step 13.
214        elif duration > 0:
215            try:
216                # Note: The test should timeout waiting for a directed advertising event
217                # past the direct advertising timeout.
218                end_time = asyncio.get_running_loop().time() + duration / 100
219                while asyncio.get_running_loop().time() < end_time:
220                    await asyncio.wait_for(self.controller.receive_ll(), timeout=1)
221                self.assertTrue(False)
222            except asyncio.exceptions.TimeoutError:
223                pass
224
225            await self.expect_evt(
226                hci.LeAdvertisingSetTerminated(status=ErrorCode.ADVERTISING_TIMEOUT,
227                                               advertising_handle=0,
228                                               connection_handle=0,
229                                               num_completed_extended_advertising_events=0))
230
231        # 12. Otherwise, repeat steps 6–9 until a number of advertising intervals (10) have been detected.
232
233        # 13. The Upper Tester disables advertising using the HCI_LE_Set_Extended_Advertising_Enable
234        # command.
235        controller.send_cmd(hci.LeSetExtendedAdvertisingEnable(enable=hci.Enable.DISABLED, enabled_sets=[]))
236
237        await self.expect_evt(
238            hci.LeSetExtendedAdvertisingEnableComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
239