From ac877ba904138a43d0c0a9fc35700adbf188a901 Mon Sep 17 00:00:00 2001 From: Strycher Date: Sun, 19 Jul 2026 20:45:58 -0400 Subject: [PATCH] fix(#298): log path units truthfully instead of asserting wrong hop counts The original diagnostic derived hops via realHopCount(), which divides a value that firmware already defines as a hop count (src/Packet.h:79-84: getPathByteLen() == getPathHashCount() * getPathHashSize()). It would have reported hops=1 for a real 2-hop path, misleading the exact investigation this logging exists to serve. _pathDiag now reports the hop count directly and prints held-vs-implied byte counts, flagging TRUNCATED when short. That makes the #309 decode truncation self-evident in any capture without the device present. The login dialogs no longer assert a hop count at all: selection.hopCount is ambiguous by construction (device paths carry hops, user overrides carry bytes, per #279), so they log the raw field, width and actual byte length, which discriminates the two cases. Refs #240, #279, #309 Co-Authored-By: Claude Opus 4.8 --- lib/connector/meshcore_connector.dart | 31 +++++++++++++++++--------- lib/widgets/repeater_login_dialog.dart | 14 +++++++----- lib/widgets/room_login_dialog.dart | 14 +++++++----- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 2fb3a16..90fddc8 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -492,21 +492,30 @@ class MeshCoreConnector extends ChangeNotifier { int get pathHashByteWidth => _pathHashByteWidth; - /// Compact path rendering for logs: the raw byte length, the width it is - /// being sliced at, the resulting hop count, and the hop-grouped bytes. + /// Compact path rendering for logs: hop count, applied width, the bytes we + /// actually hold versus the bytes that hop count implies, and the hex. /// - /// Captures previously logged only the byte length, which made it impossible - /// to tell a single 2-byte hop from two 1-byte hops. That is the exact - /// question #240/#279 turn on. One line, existing log sites only, no new - /// per-frame logging. (#298) - String _pathDiag(List pathBytes, int byteLen) { - if (byteLen < 0) return 'flood'; + /// Captures previously logged only a length, which made it impossible to tell + /// a single 2-byte hop from two 1-byte hops. That is the exact question + /// #240/#279 turn on. + /// + /// [pathLenField] is the firmware path-len field's low 6 bits, which is a HOP + /// COUNT, not a byte length: firmware `src/Packet.h:79-84` defines + /// `getPathByteLen() == getPathHashCount() * getPathHashSize()`. Logging the + /// held byte count next to the implied one makes the #309 decode truncation + /// self-evident in any capture, without the device present. + /// + /// One line, existing log sites only, no new per-frame logging. (#298) + String _pathDiag(List pathBytes, int pathLenField) { + if (pathLenField < 0) return 'flood'; final w = _pathHashByteWidth; - final hops = realHopCount(byteLen, w); - final bytes = pathBytes.isEmpty + final expectedBytes = pathLenField * w; + final hex = pathBytes.isEmpty ? 'none' : PathHelper.formatPathHex(pathBytes, w); - return 'len=$byteLen w=$w hops=$hops [$bytes]'; + final short = pathBytes.length < expectedBytes ? ' TRUNCATED' : ''; + return 'hops=$pathLenField w=$w ' + 'bytes=${pathBytes.length}/$expectedBytes$short [$hex]'; } CompanionRadioStats? get latestRadioStats => _latestRadioStats; diff --git a/lib/widgets/repeater_login_dialog.dart b/lib/widgets/repeater_login_dialog.dart index 468e1a7..d31de4e 100644 --- a/lib/widgets/repeater_login_dialog.dart +++ b/lib/widgets/repeater_login_dialog.dart @@ -116,14 +116,18 @@ class _RepeaterLoginDialogState extends State { ); final timeoutSeconds = (timeoutMs / 1000).ceil(); final timeout = Duration(milliseconds: timeoutMs + 2000); - // selection.hopCount is a BYTE count, not hops (#279). Log bytes, the - // applied width, the derived hop count and the hop-grouped bytes so a - // capture is unambiguous. (#298) + // `selection.hopCount` is ambiguous by construction: a device-discovered + // path carries a HOP count, a user override carries a BYTE count (#279). + // Do not assert hops here. Log the raw field, the width, and the actual + // byte length so a capture discriminates them: + // bytes == field -> byte-count semantics (override) + // bytes == field * w -> hop-count semantics (device) + // (#298) final routingWidth = _connector.pathHashByteWidth; final selectionLabel = selection.useFlood ? 'flood' - : 'bytes=${selection.hopCount} w=$routingWidth ' - 'hops=${realHopCount(selection.hopCount, routingWidth)} ' + : 'field=${selection.hopCount} w=$routingWidth ' + 'bytes=${selection.pathBytes.length} ' '[${PathHelper.formatPathHex(selection.pathBytes, routingWidth)}]'; appLogger.info('Login routing: $selectionLabel', tag: 'RepeaterLogin'); bool? loginResult; diff --git a/lib/widgets/room_login_dialog.dart b/lib/widgets/room_login_dialog.dart index 98156df..a12ea98 100644 --- a/lib/widgets/room_login_dialog.dart +++ b/lib/widgets/room_login_dialog.dart @@ -112,14 +112,18 @@ class _RoomLoginDialogState extends State { ); final timeoutSeconds = (timeoutMs / 1000).ceil(); final timeout = Duration(milliseconds: timeoutMs + 2000); - // selection.hopCount is a BYTE count, not hops (#279). Log bytes, the - // applied width, the derived hop count and the hop-grouped bytes so a - // capture is unambiguous. (#298) + // `selection.hopCount` is ambiguous by construction: a device-discovered + // path carries a HOP count, a user override carries a BYTE count (#279). + // Do not assert hops here. Log the raw field, the width, and the actual + // byte length so a capture discriminates them: + // bytes == field -> byte-count semantics (override) + // bytes == field * w -> hop-count semantics (device) + // (#298) final routingWidth = _connector.pathHashByteWidth; final selectionLabel = selection.useFlood ? 'flood' - : 'bytes=${selection.hopCount} w=$routingWidth ' - 'hops=${realHopCount(selection.hopCount, routingWidth)} ' + : 'field=${selection.hopCount} w=$routingWidth ' + 'bytes=${selection.pathBytes.length} ' '[${PathHelper.formatPathHex(selection.pathBytes, routingWidth)}]'; appLogger.info('Login routing: $selectionLabel', tag: 'RoomLogin'); bool? loginResult;