From 1826139cc81bbdb0b9fdd616b16fa92b07654f6b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 15 Jul 2023 11:50:07 -1000 Subject: [PATCH] feat: add ability to set the bind key after init (#83) BREAKING CHANGE: the bindkey should no longer be set directly on the object, call `set_bindkey` instead --- src/bthome_ble/parser.py | 14 ++++++---- tests/test_parser_v2.py | 56 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/bthome_ble/parser.py b/src/bthome_ble/parser.py index 13f3e07..aef9138 100644 --- a/src/bthome_ble/parser.py +++ b/src/bthome_ble/parser.py @@ -117,11 +117,7 @@ class BTHomeBluetoothDeviceData(BluetoothData): def __init__(self, bindkey: bytes | None = None) -> None: super().__init__() - self.bindkey = bindkey - if bindkey: - self.cipher: AESCCM | None = AESCCM(bindkey, tag_length=4) - else: - self.cipher = None + self.set_bindkey(bindkey) # Data that we know how to parse but don't yet map to the SensorData model. self.unhandled: dict[str, Any] = {} @@ -152,6 +148,14 @@ def __init__(self, bindkey: bytes | None = None) -> None: # If this is True, the device is not sending advertisements in a regular interval self.sleepy_device = False + def set_bindkey(self, bindkey: bytes | None) -> None: + """Set the bindkey.""" + self.bindkey = bindkey + if bindkey: + self.cipher: AESCCM | None = AESCCM(bindkey, tag_length=4) + else: + self.cipher = None + def supported(self, data: BluetoothServiceInfo) -> bool: if not super().supported(data): return False diff --git a/tests/test_parser_v2.py b/tests/test_parser_v2.py index 879d395..f14f75a 100644 --- a/tests/test_parser_v2.py +++ b/tests/test_parser_v2.py @@ -313,6 +313,62 @@ def test_bindkey_correct(): ) +def test_bindkey_set_late(): + """Test BTHome parser with correct encryption key set after the device is created.""" + bindkey = "231d39c1d7cc1ab1aee224cd096db932" + data_string = b"\x41\xa4\x72\x66\xc9\x5f\x73\x00\x11\x22\x33\x78\x23\x72\x14" + advertisement = bytes_to_service_info( + data_string, + local_name="TEST DEVICE", + address="54:48:E6:8F:80:A5", + ) + + device = BTHomeBluetoothDeviceData() + device.set_bindkey(bytes.fromhex(bindkey)) + assert device.supported(advertisement) + assert device.bindkey_verified + assert device.update(advertisement) == SensorUpdate( + title="TEST DEVICE 80A5", + devices={ + None: SensorDeviceInfo( + name="TEST DEVICE 80A5", + manufacturer=None, + model="BTHome sensor", + sw_version="BTHome BLE v2 (encrypted)", + hw_version=None, + ) + }, + entity_descriptions={ + KEY_TEMPERATURE: SensorDescription( + device_key=KEY_TEMPERATURE, + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=Units.TEMP_CELSIUS, + ), + KEY_HUMIDITY: SensorDescription( + device_key=KEY_HUMIDITY, + device_class=SensorDeviceClass.HUMIDITY, + native_unit_of_measurement=Units.PERCENTAGE, + ), + KEY_SIGNAL_STRENGTH: SensorDescription( + device_key=KEY_SIGNAL_STRENGTH, + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + ), + }, + entity_values={ + KEY_TEMPERATURE: SensorValue( + device_key=KEY_TEMPERATURE, name="Temperature", native_value=25.06 + ), + KEY_HUMIDITY: SensorValue( + device_key=KEY_HUMIDITY, name="Humidity", native_value=50.55 + ), + KEY_SIGNAL_STRENGTH: SensorValue( + device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60 + ), + }, + ) + + def test_bindkey_verified_can_be_unset(): """Test BTHome parser with wrong encryption key.""" bindkey = "814aac74c4f17b6c1581e1ab87816b99"