Deterministic packets (#11014)

This commit is contained in:
Tom
2026-07-16 08:51:47 -05:00
committed by GitHub
co-authored by GitHub
parent f52d7753ad
commit 35ab69a2d6
+42 -12
View File
@@ -1201,19 +1201,49 @@ void PhoneAPI::prefetchNodeInfos()
onNowHasData(0);
}
namespace
{
/// Derive a stable id for a replayed satellite-DB record. Unchanged history replayed on
/// every reconnect must not look like a new packet to the phone's history/dedup - the id
/// only changes when the underlying data (its timestamp) actually changes.
// `kind` only needs to distinguish the record types that can otherwise collide (e.g. device
// vs. environment metrics both replay as TELEMETRY_APP with the same node/last_heard) - pass
// the payload's own variant/port constant.
uint32_t makeReplayPacketId(NodeNum num, uint32_t timestamp, uint32_t kind)
{
uint32_t h = num;
h = h * 2654435761u + timestamp;
h = h * 2654435761u + kind;
return h ? h : 1; // some clients treat id 0 as "unset"
}
/// Populate hop_start/hop_limit from the node's real last-known hop count (if any) so a
/// replayed packet doesn't read as "heard directly" when it wasn't.
void setReplayHopFields(meshtastic_MeshPacket &pkt, const meshtastic_NodeInfoLite *header)
{
uint8_t hopLimit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
uint8_t hopsAway = (header && header->has_hops_away) ? header->hops_away : 0;
pkt.hop_start = hopLimit;
pkt.hop_limit = hopsAway < hopLimit ? (uint8_t)(hopLimit - hopsAway) : 0;
}
} // namespace
meshtastic_MeshPacket PhoneAPI::makeReplayPositionPacket(NodeNum num, const meshtastic_PositionLite &pos)
{
// Shape this exactly like a fresh live broadcast Position from the peer so the
// phone runs it through its normal "live position broadcast" handler path.
// to=ourNum would read as a DM-from-peer and never lands in node detail UI.
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
pkt.from = num;
pkt.to = NODENUM_BROADCAST;
pkt.id = generatePacketId();
pkt.rx_time = pos.time;
// Stable per-node/per-fix id: replaying the same unchanged history on every
// reconnect must not look like a brand new packet to the phone's history/dedup.
pkt.id = makeReplayPacketId(num, pkt.rx_time, meshtastic_PortNum_POSITION_APP);
pkt.channel = 0;
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
pkt.hop_start = pkt.hop_limit;
pkt.rx_snr = header ? header->snr : 0;
setReplayHopFields(pkt, header);
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// Mark as if heard over the air, not internally generated
pkt.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
@@ -1231,13 +1261,13 @@ meshtastic_MeshPacket PhoneAPI::makeReplayTelemetryPacket(NodeNum num, const mes
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
pkt.from = num;
pkt.to = NODENUM_BROADCAST;
pkt.id = generatePacketId();
// No native timestamp on telemetry packets here; use last_heard.
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
pkt.rx_time = header ? header->last_heard : 0;
pkt.id = makeReplayPacketId(num, pkt.rx_time, meshtastic_Telemetry_device_metrics_tag);
pkt.channel = 0;
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
pkt.hop_start = pkt.hop_limit;
pkt.rx_snr = header ? header->snr : 0;
setReplayHopFields(pkt, header);
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// Mark as if heard over the air, not internally generated - iOS client filters
// TRANSPORT_INTERNAL packets out of broadcast peer state updates.
@@ -1337,12 +1367,12 @@ meshtastic_MeshPacket PhoneAPI::makeReplayEnvironmentPacket(uint32_t num, const
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
pkt.from = num;
pkt.to = NODENUM_BROADCAST;
pkt.id = generatePacketId();
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
pkt.rx_time = header ? header->last_heard : 0;
pkt.id = makeReplayPacketId(num, pkt.rx_time, meshtastic_Telemetry_environment_metrics_tag);
pkt.channel = 0;
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
pkt.hop_start = pkt.hop_limit;
pkt.rx_snr = header ? header->snr : 0;
setReplayHopFields(pkt, header);
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// Mark as if heard over the air, not internally generated - iOS client filters
// TRANSPORT_INTERNAL packets out of broadcast peer state updates.
@@ -1400,13 +1430,13 @@ meshtastic_MeshPacket PhoneAPI::makeReplayStatusPacket(uint32_t num, const mesht
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
pkt.from = num;
pkt.to = NODENUM_BROADCAST;
pkt.id = generatePacketId();
// StatusMessage has no native timestamp; use last_heard.
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
pkt.rx_time = header ? header->last_heard : 0;
pkt.id = makeReplayPacketId(num, pkt.rx_time, meshtastic_PortNum_NODE_STATUS_APP);
pkt.channel = 0;
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
pkt.hop_start = pkt.hop_limit;
pkt.rx_snr = header ? header->snr : 0;
setReplayHopFields(pkt, header);
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// Mark as if heard over the air, not internally generated - client filters
pkt.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;