fix(kerykeion): store-and-forward preserves payload + real stored_at_ms#272
Merged
Merged
Conversation
MeshRouter::send stored an empty packet_bytes for offline destinations (Vec::new(), with the packet argument otherwise unused on that branch) and hardcoded stored_at_ms: 0 on every StoredMessage. Together this meant a message routed to an offline node was delivered as an empty, mis-attributed shell once node_came_online flushed it (payload_variant: None, from: 0, synthetic hop_limit/hop_start = 3), and StoreForward::prune_expired computed every message's expiry against the Unix epoch instead of its real storage time, expiring the entire store against any real wall-clock now_ms. send now encodes the real packet via prost::Message::encode_to_vec into packet_bytes, captures the live portnum from the packet's Decoded payload variant, and records stored_at_ms via jiff::Timestamp::now(). node_came_online decodes packet_bytes back into the original MeshPacket instead of building a synthetic one, so the flushed message carries the real payload and original header fields. A decode failure (never expected, since packet_bytes is only ever written by send on this same type) is skipped rather than delivering a garbage packet. Closes #189 Closes #236 Gate-Passed: kanon 0.1.10 +stages:fmt,check,clippy,nextest,lint sha:25bf2452bdc79d46e37b6844c7f3a676f34cedf4
forkwright
enabled auto-merge (squash)
July 17, 2026 00:05
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.
Summary
Store-and-forward silently discarded outbound traffic in two ways:
MeshRouter::sendstored an emptypacket_bytespayload for offline destinations (Vec::new(),packetargument otherwise unused), and hardcodedstored_at_ms: 0on every stored message. Together this meant a message routed to an offline node was delivered as an empty, mis-attributed shell once the node came back online, andStoreForward::prune_expiredcomputed every message's expiry against the Unix epoch instead of its real storage time — expiring the entire store against any real wall-clocknow_ms.Closes #189
Closes #236
Changes
MeshRouter::send: encode the realpacketviaprost::Message::encode_to_vecintoStoredMessage.packet_bytes, and capture the liveportnumfrom the packet'sDecodedpayload variant (0forEncrypted/absent).MeshRouter::send:stored_at_msis nownow_ms()(real epoch milliseconds viajiff::Timestamp::now()), not a hardcoded0.MeshRouter::node_came_online: rebuilds the originalMeshPacketby decodingpacket_bytes(MeshPacket::decode) instead of constructing a synthetic packet withfrom: 0,payload_variant: None, and hardcodedhop_limit/hop_start = 3. A decode failure (never expected —packet_bytesis only ever written bysendon this same type) is skipped rather than delivering a garbage packet.Testing
cargo fmt --checkpassescargo clippy -p kerykeion --all-targetspasses (no new warnings)cargo nextest run -p kerykeion— 207 passedkanon gate --stamp— Gate-Passed (763 workspace tests, fmt/check/clippy/nextest/lint all PASS)store_forward_round_trips_payload_and_header_on_flush: sends a packet with a realDatapayload to an unreachable node, flushes vianode_came_online, and asserts the popped message has byte-identical payload plus the originalfrom/hop_limit/hop_start(fails against the old code, which returnspayload_variant: Noneandfrom: 0).store_forward_records_real_wall_clock_and_prunes_after_ttl: sends withttl_secs: 1using real wall-clock bounds, asserts the message survivesprune_expiredwithin its TTL and is pruned oncenow_mspassesstored_at_ms + ttl(fails against the old code, which prunes immediately against any realnow_mssincestored_at_mswas0).