G1: N07 — tighten BATTERY storage-field length guard

Why: The BATTERY handler previously gated the used_kb / total_kb
reads on `len(data) > 3`, which is wrong. The full
RESP_CODE_BATT_AND_STORAGE frame is 11 bytes (1 type + 2 level +
4 used_kb + 4 total_kb), so a 4-10 byte truncated frame would pass
the guard, and io.BytesIO.read(4) silently returns short bytes
instead of raising. int.from_bytes(b"", ...) returns 0, so HA
sensor telemetry silently reports zero storage on a truncated frame.

Tighten the guard to `len(data) >= 11` so the storage fields are
only parsed when the full frame is present. Inline comment added
to document the expected frame layout.

Note: the unconditional 2-byte `level` read at the top of the
handler has the same class of issue (no guard, silent zero on a
1-byte frame). That is out of scope for finding N07 and has been
logged in issues_log.md as a separate item.

Refs: Forensics report finding N07 (S3)
This commit is contained in:
Matthew Wolter 2026-04-11 18:19:52 -07:00
parent a7e257c78d
commit 3273c3489c

View file

@ -343,7 +343,12 @@ class MessageReader:
elif packet_type_value == PacketType.BATTERY.value:
battery_level = int.from_bytes(dbuf.read(2), byteorder="little")
result = {"level": battery_level}
if len(data) > 3: # has storage info as well
# Full RESP_CODE_BATT_AND_STORAGE frame is 11 bytes:
# 1 type + 2 level + 4 used_kb + 4 total_kb. The previous
# `len(data) > 3` guard let 4-10 byte truncated frames through,
# producing silent zero values for used_kb/total_kb because
# io.BytesIO.read() returns short data without raising.
if len(data) >= 11: # has storage info as well
result["used_kb"] = int.from_bytes(dbuf.read(4), byteorder="little")
result["total_kb"] = int.from_bytes(dbuf.read(4), byteorder="little")
await self.dispatcher.dispatch(Event(EventType.BATTERY, result))