use std::cell::RefCell;
use crate::registry;
use super::sharedfs_sync::SharedFsSync;
thread_local! {
static ACTIVE: RefCell<Vec<SharedFsSync>> = const { RefCell::new(Vec::new()) };
}
const HEXD: &[u8; 16] = b"0123456789abcdef";
fn hex20(a: &[u8; 20]) -> String {
let mut s = String::with_capacity(42);
s.push_str("0x");
for b in a {
s.push(HEXD[(b >> 4) as usize] as char);
s.push(HEXD[(b & 0xf) as usize] as char);
}
s
}
fn addr20(hex: &str) -> Option<[u8; 20]> {
let h = hex.trim().trim_start_matches("0x");
if h.len() != 40 {
return None;
}
let mut out = [0u8; 20];
for (i, slot) in out.iter_mut().enumerate() {
*slot = u8::from_str_radix(h.get(i * 2..i * 2 + 2)?, 16).ok()?;
}
Some(out)
}
fn make_blob(sender_eph_hex: &str, sealed_sdp: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(sender_eph_hex.len() + 1 + sealed_sdp.len());
out.extend_from_slice(sender_eph_hex.as_bytes());
out.push(b'\n');
out.extend_from_slice(sealed_sdp);
out
}
fn parse_blob(bytes: &[u8]) -> Option<(String, Vec<u8>)> {
let nl = bytes.iter().position(|&b| b == b'\n')?;
let eph = std::str::from_utf8(&bytes[..nl]).ok()?.to_string();
Some((eph, bytes[nl + 1..].to_vec()))
}
pub(crate) async fn sync_my_devices() -> Result<usize, String> {
let (master, _) = super::chat::credit_signer().await.ok_or("no identity")?;
let owner = super::chat::credit_address_existing()
.await
.ok_or("no identity")?;
let fee_payer = super::sponsor::signer().map_err(|_| "no sponsor")?;
let topic = registry::devices_topic(&owner);
sync_topic(&master, &fee_payer, &topic).await
}
async fn sync_topic(
master: &k256::ecdsa::SigningKey,
fee_payer: &k256::ecdsa::SigningKey,
topic: &[u8; 32],
) -> Result<usize, String> {
let eph = crate::wallet::generate();
let eph_addr = addr20(&eph.address_hex()).ok_or("bad ephemeral address")?;
let me = hex20(&eph_addr);
registry::announce_sponsored(
master,
fee_payer,
topic,
&eph_addr,
&crate::wallet::pubkey_compressed(&eph.signer), registry::ALPHA_USD_ADDRESS,
)
.await?;
let peers = registry::peers_of(topic).await?;
let mut connected = 0usize;
for (peer_hex, _ts, peer_pubkey) in peers {
if peer_hex.eq_ignore_ascii_case(&me) {
continue; }
if peer_pubkey.is_empty() {
continue; }
let Some(peer_addr) = addr20(&peer_hex) else {
continue;
};
if connect_and_sync(
master,
fee_payer,
&eph.signer,
&eph_addr,
&me,
&peer_addr,
&peer_hex,
&peer_pubkey,
)
.await
.is_ok()
{
connected += 1;
}
}
Ok(connected)
}
#[allow(clippy::too_many_arguments)]
async fn connect_and_sync(
master: &k256::ecdsa::SigningKey,
fee_payer: &k256::ecdsa::SigningKey,
eph_signer: &k256::ecdsa::SigningKey,
eph_addr: &[u8; 20],
me_hex: &str,
peer_addr: &[u8; 20],
peer_hex: &str,
peer_pubkey: &[u8],
) -> Result<(), String> {
let session = if me_hex < peer_hex {
let (s, offer) = SharedFsSync::offer().await.map_err(|_| "offer failed")?;
let sealed = super::encryption::ecies_seal(peer_pubkey, offer.as_bytes())
.await
.ok_or("seal offer failed")?;
registry::post_signal_sponsored(
master,
fee_payer,
peer_addr,
&make_blob(me_hex, &sealed),
registry::ALPHA_USD_ADDRESS,
)
.await?;
let answer = poll_inbox_from(eph_signer, eph_addr, peer_hex)
.await
.ok_or("no answer")?;
s.accept_answer(&answer).await.map_err(|_| "bad answer")?;
s
} else {
let offer = poll_inbox_from(eph_signer, eph_addr, peer_hex)
.await
.ok_or("no offer")?;
let (s, answer) = SharedFsSync::answer(&offer).await.map_err(|_| "answer failed")?;
let sealed = super::encryption::ecies_seal(peer_pubkey, answer.as_bytes())
.await
.ok_or("seal answer failed")?;
registry::post_signal_sponsored(
master,
fee_payer,
peer_addr,
&make_blob(me_hex, &sealed),
registry::ALPHA_USD_ADDRESS,
)
.await?;
s
};
for _ in 0..100 {
if session.is_open() {
break;
}
registry::sleep_ms(100).await;
}
session.start().await;
ACTIVE.with(|a| a.borrow_mut().push(session));
Ok(())
}
async fn poll_inbox_from(
eph_signer: &k256::ecdsa::SigningKey,
eph_addr: &[u8; 20],
from_hex: &str,
) -> Option<String> {
for _ in 0..60 {
if let Ok(signals) = registry::inbox_of(eph_addr, 0).await {
for (_from_master, _ts, blob) in signals {
if let Some((sender_eph, sealed)) = parse_blob(&blob) {
if sender_eph.eq_ignore_ascii_case(from_hex) {
if let Some(sdp) = super::encryption::ecies_open(eph_signer, &sealed).await {
if let Ok(s) = String::from_utf8(sdp) {
return Some(s);
}
}
}
}
}
}
registry::sleep_ms(1000).await;
}
None
}