use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::{Arc, RwLock};
use iroh::address_lookup::MemoryLookup;
use iroh::{EndpointAddr, PublicKey};
use tracing::debug;
use crate::dht::chain_projection::{with_read, with_write};
use crate::dht::routing::NodeId;
const PROVENANCE: &str = "capacity_bond_registry";
const LABEL: &str = "chain dial-address directory";
#[derive(Debug, Clone)]
pub struct DialAddrDirectory {
addrs: Arc<RwLock<HashMap<NodeId, Vec<SocketAddr>>>>,
lookup: MemoryLookup,
}
impl Default for DialAddrDirectory {
fn default() -> Self {
Self {
addrs: Arc::default(),
lookup: MemoryLookup::with_provenance(PROVENANCE),
}
}
}
impl DialAddrDirectory {
#[must_use]
pub fn lookup(&self) -> MemoryLookup {
self.lookup.clone()
}
#[must_use]
pub fn get(&self, node: &NodeId) -> Option<Vec<SocketAddr>> {
with_read(&self.addrs, LABEL, |m| m.get(node).cloned())
}
pub(crate) fn set(&self, node: NodeId, addrs: Vec<SocketAddr>) {
with_write(&self.addrs, LABEL, |m| {
if addrs.is_empty() {
m.remove(&node);
self.lookup_remove(&node);
} else {
self.lookup_set(&node, &addrs);
m.insert(node, addrs);
}
});
}
pub(crate) fn remove(&self, node: &NodeId) {
with_write(&self.addrs, LABEL, |m| {
m.remove(node);
self.lookup_remove(node);
});
}
pub(crate) fn replace_all(&self, mut next: HashMap<NodeId, Vec<SocketAddr>>) {
next.retain(|_, addrs| !addrs.is_empty());
with_write(&self.addrs, LABEL, |m| {
for gone in m.keys().filter(|id| !next.contains_key(id)) {
self.lookup_remove(gone);
}
for (id, addrs) in &next {
self.lookup_set(id, addrs);
}
*m = next;
});
}
fn lookup_set(&self, node: &NodeId, addrs: &[SocketAddr]) {
let Some(pk) = public_key(node) else {
return;
};
let target = addrs
.iter()
.fold(EndpointAddr::new(pk), |t, sock| t.with_ip_addr(*sock));
self.lookup.set_endpoint_info(target);
}
fn lookup_remove(&self, node: &NodeId) {
if let Some(pk) = public_key(node) {
self.lookup.remove_endpoint_info(pk);
}
}
}
fn public_key(node: &NodeId) -> Option<PublicKey> {
PublicKey::from_bytes(node.as_bytes())
.inspect_err(|e| debug!(%node, error = %e, "registry node id is not a valid public key"))
.ok()
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests;