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 <noreply@anthropic.com>
chore/298-path-logging
Strycher 2 days ago
parent 2cab69606b
commit eb3094e149

@ -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<int> 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<int> 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;

@ -116,14 +116,18 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
);
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;

@ -112,14 +112,18 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
);
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;

Loading…
Cancel
Save

Powered by TurnKey Linux.