Add Reticulum protocol support to the web tier (#888) (#889)

Co-authored-by: oregonknuths <159513803+oregonknuths@users.noreply.github.com>
This commit is contained in:
Edwin Knuthandoregonknuths authored and GitHub committed 2026-08-24 21:34:39 +02:00
1 parent 40dfa14dfa
commit ace03b6807
37 files changed
+1022 -178

No files matched your search

@@ -212,6 +212,30 @@ module PotatoMesh
nil
end
# Decide whether an incoming node record collides with a stored row of a
# different protocol and must therefore be skipped (the cross-protocol
# node-row hijack guard in {#upsert_node}).
#
# A stored +"meshtastic"+ value doubles as the schema default stamped on
# rows ingested before their protocol was known, so a +"meshcore"+
# record may still reclaim such a row — the established bug #747
# self-heal that the upsert's +NULLIF(nodes.protocol,'meshtastic')+
# conflict clause implements. Every other differing pairing between two
# known protocols is a genuine 4-byte id collision across protocols and
# is rejected.
#
# @param stored_protocol [String, nil] protocol currently on the row, or
# nil when no row exists yet.
# @param incoming_protocol [String] resolved protocol of the incoming
# record.
# @return [Boolean] true when the record must be skipped.
def cross_protocol_conflict?(stored_protocol, incoming_protocol)
return false unless KNOWN_PROTOCOLS.include?(stored_protocol)
return false if stored_protocol == incoming_protocol
return false if stored_protocol == "meshtastic" && incoming_protocol == "meshcore"
true
end
# Insert or update a node row from an inbound NodeInfo-style payload.
#
# Two-phase write. Phase one is the freshness-guarded upsert: a record
@@ -268,6 +292,35 @@ module PotatoMesh
end
node_num = resolve_node_num(node_id, n)
# Cross-protocol node-row hijack guard. +nodes.node_id+ is a global
# TEXT primary key shared by every protocol's id mapping, and both the
# MeshCore and Reticulum mappings truncate a native identifier to its
# first 4 bytes — so two nodes on *different* protocols can collide on
# one +node_id+. Without this guard the colliding record would pass
# the freshness guard below (Reticulum announces stamp a wall-clock
# +lastHeard+) and overwrite the stored row's fields wholesale, with
# the row's protocol either flipped or silently mismatched. Skip such
# records entirely; neither row's data may corrupt the other's.
# Same-protocol prefix collisions remain the accepted MeshCore-
# inherited merge behaviour (see CONTRACTS.md, "Reticulum node id
# mapping"), and a stored default-'meshtastic' row may still be
# reclaimed by a meshcore record (the #747 self-heal preserved by
# +cross_protocol_conflict?+).
stored_protocol = db.get_first_value(
"SELECT protocol FROM nodes WHERE node_id = ? LIMIT 1",
[node_id],
)
if cross_protocol_conflict?(stored_protocol, protocol)
debug_log(
"Skipped cross-protocol node upsert",
context: "data_processing.upsert_node",
node_id: node_id,
stored_protocol: stored_protocol,
incoming_protocol: protocol,
)
return
end
# The prometheus helper still receives the raw `pos` so that gauges
# not affected by sentinel handling (e.g. precision_bits) keep
# updating; the latitude/longitude guards inside +update_prometheus_metrics+