Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 1 | # Copyright 2021-2022 Google LLC |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | # ----------------------------------------------------------------------------- |
| 16 | # Imports |
| 17 | # ----------------------------------------------------------------------------- |
| 18 | import asyncio |
| 19 | import logging |
| 20 | import os |
| 21 | from types import LambdaType |
| 22 | import pytest |
| 23 | |
| 24 | from bumble.core import BT_BR_EDR_TRANSPORT |
| 25 | from bumble.device import Connection, Device |
| 26 | from bumble.host import Host |
| 27 | from bumble.hci import ( |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 28 | HCI_ACCEPT_CONNECTION_REQUEST_COMMAND, |
| 29 | HCI_COMMAND_STATUS_PENDING, |
| 30 | HCI_CREATE_CONNECTION_COMMAND, |
| 31 | HCI_SUCCESS, |
| 32 | Address, |
| 33 | HCI_Command_Complete_Event, |
| 34 | HCI_Command_Status_Event, |
| 35 | HCI_Connection_Complete_Event, |
| 36 | HCI_Connection_Request_Event, |
| 37 | HCI_Packet, |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 38 | ) |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 39 | from bumble.gatt import ( |
| 40 | GATT_GENERIC_ACCESS_SERVICE, |
| 41 | GATT_CHARACTERISTIC_ATTRIBUTE_TYPE, |
| 42 | GATT_DEVICE_NAME_CHARACTERISTIC, |
| 43 | GATT_APPEARANCE_CHARACTERISTIC, |
| 44 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 45 | |
| 46 | # ----------------------------------------------------------------------------- |
| 47 | # Logging |
| 48 | # ----------------------------------------------------------------------------- |
| 49 | logger = logging.getLogger(__name__) |
| 50 | |
| 51 | |
| 52 | # ----------------------------------------------------------------------------- |
| 53 | class Sink: |
| 54 | def __init__(self, flow): |
| 55 | self.flow = flow |
| 56 | next(self.flow) |
| 57 | |
| 58 | def on_packet(self, packet): |
| 59 | self.flow.send(packet) |
| 60 | |
| 61 | |
| 62 | # ----------------------------------------------------------------------------- |
| 63 | @pytest.mark.asyncio |
| 64 | async def test_device_connect_parallel(): |
| 65 | d0 = Device(host=Host(None, None)) |
| 66 | d1 = Device(host=Host(None, None)) |
| 67 | d2 = Device(host=Host(None, None)) |
| 68 | |
| 69 | # enable classic |
| 70 | d0.classic_enabled = True |
| 71 | d1.classic_enabled = True |
| 72 | d2.classic_enabled = True |
| 73 | |
| 74 | # set public addresses |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 75 | d0.public_address = Address( |
| 76 | 'F0:F1:F2:F3:F4:F5', address_type=Address.PUBLIC_DEVICE_ADDRESS |
| 77 | ) |
| 78 | d1.public_address = Address( |
| 79 | 'F5:F4:F3:F2:F1:F0', address_type=Address.PUBLIC_DEVICE_ADDRESS |
| 80 | ) |
| 81 | d2.public_address = Address( |
| 82 | 'F5:F4:F3:F3:F4:F5', address_type=Address.PUBLIC_DEVICE_ADDRESS |
| 83 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 84 | |
| 85 | def d0_flow(): |
| 86 | packet = HCI_Packet.from_bytes((yield)) |
| 87 | assert packet.name == 'HCI_CREATE_CONNECTION_COMMAND' |
| 88 | assert packet.bd_addr == d1.public_address |
| 89 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 90 | d0.host.on_hci_packet( |
| 91 | HCI_Command_Status_Event( |
| 92 | status=HCI_COMMAND_STATUS_PENDING, |
| 93 | num_hci_command_packets=1, |
| 94 | command_opcode=HCI_CREATE_CONNECTION_COMMAND, |
| 95 | ) |
| 96 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 97 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 98 | d1.host.on_hci_packet( |
| 99 | HCI_Connection_Request_Event( |
| 100 | bd_addr=d0.public_address, |
| 101 | class_of_device=0, |
| 102 | link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE, |
| 103 | ) |
| 104 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 105 | |
| 106 | packet = HCI_Packet.from_bytes((yield)) |
| 107 | assert packet.name == 'HCI_CREATE_CONNECTION_COMMAND' |
| 108 | assert packet.bd_addr == d2.public_address |
| 109 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 110 | d0.host.on_hci_packet( |
| 111 | HCI_Command_Status_Event( |
| 112 | status=HCI_COMMAND_STATUS_PENDING, |
| 113 | num_hci_command_packets=1, |
| 114 | command_opcode=HCI_CREATE_CONNECTION_COMMAND, |
| 115 | ) |
| 116 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 117 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 118 | d2.host.on_hci_packet( |
| 119 | HCI_Connection_Request_Event( |
| 120 | bd_addr=d0.public_address, |
| 121 | class_of_device=0, |
| 122 | link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE, |
| 123 | ) |
| 124 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 125 | |
| 126 | assert (yield) == None |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 127 | |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 128 | def d1_flow(): |
| 129 | packet = HCI_Packet.from_bytes((yield)) |
| 130 | assert packet.name == 'HCI_ACCEPT_CONNECTION_REQUEST_COMMAND' |
| 131 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 132 | d1.host.on_hci_packet( |
| 133 | HCI_Command_Complete_Event( |
| 134 | num_hci_command_packets=1, |
| 135 | command_opcode=HCI_ACCEPT_CONNECTION_REQUEST_COMMAND, |
| 136 | return_parameters=b"\x00", |
| 137 | ) |
| 138 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 139 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 140 | d1.host.on_hci_packet( |
| 141 | HCI_Connection_Complete_Event( |
| 142 | status=HCI_SUCCESS, |
| 143 | connection_handle=0x100, |
| 144 | bd_addr=d0.public_address, |
| 145 | link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE, |
| 146 | encryption_enabled=True, |
| 147 | ) |
| 148 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 149 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 150 | d0.host.on_hci_packet( |
| 151 | HCI_Connection_Complete_Event( |
| 152 | status=HCI_SUCCESS, |
| 153 | connection_handle=0x100, |
| 154 | bd_addr=d1.public_address, |
| 155 | link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE, |
| 156 | encryption_enabled=True, |
| 157 | ) |
| 158 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 159 | |
| 160 | assert (yield) == None |
| 161 | |
| 162 | def d2_flow(): |
| 163 | packet = HCI_Packet.from_bytes((yield)) |
| 164 | assert packet.name == 'HCI_ACCEPT_CONNECTION_REQUEST_COMMAND' |
| 165 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 166 | d2.host.on_hci_packet( |
| 167 | HCI_Command_Complete_Event( |
| 168 | num_hci_command_packets=1, |
| 169 | command_opcode=HCI_ACCEPT_CONNECTION_REQUEST_COMMAND, |
| 170 | return_parameters=b"\x00", |
| 171 | ) |
| 172 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 173 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 174 | d2.host.on_hci_packet( |
| 175 | HCI_Connection_Complete_Event( |
| 176 | status=HCI_SUCCESS, |
| 177 | connection_handle=0x101, |
| 178 | bd_addr=d0.public_address, |
| 179 | link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE, |
| 180 | encryption_enabled=True, |
| 181 | ) |
| 182 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 183 | |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 184 | d0.host.on_hci_packet( |
| 185 | HCI_Connection_Complete_Event( |
| 186 | status=HCI_SUCCESS, |
| 187 | connection_handle=0x101, |
| 188 | bd_addr=d2.public_address, |
| 189 | link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE, |
| 190 | encryption_enabled=True, |
| 191 | ) |
| 192 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 193 | |
| 194 | assert (yield) == None |
| 195 | |
| 196 | d0.host.set_packet_sink(Sink(d0_flow())) |
| 197 | d1.host.set_packet_sink(Sink(d1_flow())) |
| 198 | d2.host.set_packet_sink(Sink(d2_flow())) |
| 199 | |
uael | ad27de7 | 2023-02-10 20:10:59 +0000 | [diff] [blame] | 200 | [c01, c02, a10, a20] = await asyncio.gather( |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 201 | *[ |
| 202 | asyncio.create_task( |
| 203 | d0.connect(d1.public_address, transport=BT_BR_EDR_TRANSPORT) |
| 204 | ), |
| 205 | asyncio.create_task( |
| 206 | d0.connect(d2.public_address, transport=BT_BR_EDR_TRANSPORT) |
| 207 | ), |
| 208 | asyncio.create_task(d1.accept(peer_address=d0.public_address)), |
| 209 | asyncio.create_task(d2.accept()), |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 210 | ] |
| 211 | ) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 212 | |
Abel Lucas | 56ed46a | 2022-10-19 19:00:03 +0000 | [diff] [blame] | 213 | assert type(c01) == Connection |
| 214 | assert type(c02) == Connection |
| 215 | assert type(a10) == Connection |
| 216 | assert type(a20) == Connection |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 217 | |
Abel Lucas | 56ed46a | 2022-10-19 19:00:03 +0000 | [diff] [blame] | 218 | assert c01.handle == a10.handle and c01.handle == 0x100 |
| 219 | assert c02.handle == a20.handle and c02.handle == 0x101 |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 220 | |
| 221 | |
| 222 | # ----------------------------------------------------------------------------- |
Abel Lucas | 287df94 | 2022-11-29 00:59:04 +0000 | [diff] [blame] | 223 | @pytest.mark.asyncio |
| 224 | async def test_flush(): |
| 225 | d0 = Device(host=Host(None, None)) |
| 226 | task = d0.abort_on('flush', asyncio.sleep(10000)) |
| 227 | await d0.host.flush() |
| 228 | try: |
| 229 | await task |
| 230 | assert False |
| 231 | except asyncio.CancelledError: |
| 232 | pass |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 233 | |
| 234 | |
| 235 | # ----------------------------------------------------------------------------- |
Alan Rosenthal | a8eff73 | 2022-11-30 16:15:26 -0500 | [diff] [blame] | 236 | def test_gatt_services_with_gas(): |
| 237 | device = Device(host=Host(None, None)) |
| 238 | |
| 239 | # there should be one service and two chars, therefore 5 attributes |
| 240 | assert len(device.gatt_server.attributes) == 5 |
| 241 | assert device.gatt_server.attributes[0].uuid == GATT_GENERIC_ACCESS_SERVICE |
| 242 | assert device.gatt_server.attributes[1].type == GATT_CHARACTERISTIC_ATTRIBUTE_TYPE |
| 243 | assert device.gatt_server.attributes[2].uuid == GATT_DEVICE_NAME_CHARACTERISTIC |
| 244 | assert device.gatt_server.attributes[3].type == GATT_CHARACTERISTIC_ATTRIBUTE_TYPE |
| 245 | assert device.gatt_server.attributes[4].uuid == GATT_APPEARANCE_CHARACTERISTIC |
| 246 | |
| 247 | |
| 248 | # ----------------------------------------------------------------------------- |
| 249 | def test_gatt_services_without_gas(): |
| 250 | device = Device(host=Host(None, None), generic_access_service=False) |
| 251 | |
| 252 | # there should be no services |
| 253 | assert len(device.gatt_server.attributes) == 0 |
| 254 | |
| 255 | |
| 256 | # ----------------------------------------------------------------------------- |
Abel Lucas | 287df94 | 2022-11-29 00:59:04 +0000 | [diff] [blame] | 257 | async def run_test_device(): |
| 258 | await test_device_connect_parallel() |
| 259 | await test_flush() |
| 260 | await test_gatt_services_with_gas() |
| 261 | await test_gatt_services_without_gas() |
| 262 | |
| 263 | |
| 264 | # ----------------------------------------------------------------------------- |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 265 | if __name__ == '__main__': |
Gilles Boccon-Gibod | 135df0d | 2022-12-10 08:53:51 -0800 | [diff] [blame] | 266 | logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper()) |
Abel Lucas | 16b4f18 | 2022-10-20 17:41:15 +0000 | [diff] [blame] | 267 | asyncio.run(run_test_device()) |