Compare commits

..

28 Commits

Author SHA1 Message Date
Gilles Boccon-Gibod
69c6643bb8 Merge pull request #452 from marshallpierce/mp/rust-0.2.0
Bumble crate 0.2.0
2024-03-21 17:15:43 -07:00
Marshall Pierce
b8214bf948 Bumble crate 0.2.0 2024-03-21 12:36:32 -06:00
Charlie Boutier
a9c62c44b3 pandora host: change AdvertisingType
change advertising type from high duty to low duty

Test: python le_host_test.py -c config.yml --test_bed android.bumbles --tests "test_scan('connectable','non_scannable','directed',0)" -v
2024-03-20 11:17:50 -07:00
Charlie Boutier
7d0b4ef4e0 pandora_server: Parse FLAGS into advertising data
Bug: 328089785
2024-03-18 09:20:55 -07:00
Charlie Boutier
313340f1c6 intel driver: check the vendorId and productId 2024-03-15 10:53:33 -07:00
Charlie Boutier
e8ed69fb09 pyusb: Collect vendorId and productId as metadata 2024-03-15 10:53:33 -07:00
David Duarte
16d5cf6770 usb: Add usb path moniker
Add a new moniker for usb and pyusb driver allowing
to select the usb device using its bus id and port
path like `usb:3-3.4.1`.
2024-03-15 09:17:39 -07:00
Gilles Boccon-Gibod
a2caf1deb2 Merge pull request #448 from BenjaminLawson/bump-avatar
Bump pandora-avatar to 0.0.8
2024-03-14 20:49:28 -07:00
Ben Lawson
01bfdd2c98 Bump pandora-avater to 0.0.8 2024-03-14 14:13:27 -07:00
Gilles Boccon-Gibod
4a60df108a Merge pull request #447 from BenjaminLawson/bump-rootcanal
Bump rootcanal to 1.9.0
2024-03-14 14:00:36 -07:00
Ben Lawson
ad48109748 Bump rootcanal to 1.9.0 2024-03-14 13:15:02 -07:00
Gilles Boccon-Gibod
44c51c13ac Merge pull request #445 from google/gbg/driver-probe-fix
fix intel driver probe
2024-03-12 12:51:08 -07:00
Gilles Boccon-Gibod
7507be1eab update metadata when setting the host controller directly 2024-03-12 11:50:47 -07:00
Gilles Boccon-Gibod
cbe9446dcf fix intel driver probe 2024-03-12 09:54:20 -07:00
Charlie Boutier
174930399a intel: send vsc INTEL_DDC_CONFIG_WRITE
This VSC enable host-initiated role-switching after connection.

Implement this VSC in a driver fashion.

Test: avatar security_test with the Bluetooth Dongle Intel BE200
2024-03-11 09:15:18 -07:00
Gilles Boccon-Gibod
1f3aee5566 Merge pull request #438 from BenjaminLawson/pandora-extended-advertising
Implement Pandora extended advertising
2024-03-07 20:36:56 -08:00
Ben Lawson
256044a789 Implement Pandora extended advertising
Support setting the PHY of Pandora scans.
2024-03-07 16:18:49 -08:00
Gilles Boccon-Gibod
e554bd1033 Merge pull request #434 from google/gbg/show-timestamps
show timestamps from snoop logs
2024-02-29 11:44:23 -08:00
Gilles Boccon-Gibod
38981cefa1 pad index field 2024-02-28 11:46:35 -08:00
Gilles Boccon-Gibod
f2d601f411 show timestamps from snoop logs 2024-02-27 16:40:37 -08:00
zxzxwu
6e7c64c1de Merge pull request #431 from zxzxwu/rust
Bump Rust to 1.76.0
2024-02-23 15:14:30 +08:00
Josh Wu
565d51f4db Bump Rust to 1.76.0
```
error: failed to compile `cargo-all-features v1.10.0`, intermediate artifacts can be found at `/tmp/cargo-installshCmAG`

Caused by:
  package `clap v4.5.1` cannot be built because it requires rustc 1.74 or newer, while the currently active rustc version is 1.70.0
  Try re-running cargo install with `--locked`

```
2024-02-22 15:22:20 +08:00
Gilles Boccon-Gibod
de8f3d9c1e Merge pull request #426 from akuker/patch-1
Add clarification to short circuit list feature
2024-02-12 21:22:14 -08:00
Tony Kuker
cde6d48690 Add clarification to short circuit list feature 2024-02-12 12:22:36 -06:00
zxzxwu
02180088b3 Merge pull request #425 from zxzxwu/command
Refactor command supporting list
2024-02-07 21:45:52 +08:00
zxzxwu
90f49267d1 Merge pull request #424 from zxzxwu/adv
Fix double-disable legacy advertising set
2024-02-06 16:13:51 +08:00
Josh Wu
0e6d69cd7b Refactor command supporting list 2024-02-06 12:06:00 +08:00
Josh Wu
9eccc583d5 Fix double-disable legacy advertising set
When legacy advertising set is disabled passively(by set termination),
the legacy advertising set won't be released, and the next
stop_advertising() call will try to disable it again and cause an error.
2024-02-06 12:00:30 +08:00
24 changed files with 766 additions and 621 deletions

View File

@@ -47,7 +47,7 @@ jobs:
strategy:
matrix:
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
rust-version: [ "1.70.0", "stable" ]
rust-version: [ "1.76.0", "stable" ]
fail-fast: false
steps:
- name: Check out from Git

3
.gitignore vendored
View File

@@ -9,6 +9,9 @@ __pycache__
# generated by setuptools_scm
bumble/_version.py
.vscode/launch.json
.vscode/settings.json
/.idea
venv/
.venv/
# snoop logs
out/

View File

