From 57a0ca018d46bcc36efda6f480c2cf90bf4dd023 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Wed, 22 Apr 2026 20:52:35 +0200 Subject: [PATCH] fix: treat slots with empty name as empty regardless of secret bytes Some firmwares return SHA256(\"\")[:16] (e3b0c442...) for an empty channel slot's secret instead of all zeros. The load path checked only for the all-zero sentinel, so those slots passed the \"valid\" branch and got persisted to the DB with a synthetic 'Channel N' name plus the bogus secret. The stale rows then leaked into db.get_channels() and would have supplied wrong keys for pkt_payload computation. Anchor the decision on name presence: a slot is used iff firmware returned a non-empty name. Drop the 'Channel {idx}' fallback so we never invent names for empty slots. The existing end-of-loop cleanup then removes any phantom rows already in the DB on next connect. Co-Authored-By: Claude Opus 4.7 --- app/device_manager.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/device_manager.py b/app/device_manager.py index 3b44830..33f4d9f 100644 --- a/app/device_manager.py +++ b/app/device_manager.py @@ -368,7 +368,10 @@ class DeviceManager: async def _load_channel_secrets(self): """Load channel secrets from device for pkt_payload computation and persist to DB.""" - EMPTY_SECRET = '0' * 32 # all-zero secret means empty channel slot + # A slot is empty iff firmware returns no name for it. The returned + # "secret" on an empty slot is not reliable — some firmwares send all + # zeros, others send SHA256("")[:16] — so we anchor on name presence. + EMPTY_SECRET = '0' * 32 consecutive_empty = 0 valid_channel_indices = set() try: @@ -388,10 +391,15 @@ class DeviceManager: name = data.get('channel_name', data.get('name', '')) if isinstance(name, str): name = name.strip('\x00').strip() - if secret and len(secret) == 32 and secret != EMPTY_SECRET: + secret_valid = ( + isinstance(secret, str) + and len(secret) == 32 + and secret != EMPTY_SECRET + ) + if name and secret_valid: self._channel_secrets[idx] = secret # Persist to DB so API endpoints can read without device calls - self.db.upsert_channel(idx, name or f'Channel {idx}', secret) + self.db.upsert_channel(idx, name, secret) valid_channel_indices.add(idx) consecutive_empty = 0 elif name: