Repeater: public-channel message history (store-and-forward) 🤖🤖 - #3078
Open
MDamon wants to merge 1 commit into
Open
Repeater: public-channel message history (store-and-forward) 🤖🤖#3078MDamon wants to merge 1 commit into
MDamon wants to merge 1 commit into
Conversation
A repeater already relays every public-channel (GRP_TXT) flood packet that passes through it. This keeps a bounded, in-RAM ring buffer of those packets so a client that was out of range can pull back the messages it missed, using an anonymous request (ANON_REQ sub-type 0x04) -- no login. Design points: - Repeater-only. Room server untouched. - The repeater holds no channel key. Packets are cached and replayed still channel-encrypted, so a non-member who asks receives ciphertext it cannot read, and a member decrypts with the key it already has. - Served directly to the requesting client, never re-flooded, so it adds no broadcast airtime. - No login: public channel data is not admin-only. Rate-limited by the existing anon_limiter, like the other anonymous requests. - Bounded RAM (32 entries, ~6 KB), nothing persisted -- a rotating cache in the small internal-flash FS would be a wear anti-pattern. - Channel broadcasts only, never direct messages, so this does not reintroduce the ACK/timeout problem that closed meshcore-dev#613. The request params {channel_hash, since_ts, max_count} are optional and self-describing via a leading length byte, so a stock client that sends only a reply path gets sensible defaults. A plain length check cannot work here: the ANON_REQ plaintext is zero-padded to the AES block size, so the decrypted length exceeds what the sender wrote. The reply is capped at 160 bytes. reply_data is MAX_PACKET_PAYLOAD (184), but the binding limit is the companion's host serial frame -- {push code}{reserved} {tag:4} plus the reply padded to the AES block size minus 4, against MAX_FRAME_SIZE of 176. Larger replies are transmitted but dropped before the app sees them. Clients page for the remainder with `since`. ChannelHistory is a hardware-independent template, covered by 11 googletest cases under a dedicated native env.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #3077. Opening the code alongside the feature request rather than waiting, since it is already written and validated — but treating #3077 as the place to settle the design questions. Happy to rework or drop this if maintainers want a different shape.
A repeater already relays every public-channel (
GRP_TXT) flood packet that passes through it. This keeps a bounded, in-RAM ring buffer of those packets so a client that was out of range can pull back the messages it missed, via an anonymous request (ANON_REQsub-type0x04).Design
anon_limiter, like the other anonymous requests.Only two existing code paths change: a capture hook in the repeater's
onRecvPacket(), and a new sub-type branch inonAnonDataRecv().ChannelHistoryitself is a hardware-independent template.Wire format
Documented in
docs/payloads.md.Two details worth calling out for reviewers, both found on hardware:
Params are optional and self-describing. A stock client that sends only a reply path gets sensible defaults. This needs the explicit
params_lenbyte rather than a length comparison, because theANON_REQplaintext is zero-padded to the AES block size — the decrypted length is larger than what the sender wrote, so a naive check reads its params out of the padding. Reading the length byte out of that same zero padding yields 0, which correctly means "no params".The reply is capped at 160 bytes.
reply_dataisMAX_PACKET_PAYLOAD(184), but that is not the binding limit: a companion hands the response to its host app in a serial frame of{push code}{reserved}{tag:4}plus the reply padded to the AES block size minus 4 —padded_len + 2— againstMAX_FRAME_SIZEof 176. A larger reply is transmitted by the repeater and then dropped before the app ever sees it. Clients page for the remainder withsince.Testing
Unit tests — 11 googletest cases,
pio test -e native_channel_history: content dedup of re-floods, oldest→newest ordering,sincepaging with no overlap, oversize/empty rejection, channel-hash filter, ring wrap eviction,max_count, output-buffer-full stop, clear.A separate
[env:native_channel_history]was added rather than reusing[env:native], because that env does not compile on macOS/clang for an unrelated reason (ConfigSerializer.cppusesatol/atoi/atofwithout<cstdlib>, which only builds because Linux/gcc includes it transitively). This mirrors the isolation already used fornative_kiss_modem.Hardware — two SenseCAP T1000-E units at 910.525 MHz / BW 62.5 / SF 8 / CR 5, one repeater running this firmware, one stock companion driving the requests:
flood_rx+3, all cachedrecv_ts, lengths, channel hash0x11)max_count2returned exactly 2channel_hash=0xAA(absent) returned 0The decryption check is the one that matters: it demonstrates the repeater — which holds no channel key — replaying packets a member can still read.
Builds verified for
Station_G2_repeater(ESP32-S3) andt1000e_repeater(nRF52840). RAM cost of the buffer is negligible (Station G2 build: 23.7%).On the examples/ guideline
CONTRIBUTING asks that new features include an example sketch. This extends the existing
examples/simple_repeaterrather than adding a new one, since a separate sketch would mean shipping a second, near-duplicate repeater firmware. Happy to split it out if you would prefer that.