Skip to content

Commit c9d1ba7

Browse files
authored
feat(iroh-net): Add StaticDiscovery to provide static info to endpoints (#2825)
## Description Add StaticProvider to provide static info to endpoints. The long term plan is to reduce ways to interact directly with the endpoint node map and instead provide this. ## Breaking Changes None, this just adds stuff. Long term we want to remove endpoint.add_node_info with this. ## Notes & open questions <!-- Any notes, remarks or open questions you have to make about the PR. --> ## Change checklist - [ ] Self-review. - [ ] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text), if relevant. - [ ] Tests if relevant. - [ ] All breaking changes documented.
1 parent d29537d commit c9d1ba7

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

iroh-net/src/discovery.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub mod dns;
114114
#[cfg_attr(iroh_docsrs, doc(cfg(feature = "discovery-local-network")))]
115115
pub mod local_swarm_discovery;
116116
pub mod pkarr;
117+
pub mod static_provider;
117118

118119
/// Node discovery for [`super::Endpoint`].
119120
///
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//! A static discovery implementation that allows adding info for nodes manually.
2+
use std::{
3+
collections::{btree_map::Entry, BTreeMap},
4+
sync::{Arc, RwLock},
5+
time::SystemTime,
6+
};
7+
8+
use futures_lite::stream::{self, StreamExt};
9+
use iroh_base::{
10+
key::NodeId,
11+
node_addr::{AddrInfo, NodeAddr},
12+
};
13+
14+
use super::{Discovery, DiscoveryItem};
15+
16+
/// A static discovery implementation that allows providing info for nodes manually.
17+
#[derive(Debug, Default)]
18+
#[repr(transparent)]
19+
pub struct StaticProvider {
20+
nodes: Arc<RwLock<BTreeMap<NodeId, NodeInfo>>>,
21+
}
22+
23+
#[derive(Debug)]
24+
struct NodeInfo {
25+
info: AddrInfo,
26+
last_updated: SystemTime,
27+
}
28+
29+
impl StaticProvider {
30+
/// The provenance string for this discovery implementation.
31+
pub const PROVENANCE: &'static str = "static_discovery";
32+
33+
/// Create a new static discovery instance.
34+
pub fn new() -> Self {
35+
Self::default()
36+
}
37+
38+
/// Creates a static discovery instance from something that can be converted into node addresses.
39+
///
40+
/// Example:
41+
/// ```rust
42+
/// use std::str::FromStr;
43+
///
44+
/// use iroh_base::ticket::NodeTicket;
45+
/// use iroh_net::{Endpoint, discovery::static_provider::StaticProvider};
46+
///
47+
/// # async fn example() -> anyhow::Result<()> {
48+
/// # #[derive(Default)] struct Args { tickets: Vec<NodeTicket> }
49+
/// # let args = Args::default();
50+
/// // get tickets from command line args
51+
/// let tickets: Vec<NodeTicket> = args.tickets;
52+
/// // create a StaticProvider from the tickets. Ticket info will be combined if multiple tickets refer to the same node.
53+
/// let discovery = StaticProvider::from_node_addrs(tickets);
54+
/// // create an endpoint with the discovery
55+
/// let endpoint = Endpoint::builder()
56+
/// .add_discovery(|_| Some(discovery))
57+
/// .bind().await?;
58+
/// # Ok(())
59+
/// # }
60+
/// ```
61+
pub fn from_node_addrs(infos: impl IntoIterator<Item = impl Into<NodeAddr>>) -> Self {
62+
let res = Self::default();
63+
for info in infos {
64+
res.add_node_addr(info);
65+
}
66+
res
67+
}
68+
69+
/// Add node info for the given node id.
70+
///
71+
/// This will completely overwrite any existing info for the node.
72+
pub fn set_node_addr(&self, info: impl Into<NodeAddr>) -> Option<NodeAddr> {
73+
let last_updated = SystemTime::now();
74+
let info: NodeAddr = info.into();
75+
let mut guard = self.nodes.write().unwrap();
76+
let previous = guard.insert(
77+
info.node_id,
78+
NodeInfo {
79+
info: info.info,
80+
last_updated,
81+
},
82+
);
83+
previous.map(|x| NodeAddr {
84+
node_id: info.node_id,
85+
info: x.info,
86+
})
87+
}
88+
89+
/// Add node info for the given node id, combining it with any existing info.
90+
///
91+
/// This will add any new direct addresses and overwrite the relay url.
92+
pub fn add_node_addr(&self, info: impl Into<NodeAddr>) {
93+
let info: NodeAddr = info.into();
94+
let last_updated = SystemTime::now();
95+
let mut guard = self.nodes.write().unwrap();
96+
match guard.entry(info.node_id) {
97+
Entry::Occupied(mut entry) => {
98+
let existing = entry.get_mut();
99+
existing
100+
.info
101+
.direct_addresses
102+
.extend(info.info.direct_addresses);
103+
existing.info.relay_url = info.info.relay_url;
104+
existing.last_updated = last_updated;
105+
}
106+
Entry::Vacant(entry) => {
107+
entry.insert(NodeInfo {
108+
info: info.info,
109+
last_updated,
110+
});
111+
}
112+
}
113+
}
114+
115+
/// Get node info for the given node id.
116+
pub fn get_node_addr(&self, node_id: NodeId) -> Option<NodeAddr> {
117+
let guard = self.nodes.read().unwrap();
118+
let info = guard.get(&node_id).map(|x| x.info.clone())?;
119+
Some(NodeAddr { node_id, info })
120+
}
121+
122+
/// Remove node info for the given node id.
123+
pub fn remove_node_addr(&self, node_id: NodeId) -> Option<NodeAddr> {
124+
let mut guard = self.nodes.write().unwrap();
125+
let res = guard.remove(&node_id)?;
126+
Some(NodeAddr {
127+
node_id,
128+
info: res.info,
129+
})
130+
}
131+
}
132+
133+
impl Discovery for StaticProvider {
134+
fn publish(&self, _info: &AddrInfo) {}
135+
136+
fn resolve(
137+
&self,
138+
_endpoint: crate::Endpoint,
139+
node_id: NodeId,
140+
) -> Option<futures_lite::stream::Boxed<anyhow::Result<super::DiscoveryItem>>> {
141+
let guard = self.nodes.read().unwrap();
142+
let info = guard.get(&node_id);
143+
match info {
144+
Some(addr_info) => {
145+
let item = DiscoveryItem {
146+
node_id,
147+
provenance: Self::PROVENANCE,
148+
last_updated: Some(
149+
addr_info
150+
.last_updated
151+
.duration_since(SystemTime::UNIX_EPOCH)
152+
.expect("time drift")
153+
.as_micros() as u64,
154+
),
155+
addr_info: addr_info.info.clone(),
156+
};
157+
Some(stream::iter(Some(Ok(item))).boxed())
158+
}
159+
None => None,
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)