forked from auracaster/bumble_mirror
Compare commits
23 Commits
v0.0.214
...
gbg/data-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bf0bc03af | ||
|
|
91ba2f61f1 | ||
|
|
116dc9b319 | ||
|
|
6f73b736d7 | ||
|
|
6091e6365d | ||
|
|
8bda7d2212 | ||
|
|
7aba36302a | ||
|
|
ceefe8b2a5 | ||
|
|
cd37027795 | ||
|
|
bb2aa8229d | ||
|
|
4aed53c48d | ||
|
|
4a88e9a0cf | ||
|
|
3b8dd6f3cf | ||
|
|
f41b7746d2 | ||
|
|
1b727741bf | ||
|
|
d2bc8175fb | ||
|
|
84dfff290a | ||
|
|
17563e423a | ||
|
|
19d3616032 | ||
|
|
4a48309643 | ||
|
|
870217acb3 | ||
|
|
f8077d7996 | ||
|
|
739907fa31 |
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -104,5 +104,8 @@
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python-envs.defaultEnvManager": "ms-python.python:system",
|
||||
"python-envs.pythonProjects": []
|
||||
"python-envs.pythonProjects": [],
|
||||
"nrf-connect.applications": [
|
||||
"${workspaceFolder}/extras/zephyr/hci_usb"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,12 +24,7 @@ import dataclasses
|
||||
import functools
|
||||
import logging
|
||||
import struct
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncGenerator,
|
||||
Coroutine,
|
||||
Optional,
|
||||
)
|
||||
from typing import Any, AsyncGenerator, Coroutine, Optional
|
||||
|
||||
import click
|
||||
|
||||
@@ -40,21 +35,14 @@ except ImportError as e:
|
||||
"Try `python -m pip install \"git+https://github.com/google/liblc3.git\"`."
|
||||
) from e
|
||||
|
||||
from bumble.audio import io as audio_io
|
||||
from bumble.colors import color
|
||||
from bumble import company_ids
|
||||
from bumble import core
|
||||
from bumble import gatt
|
||||
from bumble import hci
|
||||
from bumble.profiles import bap
|
||||
from bumble.profiles import le_audio
|
||||
from bumble.profiles import pbp
|
||||
from bumble.profiles import bass
|
||||
import bumble.device
|
||||
import bumble.logging
|
||||
import bumble.transport
|
||||
import bumble.utils
|
||||
import bumble.logging
|
||||
|
||||
from bumble import company_ids, core, data_types, gatt, hci
|
||||
from bumble.audio import io as audio_io
|
||||
from bumble.colors import color
|
||||
from bumble.profiles import bap, bass, le_audio, pbp
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -871,21 +859,13 @@ async def run_transmit(
|
||||
)
|
||||
broadcast_audio_announcement = bap.BroadcastAudioAnnouncement(broadcast_id)
|
||||
|
||||
advertising_manufacturer_data = (
|
||||
b''
|
||||
if manufacturer_data is None
|
||||
else bytes(
|
||||
core.AdvertisingData(
|
||||
[
|
||||
(
|
||||
core.AdvertisingData.MANUFACTURER_SPECIFIC_DATA,
|
||||
struct.pack('<H', manufacturer_data[0])
|
||||
+ manufacturer_data[1],
|
||||
)
|
||||
]
|
||||
)
|
||||
advertising_data_types: list[core.DataType] = [
|
||||
data_types.BroadcastName(broadcast_name)
|
||||
]
|
||||
if manufacturer_data is not None:
|
||||
advertising_data_types.append(
|
||||
data_types.ManufacturerSpecificData(*manufacturer_data)
|
||||
)
|
||||
)
|
||||
|
||||
advertising_set = await device.create_advertising_set(
|
||||
advertising_parameters=bumble.device.AdvertisingParameters(
|
||||
@@ -897,12 +877,7 @@ async def run_transmit(
|
||||
),
|
||||
advertising_data=(
|
||||
broadcast_audio_announcement.get_advertising_data()
|
||||
+ bytes(
|
||||
core.AdvertisingData(
|
||||
[(core.AdvertisingData.BROADCAST_NAME, broadcast_name.encode())]
|
||||
)
|
||||
)
|
||||
+ advertising_manufacturer_data
|
||||
+ bytes(core.AdvertisingData(advertising_data_types))
|
||||
),
|
||||
periodic_advertising_parameters=bumble.device.PeriodicAdvertisingParameters(
|
||||
periodic_advertising_interval_min=80,
|
||||
|
||||
@@ -26,16 +26,19 @@ from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
import bumble.core
|
||||
import bumble.logging
|
||||
import bumble.rfcomm
|
||||
from bumble import l2cap
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
PhysicalTransport,
|
||||
BT_L2CAP_PROTOCOL_ID,
|
||||
BT_RFCOMM_PROTOCOL_ID,
|
||||
UUID,
|
||||
CommandTimeoutError,
|
||||
ConnectionPHY,
|
||||
PhysicalTransport,
|
||||
)
|
||||
from bumble.colors import color
|
||||
from bumble.core import ConnectionPHY
|
||||
from bumble.device import (
|
||||
CigParameters,
|
||||
CisLink,
|
||||
@@ -49,12 +52,13 @@ from bumble.hci import (
|
||||
HCI_LE_1M_PHY,
|
||||
HCI_LE_2M_PHY,
|
||||
HCI_LE_CODED_PHY,
|
||||
Role,
|
||||
HCI_Constant,
|
||||
HCI_Error,
|
||||
HCI_StatusError,
|
||||
HCI_IsoDataPacket,
|
||||
HCI_StatusError,
|
||||
Role,
|
||||
)
|
||||
from bumble.pairing import PairingConfig
|
||||
from bumble.sdp import (
|
||||
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
||||
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
@@ -65,12 +69,7 @@ from bumble.sdp import (
|
||||
ServiceAttribute,
|
||||
)
|
||||
from bumble.transport import open_transport
|
||||
import bumble.rfcomm
|
||||
import bumble.core
|
||||
from bumble.utils import AsyncRunner
|
||||
from bumble.pairing import PairingConfig
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from bumble.colors import color
|
||||
from bumble.hci import Address
|
||||
from bumble.helpers import generate_irk, verify_rpa_with_irk
|
||||
|
||||
@@ -23,58 +23,55 @@ import asyncio
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import humanize
|
||||
from typing import Optional, Union
|
||||
from collections import OrderedDict
|
||||
from typing import Optional, Union
|
||||
|
||||
import click
|
||||
import humanize
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from prompt_toolkit import Application
|
||||
from prompt_toolkit.history import FileHistory
|
||||
from prompt_toolkit.completion import Completer, Completion, NestedCompleter
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.formatted_text import ANSI
|
||||
from prompt_toolkit.styles import Style
|
||||
from prompt_toolkit.filters import Condition
|
||||
from prompt_toolkit.widgets import TextArea, Frame
|
||||
from prompt_toolkit.widgets.toolbars import FormattedTextToolbar
|
||||
from prompt_toolkit.data_structures import Point
|
||||
from prompt_toolkit.filters import Condition
|
||||
from prompt_toolkit.formatted_text import ANSI
|
||||
from prompt_toolkit.history import FileHistory
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.layout import (
|
||||
Layout,
|
||||
HSplit,
|
||||
Window,
|
||||
CompletionsMenu,
|
||||
Float,
|
||||
FormattedTextControl,
|
||||
FloatContainer,
|
||||
ConditionalContainer,
|
||||
Dimension,
|
||||
Float,
|
||||
FloatContainer,
|
||||
FormattedTextControl,
|
||||
HSplit,
|
||||
Layout,
|
||||
Window,
|
||||
)
|
||||
from prompt_toolkit.styles import Style
|
||||
from prompt_toolkit.widgets import Frame, TextArea
|
||||
from prompt_toolkit.widgets.toolbars import FormattedTextToolbar
|
||||
|
||||
from bumble import __version__
|
||||
import bumble.core
|
||||
from bumble import colors
|
||||
from bumble import __version__, colors
|
||||
from bumble.core import UUID, AdvertisingData
|
||||
from bumble.device import (
|
||||
Connection,
|
||||
ConnectionParametersPreferences,
|
||||
ConnectionPHY,
|
||||
Device,
|
||||
Connection,
|
||||
Peer,
|
||||
)
|
||||
from bumble.utils import AsyncRunner
|
||||
from bumble.transport import open_transport
|
||||
from bumble.gatt import Characteristic, Service, CharacteristicDeclaration, Descriptor
|
||||
from bumble.gatt import Characteristic, CharacteristicDeclaration, Descriptor, Service
|
||||
from bumble.gatt_client import CharacteristicProxy
|
||||
from bumble.hci import (
|
||||
Address,
|
||||
HCI_Constant,
|
||||
HCI_LE_1M_PHY,
|
||||
HCI_LE_2M_PHY,
|
||||
HCI_LE_CODED_PHY,
|
||||
Address,
|
||||
HCI_Constant,
|
||||
)
|
||||
|
||||
from bumble.transport import open_transport
|
||||
from bumble.utils import AsyncRunner
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
|
||||
@@ -20,44 +20,44 @@ import time
|
||||
|
||||
import click
|
||||
|
||||
from bumble.company_ids import COMPANY_IDENTIFIERS
|
||||
import bumble.logging
|
||||
from bumble.colors import color
|
||||
from bumble.company_ids import COMPANY_IDENTIFIERS
|
||||
from bumble.core import name_or_number
|
||||
from bumble.hci import (
|
||||
map_null_terminated_utf8_string,
|
||||
CodecID,
|
||||
LeFeature,
|
||||
HCI_LE_READ_BUFFER_SIZE_COMMAND,
|
||||
HCI_LE_READ_BUFFER_SIZE_V2_COMMAND,
|
||||
HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND,
|
||||
HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND,
|
||||
HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND,
|
||||
HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
|
||||
HCI_READ_BD_ADDR_COMMAND,
|
||||
HCI_READ_BUFFER_SIZE_COMMAND,
|
||||
HCI_READ_LOCAL_NAME_COMMAND,
|
||||
HCI_SUCCESS,
|
||||
HCI_VERSION_NAMES,
|
||||
LMP_VERSION_NAMES,
|
||||
CodecID,
|
||||
HCI_Command,
|
||||
HCI_Command_Complete_Event,
|
||||
HCI_Command_Status_Event,
|
||||
HCI_READ_BUFFER_SIZE_COMMAND,
|
||||
HCI_Read_Buffer_Size_Command,
|
||||
HCI_LE_READ_BUFFER_SIZE_V2_COMMAND,
|
||||
HCI_LE_Read_Buffer_Size_V2_Command,
|
||||
HCI_READ_BD_ADDR_COMMAND,
|
||||
HCI_Read_BD_ADDR_Command,
|
||||
HCI_READ_LOCAL_NAME_COMMAND,
|
||||
HCI_Read_Local_Name_Command,
|
||||
HCI_LE_READ_BUFFER_SIZE_COMMAND,
|
||||
HCI_LE_Read_Buffer_Size_Command,
|
||||
HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND,
|
||||
HCI_LE_Read_Maximum_Data_Length_Command,
|
||||
HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND,
|
||||
HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command,
|
||||
HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND,
|
||||
HCI_LE_Read_Buffer_Size_V2_Command,
|
||||
HCI_LE_Read_Maximum_Advertising_Data_Length_Command,
|
||||
HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND,
|
||||
HCI_LE_Read_Maximum_Data_Length_Command,
|
||||
HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command,
|
||||
HCI_LE_Read_Suggested_Default_Data_Length_Command,
|
||||
HCI_Read_BD_ADDR_Command,
|
||||
HCI_Read_Buffer_Size_Command,
|
||||
HCI_Read_Local_Name_Command,
|
||||
HCI_Read_Local_Supported_Codecs_Command,
|
||||
HCI_Read_Local_Supported_Codecs_V2_Command,
|
||||
HCI_Read_Local_Version_Information_Command,
|
||||
LeFeature,
|
||||
map_null_terminated_utf8_string,
|
||||
)
|
||||
from bumble.host import Host
|
||||
from bumble.transport import open_transport
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -21,17 +21,17 @@ from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
import bumble.logging
|
||||
from bumble.colors import color
|
||||
from bumble.hci import (
|
||||
HCI_READ_LOOPBACK_MODE_COMMAND,
|
||||
HCI_Read_Loopback_Mode_Command,
|
||||
HCI_WRITE_LOOPBACK_MODE_COMMAND,
|
||||
HCI_Read_Loopback_Mode_Command,
|
||||
HCI_Write_Loopback_Mode_Command,
|
||||
LoopbackMode,
|
||||
)
|
||||
from bumble.host import Host
|
||||
from bumble.transport import open_transport
|
||||
import bumble.logging
|
||||
|
||||
|
||||
class Loopback:
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import bumble.logging
|
||||
from bumble.controller import Controller
|
||||
from bumble.link import LocalLink
|
||||
from bumble.transport import open_transport
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -20,18 +20,18 @@ from typing import Callable, Iterable, Optional
|
||||
|
||||
import click
|
||||
|
||||
from bumble.core import ProtocolError
|
||||
import bumble.logging
|
||||
from bumble.colors import color
|
||||
from bumble.core import ProtocolError
|
||||
from bumble.device import Device, Peer
|
||||
from bumble.gatt import Service
|
||||
from bumble.profiles.device_information_service import DeviceInformationServiceProxy
|
||||
from bumble.profiles.battery_service import BatteryServiceProxy
|
||||
from bumble.profiles.device_information_service import DeviceInformationServiceProxy
|
||||
from bumble.profiles.gap import GenericAccessServiceProxy
|
||||
from bumble.profiles.pacs import PublishedAudioCapabilitiesServiceProxy
|
||||
from bumble.profiles.tmap import TelephonyAndMediaAudioServiceProxy
|
||||
from bumble.profiles.vcs import VolumeControlServiceProxy
|
||||
from bumble.transport import open_transport
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -20,11 +20,11 @@ import asyncio
|
||||
import click
|
||||
|
||||
import bumble.core
|
||||
import bumble.logging
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device, Peer
|
||||
from bumble.gatt import show_services
|
||||
from bumble.transport import open_transport
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -20,16 +20,15 @@ import struct
|
||||
|
||||
import click
|
||||
|
||||
import bumble.logging
|
||||
from bumble import l2cap
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device, Peer
|
||||
from bumble.core import AdvertisingData
|
||||
from bumble.gatt import Service, Characteristic, CharacteristicValue
|
||||
from bumble.utils import AsyncRunner
|
||||
from bumble.transport import open_transport
|
||||
from bumble.device import Device, Peer
|
||||
from bumble.gatt import Characteristic, CharacteristicValue, Service
|
||||
from bumble.hci import HCI_Constant
|
||||
import bumble.logging
|
||||
|
||||
from bumble.transport import open_transport
|
||||
from bumble.utils import AsyncRunner
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import bumble.logging
|
||||
from bumble import hci, transport
|
||||
from bumble.bridge import HCI_Bridge
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -19,13 +19,13 @@ import asyncio
|
||||
|
||||
import click
|
||||
|
||||
import bumble.logging
|
||||
from bumble import l2cap
|
||||
from bumble.colors import color
|
||||
from bumble.transport import open_transport
|
||||
from bumble.device import Device
|
||||
from bumble.utils import FlowControlAsyncPipe
|
||||
from bumble.hci import HCI_Constant
|
||||
import bumble.logging
|
||||
from bumble.transport import open_transport
|
||||
from bumble.utils import FlowControlAsyncPipe
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -20,31 +20,30 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import datetime
|
||||
import functools
|
||||
from importlib import resources
|
||||
import json
|
||||
import logging
|
||||
import pathlib
|
||||
import weakref
|
||||
import wave
|
||||
import weakref
|
||||
from importlib import resources
|
||||
|
||||
try:
|
||||
import lc3 # type: ignore # pylint: disable=E0401
|
||||
except ImportError as e:
|
||||
raise ImportError("Try `python -m pip install \".[lc3]\"`.") from e
|
||||
|
||||
import click
|
||||
import aiohttp.web
|
||||
import click
|
||||
|
||||
import bumble
|
||||
from bumble import utils
|
||||
from bumble.core import AdvertisingData
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device, DeviceConfiguration, AdvertisingParameters, CisLink
|
||||
from bumble.transport import open_transport
|
||||
from bumble.profiles import ascs, bap, pacs
|
||||
from bumble.hci import Address, CodecID, CodingFormat, HCI_IsoDataPacket
|
||||
import bumble.logging
|
||||
|
||||
from bumble import data_types, utils
|
||||
from bumble.colors import color
|
||||
from bumble.core import AdvertisingData
|
||||
from bumble.device import AdvertisingParameters, CisLink, Device, DeviceConfiguration
|
||||
from bumble.hci import Address, CodecID, CodingFormat, HCI_IsoDataPacket
|
||||
from bumble.profiles import ascs, bap, pacs
|
||||
from bumble.transport import open_transport
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -331,22 +330,13 @@ class Speaker:
|
||||
advertising_data = bytes(
|
||||
AdvertisingData(
|
||||
[
|
||||
(
|
||||
AdvertisingData.COMPLETE_LOCAL_NAME,
|
||||
bytes(device_config.name, 'utf-8'),
|
||||
data_types.CompleteLocalName(device_config.name),
|
||||
data_types.Flags(
|
||||
AdvertisingData.Flags.LE_GENERAL_DISCOVERABLE_MODE
|
||||
| AdvertisingData.Flags.BR_EDR_NOT_SUPPORTED
|
||||
),
|
||||
(
|
||||
AdvertisingData.FLAGS,
|
||||
bytes(
|
||||
[
|
||||
AdvertisingData.LE_GENERAL_DISCOVERABLE_MODE_FLAG
|
||||
| AdvertisingData.BR_EDR_NOT_SUPPORTED_FLAG
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
|
||||
bytes(pacs.PublishedAudioCapabilitiesService.UUID),
|
||||
data_types.IncompleteListOf16BitServiceUUIDs(
|
||||
[pacs.PublishedAudioCapabilitiesService.UUID]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
73
apps/pair.py
73
apps/pair.py
@@ -16,42 +16,44 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
|
||||
import click
|
||||
from prompt_toolkit.shortcuts import PromptSession
|
||||
|
||||
from bumble import data_types
|
||||
from bumble.a2dp import make_audio_sink_service_sdp_records
|
||||
from bumble.att import (
|
||||
ATT_INSUFFICIENT_AUTHENTICATION_ERROR,
|
||||
ATT_INSUFFICIENT_ENCRYPTION_ERROR,
|
||||
ATT_Error,
|
||||
)
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device, Peer
|
||||
from bumble.transport import open_transport
|
||||
from bumble.pairing import OobData, PairingDelegate, PairingConfig
|
||||
from bumble.smp import OobContext, OobLegacyContext
|
||||
from bumble.smp import error_name as smp_error_name
|
||||
from bumble.keys import JsonKeyStore
|
||||
from bumble.core import (
|
||||
UUID,
|
||||
AdvertisingData,
|
||||
Appearance,
|
||||
ProtocolError,
|
||||
DataType,
|
||||
PhysicalTransport,
|
||||
UUID,
|
||||
ProtocolError,
|
||||
)
|
||||
from bumble.device import Device, Peer
|
||||
from bumble.gatt import (
|
||||
GATT_DEVICE_NAME_CHARACTERISTIC,
|
||||
GATT_GENERIC_ACCESS_SERVICE,
|
||||
GATT_HEART_RATE_SERVICE,
|
||||
GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
|
||||
Service,
|
||||
GATT_HEART_RATE_SERVICE,
|
||||
Characteristic,
|
||||
Service,
|
||||
)
|
||||
from bumble.hci import OwnAddressType
|
||||
from bumble.att import (
|
||||
ATT_Error,
|
||||
ATT_INSUFFICIENT_AUTHENTICATION_ERROR,
|
||||
ATT_INSUFFICIENT_ENCRYPTION_ERROR,
|
||||
)
|
||||
from bumble.keys import JsonKeyStore
|
||||
from bumble.pairing import OobData, PairingConfig, PairingDelegate
|
||||
from bumble.smp import OobContext, OobLegacyContext
|
||||
from bumble.smp import error_name as smp_error_name
|
||||
from bumble.transport import open_transport
|
||||
from bumble.utils import AsyncRunner
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -506,33 +508,21 @@ async def pair(
|
||||
if mode == 'dual':
|
||||
flags |= AdvertisingData.Flags.SIMULTANEOUS_LE_BR_EDR_CAPABLE
|
||||
|
||||
ad_structs = [
|
||||
(
|
||||
AdvertisingData.FLAGS,
|
||||
bytes([flags]),
|
||||
),
|
||||
(AdvertisingData.COMPLETE_LOCAL_NAME, 'Bumble'.encode()),
|
||||
advertising_data_types: list[DataType] = [
|
||||
data_types.Flags(flags),
|
||||
data_types.CompleteLocalName('Bumble'),
|
||||
]
|
||||
if service_uuids_16:
|
||||
ad_structs.append(
|
||||
(
|
||||
AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
|
||||
b"".join(bytes(uuid) for uuid in service_uuids_16),
|
||||
)
|
||||
advertising_data_types.append(
|
||||
data_types.IncompleteListOf16BitServiceUUIDs(service_uuids_16)
|
||||
)
|
||||
if service_uuids_32:
|
||||
ad_structs.append(
|
||||
(
|
||||
AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
|
||||
b"".join(bytes(uuid) for uuid in service_uuids_32),
|
||||
)
|
||||
advertising_data_types.append(
|
||||
data_types.IncompleteListOf32BitServiceUUIDs(service_uuids_32)
|
||||
)
|
||||
if service_uuids_128:
|
||||
ad_structs.append(
|
||||
(
|
||||
AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
|
||||
b"".join(bytes(uuid) for uuid in service_uuids_128),
|
||||
)
|
||||
advertising_data_types.append(
|
||||
data_types.IncompleteListOf128BitServiceUUIDs(service_uuids_128)
|
||||
)
|
||||
|
||||
if advertise_appearance:
|
||||
@@ -559,13 +549,10 @@ async def pair(
|
||||
advertise_appearance_int = int(
|
||||
Appearance(category_enum, subcategory_enum)
|
||||
)
|
||||
ad_structs.append(
|
||||
(
|
||||
AdvertisingData.APPEARANCE,
|
||||
struct.pack('<H', advertise_appearance_int),
|
||||
)
|
||||
advertising_data_types.append(
|
||||
data_types.Appearance(category_enum, subcategory_enum)
|
||||
)
|
||||
device.advertising_data = bytes(AdvertisingData(ad_structs))
|
||||
device.advertising_data = bytes(AdvertisingData(advertising_data_types))
|
||||
await device.start_advertising(
|
||||
auto_restart=True,
|
||||
own_address_type=(
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import asyncio
|
||||
import click
|
||||
import logging
|
||||
import json
|
||||
|
||||
from bumble.pandora import PandoraDevice, Config, serve
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
|
||||
from bumble.pandora import Config, PandoraDevice, serve
|
||||
|
||||
BUMBLE_SERVER_GRPC_PORT = 7999
|
||||
ROOTCANAL_PORT_CUTTLEFISH = 7300
|
||||
|
||||
|
||||
@@ -16,53 +16,50 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Optional, Union
|
||||
|
||||
import click
|
||||
|
||||
import bumble.logging
|
||||
from bumble.a2dp import (
|
||||
make_audio_source_service_sdp_records,
|
||||
A2DP_SBC_CODEC_TYPE,
|
||||
A2DP_MPEG_2_4_AAC_CODEC_TYPE,
|
||||
A2DP_NON_A2DP_CODEC_TYPE,
|
||||
A2DP_SBC_CODEC_TYPE,
|
||||
AacFrame,
|
||||
AacParser,
|
||||
AacPacketSource,
|
||||
AacMediaCodecInformation,
|
||||
SbcFrame,
|
||||
SbcParser,
|
||||
SbcPacketSource,
|
||||
SbcMediaCodecInformation,
|
||||
OpusPacket,
|
||||
OpusParser,
|
||||
OpusPacketSource,
|
||||
AacPacketSource,
|
||||
AacParser,
|
||||
OpusMediaCodecInformation,
|
||||
OpusPacket,
|
||||
OpusPacketSource,
|
||||
OpusParser,
|
||||
SbcFrame,
|
||||
SbcMediaCodecInformation,
|
||||
SbcPacketSource,
|
||||
SbcParser,
|
||||
make_audio_source_service_sdp_records,
|
||||
)
|
||||
from bumble.avrcp import Protocol as AvrcpProtocol
|
||||
from bumble.avdtp import (
|
||||
find_avdtp_service_with_connection,
|
||||
AVDTP_AUDIO_MEDIA_TYPE,
|
||||
AVDTP_DELAY_REPORTING_SERVICE_CATEGORY,
|
||||
MediaCodecCapabilities,
|
||||
MediaPacketPump,
|
||||
Protocol as AvdtpProtocol,
|
||||
)
|
||||
from bumble.avdtp import Protocol as AvdtpProtocol
|
||||
from bumble.avdtp import find_avdtp_service_with_connection
|
||||
from bumble.avrcp import Protocol as AvrcpProtocol
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
AdvertisingData,
|
||||
ConnectionError as BumbleConnectionError,
|
||||
DeviceClass,
|
||||
PhysicalTransport,
|
||||
)
|
||||
from bumble.core import AdvertisingData
|
||||
from bumble.core import ConnectionError as BumbleConnectionError
|
||||
from bumble.core import DeviceClass, PhysicalTransport
|
||||
from bumble.device import Connection, Device, DeviceConfiguration
|
||||
from bumble.hci import Address, HCI_CONNECTION_ALREADY_EXISTS_ERROR, HCI_Constant
|
||||
from bumble.hci import HCI_CONNECTION_ALREADY_EXISTS_ERROR, Address, HCI_Constant
|
||||
from bumble.pairing import PairingConfig
|
||||
from bumble.transport import open_transport
|
||||
from bumble.utils import AsyncRunner
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -21,15 +21,10 @@ from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device, DeviceConfiguration, Connection
|
||||
from bumble import core
|
||||
from bumble import hci
|
||||
from bumble import rfcomm
|
||||
from bumble import transport
|
||||
from bumble import utils
|
||||
import bumble.logging
|
||||
|
||||
from bumble import core, hci, rfcomm, transport, utils
|
||||
from bumble.colors import color
|
||||
from bumble.device import Connection, Device, DeviceConfiguration
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
|
||||
22
apps/scan.py
22
apps/scan.py
@@ -16,16 +16,17 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import asyncio
|
||||
|
||||
import click
|
||||
|
||||
import bumble.logging
|
||||
from bumble import data_types
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device
|
||||
from bumble.transport import open_transport
|
||||
from bumble.device import Advertisement, Device
|
||||
from bumble.hci import HCI_LE_1M_PHY, HCI_LE_CODED_PHY, Address, HCI_Constant
|
||||
from bumble.keys import JsonKeyStore
|
||||
from bumble.smp import AddressResolver
|
||||
from bumble.device import Advertisement
|
||||
from bumble.hci import Address, HCI_Constant, HCI_LE_1M_PHY, HCI_LE_CODED_PHY
|
||||
import bumble.logging
|
||||
from bumble.transport import open_transport
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -94,13 +95,22 @@ class AdvertisementPrinter:
|
||||
else:
|
||||
phy_info = ''
|
||||
|
||||
details = separator.join(
|
||||
[
|
||||
data_type.to_string(use_label=True)
|
||||
for data_type in data_types.data_types_from_advertising_data(
|
||||
advertisement.data
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
print(
|
||||
f'>>> {color(address, address_color)} '
|
||||
f'[{color(address_type_string, type_color)}]{address_qualifier}'
|
||||
f'{resolution_qualifier}:{separator}'
|
||||
f'{phy_info}'
|
||||
f'RSSI:{advertisement.rssi:4} {rssi_bar}{separator}'
|
||||
f'{advertisement.data.to_string(separator)}\n'
|
||||
f'{details}\n'
|
||||
)
|
||||
|
||||
def on_advertisement(self, advertisement):
|
||||
|
||||
@@ -22,12 +22,11 @@ import struct
|
||||
|
||||
import click
|
||||
|
||||
from bumble.colors import color
|
||||
from bumble import hci
|
||||
from bumble.transport.common import PacketReader
|
||||
from bumble.helpers import PacketTracer
|
||||
import bumble.logging
|
||||
|
||||
from bumble import hci
|
||||
from bumble.colors import color
|
||||
from bumble.helpers import PacketTracer
|
||||
from bumble.transport.common import PacketReader
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -16,49 +16,49 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import asyncio.subprocess
|
||||
from importlib import resources
|
||||
import enum
|
||||
import json
|
||||
import logging
|
||||
import pathlib
|
||||
import subprocess
|
||||
from typing import Optional
|
||||
import weakref
|
||||
from importlib import resources
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
import aiohttp
|
||||
import click
|
||||
from aiohttp import web
|
||||
|
||||
import bumble
|
||||
from bumble.colors import color
|
||||
from bumble.core import PhysicalTransport, CommandTimeoutError
|
||||
from bumble.device import Connection, Device, DeviceConfiguration
|
||||
from bumble.hci import HCI_StatusError
|
||||
from bumble.pairing import PairingConfig
|
||||
from bumble.sdp import ServiceAttribute
|
||||
from bumble.transport import open_transport
|
||||
import bumble.logging
|
||||
from bumble.a2dp import (
|
||||
A2DP_MPEG_2_4_AAC_CODEC_TYPE,
|
||||
A2DP_NON_A2DP_CODEC_TYPE,
|
||||
A2DP_SBC_CODEC_TYPE,
|
||||
AacMediaCodecInformation,
|
||||
OpusMediaCodecInformation,
|
||||
SbcMediaCodecInformation,
|
||||
make_audio_sink_service_sdp_records,
|
||||
)
|
||||
from bumble.avdtp import (
|
||||
AVDTP_AUDIO_MEDIA_TYPE,
|
||||
Listener,
|
||||
MediaCodecCapabilities,
|
||||
Protocol,
|
||||
)
|
||||
from bumble.a2dp import (
|
||||
make_audio_sink_service_sdp_records,
|
||||
A2DP_SBC_CODEC_TYPE,
|
||||
A2DP_MPEG_2_4_AAC_CODEC_TYPE,
|
||||
A2DP_NON_A2DP_CODEC_TYPE,
|
||||
SbcMediaCodecInformation,
|
||||
AacMediaCodecInformation,
|
||||
OpusMediaCodecInformation,
|
||||
)
|
||||
from bumble.utils import AsyncRunner
|
||||
from bumble.codecs import AacAudioRtpPacket
|
||||
from bumble.colors import color
|
||||
from bumble.core import CommandTimeoutError, PhysicalTransport
|
||||
from bumble.device import Connection, Device, DeviceConfiguration
|
||||
from bumble.hci import HCI_StatusError
|
||||
from bumble.pairing import PairingConfig
|
||||
from bumble.rtp import MediaPacket
|
||||
import bumble.logging
|
||||
|
||||
from bumble.sdp import ServiceAttribute
|
||||
from bumble.transport import open_transport
|
||||
from bumble.utils import AsyncRunner
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import asyncio
|
||||
|
||||
import click
|
||||
|
||||
import bumble.logging
|
||||
from bumble.device import Device
|
||||
from bumble.keys import JsonKeyStore
|
||||
from bumble.transport import open_transport
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -29,10 +29,9 @@
|
||||
import click
|
||||
import usb1
|
||||
|
||||
import bumble.logging
|
||||
from bumble.colors import color
|
||||
from bumble.transport.usb import load_libusb
|
||||
import bumble.logging
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
|
||||
@@ -17,37 +17,36 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
import dataclasses
|
||||
import enum
|
||||
import logging
|
||||
import struct
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Awaitable, Callable
|
||||
from typing_extensions import ClassVar, Self
|
||||
|
||||
from typing_extensions import ClassVar, Self
|
||||
|
||||
from bumble.codecs import AacAudioRtpPacket
|
||||
from bumble.company_ids import COMPANY_IDENTIFIERS
|
||||
from bumble.sdp import (
|
||||
DataElement,
|
||||
ServiceAttribute,
|
||||
SDP_PUBLIC_BROWSE_ROOT,
|
||||
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
||||
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
)
|
||||
from bumble.core import (
|
||||
BT_L2CAP_PROTOCOL_ID,
|
||||
BT_AUDIO_SOURCE_SERVICE,
|
||||
BT_AUDIO_SINK_SERVICE,
|
||||
BT_AVDTP_PROTOCOL_ID,
|
||||
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
|
||||
BT_AUDIO_SINK_SERVICE,
|
||||
BT_AUDIO_SOURCE_SERVICE,
|
||||
BT_AVDTP_PROTOCOL_ID,
|
||||
BT_L2CAP_PROTOCOL_ID,
|
||||
name_or_number,
|
||||
)
|
||||
from bumble.rtp import MediaPacket
|
||||
|
||||
from bumble.sdp import (
|
||||
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
||||
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_PUBLIC_BROWSE_ROOT,
|
||||
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
||||
DataElement,
|
||||
ServiceAttribute,
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
493
bumble/att.py
493
bumble/att.py
@@ -24,24 +24,26 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import enum
|
||||
import functools
|
||||
import inspect
|
||||
import struct
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Awaitable,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Generic,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
|
||||
from bumble import utils
|
||||
from bumble.core import UUID, name_or_number, InvalidOperationError, ProtocolError
|
||||
from bumble.hci import HCI_Object, key_with_value
|
||||
from bumble import hci, utils
|
||||
from bumble.colors import color
|
||||
from bumble.core import UUID, InvalidOperationError, ProtocolError
|
||||
from bumble.hci import HCI_Object
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Typing
|
||||
@@ -60,96 +62,66 @@ _T = TypeVar('_T')
|
||||
ATT_CID = 0x04
|
||||
ATT_PSM = 0x001F
|
||||
|
||||
ATT_ERROR_RESPONSE = 0x01
|
||||
ATT_EXCHANGE_MTU_REQUEST = 0x02
|
||||
ATT_EXCHANGE_MTU_RESPONSE = 0x03
|
||||
ATT_FIND_INFORMATION_REQUEST = 0x04
|
||||
ATT_FIND_INFORMATION_RESPONSE = 0x05
|
||||
ATT_FIND_BY_TYPE_VALUE_REQUEST = 0x06
|
||||
ATT_FIND_BY_TYPE_VALUE_RESPONSE = 0x07
|
||||
ATT_READ_BY_TYPE_REQUEST = 0x08
|
||||
ATT_READ_BY_TYPE_RESPONSE = 0x09
|
||||
ATT_READ_REQUEST = 0x0A
|
||||
ATT_READ_RESPONSE = 0x0B
|
||||
ATT_READ_BLOB_REQUEST = 0x0C
|
||||
ATT_READ_BLOB_RESPONSE = 0x0D
|
||||
ATT_READ_MULTIPLE_REQUEST = 0x0E
|
||||
ATT_READ_MULTIPLE_RESPONSE = 0x0F
|
||||
ATT_READ_BY_GROUP_TYPE_REQUEST = 0x10
|
||||
ATT_READ_BY_GROUP_TYPE_RESPONSE = 0x11
|
||||
ATT_WRITE_REQUEST = 0x12
|
||||
ATT_WRITE_RESPONSE = 0x13
|
||||
ATT_WRITE_COMMAND = 0x52
|
||||
ATT_SIGNED_WRITE_COMMAND = 0xD2
|
||||
ATT_PREPARE_WRITE_REQUEST = 0x16
|
||||
ATT_PREPARE_WRITE_RESPONSE = 0x17
|
||||
ATT_EXECUTE_WRITE_REQUEST = 0x18
|
||||
ATT_EXECUTE_WRITE_RESPONSE = 0x19
|
||||
ATT_HANDLE_VALUE_NOTIFICATION = 0x1B
|
||||
ATT_HANDLE_VALUE_INDICATION = 0x1D
|
||||
ATT_HANDLE_VALUE_CONFIRMATION = 0x1E
|
||||
|
||||
ATT_PDU_NAMES = {
|
||||
ATT_ERROR_RESPONSE: 'ATT_ERROR_RESPONSE',
|
||||
ATT_EXCHANGE_MTU_REQUEST: 'ATT_EXCHANGE_MTU_REQUEST',
|
||||
ATT_EXCHANGE_MTU_RESPONSE: 'ATT_EXCHANGE_MTU_RESPONSE',
|
||||
ATT_FIND_INFORMATION_REQUEST: 'ATT_FIND_INFORMATION_REQUEST',
|
||||
ATT_FIND_INFORMATION_RESPONSE: 'ATT_FIND_INFORMATION_RESPONSE',
|
||||
ATT_FIND_BY_TYPE_VALUE_REQUEST: 'ATT_FIND_BY_TYPE_VALUE_REQUEST',
|
||||
ATT_FIND_BY_TYPE_VALUE_RESPONSE: 'ATT_FIND_BY_TYPE_VALUE_RESPONSE',
|
||||
ATT_READ_BY_TYPE_REQUEST: 'ATT_READ_BY_TYPE_REQUEST',
|
||||
ATT_READ_BY_TYPE_RESPONSE: 'ATT_READ_BY_TYPE_RESPONSE',
|
||||
ATT_READ_REQUEST: 'ATT_READ_REQUEST',
|
||||
ATT_READ_RESPONSE: 'ATT_READ_RESPONSE',
|
||||
ATT_READ_BLOB_REQUEST: 'ATT_READ_BLOB_REQUEST',
|
||||
ATT_READ_BLOB_RESPONSE: 'ATT_READ_BLOB_RESPONSE',
|
||||
ATT_READ_MULTIPLE_REQUEST: 'ATT_READ_MULTIPLE_REQUEST',
|
||||
ATT_READ_MULTIPLE_RESPONSE: 'ATT_READ_MULTIPLE_RESPONSE',
|
||||
ATT_READ_BY_GROUP_TYPE_REQUEST: 'ATT_READ_BY_GROUP_TYPE_REQUEST',
|
||||
ATT_READ_BY_GROUP_TYPE_RESPONSE: 'ATT_READ_BY_GROUP_TYPE_RESPONSE',
|
||||
ATT_WRITE_REQUEST: 'ATT_WRITE_REQUEST',
|
||||
ATT_WRITE_RESPONSE: 'ATT_WRITE_RESPONSE',
|
||||
ATT_WRITE_COMMAND: 'ATT_WRITE_COMMAND',
|
||||
ATT_SIGNED_WRITE_COMMAND: 'ATT_SIGNED_WRITE_COMMAND',
|
||||
ATT_PREPARE_WRITE_REQUEST: 'ATT_PREPARE_WRITE_REQUEST',
|
||||
ATT_PREPARE_WRITE_RESPONSE: 'ATT_PREPARE_WRITE_RESPONSE',
|
||||
ATT_EXECUTE_WRITE_REQUEST: 'ATT_EXECUTE_WRITE_REQUEST',
|
||||
ATT_EXECUTE_WRITE_RESPONSE: 'ATT_EXECUTE_WRITE_RESPONSE',
|
||||
ATT_HANDLE_VALUE_NOTIFICATION: 'ATT_HANDLE_VALUE_NOTIFICATION',
|
||||
ATT_HANDLE_VALUE_INDICATION: 'ATT_HANDLE_VALUE_INDICATION',
|
||||
ATT_HANDLE_VALUE_CONFIRMATION: 'ATT_HANDLE_VALUE_CONFIRMATION'
|
||||
}
|
||||
class Opcode(hci.SpecableEnum):
|
||||
ATT_ERROR_RESPONSE = 0x01
|
||||
ATT_EXCHANGE_MTU_REQUEST = 0x02
|
||||
ATT_EXCHANGE_MTU_RESPONSE = 0x03
|
||||
ATT_FIND_INFORMATION_REQUEST = 0x04
|
||||
ATT_FIND_INFORMATION_RESPONSE = 0x05
|
||||
ATT_FIND_BY_TYPE_VALUE_REQUEST = 0x06
|
||||
ATT_FIND_BY_TYPE_VALUE_RESPONSE = 0x07
|
||||
ATT_READ_BY_TYPE_REQUEST = 0x08
|
||||
ATT_READ_BY_TYPE_RESPONSE = 0x09
|
||||
ATT_READ_REQUEST = 0x0A
|
||||
ATT_READ_RESPONSE = 0x0B
|
||||
ATT_READ_BLOB_REQUEST = 0x0C
|
||||
ATT_READ_BLOB_RESPONSE = 0x0D
|
||||
ATT_READ_MULTIPLE_REQUEST = 0x0E
|
||||
ATT_READ_MULTIPLE_RESPONSE = 0x0F
|
||||
ATT_READ_BY_GROUP_TYPE_REQUEST = 0x10
|
||||
ATT_READ_BY_GROUP_TYPE_RESPONSE = 0x11
|
||||
ATT_WRITE_REQUEST = 0x12
|
||||
ATT_WRITE_RESPONSE = 0x13
|
||||
ATT_WRITE_COMMAND = 0x52
|
||||
ATT_SIGNED_WRITE_COMMAND = 0xD2
|
||||
ATT_PREPARE_WRITE_REQUEST = 0x16
|
||||
ATT_PREPARE_WRITE_RESPONSE = 0x17
|
||||
ATT_EXECUTE_WRITE_REQUEST = 0x18
|
||||
ATT_EXECUTE_WRITE_RESPONSE = 0x19
|
||||
ATT_HANDLE_VALUE_NOTIFICATION = 0x1B
|
||||
ATT_HANDLE_VALUE_INDICATION = 0x1D
|
||||
ATT_HANDLE_VALUE_CONFIRMATION = 0x1E
|
||||
|
||||
ATT_REQUESTS = [
|
||||
ATT_EXCHANGE_MTU_REQUEST,
|
||||
ATT_FIND_INFORMATION_REQUEST,
|
||||
ATT_FIND_BY_TYPE_VALUE_REQUEST,
|
||||
ATT_READ_BY_TYPE_REQUEST,
|
||||
ATT_READ_REQUEST,
|
||||
ATT_READ_BLOB_REQUEST,
|
||||
ATT_READ_MULTIPLE_REQUEST,
|
||||
ATT_READ_BY_GROUP_TYPE_REQUEST,
|
||||
ATT_WRITE_REQUEST,
|
||||
ATT_PREPARE_WRITE_REQUEST,
|
||||
ATT_EXECUTE_WRITE_REQUEST
|
||||
Opcode.ATT_EXCHANGE_MTU_REQUEST,
|
||||
Opcode.ATT_FIND_INFORMATION_REQUEST,
|
||||
Opcode.ATT_FIND_BY_TYPE_VALUE_REQUEST,
|
||||
Opcode.ATT_READ_BY_TYPE_REQUEST,
|
||||
Opcode.ATT_READ_REQUEST,
|
||||
Opcode.ATT_READ_BLOB_REQUEST,
|
||||
Opcode.ATT_READ_MULTIPLE_REQUEST,
|
||||
Opcode.ATT_READ_BY_GROUP_TYPE_REQUEST,
|
||||
Opcode.ATT_WRITE_REQUEST,
|
||||
Opcode.ATT_PREPARE_WRITE_REQUEST,
|
||||
Opcode.ATT_EXECUTE_WRITE_REQUEST
|
||||
]
|
||||
|
||||
ATT_RESPONSES = [
|
||||
ATT_ERROR_RESPONSE,
|
||||
ATT_EXCHANGE_MTU_RESPONSE,
|
||||
ATT_FIND_INFORMATION_RESPONSE,
|
||||
ATT_FIND_BY_TYPE_VALUE_RESPONSE,
|
||||
ATT_READ_BY_TYPE_RESPONSE,
|
||||
ATT_READ_RESPONSE,
|
||||
ATT_READ_BLOB_RESPONSE,
|
||||
ATT_READ_MULTIPLE_RESPONSE,
|
||||
ATT_READ_BY_GROUP_TYPE_RESPONSE,
|
||||
ATT_WRITE_RESPONSE,
|
||||
ATT_PREPARE_WRITE_RESPONSE,
|
||||
ATT_EXECUTE_WRITE_RESPONSE
|
||||
Opcode.ATT_ERROR_RESPONSE,
|
||||
Opcode.ATT_EXCHANGE_MTU_RESPONSE,
|
||||
Opcode.ATT_FIND_INFORMATION_RESPONSE,
|
||||
Opcode.ATT_FIND_BY_TYPE_VALUE_RESPONSE,
|
||||
Opcode.ATT_READ_BY_TYPE_RESPONSE,
|
||||
Opcode.ATT_READ_RESPONSE,
|
||||
Opcode.ATT_READ_BLOB_RESPONSE,
|
||||
Opcode.ATT_READ_MULTIPLE_RESPONSE,
|
||||
Opcode.ATT_READ_BY_GROUP_TYPE_RESPONSE,
|
||||
Opcode.ATT_WRITE_RESPONSE,
|
||||
Opcode.ATT_PREPARE_WRITE_RESPONSE,
|
||||
Opcode.ATT_EXECUTE_WRITE_RESPONSE
|
||||
]
|
||||
|
||||
class ErrorCode(utils.OpenIntEnum):
|
||||
class ErrorCode(hci.SpecableEnum):
|
||||
'''
|
||||
See
|
||||
|
||||
@@ -204,10 +176,6 @@ ATT_INSUFFICIENT_RESOURCES_ERROR = ErrorCode.INSUFFICIENT_RESOURCES
|
||||
ATT_DEFAULT_MTU = 23
|
||||
|
||||
HANDLE_FIELD_SPEC = {'size': 2, 'mapper': lambda x: f'0x{x:04X}'}
|
||||
# pylint: disable-next=unnecessary-lambda-assignment,unnecessary-lambda
|
||||
UUID_2_16_FIELD_SPEC = lambda x, y: UUID.parse_uuid(x, y)
|
||||
# pylint: disable-next=unnecessary-lambda-assignment,unnecessary-lambda
|
||||
UUID_2_FIELD_SPEC = lambda x, y: UUID.parse_uuid_2(x, y) # noqa: E731
|
||||
|
||||
# fmt: on
|
||||
# pylint: enable=line-too-long
|
||||
@@ -227,7 +195,7 @@ class ATT_Error(ProtocolError):
|
||||
super().__init__(
|
||||
error_code,
|
||||
error_namespace='att',
|
||||
error_name=ATT_PDU.error_name(error_code),
|
||||
error_name=ErrorCode(error_code).name,
|
||||
)
|
||||
self.att_handle = att_handle
|
||||
self.message = message
|
||||
@@ -242,61 +210,45 @@ class ATT_Error(ProtocolError):
|
||||
# -----------------------------------------------------------------------------
|
||||
# Attribute Protocol
|
||||
# -----------------------------------------------------------------------------
|
||||
@dataclasses.dataclass
|
||||
class ATT_PDU:
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.3 ATTRIBUTE PDU
|
||||
'''
|
||||
|
||||
pdu_classes: dict[int, type[ATT_PDU]] = {}
|
||||
op_code = 0
|
||||
name: str
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(pdu):
|
||||
op_code = pdu[0]
|
||||
|
||||
cls = ATT_PDU.pdu_classes.get(op_code)
|
||||
if cls is None:
|
||||
instance = ATT_PDU(pdu)
|
||||
instance.name = ATT_PDU.pdu_name(op_code)
|
||||
instance.op_code = op_code
|
||||
return instance
|
||||
self = cls.__new__(cls)
|
||||
ATT_PDU.__init__(self, pdu)
|
||||
if hasattr(self, 'fields'):
|
||||
self.init_from_bytes(pdu, 1)
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def pdu_name(op_code):
|
||||
return name_or_number(ATT_PDU_NAMES, op_code, 2)
|
||||
pdu_classes: ClassVar[dict[int, type[ATT_PDU]]] = {}
|
||||
fields: ClassVar[hci.Fields] = ()
|
||||
op_code: int = dataclasses.field(init=False)
|
||||
name: str = dataclasses.field(init=False)
|
||||
_payload: Optional[bytes] = dataclasses.field(default=None, init=False)
|
||||
|
||||
@classmethod
|
||||
def error_name(cls, error_code: int) -> str:
|
||||
return ErrorCode(error_code).name
|
||||
def from_bytes(cls, pdu: bytes) -> ATT_PDU:
|
||||
op_code = pdu[0]
|
||||
|
||||
@staticmethod
|
||||
def subclass(fields):
|
||||
def inner(cls):
|
||||
cls.name = cls.__name__.upper()
|
||||
cls.op_code = key_with_value(ATT_PDU_NAMES, cls.name)
|
||||
if cls.op_code is None:
|
||||
raise KeyError(f'PDU name {cls.name} not found in ATT_PDU_NAMES')
|
||||
cls.fields = fields
|
||||
subclass = ATT_PDU.pdu_classes.get(op_code)
|
||||
if subclass is None:
|
||||
instance = ATT_PDU()
|
||||
instance.op_code = op_code
|
||||
instance.payload = pdu[1:]
|
||||
instance.name = Opcode(op_code).name
|
||||
return instance
|
||||
instance = subclass(**HCI_Object.dict_from_bytes(pdu, 1, subclass.fields))
|
||||
instance.payload = pdu[1:]
|
||||
return instance
|
||||
|
||||
# Register a factory for this class
|
||||
ATT_PDU.pdu_classes[cls.op_code] = cls
|
||||
_PDU = TypeVar("_PDU", bound="ATT_PDU")
|
||||
|
||||
return cls
|
||||
@classmethod
|
||||
def subclass(cls, subclass: type[_PDU]) -> type[_PDU]:
|
||||
subclass.name = subclass.__name__.upper()
|
||||
subclass.op_code = Opcode[subclass.name]
|
||||
subclass.fields = HCI_Object.fields_from_dataclass(subclass)
|
||||
|
||||
return inner
|
||||
# Register a factory for this class
|
||||
ATT_PDU.pdu_classes[subclass.op_code] = subclass
|
||||
|
||||
def __init__(self, pdu=None, **kwargs):
|
||||
if hasattr(self, 'fields') and kwargs:
|
||||
HCI_Object.init_from_fields(self, self.fields, kwargs)
|
||||
if pdu is None:
|
||||
pdu = bytes([self.op_code]) + HCI_Object.dict_to_bytes(kwargs, self.fields)
|
||||
self.pdu = pdu
|
||||
return subclass
|
||||
|
||||
def init_from_bytes(self, pdu, offset):
|
||||
return HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
|
||||
@@ -309,67 +261,91 @@ class ATT_PDU:
|
||||
def has_authentication_signature(self):
|
||||
return ((self.op_code >> 7) & 1) == 1
|
||||
|
||||
def __bytes__(self):
|
||||
return self.pdu
|
||||
@property
|
||||
def payload(self) -> bytes:
|
||||
if self._payload is None:
|
||||
self._payload = HCI_Object.dict_to_bytes(self.__dict__, self.fields)
|
||||
return self._payload
|
||||
|
||||
@payload.setter
|
||||
def payload(self, value: bytes):
|
||||
self._payload = value
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return bytes([self.op_code]) + self.payload
|
||||
|
||||
def __str__(self):
|
||||
result = color(self.name, 'yellow')
|
||||
if fields := getattr(self, 'fields', None):
|
||||
result += ':\n' + HCI_Object.format_fields(self.__dict__, fields, ' ')
|
||||
else:
|
||||
if len(self.pdu) > 1:
|
||||
result += f': {self.pdu.hex()}'
|
||||
if self.payload:
|
||||
result += f': {self.payload.hex()}'
|
||||
return result
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass(
|
||||
[
|
||||
('request_opcode_in_error', {'size': 1, 'mapper': ATT_PDU.pdu_name}),
|
||||
('attribute_handle_in_error', HANDLE_FIELD_SPEC),
|
||||
('error_code', {'size': 1, 'mapper': ATT_PDU.error_name}),
|
||||
]
|
||||
)
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Error_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.1.1 Error Response
|
||||
'''
|
||||
|
||||
request_opcode_in_error: int = dataclasses.field(metadata=Opcode.type_metadata(1))
|
||||
attribute_handle_in_error: int = dataclasses.field(
|
||||
metadata=hci.metadata(HANDLE_FIELD_SPEC)
|
||||
)
|
||||
error_code: int = dataclasses.field(metadata=ErrorCode.type_metadata(1))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('client_rx_mtu', 2)])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Exchange_MTU_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.2.1 Exchange MTU Request
|
||||
'''
|
||||
|
||||
client_rx_mtu: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('server_rx_mtu', 2)])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Exchange_MTU_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.2.2 Exchange MTU Response
|
||||
'''
|
||||
|
||||
server_rx_mtu: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass(
|
||||
[('starting_handle', HANDLE_FIELD_SPEC), ('ending_handle', HANDLE_FIELD_SPEC)]
|
||||
)
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Find_Information_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.3.1 Find Information Request
|
||||
'''
|
||||
|
||||
starting_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
ending_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('format', 1), ('information_data', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Find_Information_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.3.2 Find Information Response
|
||||
'''
|
||||
|
||||
def parse_information_data(self):
|
||||
format: int = dataclasses.field(metadata=hci.metadata(1))
|
||||
information_data: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
information: list[tuple[int, bytes]] = dataclasses.field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.information = []
|
||||
offset = 0
|
||||
uuid_size = 2 if self.format == 1 else 16
|
||||
@@ -379,14 +355,6 @@ class ATT_Find_Information_Response(ATT_PDU):
|
||||
self.information.append((handle, uuid))
|
||||
offset += 2 + uuid_size
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.parse_information_data()
|
||||
|
||||
def init_from_bytes(self, pdu, offset):
|
||||
super().init_from_bytes(pdu, offset)
|
||||
self.parse_information_data()
|
||||
|
||||
def __str__(self):
|
||||
result = color(self.name, 'yellow')
|
||||
result += ':\n' + HCI_Object.format_fields(
|
||||
@@ -408,28 +376,31 @@ class ATT_Find_Information_Response(ATT_PDU):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass(
|
||||
[
|
||||
('starting_handle', HANDLE_FIELD_SPEC),
|
||||
('ending_handle', HANDLE_FIELD_SPEC),
|
||||
('attribute_type', UUID_2_FIELD_SPEC),
|
||||
('attribute_value', '*'),
|
||||
]
|
||||
)
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Find_By_Type_Value_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.3.3 Find By Type Value Request
|
||||
'''
|
||||
|
||||
starting_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
ending_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
attribute_type: UUID = dataclasses.field(metadata=hci.metadata(UUID.parse_uuid_2))
|
||||
attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('handles_information_list', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Find_By_Type_Value_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.3.4 Find By Type Value Response
|
||||
'''
|
||||
|
||||
def parse_handles_information_list(self):
|
||||
handles_information_list: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
handles_information: list[tuple[int, int]] = dataclasses.field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.handles_information = []
|
||||
offset = 0
|
||||
while offset + 4 <= len(self.handles_information_list):
|
||||
@@ -439,14 +410,6 @@ class ATT_Find_By_Type_Value_Response(ATT_PDU):
|
||||
self.handles_information.append((found_attribute_handle, group_end_handle))
|
||||
offset += 4
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.parse_handles_information_list()
|
||||
|
||||
def init_from_bytes(self, pdu, offset):
|
||||
super().init_from_bytes(pdu, offset)
|
||||
self.parse_handles_information_list()
|
||||
|
||||
def __str__(self):
|
||||
result = color(self.name, 'yellow')
|
||||
result += ':\n' + HCI_Object.format_fields(
|
||||
@@ -470,27 +433,31 @@ class ATT_Find_By_Type_Value_Response(ATT_PDU):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass(
|
||||
[
|
||||
('starting_handle', HANDLE_FIELD_SPEC),
|
||||
('ending_handle', HANDLE_FIELD_SPEC),
|
||||
('attribute_type', UUID_2_16_FIELD_SPEC),
|
||||
]
|
||||
)
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_By_Type_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.1 Read By Type Request
|
||||
'''
|
||||
|
||||
starting_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
ending_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
attribute_type: UUID = dataclasses.field(metadata=hci.metadata(UUID.parse_uuid))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('length', 1), ('attribute_data_list', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_By_Type_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.2 Read By Type Response
|
||||
'''
|
||||
|
||||
def parse_attribute_data_list(self):
|
||||
length: int = dataclasses.field(metadata=hci.metadata(1))
|
||||
attribute_data_list: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
attributes: list[tuple[int, bytes]] = dataclasses.field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.attributes = []
|
||||
offset = 0
|
||||
while self.length != 0 and offset + self.length <= len(
|
||||
@@ -505,14 +472,6 @@ class ATT_Read_By_Type_Response(ATT_PDU):
|
||||
self.attributes.append((attribute_handle, attribute_value))
|
||||
offset += self.length
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.parse_attribute_data_list()
|
||||
|
||||
def init_from_bytes(self, pdu, offset):
|
||||
super().init_from_bytes(pdu, offset)
|
||||
self.parse_attribute_data_list()
|
||||
|
||||
def __str__(self):
|
||||
result = color(self.name, 'yellow')
|
||||
result += ':\n' + HCI_Object.format_fields(
|
||||
@@ -534,75 +493,100 @@ class ATT_Read_By_Type_Response(ATT_PDU):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC)])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.3 Read Request
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('attribute_value', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.4 Read Response
|
||||
'''
|
||||
|
||||
attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('value_offset', 2)])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_Blob_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.5 Read Blob Request
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
value_offset: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('part_attribute_value', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_Blob_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.6 Read Blob Response
|
||||
'''
|
||||
|
||||
part_attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('set_of_handles', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_Multiple_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.7 Read Multiple Request
|
||||
'''
|
||||
|
||||
set_of_handles: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('set_of_values', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_Multiple_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.8 Read Multiple Response
|
||||
'''
|
||||
|
||||
set_of_values: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass(
|
||||
[
|
||||
('starting_handle', HANDLE_FIELD_SPEC),
|
||||
('ending_handle', HANDLE_FIELD_SPEC),
|
||||
('attribute_group_type', UUID_2_16_FIELD_SPEC),
|
||||
]
|
||||
)
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_By_Group_Type_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.9 Read by Group Type Request
|
||||
'''
|
||||
|
||||
starting_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
ending_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
attribute_group_type: UUID = dataclasses.field(
|
||||
metadata=hci.metadata(UUID.parse_uuid)
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('length', 1), ('attribute_data_list', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Read_By_Group_Type_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.4.10 Read by Group Type Response
|
||||
'''
|
||||
|
||||
def parse_attribute_data_list(self):
|
||||
length: int = dataclasses.field(metadata=hci.metadata(1))
|
||||
attribute_data_list: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
attributes: list[tuple[int, int, bytes]] = dataclasses.field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.attributes = []
|
||||
offset = 0
|
||||
while self.length != 0 and offset + self.length <= len(
|
||||
@@ -619,14 +603,6 @@ class ATT_Read_By_Group_Type_Response(ATT_PDU):
|
||||
)
|
||||
offset += self.length
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.parse_attribute_data_list()
|
||||
|
||||
def init_from_bytes(self, pdu, offset):
|
||||
super().init_from_bytes(pdu, offset)
|
||||
self.parse_attribute_data_list()
|
||||
|
||||
def __str__(self):
|
||||
result = color(self.name, 'yellow')
|
||||
result += ':\n' + HCI_Object.format_fields(
|
||||
@@ -651,15 +627,20 @@ class ATT_Read_By_Group_Type_Response(ATT_PDU):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('attribute_value', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Write_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.5.1 Write Request
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Write_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.5.2 Write Response
|
||||
@@ -667,65 +648,70 @@ class ATT_Write_Response(ATT_PDU):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('attribute_value', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Write_Command(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.5.3 Write Command
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass(
|
||||
[
|
||||
('attribute_handle', HANDLE_FIELD_SPEC),
|
||||
('attribute_value', '*'),
|
||||
# ('authentication_signature', 'TODO')
|
||||
]
|
||||
)
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Signed_Write_Command(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.5.4 Signed Write Command
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
# TODO: authentication_signature
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass(
|
||||
[
|
||||
('attribute_handle', HANDLE_FIELD_SPEC),
|
||||
('value_offset', 2),
|
||||
('part_attribute_value', '*'),
|
||||
]
|
||||
)
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Prepare_Write_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.6.1 Prepare Write Request
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
value_offset: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
part_attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass(
|
||||
[
|
||||
('attribute_handle', HANDLE_FIELD_SPEC),
|
||||
('value_offset', 2),
|
||||
('part_attribute_value', '*'),
|
||||
]
|
||||
)
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Prepare_Write_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.6.2 Prepare Write Response
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
value_offset: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
part_attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([("flags", 1)])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Execute_Write_Request(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.6.3 Execute Write Request
|
||||
'''
|
||||
|
||||
flags: int = dataclasses.field(metadata=hci.metadata(1))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Execute_Write_Response(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.6.4 Execute Write Response
|
||||
@@ -733,23 +719,32 @@ class ATT_Execute_Write_Response(ATT_PDU):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('attribute_value', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Handle_Value_Notification(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.7.1 Handle Value Notification
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('attribute_value', '*')])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Handle_Value_Indication(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.7.2 Handle Value Indication
|
||||
'''
|
||||
|
||||
attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
|
||||
attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ATT_PDU.subclass([])
|
||||
@ATT_PDU.subclass
|
||||
@dataclasses.dataclass
|
||||
class ATT_Handle_Value_Confirmation(ATT_PDU):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part F - 3.4.7.3 Handle Value Confirmation
|
||||
|
||||
@@ -17,20 +17,16 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import abc
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import enum
|
||||
import logging
|
||||
import pathlib
|
||||
from typing import (
|
||||
AsyncGenerator,
|
||||
BinaryIO,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
import sys
|
||||
import wave
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import TYPE_CHECKING, AsyncGenerator, BinaryIO
|
||||
|
||||
from bumble.colors import color
|
||||
|
||||
@@ -230,8 +226,8 @@ class SoundDeviceAudioOutput(ThreadedAudioOutput):
|
||||
|
||||
try:
|
||||
self._stream.write(pcm_samples)
|
||||
except Exception as error:
|
||||
print(f'Sound device error: {error}')
|
||||
except Exception:
|
||||
logger.exception('Sound device error')
|
||||
raise
|
||||
|
||||
def _close(self):
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import struct
|
||||
from typing import Union
|
||||
|
||||
from bumble import core
|
||||
from bumble import utils
|
||||
from bumble import core, utils
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -16,15 +16,14 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
from enum import IntEnum
|
||||
|
||||
import logging
|
||||
import struct
|
||||
from typing import Callable, cast, Optional
|
||||
from enum import IntEnum
|
||||
from typing import Callable, Optional, cast
|
||||
|
||||
from bumble import avc, core, l2cap
|
||||
from bumble.colors import color
|
||||
from bumble import avc
|
||||
from bumble import core
|
||||
from bumble import l2cap
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -137,8 +136,8 @@ class MessageAssembler:
|
||||
self.pid,
|
||||
self.payload,
|
||||
)
|
||||
except Exception as error:
|
||||
logger.exception(color(f"!!! exception in callback: {error}", "red"))
|
||||
except Exception:
|
||||
logger.exception(color("!!! exception in callback", "red"))
|
||||
|
||||
self.reset()
|
||||
|
||||
|
||||
@@ -16,31 +16,25 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
import logging
|
||||
import enum
|
||||
import logging
|
||||
import time
|
||||
import warnings
|
||||
from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
Optional,
|
||||
Callable,
|
||||
AsyncGenerator,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Iterable,
|
||||
Union,
|
||||
Optional,
|
||||
SupportsBytes,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
|
||||
from bumble.core import (
|
||||
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
|
||||
InvalidStateError,
|
||||
ProtocolError,
|
||||
InvalidArgumentError,
|
||||
name_or_number,
|
||||
)
|
||||
from bumble import device, l2cap, sdp, utils
|
||||
from bumble.a2dp import (
|
||||
A2DP_CODEC_TYPE_NAMES,
|
||||
A2DP_MPEG_2_4_AAC_CODEC_TYPE,
|
||||
@@ -51,10 +45,15 @@ from bumble.a2dp import (
|
||||
SbcMediaCodecInformation,
|
||||
VendorSpecificMediaCodecInformation,
|
||||
)
|
||||
from bumble.rtp import MediaPacket
|
||||
from bumble import sdp, device, l2cap, utils
|
||||
from bumble.colors import color
|
||||
|
||||
from bumble.core import (
|
||||
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
|
||||
InvalidArgumentError,
|
||||
InvalidStateError,
|
||||
ProtocolError,
|
||||
name_or_number,
|
||||
)
|
||||
from bumble.rtp import MediaPacket
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -434,8 +433,8 @@ class MessageAssembler:
|
||||
)
|
||||
try:
|
||||
self.callback(self.transaction_label, message)
|
||||
except Exception as error:
|
||||
logger.exception(color(f'!!! exception in callback: {error}', 'red'))
|
||||
except Exception:
|
||||
logger.exception(color('!!! exception in callback', 'red'))
|
||||
|
||||
self.reset()
|
||||
|
||||
@@ -1400,10 +1399,8 @@ class Protocol(utils.EventEmitter):
|
||||
try:
|
||||
response = handler(message)
|
||||
self.send_message(transaction_label, response)
|
||||
except Exception as error:
|
||||
logger.warning(
|
||||
f'{color("!!! Exception in handler:", "red")} {error}'
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(color("!!! Exception in handler:", "red"))
|
||||
else:
|
||||
logger.warning('unhandled command')
|
||||
else:
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import logging
|
||||
import struct
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
AsyncIterator,
|
||||
Awaitable,
|
||||
Callable,
|
||||
cast,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
@@ -33,28 +33,23 @@ from typing import (
|
||||
SupportsBytes,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
|
||||
from bumble import avc, avctp, core, l2cap, utils
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device, Connection
|
||||
from bumble.device import Connection, Device
|
||||
from bumble.sdp import (
|
||||
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
||||
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
||||
SDP_PUBLIC_BROWSE_ROOT,
|
||||
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
||||
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
||||
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_PUBLIC_BROWSE_ROOT,
|
||||
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
||||
SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID,
|
||||
DataElement,
|
||||
ServiceAttribute,
|
||||
)
|
||||
from bumble import utils
|
||||
from bumble import core
|
||||
from bumble import l2cap
|
||||
from bumble import avc
|
||||
from bumble import avctp
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -267,8 +262,8 @@ class PduAssembler:
|
||||
assert self.pdu_id is not None
|
||||
try:
|
||||
self.callback(self.pdu_id, self.parameter)
|
||||
except Exception as error:
|
||||
logger.exception(color(f'!!! exception in callback: {error}', 'red'))
|
||||
except Exception:
|
||||
logger.exception(color('!!! exception in callback', 'red'))
|
||||
|
||||
self.reset()
|
||||
|
||||
@@ -1152,7 +1147,9 @@ class Protocol(utils.EventEmitter):
|
||||
A 'connection' event will be emitted when a connection is made, and a 'start'
|
||||
event will be emitted when the protocol is ready to be used on that connection.
|
||||
"""
|
||||
device.register_l2cap_server(avctp.AVCTP_PSM, self._on_avctp_connection)
|
||||
device.create_l2cap_server(
|
||||
l2cap.ClassicChannelSpec(avctp.AVCTP_PSM), self._on_avctp_connection
|
||||
)
|
||||
|
||||
async def connect(self, connection: Connection) -> None:
|
||||
"""
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble import core
|
||||
|
||||
@@ -17,17 +17,17 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import itertools
|
||||
import logging
|
||||
import random
|
||||
import struct
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
PhysicalTransport,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||
|
||||
from bumble import hci
|
||||
from bumble.colors import color
|
||||
from bumble.core import PhysicalTransport
|
||||
from bumble.hci import (
|
||||
HCI_ACL_DATA_PACKET,
|
||||
HCI_COMMAND_DISALLOWED_ERROR,
|
||||
@@ -38,13 +38,12 @@ from bumble.hci import (
|
||||
HCI_EVENT_PACKET,
|
||||
HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR,
|
||||
HCI_LE_1M_PHY,
|
||||
HCI_SUCCESS,
|
||||
HCI_UNKNOWN_HCI_COMMAND_ERROR,
|
||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
||||
HCI_SUCCESS,
|
||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
HCI_UNKNOWN_HCI_COMMAND_ERROR,
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_0,
|
||||
Address,
|
||||
Role,
|
||||
HCI_AclDataPacket,
|
||||
HCI_AclDataPacketAssembler,
|
||||
HCI_Command_Complete_Event,
|
||||
@@ -53,7 +52,6 @@ from bumble.hci import (
|
||||
HCI_Connection_Request_Event,
|
||||
HCI_Disconnection_Complete_Event,
|
||||
HCI_Encryption_Change_Event,
|
||||
HCI_Synchronous_Connection_Complete_Event,
|
||||
HCI_LE_Advertising_Report_Event,
|
||||
HCI_LE_CIS_Established_Event,
|
||||
HCI_LE_CIS_Request_Event,
|
||||
@@ -62,8 +60,9 @@ from bumble.hci import (
|
||||
HCI_Number_Of_Completed_Packets_Event,
|
||||
HCI_Packet,
|
||||
HCI_Role_Change_Event,
|
||||
HCI_Synchronous_Connection_Complete_Event,
|
||||
Role,
|
||||
)
|
||||
from typing import Optional, Union, Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.link import LocalLink
|
||||
|
||||
814
bumble/core.py
814
bumble/core.py
File diff suppressed because it is too large
Load Diff
@@ -22,12 +22,12 @@ import operator
|
||||
import secrets
|
||||
|
||||
try:
|
||||
from bumble.crypto.cryptography import EccKey, e, aes_cmac
|
||||
from bumble.crypto.cryptography import EccKey, aes_cmac, e
|
||||
except ImportError:
|
||||
logging.getLogger(__name__).debug(
|
||||
"Unable to import cryptography, use built-in primitives."
|
||||
)
|
||||
from bumble.crypto.builtin import EccKey, e, aes_cmac # type: ignore[assignment]
|
||||
from bumble.crypto.builtin import EccKey, aes_cmac, e # type: ignore[assignment]
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import dataclasses
|
||||
import functools
|
||||
import copy
|
||||
import secrets
|
||||
import struct
|
||||
from typing import Optional
|
||||
|
||||
@@ -16,11 +16,9 @@ from __future__ import annotations
|
||||
|
||||
import functools
|
||||
|
||||
from cryptography.hazmat.primitives import ciphers
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
from cryptography.hazmat.primitives import ciphers, cmac
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives import cmac
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms, modes
|
||||
|
||||
|
||||
def e(key: bytes, data: bytes) -> bytes:
|
||||
|
||||
1025
bumble/data_types.py
Normal file
1025
bumble/data_types.py
Normal file
File diff suppressed because it is too large
Load Diff
143
bumble/device.py
143
bumble/device.py
@@ -16,24 +16,22 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import collections
|
||||
from collections.abc import Iterable, Sequence
|
||||
from contextlib import (
|
||||
asynccontextmanager,
|
||||
AsyncExitStack,
|
||||
closing,
|
||||
)
|
||||
import copy
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, IntEnum
|
||||
import functools
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import secrets
|
||||
import sys
|
||||
from collections.abc import Iterable, Sequence
|
||||
from contextlib import AsyncExitStack, asynccontextmanager, closing
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, IntEnum
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
@@ -43,47 +41,46 @@ from typing import (
|
||||
Union,
|
||||
cast,
|
||||
overload,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
from bumble.colors import color
|
||||
from bumble import (
|
||||
core,
|
||||
data_types,
|
||||
gatt_client,
|
||||
gatt_server,
|
||||
hci,
|
||||
l2cap,
|
||||
pairing,
|
||||
sdp,
|
||||
smp,
|
||||
utils,
|
||||
)
|
||||
from bumble.att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
|
||||
from bumble.gatt import Attribute, Characteristic, Descriptor, Service
|
||||
from bumble.host import DataPacketQueue, Host
|
||||
from bumble.profiles.gap import GenericAccessService
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
PhysicalTransport,
|
||||
AdvertisingData,
|
||||
BaseBumbleError,
|
||||
ConnectionParameterUpdateError,
|
||||
CommandTimeoutError,
|
||||
ConnectionParameterUpdateError,
|
||||
ConnectionPHY,
|
||||
InvalidArgumentError,
|
||||
InvalidOperationError,
|
||||
InvalidStateError,
|
||||
NotSupportedError,
|
||||
OutOfResourcesError,
|
||||
PhysicalTransport,
|
||||
UnreachableError,
|
||||
)
|
||||
from bumble import utils
|
||||
from bumble.keys import (
|
||||
KeyStore,
|
||||
PairingKeys,
|
||||
)
|
||||
from bumble import hci
|
||||
from bumble import pairing
|
||||
from bumble import gatt_client
|
||||
from bumble import gatt_server
|
||||
from bumble import smp
|
||||
from bumble import sdp
|
||||
from bumble import l2cap
|
||||
from bumble import core
|
||||
from bumble.gatt import Attribute, Characteristic, Descriptor, Service
|
||||
from bumble.host import DataPacketQueue, Host
|
||||
from bumble.keys import KeyStore, PairingKeys
|
||||
from bumble.profiles import gatt_service
|
||||
from bumble.profiles.gap import GenericAccessService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.transport.common import TransportSource, TransportSink
|
||||
from bumble.transport.common import TransportSink, TransportSource
|
||||
|
||||
_T = TypeVar('_T')
|
||||
|
||||
@@ -1166,14 +1163,11 @@ class BigSync(utils.EventEmitter):
|
||||
logger.error('BIG Sync %d is not active.', self.big_handle)
|
||||
return
|
||||
|
||||
with closing(utils.EventWatcher()) as watcher:
|
||||
terminated = asyncio.Event()
|
||||
watcher.once(self, BigSync.Event.TERMINATION, lambda _: terminated.set())
|
||||
await self.device.send_command(
|
||||
hci.HCI_LE_BIG_Terminate_Sync_Command(big_handle=self.big_handle),
|
||||
check_result=True,
|
||||
)
|
||||
await terminated.wait()
|
||||
await self.device.send_command(
|
||||
hci.HCI_LE_BIG_Terminate_Sync_Command(big_handle=self.big_handle),
|
||||
check_result=True,
|
||||
)
|
||||
self.state = BigSync.State.TERMINATED
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -1886,16 +1880,6 @@ class Connection(utils.CompositeEventEmitter):
|
||||
def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
|
||||
self.device.send_l2cap_pdu(self.handle, cid, pdu)
|
||||
|
||||
@utils.deprecated("Please use create_l2cap_channel()")
|
||||
async def open_l2cap_channel(
|
||||
self,
|
||||
psm,
|
||||
max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
|
||||
mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
|
||||
mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
|
||||
):
|
||||
return await self.device.open_l2cap_channel(self, psm, max_credits, mtu, mps)
|
||||
|
||||
@overload
|
||||
async def create_l2cap_channel(
|
||||
self, spec: l2cap.ClassicChannelSpec
|
||||
@@ -2076,9 +2060,7 @@ class DeviceConfiguration:
|
||||
connectable: bool = True
|
||||
discoverable: bool = True
|
||||
advertising_data: bytes = bytes(
|
||||
AdvertisingData(
|
||||
[(AdvertisingData.COMPLETE_LOCAL_NAME, bytes(DEVICE_DEFAULT_NAME, 'utf-8'))]
|
||||
)
|
||||
AdvertisingData([data_types.CompleteLocalName(DEVICE_DEFAULT_NAME)])
|
||||
)
|
||||
irk: bytes = bytes(16) # This really must be changed for any level of security
|
||||
keystore: Optional[str] = None
|
||||
@@ -2122,9 +2104,7 @@ class DeviceConfiguration:
|
||||
self.advertising_data = bytes.fromhex(advertising_data)
|
||||
elif name is not None:
|
||||
self.advertising_data = bytes(
|
||||
AdvertisingData(
|
||||
[(AdvertisingData.COMPLETE_LOCAL_NAME, bytes(self.name, 'utf-8'))]
|
||||
)
|
||||
AdvertisingData([data_types.CompleteLocalName(self.name)])
|
||||
)
|
||||
|
||||
# Load scan response data
|
||||
@@ -2608,36 +2588,6 @@ class Device(utils.CompositeEventEmitter):
|
||||
None,
|
||||
)
|
||||
|
||||
@utils.deprecated("Please use create_l2cap_server()")
|
||||
def register_l2cap_server(self, psm, server) -> int:
|
||||
return self.l2cap_channel_manager.register_server(psm, server)
|
||||
|
||||
@utils.deprecated("Please use create_l2cap_server()")
|
||||
def register_l2cap_channel_server(
|
||||
self,
|
||||
psm,
|
||||
server,
|
||||
max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
|
||||
mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
|
||||
mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
|
||||
):
|
||||
return self.l2cap_channel_manager.register_le_coc_server(
|
||||
psm, server, max_credits, mtu, mps
|
||||
)
|
||||
|
||||
@utils.deprecated("Please use create_l2cap_channel()")
|
||||
async def open_l2cap_channel(
|
||||
self,
|
||||
connection,
|
||||
psm,
|
||||
max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
|
||||
mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
|
||||
mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
|
||||
):
|
||||
return await self.l2cap_channel_manager.open_le_coc(
|
||||
connection, psm, max_credits, mtu, mps
|
||||
)
|
||||
|
||||
@overload
|
||||
async def create_l2cap_channel(
|
||||
self,
|
||||
@@ -3245,8 +3195,8 @@ class Device(utils.CompositeEventEmitter):
|
||||
else 0
|
||||
)
|
||||
await advertising_set.start(duration=duration)
|
||||
except Exception as error:
|
||||
logger.exception(f'failed to start advertising set: {error}')
|
||||
except Exception:
|
||||
logger.exception('failed to start advertising set')
|
||||
await advertising_set.remove()
|
||||
raise
|
||||
|
||||
@@ -3601,14 +3551,7 @@ class Device(utils.CompositeEventEmitter):
|
||||
# Synthesize an inquiry response if none is set already
|
||||
if self.inquiry_response is None:
|
||||
self.inquiry_response = bytes(
|
||||
AdvertisingData(
|
||||
[
|
||||
(
|
||||
AdvertisingData.COMPLETE_LOCAL_NAME,
|
||||
bytes(self.name, 'utf-8'),
|
||||
)
|
||||
]
|
||||
)
|
||||
AdvertisingData([data_types.CompleteLocalName(self.name)])
|
||||
)
|
||||
|
||||
# Update the controller
|
||||
@@ -4610,8 +4553,8 @@ class Device(utils.CompositeEventEmitter):
|
||||
try:
|
||||
await self.keystore.update(address, keys)
|
||||
await self.refresh_resolving_list()
|
||||
except Exception as error:
|
||||
logger.warning(f'!!! error while storing keys: {error}')
|
||||
except Exception:
|
||||
logger.exception('!!! error while storing keys')
|
||||
else:
|
||||
self.emit(self.EVENT_KEY_STORE_UPDATE)
|
||||
|
||||
@@ -5859,8 +5802,8 @@ class Device(utils.CompositeEventEmitter):
|
||||
)
|
||||
)
|
||||
return
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while confirming: {error}')
|
||||
except Exception:
|
||||
logger.exception('exception while confirming')
|
||||
|
||||
await self.host.send_command(
|
||||
hci.HCI_User_Confirmation_Request_Negative_Reply_Command(
|
||||
@@ -5889,8 +5832,8 @@ class Device(utils.CompositeEventEmitter):
|
||||
)
|
||||
)
|
||||
return
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while asking for pass-key: {error}')
|
||||
except Exception:
|
||||
logger.exception('exception while asking for pass-key')
|
||||
|
||||
await self.host.send_command(
|
||||
hci.HCI_User_Passkey_Request_Negative_Reply_Command(
|
||||
|
||||
@@ -20,12 +20,13 @@ like loading firmware after a cold start.
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import pathlib
|
||||
import platform
|
||||
from typing import Iterable, Optional, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Iterable, Optional
|
||||
|
||||
from bumble.drivers import rtk, intel
|
||||
from bumble.drivers import intel, rtk
|
||||
from bumble.drivers.common import Driver
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -20,6 +20,7 @@ Loosely based on the Fuchsia OS implementation.
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import collections
|
||||
import dataclasses
|
||||
@@ -28,12 +29,10 @@ import os
|
||||
import pathlib
|
||||
import platform
|
||||
import struct
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from bumble import core
|
||||
from bumble import core, hci, utils
|
||||
from bumble.drivers import common
|
||||
from bumble import hci
|
||||
from bumble import utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.host import Host
|
||||
|
||||
@@ -17,10 +17,6 @@ Based on various online bits of information, including the Linux kernel.
|
||||
(see `drivers/bluetooth/btrtl.c`)
|
||||
"""
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from dataclasses import dataclass, field
|
||||
import asyncio
|
||||
import enum
|
||||
import logging
|
||||
@@ -31,9 +27,12 @@ import platform
|
||||
import struct
|
||||
import weakref
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from bumble import core
|
||||
from bumble import hci
|
||||
from bumble import core, hci
|
||||
from bumble.drivers import common
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -489,6 +488,21 @@ class Driver(common.Driver):
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
async def get_loaded_firmware_version(host):
|
||||
response = await host.send_command(HCI_RTK_Read_ROM_Version_Command())
|
||||
|
||||
if response.return_parameters.status != hci.HCI_SUCCESS:
|
||||
return None
|
||||
|
||||
response = await host.send_command(
|
||||
hci.HCI_Read_Local_Version_Information_Command(), check_result=True
|
||||
)
|
||||
return (
|
||||
response.return_parameters.hci_subversion << 16
|
||||
| response.return_parameters.lmp_subversion
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def driver_info_for_host(cls, host):
|
||||
try:
|
||||
@@ -592,7 +606,7 @@ class Driver(common.Driver):
|
||||
)
|
||||
if response.return_parameters.status != hci.HCI_SUCCESS:
|
||||
logger.warning("can't get ROM version")
|
||||
return
|
||||
return None
|
||||
rom_version = response.return_parameters.version
|
||||
logger.debug(f"ROM version before download: {rom_version:04X}")
|
||||
else:
|
||||
@@ -600,13 +614,14 @@ class Driver(common.Driver):
|
||||
|
||||
firmware = Firmware(self.firmware)
|
||||
logger.debug(f"firmware: project_id=0x{firmware.project_id:04X}")
|
||||
logger.debug(f"firmware: version=0x{firmware.version:04X}")
|
||||
for patch in firmware.patches:
|
||||
if patch[0] == rom_version + 1:
|
||||
logger.debug(f"using patch {patch[0]}")
|
||||
break
|
||||
else:
|
||||
logger.warning("no valid patch found for rom version {rom_version}")
|
||||
return
|
||||
return None
|
||||
|
||||
# Append the config if there is one.
|
||||
if self.config:
|
||||
@@ -642,7 +657,9 @@ class Driver(common.Driver):
|
||||
logger.warning("can't get ROM version")
|
||||
else:
|
||||
rom_version = response.return_parameters.version
|
||||
logger.debug(f"ROM version after download: {rom_version:04X}")
|
||||
logger.debug(f"ROM version after download: {rom_version:02X}")
|
||||
|
||||
return firmware.version
|
||||
|
||||
async def download_firmware(self):
|
||||
if self.driver_info.rom == RTK_ROM_LMP_8723A:
|
||||
|
||||
@@ -19,11 +19,11 @@ import logging
|
||||
import struct
|
||||
|
||||
from bumble.gatt import (
|
||||
Service,
|
||||
Characteristic,
|
||||
GATT_GENERIC_ACCESS_SERVICE,
|
||||
GATT_DEVICE_NAME_CHARACTERISTIC,
|
||||
GATT_APPEARANCE_CHARACTERISTIC,
|
||||
GATT_DEVICE_NAME_CHARACTERISTIC,
|
||||
GATT_GENERIC_ACCESS_SERVICE,
|
||||
Characteristic,
|
||||
Service,
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -23,15 +23,16 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import functools
|
||||
import logging
|
||||
import struct
|
||||
from typing import Iterable, Optional, Sequence, TypeVar, Union
|
||||
|
||||
from bumble.colors import color
|
||||
from bumble.core import BaseBumbleError, UUID
|
||||
from bumble.att import Attribute, AttributeValue
|
||||
from bumble.colors import color
|
||||
from bumble.core import UUID, BaseBumbleError
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Typing
|
||||
|
||||
@@ -20,22 +20,14 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import struct
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Generic,
|
||||
Iterable,
|
||||
Literal,
|
||||
Optional,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
import struct
|
||||
from typing import Any, Callable, Generic, Iterable, Literal, Optional, TypeVar
|
||||
|
||||
from bumble import utils
|
||||
from bumble.core import InvalidOperationError
|
||||
from bumble.gatt import Characteristic
|
||||
from bumble.gatt_client import CharacteristicProxy
|
||||
from bumble import utils
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Typing
|
||||
|
||||
@@ -24,60 +24,38 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import struct
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Generic,
|
||||
Iterable,
|
||||
Optional,
|
||||
Union,
|
||||
TypeVar,
|
||||
TYPE_CHECKING,
|
||||
Union,
|
||||
)
|
||||
|
||||
|
||||
from bumble import att, core, utils
|
||||
from bumble.colors import color
|
||||
from bumble.hci import HCI_Constant
|
||||
from bumble.att import (
|
||||
ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
ATT_ATTRIBUTE_NOT_LONG_ERROR,
|
||||
ATT_CID,
|
||||
ATT_DEFAULT_MTU,
|
||||
ATT_ERROR_RESPONSE,
|
||||
ATT_INVALID_OFFSET_ERROR,
|
||||
ATT_PDU,
|
||||
ATT_RESPONSES,
|
||||
ATT_Exchange_MTU_Request,
|
||||
ATT_Find_By_Type_Value_Request,
|
||||
ATT_Find_Information_Request,
|
||||
ATT_Handle_Value_Confirmation,
|
||||
ATT_Read_Blob_Request,
|
||||
ATT_Read_By_Group_Type_Request,
|
||||
ATT_Read_By_Type_Request,
|
||||
ATT_Read_Request,
|
||||
ATT_Write_Command,
|
||||
ATT_Write_Request,
|
||||
ATT_Error,
|
||||
)
|
||||
from bumble import utils
|
||||
from bumble import core
|
||||
from bumble.core import UUID, InvalidStateError
|
||||
from bumble.gatt import (
|
||||
GATT_CHARACTERISTIC_ATTRIBUTE_TYPE,
|
||||
GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
|
||||
GATT_INCLUDE_ATTRIBUTE_TYPE,
|
||||
GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE,
|
||||
GATT_REQUEST_TIMEOUT,
|
||||
GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
|
||||
GATT_INCLUDE_ATTRIBUTE_TYPE,
|
||||
Characteristic,
|
||||
ClientCharacteristicConfigurationBits,
|
||||
InvalidServiceError,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.hci import HCI_Constant
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Typing
|
||||
@@ -291,8 +269,8 @@ class Client:
|
||||
indication_subscribers: dict[
|
||||
int, set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
|
||||
]
|
||||
pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
|
||||
pending_request: Optional[ATT_PDU]
|
||||
pending_response: Optional[asyncio.futures.Future[att.ATT_PDU]]
|
||||
pending_request: Optional[att.ATT_PDU]
|
||||
|
||||
def __init__(self, connection: Connection) -> None:
|
||||
self.connection = connection
|
||||
@@ -308,15 +286,15 @@ class Client:
|
||||
connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
|
||||
|
||||
def send_gatt_pdu(self, pdu: bytes) -> None:
|
||||
self.connection.send_l2cap_pdu(ATT_CID, pdu)
|
||||
self.connection.send_l2cap_pdu(att.ATT_CID, pdu)
|
||||
|
||||
async def send_command(self, command: ATT_PDU) -> None:
|
||||
async def send_command(self, command: att.ATT_PDU) -> None:
|
||||
logger.debug(
|
||||
f'GATT Command from client: [0x{self.connection.handle:04X}] {command}'
|
||||
)
|
||||
self.send_gatt_pdu(bytes(command))
|
||||
|
||||
async def send_request(self, request: ATT_PDU):
|
||||
async def send_request(self, request: att.ATT_PDU):
|
||||
logger.debug(
|
||||
f'GATT Request from client: [0x{self.connection.handle:04X}] {request}'
|
||||
)
|
||||
@@ -345,7 +323,9 @@ class Client:
|
||||
|
||||
return response
|
||||
|
||||
def send_confirmation(self, confirmation: ATT_Handle_Value_Confirmation) -> None:
|
||||
def send_confirmation(
|
||||
self, confirmation: att.ATT_Handle_Value_Confirmation
|
||||
) -> None:
|
||||
logger.debug(
|
||||
f'GATT Confirmation from client: [0x{self.connection.handle:04X}] '
|
||||
f'{confirmation}'
|
||||
@@ -354,8 +334,8 @@ class Client:
|
||||
|
||||
async def request_mtu(self, mtu: int) -> int:
|
||||
# Check the range
|
||||
if mtu < ATT_DEFAULT_MTU:
|
||||
raise core.InvalidArgumentError(f'MTU must be >= {ATT_DEFAULT_MTU}')
|
||||
if mtu < att.ATT_DEFAULT_MTU:
|
||||
raise core.InvalidArgumentError(f'MTU must be >= {att.ATT_DEFAULT_MTU}')
|
||||
if mtu > 0xFFFF:
|
||||
raise core.InvalidArgumentError('MTU must be <= 0xFFFF')
|
||||
|
||||
@@ -365,9 +345,11 @@ class Client:
|
||||
|
||||
# Send the request
|
||||
self.mtu_exchange_done = True
|
||||
response = await self.send_request(ATT_Exchange_MTU_Request(client_rx_mtu=mtu))
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
raise ATT_Error(error_code=response.error_code, message=response)
|
||||
response = await self.send_request(
|
||||
att.ATT_Exchange_MTU_Request(client_rx_mtu=mtu)
|
||||
)
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
raise att.ATT_Error(error_code=response.error_code, message=response)
|
||||
|
||||
# Compute the final MTU
|
||||
self.connection.att_mtu = min(mtu, response.server_rx_mtu)
|
||||
@@ -432,7 +414,7 @@ class Client:
|
||||
services = []
|
||||
while starting_handle < 0xFFFF:
|
||||
response = await self.send_request(
|
||||
ATT_Read_By_Group_Type_Request(
|
||||
att.ATT_Read_By_Group_Type_Request(
|
||||
starting_handle=starting_handle,
|
||||
ending_handle=0xFFFF,
|
||||
attribute_group_type=GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE,
|
||||
@@ -443,14 +425,14 @@ class Client:
|
||||
return []
|
||||
|
||||
# Check if we reached the end of the iteration
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
if response.error_code != ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
if response.error_code != att.ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
# Unexpected end
|
||||
logger.warning(
|
||||
'!!! unexpected error while discovering services: '
|
||||
f'{HCI_Constant.error_name(response.error_code)}'
|
||||
)
|
||||
raise ATT_Error(
|
||||
raise att.ATT_Error(
|
||||
error_code=response.error_code,
|
||||
message='Unexpected error while discovering services',
|
||||
)
|
||||
@@ -509,7 +491,7 @@ class Client:
|
||||
services = []
|
||||
while starting_handle < 0xFFFF:
|
||||
response = await self.send_request(
|
||||
ATT_Find_By_Type_Value_Request(
|
||||
att.ATT_Find_By_Type_Value_Request(
|
||||
starting_handle=starting_handle,
|
||||
ending_handle=0xFFFF,
|
||||
attribute_type=GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE,
|
||||
@@ -521,8 +503,8 @@ class Client:
|
||||
return []
|
||||
|
||||
# Check if we reached the end of the iteration
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
if response.error_code != ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
if response.error_code != att.ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
# Unexpected end
|
||||
logger.warning(
|
||||
'!!! unexpected error while discovering services: '
|
||||
@@ -578,7 +560,7 @@ class Client:
|
||||
included_services: list[ServiceProxy] = []
|
||||
while starting_handle <= ending_handle:
|
||||
response = await self.send_request(
|
||||
ATT_Read_By_Type_Request(
|
||||
att.ATT_Read_By_Type_Request(
|
||||
starting_handle=starting_handle,
|
||||
ending_handle=ending_handle,
|
||||
attribute_type=GATT_INCLUDE_ATTRIBUTE_TYPE,
|
||||
@@ -589,14 +571,14 @@ class Client:
|
||||
return []
|
||||
|
||||
# Check if we reached the end of the iteration
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
if response.error_code != ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
if response.error_code != att.ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
# Unexpected end
|
||||
logger.warning(
|
||||
'!!! unexpected error while discovering included services: '
|
||||
f'{HCI_Constant.error_name(response.error_code)}'
|
||||
)
|
||||
raise ATT_Error(
|
||||
raise att.ATT_Error(
|
||||
error_code=response.error_code,
|
||||
message='Unexpected error while discovering included services',
|
||||
)
|
||||
@@ -652,7 +634,7 @@ class Client:
|
||||
characteristics: list[CharacteristicProxy[bytes]] = []
|
||||
while starting_handle <= ending_handle:
|
||||
response = await self.send_request(
|
||||
ATT_Read_By_Type_Request(
|
||||
att.ATT_Read_By_Type_Request(
|
||||
starting_handle=starting_handle,
|
||||
ending_handle=ending_handle,
|
||||
attribute_type=GATT_CHARACTERISTIC_ATTRIBUTE_TYPE,
|
||||
@@ -663,14 +645,14 @@ class Client:
|
||||
return []
|
||||
|
||||
# Check if we reached the end of the iteration
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
if response.error_code != ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
if response.error_code != att.ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
# Unexpected end
|
||||
logger.warning(
|
||||
'!!! unexpected error while discovering characteristics: '
|
||||
f'{HCI_Constant.error_name(response.error_code)}'
|
||||
)
|
||||
raise ATT_Error(
|
||||
raise att.ATT_Error(
|
||||
error_code=response.error_code,
|
||||
message='Unexpected error while discovering characteristics',
|
||||
)
|
||||
@@ -736,7 +718,7 @@ class Client:
|
||||
descriptors: list[DescriptorProxy] = []
|
||||
while starting_handle <= ending_handle:
|
||||
response = await self.send_request(
|
||||
ATT_Find_Information_Request(
|
||||
att.ATT_Find_Information_Request(
|
||||
starting_handle=starting_handle, ending_handle=ending_handle
|
||||
)
|
||||
)
|
||||
@@ -745,8 +727,8 @@ class Client:
|
||||
return []
|
||||
|
||||
# Check if we reached the end of the iteration
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
if response.error_code != ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
if response.error_code != att.ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
# Unexpected end
|
||||
logger.warning(
|
||||
'!!! unexpected error while discovering descriptors: '
|
||||
@@ -791,7 +773,7 @@ class Client:
|
||||
attributes = []
|
||||
while True:
|
||||
response = await self.send_request(
|
||||
ATT_Find_Information_Request(
|
||||
att.ATT_Find_Information_Request(
|
||||
starting_handle=starting_handle, ending_handle=ending_handle
|
||||
)
|
||||
)
|
||||
@@ -799,8 +781,8 @@ class Client:
|
||||
return []
|
||||
|
||||
# Check if we reached the end of the iteration
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
if response.error_code != ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
if response.error_code != att.ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
# Unexpected end
|
||||
logger.warning(
|
||||
'!!! unexpected error while discovering attributes: '
|
||||
@@ -954,12 +936,12 @@ class Client:
|
||||
# Send a request to read
|
||||
attribute_handle = attribute if isinstance(attribute, int) else attribute.handle
|
||||
response = await self.send_request(
|
||||
ATT_Read_Request(attribute_handle=attribute_handle)
|
||||
att.ATT_Read_Request(attribute_handle=attribute_handle)
|
||||
)
|
||||
if response is None:
|
||||
raise TimeoutError('read timeout')
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
raise ATT_Error(error_code=response.error_code, message=response)
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
raise att.ATT_Error(error_code=response.error_code, message=response)
|
||||
|
||||
# If the value is the max size for the MTU, try to read more unless the caller
|
||||
# specifically asked not to do that
|
||||
@@ -969,19 +951,21 @@ class Client:
|
||||
offset = len(attribute_value)
|
||||
while True:
|
||||
response = await self.send_request(
|
||||
ATT_Read_Blob_Request(
|
||||
att.ATT_Read_Blob_Request(
|
||||
attribute_handle=attribute_handle, value_offset=offset
|
||||
)
|
||||
)
|
||||
if response is None:
|
||||
raise TimeoutError('read timeout')
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
if response.error_code in (
|
||||
ATT_ATTRIBUTE_NOT_LONG_ERROR,
|
||||
ATT_INVALID_OFFSET_ERROR,
|
||||
att.ATT_ATTRIBUTE_NOT_LONG_ERROR,
|
||||
att.ATT_INVALID_OFFSET_ERROR,
|
||||
):
|
||||
break
|
||||
raise ATT_Error(error_code=response.error_code, message=response)
|
||||
raise att.ATT_Error(
|
||||
error_code=response.error_code, message=response
|
||||
)
|
||||
|
||||
part = response.part_attribute_value
|
||||
attribute_value += part
|
||||
@@ -1012,7 +996,7 @@ class Client:
|
||||
characteristics_values = []
|
||||
while starting_handle <= ending_handle:
|
||||
response = await self.send_request(
|
||||
ATT_Read_By_Type_Request(
|
||||
att.ATT_Read_By_Type_Request(
|
||||
starting_handle=starting_handle,
|
||||
ending_handle=ending_handle,
|
||||
attribute_type=uuid,
|
||||
@@ -1023,8 +1007,8 @@ class Client:
|
||||
return []
|
||||
|
||||
# Check if we reached the end of the iteration
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
if response.error_code != ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
if response.error_code != att.ATT_ATTRIBUTE_NOT_FOUND_ERROR:
|
||||
# Unexpected end
|
||||
logger.warning(
|
||||
'!!! unexpected error while reading characteristics: '
|
||||
@@ -1069,15 +1053,15 @@ class Client:
|
||||
attribute_handle = attribute if isinstance(attribute, int) else attribute.handle
|
||||
if with_response:
|
||||
response = await self.send_request(
|
||||
ATT_Write_Request(
|
||||
att.ATT_Write_Request(
|
||||
attribute_handle=attribute_handle, attribute_value=value
|
||||
)
|
||||
)
|
||||
if response.op_code == ATT_ERROR_RESPONSE:
|
||||
raise ATT_Error(error_code=response.error_code, message=response)
|
||||
if response.op_code == att.Opcode.ATT_ERROR_RESPONSE:
|
||||
raise att.ATT_Error(error_code=response.error_code, message=response)
|
||||
else:
|
||||
await self.send_command(
|
||||
ATT_Write_Command(
|
||||
att.ATT_Write_Command(
|
||||
attribute_handle=attribute_handle, attribute_value=value
|
||||
)
|
||||
)
|
||||
@@ -1086,11 +1070,11 @@ class Client:
|
||||
if self.pending_response and not self.pending_response.done():
|
||||
self.pending_response.cancel()
|
||||
|
||||
def on_gatt_pdu(self, att_pdu: ATT_PDU) -> None:
|
||||
def on_gatt_pdu(self, att_pdu: att.ATT_PDU) -> None:
|
||||
logger.debug(
|
||||
f'GATT Response to client: [0x{self.connection.handle:04X}] {att_pdu}'
|
||||
)
|
||||
if att_pdu.op_code in ATT_RESPONSES:
|
||||
if att_pdu.op_code in att.ATT_RESPONSES:
|
||||
if self.pending_request is None:
|
||||
# Not expected!
|
||||
logger.warning('!!! unexpected response, there is no pending request')
|
||||
@@ -1098,7 +1082,7 @@ class Client:
|
||||
|
||||
# The response should match the pending request unless it is
|
||||
# an error response
|
||||
if att_pdu.op_code != ATT_ERROR_RESPONSE:
|
||||
if att_pdu.op_code != att.Opcode.ATT_ERROR_RESPONSE:
|
||||
expected_response_name = self.pending_request.name.replace(
|
||||
'_REQUEST', '_RESPONSE'
|
||||
)
|
||||
@@ -1126,7 +1110,9 @@ class Client:
|
||||
+ str(att_pdu)
|
||||
)
|
||||
|
||||
def on_att_handle_value_notification(self, notification):
|
||||
def on_att_handle_value_notification(
|
||||
self, notification: att.ATT_Handle_Value_Notification
|
||||
):
|
||||
# Call all subscribers
|
||||
subscribers = self.notification_subscribers.get(
|
||||
notification.attribute_handle, set()
|
||||
@@ -1141,7 +1127,9 @@ class Client:
|
||||
else:
|
||||
subscriber.emit(subscriber.EVENT_UPDATE, notification.attribute_value)
|
||||
|
||||
def on_att_handle_value_indication(self, indication):
|
||||
def on_att_handle_value_indication(
|
||||
self, indication: att.ATT_Handle_Value_Indication
|
||||
):
|
||||
# Call all subscribers
|
||||
subscribers = self.indication_subscribers.get(
|
||||
indication.attribute_handle, set()
|
||||
@@ -1157,7 +1145,7 @@ class Client:
|
||||
subscriber.emit(subscriber.EVENT_UPDATE, indication.attribute_value)
|
||||
|
||||
# Confirm that we received the indication
|
||||
self.send_confirmation(ATT_Handle_Value_Confirmation())
|
||||
self.send_confirmation(att.ATT_Handle_Value_Confirmation())
|
||||
|
||||
def cache_value(self, attribute_handle: int, value: bytes) -> None:
|
||||
self.cached_values[attribute_handle] = (
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# GATT - Generic Attribute Profile
|
||||
# GATT - Generic att.Attribute Profile
|
||||
# Server
|
||||
#
|
||||
# See Bluetooth spec @ Vol 3, Part G
|
||||
@@ -24,46 +24,16 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
import struct
|
||||
from typing import (
|
||||
Iterable,
|
||||
Optional,
|
||||
TypeVar,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Iterable, Optional, TypeVar
|
||||
|
||||
from bumble import att, utils
|
||||
from bumble.colors import color
|
||||
from bumble.core import UUID
|
||||
from bumble.att import (
|
||||
ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
ATT_ATTRIBUTE_NOT_LONG_ERROR,
|
||||
ATT_CID,
|
||||
ATT_DEFAULT_MTU,
|
||||
ATT_INVALID_ATTRIBUTE_LENGTH_ERROR,
|
||||
ATT_INVALID_HANDLE_ERROR,
|
||||
ATT_INVALID_OFFSET_ERROR,
|
||||
ATT_REQUEST_NOT_SUPPORTED_ERROR,
|
||||
ATT_REQUESTS,
|
||||
ATT_PDU,
|
||||
ATT_UNLIKELY_ERROR_ERROR,
|
||||
ATT_UNSUPPORTED_GROUP_TYPE_ERROR,
|
||||
ATT_Error,
|
||||
ATT_Error_Response,
|
||||
ATT_Exchange_MTU_Response,
|
||||
ATT_Find_By_Type_Value_Response,
|
||||
ATT_Find_Information_Response,
|
||||
ATT_Handle_Value_Indication,
|
||||
ATT_Handle_Value_Notification,
|
||||
ATT_Read_Blob_Response,
|
||||
ATT_Read_By_Group_Type_Response,
|
||||
ATT_Read_By_Type_Response,
|
||||
ATT_Read_Response,
|
||||
ATT_Write_Response,
|
||||
Attribute,
|
||||
)
|
||||
from bumble.gatt import (
|
||||
GATT_CHARACTERISTIC_ATTRIBUTE_TYPE,
|
||||
GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
|
||||
@@ -74,14 +44,13 @@ from bumble.gatt import (
|
||||
Characteristic,
|
||||
CharacteristicDeclaration,
|
||||
CharacteristicValue,
|
||||
IncludedServiceDeclaration,
|
||||
Descriptor,
|
||||
IncludedServiceDeclaration,
|
||||
Service,
|
||||
)
|
||||
from bumble import utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.device import Device, Connection
|
||||
from bumble.device import Connection, Device
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -99,9 +68,9 @@ GATT_SERVER_DEFAULT_MAX_MTU = 517
|
||||
# GATT Server
|
||||
# -----------------------------------------------------------------------------
|
||||
class Server(utils.EventEmitter):
|
||||
attributes: list[Attribute]
|
||||
attributes: list[att.Attribute]
|
||||
services: list[Service]
|
||||
attributes_by_handle: dict[int, Attribute]
|
||||
attributes_by_handle: dict[int, att.Attribute]
|
||||
subscribers: dict[int, dict[int, bytes]]
|
||||
indication_semaphores: defaultdict[int, asyncio.Semaphore]
|
||||
pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
|
||||
@@ -112,7 +81,7 @@ class Server(utils.EventEmitter):
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.services = []
|
||||
self.attributes = [] # Attributes, ordered by increasing handle values
|
||||
self.attributes = [] # att.Attributes, ordered by increasing handle values
|
||||
self.attributes_by_handle = {} # Map for fast attribute access by handle
|
||||
self.max_mtu = (
|
||||
GATT_SERVER_DEFAULT_MAX_MTU # The max MTU we're willing to negotiate
|
||||
@@ -127,12 +96,12 @@ class Server(utils.EventEmitter):
|
||||
return "\n".join(map(str, self.attributes))
|
||||
|
||||
def send_gatt_pdu(self, connection_handle: int, pdu: bytes) -> None:
|
||||
self.device.send_l2cap_pdu(connection_handle, ATT_CID, pdu)
|
||||
self.device.send_l2cap_pdu(connection_handle, att.ATT_CID, pdu)
|
||||
|
||||
def next_handle(self) -> int:
|
||||
return 1 + len(self.attributes)
|
||||
|
||||
def get_advertising_service_data(self) -> dict[Attribute, bytes]:
|
||||
def get_advertising_service_data(self) -> dict[att.Attribute, bytes]:
|
||||
return {
|
||||
attribute: data
|
||||
for attribute in self.attributes
|
||||
@@ -140,7 +109,7 @@ class Server(utils.EventEmitter):
|
||||
and (data := attribute.get_advertising_data())
|
||||
}
|
||||
|
||||
def get_attribute(self, handle: int) -> Optional[Attribute]:
|
||||
def get_attribute(self, handle: int) -> Optional[att.Attribute]:
|
||||
attribute = self.attributes_by_handle.get(handle)
|
||||
if attribute:
|
||||
return attribute
|
||||
@@ -231,7 +200,7 @@ class Server(utils.EventEmitter):
|
||||
None,
|
||||
)
|
||||
|
||||
def add_attribute(self, attribute: Attribute) -> None:
|
||||
def add_attribute(self, attribute: att.Attribute) -> None:
|
||||
# Assign a handle to this attribute
|
||||
attribute.handle = self.next_handle()
|
||||
attribute.end_group_handle = (
|
||||
@@ -286,7 +255,7 @@ class Server(utils.EventEmitter):
|
||||
# pylint: disable=line-too-long
|
||||
Descriptor(
|
||||
GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
|
||||
Attribute.READABLE | Attribute.WRITEABLE,
|
||||
att.Attribute.READABLE | att.Attribute.WRITEABLE,
|
||||
CharacteristicValue(
|
||||
read=lambda connection, characteristic=characteristic: self.read_cccd(
|
||||
connection, characteristic
|
||||
@@ -355,7 +324,7 @@ class Server(utils.EventEmitter):
|
||||
indicate_enabled,
|
||||
)
|
||||
|
||||
def send_response(self, connection: Connection, response: ATT_PDU) -> None:
|
||||
def send_response(self, connection: Connection, response: att.ATT_PDU) -> None:
|
||||
logger.debug(
|
||||
f'GATT Response from server: [0x{connection.handle:04X}] {response}'
|
||||
)
|
||||
@@ -364,7 +333,7 @@ class Server(utils.EventEmitter):
|
||||
async def notify_subscriber(
|
||||
self,
|
||||
connection: Connection,
|
||||
attribute: Attribute,
|
||||
attribute: att.Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
@@ -396,7 +365,7 @@ class Server(utils.EventEmitter):
|
||||
value = value[: connection.att_mtu - 3]
|
||||
|
||||
# Notify
|
||||
notification = ATT_Handle_Value_Notification(
|
||||
notification = att.ATT_Handle_Value_Notification(
|
||||
attribute_handle=attribute.handle, attribute_value=value
|
||||
)
|
||||
logger.debug(
|
||||
@@ -407,7 +376,7 @@ class Server(utils.EventEmitter):
|
||||
async def indicate_subscriber(
|
||||
self,
|
||||
connection: Connection,
|
||||
attribute: Attribute,
|
||||
attribute: att.Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
@@ -439,7 +408,7 @@ class Server(utils.EventEmitter):
|
||||
value = value[: connection.att_mtu - 3]
|
||||
|
||||
# Indicate
|
||||
indication = ATT_Handle_Value_Indication(
|
||||
indication = att.ATT_Handle_Value_Indication(
|
||||
attribute_handle=attribute.handle, attribute_value=value
|
||||
)
|
||||
logger.debug(
|
||||
@@ -467,7 +436,7 @@ class Server(utils.EventEmitter):
|
||||
async def _notify_or_indicate_subscribers(
|
||||
self,
|
||||
indicate: bool,
|
||||
attribute: Attribute,
|
||||
attribute: att.Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
@@ -494,7 +463,7 @@ class Server(utils.EventEmitter):
|
||||
|
||||
async def notify_subscribers(
|
||||
self,
|
||||
attribute: Attribute,
|
||||
attribute: att.Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
):
|
||||
@@ -504,7 +473,7 @@ class Server(utils.EventEmitter):
|
||||
|
||||
async def indicate_subscribers(
|
||||
self,
|
||||
attribute: Attribute,
|
||||
attribute: att.Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
):
|
||||
@@ -518,33 +487,33 @@ class Server(utils.EventEmitter):
|
||||
if connection.handle in self.pending_confirmations:
|
||||
del self.pending_confirmations[connection.handle]
|
||||
|
||||
def on_gatt_pdu(self, connection: Connection, att_pdu: ATT_PDU) -> None:
|
||||
def on_gatt_pdu(self, connection: Connection, att_pdu: att.ATT_PDU) -> None:
|
||||
logger.debug(f'GATT Request to server: [0x{connection.handle:04X}] {att_pdu}')
|
||||
handler_name = f'on_{att_pdu.name.lower()}'
|
||||
handler = getattr(self, handler_name, None)
|
||||
if handler is not None:
|
||||
try:
|
||||
handler(connection, att_pdu)
|
||||
except ATT_Error as error:
|
||||
except att.ATT_Error as error:
|
||||
logger.debug(f'normal exception returned by handler: {error}')
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=att_pdu.op_code,
|
||||
attribute_handle_in_error=error.att_handle,
|
||||
error_code=error.error_code,
|
||||
)
|
||||
self.send_response(connection, response)
|
||||
except Exception as error:
|
||||
logger.warning(f'{color("!!! Exception in handler:", "red")} {error}')
|
||||
response = ATT_Error_Response(
|
||||
except Exception:
|
||||
logger.exception(color("!!! Exception in handler:", "red"))
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=att_pdu.op_code,
|
||||
attribute_handle_in_error=0x0000,
|
||||
error_code=ATT_UNLIKELY_ERROR_ERROR,
|
||||
error_code=att.ATT_UNLIKELY_ERROR_ERROR,
|
||||
)
|
||||
self.send_response(connection, response)
|
||||
raise error
|
||||
raise
|
||||
else:
|
||||
# No specific handler registered
|
||||
if att_pdu.op_code in ATT_REQUESTS:
|
||||
if att_pdu.op_code in att.ATT_REQUESTS:
|
||||
# Invoke the generic handler
|
||||
self.on_att_request(connection, att_pdu)
|
||||
else:
|
||||
@@ -560,7 +529,7 @@ class Server(utils.EventEmitter):
|
||||
#######################################################
|
||||
# ATT handlers
|
||||
#######################################################
|
||||
def on_att_request(self, connection: Connection, pdu: ATT_PDU) -> None:
|
||||
def on_att_request(self, connection: Connection, pdu: att.ATT_PDU) -> None:
|
||||
'''
|
||||
Handler for requests without a more specific handler
|
||||
'''
|
||||
@@ -570,23 +539,25 @@ class Server(utils.EventEmitter):
|
||||
)
|
||||
+ str(pdu)
|
||||
)
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=pdu.op_code,
|
||||
attribute_handle_in_error=0x0000,
|
||||
error_code=ATT_REQUEST_NOT_SUPPORTED_ERROR,
|
||||
error_code=att.ATT_REQUEST_NOT_SUPPORTED_ERROR,
|
||||
)
|
||||
self.send_response(connection, response)
|
||||
|
||||
def on_att_exchange_mtu_request(self, connection, request):
|
||||
def on_att_exchange_mtu_request(
|
||||
self, connection: Connection, request: att.ATT_Exchange_MTU_Request
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.2.1 Exchange MTU Request
|
||||
'''
|
||||
self.send_response(
|
||||
connection, ATT_Exchange_MTU_Response(server_rx_mtu=self.max_mtu)
|
||||
connection, att.ATT_Exchange_MTU_Response(server_rx_mtu=self.max_mtu)
|
||||
)
|
||||
|
||||
# Compute the final MTU
|
||||
if request.client_rx_mtu >= ATT_DEFAULT_MTU:
|
||||
if request.client_rx_mtu >= att.ATT_DEFAULT_MTU:
|
||||
mtu = min(self.max_mtu, request.client_rx_mtu)
|
||||
|
||||
# Notify the device
|
||||
@@ -594,11 +565,14 @@ class Server(utils.EventEmitter):
|
||||
else:
|
||||
logger.warning('invalid client_rx_mtu received, MTU not changed')
|
||||
|
||||
def on_att_find_information_request(self, connection, request):
|
||||
def on_att_find_information_request(
|
||||
self, connection: Connection, request: att.ATT_Find_Information_Request
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.3.1 Find Information Request
|
||||
'''
|
||||
|
||||
response: att.ATT_PDU
|
||||
# Check the request parameters
|
||||
if (
|
||||
request.starting_handle == 0
|
||||
@@ -606,17 +580,17 @@ class Server(utils.EventEmitter):
|
||||
):
|
||||
self.send_response(
|
||||
connection,
|
||||
ATT_Error_Response(
|
||||
att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.starting_handle,
|
||||
error_code=ATT_INVALID_HANDLE_ERROR,
|
||||
error_code=att.ATT_INVALID_HANDLE_ERROR,
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
# Build list of returned attributes
|
||||
pdu_space_available = connection.att_mtu - 2
|
||||
attributes = []
|
||||
attributes: list[att.Attribute] = []
|
||||
uuid_size = 0
|
||||
for attribute in (
|
||||
attribute
|
||||
@@ -646,21 +620,23 @@ class Server(utils.EventEmitter):
|
||||
struct.pack('<H', attribute.handle) + attribute.type.to_pdu_bytes()
|
||||
for attribute in attributes
|
||||
]
|
||||
response = ATT_Find_Information_Response(
|
||||
response = att.ATT_Find_Information_Response(
|
||||
format=1 if len(attributes[0].type.to_pdu_bytes()) == 2 else 2,
|
||||
information_data=b''.join(information_data_list),
|
||||
)
|
||||
else:
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.starting_handle,
|
||||
error_code=ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
)
|
||||
|
||||
self.send_response(connection, response)
|
||||
|
||||
@utils.AsyncRunner.run_in_task()
|
||||
async def on_att_find_by_type_value_request(self, connection, request):
|
||||
async def on_att_find_by_type_value_request(
|
||||
self, connection: Connection, request: att.ATT_Find_By_Type_Value_Request
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.3.3 Find By Type Value Request
|
||||
'''
|
||||
@@ -668,6 +644,7 @@ class Server(utils.EventEmitter):
|
||||
# Build list of returned attributes
|
||||
pdu_space_available = connection.att_mtu - 2
|
||||
attributes = []
|
||||
response: att.ATT_PDU
|
||||
async for attribute in (
|
||||
attribute
|
||||
for attribute in self.attributes
|
||||
@@ -700,33 +677,35 @@ class Server(utils.EventEmitter):
|
||||
handles_information_list.append(
|
||||
struct.pack('<HH', attribute.handle, group_end_handle)
|
||||
)
|
||||
response = ATT_Find_By_Type_Value_Response(
|
||||
response = att.ATT_Find_By_Type_Value_Response(
|
||||
handles_information_list=b''.join(handles_information_list)
|
||||
)
|
||||
else:
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.starting_handle,
|
||||
error_code=ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
)
|
||||
|
||||
self.send_response(connection, response)
|
||||
|
||||
@utils.AsyncRunner.run_in_task()
|
||||
async def on_att_read_by_type_request(self, connection, request):
|
||||
async def on_att_read_by_type_request(
|
||||
self, connection: Connection, request: att.ATT_Read_By_Type_Request
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.4.1 Read By Type Request
|
||||
'''
|
||||
|
||||
pdu_space_available = connection.att_mtu - 2
|
||||
|
||||
response = ATT_Error_Response(
|
||||
response: att.ATT_PDU = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.starting_handle,
|
||||
error_code=ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
)
|
||||
|
||||
attributes = []
|
||||
attributes: list[tuple[int, bytes]] = []
|
||||
for attribute in (
|
||||
attribute
|
||||
for attribute in self.attributes
|
||||
@@ -737,11 +716,11 @@ class Server(utils.EventEmitter):
|
||||
):
|
||||
try:
|
||||
attribute_value = await attribute.read_value(connection)
|
||||
except ATT_Error as error:
|
||||
except att.ATT_Error as error:
|
||||
# If the first attribute is unreadable, return an error
|
||||
# Otherwise return attributes up to this point
|
||||
if not attributes:
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=attribute.handle,
|
||||
error_code=error.error_code,
|
||||
@@ -770,7 +749,7 @@ class Server(utils.EventEmitter):
|
||||
attribute_data_list = [
|
||||
struct.pack('<H', handle) + value for handle, value in attributes
|
||||
]
|
||||
response = ATT_Read_By_Type_Response(
|
||||
response = att.ATT_Read_By_Type_Response(
|
||||
length=entry_size, attribute_data_list=b''.join(attribute_data_list)
|
||||
)
|
||||
else:
|
||||
@@ -779,95 +758,104 @@ class Server(utils.EventEmitter):
|
||||
self.send_response(connection, response)
|
||||
|
||||
@utils.AsyncRunner.run_in_task()
|
||||
async def on_att_read_request(self, connection, request):
|
||||
async def on_att_read_request(
|
||||
self, connection: Connection, request: att.ATT_Read_Request
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.4.3 Read Request
|
||||
'''
|
||||
|
||||
response: att.ATT_PDU
|
||||
if attribute := self.get_attribute(request.attribute_handle):
|
||||
try:
|
||||
value = await attribute.read_value(connection)
|
||||
except ATT_Error as error:
|
||||
response = ATT_Error_Response(
|
||||
except att.ATT_Error as error:
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=error.error_code,
|
||||
)
|
||||
else:
|
||||
value_size = min(connection.att_mtu - 1, len(value))
|
||||
response = ATT_Read_Response(attribute_value=value[:value_size])
|
||||
response = att.ATT_Read_Response(attribute_value=value[:value_size])
|
||||
else:
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=ATT_INVALID_HANDLE_ERROR,
|
||||
error_code=att.ATT_INVALID_HANDLE_ERROR,
|
||||
)
|
||||
self.send_response(connection, response)
|
||||
|
||||
@utils.AsyncRunner.run_in_task()
|
||||
async def on_att_read_blob_request(self, connection, request):
|
||||
async def on_att_read_blob_request(
|
||||
self, connection: Connection, request: att.ATT_Read_Blob_Request
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.4.5 Read Blob Request
|
||||
'''
|
||||
|
||||
response: att.ATT_PDU
|
||||
if attribute := self.get_attribute(request.attribute_handle):
|
||||
try:
|
||||
value = await attribute.read_value(connection)
|
||||
except ATT_Error as error:
|
||||
response = ATT_Error_Response(
|
||||
except att.ATT_Error as error:
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=error.error_code,
|
||||
)
|
||||
else:
|
||||
if request.value_offset > len(value):
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=ATT_INVALID_OFFSET_ERROR,
|
||||
error_code=att.ATT_INVALID_OFFSET_ERROR,
|
||||
)
|
||||
elif len(value) <= connection.att_mtu - 1:
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=ATT_ATTRIBUTE_NOT_LONG_ERROR,
|
||||
error_code=att.ATT_ATTRIBUTE_NOT_LONG_ERROR,
|
||||
)
|
||||
else:
|
||||
part_size = min(
|
||||
connection.att_mtu - 1, len(value) - request.value_offset
|
||||
)
|
||||
response = ATT_Read_Blob_Response(
|
||||
response = att.ATT_Read_Blob_Response(
|
||||
part_attribute_value=value[
|
||||
request.value_offset : request.value_offset + part_size
|
||||
]
|
||||
)
|
||||
else:
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=ATT_INVALID_HANDLE_ERROR,
|
||||
error_code=att.ATT_INVALID_HANDLE_ERROR,
|
||||
)
|
||||
self.send_response(connection, response)
|
||||
|
||||
@utils.AsyncRunner.run_in_task()
|
||||
async def on_att_read_by_group_type_request(self, connection, request):
|
||||
async def on_att_read_by_group_type_request(
|
||||
self, connection: Connection, request: att.ATT_Read_By_Group_Type_Request
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.4.9 Read by Group Type Request
|
||||
'''
|
||||
response: att.ATT_PDU
|
||||
if request.attribute_group_type not in (
|
||||
GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE,
|
||||
GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
|
||||
):
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.starting_handle,
|
||||
error_code=ATT_UNSUPPORTED_GROUP_TYPE_ERROR,
|
||||
error_code=att.ATT_UNSUPPORTED_GROUP_TYPE_ERROR,
|
||||
)
|
||||
self.send_response(connection, response)
|
||||
return
|
||||
|
||||
pdu_space_available = connection.att_mtu - 2
|
||||
attributes = []
|
||||
attributes: list[tuple[int, int, bytes]] = []
|
||||
for attribute in (
|
||||
attribute
|
||||
for attribute in self.attributes
|
||||
@@ -904,21 +892,23 @@ class Server(utils.EventEmitter):
|
||||
struct.pack('<HH', handle, end_group_handle) + value
|
||||
for handle, end_group_handle, value in attributes
|
||||
]
|
||||
response = ATT_Read_By_Group_Type_Response(
|
||||
response = att.ATT_Read_By_Group_Type_Response(
|
||||
length=len(attribute_data_list[0]),
|
||||
attribute_data_list=b''.join(attribute_data_list),
|
||||
)
|
||||
else:
|
||||
response = ATT_Error_Response(
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.starting_handle,
|
||||
error_code=ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||
)
|
||||
|
||||
self.send_response(connection, response)
|
||||
|
||||
@utils.AsyncRunner.run_in_task()
|
||||
async def on_att_write_request(self, connection, request):
|
||||
async def on_att_write_request(
|
||||
self, connection: Connection, request: att.ATT_Write_Request
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.5.1 Write Request
|
||||
'''
|
||||
@@ -928,10 +918,10 @@ class Server(utils.EventEmitter):
|
||||
if attribute is None:
|
||||
self.send_response(
|
||||
connection,
|
||||
ATT_Error_Response(
|
||||
att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=ATT_INVALID_HANDLE_ERROR,
|
||||
error_code=att.ATT_INVALID_HANDLE_ERROR,
|
||||
),
|
||||
)
|
||||
return
|
||||
@@ -942,30 +932,33 @@ class Server(utils.EventEmitter):
|
||||
if len(request.attribute_value) > GATT_MAX_ATTRIBUTE_VALUE_SIZE:
|
||||
self.send_response(
|
||||
connection,
|
||||
ATT_Error_Response(
|
||||
att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=ATT_INVALID_ATTRIBUTE_LENGTH_ERROR,
|
||||
error_code=att.ATT_INVALID_ATTRIBUTE_LENGTH_ERROR,
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
response: att.ATT_PDU
|
||||
try:
|
||||
# Accept the value
|
||||
await attribute.write_value(connection, request.attribute_value)
|
||||
except ATT_Error as error:
|
||||
response = ATT_Error_Response(
|
||||
except att.ATT_Error as error:
|
||||
response = att.ATT_Error_Response(
|
||||
request_opcode_in_error=request.op_code,
|
||||
attribute_handle_in_error=request.attribute_handle,
|
||||
error_code=error.error_code,
|
||||
)
|
||||
else:
|
||||
# Done
|
||||
response = ATT_Write_Response()
|
||||
response = att.ATT_Write_Response()
|
||||
self.send_response(connection, response)
|
||||
|
||||
@utils.AsyncRunner.run_in_task()
|
||||
async def on_att_write_command(self, connection, request):
|
||||
async def on_att_write_command(
|
||||
self, connection: Connection, request: att.ATT_Write_Command
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.5.3 Write Command
|
||||
'''
|
||||
@@ -984,18 +977,25 @@ class Server(utils.EventEmitter):
|
||||
# Accept the value
|
||||
try:
|
||||
await attribute.write_value(connection, request.attribute_value)
|
||||
except Exception as error:
|
||||
logger.exception(f'!!! ignoring exception: {error}')
|
||||
except Exception:
|
||||
logger.exception('!!! ignoring exception')
|
||||
|
||||
def on_att_handle_value_confirmation(self, connection, _confirmation):
|
||||
def on_att_handle_value_confirmation(
|
||||
self,
|
||||
connection: Connection,
|
||||
confirmation: att.ATT_Handle_Value_Confirmation,
|
||||
):
|
||||
'''
|
||||
See Bluetooth spec Vol 3, Part F - 3.4.7.3 Handle Value Confirmation
|
||||
'''
|
||||
if self.pending_confirmations[connection.handle] is None:
|
||||
del confirmation # Unused.
|
||||
if (
|
||||
pending_confirmation := self.pending_confirmations[connection.handle]
|
||||
) is None:
|
||||
# Not expected!
|
||||
logger.warning(
|
||||
'!!! unexpected confirmation, there is no pending indication'
|
||||
)
|
||||
return
|
||||
|
||||
self.pending_confirmations[connection.handle].set_result(None)
|
||||
pending_confirmation.set_result(None)
|
||||
|
||||
@@ -16,19 +16,21 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import dataclasses
|
||||
from dataclasses import field
|
||||
import enum
|
||||
import functools
|
||||
import logging
|
||||
import secrets
|
||||
import struct
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Callable, Iterable, Optional, Union, TypeVar, ClassVar, cast
|
||||
from dataclasses import field
|
||||
from typing import Any, Callable, ClassVar, Iterable, Optional, TypeVar, Union, cast
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble import crypto
|
||||
from bumble import crypto, utils
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
DeviceClass,
|
||||
@@ -40,8 +42,6 @@ from bumble.core import (
|
||||
name_or_number,
|
||||
padded_bytes,
|
||||
)
|
||||
from bumble import utils
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -1322,7 +1322,7 @@ class LeFeature(SpecableEnum):
|
||||
MONITORING_ADVERTISERS = 64
|
||||
FRAME_SPACE_UPDATE = 65
|
||||
|
||||
class LeFeatureMask(enum.IntFlag):
|
||||
class LeFeatureMask(utils.CompatibleIntFlag):
|
||||
LE_ENCRYPTION = 1 << LeFeature.LE_ENCRYPTION
|
||||
CONNECTION_PARAMETERS_REQUEST_PROCEDURE = 1 << LeFeature.CONNECTION_PARAMETERS_REQUEST_PROCEDURE
|
||||
EXTENDED_REJECT_INDICATION = 1 << LeFeature.EXTENDED_REJECT_INDICATION
|
||||
@@ -1463,7 +1463,7 @@ class LmpFeature(SpecableEnum):
|
||||
SLOT_AVAILABILITY_MASK = 138
|
||||
TRAIN_NUDGING = 139
|
||||
|
||||
class LmpFeatureMask(enum.IntFlag):
|
||||
class LmpFeatureMask(utils.CompatibleIntFlag):
|
||||
# Page 0 (Legacy LMP features)
|
||||
LMP_3_SLOT_PACKETS = (1 << LmpFeature.LMP_3_SLOT_PACKETS)
|
||||
LMP_5_SLOT_PACKETS = (1 << LmpFeature.LMP_5_SLOT_PACKETS)
|
||||
@@ -2135,6 +2135,7 @@ class Address:
|
||||
if len(address) == 12 + 5:
|
||||
# Form with ':' separators
|
||||
address = address.replace(':', '')
|
||||
|
||||
self.address_bytes = bytes(reversed(bytes.fromhex(address)))
|
||||
|
||||
if len(self.address_bytes) != 6:
|
||||
@@ -5257,7 +5258,7 @@ class HCI_LE_BIG_Terminate_Sync_Command(HCI_Command):
|
||||
|
||||
return_parameters_fields = [
|
||||
('status', STATUS_SPEC),
|
||||
('big_handle', 2),
|
||||
('big_handle', 1),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -17,43 +17,36 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, MutableMapping
|
||||
import datetime
|
||||
from typing import cast, Any, Optional
|
||||
import logging
|
||||
from collections.abc import Callable, MutableMapping
|
||||
from typing import Any, Optional, cast
|
||||
|
||||
from bumble import avc
|
||||
from bumble import avctp
|
||||
from bumble import avdtp
|
||||
from bumble import avrcp
|
||||
from bumble import crypto
|
||||
from bumble import rfcomm
|
||||
from bumble import sdp
|
||||
from bumble.colors import color
|
||||
from bumble import avc, avctp, avdtp, avrcp, crypto, rfcomm, sdp
|
||||
from bumble.att import ATT_CID, ATT_PDU
|
||||
from bumble.smp import SMP_CID, SMP_Command
|
||||
from bumble.colors import color
|
||||
from bumble.core import name_or_number
|
||||
from bumble.l2cap import (
|
||||
CommandCode,
|
||||
L2CAP_PDU,
|
||||
L2CAP_SIGNALING_CID,
|
||||
L2CAP_LE_SIGNALING_CID,
|
||||
L2CAP_Control_Frame,
|
||||
L2CAP_Connection_Request,
|
||||
L2CAP_Connection_Response,
|
||||
)
|
||||
from bumble.hci import (
|
||||
Address,
|
||||
HCI_EVENT_PACKET,
|
||||
HCI_ACL_DATA_PACKET,
|
||||
HCI_DISCONNECTION_COMPLETE_EVENT,
|
||||
HCI_AclDataPacketAssembler,
|
||||
HCI_Packet,
|
||||
HCI_Event,
|
||||
HCI_EVENT_PACKET,
|
||||
Address,
|
||||
HCI_AclDataPacket,
|
||||
HCI_AclDataPacketAssembler,
|
||||
HCI_Disconnection_Complete_Event,
|
||||
HCI_Event,
|
||||
HCI_Packet,
|
||||
)
|
||||
|
||||
from bumble.l2cap import (
|
||||
L2CAP_LE_SIGNALING_CID,
|
||||
L2CAP_PDU,
|
||||
L2CAP_SIGNALING_CID,
|
||||
CommandCode,
|
||||
L2CAP_Connection_Request,
|
||||
L2CAP_Connection_Response,
|
||||
L2CAP_Control_Frame,
|
||||
)
|
||||
from bumble.smp import SMP_CID, SMP_Command
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -17,45 +17,34 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import collections
|
||||
import collections.abc
|
||||
import logging
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import enum
|
||||
import traceback
|
||||
import logging
|
||||
import re
|
||||
from typing import (
|
||||
Union,
|
||||
Any,
|
||||
Optional,
|
||||
ClassVar,
|
||||
Iterable,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Optional, Union
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble import at
|
||||
from bumble import device
|
||||
from bumble import rfcomm
|
||||
from bumble import sdp
|
||||
from bumble import utils
|
||||
from bumble import at, device, rfcomm, sdp, utils
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
ProtocolError,
|
||||
BT_GENERIC_AUDIO_SERVICE,
|
||||
BT_HANDSFREE_SERVICE,
|
||||
BT_HANDSFREE_AUDIO_GATEWAY_SERVICE,
|
||||
BT_HANDSFREE_SERVICE,
|
||||
BT_L2CAP_PROTOCOL_ID,
|
||||
BT_RFCOMM_PROTOCOL_ID,
|
||||
ProtocolError,
|
||||
)
|
||||
from bumble.hci import (
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command,
|
||||
CodingFormat,
|
||||
CodecID,
|
||||
CodingFormat,
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command,
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -16,22 +16,20 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import enum
|
||||
import struct
|
||||
|
||||
import enum
|
||||
import logging
|
||||
import struct
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Optional
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
from bumble import l2cap
|
||||
from bumble import device
|
||||
from bumble import utils
|
||||
from bumble import device, l2cap, utils
|
||||
from bumble.core import InvalidStateError, ProtocolError
|
||||
from bumble.hci import Address
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -219,33 +217,41 @@ class HID(ABC, utils.EventEmitter):
|
||||
self.role = role
|
||||
|
||||
# Register ourselves with the L2CAP channel manager
|
||||
device.register_l2cap_server(HID_CONTROL_PSM, self.on_l2cap_connection)
|
||||
device.register_l2cap_server(HID_INTERRUPT_PSM, self.on_l2cap_connection)
|
||||
device.create_l2cap_server(
|
||||
l2cap.ClassicChannelSpec(HID_CONTROL_PSM), self.on_l2cap_connection
|
||||
)
|
||||
device.create_l2cap_server(
|
||||
l2cap.ClassicChannelSpec(HID_INTERRUPT_PSM), self.on_l2cap_connection
|
||||
)
|
||||
|
||||
device.on(device.EVENT_CONNECTION, self.on_device_connection)
|
||||
|
||||
async def connect_control_channel(self) -> None:
|
||||
if not self.connection:
|
||||
raise InvalidStateError("Connection is not established!")
|
||||
# Create a new L2CAP connection - control channel
|
||||
try:
|
||||
channel = await self.device.l2cap_channel_manager.connect(
|
||||
self.connection, HID_CONTROL_PSM
|
||||
channel = await self.connection.create_l2cap_channel(
|
||||
l2cap.ClassicChannelSpec(HID_CONTROL_PSM)
|
||||
)
|
||||
channel.sink = self.on_ctrl_pdu
|
||||
self.l2cap_ctrl_channel = channel
|
||||
except ProtocolError:
|
||||
logging.exception(f'L2CAP connection failed.')
|
||||
logging.exception('L2CAP connection failed.')
|
||||
raise
|
||||
|
||||
async def connect_interrupt_channel(self) -> None:
|
||||
if not self.connection:
|
||||
raise InvalidStateError("Connection is not established!")
|
||||
# Create a new L2CAP connection - interrupt channel
|
||||
try:
|
||||
channel = await self.device.l2cap_channel_manager.connect(
|
||||
self.connection, HID_INTERRUPT_PSM
|
||||
channel = await self.connection.create_l2cap_channel(
|
||||
l2cap.ClassicChannelSpec(HID_CONTROL_PSM)
|
||||
)
|
||||
channel.sink = self.on_intr_pdu
|
||||
self.l2cap_intr_channel = channel
|
||||
except ProtocolError:
|
||||
logging.exception(f'L2CAP connection failed.')
|
||||
logging.exception('L2CAP connection failed.')
|
||||
raise
|
||||
|
||||
async def disconnect_interrupt_channel(self) -> None:
|
||||
|
||||
@@ -16,33 +16,19 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import collections
|
||||
import dataclasses
|
||||
import logging
|
||||
import struct
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, cast
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Optional,
|
||||
cast,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
|
||||
from bumble import drivers, hci, utils
|
||||
from bumble.colors import color
|
||||
from bumble.core import ConnectionParameters, ConnectionPHY, PhysicalTransport
|
||||
from bumble.l2cap import L2CAP_PDU
|
||||
from bumble.snoop import Snooper
|
||||
from bumble import drivers
|
||||
from bumble import hci
|
||||
from bumble.core import (
|
||||
PhysicalTransport,
|
||||
ConnectionPHY,
|
||||
ConnectionParameters,
|
||||
)
|
||||
from bumble import utils
|
||||
from bumble.transport.common import TransportLostError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -707,11 +693,9 @@ class Host(utils.EventEmitter):
|
||||
raise hci.HCI_Error(status)
|
||||
|
||||
return response
|
||||
except Exception as error:
|
||||
logger.exception(
|
||||
f'{color("!!! Exception while sending command:", "red")} {error}'
|
||||
)
|
||||
raise error
|
||||
except Exception:
|
||||
logger.exception(color("!!! Exception while sending command:", "red"))
|
||||
raise
|
||||
finally:
|
||||
self.pending_command = None
|
||||
self.pending_response = None
|
||||
|
||||
@@ -21,16 +21,18 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Optional, Any
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble.colors import color
|
||||
from bumble import hci
|
||||
from bumble.colors import color
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.device import Device
|
||||
|
||||
195
bumble/l2cap.py
195
bumble/l2cap.py
@@ -16,32 +16,32 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import enum
|
||||
import logging
|
||||
import struct
|
||||
|
||||
from collections import deque
|
||||
from collections.abc import Sequence
|
||||
from typing import (
|
||||
Optional,
|
||||
Callable,
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Union,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Iterable,
|
||||
Optional,
|
||||
SupportsBytes,
|
||||
TypeVar,
|
||||
ClassVar,
|
||||
TYPE_CHECKING,
|
||||
Union,
|
||||
)
|
||||
|
||||
from bumble import utils
|
||||
from bumble import hci
|
||||
from bumble import hci, utils
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
InvalidStateError,
|
||||
InvalidArgumentError,
|
||||
InvalidPacketError,
|
||||
InvalidStateError,
|
||||
OutOfResourcesError,
|
||||
ProtocolError,
|
||||
)
|
||||
@@ -112,6 +112,10 @@ class CommandCode(hci.SpecableEnum):
|
||||
L2CAP_LE_CREDIT_BASED_CONNECTION_REQUEST = 0x14
|
||||
L2CAP_LE_CREDIT_BASED_CONNECTION_RESPONSE = 0x15
|
||||
L2CAP_LE_FLOW_CONTROL_CREDIT = 0x16
|
||||
L2CAP_CREDIT_BASED_CONNECTION_REQUEST = 0x17
|
||||
L2CAP_CREDIT_BASED_CONNECTION_RESPONSE = 0x18
|
||||
L2CAP_CREDIT_BASED_RECONFIGURE_REQUEST = 0x19
|
||||
L2CAP_CREDIT_BASED_RECONFIGURE_RESPONSE = 0x1A
|
||||
|
||||
L2CAP_CONNECTION_PARAMETERS_ACCEPTED_RESULT = 0x0000
|
||||
L2CAP_CONNECTION_PARAMETERS_REJECTED_RESULT = 0x0001
|
||||
@@ -595,6 +599,109 @@ class L2CAP_LE_Flow_Control_Credit(L2CAP_Control_Frame):
|
||||
credits: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@L2CAP_Control_Frame.subclass
|
||||
@dataclasses.dataclass
|
||||
class L2CAP_Credit_Based_Connection_Request(L2CAP_Control_Frame):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part A - 4.25 L2CAP_CREDIT_BASED_CONNECTION_REQ (0x17).
|
||||
'''
|
||||
|
||||
@classmethod
|
||||
def parse_cid_list(cls, data: bytes, offset: int) -> tuple[int, list[int]]:
|
||||
count = (len(data) - offset) // 2
|
||||
return len(data), list(struct.unpack_from("<" + ("H" * count), data, offset))
|
||||
|
||||
@classmethod
|
||||
def serialize_cid_list(cls, cids: Sequence[int]) -> bytes:
|
||||
return b"".join([struct.pack("<H", cid) for cid in cids])
|
||||
|
||||
CID_METADATA: ClassVar[dict[str, Any]] = hci.metadata(
|
||||
{
|
||||
'parser': lambda data, offset: L2CAP_Credit_Based_Connection_Request.parse_cid_list(
|
||||
data, offset
|
||||
),
|
||||
'serializer': lambda value: L2CAP_Credit_Based_Connection_Request.serialize_cid_list(
|
||||
value
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
spsm: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
mtu: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
mps: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
initial_credits: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
source_cid: Sequence[int] = dataclasses.field(metadata=CID_METADATA)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@L2CAP_Control_Frame.subclass
|
||||
@dataclasses.dataclass
|
||||
class L2CAP_Credit_Based_Connection_Response(L2CAP_Control_Frame):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part A - 4.26 L2CAP_CREDIT_BASED_CONNECTION_RSP (0x18).
|
||||
'''
|
||||
|
||||
class Result(hci.SpecableEnum):
|
||||
ALL_CONNECTIONS_SUCCESSFUL = 0x0000
|
||||
ALL_CONNECTIONS_REFUSED_SPSM_NOT_SUPPORTED = 0x0002
|
||||
SOME_CONNECTIONS_REFUSED_INSUFFICIENT_RESOURCES_AVAILABLE = 0x0004
|
||||
ALL_CONNECTIONS_REFUSED_INSUFFICIENT_AUTHENTICATION = 0x0005
|
||||
ALL_CONNECTIONS_REFUSED_INSUFFICIENT_AUTHORIZATION = 0x0006
|
||||
ALL_CONNECTIONS_REFUSED_ENCRYPTION_KEY_SIZE_TOO_SHORT = 0x0007
|
||||
ALL_CONNECTIONS_REFUSED_INSUFFICIENT_ENCRYPTION = 0x0008
|
||||
SOME_CONNECTIONS_REFUSED_INVALID_SOURCE_CID = 0x0009
|
||||
SOME_CONNECTIONS_REFUSED_SOURCE_CID_ALREADY_ALLOCATED = 0x000A
|
||||
ALL_CONNECTIONS_REFUSED_UNACCEPTABLE_PARAMETERS = 0x000B
|
||||
ALL_CONNECTIONS_REFUSED_INVALID_PARAMETERS = 0x000C
|
||||
ALL_CONNECTIONS_PENDING_NO_FURTHER_INFORMATION_AVAILABLE = 0x000D
|
||||
ALL_CONNECTIONS_PENDING_AUTHENTICATION_PENDING = 0x000E
|
||||
ALL_CONNECTIONS_PENDING_AUTHORIZATION_PENDING = 0x000F
|
||||
|
||||
mtu: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
mps: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
initial_credits: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
result: int = dataclasses.field(metadata=Result.type_metadata(2))
|
||||
destination_cid: Sequence[int] = dataclasses.field(
|
||||
metadata=L2CAP_Credit_Based_Connection_Request.CID_METADATA
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@L2CAP_Control_Frame.subclass
|
||||
@dataclasses.dataclass
|
||||
class L2CAP_Credit_Based_Reconfigure_Request(L2CAP_Control_Frame):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part A - 4.27 L2CAP_CREDIT_BASED_RECONFIGURE_REQ (0x19).
|
||||
'''
|
||||
|
||||
mtu: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
mps: int = dataclasses.field(metadata=hci.metadata(2))
|
||||
destination_cid: Sequence[int] = dataclasses.field(
|
||||
metadata=L2CAP_Credit_Based_Connection_Request.CID_METADATA
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@L2CAP_Control_Frame.subclass
|
||||
@dataclasses.dataclass
|
||||
class L2CAP_Credit_Based_Reconfigure_Response(L2CAP_Control_Frame):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part A - 4.28 L2CAP_CREDIT_BASED_RECONFIGURE_RSP (0x1A).
|
||||
'''
|
||||
|
||||
class Result(hci.SpecableEnum):
|
||||
RECONFIGURATION_SUCCESSFUL = 0x0000
|
||||
RECONFIGURATION_FAILED_REDUCTION_IN_SIZE_OF_MTU_NOT_ALLOWED = 0x0001
|
||||
RECONFIGURATION_FAILED_REDUCTION_IN_SIZE_OF_MPS_NOT_ALLOWED_FOR_MORE_THAN_ONE_CHANNEL_AT_A_TIME = (
|
||||
0x0002
|
||||
)
|
||||
RECONFIGURATION_FAILED_ONE_OR_MORE_DESTINATION_CIDS_INVALID = 0x0003
|
||||
RECONFIGURATION_FAILED_OTHER_UNACCEPTABLE_PARAMETERS = 0x0004
|
||||
|
||||
result: int = dataclasses.field(metadata=Result.type_metadata(2))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class ClassicChannel(utils.EventEmitter):
|
||||
class State(enum.IntEnum):
|
||||
@@ -1424,16 +1531,6 @@ class ChannelManager:
|
||||
if cid in self.fixed_channels:
|
||||
del self.fixed_channels[cid]
|
||||
|
||||
@utils.deprecated("Please use create_classic_server")
|
||||
def register_server(
|
||||
self,
|
||||
psm: int,
|
||||
server: Callable[[ClassicChannel], Any],
|
||||
) -> int:
|
||||
return self.create_classic_server(
|
||||
handler=server, spec=ClassicChannelSpec(psm=psm)
|
||||
).psm
|
||||
|
||||
def create_classic_server(
|
||||
self,
|
||||
spec: ClassicChannelSpec,
|
||||
@@ -1470,22 +1567,6 @@ class ChannelManager:
|
||||
|
||||
return self.servers[spec.psm]
|
||||
|
||||
@utils.deprecated("Please use create_le_credit_based_server()")
|
||||
def register_le_coc_server(
|
||||
self,
|
||||
psm: int,
|
||||
server: Callable[[LeCreditBasedChannel], Any],
|
||||
max_credits: int,
|
||||
mtu: int,
|
||||
mps: int,
|
||||
) -> int:
|
||||
return self.create_le_credit_based_server(
|
||||
spec=LeCreditBasedChannelSpec(
|
||||
psm=None if psm == 0 else psm, mtu=mtu, mps=mps, max_credits=max_credits
|
||||
),
|
||||
handler=server,
|
||||
).psm
|
||||
|
||||
def create_le_credit_based_server(
|
||||
self,
|
||||
spec: LeCreditBasedChannelSpec,
|
||||
@@ -1587,8 +1668,8 @@ class ChannelManager:
|
||||
if handler:
|
||||
try:
|
||||
handler(connection, cid, control_frame)
|
||||
except Exception as error:
|
||||
logger.warning(f'{color("!!! Exception in handler:", "red")} {error}')
|
||||
except Exception:
|
||||
logger.exception(color("!!! Exception in handler:", "red"))
|
||||
self.send_control_frame(
|
||||
connection,
|
||||
cid,
|
||||
@@ -1598,7 +1679,7 @@ class ChannelManager:
|
||||
data=b'',
|
||||
),
|
||||
)
|
||||
raise error
|
||||
raise
|
||||
else:
|
||||
logger.error(color('Channel Manager command not handled???', 'red'))
|
||||
self.send_control_frame(
|
||||
@@ -2038,17 +2119,6 @@ class ChannelManager:
|
||||
if channel.source_cid in connection_channels:
|
||||
del connection_channels[channel.source_cid]
|
||||
|
||||
@utils.deprecated("Please use create_le_credit_based_channel()")
|
||||
async def open_le_coc(
|
||||
self, connection: Connection, psm: int, max_credits: int, mtu: int, mps: int
|
||||
) -> LeCreditBasedChannel:
|
||||
return await self.create_le_credit_based_channel(
|
||||
connection=connection,
|
||||
spec=LeCreditBasedChannelSpec(
|
||||
psm=psm, max_credits=max_credits, mtu=mtu, mps=mps
|
||||
),
|
||||
)
|
||||
|
||||
async def create_le_credit_based_channel(
|
||||
self,
|
||||
connection: Connection,
|
||||
@@ -2084,8 +2154,8 @@ class ChannelManager:
|
||||
# Connect
|
||||
try:
|
||||
await channel.connect()
|
||||
except Exception as error:
|
||||
logger.warning(f'connection failed: {error}')
|
||||
except Exception:
|
||||
logger.exception('connection failed')
|
||||
del connection_channels[source_cid]
|
||||
raise
|
||||
|
||||
@@ -2095,12 +2165,6 @@ class ChannelManager:
|
||||
|
||||
return channel
|
||||
|
||||
@utils.deprecated("Please use create_classic_channel()")
|
||||
async def connect(self, connection: Connection, psm: int) -> ClassicChannel:
|
||||
return await self.create_classic_channel(
|
||||
connection=connection, spec=ClassicChannelSpec(psm=psm)
|
||||
)
|
||||
|
||||
async def create_classic_channel(
|
||||
self, connection: Connection, spec: ClassicChannelSpec
|
||||
) -> ClassicChannel:
|
||||
@@ -2137,20 +2201,3 @@ class ChannelManager:
|
||||
raise e
|
||||
|
||||
return channel
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Deprecated Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Channel(ClassicChannel):
|
||||
@utils.deprecated("Please use ClassicChannel")
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class LeConnectionOrientedChannel(LeCreditBasedChannel):
|
||||
@utils.deprecated("Please use LeCreditBasedChannel")
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -12,26 +12,25 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from bumble import core
|
||||
from bumble.hci import (
|
||||
Address,
|
||||
Role,
|
||||
HCI_SUCCESS,
|
||||
HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR,
|
||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
HCI_PAGE_TIMEOUT_ERROR,
|
||||
HCI_Connection_Complete_Event,
|
||||
)
|
||||
from bumble import controller
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from bumble import controller, core
|
||||
from bumble.hci import (
|
||||
HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR,
|
||||
HCI_PAGE_TIMEOUT_ERROR,
|
||||
HCI_SUCCESS,
|
||||
HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
|
||||
Address,
|
||||
HCI_Connection_Complete_Event,
|
||||
Role,
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -16,27 +16,28 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
from dataclasses import dataclass
|
||||
import secrets
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from bumble import hci
|
||||
from bumble.core import AdvertisingData, LeRole
|
||||
from bumble.smp import (
|
||||
SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
|
||||
SMP_KEYBOARD_ONLY_IO_CAPABILITY,
|
||||
SMP_DISPLAY_ONLY_IO_CAPABILITY,
|
||||
SMP_DISPLAY_YES_NO_IO_CAPABILITY,
|
||||
SMP_KEYBOARD_DISPLAY_IO_CAPABILITY,
|
||||
SMP_ENC_KEY_DISTRIBUTION_FLAG,
|
||||
SMP_ID_KEY_DISTRIBUTION_FLAG,
|
||||
SMP_SIGN_KEY_DISTRIBUTION_FLAG,
|
||||
SMP_KEYBOARD_DISPLAY_IO_CAPABILITY,
|
||||
SMP_KEYBOARD_ONLY_IO_CAPABILITY,
|
||||
SMP_LINK_KEY_DISTRIBUTION_FLAG,
|
||||
SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
|
||||
SMP_SIGN_KEY_DISTRIBUTION_FLAG,
|
||||
OobContext,
|
||||
OobLegacyContext,
|
||||
OobSharedData,
|
||||
)
|
||||
from bumble.core import AdvertisingData, LeRole
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -19,21 +19,22 @@ This module implement the Pandora Bluetooth test APIs for the Bumble stack.
|
||||
|
||||
__version__ = "0.0.1"
|
||||
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
import grpc
|
||||
import grpc.aio
|
||||
|
||||
from bumble.pandora.config import Config
|
||||
from bumble.pandora.device import PandoraDevice
|
||||
from bumble.pandora.host import HostService
|
||||
from bumble.pandora.l2cap import L2CAPService
|
||||
from bumble.pandora.security import SecurityService, SecurityStorageService
|
||||
from pandora.host_grpc_aio import add_HostServicer_to_server
|
||||
from pandora.l2cap_grpc_aio import add_L2CAPServicer_to_server
|
||||
from pandora.security_grpc_aio import (
|
||||
add_SecurityServicer_to_server,
|
||||
add_SecurityStorageServicer_to_server,
|
||||
)
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from bumble.pandora.config import Config
|
||||
from bumble.pandora.device import PandoraDevice
|
||||
from bumble.pandora.host import HostService
|
||||
from bumble.pandora.l2cap import L2CAPService
|
||||
from bumble.pandora.security import SecurityService, SecurityStorageService
|
||||
|
||||
# public symbols
|
||||
__all__ = [
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
from bumble.pairing import PairingConfig, PairingDelegate
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from bumble.pairing import PairingConfig, PairingDelegate
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
"""Generic & dependency free Bumble (reference) device."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from bumble import transport
|
||||
from bumble.core import (
|
||||
BT_GENERIC_AUDIO_SERVICE,
|
||||
@@ -32,8 +35,6 @@ from bumble.sdp import (
|
||||
DataElement,
|
||||
ServiceAttribute,
|
||||
)
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
# Default rootcanal HCI TCP address
|
||||
ROOTCANAL_HCI_ADDRESS = "localhost:6402"
|
||||
|
||||
@@ -13,51 +13,23 @@
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import bumble.device
|
||||
import grpc
|
||||
import grpc.aio
|
||||
import logging
|
||||
import struct
|
||||
from typing import AsyncGenerator, Optional, cast
|
||||
|
||||
import bumble.utils
|
||||
from bumble.pandora import utils
|
||||
from bumble.pandora.config import Config
|
||||
from bumble.core import (
|
||||
PhysicalTransport,
|
||||
UUID,
|
||||
AdvertisingData,
|
||||
Appearance,
|
||||
ConnectionError,
|
||||
)
|
||||
from bumble.device import (
|
||||
DEVICE_DEFAULT_SCAN_INTERVAL,
|
||||
DEVICE_DEFAULT_SCAN_WINDOW,
|
||||
Advertisement,
|
||||
AdvertisingParameters,
|
||||
AdvertisingEventProperties,
|
||||
AdvertisingType,
|
||||
Device,
|
||||
)
|
||||
from bumble.gatt import Service
|
||||
from bumble.hci import (
|
||||
HCI_CONNECTION_ALREADY_EXISTS_ERROR,
|
||||
HCI_PAGE_TIMEOUT_ERROR,
|
||||
HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
||||
Address,
|
||||
Phy,
|
||||
Role,
|
||||
OwnAddressType,
|
||||
)
|
||||
import grpc
|
||||
import grpc.aio
|
||||
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_grpc_aio import HostServicer
|
||||
from pandora.host_pb2 import (
|
||||
DISCOVERABLE_GENERAL,
|
||||
DISCOVERABLE_LIMITED,
|
||||
NOT_CONNECTABLE,
|
||||
NOT_DISCOVERABLE,
|
||||
DISCOVERABLE_LIMITED,
|
||||
DISCOVERABLE_GENERAL,
|
||||
PRIMARY_1M,
|
||||
PRIMARY_CODED,
|
||||
SECONDARY_1M,
|
||||
@@ -85,7 +57,37 @@ from pandora.host_pb2 import (
|
||||
WaitConnectionResponse,
|
||||
WaitDisconnectionRequest,
|
||||
)
|
||||
from typing import AsyncGenerator, Optional, cast
|
||||
|
||||
import bumble.device
|
||||
import bumble.utils
|
||||
from bumble.core import (
|
||||
UUID,
|
||||
AdvertisingData,
|
||||
Appearance,
|
||||
ConnectionError,
|
||||
PhysicalTransport,
|
||||
)
|
||||
from bumble.device import (
|
||||
DEVICE_DEFAULT_SCAN_INTERVAL,
|
||||
DEVICE_DEFAULT_SCAN_WINDOW,
|
||||
Advertisement,
|
||||
AdvertisingEventProperties,
|
||||
AdvertisingParameters,
|
||||
AdvertisingType,
|
||||
Device,
|
||||
)
|
||||
from bumble.gatt import Service
|
||||
from bumble.hci import (
|
||||
HCI_CONNECTION_ALREADY_EXISTS_ERROR,
|
||||
HCI_PAGE_TIMEOUT_ERROR,
|
||||
HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
||||
Address,
|
||||
OwnAddressType,
|
||||
Phy,
|
||||
Role,
|
||||
)
|
||||
from bumble.pandora import utils
|
||||
from bumble.pandora.config import Config
|
||||
|
||||
PRIMARY_PHY_MAP: dict[int, PrimaryPhy] = {
|
||||
# Default value reported by Bumble for legacy Advertising reports.
|
||||
|
||||
@@ -12,31 +12,21 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import grpc
|
||||
import json
|
||||
import logging
|
||||
from asyncio import Future
|
||||
from asyncio import Queue as AsyncQueue
|
||||
from dataclasses import dataclass
|
||||
from typing import AsyncGenerator, Optional, Union
|
||||
|
||||
from asyncio import Queue as AsyncQueue, Future
|
||||
|
||||
from bumble.pandora import utils
|
||||
from bumble.pandora.config import Config
|
||||
from bumble.core import OutOfResourcesError, InvalidArgumentError
|
||||
from bumble.device import Device
|
||||
from bumble.l2cap import (
|
||||
ClassicChannel,
|
||||
ClassicChannelServer,
|
||||
ClassicChannelSpec,
|
||||
LeCreditBasedChannel,
|
||||
LeCreditBasedChannelServer,
|
||||
LeCreditBasedChannelSpec,
|
||||
)
|
||||
import grpc
|
||||
from google.protobuf import any_pb2, empty_pb2 # pytype: disable=pyi-error
|
||||
from pandora.l2cap_grpc_aio import L2CAPServicer # pytype: disable=pyi-error
|
||||
from pandora.l2cap_pb2 import ( # pytype: disable=pyi-error
|
||||
COMMAND_NOT_UNDERSTOOD,
|
||||
INVALID_CID_IN_REQUEST,
|
||||
Channel as PandoraChannel,
|
||||
from pandora.l2cap_pb2 import COMMAND_NOT_UNDERSTOOD, INVALID_CID_IN_REQUEST
|
||||
from pandora.l2cap_pb2 import Channel as PandoraChannel # pytype: disable=pyi-error
|
||||
from pandora.l2cap_pb2 import (
|
||||
ConnectRequest,
|
||||
ConnectResponse,
|
||||
CreditBasedChannelRequest,
|
||||
@@ -51,8 +41,19 @@ from pandora.l2cap_pb2 import ( # pytype: disable=pyi-error
|
||||
WaitDisconnectionRequest,
|
||||
WaitDisconnectionResponse,
|
||||
)
|
||||
from typing import AsyncGenerator, Optional, Union
|
||||
from dataclasses import dataclass
|
||||
|
||||
from bumble.core import InvalidArgumentError, OutOfResourcesError
|
||||
from bumble.device import Device
|
||||
from bumble.l2cap import (
|
||||
ClassicChannel,
|
||||
ClassicChannelServer,
|
||||
ClassicChannelSpec,
|
||||
LeCreditBasedChannel,
|
||||
LeCreditBasedChannelServer,
|
||||
LeCreditBasedChannelSpec,
|
||||
)
|
||||
from bumble.pandora import utils
|
||||
from bumble.pandora.config import Config
|
||||
|
||||
L2capChannel = Union[ClassicChannel, LeCreditBasedChannel]
|
||||
|
||||
|
||||
@@ -13,24 +13,14 @@
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
from collections.abc import Awaitable
|
||||
import grpc
|
||||
import logging
|
||||
from collections.abc import Awaitable
|
||||
from typing import Any, AsyncGenerator, AsyncIterator, Callable, Optional, Union
|
||||
|
||||
from bumble.pandora import utils
|
||||
from bumble.pandora.config import Config
|
||||
from bumble import hci
|
||||
from bumble.core import (
|
||||
PhysicalTransport,
|
||||
ProtocolError,
|
||||
InvalidArgumentError,
|
||||
)
|
||||
import bumble.utils
|
||||
from bumble.device import Connection as BumbleConnection, Device
|
||||
from bumble.hci import HCI_Error, Role
|
||||
from bumble.pairing import PairingConfig, PairingDelegate as BasePairingDelegate
|
||||
import grpc
|
||||
from google.protobuf import any_pb2 # pytype: disable=pyi-error
|
||||
from google.protobuf import empty_pb2 # pytype: disable=pyi-error
|
||||
from google.protobuf import wrappers_pb2 # pytype: disable=pyi-error
|
||||
@@ -57,7 +47,17 @@ from pandora.security_pb2 import (
|
||||
WaitSecurityRequest,
|
||||
WaitSecurityResponse,
|
||||
)
|
||||
from typing import Any, AsyncGenerator, AsyncIterator, Callable, Optional, Union
|
||||
|
||||
import bumble.utils
|
||||
from bumble import hci
|
||||
from bumble.core import InvalidArgumentError, PhysicalTransport, ProtocolError
|
||||
from bumble.device import Connection as BumbleConnection
|
||||
from bumble.device import Device
|
||||
from bumble.hci import HCI_Error, Role
|
||||
from bumble.pairing import PairingConfig
|
||||
from bumble.pairing import PairingDelegate as BasePairingDelegate
|
||||
from bumble.pandora import utils
|
||||
from bumble.pandora.config import Config
|
||||
|
||||
|
||||
class PairingDelegate(BasePairingDelegate):
|
||||
|
||||
@@ -13,16 +13,18 @@
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import grpc
|
||||
import inspect
|
||||
import logging
|
||||
from typing import Any, Generator, MutableMapping, Optional
|
||||
|
||||
import grpc
|
||||
from google.protobuf.message import Message # pytype: disable=pyi-error
|
||||
|
||||
from bumble.device import Device
|
||||
from bumble.hci import Address, AddressType
|
||||
from google.protobuf.message import Message # pytype: disable=pyi-error
|
||||
from typing import Any, Generator, MutableMapping, Optional
|
||||
|
||||
ADDRESS_TYPES: dict[str, AddressType] = {
|
||||
"public": Address.PUBLIC_DEVICE_ADDRESS,
|
||||
|
||||
@@ -18,26 +18,27 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import struct
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from bumble.device import Connection
|
||||
from bumble import utils
|
||||
from bumble.att import ATT_Error
|
||||
from bumble.device import Connection
|
||||
from bumble.gatt import (
|
||||
GATT_AUDIO_INPUT_CONTROL_POINT_CHARACTERISTIC,
|
||||
GATT_AUDIO_INPUT_CONTROL_SERVICE,
|
||||
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC,
|
||||
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC,
|
||||
GATT_AUDIO_INPUT_STATUS_CHARACTERISTIC,
|
||||
GATT_AUDIO_INPUT_TYPE_CHARACTERISTIC,
|
||||
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC,
|
||||
Attribute,
|
||||
Characteristic,
|
||||
TemplateService,
|
||||
CharacteristicValue,
|
||||
GATT_AUDIO_INPUT_CONTROL_SERVICE,
|
||||
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC,
|
||||
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC,
|
||||
GATT_AUDIO_INPUT_TYPE_CHARACTERISTIC,
|
||||
GATT_AUDIO_INPUT_STATUS_CHARACTERISTIC,
|
||||
GATT_AUDIO_INPUT_CONTROL_POINT_CHARACTERISTIC,
|
||||
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_adapters import (
|
||||
CharacteristicProxy,
|
||||
@@ -48,7 +49,6 @@ from bumble.gatt_adapters import (
|
||||
UTF8CharacteristicProxyAdapter,
|
||||
)
|
||||
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
|
||||
from bumble import utils
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -20,25 +20,24 @@ Apple Media Service (AMS).
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import enum
|
||||
import logging
|
||||
from typing import Optional, Iterable, Union
|
||||
|
||||
from typing import Iterable, Optional, Union
|
||||
|
||||
from bumble import utils
|
||||
from bumble.device import Peer
|
||||
from bumble.gatt import (
|
||||
Characteristic,
|
||||
GATT_AMS_SERVICE,
|
||||
GATT_AMS_REMOTE_COMMAND_CHARACTERISTIC,
|
||||
GATT_AMS_ENTITY_UPDATE_CHARACTERISTIC,
|
||||
GATT_AMS_ENTITY_ATTRIBUTE_CHARACTERISTIC,
|
||||
GATT_AMS_ENTITY_UPDATE_CHARACTERISTIC,
|
||||
GATT_AMS_REMOTE_COMMAND_CHARACTERISTIC,
|
||||
GATT_AMS_SERVICE,
|
||||
Characteristic,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
||||
from bumble import utils
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -20,6 +20,7 @@ Apple Notification Center Service (ANCS).
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import datetime
|
||||
@@ -28,21 +29,19 @@ import logging
|
||||
import struct
|
||||
from typing import Optional, Sequence, Union
|
||||
|
||||
|
||||
from bumble import utils
|
||||
from bumble.att import ATT_Error
|
||||
from bumble.device import Peer
|
||||
from bumble.gatt import (
|
||||
Characteristic,
|
||||
GATT_ANCS_SERVICE,
|
||||
GATT_ANCS_NOTIFICATION_SOURCE_CHARACTERISTIC,
|
||||
GATT_ANCS_CONTROL_POINT_CHARACTERISTIC,
|
||||
GATT_ANCS_DATA_SOURCE_CHARACTERISTIC,
|
||||
GATT_ANCS_NOTIFICATION_SOURCE_CHARACTERISTIC,
|
||||
GATT_ANCS_SERVICE,
|
||||
Characteristic,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
||||
from bumble.gatt_adapters import SerializableCharacteristicProxyAdapter
|
||||
from bumble import utils
|
||||
|
||||
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
|
||||
@@ -18,22 +18,17 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
import enum
|
||||
import functools
|
||||
import logging
|
||||
import struct
|
||||
from typing import Any, Optional, Union, TypeVar
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional, TypeVar, Union
|
||||
|
||||
from bumble import utils
|
||||
from bumble import colors
|
||||
from bumble.profiles.bap import CodecSpecificConfiguration
|
||||
from bumble import colors, device, gatt, gatt_client, hci, utils
|
||||
from bumble.profiles import le_audio
|
||||
from bumble import device
|
||||
from bumble import gatt
|
||||
from bumble import gatt_client
|
||||
from bumble import hci
|
||||
from bumble.profiles.bap import CodecSpecificConfiguration
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -17,16 +17,13 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import enum
|
||||
import struct
|
||||
import logging
|
||||
from typing import Optional, Callable, Union, Any
|
||||
import struct
|
||||
from typing import Any, Callable, Optional, Union
|
||||
|
||||
from bumble import l2cap
|
||||
from bumble import utils
|
||||
from bumble import gatt
|
||||
from bumble import gatt_client
|
||||
from bumble import data_types, gatt, gatt_client, l2cap, utils
|
||||
from bumble.core import AdvertisingData
|
||||
from bumble.device import Device, Connection
|
||||
from bumble.device import Connection, Device
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -188,12 +185,11 @@ class AshaService(gatt.TemplateService):
|
||||
return bytes(
|
||||
AdvertisingData(
|
||||
[
|
||||
(
|
||||
AdvertisingData.SERVICE_DATA_16_BIT_UUID,
|
||||
bytes(gatt.GATT_ASHA_SERVICE)
|
||||
+ bytes([self.protocol_version, self.capability])
|
||||
data_types.ServiceData16BitUUID(
|
||||
gatt.GATT_ASHA_SERVICE,
|
||||
bytes([self.protocol_version, self.capability])
|
||||
+ self.hisyncid[:4],
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -18,21 +18,18 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
import dataclasses
|
||||
import enum
|
||||
import struct
|
||||
import functools
|
||||
import logging
|
||||
import struct
|
||||
from collections.abc import Sequence
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble import core
|
||||
from bumble import hci
|
||||
from bumble import gatt
|
||||
from bumble import utils
|
||||
from bumble import core, data_types, gatt, hci, utils
|
||||
from bumble.profiles import le_audio
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -260,11 +257,10 @@ class UnicastServerAdvertisingData:
|
||||
return bytes(
|
||||
core.AdvertisingData(
|
||||
[
|
||||
(
|
||||
core.AdvertisingData.SERVICE_DATA_16_BIT_UUID,
|
||||
data_types.ServiceData16BitUUID(
|
||||
gatt.GATT_AUDIO_STREAM_CONTROL_SERVICE,
|
||||
struct.pack(
|
||||
'<2sBIB',
|
||||
bytes(gatt.GATT_AUDIO_STREAM_CONTROL_SERVICE),
|
||||
'<BIB',
|
||||
self.announcement_type,
|
||||
self.available_audio_contexts,
|
||||
len(self.metadata),
|
||||
@@ -493,12 +489,8 @@ class BroadcastAudioAnnouncement:
|
||||
return bytes(
|
||||
core.AdvertisingData(
|
||||
[
|
||||
(
|
||||
core.AdvertisingData.SERVICE_DATA_16_BIT_UUID,
|
||||
(
|
||||
bytes(gatt.GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE)
|
||||
+ bytes(self)
|
||||
),
|
||||
data_types.ServiceData16BitUUID(
|
||||
gatt.GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE, bytes(self)
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -610,12 +602,8 @@ class BasicAudioAnnouncement:
|
||||
return bytes(
|
||||
core.AdvertisingData(
|
||||
[
|
||||
(
|
||||
core.AdvertisingData.SERVICE_DATA_16_BIT_UUID,
|
||||
(
|
||||
bytes(gatt.GATT_BASIC_AUDIO_ANNOUNCEMENT_SERVICE)
|
||||
+ bytes(self)
|
||||
),
|
||||
data_types.ServiceData16BitUUID(
|
||||
gatt.GATT_BASIC_AUDIO_ANNOUNCEMENT_SERVICE, bytes(self)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -17,18 +17,13 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
import struct
|
||||
from typing import ClassVar, Optional, Sequence
|
||||
|
||||
from bumble import core
|
||||
from bumble import device
|
||||
from bumble import gatt
|
||||
from bumble import gatt_adapters
|
||||
from bumble import gatt_client
|
||||
from bumble import hci
|
||||
from bumble import utils
|
||||
from bumble import core, device, gatt, gatt_adapters, gatt_client, hci, utils
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -18,19 +18,18 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from typing import Optional
|
||||
|
||||
from bumble.gatt_client import ProfileServiceProxy
|
||||
from bumble.gatt import (
|
||||
GATT_BATTERY_SERVICE,
|
||||
GATT_BATTERY_LEVEL_CHARACTERISTIC,
|
||||
TemplateService,
|
||||
GATT_BATTERY_SERVICE,
|
||||
Characteristic,
|
||||
CharacteristicValue,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_client import CharacteristicProxy
|
||||
from bumble.gatt_adapters import (
|
||||
PackedCharacteristicAdapter,
|
||||
PackedCharacteristicProxyAdapter,
|
||||
)
|
||||
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from bumble import gatt
|
||||
from bumble import gatt_client
|
||||
from bumble import gatt, gatt_client
|
||||
from bumble.profiles import csip
|
||||
|
||||
|
||||
|
||||
@@ -17,16 +17,12 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import struct
|
||||
from typing import Optional
|
||||
|
||||
from bumble import core
|
||||
from bumble import crypto
|
||||
from bumble import device
|
||||
from bumble import gatt
|
||||
from bumble import gatt_client
|
||||
|
||||
from bumble import core, crypto, device, gatt, gatt_client
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
|
||||
@@ -25,12 +25,12 @@ from bumble.gatt import (
|
||||
GATT_HARDWARE_REVISION_STRING_CHARACTERISTIC,
|
||||
GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC,
|
||||
GATT_MODEL_NUMBER_STRING_CHARACTERISTIC,
|
||||
GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC,
|
||||
GATT_SERIAL_NUMBER_STRING_CHARACTERISTIC,
|
||||
GATT_SOFTWARE_REVISION_STRING_CHARACTERISTIC,
|
||||
GATT_SYSTEM_ID_CHARACTERISTIC,
|
||||
GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC,
|
||||
TemplateService,
|
||||
Characteristic,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_adapters import (
|
||||
DelegatedCharacteristicProxyAdapter,
|
||||
|
||||
@@ -23,11 +23,11 @@ from typing import Optional, Union
|
||||
|
||||
from bumble.core import Appearance
|
||||
from bumble.gatt import (
|
||||
TemplateService,
|
||||
Characteristic,
|
||||
GATT_GENERIC_ACCESS_SERVICE,
|
||||
GATT_DEVICE_NAME_CHARACTERISTIC,
|
||||
GATT_APPEARANCE_CHARACTERISTIC,
|
||||
GATT_DEVICE_NAME_CHARACTERISTIC,
|
||||
GATT_GENERIC_ACCESS_SERVICE,
|
||||
Characteristic,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_adapters import (
|
||||
DelegatedCharacteristicProxyAdapter,
|
||||
|
||||
@@ -17,10 +17,7 @@ from __future__ import annotations
|
||||
import struct
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bumble import att
|
||||
from bumble import gatt
|
||||
from bumble import gatt_client
|
||||
from bumble import crypto
|
||||
from bumble import att, crypto, gatt, gatt_client
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble import device
|
||||
|
||||
@@ -18,21 +18,21 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import struct
|
||||
from enum import IntFlag
|
||||
from typing import Optional
|
||||
|
||||
from bumble.gatt import (
|
||||
TemplateService,
|
||||
Characteristic,
|
||||
GATT_BGR_FEATURES_CHARACTERISTIC,
|
||||
GATT_BGS_FEATURES_CHARACTERISTIC,
|
||||
GATT_GAMING_AUDIO_SERVICE,
|
||||
GATT_GMAP_ROLE_CHARACTERISTIC,
|
||||
GATT_UGG_FEATURES_CHARACTERISTIC,
|
||||
GATT_UGT_FEATURES_CHARACTERISTIC,
|
||||
GATT_BGS_FEATURES_CHARACTERISTIC,
|
||||
GATT_BGR_FEATURES_CHARACTERISTIC,
|
||||
Characteristic,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_adapters import DelegatedCharacteristicProxyAdapter
|
||||
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
||||
from enum import IntFlag
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from bumble import att, gatt, gatt_adapters, gatt_client
|
||||
from bumble import att, gatt, gatt_adapters, gatt_client, utils
|
||||
from bumble.core import InvalidArgumentError, InvalidStateError
|
||||
from bumble.device import Device, Connection
|
||||
from bumble import utils
|
||||
from bumble.device import Connection, Device
|
||||
from bumble.hci import Address
|
||||
|
||||
|
||||
|
||||
@@ -17,20 +17,21 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
from enum import IntEnum
|
||||
|
||||
import struct
|
||||
from enum import IntEnum
|
||||
from typing import Optional
|
||||
|
||||
from bumble import core
|
||||
from bumble.att import ATT_Error
|
||||
from bumble.gatt import (
|
||||
GATT_HEART_RATE_SERVICE,
|
||||
GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
|
||||
GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC,
|
||||
GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC,
|
||||
TemplateService,
|
||||
GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
|
||||
GATT_HEART_RATE_SERVICE,
|
||||
Characteristic,
|
||||
CharacteristicValue,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_adapters import (
|
||||
DelegatedCharacteristicAdapter,
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import enum
|
||||
import struct
|
||||
from typing import Any
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble.profiles import bap
|
||||
from bumble import utils
|
||||
from bumble.profiles import bap
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -22,16 +22,12 @@ import asyncio
|
||||
import dataclasses
|
||||
import enum
|
||||
import struct
|
||||
from typing import TYPE_CHECKING, ClassVar, Optional
|
||||
|
||||
from bumble import core
|
||||
from bumble import device
|
||||
from bumble import gatt
|
||||
from bumble import gatt_client
|
||||
from bumble import utils
|
||||
|
||||
from typing import Optional, ClassVar, TYPE_CHECKING
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble import core, device, gatt, gatt_client, utils
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -17,18 +17,15 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
import struct
|
||||
from typing import Optional, Sequence, Union
|
||||
|
||||
from bumble.profiles.bap import AudioLocation, CodecSpecificCapabilities, ContextType
|
||||
from bumble import gatt, gatt_adapters, gatt_client, hci
|
||||
from bumble.profiles import le_audio
|
||||
from bumble import gatt
|
||||
from bumble import gatt_adapters
|
||||
from bumble import gatt_client
|
||||
from bumble import hci
|
||||
|
||||
from bumble.profiles.bap import AudioLocation, CodecSpecificCapabilities, ContextType
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import enum
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble.profiles import le_audio
|
||||
|
||||
@@ -22,15 +22,14 @@ import logging
|
||||
import struct
|
||||
|
||||
from bumble.gatt import (
|
||||
TemplateService,
|
||||
Characteristic,
|
||||
GATT_TELEPHONY_AND_MEDIA_AUDIO_SERVICE,
|
||||
GATT_TMAP_ROLE_CHARACTERISTIC,
|
||||
Characteristic,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_adapters import DelegatedCharacteristicProxyAdapter
|
||||
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -17,18 +17,12 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import enum
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
from bumble import att
|
||||
from bumble import utils
|
||||
from bumble import device
|
||||
from bumble import gatt
|
||||
from bumble import gatt_adapters
|
||||
from bumble import gatt_client
|
||||
|
||||
from bumble import att, device, gatt, gatt_adapters, gatt_client, utils
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
|
||||
@@ -20,17 +20,18 @@ import struct
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from bumble.device import Connection
|
||||
from bumble import utils
|
||||
from bumble.att import ATT_Error
|
||||
from bumble.device import Connection
|
||||
from bumble.gatt import (
|
||||
Characteristic,
|
||||
TemplateService,
|
||||
CharacteristicValue,
|
||||
GATT_AUDIO_LOCATION_CHARACTERISTIC,
|
||||
GATT_AUDIO_OUTPUT_DESCRIPTION_CHARACTERISTIC,
|
||||
GATT_VOLUME_OFFSET_CONTROL_POINT_CHARACTERISTIC,
|
||||
GATT_VOLUME_OFFSET_CONTROL_SERVICE,
|
||||
GATT_VOLUME_OFFSET_STATE_CHARACTERISTIC,
|
||||
GATT_AUDIO_LOCATION_CHARACTERISTIC,
|
||||
GATT_VOLUME_OFFSET_CONTROL_POINT_CHARACTERISTIC,
|
||||
GATT_AUDIO_OUTPUT_DESCRIPTION_CHARACTERISTIC,
|
||||
Characteristic,
|
||||
CharacteristicValue,
|
||||
TemplateService,
|
||||
)
|
||||
from bumble.gatt_adapters import (
|
||||
DelegatedCharacteristicProxyAdapter,
|
||||
@@ -38,7 +39,6 @@ from bumble.gatt_adapters import (
|
||||
UTF8CharacteristicProxyAdapter,
|
||||
)
|
||||
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
|
||||
from bumble import utils
|
||||
from bumble.profiles.bap import AudioLocation
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -17,33 +17,30 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
import collections
|
||||
import dataclasses
|
||||
import enum
|
||||
from typing import Callable, Optional, Union, TYPE_CHECKING
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Callable, Optional, Union
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
from bumble import core
|
||||
from bumble import l2cap
|
||||
from bumble import sdp
|
||||
from bumble import utils
|
||||
from bumble import core, l2cap, sdp, utils
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
UUID,
|
||||
BT_RFCOMM_PROTOCOL_ID,
|
||||
PhysicalTransport,
|
||||
BT_L2CAP_PROTOCOL_ID,
|
||||
BT_RFCOMM_PROTOCOL_ID,
|
||||
UUID,
|
||||
InvalidArgumentError,
|
||||
InvalidStateError,
|
||||
InvalidPacketError,
|
||||
InvalidStateError,
|
||||
PhysicalTransport,
|
||||
ProtocolError,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.device import Device, Connection
|
||||
from bumble.device import Connection, Device
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -1047,8 +1044,8 @@ class Client:
|
||||
self.l2cap_channel = await self.connection.create_l2cap_channel(
|
||||
spec=l2cap.ClassicChannelSpec(psm=RFCOMM_PSM, mtu=self.l2cap_mtu)
|
||||
)
|
||||
except ProtocolError as error:
|
||||
logger.warning(f'L2CAP connection failed: {error}')
|
||||
except ProtocolError:
|
||||
logger.exception('L2CAP connection failed')
|
||||
raise
|
||||
|
||||
assert self.l2cap_channel is not None
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
|
||||
|
||||
|
||||
@@ -16,24 +16,26 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import struct
|
||||
from typing import Iterable, NewType, Optional, Union, Sequence, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Iterable, NewType, Optional, Sequence, Union
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from bumble import core, l2cap
|
||||
from bumble.colors import color
|
||||
from bumble.core import (
|
||||
InvalidStateError,
|
||||
InvalidArgumentError,
|
||||
InvalidPacketError,
|
||||
InvalidStateError,
|
||||
ProtocolError,
|
||||
)
|
||||
from bumble.hci import HCI_Object, name_or_number, key_with_value
|
||||
from bumble.hci import HCI_Object, key_with_value, name_or_number
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.device import Device, Connection
|
||||
from bumble.device import Connection, Device
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -1084,8 +1086,8 @@ class Server:
|
||||
def on_pdu(self, pdu):
|
||||
try:
|
||||
sdp_pdu = SDP_PDU.from_bytes(pdu)
|
||||
except Exception as error:
|
||||
logger.warning(color(f'failed to parse SDP Request PDU: {error}', 'red'))
|
||||
except Exception:
|
||||
logger.exception(color('failed to parse SDP Request PDU', 'red'))
|
||||
self.send_response(
|
||||
SDP_ErrorResponse(
|
||||
transaction_id=0, error_code=SDP_INVALID_REQUEST_SYNTAX_ERROR
|
||||
@@ -1100,8 +1102,8 @@ class Server:
|
||||
if handler:
|
||||
try:
|
||||
handler(sdp_pdu)
|
||||
except Exception as error:
|
||||
logger.exception(f'{color("!!! Exception in handler:", "red")} {error}')
|
||||
except Exception:
|
||||
logger.exception(color("!!! Exception in handler:", "red"))
|
||||
self.send_response(
|
||||
SDP_ErrorResponse(
|
||||
transaction_id=sdp_pdu.transaction_id,
|
||||
|
||||
295
bumble/smp.py
295
bumble/smp.py
@@ -23,38 +23,41 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
|
||||
import asyncio
|
||||
import enum
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Optional,
|
||||
TypeVar,
|
||||
cast,
|
||||
)
|
||||
|
||||
|
||||
from bumble import crypto, utils
|
||||
from bumble.colors import color
|
||||
from bumble.hci import (
|
||||
Address,
|
||||
Role,
|
||||
HCI_LE_Enable_Encryption_Command,
|
||||
HCI_Object,
|
||||
key_with_value,
|
||||
)
|
||||
from bumble.core import (
|
||||
PhysicalTransport,
|
||||
AdvertisingData,
|
||||
InvalidArgumentError,
|
||||
PhysicalTransport,
|
||||
ProtocolError,
|
||||
name_or_number,
|
||||
)
|
||||
from bumble.hci import (
|
||||
Address,
|
||||
Fields,
|
||||
HCI_LE_Enable_Encryption_Command,
|
||||
HCI_Object,
|
||||
Role,
|
||||
key_with_value,
|
||||
metadata,
|
||||
)
|
||||
from bumble.keys import PairingKeys
|
||||
from bumble import crypto
|
||||
from bumble import utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.device import Connection, Device
|
||||
@@ -200,31 +203,32 @@ def error_name(error_code: int) -> str:
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
@dataclass
|
||||
class SMP_Command:
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3 SECURITY MANAGER PROTOCOL
|
||||
'''
|
||||
|
||||
smp_classes: dict[int, type[SMP_Command]] = {}
|
||||
fields: Any
|
||||
code = 0
|
||||
name = ''
|
||||
smp_classes: ClassVar[dict[int, type[SMP_Command]]] = {}
|
||||
fields: ClassVar[Fields]
|
||||
code: int = field(default=0, init=False)
|
||||
name: str = field(default='', init=False)
|
||||
_payload: Optional[bytes] = field(default=None, init=False)
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(pdu: bytes) -> "SMP_Command":
|
||||
@classmethod
|
||||
def from_bytes(cls, pdu: bytes) -> "SMP_Command":
|
||||
code = pdu[0]
|
||||
|
||||
cls = SMP_Command.smp_classes.get(code)
|
||||
if cls is None:
|
||||
instance = SMP_Command(pdu)
|
||||
subclass = SMP_Command.smp_classes.get(code)
|
||||
if subclass is None:
|
||||
instance = SMP_Command()
|
||||
instance.name = SMP_Command.command_name(code)
|
||||
instance.code = code
|
||||
instance.payload = pdu
|
||||
return instance
|
||||
self = cls.__new__(cls)
|
||||
SMP_Command.__init__(self, pdu)
|
||||
if hasattr(self, 'fields'):
|
||||
self.init_from_bytes(pdu, 1)
|
||||
return self
|
||||
instance = subclass(**HCI_Object.dict_from_bytes(pdu, 1, subclass.fields))
|
||||
instance.payload = pdu[1:]
|
||||
return instance
|
||||
|
||||
@staticmethod
|
||||
def command_name(code: int) -> str:
|
||||
@@ -264,36 +268,35 @@ class SMP_Command:
|
||||
def keypress_notification_type_name(notification_type: int) -> str:
|
||||
return name_or_number(SMP_KEYPRESS_NOTIFICATION_TYPE_NAMES, notification_type)
|
||||
|
||||
@staticmethod
|
||||
def subclass(fields):
|
||||
def inner(cls):
|
||||
cls.name = cls.__name__.upper()
|
||||
cls.code = key_with_value(SMP_COMMAND_NAMES, cls.name)
|
||||
if cls.code is None:
|
||||
raise KeyError(
|
||||
f'Command name {cls.name} not found in SMP_COMMAND_NAMES'
|
||||
)
|
||||
cls.fields = fields
|
||||
_Command = TypeVar("_Command", bound="SMP_Command")
|
||||
|
||||
# Register a factory for this class
|
||||
SMP_Command.smp_classes[cls.code] = cls
|
||||
@classmethod
|
||||
def subclass(cls, subclass: type[_Command]) -> type[_Command]:
|
||||
subclass.name = subclass.__name__.upper()
|
||||
subclass.code = key_with_value(SMP_COMMAND_NAMES, subclass.name)
|
||||
if subclass.code is None:
|
||||
raise KeyError(
|
||||
f'Command name {subclass.name} not found in SMP_COMMAND_NAMES'
|
||||
)
|
||||
subclass.fields = HCI_Object.fields_from_dataclass(subclass)
|
||||
|
||||
return cls
|
||||
# Register a factory for this class
|
||||
SMP_Command.smp_classes[subclass.code] = subclass
|
||||
|
||||
return inner
|
||||
return subclass
|
||||
|
||||
def __init__(self, pdu: Optional[bytes] = None, **kwargs: Any) -> None:
|
||||
if hasattr(self, 'fields') and kwargs:
|
||||
HCI_Object.init_from_fields(self, self.fields, kwargs)
|
||||
if pdu is None:
|
||||
pdu = bytes([self.code]) + HCI_Object.dict_to_bytes(kwargs, self.fields)
|
||||
self.pdu = pdu
|
||||
@property
|
||||
def payload(self) -> bytes:
|
||||
if self._payload is None:
|
||||
self._payload = HCI_Object.dict_to_bytes(self.__dict__, self.fields)
|
||||
return self._payload
|
||||
|
||||
def init_from_bytes(self, pdu: bytes, offset: int) -> None:
|
||||
return HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
|
||||
@payload.setter
|
||||
def payload(self, value: bytes) -> None:
|
||||
self._payload = value
|
||||
|
||||
def __bytes__(self):
|
||||
return self.pdu
|
||||
return bytes([self.code]) + self.payload
|
||||
|
||||
def __str__(self):
|
||||
result = color(self.name, 'yellow')
|
||||
@@ -306,206 +309,192 @@ class SMP_Command:
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass(
|
||||
[
|
||||
('io_capability', {'size': 1, 'mapper': SMP_Command.io_capability_name}),
|
||||
('oob_data_flag', 1),
|
||||
('auth_req', {'size': 1, 'mapper': SMP_Command.auth_req_str}),
|
||||
('maximum_encryption_key_size', 1),
|
||||
(
|
||||
'initiator_key_distribution',
|
||||
{'size': 1, 'mapper': SMP_Command.key_distribution_str},
|
||||
),
|
||||
(
|
||||
'responder_key_distribution',
|
||||
{'size': 1, 'mapper': SMP_Command.key_distribution_str},
|
||||
),
|
||||
]
|
||||
)
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Pairing_Request_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.5.1 Pairing Request
|
||||
'''
|
||||
|
||||
io_capability: int
|
||||
oob_data_flag: int
|
||||
auth_req: int
|
||||
maximum_encryption_key_size: int
|
||||
initiator_key_distribution: int
|
||||
responder_key_distribution: int
|
||||
io_capability: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.io_capability_name})
|
||||
)
|
||||
oob_data_flag: int = field(metadata=metadata(1))
|
||||
auth_req: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.auth_req_str})
|
||||
)
|
||||
maximum_encryption_key_size: int = field(metadata=metadata(1))
|
||||
initiator_key_distribution: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.key_distribution_str})
|
||||
)
|
||||
responder_key_distribution: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.key_distribution_str})
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass(
|
||||
[
|
||||
('io_capability', {'size': 1, 'mapper': SMP_Command.io_capability_name}),
|
||||
('oob_data_flag', 1),
|
||||
('auth_req', {'size': 1, 'mapper': SMP_Command.auth_req_str}),
|
||||
('maximum_encryption_key_size', 1),
|
||||
(
|
||||
'initiator_key_distribution',
|
||||
{'size': 1, 'mapper': SMP_Command.key_distribution_str},
|
||||
),
|
||||
(
|
||||
'responder_key_distribution',
|
||||
{'size': 1, 'mapper': SMP_Command.key_distribution_str},
|
||||
),
|
||||
]
|
||||
)
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Pairing_Response_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.5.2 Pairing Response
|
||||
'''
|
||||
|
||||
io_capability: int
|
||||
oob_data_flag: int
|
||||
auth_req: int
|
||||
maximum_encryption_key_size: int
|
||||
initiator_key_distribution: int
|
||||
responder_key_distribution: int
|
||||
io_capability: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.io_capability_name})
|
||||
)
|
||||
oob_data_flag: int = field(metadata=metadata(1))
|
||||
auth_req: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.auth_req_str})
|
||||
)
|
||||
maximum_encryption_key_size: int = field(metadata=metadata(1))
|
||||
initiator_key_distribution: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.key_distribution_str})
|
||||
)
|
||||
responder_key_distribution: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.key_distribution_str})
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass([('confirm_value', 16)])
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Pairing_Confirm_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.5.3 Pairing Confirm
|
||||
'''
|
||||
|
||||
confirm_value: bytes
|
||||
confirm_value: bytes = field(metadata=metadata(16))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass([('random_value', 16)])
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Pairing_Random_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.5.4 Pairing Random
|
||||
'''
|
||||
|
||||
random_value: bytes
|
||||
random_value: bytes = field(metadata=metadata(16))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass([('reason', {'size': 1, 'mapper': error_name})])
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Pairing_Failed_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.5.5 Pairing Failed
|
||||
'''
|
||||
|
||||
reason: int
|
||||
reason: int = field(metadata=metadata({'size': 1, 'mapper': error_name}))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass([('public_key_x', 32), ('public_key_y', 32)])
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Pairing_Public_Key_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.5.6 Pairing Public Key
|
||||
'''
|
||||
|
||||
public_key_x: bytes
|
||||
public_key_y: bytes
|
||||
public_key_x: bytes = field(metadata=metadata(32))
|
||||
public_key_y: bytes = field(metadata=metadata(32))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass(
|
||||
[
|
||||
('dhkey_check', 16),
|
||||
]
|
||||
)
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Pairing_DHKey_Check_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.5.7 Pairing DHKey Check
|
||||
'''
|
||||
|
||||
dhkey_check: bytes
|
||||
dhkey_check: bytes = field(metadata=metadata(16))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass(
|
||||
[
|
||||
(
|
||||
'notification_type',
|
||||
{'size': 1, 'mapper': SMP_Command.keypress_notification_type_name},
|
||||
),
|
||||
]
|
||||
)
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Pairing_Keypress_Notification_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.5.8 Keypress Notification
|
||||
'''
|
||||
|
||||
notification_type: int
|
||||
notification_type: int = field(
|
||||
metadata=metadata(
|
||||
{'size': 1, 'mapper': SMP_Command.keypress_notification_type_name}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass([('long_term_key', 16)])
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Encryption_Information_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.6.2 Encryption Information
|
||||
'''
|
||||
|
||||
long_term_key: bytes
|
||||
long_term_key: bytes = field(metadata=metadata(16))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass([('ediv', 2), ('rand', 8)])
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Master_Identification_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.6.3 Master Identification
|
||||
'''
|
||||
|
||||
ediv: int
|
||||
rand: bytes
|
||||
ediv: int = field(metadata=metadata(2))
|
||||
rand: bytes = field(metadata=metadata(8))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass([('identity_resolving_key', 16)])
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Identity_Information_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.6.4 Identity Information
|
||||
'''
|
||||
|
||||
identity_resolving_key: bytes
|
||||
identity_resolving_key: bytes = field(metadata=metadata(16))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass(
|
||||
[
|
||||
('addr_type', Address.ADDRESS_TYPE_SPEC),
|
||||
('bd_addr', Address.parse_address_preceded_by_type),
|
||||
]
|
||||
)
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Identity_Address_Information_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.6.5 Identity Address Information
|
||||
'''
|
||||
|
||||
addr_type: int
|
||||
bd_addr: Address
|
||||
addr_type: int = field(metadata=metadata(Address.ADDRESS_TYPE_SPEC))
|
||||
bd_addr: Address = field(metadata=metadata(Address.parse_address_preceded_by_type))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass([('signature_key', 16)])
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Signing_Information_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.6.6 Signing Information
|
||||
'''
|
||||
|
||||
signature_key: bytes
|
||||
signature_key: bytes = field(metadata=metadata(16))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@SMP_Command.subclass(
|
||||
[
|
||||
('auth_req', {'size': 1, 'mapper': SMP_Command.auth_req_str}),
|
||||
]
|
||||
)
|
||||
@SMP_Command.subclass
|
||||
@dataclass
|
||||
class SMP_Security_Request_Command(SMP_Command):
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part H - 3.6.7 Security Request
|
||||
'''
|
||||
|
||||
auth_req: int
|
||||
auth_req: int = field(
|
||||
metadata=metadata({'size': 1, 'mapper': SMP_Command.auth_req_str})
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -892,8 +881,8 @@ class Session:
|
||||
if response:
|
||||
next_steps()
|
||||
return
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while confirm: {error}')
|
||||
except Exception:
|
||||
logger.exception('exception while confirm')
|
||||
|
||||
self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
|
||||
|
||||
@@ -911,8 +900,8 @@ class Session:
|
||||
if response:
|
||||
next_steps()
|
||||
return
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while prompting: {error}')
|
||||
except Exception:
|
||||
logger.exception('exception while prompting')
|
||||
|
||||
self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
|
||||
|
||||
@@ -929,8 +918,8 @@ class Session:
|
||||
return
|
||||
logger.debug(f'user input: {passkey}')
|
||||
next_steps(passkey)
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while prompting: {error}')
|
||||
except Exception:
|
||||
logger.exception('exception while prompting')
|
||||
self.send_pairing_failed(SMP_PASSKEY_ENTRY_FAILED_ERROR)
|
||||
|
||||
self.connection.cancel_on_disconnection(prompt())
|
||||
@@ -978,8 +967,8 @@ class Session:
|
||||
|
||||
try:
|
||||
self.connection.cancel_on_disconnection(display_passkey())
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while displaying passkey: {error}')
|
||||
except Exception:
|
||||
logger.exception('exception while displaying passkey')
|
||||
else:
|
||||
self.input_passkey(next_steps)
|
||||
|
||||
@@ -1424,8 +1413,8 @@ class Session:
|
||||
if handler is not None:
|
||||
try:
|
||||
handler(command)
|
||||
except Exception as error:
|
||||
logger.exception(f'{color("!!! Exception in handler:", "red")} {error}')
|
||||
except Exception:
|
||||
logger.exception(color("!!! Exception in handler:", "red"))
|
||||
response = SMP_Pairing_Failed_Command(
|
||||
reason=SMP_UNSPECIFIED_REASON_ERROR
|
||||
)
|
||||
@@ -1446,8 +1435,8 @@ class Session:
|
||||
# Check if the request should proceed
|
||||
try:
|
||||
accepted = await self.pairing_config.delegate.accept()
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while accepting: {error}')
|
||||
except Exception:
|
||||
logger.exception('exception while accepting')
|
||||
accepted = False
|
||||
if not accepted:
|
||||
logger.debug('pairing rejected by delegate')
|
||||
|
||||
@@ -12,21 +12,21 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from contextlib import contextmanager
|
||||
from enum import IntEnum
|
||||
import logging
|
||||
import struct
|
||||
import datetime
|
||||
from typing import BinaryIO, Generator
|
||||
import os
|
||||
|
||||
from bumble import core
|
||||
from bumble.hci import HCI_COMMAND_PACKET, HCI_EVENT_PACKET
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -12,21 +12,18 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from contextlib import asynccontextmanager
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from bumble import utils
|
||||
from bumble.transport.common import (
|
||||
Transport,
|
||||
SnoopingTransport,
|
||||
TransportSpecError,
|
||||
)
|
||||
from bumble.snoop import create_snooper
|
||||
from bumble.transport.common import SnoopingTransport, Transport, TransportSpecError
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -48,8 +45,8 @@ def _wrap_transport(transport: Transport) -> Transport:
|
||||
return SnoopingTransport.create_with(
|
||||
transport, create_snooper(snooper_spec)
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(f'Exception while creating snooper: {exc}')
|
||||
except Exception:
|
||||
logger.exception('Exception while creating snooper')
|
||||
|
||||
return transport
|
||||
|
||||
@@ -185,12 +182,18 @@ async def _open_transport(scheme: str, spec: Optional[str]) -> Transport:
|
||||
|
||||
return await open_android_netsim_transport(spec)
|
||||
|
||||
if scheme == 'unix':
|
||||
if scheme in ('unix', 'unix-client'):
|
||||
from bumble.transport.unix import open_unix_client_transport
|
||||
|
||||
assert spec
|
||||
return await open_unix_client_transport(spec)
|
||||
|
||||
if scheme == 'unix-server':
|
||||
from bumble.transport.unix import open_unix_server_transport
|
||||
|
||||
assert spec
|
||||
return await open_unix_server_transport(spec)
|
||||
|
||||
raise TransportSpecError('unknown transport scheme')
|
||||
|
||||
|
||||
|
||||
@@ -16,28 +16,27 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import logging
|
||||
import grpc.aio
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
import grpc.aio
|
||||
|
||||
from bumble.transport.common import (
|
||||
PumpedTransport,
|
||||
PumpedPacketSource,
|
||||
PumpedPacketSink,
|
||||
PumpedPacketSource,
|
||||
PumpedTransport,
|
||||
Transport,
|
||||
TransportSpecError,
|
||||
)
|
||||
|
||||
# pylint: disable=no-name-in-module
|
||||
from bumble.transport.grpc_protobuf.emulated_bluetooth_packets_pb2 import HCIPacket
|
||||
from bumble.transport.grpc_protobuf.emulated_bluetooth_pb2_grpc import (
|
||||
EmulatedBluetoothServiceStub,
|
||||
)
|
||||
from bumble.transport.grpc_protobuf.emulated_bluetooth_packets_pb2 import HCIPacket
|
||||
from bumble.transport.grpc_protobuf.emulated_bluetooth_vhci_pb2_grpc import (
|
||||
VhciForwardingServiceStub,
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -77,21 +76,17 @@ async def open_android_emulator_transport(spec: Optional[str]) -> Transport:
|
||||
|
||||
# Parse the parameters
|
||||
mode = 'host'
|
||||
server_host = 'localhost'
|
||||
server_port = '8554'
|
||||
server_address = 'localhost:8554'
|
||||
if spec:
|
||||
params = spec.split(',')
|
||||
for param in params:
|
||||
if param.startswith('mode='):
|
||||
mode = param.split('=')[1]
|
||||
elif ':' in param:
|
||||
server_host, server_port = param.split(':')
|
||||
else:
|
||||
raise TransportSpecError('invalid parameter')
|
||||
server_address = param
|
||||
|
||||
# Connect to the gRPC server
|
||||
server_address = f'{server_host}:{server_port}'
|
||||
logger.debug(f'connecting to gRPC server at {server_address}')
|
||||
logger.debug('connecting to gRPC server at %s', server_address)
|
||||
channel = grpc.aio.insecure_channel(server_address)
|
||||
|
||||
service: Union[EmulatedBluetoothServiceStub, VhciForwardingServiceStub]
|
||||
|
||||
@@ -29,28 +29,27 @@ import grpc.aio
|
||||
import bumble
|
||||
from bumble.transport.common import (
|
||||
ParserSource,
|
||||
PumpedTransport,
|
||||
PumpedPacketSource,
|
||||
PumpedPacketSink,
|
||||
PumpedPacketSource,
|
||||
PumpedTransport,
|
||||
Transport,
|
||||
TransportSpecError,
|
||||
TransportInitError,
|
||||
TransportSpecError,
|
||||
)
|
||||
|
||||
# pylint: disable=no-name-in-module
|
||||
from bumble.transport.grpc_protobuf.netsim.packet_streamer_pb2_grpc import (
|
||||
PacketStreamerStub,
|
||||
PacketStreamerServicer,
|
||||
add_PacketStreamerServicer_to_server,
|
||||
)
|
||||
from bumble.transport.grpc_protobuf.netsim.common_pb2 import ChipKind
|
||||
from bumble.transport.grpc_protobuf.netsim.hci_packet_pb2 import HCIPacket
|
||||
from bumble.transport.grpc_protobuf.netsim.packet_streamer_pb2 import (
|
||||
PacketRequest,
|
||||
PacketResponse,
|
||||
)
|
||||
from bumble.transport.grpc_protobuf.netsim.hci_packet_pb2 import HCIPacket
|
||||
from bumble.transport.grpc_protobuf.netsim.packet_streamer_pb2_grpc import (
|
||||
PacketStreamerServicer,
|
||||
PacketStreamerStub,
|
||||
add_PacketStreamerServicer_to_server,
|
||||
)
|
||||
from bumble.transport.grpc_protobuf.netsim.startup_pb2 import Chip, ChipInfo, DeviceInfo
|
||||
from bumble.transport.grpc_protobuf.netsim.common_pb2 import ChipKind
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -145,8 +144,6 @@ def publish_grpc_port(grpc_port: int, instance_number: int) -> bool:
|
||||
async def open_android_netsim_controller_transport(
|
||||
server_host: Optional[str], server_port: int, options: dict[str, str]
|
||||
) -> Transport:
|
||||
if not server_port:
|
||||
raise TransportSpecError('invalid port')
|
||||
if server_host == '_' or not server_host:
|
||||
server_host = 'localhost'
|
||||
|
||||
@@ -168,14 +165,16 @@ async def open_android_netsim_controller_transport(
|
||||
await self.pump_loop()
|
||||
except asyncio.CancelledError:
|
||||
logger.debug('Pump task canceled')
|
||||
self.done.set_result(None)
|
||||
if not self.done.done():
|
||||
self.done.set_result(None)
|
||||
|
||||
async def pump_loop(self):
|
||||
while True:
|
||||
request = await self.context.read()
|
||||
if request == grpc.aio.EOF:
|
||||
logger.debug('End of request stream')
|
||||
self.done.set_result(None)
|
||||
if not self.done.done():
|
||||
self.done.set_result(None)
|
||||
return
|
||||
|
||||
# If we're not initialized yet, wait for a init packet.
|
||||
@@ -220,6 +219,8 @@ async def open_android_netsim_controller_transport(
|
||||
async def wait_for_termination(self):
|
||||
await self.done
|
||||
|
||||
server_address = f'{server_host}:{server_port}'
|
||||
|
||||
class Server(PacketStreamerServicer, ParserSource):
|
||||
def __init__(self):
|
||||
PacketStreamerServicer.__init__(self)
|
||||
@@ -230,8 +231,8 @@ async def open_android_netsim_controller_transport(
|
||||
# a server listening on that port, we get an exception.
|
||||
self.grpc_server = grpc.aio.server(options=(('grpc.so_reuseport', 0),))
|
||||
add_PacketStreamerServicer_to_server(self, self.grpc_server)
|
||||
self.grpc_server.add_insecure_port(f'{server_host}:{server_port}')
|
||||
logger.debug(f'gRPC server listening on {server_host}:{server_port}')
|
||||
self.port = self.grpc_server.add_insecure_port(server_address)
|
||||
logger.debug('gRPC server listening on %s', server_address)
|
||||
|
||||
async def start(self):
|
||||
logger.debug('Starting gRPC server')
|
||||
@@ -443,7 +444,7 @@ async def open_android_netsim_transport(spec: Optional[str]) -> Transport:
|
||||
params = spec.split(',') if spec else []
|
||||
if params and ':' in params[0]:
|
||||
# Explicit <host>:<port>
|
||||
host, port_str = params[0].split(':')
|
||||
host, port_str = params[0].rsplit(':', maxsplit=1)
|
||||
port = int(port_str)
|
||||
params_offset = 1
|
||||
else:
|
||||
|
||||
@@ -16,19 +16,18 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import contextlib
|
||||
import struct
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import contextlib
|
||||
import io
|
||||
import logging
|
||||
import struct
|
||||
from typing import Any, ContextManager, Optional, Protocol
|
||||
|
||||
from bumble import core
|
||||
from bumble import hci
|
||||
from bumble import core, hci
|
||||
from bumble.colors import color
|
||||
from bumble.snoop import Snooper
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -90,8 +89,8 @@ class PacketPump:
|
||||
try:
|
||||
# Deliver the packet to the sink
|
||||
self.sink.on_packet(await self.reader.next_packet())
|
||||
except Exception as error:
|
||||
logger.warning(f'!!! {error}')
|
||||
except Exception:
|
||||
logger.exception('!!!')
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -158,10 +157,8 @@ class PacketParser:
|
||||
if self.sink:
|
||||
try:
|
||||
self.sink.on_packet(bytes(self.packet))
|
||||
except Exception as error:
|
||||
logger.exception(
|
||||
color(f'!!! Exception in on_packet: {error}', 'red')
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(color('!!! Exception in on_packet', 'red'))
|
||||
self.reset()
|
||||
|
||||
def set_packet_sink(self, sink: TransportSink) -> None:
|
||||
@@ -378,7 +375,7 @@ class PumpedPacketSource(ParserSource):
|
||||
self.terminated.set_result(None)
|
||||
break
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while waiting for packet: {error}')
|
||||
logger.exception('exception while waiting for packet')
|
||||
if not self.terminated.done():
|
||||
self.terminated.set_exception(error)
|
||||
break
|
||||
@@ -409,8 +406,8 @@ class PumpedPacketSink:
|
||||
except asyncio.CancelledError:
|
||||
logger.debug('sink pump task done')
|
||||
break
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while sending packet: {error}')
|
||||
except Exception:
|
||||
logger.exception('exception while sending packet')
|
||||
break
|
||||
|
||||
self.pump_task = asyncio.create_task(pump_packets())
|
||||
|
||||
@@ -19,7 +19,7 @@ import asyncio
|
||||
import io
|
||||
import logging
|
||||
|
||||
from bumble.transport.common import Transport, StreamPacketSource, StreamPacketSink
|
||||
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -16,17 +16,15 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import collections
|
||||
import ctypes
|
||||
import logging
|
||||
import struct
|
||||
import os
|
||||
import socket
|
||||
import ctypes
|
||||
import collections
|
||||
|
||||
import struct
|
||||
from typing import Optional
|
||||
|
||||
from bumble.transport.common import Transport, ParserSource
|
||||
|
||||
from bumble.transport.common import ParserSource, Transport
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -16,16 +16,15 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import atexit
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import pty
|
||||
import tty
|
||||
import io
|
||||
import atexit
|
||||
import os
|
||||
import logging
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from bumble.transport.common import Transport, StreamPacketSource, StreamPacketSink
|
||||
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -19,20 +19,18 @@ import asyncio
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import usb.core
|
||||
import usb.util
|
||||
|
||||
from typing import Optional
|
||||
from usb.core import Device as UsbDevice
|
||||
from usb.core import USBError
|
||||
from usb.util import CTRL_TYPE_CLASS, CTRL_RECIPIENT_OTHER
|
||||
from usb.legacy import REQ_SET_FEATURE, REQ_CLEAR_FEATURE, CLASS_HUB
|
||||
from usb.legacy import CLASS_HUB, REQ_CLEAR_FEATURE, REQ_SET_FEATURE
|
||||
from usb.util import CTRL_RECIPIENT_OTHER, CTRL_TYPE_CLASS
|
||||
|
||||
from bumble.transport.common import Transport, ParserSource, TransportInitError
|
||||
from bumble import hci
|
||||
from bumble.colors import color
|
||||
|
||||
from bumble.transport.common import ParserSource, Transport, TransportInitError
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constant
|
||||
@@ -285,7 +283,7 @@ async def open_pyusb_transport(spec: str) -> Transport:
|
||||
try:
|
||||
device = await _power_cycle(device) # type: ignore
|
||||
except Exception as e:
|
||||
logging.debug(e)
|
||||
logging.debug(e, stack_info=True)
|
||||
logging.info(f"Unable to power cycle {hex(device.idVendor)} {hex(device.idProduct)}") # type: ignore
|
||||
|
||||
# Collect the metadata
|
||||
@@ -371,9 +369,8 @@ async def _power_cycle(device: UsbDevice) -> UsbDevice:
|
||||
|
||||
# Device needs to be find again otherwise it will appear as disconnected
|
||||
return usb.core.find(idVendor=device.idVendor, idProduct=device.idProduct) # type: ignore
|
||||
except USBError as e:
|
||||
logger.error(f"Adjustment needed: Please revise the udev rule for device {hex(device.idVendor)}:{hex(device.idProduct)} for proper recognition.") # type: ignore
|
||||
logger.error(e)
|
||||
except USBError:
|
||||
logger.exception(f"Adjustment needed: Please revise the udev rule for device {hex(device.idVendor)}:{hex(device.idProduct)} for proper recognition.") # type: ignore
|
||||
|
||||
return device
|
||||
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import serial_asyncio
|
||||
|
||||
from bumble.transport.common import Transport, StreamPacketSource, StreamPacketSink
|
||||
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from bumble.transport.common import Transport, StreamPacketSource, StreamPacketSink
|
||||
from bumble.transport.common import StreamPacketSink, StreamPacketSource, Transport
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -41,7 +41,7 @@ async def open_tcp_client_transport(spec: str) -> Transport:
|
||||
logger.debug(f'connection lost: {exc}')
|
||||
self.on_transport_lost()
|
||||
|
||||
remote_host, remote_port = spec.split(':')
|
||||
remote_host, remote_port = spec.rsplit(':', maxsplit=1)
|
||||
tcp_transport, packet_source = await asyncio.get_running_loop().create_connection(
|
||||
TcpPacketSource,
|
||||
host=remote_host,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user