From 57c8fccedb9bffd81fb63e84d5945ad59ea4eac0 Mon Sep 17 00:00:00 2001 From: NK Date: Thu, 3 Sep 2026 02:39:59 +0530 Subject: [PATCH] zigbee: fix ZCLPricePublishPrice rate_label dissection rate_label used a self-referential length callback, int(pkt.rate_label[0]), which reads the field before it is set, so dissecting the command always raised IndexError and the packet could never be parsed. The Rate Label is an octet string whose first octet carries its length; read that octet from the remaining buffer instead, keeping the length octet as part of the value (same convention as _DiscreteString in this module). AI-Assisted: yes (Claude) --- scapy/layers/zigbee.py | 14 +++++++++++++- test/scapy/layers/dot15d4.uts | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/scapy/layers/zigbee.py b/scapy/layers/zigbee.py index 8531ca6f79d..4487319b024 100644 --- a/scapy/layers/zigbee.py +++ b/scapy/layers/zigbee.py @@ -1318,12 +1318,24 @@ class ZCLPriceGetScheduledPrices(Packet): ] +class _RateLabelStrField(StrLenField): + """Rate Label is a UTF-8 encoded octet string whose first octet indicates + the length of the label; the value includes the length octet.""" + + def getfield(self, pkt, s): + if not s: + return s, "" + length = s[0] if isinstance(s[0], int) else ord(s[0]) + self.length_from = lambda x: length + 1 + return StrLenField.getfield(self, pkt, s) + + class ZCLPricePublishPrice(Packet): name = "Price Cluster: Publish Price Command (Server: Generated)" fields_desc = [ XLEIntField("provider_id", 0x00000000), # Unsigned 32-bit Integer (4 octets) # noqa: E501 # Rate Label is a UTF-8 encoded Octet String (0-12 octets). The first Octet indicates the length. # noqa: E501 - StrLenField("rate_label", "", length_from=lambda pkt:int(pkt.rate_label[0])), # TODO verify # noqa: E501 + _RateLabelStrField("rate_label", ""), XLEIntField("issuer_event_id", 0x00000000), # Unsigned 32-bit Integer (4 octets) # noqa: E501 XLEIntField("current_time", 0x00000000), # UTCTime (4 octets) ByteField("unit_of_measure", 0), # 8 bits enumeration (1 octet) diff --git a/test/scapy/layers/dot15d4.uts b/test/scapy/layers/dot15d4.uts index 15ad74de801..4017c9460dd 100644 --- a/test/scapy/layers/dot15d4.uts +++ b/test/scapy/layers/dot15d4.uts @@ -948,3 +948,17 @@ assert ZCLIASZoneZoneEnrollResponse in pkt.layers() assert pkt[ZCLIASZoneZoneEnrollResponse].rsp_code == 0x00 assert pkt[ZCLIASZoneZoneEnrollResponse].zone_id == 0x3a assert raw(pkt[ZCLIASZoneZoneEnrollResponse].payload) == b'' + += ZCLPricePublishPrice rate label dissection + +p = ZCLPricePublishPrice(bytes.fromhex("1122334404616263645566778899aabbcc01ccdd0203445566778899aabbccdd112233445566778899aabbccddee")) +assert p.provider_id == 0x44332211 +assert p.rate_label == b"\x04abcd" +assert p.issuer_event_id == 0x88776655 +assert p.current_time == 0xCCBBAA99 +assert p.price == 0xDDCCBBAA +assert p.price_ratio == 0x11 +assert p.generation_price == 0x55443322 +assert p.number_of_block_thresholds == 0xDD +assert p.price_control == 0xEE +assert raw(p) == bytes.fromhex("1122334404616263645566778899aabbcc01ccdd0203445566778899aabbccdd112233445566778899aabbccddee")