Conversation
Each channel message now shows the path hash size its sender used
("1-byte", "2-bytes", "3-bytes"), right before the region scope chip.
The size comes from the path byte of raw_hex. Mesh::sendFlood sets it
before the first hop, so a flood packet carries it even at 0 hops; a
direct packet with path byte 0x00 is sendZeroHop's marker and carries
none, and TRACE path bytes are SNR readings. packetpath.HashSize (Go)
and pathHashSize (app.js) implement that rule and share one test table.
/api/channels/{hash}/messages returns hash_size from both the in-memory
store and the SQLite fallback. Live WebSocket messages and channels
decrypted in the browser compute it from raw_hex, which both already
carry, so the broadcast payload is unchanged.
Constraint: Hash size is set by the originator and repeaters keep it, so it is per message, not per observation
Rejected: Add hash_size to the WS broadcast maps | every packet broadcast would grow for a value the client can read from raw_hex
Rejected: Show the size whenever path byte bits 7-6 are 00 | a direct zero-hop packet would claim 1-byte
Directive: packetpath.HashSize and pathHashSize must stay in agreement; their tests use the same cases
Confidence: high
Scope-risk: narrow
Not-tested: Real 3-byte traffic in the browser (the fixture has only 1- and 2-byte messages; 3-byte is covered by unit tests)
channels.js calls pathHashSize() from app.js; like every other app.js global it has to be listed in .eslintrc.json, or the frontend lint step fails with no-undef. Confidence: high Scope-risk: narrow
|
Some field data in support of the "repeaters don't change the hash size" assumption, in case it's useful for review. On a downstream fork we built a variant that derives the width from the relayed hops in every stored observation's Why we went that way:
What we found on real traffic:
No message showed different widths across its observations, so the case we guarded against didn't occur, which matches the firmware behaviour described here. And the conservative choice has a cost this PR doesn't have: the 23 zero-hop floods got no width from us, while the path byte would still carry the sender's size. |
|
Reviewed against The code never infers the width from hop-token widths in
For this surface specifically the value is even more direct than the PR says: a channel message is built by I also went looking for a counter-example to "a repeater cannot change it" and there is none. The only mechanism that rewrites the size bits is On @dborup's point: it is correct at the schema level and sharper than stated. Absent or ambiguous evidence shows nothing rather than a confident wrong number, and the tests cover short hex, transport-truncated hex, invalid hex, empty, null, undefined, the reserved bits, TRACE and direct zero-hop. The value is computed once per message at build time, not per render, and One thing should be fixed before this merges. The same packet reports two different sizes on two pages, one click apart
const hashSize = (isNaN(rawPathByte) || (rawPathByte & 0x3F) === 0) ? null : ((rawPathByte >> 6) + 1);That suppresses the size whenever the hop count is 0, for every route type including FLOOD. Per Trigger: any 0-hop flood channel message, one an observer heard directly from its sender.
@dborup's sample puts this at 23/500, about 5%, and it is precisely the case this PR advertises as its advantage. The fix is to have The rule now has a sixth and seventh copy
AGENTS.md names this exact failure as its cautionary tale, and the point above is that bug factory producing its next bug. I am not asking for a refactor of all eleven sites. I am asking that the new helper become the single frontend source and that One measured divergence to resolveTwo Go helpers in the same binary disagree on one input. I probed raw
For floods at 0 hops the two agree ( Minor, no action needed
I ran |
What
Each message in the Channels view now shows the path hash size its sender used. It sits right before the region scope chip:
The label is
1-byte,2-bytesor3-bytes, with a tooltip saying "Path hash size the sender used". It's left out when the packet doesn't encode a size.Where the size comes from
The size is read from the path byte in
raw_hex(bits 7-6, plus 1), following the firmware:Mesh::sendFloodsetssetPathHashSizeAndCount(size, 0)before the first hop. So a flood packet carries the size even at 0 hops, including a message heard directly from its sender.Mesh::sendZeroHopsetspath_len = 0on a direct route. That's the zero-hop marker, not a 1-byte size, so no size is shown.PathBytesAreHops), so no size is shown.0b11are reserved (the firmware rejects sizes above 3), so no size is shown.Repeaters don't change the hash size, so it's the same for every observation of a message.
Changes
internal/packetpath: a newHashSize(rawHex) intthat returns 0 when the size is unknown. It decodes only the header and path bytes.cmd/server:/api/channels/{hash}/messagesreturnshash_sizefrom both sources, the in-memory store (store.go) and the SQLite fallback (db.go). The SQLite query selectssubstr(t.raw_hex, 1, 12)instead of the whole packet.public/app.js: a newpathHashSize(rawHex)that returnsnullwhen unknown. It sits next togetPathLenOffsetand follows the same rule as the Go helper.public/channels.js:raw_hex, which both already carryhash_sizeexisted is decrypted again once, the same way [feature request] region notation on messages in channels #1851 handledscope_nameThe WebSocket broadcast payload is unchanged.
Performance
HashSizeper message built. It's O(1), reads at most 6 bytes, and runs only for the page of messages returned, not for every packet. The SQLite path reads 12 characters ofraw_hexper row instead of none.pathHashSizecall per GRP_TXT message in the selected channel. There's no extra fetch, and the broadcast carries no new field.Tests
internal/packetpath/path_test.goTestHashSize: flood / transport flood / direct at 0 and more hops, zero-hop, reserved bits, TRACE, malformed input.tests/unit/test-frontend-helpers.js: the same case table forpathHashSize, plus null and undefined, so the two helpers must agree.cmd/server/channel_message_hash_size_test.go: both the store and the SQLite path return the expectedhash_size, including 0 for a zero-hop packet.tests/unit/test-issue-1851-channel-message-scope.js: the REST, WebSocket and in-browser-decrypted paths render the exact label before the scope chip, render nothing when unknown, and re-decrypt a cache saved before this change. I checked that these fail with thechannels.jschange reverted.test-frontend-helpers.js,test-channel-live-decrypt-userprefix.js) now providepathHashSize.Local runs:
go test ./...incmd/serverandinternal/packetpath: pass.go vet: clean.test-all.shpass excepttest-issue-1956-release-routing.js, which fails locally because my sandbox blocksmktemp. It doesn't touch this code.Browser check
I ran the server against a migrated copy of
test-fixtures/e2e-fixture.db, with one message given a#belgiumscope so the ordering shows. In#/channels/%23test, 34 messages show1-byteand one shows2-bytes · #belgium, and the console has no errors. The fixture has no 3-byte traffic; that case is covered by the unit tests.Customizer
No new configurable values.