0eb9845cd9d1d4e4e45d22d545a41cb997ae09f7
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c8c7eab128 |
A direct message's frames are its own, by the MAC over its ciphertext
The paths on a DM listed a message's own retransmissions, the other end's traffic, and both of those for every other message sent in the same minute and a half. On one real bob_caribou message that was 30 rows over 8 paths, of which 23 were the same two one-hop routes repeated. Those one-hops were the tell (JP): they were our *own* sends, overheard coming back off the two repeaters in earshot, correctly showing one hop because one hop is all they had travelled — but shown under the message as if they were its route. The channel view has always matched by decrypted content, and asked for here. A direct frame cannot be read, but it does not need to be: its body is [dest 1B][src 1B][MAC 2B][ciphertext], and that MAC is a tag over this message's plaintext. Copies of one message share it; the next message's differ. frame_addressing already knew where those two bytes were and threw them away, and the observations table already had a cipher_mac column its channel sibling used — so a direct message now groups exactly as a channel one does, with no schema change and nothing decrypted. Three filters, in order. The frame ran between the conversation's two ends; it travelled the direction this message did; it carries its group's MAC. The middle one is doing more work than it looks: both ends' hashes sit on every frame either way, so matching on the pair alone was what put our sends under a received message. The window is clamped by the conversation's own messages rather than a flat ±90 s — nine messages fell inside one such window. The clamp reads only messages going the *same* way, which the first cut of it got wrong: an inbound stamp is the sender's clock, so ordering it against our own sends compares two clocks, and a received message came back with an empty window. The directions are also bounded differently, because we know different things about them. A send is stamped by our clock as it leaves, so no frame of it predates it and all the room goes forward to the next send, where the retries are. A received message keeps a centred window, halfway to the received messages either side. Rows are paths now, not frames: identical routes fold into one line carrying its copy count, its first sighting, and the best SNR that path managed. Twelve identical lines said "twelve arrivals" where the truth was "two paths, six times each". Measured on the conversation that prompted this, whose history predates the MAC and so exercises only the direction and window fixes: outbound "Allo! Ça marche JF" 30 rows / 8 paths -> 2 rows (x3 each) inbound "J'ai pas de ack..." 30 rows / 8 paths -> 2 rows, 6 and 7 hops which is exactly what the packet log says by hand. Old history keeps the address-and-time fallback and is billed as such; frames recorded from now on match exactly, and say so in the same words the channel view uses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6217a7dc58 |
Packets: the frames a neighbour sends were being thrown away
Two silent drops in the RX-log mapper, and between them a device sitting beside this one could trace all day and never appear in the feed. A frame that crossed no relays was discarded as having "no topology content". It has the most there is: nothing stood between us, so we heard the transmitter itself. Only an advert survived, and only because it carries an origin key — every trace, message, request and ack from an adjacent node went into the bin. Now a frame is kept unless the library could not even give it a payload class, and the empty path says what it means: zero hops crossed. The other is a trace reading its own header wrong. Every class keeps relay hashes in the path field; a trace keeps one signed SNR byte per hop it has traversed. Read as hashes those bytes resolved to whichever nodes shared their leading digits and entered the topology graph as links that were never observed. The population says so plainly: across 1604 stored traces, all 2221 path entries land inside the LoRa SNR band when read as signed bytes, spanning -10.5 to +15.0 dB, where hashes would put five in six outside it. So a trace now yields no hops, and its readings — which are worth having — ride alongside as trace_snrs. The 1604 rows already stored keep their bad paths; a one-shot cleanup follows separately. |
||
|
|
01ca242c98 |
Live feed: the lane is about the packet, whatever the packet is about
The NODE lane suited the two classes that name a node and left every other one reading a dash. On this mesh that is half the traffic: the direct messages, requests, responses, returned paths, acks and traces that relay past us naming no origin. So the lane asks each class its own question instead, and the header says SUBJECT — the word that stays true down the whole column. An advert or a telemetry frame is still about its node. A channel text is about its channel, named by the key whose MAC confirms the frame — never by the fingerprint alone, which 256 buckets make no proof at all; a channel we hold no key for reads as that fingerprint and says so. A direct message, request, response, returned path or anonymous request is about the two nodes it travels between, drawn sender → recipient on the hop-sequence widget, so endpoints read exactly as route hops do: resolved names, key-derived hues, our own node white the moment a frame is addressed to us. Too wide, and the sender drops to its hash — the frame is addressed, so the addressee is the half worth the cells. An ack is about the message it answers, a trace about its tag. And an unsigned channel post is filed under its channel rather than the bare word "channel", which named nothing. Half of that was unreadable before because nobody had opened the frame. The companion hands every overheard packet to the library, which decodes the body for exactly two classes — advert and channel text — and leaves the rest as bytes. Those bytes are not opaque: MeshCore addresses a frame in the first few of them, and core/frames.py now reads them, class by class, against the layouts the protocol's own docs/payloads.md publishes. The trace tag is the little-endian uint32 the firmware writes, so it reads as the same number a trace reply reports; an ack's checksum keeps wire order, the form the companion reports its own delivery acks in. Two checks guard every decode, because a wrong slice yields not an error but a plausible-looking hash: the frame's payload version must be v1 (the header reserves two bits for a v2 that widens both hash and MAC), and the body must be long enough for the layout its class promises. Either failing yields nothing at all. The feed opens seeded from history, so any of this that lived only in a live event's raw payload would leave the opening screen blank — the same problem the channel-text crypto trio had. Same answer: three columns (dest/src/tag), written on capture and restored to the very keys the event used, so a replayed frame and a fresh one read identically all the way up. Older rows carry NULL until re-heard. The viewer gained the matching rows: to, from, tag, acks, and a channel for a datagram — whose body is not text, but whose MAC still names it. Verified against the MeshCore protocol documentation and firmware source, then corroborated on the air: every frame captured off the radio has a body length that decomposes exactly into its documented layout, an anonymous request's 51 bytes (1 + 32 + 2 + one AES block) included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |