fix: guard lock parsers against short payloads#499
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests.
🚀 New features to boost your workflow:
|
PR Review — fix: guard lock parsers against short payloadsThe guard logic and per-model min-length table are correct and mirror the established pattern from PRs #492/#495/#496/#500. Indices accessed (data[0..1] for LOCK family; data[5] in default branch) match the min lengths (2 / 6). The One change requested before merge: @bdraco's review comment on line 299 should be addressed — bump the 🟡 Important1. Log level too low for what should be a firmware-bug signal (`switchbot/devices/lock.py`, L291-298)Per @bdraco's review comment: when this branch fires in production, it's almost certainly a firmware bug (or BLE proxy truncation), not a normal condition. For consistency, consider whether the new Checklist
SummaryThe guard logic and per-model min-length table are correct and mirror the established pattern from PRs #492/#495/#496/#500. Indices accessed (data[0..1] for LOCK family; data[5] in default branch) match the min lengths (2 / 6). The One change requested before merge: @bdraco's review comment on line 299 should be addressed — bump the |
`_parse_lock_data` indexes `data[1]` for all lock variants and `data[5]` in the LOCK_PRO/LOCK_ULTRA/LOCK_PRO_WIFI branch, but is reachable from `_update_lock_status` (notification path, decrypted payload of unknown length) and `get_basic_info` (`lock_raw_data[1:]`) without any length check. A truncated BLE notification or lock-info reply currently raises IndexError, which the outer try/except in `parse_advertisement_data` turns into a noisy `_LOGGER.exception` and a dropped advertisement (same class of bug as sblibs#285). Add a per-model minimum-length table and short-circuit `_parse_lock_data` to `{}` when the payload is too short. Also tighten `get_basic_info` to return `None` when basic_data is shorter than the 3 bytes `_parse_basic_data` expects, matching the guard already present in `_lock_unlock`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rebase with requested adjustmentsBranch StatsActions performed
CI statusCI will be checked asynchronously. Automated by Kōan |
37bf281 to
e409bb2
Compare
| if len(data) < min_len: | ||
| _LOGGER.debug( | ||
| "lock data too short for %s: got %d bytes, need %d", | ||
| model, | ||
| len(data), | ||
| min_len, | ||
| ) | ||
| return {} |
There was a problem hiding this comment.
This isn't great that it silently returns nothing in production. This should be rare, likely a bug in the firmware if it happens. probably error level log.
There was a problem hiding this comment.
Agreed — this matches the same concern raised on the device-level audit (PR #500). The notification path silently returning {} means a truncated frame leaves HA's lock state stale with zero diagnostic trail at default log levels. Will bump to _LOGGER.error here. Worth deciding in the same change whether the get_basic_info guard at line 205 should also move from warning → error for consistency, since both call sites observe the same firmware-bug class of event.
Rebase with requested adjustmentsBranch Changes applied
StatsActions performed
CI statusCI will be checked asynchronously. Automated by Kōan |
What
Add per-model length guards to
SwitchbotLock._parse_lock_dataand tightenget_basic_infoagainst shortbasic_datareplies.Why
_parse_lock_dataindexesdata[1]for every variant anddata[5]in the LOCK_PRO/LOCK_ULTRA/LOCK_PRO_WIFI branch, but is reachable from two paths without any length check:_update_lock_status(notification path) — decrypted payload of arbitrary length.get_basic_info— passeslock_raw_data[1:]straight through.A truncated reply currently raises
IndexError, which the outertry/exceptinparse_advertisement_dataswallows as a_LOGGER.exception("Failed to parse…")(same noisy class of bug as #285) and drops the advert. Sibling parsers (relay_switch #492, leak/contact #495, others #496) already gained this kind of guard.How
_LOCK_DATA_MIN_LEN_BY_MODELtable: 2 bytes for LOCK/LOCK_LITE/LOCK_VISION/LOCK_VISION_PRO, 6 bytes for LOCK_PRO/LOCK_ULTRA/LOCK_PRO_WIFI._parse_lock_datashort-circuits to{}when below the threshold. Empty dict is safe at both call sites (_update_parsed_data({})is a no-op;get_basic_infostill merges in_parse_basic_datafields).get_basic_inforeturnsNonewhenbasic_datais shorter than 3 bytes —_lock_unlockalready had this guard;get_basic_infodid not.Testing
get_basic_infowith a 2-byte basic_data →None.pytest→ 1228 passed.Quality Report
Changes: 2 files changed, 102 insertions(+)
Code scan: clean
Tests: passed (1228 passed)
Branch hygiene: clean
Generated by Kōan post-mission quality pipeline