forked from auracaster/bumble_mirror
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5275ade29 | |||
| e7b39c4188 | |||
| 0594eaef09 | |||
| 05200284d2 | |||
| d21da78aa3 | |||
| fbc7cf02a3 | |||
| a8beb6b1ff | |||
| 2d44de611f | |||
| 9874bb3b37 | |||
| 6645ad47ee | |||
| ad27de7717 | |||
| e6fc63b2d8 | |||
| 1321c7da81 | |||
| 5a1b03fd91 | |||
| de47721753 | |||
| 83a76a75d3 | |||
| d5b5ef8313 | |||
| 856a8d53cd | |||
| 177c273a57 |
Vendored
+6
-1
@@ -71,5 +71,10 @@
|
|||||||
"editor.rulers": [88]
|
"editor.rulers": [88]
|
||||||
},
|
},
|
||||||
"python.formatting.provider": "black",
|
"python.formatting.provider": "black",
|
||||||
"pylint.importStrategy": "useBundled"
|
"pylint.importStrategy": "useBundled",
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,6 @@ import re
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import colors
|
|
||||||
|
|
||||||
from prompt_toolkit import Application
|
from prompt_toolkit import Application
|
||||||
from prompt_toolkit.history import FileHistory
|
from prompt_toolkit.history import FileHistory
|
||||||
@@ -53,6 +52,7 @@ from prompt_toolkit.layout import (
|
|||||||
|
|
||||||
from bumble import __version__
|
from bumble import __version__
|
||||||
import bumble.core
|
import bumble.core
|
||||||
|
from bumble import colors
|
||||||
from bumble.core import UUID, AdvertisingData, BT_LE_TRANSPORT
|
from bumble.core import UUID, AdvertisingData, BT_LE_TRANSPORT
|
||||||
from bumble.device import ConnectionParametersPreferences, Device, Connection, Peer
|
from bumble.device import ConnectionParametersPreferences, Device, Connection, Peer
|
||||||
from bumble.utils import AsyncRunner
|
from bumble.utils import AsyncRunner
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import asyncio
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import click
|
import click
|
||||||
from colors import color
|
|
||||||
from bumble.company_ids import COMPANY_IDENTIFIERS
|
from bumble.company_ids import COMPANY_IDENTIFIERS
|
||||||
|
|
||||||
|
from bumble.colors import color
|
||||||
from bumble.core import name_or_number
|
from bumble.core import name_or_number
|
||||||
from bumble.hci import (
|
from bumble.hci import (
|
||||||
map_null_terminated_utf8_string,
|
map_null_terminated_utf8_string,
|
||||||
|
|||||||
+1
-1
@@ -19,9 +19,9 @@ import asyncio
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import click
|
import click
|
||||||
from colors import color
|
|
||||||
|
|
||||||
import bumble.core
|
import bumble.core
|
||||||
|
from bumble.colors import color
|
||||||
from bumble.device import Device, Peer
|
from bumble.device import Device, Peer
|
||||||
from bumble.gatt import show_services
|
from bumble.gatt import show_services
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
|
|||||||
+1
-1
@@ -20,8 +20,8 @@ import os
|
|||||||
import struct
|
import struct
|
||||||
import logging
|
import logging
|
||||||
import click
|
import click
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from bumble.colors import color
|
||||||
from bumble.device import Device, Peer
|
from bumble.device import Device, Peer
|
||||||
from bumble.core import AdvertisingData
|
from bumble.core import AdvertisingData
|
||||||
from bumble.gatt import Service, Characteristic, CharacteristicValue
|
from bumble.gatt import Service, Characteristic, CharacteristicValue
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import click
|
import click
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from bumble.colors import color
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.utils import FlowControlAsyncPipe
|
from bumble.utils import FlowControlAsyncPipe
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ import argparse
|
|||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from colors import color
|
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
|
from bumble.colors import color
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Logging
|
# Logging
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
+78
-43
@@ -19,9 +19,9 @@ import asyncio
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import click
|
import click
|
||||||
import aioconsole
|
from prompt_toolkit.shortcuts import PromptSession
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from bumble.colors import color
|
||||||
from bumble.device import Device, Peer
|
from bumble.device import Device, Peer
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
from bumble.smp import PairingDelegate, PairingConfig
|
from bumble.smp import PairingDelegate, PairingConfig
|
||||||
@@ -42,9 +42,23 @@ from bumble.att import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
class Waiter:
|
||||||
|
instance = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.done = asyncio.get_running_loop().create_future()
|
||||||
|
|
||||||
|
def terminate(self):
|
||||||
|
self.done.set_result(None)
|
||||||
|
|
||||||
|
async def wait_until_terminated(self):
|
||||||
|
return await self.done
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Delegate(PairingDelegate):
|
class Delegate(PairingDelegate):
|
||||||
def __init__(self, mode, connection, capability_string, prompt):
|
def __init__(self, mode, connection, capability_string, do_prompt):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
{
|
{
|
||||||
'keyboard': PairingDelegate.KEYBOARD_INPUT_ONLY,
|
'keyboard': PairingDelegate.KEYBOARD_INPUT_ONLY,
|
||||||
@@ -58,7 +72,18 @@ class Delegate(PairingDelegate):
|
|||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.peer = Peer(connection)
|
self.peer = Peer(connection)
|
||||||
self.peer_name = None
|
self.peer_name = None
|
||||||
self.prompt = prompt
|
self.do_prompt = do_prompt
|
||||||
|
|
||||||
|
def print(self, message):
|
||||||
|
print(color(message, 'yellow'))
|
||||||
|
|
||||||
|
async def prompt(self, message):
|
||||||
|
# Wait a bit to allow some of the log lines to print before we prompt
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
session = PromptSession(message)
|
||||||
|
response = await session.prompt_async()
|
||||||
|
return response.lower().strip()
|
||||||
|
|
||||||
async def update_peer_name(self):
|
async def update_peer_name(self):
|
||||||
if self.peer_name is not None:
|
if self.peer_name is not None:
|
||||||
@@ -73,19 +98,15 @@ class Delegate(PairingDelegate):
|
|||||||
self.peer_name = '[?]'
|
self.peer_name = '[?]'
|
||||||
|
|
||||||
async def accept(self):
|
async def accept(self):
|
||||||
if self.prompt:
|
if self.do_prompt:
|
||||||
await self.update_peer_name()
|
await self.update_peer_name()
|
||||||
|
|
||||||
# Wait a bit to allow some of the log lines to print before we prompt
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
# Prompt for acceptance
|
# Prompt for acceptance
|
||||||
print(color('###-----------------------------------', 'yellow'))
|
self.print('###-----------------------------------')
|
||||||
print(color(f'### Pairing request from {self.peer_name}', 'yellow'))
|
self.print(f'### Pairing request from {self.peer_name}')
|
||||||
print(color('###-----------------------------------', 'yellow'))
|
self.print('###-----------------------------------')
|
||||||
while True:
|
while True:
|
||||||
response = await aioconsole.ainput(color('>>> Accept? ', 'yellow'))
|
response = await self.prompt('>>> Accept? ')
|
||||||
response = response.lower().strip()
|
|
||||||
|
|
||||||
if response == 'yes':
|
if response == 'yes':
|
||||||
return True
|
return True
|
||||||
@@ -96,23 +117,17 @@ class Delegate(PairingDelegate):
|
|||||||
# Accept silently
|
# Accept silently
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def compare_numbers(self, number, digits=6):
|
async def compare_numbers(self, number, digits):
|
||||||
await self.update_peer_name()
|
await self.update_peer_name()
|
||||||
|
|
||||||
# Wait a bit to allow some of the log lines to print before we prompt
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
# Prompt for a numeric comparison
|
# Prompt for a numeric comparison
|
||||||
print(color('###-----------------------------------', 'yellow'))
|
self.print('###-----------------------------------')
|
||||||
print(color(f'### Pairing with {self.peer_name}', 'yellow'))
|
self.print(f'### Pairing with {self.peer_name}')
|
||||||
print(color('###-----------------------------------', 'yellow'))
|
self.print('###-----------------------------------')
|
||||||
while True:
|
while True:
|
||||||
response = await aioconsole.ainput(
|
response = await self.prompt(
|
||||||
color(
|
f'>>> Does the other device display {number:0{digits}}? '
|
||||||
f'>>> Does the other device display {number:0{digits}}? ', 'yellow'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
response = response.lower().strip()
|
|
||||||
|
|
||||||
if response == 'yes':
|
if response == 'yes':
|
||||||
return True
|
return True
|
||||||
@@ -123,30 +138,24 @@ class Delegate(PairingDelegate):
|
|||||||
async def get_number(self):
|
async def get_number(self):
|
||||||
await self.update_peer_name()
|
await self.update_peer_name()
|
||||||
|
|
||||||
# Wait a bit to allow some of the log lines to print before we prompt
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
# Prompt for a PIN
|
# Prompt for a PIN
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
print(color('###-----------------------------------', 'yellow'))
|
self.print('###-----------------------------------')
|
||||||
print(color(f'### Pairing with {self.peer_name}', 'yellow'))
|
self.print(f'### Pairing with {self.peer_name}')
|
||||||
print(color('###-----------------------------------', 'yellow'))
|
self.print('###-----------------------------------')
|
||||||
return int(await aioconsole.ainput(color('>>> Enter PIN: ', 'yellow')))
|
return int(await self.prompt('>>> Enter PIN: '))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def display_number(self, number, digits=6):
|
async def display_number(self, number, digits):
|
||||||
await self.update_peer_name()
|
await self.update_peer_name()
|
||||||
|
|
||||||
# Wait a bit to allow some of the log lines to print before we prompt
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
# Display a PIN code
|
# Display a PIN code
|
||||||
print(color('###-----------------------------------', 'yellow'))
|
self.print('###-----------------------------------')
|
||||||
print(color(f'### Pairing with {self.peer_name}', 'yellow'))
|
self.print(f'### Pairing with {self.peer_name}')
|
||||||
print(color(f'### PIN: {number:0{digits}}', 'yellow'))
|
self.print(f'### PIN: {number:0{digits}}')
|
||||||
print(color('###-----------------------------------', 'yellow'))
|
self.print('###-----------------------------------')
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -238,6 +247,7 @@ def on_pairing(keys):
|
|||||||
print(color('*** Paired!', 'cyan'))
|
print(color('*** Paired!', 'cyan'))
|
||||||
keys.print(prefix=color('*** ', 'cyan'))
|
keys.print(prefix=color('*** ', 'cyan'))
|
||||||
print(color('***-----------------------------------', 'cyan'))
|
print(color('***-----------------------------------', 'cyan'))
|
||||||
|
Waiter.instance.terminate()
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -245,6 +255,7 @@ def on_pairing_failure(reason):
|
|||||||
print(color('***-----------------------------------', 'red'))
|
print(color('***-----------------------------------', 'red'))
|
||||||
print(color(f'*** Pairing failed: {smp_error_name(reason)}', 'red'))
|
print(color(f'*** Pairing failed: {smp_error_name(reason)}', 'red'))
|
||||||
print(color('***-----------------------------------', 'red'))
|
print(color('***-----------------------------------', 'red'))
|
||||||
|
Waiter.instance.terminate()
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -262,6 +273,8 @@ async def pair(
|
|||||||
hci_transport,
|
hci_transport,
|
||||||
address_or_name,
|
address_or_name,
|
||||||
):
|
):
|
||||||
|
Waiter.instance = Waiter()
|
||||||
|
|
||||||
print('<<< connecting to HCI...')
|
print('<<< connecting to HCI...')
|
||||||
async with await open_transport_or_link(hci_transport) as (hci_source, hci_sink):
|
async with await open_transport_or_link(hci_transport) as (hci_source, hci_sink):
|
||||||
print('<<< connected')
|
print('<<< connected')
|
||||||
@@ -332,7 +345,19 @@ async def pair(
|
|||||||
# Advertise so that peers can find us and connect
|
# Advertise so that peers can find us and connect
|
||||||
await device.start_advertising(auto_restart=True)
|
await device.start_advertising(auto_restart=True)
|
||||||
|
|
||||||
await hci_source.wait_for_termination()
|
# Run until the user asks to exit
|
||||||
|
await Waiter.instance.wait_until_terminated()
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
class LogHandler(logging.Handler):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setFormatter(logging.Formatter('%(levelname)s:%(name)s:%(message)s'))
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
message = self.format(record)
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -366,7 +391,11 @@ async def pair(
|
|||||||
'--request', is_flag=True, help='Request that the connecting peer initiate pairing'
|
'--request', is_flag=True, help='Request that the connecting peer initiate pairing'
|
||||||
)
|
)
|
||||||
@click.option('--print-keys', is_flag=True, help='Print the bond keys before pairing')
|
@click.option('--print-keys', is_flag=True, help='Print the bond keys before pairing')
|
||||||
@click.option('--keystore-file', help='File in which to store the pairing keys')
|
@click.option(
|
||||||
|
'--keystore-file',
|
||||||
|
metavar='<filename>',
|
||||||
|
help='File in which to store the pairing keys',
|
||||||
|
)
|
||||||
@click.argument('device-config')
|
@click.argument('device-config')
|
||||||
@click.argument('hci_transport')
|
@click.argument('hci_transport')
|
||||||
@click.argument('address-or-name', required=False)
|
@click.argument('address-or-name', required=False)
|
||||||
@@ -384,7 +413,13 @@ def main(
|
|||||||
hci_transport,
|
hci_transport,
|
||||||
address_or_name,
|
address_or_name,
|
||||||
):
|
):
|
||||||
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper())
|
# Setup logging
|
||||||
|
log_handler = LogHandler()
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.addHandler(log_handler)
|
||||||
|
root_logger.setLevel(os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper())
|
||||||
|
|
||||||
|
# Pair
|
||||||
asyncio.run(
|
asyncio.run(
|
||||||
pair(
|
pair(
|
||||||
mode,
|
mode,
|
||||||
|
|||||||
+1
-1
@@ -19,8 +19,8 @@ import asyncio
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import click
|
import click
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from bumble.colors import color
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
from bumble.keys import JsonKeyStore
|
from bumble.keys import JsonKeyStore
|
||||||
|
|||||||
+1
-1
@@ -17,8 +17,8 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import struct
|
import struct
|
||||||
import click
|
import click
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from bumble.colors import color
|
||||||
from bumble import hci
|
from bumble import hci
|
||||||
from bumble.transport.common import PacketReader
|
from bumble.transport.common import PacketReader
|
||||||
from bumble.helpers import PacketTracer
|
from bumble.helpers import PacketTracer
|
||||||
|
|||||||
+1
-1
@@ -30,8 +30,8 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
import click
|
import click
|
||||||
import usb1
|
import usb1
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from bumble.colors import color
|
||||||
from bumble.transport.usb import load_libusb
|
from bumble.transport.usb import load_libusb
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+45
-30
@@ -18,7 +18,6 @@
|
|||||||
import struct
|
import struct
|
||||||
import logging
|
import logging
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import construct
|
|
||||||
|
|
||||||
from .company_ids import COMPANY_IDENTIFIERS
|
from .company_ids import COMPANY_IDENTIFIERS
|
||||||
from .sdp import (
|
from .sdp import (
|
||||||
@@ -258,17 +257,6 @@ class SbcMediaCodecInformation(
|
|||||||
A2DP spec - 4.3.2 Codec Specific Information Elements
|
A2DP spec - 4.3.2 Codec Specific Information Elements
|
||||||
'''
|
'''
|
||||||
|
|
||||||
BIT_FIELDS = construct.Bitwise(
|
|
||||||
construct.Sequence(
|
|
||||||
construct.BitsInteger(4),
|
|
||||||
construct.BitsInteger(4),
|
|
||||||
construct.BitsInteger(4),
|
|
||||||
construct.BitsInteger(2),
|
|
||||||
construct.BitsInteger(2),
|
|
||||||
construct.BitsInteger(8),
|
|
||||||
construct.BitsInteger(8),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
SAMPLING_FREQUENCY_BITS = {16000: 1 << 3, 32000: 1 << 2, 44100: 1 << 1, 48000: 1}
|
SAMPLING_FREQUENCY_BITS = {16000: 1 << 3, 32000: 1 << 2, 44100: 1 << 1, 48000: 1}
|
||||||
CHANNEL_MODE_BITS = {
|
CHANNEL_MODE_BITS = {
|
||||||
SBC_MONO_CHANNEL_MODE: 1 << 3,
|
SBC_MONO_CHANNEL_MODE: 1 << 3,
|
||||||
@@ -284,9 +272,22 @@ class SbcMediaCodecInformation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bytes(data):
|
def from_bytes(data: bytes) -> 'SbcMediaCodecInformation':
|
||||||
|
sampling_frequency = (data[0] >> 4) & 0x0F
|
||||||
|
channel_mode = (data[0] >> 0) & 0x0F
|
||||||
|
block_length = (data[1] >> 4) & 0x0F
|
||||||
|
subbands = (data[1] >> 2) & 0x03
|
||||||
|
allocation_method = (data[1] >> 0) & 0x03
|
||||||
|
minimum_bitpool_value = (data[2] >> 0) & 0xFF
|
||||||
|
maximum_bitpool_value = (data[3] >> 0) & 0xFF
|
||||||
return SbcMediaCodecInformation(
|
return SbcMediaCodecInformation(
|
||||||
*SbcMediaCodecInformation.BIT_FIELDS.parse(data)
|
sampling_frequency,
|
||||||
|
channel_mode,
|
||||||
|
block_length,
|
||||||
|
subbands,
|
||||||
|
allocation_method,
|
||||||
|
minimum_bitpool_value,
|
||||||
|
maximum_bitpool_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -335,8 +336,17 @@ class SbcMediaCodecInformation(
|
|||||||
maximum_bitpool_value=maximum_bitpool_value,
|
maximum_bitpool_value=maximum_bitpool_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __bytes__(self):
|
def __bytes__(self) -> bytes:
|
||||||
return self.BIT_FIELDS.build(self)
|
return bytes(
|
||||||
|
[
|
||||||
|
(self.sampling_frequency << 4) | self.channel_mode,
|
||||||
|
(self.block_length << 4)
|
||||||
|
| (self.subbands << 2)
|
||||||
|
| self.allocation_method,
|
||||||
|
self.minimum_bitpool_value,
|
||||||
|
self.maximum_bitpool_value,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
channel_modes = ['MONO', 'DUAL_CHANNEL', 'STEREO', 'JOINT_STEREO']
|
channel_modes = ['MONO', 'DUAL_CHANNEL', 'STEREO', 'JOINT_STEREO']
|
||||||
@@ -367,16 +377,6 @@ class AacMediaCodecInformation(
|
|||||||
A2DP spec - 4.5.2 Codec Specific Information Elements
|
A2DP spec - 4.5.2 Codec Specific Information Elements
|
||||||
'''
|
'''
|
||||||
|
|
||||||
BIT_FIELDS = construct.Bitwise(
|
|
||||||
construct.Sequence(
|
|
||||||
construct.BitsInteger(8),
|
|
||||||
construct.BitsInteger(12),
|
|
||||||
construct.BitsInteger(2),
|
|
||||||
construct.BitsInteger(2),
|
|
||||||
construct.BitsInteger(1),
|
|
||||||
construct.BitsInteger(23),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
OBJECT_TYPE_BITS = {
|
OBJECT_TYPE_BITS = {
|
||||||
MPEG_2_AAC_LC_OBJECT_TYPE: 1 << 7,
|
MPEG_2_AAC_LC_OBJECT_TYPE: 1 << 7,
|
||||||
MPEG_4_AAC_LC_OBJECT_TYPE: 1 << 6,
|
MPEG_4_AAC_LC_OBJECT_TYPE: 1 << 6,
|
||||||
@@ -400,9 +400,15 @@ class AacMediaCodecInformation(
|
|||||||
CHANNELS_BITS = {1: 1 << 1, 2: 1}
|
CHANNELS_BITS = {1: 1 << 1, 2: 1}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bytes(data):
|
def from_bytes(data: bytes) -> 'AacMediaCodecInformation':
|
||||||
|
object_type = data[0]
|
||||||
|
sampling_frequency = (data[1] << 4) | ((data[2] >> 4) & 0x0F)
|
||||||
|
channels = (data[2] >> 2) & 0x03
|
||||||
|
rfa = 0
|
||||||
|
vbr = (data[3] >> 7) & 0x01
|
||||||
|
bitrate = ((data[3] & 0x7F) << 16) | (data[4] << 8) | data[5]
|
||||||
return AacMediaCodecInformation(
|
return AacMediaCodecInformation(
|
||||||
*AacMediaCodecInformation.BIT_FIELDS.parse(data)
|
object_type, sampling_frequency, channels, rfa, vbr, bitrate
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -430,8 +436,17 @@ class AacMediaCodecInformation(
|
|||||||
bitrate=bitrate,
|
bitrate=bitrate,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __bytes__(self):
|
def __bytes__(self) -> bytes:
|
||||||
return self.BIT_FIELDS.build(self)
|
return bytes(
|
||||||
|
[
|
||||||
|
self.object_type & 0xFF,
|
||||||
|
(self.sampling_frequency >> 4) & 0xFF,
|
||||||
|
(((self.sampling_frequency & 0x0F) << 4) | (self.channels << 2)) & 0xFF,
|
||||||
|
((self.vbr << 7) | ((self.bitrate >> 16) & 0x7F)) & 0xFF,
|
||||||
|
((self.bitrate >> 8) & 0xFF) & 0xFF,
|
||||||
|
self.bitrate & 0xFF,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
object_types = [
|
object_types = [
|
||||||
|
|||||||
+1
-1
@@ -24,12 +24,12 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import struct
|
import struct
|
||||||
from colors import color
|
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
from typing import Dict, Type
|
from typing import Dict, Type
|
||||||
|
|
||||||
from bumble.core import UUID, name_or_number
|
from bumble.core import UUID, name_or_number
|
||||||
from bumble.hci import HCI_Object, key_with_value
|
from bumble.hci import HCI_Object, key_with_value
|
||||||
|
from bumble.colors import color
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,6 @@ import asyncio
|
|||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
from typing import Dict, Type
|
from typing import Dict, Type
|
||||||
|
|
||||||
@@ -40,6 +39,7 @@ from .a2dp import (
|
|||||||
VendorSpecificMediaCodecInformation,
|
VendorSpecificMediaCodecInformation,
|
||||||
)
|
)
|
||||||
from . import sdp
|
from . import sdp
|
||||||
|
from .colors import color
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Logging
|
# Logging
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and distribute this software for any
|
||||||
|
# purpose with or without fee is hereby granted, provided that the above
|
||||||
|
# copyright notice and this permission notice appear in all copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
|
||||||
|
# ANSI color names. There is also a "default"
|
||||||
|
COLORS = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
|
||||||
|
|
||||||
|
# ANSI style names
|
||||||
|
STYLES = (
|
||||||
|
'none',
|
||||||
|
'bold',
|
||||||
|
'faint',
|
||||||
|
'italic',
|
||||||
|
'underline',
|
||||||
|
'blink',
|
||||||
|
'blink2',
|
||||||
|
'negative',
|
||||||
|
'concealed',
|
||||||
|
'crossed',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ColorSpec = Union[str, int]
|
||||||
|
|
||||||
|
|
||||||
|
def _join(*values: ColorSpec) -> str:
|
||||||
|
return ';'.join(str(v) for v in values)
|
||||||
|
|
||||||
|
|
||||||
|
def _color_code(spec: ColorSpec, base: int) -> str:
|
||||||
|
if isinstance(spec, str):
|
||||||
|
spec = spec.strip().lower()
|
||||||
|
|
||||||
|
if spec == 'default':
|
||||||
|
return _join(base + 9)
|
||||||
|
elif spec in COLORS:
|
||||||
|
return _join(base + COLORS.index(spec))
|
||||||
|
elif isinstance(spec, int) and 0 <= spec <= 255:
|
||||||
|
return _join(base + 8, 5, spec)
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid color spec "%s"' % spec)
|
||||||
|
|
||||||
|
|
||||||
|
def color(
|
||||||
|
s: str,
|
||||||
|
fg: Optional[ColorSpec] = None,
|
||||||
|
bg: Optional[ColorSpec] = None,
|
||||||
|
style: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
|
codes: List[ColorSpec] = []
|
||||||
|
|
||||||
|
if fg:
|
||||||
|
codes.append(_color_code(fg, 30))
|
||||||
|
if bg:
|
||||||
|
codes.append(_color_code(bg, 40))
|
||||||
|
if style:
|
||||||
|
for style_part in style.split('+'):
|
||||||
|
if style_part in STYLES:
|
||||||
|
codes.append(STYLES.index(style_part))
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid style "%s"' % style_part)
|
||||||
|
|
||||||
|
if codes:
|
||||||
|
return '\x1b[{0}m{1}\x1b[0m'.format(_join(*codes), s)
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
# Foreground color shortcuts
|
||||||
|
black = partial(color, fg='black')
|
||||||
|
red = partial(color, fg='red')
|
||||||
|
green = partial(color, fg='green')
|
||||||
|
yellow = partial(color, fg='yellow')
|
||||||
|
blue = partial(color, fg='blue')
|
||||||
|
magenta = partial(color, fg='magenta')
|
||||||
|
cyan = partial(color, fg='cyan')
|
||||||
|
white = partial(color, fg='white')
|
||||||
|
|
||||||
|
# Style shortcuts
|
||||||
|
bold = partial(color, style='bold')
|
||||||
|
none = partial(color, style='none')
|
||||||
|
faint = partial(color, style='faint')
|
||||||
|
italic = partial(color, style='italic')
|
||||||
|
underline = partial(color, style='underline')
|
||||||
|
blink = partial(color, style='blink')
|
||||||
|
blink2 = partial(color, style='blink2')
|
||||||
|
negative = partial(color, style='negative')
|
||||||
|
concealed = partial(color, style='concealed')
|
||||||
|
crossed = partial(color, style='crossed')
|
||||||
@@ -20,7 +20,7 @@ import asyncio
|
|||||||
import itertools
|
import itertools
|
||||||
import random
|
import random
|
||||||
import struct
|
import struct
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
from bumble.core import BT_CENTRAL_ROLE, BT_PERIPHERAL_ROLE
|
from bumble.core import BT_CENTRAL_ROLE, BT_PERIPHERAL_ROLE
|
||||||
|
|
||||||
from bumble.hci import (
|
from bumble.hci import (
|
||||||
|
|||||||
+2
-13
@@ -25,8 +25,7 @@ from contextlib import asynccontextmanager, AsyncExitStack
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union
|
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from colors import color
|
from .colors import color
|
||||||
|
|
||||||
from .att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
|
from .att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
|
||||||
from .gatt import Characteristic, Descriptor, Service
|
from .gatt import Characteristic, Descriptor, Service
|
||||||
from .hci import (
|
from .hci import (
|
||||||
@@ -1827,7 +1826,7 @@ class Device(CompositeEventEmitter):
|
|||||||
set.
|
set.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
* A `connect` to the same peer will also complete this call.
|
* A `connect` to the same peer will not complete this call.
|
||||||
* The `timeout` parameter is only handled while waiting for the connection
|
* The `timeout` parameter is only handled while waiting for the connection
|
||||||
request, once received and accepted, the controller shall issue a connection
|
request, once received and accepted, the controller shall issue a connection
|
||||||
complete event.
|
complete event.
|
||||||
@@ -2409,16 +2408,6 @@ class Device(CompositeEventEmitter):
|
|||||||
)
|
)
|
||||||
self.connections[connection_handle] = connection
|
self.connections[connection_handle] = connection
|
||||||
|
|
||||||
# We may have an accept ongoing waiting for a connection request for
|
|
||||||
# `peer_address`.
|
|
||||||
# Typically happen when using `connect` to the same `peer_address` we are
|
|
||||||
# waiting for with an `accept`.
|
|
||||||
# In this case, set the completed `connection` to the `accept` future
|
|
||||||
# result.
|
|
||||||
if peer_address in self.classic_pending_accepts:
|
|
||||||
future, *_ = self.classic_pending_accepts.pop(peer_address)
|
|
||||||
future.set_result(connection)
|
|
||||||
|
|
||||||
# Emit an event to notify listeners of the new connection
|
# Emit an event to notify listeners of the new connection
|
||||||
self.emit('connection', connection)
|
self.emit('connection', connection)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+1
-1
@@ -29,8 +29,8 @@ import functools
|
|||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from typing import Optional, Sequence
|
from typing import Optional, Sequence
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
from .core import UUID, get_dict_key_by_value
|
from .core import UUID, get_dict_key_by_value
|
||||||
from .att import Attribute
|
from .att import Attribute
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from colors import color
|
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
from .hci import HCI_Constant
|
from .hci import HCI_Constant
|
||||||
from .att import (
|
from .att import (
|
||||||
ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ from collections import defaultdict
|
|||||||
import struct
|
import struct
|
||||||
from typing import List, Tuple, Optional
|
from typing import List, Tuple, Optional
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
from .core import UUID
|
from .core import UUID
|
||||||
from .att import (
|
from .att import (
|
||||||
ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
||||||
|
|||||||
+9
-1
@@ -20,9 +20,9 @@ import struct
|
|||||||
import collections
|
import collections
|
||||||
import logging
|
import logging
|
||||||
import functools
|
import functools
|
||||||
from colors import color
|
|
||||||
from typing import Dict, Type, Union
|
from typing import Dict, Type, Union
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
from .core import (
|
from .core import (
|
||||||
BT_BR_EDR_TRANSPORT,
|
BT_BR_EDR_TRANSPORT,
|
||||||
AdvertisingData,
|
AdvertisingData,
|
||||||
@@ -1846,6 +1846,8 @@ class HCI_Packet:
|
|||||||
Abstract Base class for HCI packets
|
Abstract Base class for HCI packets
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
hci_packet_type: int
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bytes(packet):
|
def from_bytes(packet):
|
||||||
packet_type = packet[0]
|
packet_type = packet[0]
|
||||||
@@ -1864,6 +1866,9 @@ class HCI_Packet:
|
|||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
def __bytes__(self) -> bytes:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@@ -1875,6 +1880,9 @@ class HCI_CustomPacket(HCI_Packet):
|
|||||||
self.hci_packet_type = payload[0]
|
self.hci_packet_type = payload[0]
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
|
|
||||||
|
def __bytes__(self) -> bytes:
|
||||||
|
return self.payload
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class HCI_Command(HCI_Packet):
|
class HCI_Command(HCI_Packet):
|
||||||
|
|||||||
+1
-1
@@ -16,8 +16,8 @@
|
|||||||
# Imports
|
# Imports
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
from .att import ATT_CID, ATT_PDU
|
from .att import ATT_CID, ATT_PDU
|
||||||
from .smp import SMP_CID, SMP_Command
|
from .smp import SMP_CID, SMP_Command
|
||||||
from .core import name_or_number
|
from .core import name_or_number
|
||||||
|
|||||||
+2
-1
@@ -18,7 +18,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
import collections
|
import collections
|
||||||
from colors import color
|
|
||||||
|
from .colors import color
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
+9
-2
@@ -20,9 +20,9 @@ import collections
|
|||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
from bumble.l2cap import L2CAP_PDU
|
from bumble.l2cap import L2CAP_PDU
|
||||||
|
from bumble.snoop import Snooper
|
||||||
|
|
||||||
from .hci import (
|
from .hci import (
|
||||||
HCI_ACL_DATA_PACKET,
|
HCI_ACL_DATA_PACKET,
|
||||||
@@ -134,6 +134,7 @@ class Host(AbortableEventEmitter):
|
|||||||
self.long_term_key_provider = None
|
self.long_term_key_provider = None
|
||||||
self.link_key_provider = None
|
self.link_key_provider = None
|
||||||
self.pairing_io_capability_provider = None # Classic only
|
self.pairing_io_capability_provider = None # Classic only
|
||||||
|
self.snooper = None
|
||||||
|
|
||||||
# Connect to the source and sink if specified
|
# Connect to the source and sink if specified
|
||||||
if controller_source:
|
if controller_source:
|
||||||
@@ -274,6 +275,9 @@ class Host(AbortableEventEmitter):
|
|||||||
self.hci_sink = sink
|
self.hci_sink = sink
|
||||||
|
|
||||||
def send_hci_packet(self, packet):
|
def send_hci_packet(self, packet):
|
||||||
|
if self.snooper:
|
||||||
|
self.snooper.snoop(packet, Snooper.Direction.HOST_TO_CONTROLLER)
|
||||||
|
|
||||||
self.hci_sink.on_packet(packet.to_bytes())
|
self.hci_sink.on_packet(packet.to_bytes())
|
||||||
|
|
||||||
async def send_command(self, command, check_result=False):
|
async def send_command(self, command, check_result=False):
|
||||||
@@ -420,6 +424,9 @@ class Host(AbortableEventEmitter):
|
|||||||
def on_hci_packet(self, packet):
|
def on_hci_packet(self, packet):
|
||||||
logger.debug(f'{color("### CONTROLLER -> HOST", "green")}: {packet}')
|
logger.debug(f'{color("### CONTROLLER -> HOST", "green")}: {packet}')
|
||||||
|
|
||||||
|
if self.snooper:
|
||||||
|
self.snooper.snoop(packet, Snooper.Direction.CONTROLLER_TO_HOST)
|
||||||
|
|
||||||
# If the packet is a command, invoke the handler for this packet
|
# If the packet is a command, invoke the handler for this packet
|
||||||
if packet.hci_packet_type == HCI_COMMAND_PACKET:
|
if packet.hci_packet_type == HCI_COMMAND_PACKET:
|
||||||
self.on_hci_command_packet(packet)
|
self.on_hci_command_packet(packet)
|
||||||
|
|||||||
+3
-3
@@ -25,8 +25,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
from .hci import Address
|
from .hci import Address
|
||||||
|
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ class PairingKeys:
|
|||||||
for (key_property, key_value) in value.items():
|
for (key_property, key_value) in value.items():
|
||||||
print(f'{prefix} {color(key_property, "green")}: {key_value}')
|
print(f'{prefix} {color(key_property, "green")}: {key_value}')
|
||||||
else:
|
else:
|
||||||
print(f'{prefix}{color(property, "cyan")}: {value}')
|
print(f'{prefix}{color(container_property, "cyan")}: {value}')
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -217,7 +217,7 @@ class JsonKeyStore(KeyStore):
|
|||||||
params = device_config.keystore.split(':', 1)[1:]
|
params = device_config.keystore.split(':', 1)[1:]
|
||||||
namespace = str(device_config.address)
|
namespace = str(device_config.address)
|
||||||
if params:
|
if params:
|
||||||
filename = params[1]
|
filename = params[0]
|
||||||
else:
|
else:
|
||||||
filename = None
|
filename = None
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -21,10 +21,10 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from colors import color
|
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
from typing import Dict, Type
|
from typing import Dict, Type
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
|
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
|
||||||
from .hci import (
|
from .hci import (
|
||||||
HCI_LE_Connection_Update_Command,
|
HCI_LE_Connection_Update_Command,
|
||||||
|
|||||||
+3
-3
@@ -19,9 +19,7 @@ import logging
|
|||||||
import asyncio
|
import asyncio
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
import websockets
|
|
||||||
|
|
||||||
from bumble.hci import (
|
from bumble.hci import (
|
||||||
Address,
|
Address,
|
||||||
HCI_SUCCESS,
|
HCI_SUCCESS,
|
||||||
@@ -220,6 +218,8 @@ class RemoteLink:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def run_connection(self):
|
async def run_connection(self):
|
||||||
|
import websockets # lazy import
|
||||||
|
|
||||||
# Connect to the relay
|
# Connect to the relay
|
||||||
logger.debug(f'connecting to {self.uri}')
|
logger.debug(f'connecting to {self.uri}')
|
||||||
# pylint: disable-next=no-member
|
# pylint: disable-next=no-member
|
||||||
|
|||||||
+1
-1
@@ -18,10 +18,10 @@
|
|||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from colors import color
|
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
|
|
||||||
from . import core
|
from . import core
|
||||||
|
from .colors import color
|
||||||
from .core import BT_BR_EDR_TRANSPORT, InvalidStateError, ProtocolError
|
from .core import BT_BR_EDR_TRANSPORT, InvalidStateError, ProtocolError
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
+2
-3
@@ -18,11 +18,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
from colors import color
|
|
||||||
import colors
|
|
||||||
from typing import Dict, List, Type
|
from typing import Dict, List, Type
|
||||||
|
|
||||||
from . import core
|
from . import core
|
||||||
|
from .colors import color
|
||||||
from .core import InvalidStateError
|
from .core import InvalidStateError
|
||||||
from .hci import HCI_Object, name_or_number, key_with_value
|
from .hci import HCI_Object, name_or_number, key_with_value
|
||||||
|
|
||||||
@@ -506,7 +505,7 @@ class ServiceAttribute:
|
|||||||
def to_string(self, with_colors=False):
|
def to_string(self, with_colors=False):
|
||||||
if with_colors:
|
if with_colors:
|
||||||
return (
|
return (
|
||||||
f'Attribute(id={colors.color(self.id_name(self.id),"magenta")},'
|
f'Attribute(id={color(self.id_name(self.id),"magenta")},'
|
||||||
f'value={self.value})'
|
f'value={self.value})'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+48
-30
@@ -29,8 +29,8 @@ import secrets
|
|||||||
from typing import Dict, Optional, Type
|
from typing import Dict, Optional, Type
|
||||||
|
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
from colors import color
|
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
from .hci import Address, HCI_LE_Enable_Encryption_Command, HCI_Object, key_with_value
|
from .hci import Address, HCI_LE_Enable_Encryption_Command, HCI_Object, key_with_value
|
||||||
from .core import (
|
from .core import (
|
||||||
BT_BR_EDR_TRANSPORT,
|
BT_BR_EDR_TRANSPORT,
|
||||||
@@ -498,7 +498,7 @@ class PairingDelegate:
|
|||||||
DISPLAY_OUTPUT_ONLY = SMP_DISPLAY_ONLY_IO_CAPABILITY
|
DISPLAY_OUTPUT_ONLY = SMP_DISPLAY_ONLY_IO_CAPABILITY
|
||||||
DISPLAY_OUTPUT_AND_YES_NO_INPUT = SMP_DISPLAY_YES_NO_IO_CAPABILITY
|
DISPLAY_OUTPUT_AND_YES_NO_INPUT = SMP_DISPLAY_YES_NO_IO_CAPABILITY
|
||||||
DISPLAY_OUTPUT_AND_KEYBOARD_INPUT = SMP_KEYBOARD_DISPLAY_IO_CAPABILITY
|
DISPLAY_OUTPUT_AND_KEYBOARD_INPUT = SMP_KEYBOARD_DISPLAY_IO_CAPABILITY
|
||||||
DEFAULT_KEY_DISTRIBUTION = (
|
DEFAULT_KEY_DISTRIBUTION: int = (
|
||||||
SMP_ENC_KEY_DISTRIBUTION_FLAG | SMP_ID_KEY_DISTRIBUTION_FLAG
|
SMP_ENC_KEY_DISTRIBUTION_FLAG | SMP_ID_KEY_DISTRIBUTION_FLAG
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -518,13 +518,15 @@ class PairingDelegate:
|
|||||||
async def confirm(self) -> bool:
|
async def confirm(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def compare_numbers(self, _number: int, _digits: int = 6) -> bool:
|
# pylint: disable-next=unused-argument
|
||||||
|
async def compare_numbers(self, number: int, digits: int) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_number(self) -> int:
|
async def get_number(self) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
async def display_number(self, _number: int, _digits: int = 6) -> None:
|
# pylint: disable-next=unused-argument
|
||||||
|
async def display_number(self, number: int, digits: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def key_distribution_response(
|
async def key_distribution_response(
|
||||||
@@ -661,7 +663,8 @@ class Session:
|
|||||||
self.peer_expected_distributions = []
|
self.peer_expected_distributions = []
|
||||||
self.dh_key = None
|
self.dh_key = None
|
||||||
self.confirm_value = None
|
self.confirm_value = None
|
||||||
self.passkey = 0
|
self.passkey = None
|
||||||
|
self.passkey_ready = asyncio.Event()
|
||||||
self.passkey_step = 0
|
self.passkey_step = 0
|
||||||
self.passkey_display = False
|
self.passkey_display = False
|
||||||
self.pairing_method = 0
|
self.pairing_method = 0
|
||||||
@@ -839,6 +842,7 @@ class Session:
|
|||||||
# Generate random Passkey/PIN code
|
# Generate random Passkey/PIN code
|
||||||
self.passkey = secrets.randbelow(1000000)
|
self.passkey = secrets.randbelow(1000000)
|
||||||
logger.debug(f'Pairing PIN CODE: {self.passkey:06}')
|
logger.debug(f'Pairing PIN CODE: {self.passkey:06}')
|
||||||
|
self.passkey_ready.set()
|
||||||
|
|
||||||
# The value of TK is computed from the PIN code
|
# The value of TK is computed from the PIN code
|
||||||
if not self.sc:
|
if not self.sc:
|
||||||
@@ -859,6 +863,8 @@ class Session:
|
|||||||
self.tk = passkey.to_bytes(16, byteorder='little')
|
self.tk = passkey.to_bytes(16, byteorder='little')
|
||||||
logger.debug(f'TK from passkey = {self.tk.hex()}')
|
logger.debug(f'TK from passkey = {self.tk.hex()}')
|
||||||
|
|
||||||
|
self.passkey_ready.set()
|
||||||
|
|
||||||
if next_steps is not None:
|
if next_steps is not None:
|
||||||
next_steps()
|
next_steps()
|
||||||
|
|
||||||
@@ -910,17 +916,29 @@ class Session:
|
|||||||
logger.debug(f'generated random: {self.r.hex()}')
|
logger.debug(f'generated random: {self.r.hex()}')
|
||||||
|
|
||||||
if self.sc:
|
if self.sc:
|
||||||
if self.pairing_method in (self.JUST_WORKS, self.NUMERIC_COMPARISON):
|
|
||||||
z = 0
|
|
||||||
elif self.pairing_method == self.PASSKEY:
|
|
||||||
z = 0x80 + ((self.passkey >> self.passkey_step) & 1)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.is_initiator:
|
async def next_steps():
|
||||||
confirm_value = crypto.f4(self.pka, self.pkb, self.r, bytes([z]))
|
if self.pairing_method in (self.JUST_WORKS, self.NUMERIC_COMPARISON):
|
||||||
else:
|
z = 0
|
||||||
confirm_value = crypto.f4(self.pkb, self.pka, self.r, bytes([z]))
|
elif self.pairing_method == self.PASSKEY:
|
||||||
|
# We need a passkey
|
||||||
|
await self.passkey_ready.wait()
|
||||||
|
|
||||||
|
z = 0x80 + ((self.passkey >> self.passkey_step) & 1)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.is_initiator:
|
||||||
|
confirm_value = crypto.f4(self.pka, self.pkb, self.r, bytes([z]))
|
||||||
|
else:
|
||||||
|
confirm_value = crypto.f4(self.pkb, self.pka, self.r, bytes([z]))
|
||||||
|
|
||||||
|
self.send_command(
|
||||||
|
SMP_Pairing_Confirm_Command(confirm_value=confirm_value)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Perform the next steps asynchronously in case we need to wait for input
|
||||||
|
self.connection.abort_on('disconnection', next_steps())
|
||||||
else:
|
else:
|
||||||
confirm_value = crypto.c1(
|
confirm_value = crypto.c1(
|
||||||
self.tk,
|
self.tk,
|
||||||
@@ -933,7 +951,7 @@ class Session:
|
|||||||
self.ra,
|
self.ra,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.send_command(SMP_Pairing_Confirm_Command(confirm_value=confirm_value))
|
self.send_command(SMP_Pairing_Confirm_Command(confirm_value=confirm_value))
|
||||||
|
|
||||||
def send_pairing_random_command(self):
|
def send_pairing_random_command(self):
|
||||||
self.send_command(SMP_Pairing_Random_Command(random_value=self.r))
|
self.send_command(SMP_Pairing_Random_Command(random_value=self.r))
|
||||||
@@ -1364,8 +1382,8 @@ class Session:
|
|||||||
|
|
||||||
# Start phase 2
|
# Start phase 2
|
||||||
if self.sc:
|
if self.sc:
|
||||||
if self.pairing_method == self.PASSKEY and self.passkey_display:
|
if self.pairing_method == self.PASSKEY:
|
||||||
self.display_passkey()
|
self.display_or_input_passkey()
|
||||||
|
|
||||||
self.send_public_key_command()
|
self.send_public_key_command()
|
||||||
else:
|
else:
|
||||||
@@ -1426,18 +1444,22 @@ class Session:
|
|||||||
else:
|
else:
|
||||||
srand = self.r
|
srand = self.r
|
||||||
mrand = command.random_value
|
mrand = command.random_value
|
||||||
stk = crypto.s1(self.tk, srand, mrand)
|
self.stk = crypto.s1(self.tk, srand, mrand)
|
||||||
logger.debug(f'STK = {stk.hex()}')
|
logger.debug(f'STK = {self.stk.hex()}')
|
||||||
|
|
||||||
# Generate LTK
|
# Generate LTK
|
||||||
self.ltk = crypto.r()
|
self.ltk = crypto.r()
|
||||||
|
|
||||||
if self.is_initiator:
|
if self.is_initiator:
|
||||||
self.start_encryption(stk)
|
self.start_encryption(self.stk)
|
||||||
else:
|
else:
|
||||||
self.send_pairing_random_command()
|
self.send_pairing_random_command()
|
||||||
|
|
||||||
def on_smp_pairing_random_command_secure_connections(self, command):
|
def on_smp_pairing_random_command_secure_connections(self, command):
|
||||||
|
if self.pairing_method == self.PASSKEY and self.passkey is None:
|
||||||
|
logger.warning('no passkey entered, ignoring command')
|
||||||
|
return
|
||||||
|
|
||||||
# pylint: disable=too-many-return-statements
|
# pylint: disable=too-many-return-statements
|
||||||
if self.is_initiator:
|
if self.is_initiator:
|
||||||
if self.pairing_method in (self.JUST_WORKS, self.NUMERIC_COMPARISON):
|
if self.pairing_method in (self.JUST_WORKS, self.NUMERIC_COMPARISON):
|
||||||
@@ -1565,17 +1587,13 @@ class Session:
|
|||||||
logger.debug(f'DH key: {self.dh_key.hex()}')
|
logger.debug(f'DH key: {self.dh_key.hex()}')
|
||||||
|
|
||||||
if self.is_initiator:
|
if self.is_initiator:
|
||||||
if self.pairing_method == self.PASSKEY:
|
self.send_pairing_confirm_command()
|
||||||
if self.passkey_display:
|
|
||||||
self.send_pairing_confirm_command()
|
|
||||||
else:
|
|
||||||
self.input_passkey(self.send_pairing_confirm_command)
|
|
||||||
else:
|
else:
|
||||||
# Send our public key back to the initiator
|
|
||||||
if self.pairing_method == self.PASSKEY:
|
if self.pairing_method == self.PASSKEY:
|
||||||
self.display_or_input_passkey(self.send_public_key_command)
|
self.display_or_input_passkey()
|
||||||
else:
|
|
||||||
self.send_public_key_command()
|
# Send our public key back to the initiator
|
||||||
|
self.send_public_key_command()
|
||||||
|
|
||||||
if self.pairing_method in (self.JUST_WORKS, self.NUMERIC_COMPARISON):
|
if self.pairing_method in (self.JUST_WORKS, self.NUMERIC_COMPARISON):
|
||||||
# We can now send the confirmation value
|
# We can now send the confirmation value
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Copyright 2021-2023 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
from enum import IntEnum
|
||||||
|
import struct
|
||||||
|
import datetime
|
||||||
|
from typing import BinaryIO
|
||||||
|
|
||||||
|
from bumble.hci import HCI_Packet, HCI_COMMAND_PACKET, HCI_EVENT_PACKET
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Classes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
class Snooper:
|
||||||
|
"""
|
||||||
|
Base class for snooper implementations.
|
||||||
|
|
||||||
|
A snooper is an object that will be provided with HCI packets as they are
|
||||||
|
exchanged between a host and a controller.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Direction(IntEnum):
|
||||||
|
HOST_TO_CONTROLLER = 0
|
||||||
|
CONTROLLER_TO_HOST = 1
|
||||||
|
|
||||||
|
class DataLinkType(IntEnum):
|
||||||
|
H1 = 1001
|
||||||
|
H4 = 1002
|
||||||
|
HCI_BSCP = 1003
|
||||||
|
H5 = 1004
|
||||||
|
|
||||||
|
def snoop(self, hci_packet: HCI_Packet, direction: Direction) -> None:
|
||||||
|
"""Snoop on an HCI packet."""
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
class BtSnooper(Snooper):
|
||||||
|
"""
|
||||||
|
Snooper that saves HCI packets using the BTSnoop format, based on RFC 1761.
|
||||||
|
"""
|
||||||
|
|
||||||
|
IDENTIFICATION_PATTERN = b'btsnoop\0'
|
||||||
|
TIMESTAMP_ANCHOR = datetime.datetime(2000, 1, 1)
|
||||||
|
TIMESTAMP_DELTA = 0x00E03AB44A676000
|
||||||
|
ONE_MS = datetime.timedelta(microseconds=1)
|
||||||
|
|
||||||
|
def __init__(self, output: BinaryIO):
|
||||||
|
self.output = output
|
||||||
|
|
||||||
|
# Write the header
|
||||||
|
self.output.write(
|
||||||
|
self.IDENTIFICATION_PATTERN + struct.pack('>LL', 1, self.DataLinkType.H4)
|
||||||
|
)
|
||||||
|
|
||||||
|
def snoop(self, hci_packet: HCI_Packet, direction: Snooper.Direction) -> None:
|
||||||
|
flags = int(direction)
|
||||||
|
if hci_packet.hci_packet_type in (HCI_EVENT_PACKET, HCI_COMMAND_PACKET):
|
||||||
|
flags |= 0x10
|
||||||
|
|
||||||
|
# Compute the current timestamp
|
||||||
|
timestamp = (
|
||||||
|
int((datetime.datetime.utcnow() - self.TIMESTAMP_ANCHOR) / self.ONE_MS)
|
||||||
|
+ self.TIMESTAMP_DELTA
|
||||||
|
)
|
||||||
|
|
||||||
|
# Emit the record
|
||||||
|
packet_data = bytes(hci_packet)
|
||||||
|
self.output.write(
|
||||||
|
struct.pack(
|
||||||
|
'>IIIIQ',
|
||||||
|
len(packet_data), # Original Length
|
||||||
|
len(packet_data), # Included Length
|
||||||
|
flags, # Packet Flags
|
||||||
|
0, # Cumulative Drops
|
||||||
|
timestamp, # Timestamp
|
||||||
|
)
|
||||||
|
+ packet_data
|
||||||
|
)
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .common import Transport, AsyncPipeSink
|
from .common import Transport, AsyncPipeSink
|
||||||
from ..link import RemoteLink
|
|
||||||
from ..controller import Controller
|
from ..controller import Controller
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -110,6 +109,8 @@ async def open_transport(name: str) -> Transport:
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
async def open_transport_or_link(name):
|
async def open_transport_or_link(name):
|
||||||
if name.startswith('link-relay:'):
|
if name.startswith('link-relay:'):
|
||||||
|
from ..link import RemoteLink # lazy import
|
||||||
|
|
||||||
link = RemoteLink(name[11:])
|
link = RemoteLink(name[11:])
|
||||||
await link.wait_until_connected()
|
await link.wait_until_connected()
|
||||||
controller = Controller('remote', link=link)
|
controller = Controller('remote', link=link)
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
import struct
|
import struct
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
|
||||||
|
|
||||||
from .. import hci
|
from .. import hci
|
||||||
|
from ..colors import color
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import time
|
|||||||
|
|
||||||
import usb.core
|
import usb.core
|
||||||
import usb.util
|
import usb.util
|
||||||
from colors import color
|
|
||||||
|
|
||||||
from .common import Transport, ParserSource
|
from .common import Transport, ParserSource
|
||||||
from .. import hci
|
from .. import hci
|
||||||
|
from ..colors import color
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import ctypes
|
|||||||
import platform
|
import platform
|
||||||
|
|
||||||
import usb1
|
import usb1
|
||||||
from colors import color
|
|
||||||
|
|
||||||
from .common import Transport, ParserSource
|
from .common import Transport, ParserSource
|
||||||
from .. import hci
|
from .. import hci
|
||||||
|
from ..colors import color
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
+3
-1
@@ -22,9 +22,9 @@ import collections
|
|||||||
import sys
|
import sys
|
||||||
from typing import Awaitable, TypeVar
|
from typing import Awaitable, TypeVar
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from colors import color
|
|
||||||
from pyee import EventEmitter
|
from pyee import EventEmitter
|
||||||
|
|
||||||
|
from .colors import color
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Logging
|
# Logging
|
||||||
@@ -78,6 +78,8 @@ class AbortableEventEmitter(EventEmitter):
|
|||||||
return future
|
return future
|
||||||
|
|
||||||
def on_event(*_):
|
def on_event(*_):
|
||||||
|
if future.done():
|
||||||
|
return
|
||||||
msg = f'abort: {event} event occurred.'
|
msg = f'abort: {event} event occurred.'
|
||||||
if isinstance(future, asyncio.Task):
|
if isinstance(future, asyncio.Task):
|
||||||
# python < 3.9 does not support passing a message on `Task.cancel`
|
# python < 3.9 does not support passing a message on `Task.cancel`
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.transport import open_transport
|
from bumble.transport import open_transport
|
||||||
from bumble.profiles.battery_service import BatteryServiceProxy
|
from bumble.profiles.battery_service import BatteryServiceProxy
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
from bumble.device import Device, Peer
|
from bumble.device import Device, Peer
|
||||||
from bumble.profiles.device_information_service import DeviceInformationServiceProxy
|
from bumble.profiles.device_information_service import DeviceInformationServiceProxy
|
||||||
from bumble.transport import open_transport
|
from bumble.transport import open_transport
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.transport import open_transport
|
from bumble.transport import open_transport
|
||||||
from bumble.profiles.heart_rate_service import HeartRateServiceProxy
|
from bumble.profiles.heart_rate_service import HeartRateServiceProxy
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
import json
|
import json
|
||||||
import websockets
|
import websockets
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
from bumble.core import AdvertisingData
|
from bumble.core import AdvertisingData
|
||||||
from bumble.device import Device, Connection, Peer
|
from bumble.device import Device, Connection, Peer
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
from bumble.core import (
|
from bumble.core import (
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
from bumble.core import BT_BR_EDR_TRANSPORT
|
from bumble.core import BT_BR_EDR_TRANSPORT
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import logging
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.controller import Controller
|
from bumble.controller import Controller
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Copyright 2021-2023 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from bumble.colors import color
|
||||||
|
|
||||||
|
from bumble.device import Device
|
||||||
|
from bumble.transport import open_transport_or_link
|
||||||
|
from bumble.snoop import BtSnooper
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
async def main():
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print('Usage: run_device_with_snooper.py <transport-spec> <snoop-file>')
|
||||||
|
print('example: run_device_with_snooper.py usb:0 btsnoop.log')
|
||||||
|
return
|
||||||
|
|
||||||
|
print('<<< connecting to HCI...')
|
||||||
|
async with await open_transport_or_link(sys.argv[1]) as (hci_source, hci_sink):
|
||||||
|
print('<<< connected')
|
||||||
|
|
||||||
|
device = Device.with_hci('Bumble', 'F0:F1:F2:F3:F4:F5', hci_source, hci_sink)
|
||||||
|
|
||||||
|
with open(sys.argv[2], "wb") as snoop_file:
|
||||||
|
device.host.snooper = BtSnooper(snoop_file)
|
||||||
|
await device.power_on()
|
||||||
|
await device.start_scanning()
|
||||||
|
|
||||||
|
await hci_source.wait_for_termination()
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
|
||||||
|
asyncio.run(main())
|
||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
from bumble.core import ProtocolError
|
from bumble.core import ProtocolError
|
||||||
from bumble.device import Device, Peer
|
from bumble.device import Device, Peer
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
from bumble.core import ProtocolError
|
from bumble.core import ProtocolError
|
||||||
from bumble.controller import Controller
|
from bumble.controller import Controller
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
import bumble.core
|
import bumble.core
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
import bumble.core
|
import bumble.core
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from colors import color
|
from bumble.colors import color
|
||||||
|
|
||||||
from bumble.device import Device
|
from bumble.device import Device
|
||||||
from bumble.transport import open_transport_or_link
|
from bumble.transport import open_transport_or_link
|
||||||
|
|||||||
@@ -30,11 +30,8 @@ package_dir =
|
|||||||
bumble.apps = apps
|
bumble.apps = apps
|
||||||
include-package-data = True
|
include-package-data = True
|
||||||
install_requires =
|
install_requires =
|
||||||
aioconsole >= 0.4.1
|
|
||||||
ansicolors >= 1.1
|
|
||||||
appdirs >= 1.4
|
appdirs >= 1.4
|
||||||
click >= 7.1.2; platform_system!='Emscripten'
|
click >= 7.1.2; platform_system!='Emscripten'
|
||||||
construct >= 2.10
|
|
||||||
cryptography == 35; platform_system!='Emscripten'
|
cryptography == 35; platform_system!='Emscripten'
|
||||||
grpcio >= 1.46; platform_system!='Emscripten'
|
grpcio >= 1.46; platform_system!='Emscripten'
|
||||||
libusb1 >= 2.0.1; platform_system!='Emscripten'
|
libusb1 >= 2.0.1; platform_system!='Emscripten'
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ async def test_device_connect_parallel():
|
|||||||
d1.host.set_packet_sink(Sink(d1_flow()))
|
d1.host.set_packet_sink(Sink(d1_flow()))
|
||||||
d2.host.set_packet_sink(Sink(d2_flow()))
|
d2.host.set_packet_sink(Sink(d2_flow()))
|
||||||
|
|
||||||
[c01, c02, a10, a20, a01] = await asyncio.gather(
|
[c01, c02, a10, a20] = await asyncio.gather(
|
||||||
*[
|
*[
|
||||||
asyncio.create_task(
|
asyncio.create_task(
|
||||||
d0.connect(d1.public_address, transport=BT_BR_EDR_TRANSPORT)
|
d0.connect(d1.public_address, transport=BT_BR_EDR_TRANSPORT)
|
||||||
@@ -207,7 +207,6 @@ async def test_device_connect_parallel():
|
|||||||
),
|
),
|
||||||
asyncio.create_task(d1.accept(peer_address=d0.public_address)),
|
asyncio.create_task(d1.accept(peer_address=d0.public_address)),
|
||||||
asyncio.create_task(d2.accept()),
|
asyncio.create_task(d2.accept()),
|
||||||
asyncio.create_task(d0.accept(peer_address=d1.public_address)),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -215,11 +214,9 @@ async def test_device_connect_parallel():
|
|||||||
assert type(c02) == Connection
|
assert type(c02) == Connection
|
||||||
assert type(a10) == Connection
|
assert type(a10) == Connection
|
||||||
assert type(a20) == Connection
|
assert type(a20) == Connection
|
||||||
assert type(a01) == Connection
|
|
||||||
|
|
||||||
assert c01.handle == a10.handle and c01.handle == 0x100
|
assert c01.handle == a10.handle and c01.handle == 0x100
|
||||||
assert c02.handle == a20.handle and c02.handle == 0x101
|
assert c02.handle == a20.handle and c02.handle == 0x101
|
||||||
assert a01 == c01
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
+12
-5
@@ -32,7 +32,6 @@ from bumble.smp import (
|
|||||||
PairingDelegate,
|
PairingDelegate,
|
||||||
SMP_PAIRING_NOT_SUPPORTED_ERROR,
|
SMP_PAIRING_NOT_SUPPORTED_ERROR,
|
||||||
SMP_CONFIRM_VALUE_FAILED_ERROR,
|
SMP_CONFIRM_VALUE_FAILED_ERROR,
|
||||||
SMP_ID_KEY_DISTRIBUTION_FLAG,
|
|
||||||
)
|
)
|
||||||
from bumble.core import ProtocolError
|
from bumble.core import ProtocolError
|
||||||
|
|
||||||
@@ -273,9 +272,15 @@ KEY_DIST = range(16)
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'io_cap, sc, mitm, key_dist', itertools.product(IO_CAP, SC, MITM, KEY_DIST)
|
'io_caps, sc, mitm, key_dist',
|
||||||
|
itertools.chain(
|
||||||
|
itertools.product([IO_CAP], SC, MITM, [15]),
|
||||||
|
itertools.product(
|
||||||
|
[[PairingDelegate.DISPLAY_OUTPUT_AND_KEYBOARD_INPUT]], SC, MITM, KEY_DIST
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
async def test_self_smp(io_cap, sc, mitm, key_dist):
|
async def test_self_smp(io_caps, sc, mitm, key_dist):
|
||||||
class Delegate(PairingDelegate):
|
class Delegate(PairingDelegate):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -296,6 +301,7 @@ async def test_self_smp(io_cap, sc, mitm, key_dist):
|
|||||||
self.peer_delegate = None
|
self.peer_delegate = None
|
||||||
self.number = asyncio.get_running_loop().create_future()
|
self.number = asyncio.get_running_loop().create_future()
|
||||||
|
|
||||||
|
# pylint: disable-next=unused-argument
|
||||||
async def compare_numbers(self, number, digits):
|
async def compare_numbers(self, number, digits):
|
||||||
if self.peer_delegate is None:
|
if self.peer_delegate is None:
|
||||||
logger.warning(f'[{self.name}] no peer delegate')
|
logger.warning(f'[{self.name}] no peer delegate')
|
||||||
@@ -331,8 +337,9 @@ async def test_self_smp(io_cap, sc, mitm, key_dist):
|
|||||||
|
|
||||||
pairing_config_sets = [('Initiator', [None]), ('Responder', [None])]
|
pairing_config_sets = [('Initiator', [None]), ('Responder', [None])]
|
||||||
for pairing_config_set in pairing_config_sets:
|
for pairing_config_set in pairing_config_sets:
|
||||||
delegate = Delegate(pairing_config_set[0], io_cap, key_dist, key_dist)
|
for io_cap in io_caps:
|
||||||
pairing_config_set[1].append(PairingConfig(sc, mitm, True, delegate))
|
delegate = Delegate(pairing_config_set[0], io_cap, key_dist, key_dist)
|
||||||
|
pairing_config_set[1].append(PairingConfig(sc, mitm, True, delegate))
|
||||||
|
|
||||||
for pairing_config1 in pairing_config_sets[0][1]:
|
for pairing_config1 in pairing_config_sets[0][1]:
|
||||||
for pairing_config2 in pairing_config_sets[1][1]:
|
for pairing_config2 in pairing_config_sets[1][1]:
|
||||||
|
|||||||
Reference in New Issue
Block a user