Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for bleak max_write_without_response_size and current bleak MTU #141

Merged
merged 6 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions aiohomekit/controller/ble/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from typing import Any, Callable, TypeVar, cast

from bleak import BleakClient
from bleak.backends.characteristic import BleakGATTCharacteristic

from aiohomekit.controller.ble.key import DecryptionKey, EncryptionKey
from aiohomekit.exceptions import EncryptionError
Expand Down Expand Up @@ -76,7 +77,7 @@ async def ble_request(
encryption_key: EncryptionKey | None,
decryption_key: DecryptionKey | None,
opcode: OpCode,
handle: int,
handle: BleakGATTCharacteristic,
iid: int,
data: bytes | None = None,
) -> tuple[PDUStatus, bytes]:
Expand All @@ -85,19 +86,53 @@ async def ble_request(
# We think there is a 3 byte overhead for ATT
# https://github.com/jlusiardi/homekit_python/issues/211#issuecomment-996751939
# But we haven't confirmed that this isn't already taken into account
debug_enabled = logger.isEnabledFor(logging.DEBUG)

# Newer bleak, not currently released
if max_write_without_response_size := getattr(
handle, "max_write_without_response_size", None
):
if debug_enabled:
logger.debug(
"max_write_without_response_size: %s, mtu_size-3: %s",
max_write_without_response_size,
client.mtu_size - 3,
)
fragment_size = max(max_write_without_response_size, client.mtu_size - 3)
# Bleak 0.15.1 and below
elif (
(char_obj := getattr(handle, "obj", None))
and isinstance(char_obj, dict)
and (char_mtu := char_obj.get("MTU"))
):
if debug_enabled:
logger.debug(
"bleak obj MTU: %s, mtu_size-3: %s",
char_mtu,
client.mtu_size - 3,
)
fragment_size = max(char_mtu - 3, client.mtu_size - 3)
else:
if debug_enabled:
logger.debug(
"no bleak obj MTU or max_write_without_response_size, using mtu_size-3: %s",
client.mtu_size - 3,
)
fragment_size = client.mtu_size - 3

fragment_size = client.mtu_size - 3
if encryption_key:
# Secure session means an extra 16 bytes of overhead
fragment_size -= 16

logger.debug("Using fragment size: %s", fragment_size)
if debug_enabled:
logger.debug("Using fragment size: %s", fragment_size)

# Wrap data in one or more PDU's split at fragment_size
# And write each one to the target characterstic handle
writes = []
for data in encode_pdu(opcode, tid, iid, data, fragment_size):
logger.debug("Queuing fragment for write: %s", data)
if debug_enabled:
logger.debug("Queuing fragment for write: %s", data)
if encryption_key:
data = encryption_key.encrypt(data)
writes.append(data)
Expand All @@ -110,7 +145,9 @@ async def ble_request(
data = decryption_key.decrypt(data)
if data is False:
raise EncryptionError("Decryption failed")
logger.debug("Read fragment: %s", data)

if debug_enabled:
logger.debug("Read fragment: %s", data)

# Validate the PDU header
status, expected_length, data = decode_pdu(tid, data)
Expand All @@ -127,7 +164,8 @@ async def ble_request(
next = decryption_key.decrypt(next)
if next is False:
raise EncryptionError("Decryption failed")
logger.debug("Read fragment: %s", next)
if debug_enabled:
logger.debug("Read fragment: %s", next)

data += decode_pdu_continuation(tid, next)

Expand All @@ -138,7 +176,7 @@ async def char_write(
client: BleakClient,
encryption_key: EncryptionKey | None,
decryption_key: DecryptionKey | None,
handle: int,
handle: BleakGATTCharacteristic,
iid: int,
body: bytes,
):
Expand All @@ -158,7 +196,7 @@ async def char_read(
client: BleakClient,
encryption_key: EncryptionKey | None,
decryption_key: DecryptionKey | None,
handle: int,
handle: BleakGATTCharacteristic,
iid: int,
):
pdu_status, data = await ble_request(
Expand Down Expand Up @@ -188,7 +226,7 @@ async def drive_pairing_state_machine(
client,
None,
None,
char.handle,
char,
iid,
body,
)
Expand Down
4 changes: 2 additions & 2 deletions aiohomekit/controller/ble/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ async def _async_start_pairing(self, alias: str) -> tuple[bytearray, bytearray]:
CharacteristicsTypes.PAIRING_FEATURES,
)
ff_iid = await self.client.get_characteristic_iid(ff_char)
ff_raw = await char_read(self.client, None, None, ff_char.handle, ff_iid)
ff_raw = await char_read(self.client, None, None, ff_char, ff_iid)
ff = FeatureFlags(ff_raw[0])
return await drive_pairing_state_machine(
self.client,
Expand Down Expand Up @@ -182,7 +182,7 @@ async def async_identify(self) -> None:
)
iid = await self.client.get_characteristic_iid(char)

await char_write(self.client, None, None, char.handle, iid, b"\x01")
await char_write(self.client, None, None, char, iid, b"\x01")

def _async_process_advertisement(
self, device: BLEDevice, description: HomeKitAdvertisement
Expand Down
6 changes: 3 additions & 3 deletions aiohomekit/controller/ble/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ async def _async_request_under_lock(
self._encryption_key,
self._decryption_key,
opcode,
endpoint.handle,
endpoint,
char.iid,
data,
)
Expand Down Expand Up @@ -448,12 +448,12 @@ async def _async_fetch_gatt_database(self) -> Accessories:
iid,
):
await self.client.write_gatt_char(
char.handle,
char,
data,
"write-without-response" not in char.properties,
)

payload = await self.client.read_gatt_char(char.handle)
payload = await self.client.read_gatt_char(char)

status, _, signature = decode_pdu(tid, payload)
if status != PDUStatus.SUCCESS:
Expand Down