From 937f7b71ecdfe3753b13e5175c98913d46102795 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Fri, 7 Aug 2026 12:26:00 +0000 Subject: [PATCH] fix(mesh): route send-content-inline over federation for radio-less peers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mesh.send-content-inline always called send_typed_wire (the LoRa/radio path), which fails with "Peer is federation-only (no radio twin)" for any contact reachable only via Tor federation — reproduced sending a picture from the companion app to a federation-only peer on archy-x250-mad2. mesh.send-content already resolves the peer's federation onion and falls back to send_typed_wire_via_federation; mirror that same lookup here. Co-Authored-By: Claude Sonnet 5 --- .../src/api/rpc/mesh/typed_messages.rs | 61 ++++++++++++++++--- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/core/archipelago/src/api/rpc/mesh/typed_messages.rs b/core/archipelago/src/api/rpc/mesh/typed_messages.rs index a6cdfda6..65c5406e 100644 --- a/core/archipelago/src/api/rpc/mesh/typed_messages.rs +++ b/core/archipelago/src/api/rpc/mesh/typed_messages.rs @@ -492,15 +492,58 @@ impl RpcHandler { ) .await? } else { - svc.send_typed_wire( - contact_id, - wire, - "content_ref", - &display, - Some(typed_json), - seq, - ) - .await? + // Federation-only peers have no radio twin for + // send_typed_wire's LoRa dest-prefix resolution — route over + // Tor federation instead, mirroring mesh.send-content's onion + // lookup, or the send fails with "Peer is federation-only (no + // radio twin)" (picture-send from a federation-only contact, + // 2026-08-07). + let federation_onion = { + let state = svc.shared_state(); + let peers = state.peers.read().await; + peers + .get(&contact_id) + .map(|p| (p.pubkey_hex.clone(), p.did.clone())) + }; + let federation_onion = match federation_onion { + Some((Some(pubkey_hex), did)) => { + let nodes = crate::federation::load_nodes(&self.config.data_dir) + .await + .unwrap_or_default(); + nodes + .iter() + .find(|n| n.pubkey == pubkey_hex) + .map(|n| n.onion.clone()) + .or_else(|| { + did.as_ref().and_then(|d| { + nodes.iter().find(|n| &n.did == d).map(|n| n.onion.clone()) + }) + }) + } + _ => None, + }; + if let Some(onion) = federation_onion { + svc.send_typed_wire_via_federation( + contact_id, + &onion, + wire, + "content_ref", + &display, + Some(typed_json), + seq, + ) + .await? + } else { + svc.send_typed_wire( + contact_id, + wire, + "content_ref", + &display, + Some(typed_json), + seq, + ) + .await? + } } };