diff --git a/bellows/ezsp/v8/types/struct.py b/bellows/ezsp/v8/types/struct.py index 89de3ebf..07e5b91e 100644 --- a/bellows/ezsp/v8/types/struct.py +++ b/bellows/ezsp/v8/types/struct.py @@ -1,5 +1,7 @@ """Protocol version 8 specific structs.""" +from __future__ import annotations + import bellows.types.basic as basic from bellows.types.struct import ( # noqa: F401 EmberAesMmoHashContext, @@ -98,6 +100,15 @@ class EmberKeyStruct(EzspStruct): # The IEEE address of the partner device also in possession of the key. partnerEUI64: named.EUI64 + @classmethod + def deserialize(cls, data: bytes) -> tuple[EmberKeyStruct, bytes]: + if len(data) == 24: + # XXX: `key` can seemingly be replaced with the uint32_t `psa_id` field in + # an invalid response. Pad it with zeroes so it deserializes. + data = data[:7] + b"\x00" * 12 + data[7:] + + return super().deserialize(data) + class EmberGpSinkListEntry(EzspStruct): # A sink list entry diff --git a/tests/test_ezsp_v8.py b/tests/test_ezsp_v8.py index f7de6beb..8d26af6b 100644 --- a/tests/test_ezsp_v8.py +++ b/tests/test_ezsp_v8.py @@ -1,5 +1,6 @@ import pytest +from bellows.ash import DataFrame import bellows.ezsp.v8 from .async_mock import AsyncMock, MagicMock, patch @@ -47,6 +48,20 @@ def test_command_frames(ezsp_f): assert ezsp_f.COMMANDS_BY_ID[frame_id][0] == name +def test_get_key_table_entry_fallback_parsing(ezsp_f): + """Test parsing of a getKeyTableEntry response with an invalid length.""" + data_frame = DataFrame.from_bytes( + bytes.fromhex( + "039ba1a9252a1659c6974b25aa55d1209c6e76ddedce958bfdc6f29ffc5e0d2845" + ) + ) + ezsp_f(data_frame.ezsp_frame) + + assert len(ezsp_f._handle_callback.mock_calls) == 1 + mock_call = ezsp_f._handle_callback.mock_calls[0] + assert mock_call.args[0] == "getKeyTableEntry" + + command_frames = { "addEndpoint": 0x0002, "addOrUpdateKeyTableEntry": 0x0066,