@@ -15,7 +15,11 @@
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
import datetime
import logging
import os
import struct
import click
from bumble.colors import color
@@ -24,6 +28,14 @@ from bumble.transport.common import PacketReader
from bumble.helpers import PacketTracer
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
# Classes
# -----------------------------------------------------------------------------
class SnoopPacketReader:
'''
@@ -36,12 +48,18 @@ class SnoopPacketReader:
DATALINK_BSCP = 1003
DATALINK_H5 = 1004
IDENTIFICATION_PATTERN = b'btsnoop\0'
TIMESTAMP_ANCHOR = datetime.datetime(2000, 1, 1)
TIMESTAMP_DELTA = 0x00E03AB44A676000
ONE_MICROSECOND = datetime.timedelta(microseconds=1)
def __init__(self, source):
self.source = source
self.at_end = False
# Read the header
identification_pattern = source.read(8)
if identification_pattern.hex().lower() != '6274736e6f6f7000':
if identification_pattern != self.IDENTIFICATION_PATTERN:
raise ValueError(
'not a valid snoop file, unexpected identification pattern'
)
@@ -55,19 +73,32 @@ class SnoopPacketReader:
# Read the record header
header = self.source.read(24)
if len(header) < 24:
return (0, None)
self.at_end = True
return (None, 0, None)
# Parse the header
(
original_length,
included_length,
packet_flags,
_cumulative_drops,
_timestamp_seconds,
_timestamp_microsecond,
) = struct.unpack('>IIIIII', header)
timestamp,
) = struct.unpack('>IIIIQ', header)
# Abort on truncated packets
# Skip truncated packets
if original_length != included_length:
return (0, None)
print(
color(
f"!!! truncated packet ({included_length}/{original_length})", "red"
)
)
self.source.read(included_length)
return (None, 0, None)
# Convert the timestamp to a datetime object.
ts_dt = self.TIMESTAMP_ANCHOR + datetime.timedelta(
microseconds=timestamp - self.TIMESTAMP_DELTA
)
if self.data_link_type == self.DATALINK_H1:
# The packet is un-encapsulated, look at the flags to figure out its type
@@ -89,7 +120,17 @@ class SnoopPacketReader:
bytes([packet_type]) + self.source.read(included_length),
)
return (packet_flags & 1, self.source.read(included_length))
return (ts_dt, packet_flags & 1, self.source.read(included_length))
# -----------------------------------------------------------------------------
class Printer:
def __init__(self):
self.index = 0
def print(self, message: str) -> None:
self.index += 1
print(f"[{self.index:8}]{message}")
# -----------------------------------------------------------------------------
@@ -122,24 +163,28 @@ def main(format, vendors, filename):
packet_reader = PacketReader(input)
def read_next_packet():
return (0, packet_reader.next_packet())
return (None, 0, packet_reader.next_packet())
else:
packet_reader = SnoopPacketReader(input)
read_next_packet = packet_reader.next_packet
tracer = PacketTracer(emit_message=print)
printer = Printer()
tracer = PacketTracer(emit_message=printer.print)
while True:
while not packet_reader.at_end:
try:
(direction, packet) = read_next_packet()
if packet is None:
break
tracer.trace(hci.HCI_Packet.from_bytes(packet), direction)
(timestamp, direction, packet) = read_next_packet()
if packet:
tracer.trace(hci.HCI_Packet.from_bytes(packet), direction, timestamp)
else:
printer.print(color("[TRUNCATED]", "red"))
except Exception as error:
logger.exception()
print(color(f'!!! {error}', 'red'))
# -----------------------------------------------------------------------------
if __name__ == '__main__':
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper())
main() # pylint: disable=no-value-for-parameter

View File

@@ -2065,7 +2065,9 @@ class Device(CompositeEventEmitter):
"""Stop legacy advertising."""
# Disable advertising
if self.legacy_advertising_set:
await self.legacy_advertising_set.stop()
if self.legacy_advertising_set.enabled:
await self.legacy_advertising_set.stop()
await self.legacy_advertising_set.remove()
self.legacy_advertising_set = None
elif self.legacy_advertiser:
await self.legacy_advertiser.stop()
@@ -2110,6 +2112,20 @@ class Device(CompositeEventEmitter):
Returns:
An AdvertisingSet instance.
"""
# Instantiate default values
if advertising_parameters is None:
advertising_parameters = AdvertisingParameters()
if (
not advertising_parameters.advertising_event_properties.is_legacy
and advertising_data
and scan_response_data
):
raise ValueError(
"Extended advertisements can't have both data and scan \
response data"
)
# Allocate a new handle
try:
advertising_handle = next(
@@ -2123,10 +2139,6 @@ class Device(CompositeEventEmitter):
except StopIteration as exc:
raise RuntimeError("all valid advertising handles already in use") from exc
# Instantiate default values
if advertising_parameters is None:
advertising_parameters = AdvertisingParameters()
# Use the device's random address if a random address is needed but none was
# provided.
if (
@@ -2207,9 +2219,6 @@ class Device(CompositeEventEmitter):
if self.legacy_advertiser:
return True
if self.legacy_advertising_set and self.legacy_advertising_set.enabled:
return True
return any(
advertising_set.enabled
for advertising_set in self.extended_advertising_sets.values()
@@ -2223,7 +2232,7 @@ class Device(CompositeEventEmitter):
scan_window: int = DEVICE_DEFAULT_SCAN_WINDOW, # Scan window in ms
own_address_type: int = OwnAddressType.RANDOM,
filter_duplicates: bool = False,
scanning_phys: Tuple[int, int] = (HCI_LE_1M_PHY, HCI_LE_CODED_PHY),
scanning_phys: List[int] = [HCI_LE_1M_PHY, HCI_LE_CODED_PHY],
) -> None:
# Check that the arguments are legal
if scan_interval < scan_window:
@@ -3541,11 +3550,9 @@ class Device(CompositeEventEmitter):
connection_handle,
number_of_completed_extended_advertising_events,
):
# Legacy advertising set is also one of extended advertising sets.
if not (
advertising_set := (
self.extended_advertising_sets.get(advertising_handle)
or self.legacy_advertising_set
)
advertising_set := self.extended_advertising_sets.get(advertising_handle)
):
logger.warning(f'advertising set {advertising_handle} not found')
return

View File

@@ -25,7 +25,7 @@ import pathlib
import platform
from typing import Dict, Iterable, Optional, Type, TYPE_CHECKING
from . import rtk
from . import rtk, intel
from .common import Driver
if TYPE_CHECKING:
@@ -45,7 +45,7 @@ async def get_driver_for_host(host: Host) -> Optional[Driver]:
found.
If a "driver" HCI metadata entry is present, only that driver class will be probed.
"""
driver_classes: Dict[str, Type[Driver]] = {"rtk": rtk.Driver}
driver_classes: Dict[str, Type[Driver]] = {"rtk": rtk.Driver, "intel": intel.Driver}
probe_list: Iterable[str]
if driver_name := host.hci_metadata.get("driver"):
# Only probe a single driver

102
bumble/drivers/intel.py Normal file
View File

@@ -0,0 +1,102 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
import logging
from bumble.drivers import common
from bumble.hci import (
hci_vendor_command_op_code, # type: ignore
HCI_Command,
HCI_Reset_Command,
)
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
# Constant
# -----------------------------------------------------------------------------
INTEL_USB_PRODUCTS = {
# Intel AX210
(0x8087, 0x0032),
# Intel BE200
(0x8087, 0x0036),
}
# -----------------------------------------------------------------------------
# HCI Commands
# -----------------------------------------------------------------------------
HCI_INTEL_DDC_CONFIG_WRITE_COMMAND = hci_vendor_command_op_code(0xFC8B) # type: ignore
HCI_INTEL_DDC_CONFIG_WRITE_PAYLOAD = [0x03, 0xE4, 0x02, 0x00]
HCI_Command.register_commands(globals())
@HCI_Command.command( # type: ignore
fields=[("params", "*")],
return_parameters_fields=[
("params", "*"),
],
)
class Hci_Intel_DDC_Config_Write_Command(HCI_Command):
pass
class Driver(common.Driver):
def __init__(self, host):
self.host = host
@staticmethod
def check(host):
driver = host.hci_metadata.get("driver")
if driver == "intel":
return True
vendor_id = host.hci_metadata.get("vendor_id")
product_id = host.hci_metadata.get("product_id")
if vendor_id is None or product_id is None:
logger.debug("USB metadata not sufficient")
return False
if (vendor_id, product_id) not in INTEL_USB_PRODUCTS:
logger.debug(
f"USB device ({vendor_id:04X}, {product_id:04X}) " "not in known list"
)
return False
return True
@classmethod
async def for_host(cls, host, force=False): # type: ignore
# Only instantiate this driver if explicitly selected
if not force and not cls.check(host):
return None
return cls(host)
async def init_controller(self):
self.host.ready = True
await self.host.send_command(HCI_Reset_Command(), check_result=True)
await self.host.send_command(
Hci_Intel_DDC_Config_Write_Command(
params=HCI_INTEL_DDC_CONFIG_WRITE_PAYLOAD
)
)

View File

@@ -790,538 +790,317 @@ HCI_RANDOM_DEVICE_ADDRESS_TYPE = 0x01
HCI_PUBLIC_IDENTITY_ADDRESS_TYPE = 0x02
HCI_RANDOM_IDENTITY_ADDRESS_TYPE = 0x03
# Supported Commands Flags
# Supported Commands Masks
# See Bluetooth spec @ 6.27 SUPPORTED COMMANDS
HCI_SUPPORTED_COMMANDS_FLAGS = (
# Octet 0
(
HCI_INQUIRY_COMMAND,
HCI_INQUIRY_CANCEL_COMMAND,
HCI_PERIODIC_INQUIRY_MODE_COMMAND,
HCI_EXIT_PERIODIC_INQUIRY_MODE_COMMAND,
HCI_CREATE_CONNECTION_COMMAND,
HCI_DISCONNECT_COMMAND,
None,
HCI_CREATE_CONNECTION_CANCEL_COMMAND
),
# Octet 1
(
HCI_ACCEPT_CONNECTION_REQUEST_COMMAND,
HCI_REJECT_CONNECTION_REQUEST_COMMAND,
HCI_LINK_KEY_REQUEST_REPLY_COMMAND,
HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY_COMMAND,
HCI_PIN_CODE_REQUEST_REPLY_COMMAND,
HCI_PIN_CODE_REQUEST_NEGATIVE_REPLY_COMMAND,
HCI_CHANGE_CONNECTION_PACKET_TYPE_COMMAND,
HCI_AUTHENTICATION_REQUESTED_COMMAND
),
# Octet 2
(
HCI_SET_CONNECTION_ENCRYPTION_COMMAND,
HCI_CHANGE_CONNECTION_LINK_KEY_COMMAND,
HCI_LINK_KEY_SELECTION_COMMAND,
HCI_REMOTE_NAME_REQUEST_COMMAND,
HCI_REMOTE_NAME_REQUEST_CANCEL_COMMAND,
HCI_READ_REMOTE_SUPPORTED_FEATURES_COMMAND,
HCI_READ_REMOTE_EXTENDED_FEATURES_COMMAND,
HCI_READ_REMOTE_VERSION_INFORMATION_COMMAND
),
# Octet 3
(
HCI_READ_CLOCK_OFFSET_COMMAND,
HCI_READ_LMP_HANDLE_COMMAND,
None,
None,
None,
None,
None,
None
),
# Octet 4
(
None,
HCI_HOLD_MODE_COMMAND,
HCI_SNIFF_MODE_COMMAND,
HCI_EXIT_SNIFF_MODE_COMMAND,
None,
None,
HCI_QOS_SETUP_COMMAND,
HCI_ROLE_DISCOVERY_COMMAND
),
# Octet 5
(
HCI_SWITCH_ROLE_COMMAND,
HCI_READ_LINK_POLICY_SETTINGS_COMMAND,
HCI_WRITE_LINK_POLICY_SETTINGS_COMMAND,
HCI_READ_DEFAULT_LINK_POLICY_SETTINGS_COMMAND,
HCI_WRITE_DEFAULT_LINK_POLICY_SETTINGS_COMMAND,
HCI_FLOW_SPECIFICATION_COMMAND,
HCI_SET_EVENT_MASK_COMMAND,
HCI_RESET_COMMAND
),
# Octet 6
(
HCI_SET_EVENT_FILTER_COMMAND,
HCI_FLUSH_COMMAND,
HCI_READ_PIN_TYPE_COMMAND,
HCI_WRITE_PIN_TYPE_COMMAND,
None,
HCI_READ_STORED_LINK_KEY_COMMAND,
HCI_WRITE_STORED_LINK_KEY_COMMAND,
HCI_DELETE_STORED_LINK_KEY_COMMAND
),
# Octet 7
(
HCI_WRITE_LOCAL_NAME_COMMAND,
HCI_READ_LOCAL_NAME_COMMAND,
HCI_READ_CONNECTION_ACCEPT_TIMEOUT_COMMAND,
HCI_WRITE_CONNECTION_ACCEPT_TIMEOUT_COMMAND,
HCI_READ_PAGE_TIMEOUT_COMMAND,
HCI_WRITE_PAGE_TIMEOUT_COMMAND,
HCI_READ_SCAN_ENABLE_COMMAND,
HCI_WRITE_SCAN_ENABLE_COMMAND
),
# Octet 8
(
HCI_READ_PAGE_SCAN_ACTIVITY_COMMAND,
HCI_WRITE_PAGE_SCAN_ACTIVITY_COMMAND,
HCI_READ_INQUIRY_SCAN_ACTIVITY_COMMAND,
HCI_WRITE_INQUIRY_SCAN_ACTIVITY_COMMAND,
HCI_READ_AUTHENTICATION_ENABLE_COMMAND,
HCI_WRITE_AUTHENTICATION_ENABLE_COMMAND,
None,
None
),
# Octet 9
(
HCI_READ_CLASS_OF_DEVICE_COMMAND,
HCI_WRITE_CLASS_OF_DEVICE_COMMAND,
HCI_READ_VOICE_SETTING_COMMAND,
HCI_WRITE_VOICE_SETTING_COMMAND,
HCI_READ_AUTOMATIC_FLUSH_TIMEOUT_COMMAND,
HCI_WRITE_AUTOMATIC_FLUSH_TIMEOUT_COMMAND,
HCI_READ_NUM_BROADCAST_RETRANSMISSIONS_COMMAND,
HCI_WRITE_NUM_BROADCAST_RETRANSMISSIONS_COMMAND
),
# Octet 10
(
HCI_READ_HOLD_MODE_ACTIVITY_COMMAND,
HCI_WRITE_HOLD_MODE_ACTIVITY_COMMAND,
HCI_READ_TRANSMIT_POWER_LEVEL_COMMAND,
HCI_READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND,
HCI_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND,
HCI_SET_CONTROLLER_TO_HOST_FLOW_CONTROL_COMMAND,
HCI_HOST_BUFFER_SIZE_COMMAND,
HCI_HOST_NUMBER_OF_COMPLETED_PACKETS_COMMAND
),
# Octet 11
(
HCI_READ_LINK_SUPERVISION_TIMEOUT_COMMAND,
HCI_WRITE_LINK_SUPERVISION_TIMEOUT_COMMAND,
HCI_READ_NUMBER_OF_SUPPORTED_IAC_COMMAND,
HCI_READ_CURRENT_IAC_LAP_COMMAND,
HCI_WRITE_CURRENT_IAC_LAP_COMMAND,
None,
None,
None
),
# Octet 12
(
None,
HCI_SET_AFH_HOST_CHANNEL_CLASSIFICATION_COMMAND,
None,
None,
HCI_READ_INQUIRY_SCAN_TYPE_COMMAND,
HCI_WRITE_INQUIRY_SCAN_TYPE_COMMAND,
HCI_READ_INQUIRY_MODE_COMMAND,
HCI_WRITE_INQUIRY_MODE_COMMAND
),
# Octet 13
(
HCI_READ_PAGE_SCAN_TYPE_COMMAND,
HCI_WRITE_PAGE_SCAN_TYPE_COMMAND,
HCI_READ_AFH_CHANNEL_ASSESSMENT_MODE_COMMAND,
HCI_WRITE_AFH_CHANNEL_ASSESSMENT_MODE_COMMAND,
None,
None,
None,
None,
),
# Octet 14
(
None,
None,
None,
HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND,
None,
HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND,
HCI_READ_LOCAL_EXTENDED_FEATURES_COMMAND,
HCI_READ_BUFFER_SIZE_COMMAND
),
# Octet 15
(
None,
HCI_READ_BD_ADDR_COMMAND,
HCI_READ_FAILED_CONTACT_COUNTER_COMMAND,
HCI_RESET_FAILED_CONTACT_COUNTER_COMMAND,
HCI_READ_LINK_QUALITY_COMMAND,
HCI_READ_RSSI_COMMAND,
HCI_READ_AFH_CHANNEL_MAP_COMMAND,
HCI_READ_CLOCK_COMMAND
),
# Octet 16
(
HCI_READ_LOOPBACK_MODE_COMMAND,
HCI_WRITE_LOOPBACK_MODE_COMMAND,
HCI_ENABLE_DEVICE_UNDER_TEST_MODE_COMMAND,
HCI_SETUP_SYNCHRONOUS_CONNECTION_COMMAND,
HCI_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND,
HCI_REJECT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND,
None,
None,
),
# Octet 17
(
HCI_READ_EXTENDED_INQUIRY_RESPONSE_COMMAND,
HCI_WRITE_EXTENDED_INQUIRY_RESPONSE_COMMAND,
HCI_REFRESH_ENCRYPTION_KEY_COMMAND,
None,
HCI_SNIFF_SUBRATING_COMMAND,
HCI_READ_SIMPLE_PAIRING_MODE_COMMAND,
HCI_WRITE_SIMPLE_PAIRING_MODE_COMMAND,
HCI_READ_LOCAL_OOB_DATA_COMMAND
),
# Octet 18
(
HCI_READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL_COMMAND,
HCI_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_COMMAND,
HCI_READ_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND,
HCI_WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND,
None,
None,
None,
HCI_IO_CAPABILITY_REQUEST_REPLY_COMMAND
),
# Octet 19
(
HCI_USER_CONFIRMATION_REQUEST_REPLY_COMMAND,
HCI_USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY_COMMAND,
HCI_USER_PASSKEY_REQUEST_REPLY_COMMAND,
HCI_USER_PASSKEY_REQUEST_NEGATIVE_REPLY_COMMAND,
HCI_REMOTE_OOB_DATA_REQUEST_REPLY_COMMAND,
HCI_WRITE_SIMPLE_PAIRING_DEBUG_MODE_COMMAND,
HCI_ENHANCED_FLUSH_COMMAND,
HCI_REMOTE_OOB_DATA_REQUEST_NEGATIVE_REPLY_COMMAND
),
# Octet 20
(
None,
None,
HCI_SEND_KEYPRESS_NOTIFICATION_COMMAND,
HCI_IO_CAPABILITY_REQUEST_NEGATIVE_REPLY_COMMAND,
HCI_READ_ENCRYPTION_KEY_SIZE_COMMAND,
None,
None,
None,
),
# Octet 21
(
None,
None,
None,
None,
None,
None,
None,
None,
),
# Octet 22
(
None,
None,
HCI_SET_EVENT_MASK_PAGE_2_COMMAND,
None,
None,
None,
None,
None,
),
# Octet 23
(
HCI_READ_FLOW_CONTROL_MODE_COMMAND,
HCI_WRITE_FLOW_CONTROL_MODE_COMMAND,
HCI_READ_DATA_BLOCK_SIZE_COMMAND,
None,
None,
None,
None,
None,
),
# Octet 24
(
HCI_READ_ENHANCED_TRANSMIT_POWER_LEVEL_COMMAND,
None,
None,
None,
None,
HCI_READ_LE_HOST_SUPPORT_COMMAND,
HCI_WRITE_LE_HOST_SUPPORT_COMMAND,
None,
),
# Octet 25
(
HCI_LE_SET_EVENT_MASK_COMMAND,
HCI_LE_READ_BUFFER_SIZE_COMMAND,
HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND,
None,
HCI_LE_SET_RANDOM_ADDRESS_COMMAND,
HCI_LE_SET_ADVERTISING_PARAMETERS_COMMAND,
HCI_LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER_COMMAND,
HCI_LE_SET_ADVERTISING_DATA_COMMAND,
),
# Octet 26
(
HCI_LE_SET_SCAN_RESPONSE_DATA_COMMAND,
HCI_LE_SET_ADVERTISING_ENABLE_COMMAND,
HCI_LE_SET_SCAN_PARAMETERS_COMMAND,
HCI_LE_SET_SCAN_ENABLE_COMMAND,
HCI_LE_CREATE_CONNECTION_COMMAND,
HCI_LE_CREATE_CONNECTION_CANCEL_COMMAND,
HCI_LE_READ_FILTER_ACCEPT_LIST_SIZE_COMMAND,
HCI_LE_CLEAR_FILTER_ACCEPT_LIST_COMMAND
),
# Octet 27
(
HCI_LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST_COMMAND,
HCI_LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST_COMMAND,
HCI_LE_CONNECTION_UPDATE_COMMAND,
HCI_LE_SET_HOST_CHANNEL_CLASSIFICATION_COMMAND,
HCI_LE_READ_CHANNEL_MAP_COMMAND,
HCI_LE_READ_REMOTE_FEATURES_COMMAND,
HCI_LE_ENCRYPT_COMMAND,
HCI_LE_RAND_COMMAND
),
# Octet 28
(
HCI_LE_ENABLE_ENCRYPTION_COMMAND,
HCI_LE_LONG_TERM_KEY_REQUEST_REPLY_COMMAND,
HCI_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY_COMMAND,
HCI_LE_READ_SUPPORTED_STATES_COMMAND,
HCI_LE_RECEIVER_TEST_COMMAND,
HCI_LE_TRANSMITTER_TEST_COMMAND,
HCI_LE_TEST_END_COMMAND,
None,
),
# Octet 29
(
None,
None,
None,
HCI_ENHANCED_SETUP_SYNCHRONOUS_CONNECTION_COMMAND,
HCI_ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND,
HCI_READ_LOCAL_SUPPORTED_CODECS_COMMAND,
HCI_SET_MWS_CHANNEL_PARAMETERS_COMMAND,
HCI_SET_EXTERNAL_FRAME_CONFIGURATION_COMMAND
),
# Octet 30
(
HCI_SET_MWS_SIGNALING_COMMAND,
HCI_SET_MWS_TRANSPORT_LAYER_COMMAND,
HCI_SET_MWS_SCAN_FREQUENCY_TABLE_COMMAND,
HCI_GET_MWS_TRANSPORT_LAYER_CONFIGURATION_COMMAND,
HCI_SET_MWS_PATTERN_CONFIGURATION_COMMAND,
HCI_SET_TRIGGERED_CLOCK_CAPTURE_COMMAND,
HCI_TRUNCATED_PAGE_COMMAND,
HCI_TRUNCATED_PAGE_CANCEL_COMMAND
),
# Octet 31
(
HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_COMMAND,
HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_RECEIVE_COMMAND,
HCI_START_SYNCHRONIZATION_TRAIN_COMMAND,
HCI_RECEIVE_SYNCHRONIZATION_TRAIN_COMMAND,
HCI_SET_RESERVED_LT_ADDR_COMMAND,
HCI_DELETE_RESERVED_LT_ADDR_COMMAND,
HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_DATA_COMMAND,
HCI_READ_SYNCHRONIZATION_TRAIN_PARAMETERS_COMMAND
),
# Octet 32
(
HCI_WRITE_SYNCHRONIZATION_TRAIN_PARAMETERS_COMMAND,
HCI_REMOTE_OOB_EXTENDED_DATA_REQUEST_REPLY_COMMAND,
HCI_READ_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND,
HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND,
HCI_READ_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND,
HCI_WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND,
HCI_READ_LOCAL_OOB_EXTENDED_DATA_COMMAND,
HCI_WRITE_SECURE_CONNECTIONS_TEST_MODE_COMMAND
),
# Octet 33
(
HCI_READ_EXTENDED_PAGE_TIMEOUT_COMMAND,
HCI_WRITE_EXTENDED_PAGE_TIMEOUT_COMMAND,
HCI_READ_EXTENDED_INQUIRY_LENGTH_COMMAND,
HCI_WRITE_EXTENDED_INQUIRY_LENGTH_COMMAND,
HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY_COMMAND,
HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY_COMMAND,
HCI_LE_SET_DATA_LENGTH_COMMAND,
HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND
),
# Octet 34
(
HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMMAND,
HCI_LE_GENERATE_DHKEY_COMMAND,
HCI_LE_ADD_DEVICE_TO_RESOLVING_LIST_COMMAND,
HCI_LE_REMOVE_DEVICE_FROM_RESOLVING_LIST_COMMAND,
HCI_LE_CLEAR_RESOLVING_LIST_COMMAND,
HCI_LE_READ_RESOLVING_LIST_SIZE_COMMAND,
HCI_LE_READ_PEER_RESOLVABLE_ADDRESS_COMMAND
),
# Octet 35
(
HCI_LE_READ_LOCAL_RESOLVABLE_ADDRESS_COMMAND,
HCI_LE_SET_ADDRESS_RESOLUTION_ENABLE_COMMAND,
HCI_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT_COMMAND,
HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND,
HCI_LE_READ_PHY_COMMAND,
HCI_LE_SET_DEFAULT_PHY_COMMAND,
HCI_LE_SET_PHY_COMMAND,
HCI_LE_RECEIVER_TEST_V2_COMMAND
),
# Octet 36
(
HCI_LE_TRANSMITTER_TEST_V2_COMMAND,
HCI_LE_SET_ADVERTISING_SET_RANDOM_ADDRESS_COMMAND,
HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_COMMAND,
HCI_LE_SET_EXTENDED_ADVERTISING_DATA_COMMAND,
HCI_LE_SET_EXTENDED_SCAN_RESPONSE_DATA_COMMAND,
HCI_LE_SET_EXTENDED_ADVERTISING_ENABLE_COMMAND,
HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND,
HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND,
),
# Octet 37
(
HCI_LE_REMOVE_ADVERTISING_SET_COMMAND,
HCI_LE_CLEAR_ADVERTISING_SETS_COMMAND,
HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_COMMAND,
HCI_LE_SET_PERIODIC_ADVERTISING_DATA_COMMAND,
HCI_LE_SET_PERIODIC_ADVERTISING_ENABLE_COMMAND,
HCI_LE_SET_EXTENDED_SCAN_PARAMETERS_COMMAND,
HCI_LE_SET_EXTENDED_SCAN_ENABLE_COMMAND,
HCI_LE_EXTENDED_CREATE_CONNECTION_COMMAND
),
# Octet 38
(
HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_COMMAND,
HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL_COMMAND,
HCI_LE_PERIODIC_ADVERTISING_TERMINATE_SYNC_COMMAND,
HCI_LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST_COMMAND,
HCI_LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST_COMMAND,
HCI_LE_CLEAR_PERIODIC_ADVERTISER_LIST_COMMAND,
HCI_LE_READ_PERIODIC_ADVERTISER_LIST_SIZE_COMMAND,
HCI_LE_READ_TRANSMIT_POWER_COMMAND
),
# Octet 39
(
HCI_LE_READ_RF_PATH_COMPENSATION_COMMAND,
HCI_LE_WRITE_RF_PATH_COMPENSATION_COMMAND,
HCI_LE_SET_PRIVACY_MODE_COMMAND,
HCI_LE_RECEIVER_TEST_V3_COMMAND,
HCI_LE_TRANSMITTER_TEST_V3_COMMAND,
HCI_LE_SET_CONNECTIONLESS_CTE_TRANSMIT_PARAMETERS_COMMAND,
HCI_LE_SET_CONNECTIONLESS_CTE_TRANSMIT_ENABLE_COMMAND,
HCI_LE_SET_CONNECTIONLESS_IQ_SAMPLING_ENABLE_COMMAND,
),
# Octet 40
(
HCI_LE_SET_CONNECTION_CTE_RECEIVE_PARAMETERS_COMMAND,
HCI_LE_SET_CONNECTION_CTE_TRANSMIT_PARAMETERS_COMMAND,
HCI_LE_CONNECTION_CTE_REQUEST_ENABLE_COMMAND,
HCI_LE_CONNECTION_CTE_RESPONSE_ENABLE_COMMAND,
HCI_LE_READ_ANTENNA_INFORMATION_COMMAND,
HCI_LE_SET_PERIODIC_ADVERTISING_RECEIVE_ENABLE_COMMAND,
HCI_LE_PERIODIC_ADVERTISING_SYNC_TRANSFER_COMMAND,
HCI_LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER_COMMAND
),
# Octet 41
(
HCI_LE_SET_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS_COMMAND,
HCI_LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS_COMMAND,
HCI_LE_GENERATE_DHKEY_V2_COMMAND,
HCI_READ_LOCAL_SIMPLE_PAIRING_OPTIONS_COMMAND,
HCI_LE_MODIFY_SLEEP_CLOCK_ACCURACY_COMMAND,
HCI_LE_READ_BUFFER_SIZE_V2_COMMAND,
HCI_LE_READ_ISO_TX_SYNC_COMMAND,
HCI_LE_SET_CIG_PARAMETERS_COMMAND
),
# Octet 42
(
HCI_LE_SET_CIG_PARAMETERS_TEST_COMMAND,
HCI_LE_CREATE_CIS_COMMAND,
HCI_LE_REMOVE_CIG_COMMAND,
HCI_LE_ACCEPT_CIS_REQUEST_COMMAND,
HCI_LE_REJECT_CIS_REQUEST_COMMAND,
HCI_LE_CREATE_BIG_COMMAND,
HCI_LE_CREATE_BIG_TEST_COMMAND,
HCI_LE_TERMINATE_BIG_COMMAND,
),
# Octet 43
(
HCI_LE_BIG_CREATE_SYNC_COMMAND,
HCI_LE_BIG_TERMINATE_SYNC_COMMAND,
HCI_LE_REQUEST_PEER_SCA_COMMAND,
HCI_LE_SETUP_ISO_DATA_PATH_COMMAND,
HCI_LE_REMOVE_ISO_DATA_PATH_COMMAND,
HCI_LE_ISO_TRANSMIT_TEST_COMMAND,
HCI_LE_ISO_RECEIVE_TEST_COMMAND,
HCI_LE_ISO_READ_TEST_COUNTERS_COMMAND
),
# Octet 44
(
HCI_LE_ISO_TEST_END_COMMAND,
HCI_LE_SET_HOST_FEATURE_COMMAND,
HCI_LE_READ_ISO_LINK_QUALITY_COMMAND,
HCI_LE_ENHANCED_READ_TRANSMIT_POWER_LEVEL_COMMAND,
HCI_LE_READ_REMOTE_TRANSMIT_POWER_LEVEL_COMMAND,
HCI_LE_SET_PATH_LOSS_REPORTING_PARAMETERS_COMMAND,
HCI_LE_SET_PATH_LOSS_REPORTING_ENABLE_COMMAND,
HCI_LE_SET_TRANSMIT_POWER_REPORTING_ENABLE_COMMAND
),
# Octet 45
(
HCI_LE_TRANSMITTER_TEST_V4_COMMAND,
HCI_SET_ECOSYSTEM_BASE_INTERVAL_COMMAND,
HCI_READ_LOCAL_SUPPORTED_CODECS_V2_COMMAND,
HCI_READ_LOCAL_SUPPORTED_CODEC_CAPABILITIES_COMMAND,
HCI_READ_LOCAL_SUPPORTED_CONTROLLER_DELAY_COMMAND,
HCI_CONFIGURE_DATA_PATH_COMMAND,
HCI_LE_SET_DATA_RELATED_ADDRESS_CHANGES_COMMAND,
HCI_SET_MIN_ENCRYPTION_KEY_SIZE_COMMAND
),
# Octet 46
(
HCI_LE_SET_DEFAULT_SUBRATE_COMMAND,
HCI_LE_SUBRATE_REQUEST_COMMAND,
HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_V2_COMMAND,
None,
None,
HCI_LE_SET_PERIODIC_ADVERTISING_SUBEVENT_DATA_COMMAND,
HCI_LE_SET_PERIODIC_ADVERTISING_RESPONSE_DATA_COMMAND,
HCI_LE_SET_PERIODIC_SYNC_SUBEVENT_COMMAND
),
# Octet 47
(
HCI_LE_EXTENDED_CREATE_CONNECTION_V2_COMMAND,
HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_V2_COMMAND,
None,
None,
None,
None,
None,
None,
)
)
HCI_SUPPORTED_COMMANDS_MASKS = {
HCI_INQUIRY_COMMAND : 1 << (0*8+0),
HCI_INQUIRY_CANCEL_COMMAND : 1 << (0*8+1),
HCI_PERIODIC_INQUIRY_MODE_COMMAND : 1 << (0*8+2),
HCI_EXIT_PERIODIC_INQUIRY_MODE_COMMAND : 1 << (0*8+3),
HCI_CREATE_CONNECTION_COMMAND : 1 << (0*8+4),
HCI_DISCONNECT_COMMAND : 1 << (0*8+5),
HCI_CREATE_CONNECTION_CANCEL_COMMAND : 1 << (0*8+7),
HCI_ACCEPT_CONNECTION_REQUEST_COMMAND : 1 << (1*8+0),
HCI_REJECT_CONNECTION_REQUEST_COMMAND : 1 << (1*8+1),
HCI_LINK_KEY_REQUEST_REPLY_COMMAND : 1 << (1*8+2),
HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (1*8+3),
HCI_PIN_CODE_REQUEST_REPLY_COMMAND : 1 << (1*8+4),
HCI_PIN_CODE_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (1*8+5),
HCI_CHANGE_CONNECTION_PACKET_TYPE_COMMAND : 1 << (1*8+6),
HCI_AUTHENTICATION_REQUESTED_COMMAND : 1 << (1*8+7),
HCI_SET_CONNECTION_ENCRYPTION_COMMAND : 1 << (2*8+0),
HCI_CHANGE_CONNECTION_LINK_KEY_COMMAND : 1 << (2*8+1),
HCI_LINK_KEY_SELECTION_COMMAND : 1 << (2*8+2),
HCI_REMOTE_NAME_REQUEST_COMMAND : 1 << (2*8+3),
HCI_REMOTE_NAME_REQUEST_CANCEL_COMMAND : 1 << (2*8+4),
HCI_READ_REMOTE_SUPPORTED_FEATURES_COMMAND : 1 << (2*8+5),
HCI_READ_REMOTE_EXTENDED_FEATURES_COMMAND : 1 << (2*8+6),
HCI_READ_REMOTE_VERSION_INFORMATION_COMMAND : 1 << (2*8+7),
HCI_READ_CLOCK_OFFSET_COMMAND : 1 << (3*8+0),
HCI_READ_LMP_HANDLE_COMMAND : 1 << (3*8+1),
HCI_HOLD_MODE_COMMAND : 1 << (4*8+1),
HCI_SNIFF_MODE_COMMAND : 1 << (4*8+2),
HCI_EXIT_SNIFF_MODE_COMMAND : 1 << (4*8+3),
HCI_QOS_SETUP_COMMAND : 1 << (4*8+6),
HCI_ROLE_DISCOVERY_COMMAND : 1 << (4*8+7),
HCI_SWITCH_ROLE_COMMAND : 1 << (5*8+0),
HCI_READ_LINK_POLICY_SETTINGS_COMMAND : 1 << (5*8+1),
HCI_WRITE_LINK_POLICY_SETTINGS_COMMAND : 1 << (5*8+2),
HCI_READ_DEFAULT_LINK_POLICY_SETTINGS_COMMAND : 1 << (5*8+3),
HCI_WRITE_DEFAULT_LINK_POLICY_SETTINGS_COMMAND : 1 << (5*8+4),
HCI_FLOW_SPECIFICATION_COMMAND : 1 << (5*8+5),
HCI_SET_EVENT_MASK_COMMAND : 1 << (5*8+6),
HCI_RESET_COMMAND : 1 << (5*8+7),
HCI_SET_EVENT_FILTER_COMMAND : 1 << (6*8+0),
HCI_FLUSH_COMMAND : 1 << (6*8+1),
HCI_READ_PIN_TYPE_COMMAND : 1 << (6*8+2),
HCI_WRITE_PIN_TYPE_COMMAND : 1 << (6*8+3),
HCI_READ_STORED_LINK_KEY_COMMAND : 1 << (6*8+5),
HCI_WRITE_STORED_LINK_KEY_COMMAND : 1 << (6*8+6),
HCI_DELETE_STORED_LINK_KEY_COMMAND : 1 << (6*8+7),
HCI_WRITE_LOCAL_NAME_COMMAND : 1 << (7*8+0),
HCI_READ_LOCAL_NAME_COMMAND : 1 << (7*8+1),
HCI_READ_CONNECTION_ACCEPT_TIMEOUT_COMMAND : 1 << (7*8+2),
HCI_WRITE_CONNECTION_ACCEPT_TIMEOUT_COMMAND : 1 << (7*8+3),
HCI_READ_PAGE_TIMEOUT_COMMAND : 1 << (7*8+4),
HCI_WRITE_PAGE_TIMEOUT_COMMAND : 1 << (7*8+5),
HCI_READ_SCAN_ENABLE_COMMAND : 1 << (7*8+6),
HCI_WRITE_SCAN_ENABLE_COMMAND : 1 << (7*8+7),
HCI_READ_PAGE_SCAN_ACTIVITY_COMMAND : 1 << (8*8+0),
HCI_WRITE_PAGE_SCAN_ACTIVITY_COMMAND : 1 << (8*8+1),
HCI_READ_INQUIRY_SCAN_ACTIVITY_COMMAND : 1 << (8*8+2),
HCI_WRITE_INQUIRY_SCAN_ACTIVITY_COMMAND : 1 << (8*8+3),
HCI_READ_AUTHENTICATION_ENABLE_COMMAND : 1 << (8*8+4),
HCI_WRITE_AUTHENTICATION_ENABLE_COMMAND : 1 << (8*8+5),
HCI_READ_CLASS_OF_DEVICE_COMMAND : 1 << (9*8+0),
HCI_WRITE_CLASS_OF_DEVICE_COMMAND : 1 << (9*8+1),
HCI_READ_VOICE_SETTING_COMMAND : 1 << (9*8+2),
HCI_WRITE_VOICE_SETTING_COMMAND : 1 << (9*8+3),
HCI_READ_AUTOMATIC_FLUSH_TIMEOUT_COMMAND : 1 << (9*8+4),
HCI_WRITE_AUTOMATIC_FLUSH_TIMEOUT_COMMAND : 1 << (9*8+5),
HCI_READ_NUM_BROADCAST_RETRANSMISSIONS_COMMAND : 1 << (9*8+6),
HCI_WRITE_NUM_BROADCAST_RETRANSMISSIONS_COMMAND : 1 << (9*8+7),
HCI_READ_HOLD_MODE_ACTIVITY_COMMAND : 1 << (10*8+0),
HCI_WRITE_HOLD_MODE_ACTIVITY_COMMAND : 1 << (10*8+1),
HCI_READ_TRANSMIT_POWER_LEVEL_COMMAND : 1 << (10*8+2),
HCI_READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND : 1 << (10*8+3),
HCI_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE_COMMAND : 1 << (10*8+4),
HCI_SET_CONTROLLER_TO_HOST_FLOW_CONTROL_COMMAND : 1 << (10*8+5),
HCI_HOST_BUFFER_SIZE_COMMAND : 1 << (10*8+6),
HCI_HOST_NUMBER_OF_COMPLETED_PACKETS_COMMAND : 1 << (10*8+7),
HCI_READ_LINK_SUPERVISION_TIMEOUT_COMMAND : 1 << (11*8+0),
HCI_WRITE_LINK_SUPERVISION_TIMEOUT_COMMAND : 1 << (11*8+1),
HCI_READ_NUMBER_OF_SUPPORTED_IAC_COMMAND : 1 << (11*8+2),
HCI_READ_CURRENT_IAC_LAP_COMMAND : 1 << (11*8+3),
HCI_WRITE_CURRENT_IAC_LAP_COMMAND : 1 << (11*8+4),
HCI_SET_AFH_HOST_CHANNEL_CLASSIFICATION_COMMAND : 1 << (12*8+1),
HCI_READ_INQUIRY_SCAN_TYPE_COMMAND : 1 << (12*8+4),
HCI_WRITE_INQUIRY_SCAN_TYPE_COMMAND : 1 << (12*8+5),
HCI_READ_INQUIRY_MODE_COMMAND : 1 << (12*8+6),
HCI_WRITE_INQUIRY_MODE_COMMAND : 1 << (12*8+7),
HCI_READ_PAGE_SCAN_TYPE_COMMAND : 1 << (13*8+0),
HCI_WRITE_PAGE_SCAN_TYPE_COMMAND : 1 << (13*8+1),
HCI_READ_AFH_CHANNEL_ASSESSMENT_MODE_COMMAND : 1 << (13*8+2),
HCI_WRITE_AFH_CHANNEL_ASSESSMENT_MODE_COMMAND : 1 << (13*8+3),
HCI_READ_LOCAL_VERSION_INFORMATION_COMMAND : 1 << (14*8+3),
HCI_READ_LOCAL_SUPPORTED_FEATURES_COMMAND : 1 << (14*8+5),
HCI_READ_LOCAL_EXTENDED_FEATURES_COMMAND : 1 << (14*8+6),
HCI_READ_BUFFER_SIZE_COMMAND : 1 << (14*8+7),
HCI_READ_BD_ADDR_COMMAND : 1 << (15*8+1),
HCI_READ_FAILED_CONTACT_COUNTER_COMMAND : 1 << (15*8+2),
HCI_RESET_FAILED_CONTACT_COUNTER_COMMAND : 1 << (15*8+3),
HCI_READ_LINK_QUALITY_COMMAND : 1 << (15*8+4),
HCI_READ_RSSI_COMMAND : 1 << (15*8+5),
HCI_READ_AFH_CHANNEL_MAP_COMMAND : 1 << (15*8+6),
HCI_READ_CLOCK_COMMAND : 1 << (15*8+7),
HCI_READ_LOOPBACK_MODE_COMMAND : 1 << (16*8+0),
HCI_WRITE_LOOPBACK_MODE_COMMAND : 1 << (16*8+1),
HCI_ENABLE_DEVICE_UNDER_TEST_MODE_COMMAND : 1 << (16*8+2),
HCI_SETUP_SYNCHRONOUS_CONNECTION_COMMAND : 1 << (16*8+3),
HCI_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND : 1 << (16*8+4),
HCI_REJECT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND : 1 << (16*8+5),
HCI_READ_EXTENDED_INQUIRY_RESPONSE_COMMAND : 1 << (17*8+0),
HCI_WRITE_EXTENDED_INQUIRY_RESPONSE_COMMAND : 1 << (17*8+1),
HCI_REFRESH_ENCRYPTION_KEY_COMMAND : 1 << (17*8+2),
HCI_SNIFF_SUBRATING_COMMAND : 1 << (17*8+4),
HCI_READ_SIMPLE_PAIRING_MODE_COMMAND : 1 << (17*8+5),
HCI_WRITE_SIMPLE_PAIRING_MODE_COMMAND : 1 << (17*8+6),
HCI_READ_LOCAL_OOB_DATA_COMMAND : 1 << (17*8+7),
HCI_READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL_COMMAND : 1 << (18*8+0),
HCI_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_COMMAND : 1 << (18*8+1),
HCI_READ_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND : 1 << (18*8+2),
HCI_WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING_COMMAND : 1 << (18*8+3),
HCI_IO_CAPABILITY_REQUEST_REPLY_COMMAND : 1 << (18*8+7),
HCI_USER_CONFIRMATION_REQUEST_REPLY_COMMAND : 1 << (19*8+0),
HCI_USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (19*8+1),
HCI_USER_PASSKEY_REQUEST_REPLY_COMMAND : 1 << (19*8+2),
HCI_USER_PASSKEY_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (19*8+3),
HCI_REMOTE_OOB_DATA_REQUEST_REPLY_COMMAND : 1 << (19*8+4),
HCI_WRITE_SIMPLE_PAIRING_DEBUG_MODE_COMMAND : 1 << (19*8+5),
HCI_ENHANCED_FLUSH_COMMAND : 1 << (19*8+6),
HCI_REMOTE_OOB_DATA_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (19*8+7),
HCI_SEND_KEYPRESS_NOTIFICATION_COMMAND : 1 << (20*8+2),
HCI_IO_CAPABILITY_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (20*8+3),
HCI_READ_ENCRYPTION_KEY_SIZE_COMMAND : 1 << (20*8+4),
HCI_SET_EVENT_MASK_PAGE_2_COMMAND : 1 << (22*8+2),
HCI_READ_FLOW_CONTROL_MODE_COMMAND : 1 << (23*8+0),
HCI_WRITE_FLOW_CONTROL_MODE_COMMAND : 1 << (23*8+1),
HCI_READ_DATA_BLOCK_SIZE_COMMAND : 1 << (23*8+2),
HCI_READ_ENHANCED_TRANSMIT_POWER_LEVEL_COMMAND : 1 << (24*8+0),
HCI_READ_LE_HOST_SUPPORT_COMMAND : 1 << (24*8+5),
HCI_WRITE_LE_HOST_SUPPORT_COMMAND : 1 << (24*8+6),
HCI_LE_SET_EVENT_MASK_COMMAND : 1 << (25*8+0),
HCI_LE_READ_BUFFER_SIZE_COMMAND : 1 << (25*8+1),
HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND : 1 << (25*8+2),
HCI_LE_SET_RANDOM_ADDRESS_COMMAND : 1 << (25*8+4),
HCI_LE_SET_ADVERTISING_PARAMETERS_COMMAND : 1 << (25*8+5),
HCI_LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER_COMMAND : 1 << (25*8+6),
HCI_LE_SET_ADVERTISING_DATA_COMMAND : 1 << (25*8+7),
HCI_LE_SET_SCAN_RESPONSE_DATA_COMMAND : 1 << (26*8+0),
HCI_LE_SET_ADVERTISING_ENABLE_COMMAND : 1 << (26*8+1),
HCI_LE_SET_SCAN_PARAMETERS_COMMAND : 1 << (26*8+2),
HCI_LE_SET_SCAN_ENABLE_COMMAND : 1 << (26*8+3),
HCI_LE_CREATE_CONNECTION_COMMAND : 1 << (26*8+4),
HCI_LE_CREATE_CONNECTION_CANCEL_COMMAND : 1 << (26*8+5),
HCI_LE_READ_FILTER_ACCEPT_LIST_SIZE_COMMAND : 1 << (26*8+6),
HCI_LE_CLEAR_FILTER_ACCEPT_LIST_COMMAND : 1 << (26*8+7),
HCI_LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST_COMMAND : 1 << (27*8+0),
HCI_LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST_COMMAND : 1 << (27*8+1),
HCI_LE_CONNECTION_UPDATE_COMMAND : 1 << (27*8+2),
HCI_LE_SET_HOST_CHANNEL_CLASSIFICATION_COMMAND : 1 << (27*8+3),
HCI_LE_READ_CHANNEL_MAP_COMMAND : 1 << (27*8+4),
HCI_LE_READ_REMOTE_FEATURES_COMMAND : 1 << (27*8+5),
HCI_LE_ENCRYPT_COMMAND : 1 << (27*8+6),
HCI_LE_RAND_COMMAND : 1 << (27*8+7),
HCI_LE_ENABLE_ENCRYPTION_COMMAND : 1 << (28*8+0),
HCI_LE_LONG_TERM_KEY_REQUEST_REPLY_COMMAND : 1 << (28*8+1),
HCI_LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (28*8+2),
HCI_LE_READ_SUPPORTED_STATES_COMMAND : 1 << (28*8+3),
HCI_LE_RECEIVER_TEST_COMMAND : 1 << (28*8+4),
HCI_LE_TRANSMITTER_TEST_COMMAND : 1 << (28*8+5),
HCI_LE_TEST_END_COMMAND : 1 << (28*8+6),
HCI_ENHANCED_SETUP_SYNCHRONOUS_CONNECTION_COMMAND : 1 << (29*8+3),
HCI_ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND : 1 << (29*8+4),
HCI_READ_LOCAL_SUPPORTED_CODECS_COMMAND : 1 << (29*8+5),
HCI_SET_MWS_CHANNEL_PARAMETERS_COMMAND : 1 << (29*8+6),
HCI_SET_EXTERNAL_FRAME_CONFIGURATION_COMMAND : 1 << (29*8+7),
HCI_SET_MWS_SIGNALING_COMMAND : 1 << (30*8+0),
HCI_SET_MWS_TRANSPORT_LAYER_COMMAND : 1 << (30*8+1),
HCI_SET_MWS_SCAN_FREQUENCY_TABLE_COMMAND : 1 << (30*8+2),
HCI_GET_MWS_TRANSPORT_LAYER_CONFIGURATION_COMMAND : 1 << (30*8+3),
HCI_SET_MWS_PATTERN_CONFIGURATION_COMMAND : 1 << (30*8+4),
HCI_SET_TRIGGERED_CLOCK_CAPTURE_COMMAND : 1 << (30*8+5),
HCI_TRUNCATED_PAGE_COMMAND : 1 << (30*8+6),
HCI_TRUNCATED_PAGE_CANCEL_COMMAND : 1 << (30*8+7),
HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_COMMAND : 1 << (31*8+0),
HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_RECEIVE_COMMAND : 1 << (31*8+1),
HCI_START_SYNCHRONIZATION_TRAIN_COMMAND : 1 << (31*8+2),
HCI_RECEIVE_SYNCHRONIZATION_TRAIN_COMMAND : 1 << (31*8+3),
HCI_SET_RESERVED_LT_ADDR_COMMAND : 1 << (31*8+4),
HCI_DELETE_RESERVED_LT_ADDR_COMMAND : 1 << (31*8+5),
HCI_SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_DATA_COMMAND : 1 << (31*8+6),
HCI_READ_SYNCHRONIZATION_TRAIN_PARAMETERS_COMMAND : 1 << (31*8+7),
HCI_WRITE_SYNCHRONIZATION_TRAIN_PARAMETERS_COMMAND : 1 << (32*8+0),
HCI_REMOTE_OOB_EXTENDED_DATA_REQUEST_REPLY_COMMAND : 1 << (32*8+1),
HCI_READ_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND : 1 << (32*8+2),
HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT_COMMAND : 1 << (32*8+3),
HCI_READ_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND : 1 << (32*8+4),
HCI_WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT_COMMAND : 1 << (32*8+5),
HCI_READ_LOCAL_OOB_EXTENDED_DATA_COMMAND : 1 << (32*8+6),
HCI_WRITE_SECURE_CONNECTIONS_TEST_MODE_COMMAND : 1 << (32*8+7),
HCI_READ_EXTENDED_PAGE_TIMEOUT_COMMAND : 1 << (33*8+0),
HCI_WRITE_EXTENDED_PAGE_TIMEOUT_COMMAND : 1 << (33*8+1),
HCI_READ_EXTENDED_INQUIRY_LENGTH_COMMAND : 1 << (33*8+2),
HCI_WRITE_EXTENDED_INQUIRY_LENGTH_COMMAND : 1 << (33*8+3),
HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY_COMMAND : 1 << (33*8+4),
HCI_LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (33*8+5),
HCI_LE_SET_DATA_LENGTH_COMMAND : 1 << (33*8+6),
HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND : 1 << (33*8+7),
HCI_LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND : 1 << (34*8+0),
HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMMAND : 1 << (34*8+1),
HCI_LE_GENERATE_DHKEY_COMMAND : 1 << (34*8+2),
HCI_LE_ADD_DEVICE_TO_RESOLVING_LIST_COMMAND : 1 << (34*8+3),
HCI_LE_REMOVE_DEVICE_FROM_RESOLVING_LIST_COMMAND : 1 << (34*8+4),
HCI_LE_CLEAR_RESOLVING_LIST_COMMAND : 1 << (34*8+5),
HCI_LE_READ_RESOLVING_LIST_SIZE_COMMAND : 1 << (34*8+6),
HCI_LE_READ_PEER_RESOLVABLE_ADDRESS_COMMAND : 1 << (34*8+7),
HCI_LE_READ_LOCAL_RESOLVABLE_ADDRESS_COMMAND : 1 << (35*8+0),
HCI_LE_SET_ADDRESS_RESOLUTION_ENABLE_COMMAND : 1 << (35*8+1),
HCI_LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT_COMMAND : 1 << (35*8+2),
HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND : 1 << (35*8+3),
HCI_LE_READ_PHY_COMMAND : 1 << (35*8+4),
HCI_LE_SET_DEFAULT_PHY_COMMAND : 1 << (35*8+5),
HCI_LE_SET_PHY_COMMAND : 1 << (35*8+6),
HCI_LE_RECEIVER_TEST_V2_COMMAND : 1 << (35*8+7),
HCI_LE_TRANSMITTER_TEST_V2_COMMAND : 1 << (36*8+0),
HCI_LE_SET_ADVERTISING_SET_RANDOM_ADDRESS_COMMAND : 1 << (36*8+1),
HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_COMMAND : 1 << (36*8+2),
HCI_LE_SET_EXTENDED_ADVERTISING_DATA_COMMAND : 1 << (36*8+3),
HCI_LE_SET_EXTENDED_SCAN_RESPONSE_DATA_COMMAND : 1 << (36*8+4),
HCI_LE_SET_EXTENDED_ADVERTISING_ENABLE_COMMAND : 1 << (36*8+5),
HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND : 1 << (36*8+6),
HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND : 1 << (36*8+7),
HCI_LE_REMOVE_ADVERTISING_SET_COMMAND : 1 << (37*8+0),
HCI_LE_CLEAR_ADVERTISING_SETS_COMMAND : 1 << (37*8+1),
HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_COMMAND : 1 << (37*8+2),
HCI_LE_SET_PERIODIC_ADVERTISING_DATA_COMMAND : 1 << (37*8+3),
HCI_LE_SET_PERIODIC_ADVERTISING_ENABLE_COMMAND : 1 << (37*8+4),
HCI_LE_SET_EXTENDED_SCAN_PARAMETERS_COMMAND : 1 << (37*8+5),
HCI_LE_SET_EXTENDED_SCAN_ENABLE_COMMAND : 1 << (37*8+6),
HCI_LE_EXTENDED_CREATE_CONNECTION_COMMAND : 1 << (37*8+7),
HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_COMMAND : 1 << (38*8+0),
HCI_LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL_COMMAND : 1 << (38*8+1),
HCI_LE_PERIODIC_ADVERTISING_TERMINATE_SYNC_COMMAND : 1 << (38*8+2),
HCI_LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST_COMMAND : 1 << (38*8+3),
HCI_LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST_COMMAND : 1 << (38*8+4),
HCI_LE_CLEAR_PERIODIC_ADVERTISER_LIST_COMMAND : 1 << (38*8+5),
HCI_LE_READ_PERIODIC_ADVERTISER_LIST_SIZE_COMMAND : 1 << (38*8+6),
HCI_LE_READ_TRANSMIT_POWER_COMMAND : 1 << (38*8+7),
HCI_LE_READ_RF_PATH_COMPENSATION_COMMAND : 1 << (39*8+0),
HCI_LE_WRITE_RF_PATH_COMPENSATION_COMMAND : 1 << (39*8+1),
HCI_LE_SET_PRIVACY_MODE_COMMAND : 1 << (39*8+2),
HCI_LE_RECEIVER_TEST_V3_COMMAND : 1 << (39*8+3),
HCI_LE_TRANSMITTER_TEST_V3_COMMAND : 1 << (39*8+4),
HCI_LE_SET_CONNECTIONLESS_CTE_TRANSMIT_PARAMETERS_COMMAND : 1 << (39*8+5),
HCI_LE_SET_CONNECTIONLESS_CTE_TRANSMIT_ENABLE_COMMAND : 1 << (39*8+6),
HCI_LE_SET_CONNECTIONLESS_IQ_SAMPLING_ENABLE_COMMAND : 1 << (39*8+7),
HCI_LE_SET_CONNECTION_CTE_RECEIVE_PARAMETERS_COMMAND : 1 << (40*8+0),
HCI_LE_SET_CONNECTION_CTE_TRANSMIT_PARAMETERS_COMMAND : 1 << (40*8+1),
HCI_LE_CONNECTION_CTE_REQUEST_ENABLE_COMMAND : 1 << (40*8+2),
HCI_LE_CONNECTION_CTE_RESPONSE_ENABLE_COMMAND : 1 << (40*8+3),
HCI_LE_READ_ANTENNA_INFORMATION_COMMAND : 1 << (40*8+4),
HCI_LE_SET_PERIODIC_ADVERTISING_RECEIVE_ENABLE_COMMAND : 1 << (40*8+5),
HCI_LE_PERIODIC_ADVERTISING_SYNC_TRANSFER_COMMAND : 1 << (40*8+6),
HCI_LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER_COMMAND : 1 << (40*8+7),
HCI_LE_SET_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS_COMMAND : 1 << (41*8+0),
HCI_LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS_COMMAND : 1 << (41*8+1),
HCI_LE_GENERATE_DHKEY_V2_COMMAND : 1 << (41*8+2),
HCI_READ_LOCAL_SIMPLE_PAIRING_OPTIONS_COMMAND : 1 << (41*8+3),
HCI_LE_MODIFY_SLEEP_CLOCK_ACCURACY_COMMAND : 1 << (41*8+4),
HCI_LE_READ_BUFFER_SIZE_V2_COMMAND : 1 << (41*8+5),
HCI_LE_READ_ISO_TX_SYNC_COMMAND : 1 << (41*8+6),
HCI_LE_SET_CIG_PARAMETERS_COMMAND : 1 << (41*8+7),
HCI_LE_SET_CIG_PARAMETERS_TEST_COMMAND : 1 << (42*8+0),
HCI_LE_CREATE_CIS_COMMAND : 1 << (42*8+1),
HCI_LE_REMOVE_CIG_COMMAND : 1 << (42*8+2),
HCI_LE_ACCEPT_CIS_REQUEST_COMMAND : 1 << (42*8+3),
HCI_LE_REJECT_CIS_REQUEST_COMMAND : 1 << (42*8+4),
HCI_LE_CREATE_BIG_COMMAND : 1 << (42*8+5),
HCI_LE_CREATE_BIG_TEST_COMMAND : 1 << (42*8+6),
HCI_LE_TERMINATE_BIG_COMMAND : 1 << (42*8+7),
HCI_LE_BIG_CREATE_SYNC_COMMAND : 1 << (43*8+0),
HCI_LE_BIG_TERMINATE_SYNC_COMMAND : 1 << (43*8+1),
HCI_LE_REQUEST_PEER_SCA_COMMAND : 1 << (43*8+2),
HCI_LE_SETUP_ISO_DATA_PATH_COMMAND : 1 << (43*8+3),
HCI_LE_REMOVE_ISO_DATA_PATH_COMMAND : 1 << (43*8+4),
HCI_LE_ISO_TRANSMIT_TEST_COMMAND : 1 << (43*8+5),
HCI_LE_ISO_RECEIVE_TEST_COMMAND : 1 << (43*8+6),
HCI_LE_ISO_READ_TEST_COUNTERS_COMMAND : 1 << (43*8+7),
HCI_LE_ISO_TEST_END_COMMAND : 1 << (44*8+0),
HCI_LE_SET_HOST_FEATURE_COMMAND : 1 << (44*8+1),
HCI_LE_READ_ISO_LINK_QUALITY_COMMAND : 1 << (44*8+2),
HCI_LE_ENHANCED_READ_TRANSMIT_POWER_LEVEL_COMMAND : 1 << (44*8+3),
HCI_LE_READ_REMOTE_TRANSMIT_POWER_LEVEL_COMMAND : 1 << (44*8+4),
HCI_LE_SET_PATH_LOSS_REPORTING_PARAMETERS_COMMAND : 1 << (44*8+5),
HCI_LE_SET_PATH_LOSS_REPORTING_ENABLE_COMMAND : 1 << (44*8+6),
HCI_LE_SET_TRANSMIT_POWER_REPORTING_ENABLE_COMMAND : 1 << (44*8+7),
HCI_LE_TRANSMITTER_TEST_V4_COMMAND : 1 << (45*8+0),
HCI_SET_ECOSYSTEM_BASE_INTERVAL_COMMAND : 1 << (45*8+1),
HCI_READ_LOCAL_SUPPORTED_CODECS_V2_COMMAND : 1 << (45*8+2),
HCI_READ_LOCAL_SUPPORTED_CODEC_CAPABILITIES_COMMAND : 1 << (45*8+3),
HCI_READ_LOCAL_SUPPORTED_CONTROLLER_DELAY_COMMAND : 1 << (45*8+4),
HCI_CONFIGURE_DATA_PATH_COMMAND : 1 << (45*8+5),
HCI_LE_SET_DATA_RELATED_ADDRESS_CHANGES_COMMAND : 1 << (45*8+6),
HCI_SET_MIN_ENCRYPTION_KEY_SIZE_COMMAND : 1 << (45*8+7),
HCI_LE_SET_DEFAULT_SUBRATE_COMMAND : 1 << (46*8+0),
HCI_LE_SUBRATE_REQUEST_COMMAND : 1 << (46*8+1),
HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_V2_COMMAND : 1 << (46*8+2),
HCI_LE_SET_PERIODIC_ADVERTISING_SUBEVENT_DATA_COMMAND : 1 << (46*8+5),
HCI_LE_SET_PERIODIC_ADVERTISING_RESPONSE_DATA_COMMAND : 1 << (46*8+6),
HCI_LE_SET_PERIODIC_SYNC_SUBEVENT_COMMAND : 1 << (46*8+7),
HCI_LE_EXTENDED_CREATE_CONNECTION_V2_COMMAND : 1 << (47*8+0),
HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_V2_COMMAND : 1 << (47*8+1),
}
# LE Supported Features
# See Bluetooth spec @ Vol 6, Part B, 4.6 FEATURE SUPPORT

View File

@@ -18,6 +18,7 @@
from __future__ import annotations
from collections.abc import Callable, MutableMapping
import datetime
from typing import cast, Any, Optional
import logging
@@ -66,12 +67,13 @@ PSM_NAMES = {
rfcomm.RFCOMM_PSM: 'RFCOMM',
sdp.SDP_PSM: 'SDP',
avdtp.AVDTP_PSM: 'AVDTP',
avctp.AVCTP_PSM: 'AVCTP'
avctp.AVCTP_PSM: 'AVCTP',
# TODO: add more PSM values
}
AVCTP_PID_NAMES = {avrcp.AVRCP_PID: 'AVRCP'}
# -----------------------------------------------------------------------------
class PacketTracer:
class AclStream:
@@ -207,6 +209,7 @@ class PacketTracer:
self.label = label
self.emit_message = emit_message
self.acl_streams = {} # ACL streams, by connection handle
self.packet_timestamp: Optional[datetime.datetime] = None
def start_acl_stream(self, connection_handle: int) -> PacketTracer.AclStream:
logger.info(
@@ -234,7 +237,10 @@ class PacketTracer:
# Let the other forwarder know so it can cleanup its stream as well
self.peer.end_acl_stream(connection_handle)
def on_packet(self, packet: HCI_Packet) -> None:
def on_packet(
self, timestamp: Optional[datetime.datetime], packet: HCI_Packet
) -> None:
self.packet_timestamp = timestamp
self.emit(packet)
if packet.hci_packet_type == HCI_ACL_DATA_PACKET:
@@ -254,13 +260,22 @@ class PacketTracer:
)
def emit(self, message: Any) -> None:
self.emit_message(f'[{self.label}] {message}')
if self.packet_timestamp:
prefix = f"[{self.packet_timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')}]"
else:
prefix = ""
self.emit_message(f'{prefix}[{self.label}] {message}')
def trace(self, packet: HCI_Packet, direction: int = 0) -> None:
def trace(
self,
packet: HCI_Packet,
direction: int = 0,
timestamp: Optional[datetime.datetime] = None,
) -> None:
if direction == 0:
self.host_to_controller_analyzer.on_packet(packet)
self.host_to_controller_analyzer.on_packet(timestamp, packet)
else:
self.controller_to_host_analyzer.on_packet(packet)
self.controller_to_host_analyzer.on_packet(timestamp, packet)
def __init__(
self,

View File

@@ -22,7 +22,17 @@ import dataclasses
import logging
import struct
from typing import Any, Awaitable, Callable, Deque, Dict, Optional, cast, TYPE_CHECKING
from typing import (
Any,
Awaitable,
Callable,
Deque,
Dict,
Optional,
Set,
cast,
TYPE_CHECKING,
)
from bumble.colors import color
from bumble.l2cap import L2CAP_PDU
@@ -165,7 +175,7 @@ class Host(AbortableEventEmitter):
self.number_of_supported_advertising_sets = 0
self.maximum_advertising_data_length = 31
self.local_version = None
self.local_supported_commands = bytes(64)
self.local_supported_commands = 0
self.local_le_features = 0
self.local_lmp_features = hci.LmpFeatureMask(0) # Classic LMP features
self.suggested_max_tx_octets = 251 # Max allowed
@@ -232,7 +242,9 @@ class Host(AbortableEventEmitter):
response = await self.send_command(
hci.HCI_Read_Local_Supported_Commands_Command(), check_result=True
)
self.local_supported_commands = response.return_parameters.supported_commands
self.local_supported_commands = int.from_bytes(
response.return_parameters.supported_commands, 'little'
)
if self.supports_command(hci.HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND):
response = await self.send_command(
@@ -486,7 +498,7 @@ class Host(AbortableEventEmitter):
def controller(self, controller) -> None:
self.set_packet_sink(controller)
if controller:
controller.set_packet_sink(self)
self.set_packet_source(controller)
def set_packet_sink(self, sink: Optional[TransportSink]) -> None:
self.hci_sink = sink
@@ -583,31 +595,19 @@ class Host(AbortableEventEmitter):
offset += data_total_length
bytes_remaining -= data_total_length
def supports_command(self, command):
# Find the support flag position for this command
for octet, flags in enumerate(hci.HCI_SUPPORTED_COMMANDS_FLAGS):
for flag_position, value in enumerate(flags):
if value == command:
# Check if the flag is set
if octet < len(self.local_supported_commands) and flag_position < 8:
return (
self.local_supported_commands[octet] & (1 << flag_position)
) != 0
return False
def supports_command(self, op_code: int) -> bool:
return (
self.local_supported_commands
& hci.HCI_SUPPORTED_COMMANDS_MASKS.get(op_code, 0)
) != 0
@property
def supported_commands(self):
commands = []
for octet, flags in enumerate(self.local_supported_commands):
if octet < len(hci.HCI_SUPPORTED_COMMANDS_FLAGS):
for flag in range(8):
if flags & (1 << flag) != 0:
command = hci.HCI_SUPPORTED_COMMANDS_FLAGS[octet][flag]
if command is not None:
commands.append(command)
return commands
def supported_commands(self) -> Set[int]:
return set(
op_code
for op_code, mask in hci.HCI_SUPPORTED_COMMANDS_MASKS.items()
if self.local_supported_commands & mask
)
def supports_le_features(self, feature: hci.LeFeatureMask) -> bool:
return (self.local_le_features & feature) == feature

View File

@@ -34,8 +34,11 @@ from bumble.device import (
DEVICE_DEFAULT_SCAN_INTERVAL,
DEVICE_DEFAULT_SCAN_WINDOW,
Advertisement,
AdvertisingParameters,
AdvertisingEventProperties,
AdvertisingType,
Device,
Phy,
)
from bumble.gatt import Service
from bumble.hci import (
@@ -47,9 +50,12 @@ from bumble.hci import (
from google.protobuf import any_pb2 # pytype: disable=pyi-error
from google.protobuf import empty_pb2 # pytype: disable=pyi-error
from pandora.host_grpc_aio import HostServicer
from pandora import host_pb2
from pandora.host_pb2 import (
NOT_CONNECTABLE,
NOT_DISCOVERABLE,
DISCOVERABLE_LIMITED,
DISCOVERABLE_GENERAL,
PRIMARY_1M,
PRIMARY_CODED,
SECONDARY_1M,
@@ -65,6 +71,7 @@ from pandora.host_pb2 import (
ConnectResponse,
DataTypes,
DisconnectRequest,
DiscoverabilityMode,
InquiryResponse,
PrimaryPhy,
ReadLocalAddressResponse,
@@ -94,6 +101,25 @@ SECONDARY_PHY_MAP: Dict[int, SecondaryPhy] = {
3: SECONDARY_CODED,
}
PRIMARY_PHY_TO_BUMBLE_PHY_MAP: Dict[PrimaryPhy, Phy] = {
PRIMARY_1M: Phy.LE_1M,
PRIMARY_CODED: Phy.LE_CODED,
}
SECONDARY_PHY_TO_BUMBLE_PHY_MAP: Dict[SecondaryPhy, Phy] = {
SECONDARY_NONE: Phy.LE_1M,
SECONDARY_1M: Phy.LE_1M,
SECONDARY_2M: Phy.LE_2M,
SECONDARY_CODED: Phy.LE_CODED,
}
OWN_ADDRESS_MAP: Dict[host_pb2.OwnAddressType, bumble.hci.OwnAddressType] = {
host_pb2.PUBLIC: bumble.hci.OwnAddressType.PUBLIC,
host_pb2.RANDOM: bumble.hci.OwnAddressType.RANDOM,
host_pb2.RESOLVABLE_OR_PUBLIC: bumble.hci.OwnAddressType.RESOLVABLE_OR_PUBLIC,
host_pb2.RESOLVABLE_OR_RANDOM: bumble.hci.OwnAddressType.RESOLVABLE_OR_RANDOM,
}
class HostService(HostServicer):
waited_connections: Set[int]
@@ -281,10 +307,113 @@ class HostService(HostServicer):
async def Advertise(
self, request: AdvertiseRequest, context: grpc.ServicerContext
) -> AsyncGenerator[AdvertiseResponse, None]:
if not request.legacy:
raise NotImplementedError(
"TODO: add support for extended advertising in Bumble"
try:
if request.legacy:
async for rsp in self.legacy_advertise(request, context):
yield rsp
else:
async for rsp in self.extended_advertise(request, context):
yield rsp
finally:
pass
async def extended_advertise(
self, request: AdvertiseRequest, context: grpc.ServicerContext
) -> AsyncGenerator[AdvertiseResponse, None]:
advertising_data = bytes(self.unpack_data_types(request.data))
scan_response_data = bytes(self.unpack_data_types(request.scan_response_data))
scannable = len(scan_response_data) != 0
advertising_event_properties = AdvertisingEventProperties(
is_connectable=request.connectable,
is_scannable=scannable,
is_directed=request.target is not None,
is_high_duty_cycle_directed_connectable=False,
is_legacy=False,
is_anonymous=False,
include_tx_power=False,
)
peer_address = Address.ANY
if request.target:
# Need to reverse bytes order since Bumble Address is using MSB.
target_bytes = bytes(reversed(request.target))
if request.target_variant() == "public":
peer_address = Address(target_bytes, Address.PUBLIC_DEVICE_ADDRESS)
else:
peer_address = Address(target_bytes, Address.RANDOM_DEVICE_ADDRESS)
advertising_parameters = AdvertisingParameters(
advertising_event_properties=advertising_event_properties,
own_address_type=OWN_ADDRESS_MAP[request.own_address_type],
peer_address=peer_address,
primary_advertising_phy=PRIMARY_PHY_TO_BUMBLE_PHY_MAP[request.primary_phy],
secondary_advertising_phy=SECONDARY_PHY_TO_BUMBLE_PHY_MAP[
request.secondary_phy
],
)
if advertising_interval := request.interval:
advertising_parameters.primary_advertising_interval_min = int(
advertising_interval
)
advertising_parameters.primary_advertising_interval_max = int(
advertising_interval
)
if interval_range := request.interval_range:
advertising_parameters.primary_advertising_interval_max += int(
interval_range
)
advertising_set = await self.device.create_advertising_set(
advertising_parameters=advertising_parameters,
advertising_data=advertising_data,
scan_response_data=scan_response_data,
)
pending_connection: asyncio.Future[
bumble.device.Connection
] = asyncio.get_running_loop().create_future()
if request.connectable:
def on_connection(connection: bumble.device.Connection) -> None:
if (
connection.transport == BT_LE_TRANSPORT
and connection.role == BT_PERIPHERAL_ROLE
):
pending_connection.set_result(connection)
self.device.on('connection', on_connection)
try:
# Advertise until RPC is canceled
while True:
if not advertising_set.enabled:
self.log.debug('Advertise (extended)')
await advertising_set.start()
if not request.connectable:
await asyncio.sleep(1)
continue
connection = await pending_connection
pending_connection = asyncio.get_running_loop().create_future()
cookie = any_pb2.Any(value=connection.handle.to_bytes(4, 'big'))
yield AdvertiseResponse(connection=Connection(cookie=cookie))
await asyncio.sleep(1)
finally:
try:
self.log.debug('Stop Advertise (extended)')
await advertising_set.stop()
await advertising_set.remove()
except Exception:
pass
async def legacy_advertise(
self, request: AdvertiseRequest, context: grpc.ServicerContext
) -> AsyncGenerator[AdvertiseResponse, None]:
if advertising_interval := request.interval:
self.device.config.advertising_interval_min = int(advertising_interval)
self.device.config.advertising_interval_max = int(advertising_interval)
@@ -357,14 +486,10 @@ class HostService(HostServicer):
target_bytes = bytes(reversed(request.target))
if request.target_variant() == "public":
target = Address(target_bytes, Address.PUBLIC_DEVICE_ADDRESS)
advertising_type = (
AdvertisingType.DIRECTED_CONNECTABLE_HIGH_DUTY
) # FIXME: HIGH_DUTY ?
advertising_type = AdvertisingType.DIRECTED_CONNECTABLE_LOW_DUTY
else:
target = Address(target_bytes, Address.RANDOM_DEVICE_ADDRESS)
advertising_type = (
AdvertisingType.DIRECTED_CONNECTABLE_HIGH_DUTY
) # FIXME: HIGH_DUTY ?
advertising_type = AdvertisingType.DIRECTED_CONNECTABLE_LOW_DUTY
if request.connectable:
@@ -422,11 +547,16 @@ class HostService(HostServicer):
self, request: ScanRequest, context: grpc.ServicerContext
) -> AsyncGenerator[ScanningResponse, None]:
# TODO: modify `start_scanning` to accept floats instead of int for ms values
if request.phys:
raise NotImplementedError("TODO: add support for `request.phys`")
self.log.debug('Scan')
scanning_phys = []
if PRIMARY_1M in request.phys:
scanning_phys.append(int(Phy.LE_1M))
if PRIMARY_CODED in request.phys:
scanning_phys.append(int(Phy.LE_CODED))
if not scanning_phys:
scanning_phys = [int(Phy.LE_1M), int(Phy.LE_CODED)]
scan_queue: asyncio.Queue[Advertisement] = asyncio.Queue()
handler = self.device.on('advertisement', scan_queue.put_nowait)
await self.device.start_scanning(
@@ -439,6 +569,7 @@ class HostService(HostServicer):
scan_window=int(request.window)
if request.window
else DEVICE_DEFAULT_SCAN_WINDOW,
scanning_phys=scanning_phys,
)
try:
@@ -735,6 +866,16 @@ class HostService(HostServicer):
)
)
flag_map = {
NOT_DISCOVERABLE: 0x00,
DISCOVERABLE_LIMITED: AdvertisingData.LE_LIMITED_DISCOVERABLE_MODE_FLAG,
DISCOVERABLE_GENERAL: AdvertisingData.LE_GENERAL_DISCOVERABLE_MODE_FLAG,
}
if dt.le_discoverability_mode:
flags = flag_map[dt.le_discoverability_mode]
ad_structures.append((AdvertisingData.FLAGS, flags.to_bytes(1, 'big')))
return AdvertisingData(ad_structures)
def pack_data_types(self, ad: AdvertisingData) -> DataTypes:

View File

@@ -168,11 +168,13 @@ class PacketReader:
def __init__(self, source: io.BufferedReader) -> None:
self.source = source
self.at_end = False
def next_packet(self) -> Optional[bytes]:
# Get the packet type
packet_type = self.source.read(1)
if len(packet_type) != 1:
self.at_end = True
return None
# Get the packet info based on its type

View File

@@ -113,9 +113,10 @@ async def open_pyusb_transport(spec: str) -> Transport:
self.loop.call_soon_threadsafe(self.stop_event.set)
class UsbPacketSource(asyncio.Protocol, ParserSource):
def __init__(self, device, sco_enabled):
def __init__(self, device, metadata, sco_enabled):
super().__init__()
self.device = device
self.metadata = metadata
self.loop = asyncio.get_running_loop()
self.queue = asyncio.Queue()
self.dequeue_task = None
@@ -216,6 +217,15 @@ async def open_pyusb_transport(spec: str) -> Transport:
if ':' in spec:
vendor_id, product_id = spec.split(':')
device = usb_find(idVendor=int(vendor_id, 16), idProduct=int(product_id, 16))
elif '-' in spec:
def device_path(device):
if device.port_numbers:
return f'{device.bus}-{".".join(map(str, device.port_numbers))}'
else:
return str(device.bus)
device = usb_find(custom_match=lambda device: device_path(device) == spec)
else:
device_index = int(spec)
devices = list(
@@ -235,6 +245,9 @@ async def open_pyusb_transport(spec: str) -> Transport:
raise ValueError('device not found')
logger.debug(f'USB Device: {device}')
# Collect the metadata
device_metadata = {'vendor_id': device.idVendor, 'product_id': device.idProduct}
# Detach the kernel driver if needed
if device.is_kernel_driver_active(0):
logger.debug("detaching kernel driver")
@@ -289,7 +302,7 @@ async def open_pyusb_transport(spec: str) -> Transport:
# except usb.USBError:
# logger.warning('failed to set alternate setting')
packet_source = UsbPacketSource(device, sco_enabled)
packet_source = UsbPacketSource(device, device_metadata, sco_enabled)
packet_sink = UsbPacketSink(device)
packet_source.start()
packet_sink.start()

View File

@@ -396,6 +396,16 @@ async def open_usb_transport(spec: str) -> Transport:
break
device_index -= 1
device.close()
elif '-' in spec:
def device_path(device):
return f'{device.getBusNumber()}-{".".join(map(str, device.getPortNumberList()))}'
for device in context.getDeviceIterator(skip_on_error=True):
if device_path(device) == spec:
found = device
break
device.close()
else:
# Look for a compatible device by index
def device_is_bluetooth_hci(device):

View File

@@ -12,12 +12,25 @@ a host that send custom HCI commands that the controller may not understand.
```
python hci_bridge.py <host-transport-spec> <controller-transport-spec> [command-short-circuit-list]
```
The command-short-circuit-list field is specified by a series of comma separated Opcode Group
Field (OGF) : OpCode Command Field (OCF) pairs. The OGF/OCF values are specified in the Blutooth
core specification.
For the commands that are listed in the short-circuit-list, the HCI bridge will always generate
a Command Complete Event for the specified op code. The return parameter will be HCI_SUCCESS.
This feature can only be used for commands that return Command Complete. Other events will not be
generated by the HCI bridge tool.
!!! example "UDP to Serial"
```
python hci_bridge.py udp:0.0.0.0:9000,127.0.0.1:9001 serial:/dev/tty.usbmodem0006839912171,1000000 0x3f:0x0070,0x3f:0x0074,0x3f:0x0077,0x3f:0x0078
```
In this example, the short circuit list is specified to respond to the Vendor-specific Opcode Group
Field (0x3f) commands 0x70, 0x74, 0x77, 0x78 with Command Complete. The short circuit list can be
used where the Host uses some HCI commands that are not supported/implemented by the Controller.
!!! example "PTY to Link Relay"
```
python hci_bridge.py serial:emulated_uart_pty,1000000 link-relay:ws://127.0.0.1:10723/test
@@ -28,3 +41,4 @@ a host that send custom HCI commands that the controller may not understand.
(through which the communication with other virtual controllers will be mediated).
NOTE: this assumes you're running a Link Relay on port `10723`.

View File

@@ -10,7 +10,7 @@ used with particular HCI controller.
When the transport for an HCI controller is instantiated from a transport name,
a driver may also be forced by specifying ``driver=<driver-name>`` in the optional
metadata portion of the transport name. For example,
``usb:[driver=-rtk]0`` indicates that the ``rtk`` driver should be used with the
``usb:[driver=rtk]0`` indicates that the ``rtk`` driver should be used with the
first USB device, even if a normal probe would not have selected it based on the
USB vendor ID and product ID.

View File

@@ -10,6 +10,7 @@ The moniker for a USB transport is either:
* `usb:<vendor>:<product>`
* `usb:<vendor>:<product>/<serial-number>`
* `usb:<vendor>:<product>#<index>`
* `usb:<bus>-<port_numbers>`
with `<index>` as a 0-based index (0 being the first one) to select amongst all the matching devices when there are more than one.
In the `usb:<index>` form, matching devices are the ones supporting Bluetooth HCI, as declared by their Class, Subclass and Protocol.
@@ -17,6 +18,8 @@ In the `usb:<vendor>:<product>#<index>` form, matching devices are the ones with
`<vendor>` and `<product>` are a vendor ID and product ID in hexadecimal.
with `<port_numbers>` as a list of all port numbers from root separated with dots `.`
In addition, if the moniker ends with the symbol "!", the device will be used in "forced" mode:
the first USB interface of the device will be used, regardless of the interface class/subclass.
This may be useful for some devices that use a custom class/subclass but may nonetheless work as-is.
@@ -37,6 +40,9 @@ This may be useful for some devices that use a custom class/subclass but may non
`usb:0B05:17CB!`
The BT USB dongle vendor=0B05 and product=17CB, in "forced" mode.
`usb:3-3.4.1`
The BT USB dongle on bus 3 on port path 3, 4, 1.
## Alternative
The library includes two different implementations of the USB transport, implemented using different python bindings for `libusb`.

View File

@@ -1,7 +1,10 @@
# Next
# 0.2.0
- Code-gen company ID table
- Unstable support for extended advertisements
- CLI tools for downloading Realtek firmware
- PDL-generated types for HCI commands
# 0.1.0
- Initial release
- Initial release

2
rust/Cargo.lock generated
View File

@@ -182,7 +182,7 @@ dependencies = [
[[package]]
name = "bumble"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"anyhow",
"bytes",

View File

@@ -1,7 +1,7 @@
[package]
name = "bumble"
description = "Rust API for the Bumble Bluetooth stack"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "Apache-2.0"
homepage = "https://google.github.io/bumble/index.html"
@@ -10,7 +10,7 @@ documentation = "https://docs.rs/crate/bumble"
authors = ["Marshall Pierce <marshallpierce@google.com>"]
keywords = ["bluetooth", "ble"]
categories = ["api-bindings", "network-programming"]
rust-version = "1.70.0"
rust-version = "1.76.0"
# https://github.com/frewsxcv/cargo-all-features#options
[package.metadata.cargo-all-features]

View File

@@ -37,6 +37,11 @@ PYTHONPATH=..:[virtualenv site-packages] \
cargo run --features bumble-tools --bin bumble -- --help
```
Notable subcommands:
- `firmware realtek download`: download Realtek firmware for various chipsets so that it can be automatically loaded when needed
- `usb probe`: show USB devices, highlighting the ones usable for Bluetooth
# Development
Run the tests:
@@ -63,4 +68,4 @@ To regenerate the assigned number tables based on the Python codebase:
```
PYTHONPATH=.. cargo run --bin gen-assigned-numbers --features dev-tools
```
```

View File

@@ -35,7 +35,7 @@ impl Controller {
/// module specifies the defaults. Must be called from a thread with a Python event loop, which
/// should be true on `tokio::main` and `async_std::main`.
///
/// For more info, see https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_asyncio/#event-loop-references-and-contextvars.
/// For more info, see <https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_asyncio/#event-loop-references-and-contextvars>.
pub async fn new(
name: &str,
host_source: Option<TransportSource>,

View File

@@ -149,7 +149,7 @@ impl ToPyObject for Address {
/// An error meaning that the u64 value did not represent a valid BT address.
#[derive(Debug)]
pub struct InvalidAddress(u64);
pub struct InvalidAddress(#[allow(unused)] u64);
impl TryInto<packets::Address> for Address {
type Error = ConversionError<InvalidAddress>;

View File

@@ -71,7 +71,7 @@ impl LeConnectionOrientedChannel {
/// Must be called from a thread with a Python event loop, which should be true on
/// `tokio::main` and `async_std::main`.
///
/// For more info, see https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_asyncio/#event-loop-references-and-contextvars.
/// For more info, see <https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_asyncio/#event-loop-references-and-contextvars>.
pub async fn disconnect(&mut self) -> PyResult<()> {
Python::with_gil(|py| {
self.0

View File

@@ -98,8 +98,8 @@ development =
types-invoke >= 1.7.3
types-protobuf >= 4.21.0
avatar =
pandora-avatar == 0.0.5
rootcanal == 1.7.0 ; python_version>='3.10'
pandora-avatar == 0.0.8
rootcanal == 1.9.0 ; python_version>='3.10'
documentation =
mkdocs >= 1.4.0
mkdocs-material >= 8.5.6