Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions src/mesh/PhoneAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
Loading