Tasks 1 and 2 of 01-04. This node's own shareable URI, and a mesh
message a peer uses to advertise theirs.
lnd.getinfo now deserializes identity_pubkey and uris, which its
response struct simply did not declare before (RESEARCH.md Pitfall 5).
The identity mapping is split into a pure map_identity() so it is
testable without a live LND. A pubkey that is not 66 hex characters maps
to None rather than being forwarded: the same rule lnd.openchannel
enforces, applied where the operator is reading their own node's
identity instead of at the moment they try to open a channel. An absent
field yields an honest absence — never a fabricated or placeholder
identity.
MeshMessageType::LightningInfo = 26 is additive on a wire format shared
with every fleet node: 26 was unused, so a peer that predates this fails
to decode it rather than mis-decoding it as something else. Its payload
is deliberately two fields — this rides LoRa, where every byte is paid
for on air, and the optional alias is skip_serializing_if so an absent
one costs nothing (asserted, not assumed).
is_valid_lightning_uri() validates before anything is stored, because
this is unauthenticated RF input: 66-hex pubkey, non-empty host, optional
numeric :port, exactly one '@'. It deliberately does NOT resolve or dial
the host — that would turn a received advertisement into an outbound
connection an attacker chose.
Two preservation hazards found while wiring MeshPeer.lightning_uri, both
of which would have silently emptied the picker:
- decode.rs's identity-advert path does a WHOLESALE insert, preserving
only advert_name and lat/lon by hand. Reticulum re-emits identity
adverts every announce tick, so a stored URI would have been wiped
about once a minute. Now preserved, alongside the same guard the name
and position already had.
- session.rs's refresh_contacts and mod.rs's federation seeding rebuild
the peer record wholesale too. Neither carries a Lightning datum, so
both now carry the previous value forward rather than nulling it.
A malformed inbound URI is rejected before the write, leaving any
previously stored good URI intact — otherwise anyone in range could
blank out a real peer's picker entry (T-01-12).
Verified: 5/5 new lnd::info tests, 18/18 mesh::message_types (5 new),
cargo check --all-targets clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>