use std::{
collections::{btree_map::Entry, BTreeMap},
sync::{Arc, RwLock},
time::SystemTime,
};
use futures_lite::stream::{self, StreamExt};
use iroh_base::{
key::NodeId,
node_addr::{AddrInfo, NodeAddr},
};
use super::{Discovery, DiscoveryItem};
#[derive(Debug, Default)]
#[repr(transparent)]
pub struct StaticProvider {
nodes: Arc<RwLock<BTreeMap<NodeId, NodeInfo>>>,
}
#[derive(Debug)]
struct NodeInfo {
info: AddrInfo,
last_updated: SystemTime,
}
impl StaticProvider {
pub const PROVENANCE: &'static str = "static_discovery";
pub fn new() -> Self {
Self::default()
}
pub fn from_node_addrs(infos: impl IntoIterator<Item = impl Into<NodeAddr>>) -> Self {
let res = Self::default();
for info in infos {
res.add_node_addr(info);
}
res
}
pub fn set_node_addr(&self, info: impl Into<NodeAddr>) -> Option<NodeAddr> {
let last_updated = SystemTime::now();
let info: NodeAddr = info.into();
let mut guard = self.nodes.write().unwrap();
let previous = guard.insert(
info.node_id,
NodeInfo {
info: info.info,
last_updated,
},
);
previous.map(|x| NodeAddr {
node_id: info.node_id,
info: x.info,
})
}
pub fn add_node_addr(&self, info: impl Into<NodeAddr>) {
let info: NodeAddr = info.into();
let last_updated = SystemTime::now();
let mut guard = self.nodes.write().unwrap();
match guard.entry(info.node_id) {
Entry::Occupied(mut entry) => {
let existing = entry.get_mut();
existing
.info
.direct_addresses
.extend(info.info.direct_addresses);
existing.info.relay_url = info.info.relay_url;
existing.last_updated = last_updated;
}
Entry::Vacant(entry) => {
entry.insert(NodeInfo {
info: info.info,
last_updated,
});
}
}
}
pub fn get_node_addr(&self, node_id: NodeId) -> Option<NodeAddr> {
let guard = self.nodes.read().unwrap();
let info = guard.get(&node_id).map(|x| x.info.clone())?;
Some(NodeAddr { node_id, info })
}
pub fn remove_node_addr(&self, node_id: NodeId) -> Option<NodeAddr> {
let mut guard = self.nodes.write().unwrap();
let res = guard.remove(&node_id)?;
Some(NodeAddr {
node_id,
info: res.info,
})
}
}
impl Discovery for StaticProvider {
fn publish(&self, _info: &AddrInfo) {}
fn resolve(
&self,
_endpoint: crate::Endpoint,
node_id: NodeId,
) -> Option<futures_lite::stream::Boxed<anyhow::Result<super::DiscoveryItem>>> {
let guard = self.nodes.read().unwrap();
let info = guard.get(&node_id);
match info {
Some(addr_info) => {
let item = DiscoveryItem {
node_id,
provenance: Self::PROVENANCE,
last_updated: Some(
addr_info
.last_updated
.duration_since(SystemTime::UNIX_EPOCH)
.expect("time drift")
.as_micros() as u64,
),
addr_info: addr_info.info.clone(),
};
Some(stream::iter(Some(Ok(item))).boxed())
}
None => None,
}
}
}