1use std::{
44 borrow::Cow,
45 collections::{BTreeSet, HashSet},
46 fmt::{self, Display},
47 hash::Hash,
48 net::SocketAddr,
49 str::FromStr,
50 sync::Arc,
51};
52
53use iroh_base::{EndpointAddr, EndpointId, RelayUrl, SecretKey, TransportAddr};
54use n0_error::{ensure, stack_error};
55use url::Url;
56
57use crate::{
58 attrs::{EncodingError, IrohAttr, ParseError, TxtAttrs},
59 pkarr,
60};
61
62#[derive(Debug, Clone, Default, Eq, PartialEq)]
71pub struct EndpointData {
72 addrs: Vec<TransportAddr>,
74 user_data: Option<UserData>,
76}
77
78fn dedup<T: Eq + Hash + Clone>(items: &mut Vec<T>) -> HashSet<T> {
79 let mut seen = HashSet::new();
81 items.retain(|item| seen.insert(item.clone()));
82 seen
83}
84
85impl EndpointData {
86 pub fn new(mut addrs: Vec<TransportAddr>) -> Self {
93 dedup(&mut addrs);
94 Self {
95 addrs,
96 user_data: None,
97 }
98 }
99
100 pub fn with_user_data(mut self, user_data: UserData) -> Self {
106 self.user_data = Some(user_data);
107 self
108 }
109
110 pub fn add_relay_url(&mut self, relay_url: RelayUrl) {
112 let addr = TransportAddr::Relay(relay_url);
113 if !self.addrs.contains(&addr) {
114 self.addrs.push(addr);
115 }
116 }
117
118 pub fn add_ip_addrs(&mut self, addresses: Vec<SocketAddr>) {
120 self.add_addrs(addresses.into_iter().map(TransportAddr::Ip))
121 }
122
123 pub fn add_addrs(&mut self, addrs: impl IntoIterator<Item = TransportAddr>) {
125 let mut addr_set = dedup(&mut self.addrs);
126 for addr in addrs.into_iter() {
127 if !addr_set.contains(&addr) {
128 self.addrs.push(addr.clone());
129 addr_set.insert(addr);
130 }
131 }
132 }
133
134 pub fn set_user_data(&mut self, user_data: Option<UserData>) {
136 self.user_data = user_data;
137 }
138
139 pub fn clear_ip_addrs(&mut self) {
141 self.addrs
142 .retain(|addr| !matches!(addr, TransportAddr::Ip(_)));
143 }
144
145 pub fn clear_relay_urls(&mut self) {
147 self.addrs
148 .retain(|addr| !matches!(addr, TransportAddr::Relay(_)));
149 }
150
151 pub fn relay_urls(&self) -> impl Iterator<Item = &RelayUrl> {
153 self.addrs.iter().filter_map(|addr| match addr {
154 TransportAddr::Relay(url) => Some(url),
155 _ => None,
156 })
157 }
158
159 pub fn user_data(&self) -> Option<&UserData> {
161 self.user_data.as_ref()
162 }
163
164 pub fn ip_addrs(&self) -> impl Iterator<Item = &SocketAddr> {
166 self.addrs.iter().filter_map(|addr| match addr {
167 TransportAddr::Ip(addr) => Some(addr),
168 _ => None,
169 })
170 }
171
172 pub fn addrs(&self) -> impl Iterator<Item = &TransportAddr> {
174 self.addrs.iter()
175 }
176
177 pub fn has_addrs(&self) -> bool {
179 !self.addrs.is_empty()
180 }
181
182 pub fn filtered_addrs(&self, filter: &AddrFilter) -> Cow<'_, Vec<TransportAddr>> {
186 filter.apply(&self.addrs)
187 }
188
189 pub fn apply_filter(&self, filter: &AddrFilter) -> Cow<'_, Self> {
191 match self.filtered_addrs(filter) {
192 Cow::Borrowed(_) => Cow::Borrowed(self),
193 Cow::Owned(addrs) => {
194 let mut data = EndpointData::new(addrs);
195 data.set_user_data(self.user_data.clone());
196 Cow::Owned(data)
197 }
198 }
199 }
200}
201
202impl From<BTreeSet<TransportAddr>> for EndpointData {
205 fn from(addrs: BTreeSet<TransportAddr>) -> Self {
206 Self {
207 addrs: addrs.into_iter().collect(),
208 user_data: None,
209 }
210 }
211}
212
213impl From<BTreeSet<SocketAddr>> for EndpointData {
214 fn from(addrs: BTreeSet<SocketAddr>) -> Self {
215 Self {
216 addrs: addrs.into_iter().map(TransportAddr::Ip).collect(),
217 user_data: None,
218 }
219 }
220}
221
222impl FromIterator<TransportAddr> for EndpointData {
223 fn from_iter<T: IntoIterator<Item = TransportAddr>>(iter: T) -> Self {
224 Self::new(iter.into_iter().collect())
225 }
226}
227
228type AddrFilterFn =
230 dyn Fn(&Vec<TransportAddr>) -> Cow<'_, Vec<TransportAddr>> + Send + Sync + 'static;
231
232#[derive(Clone, Default)]
243pub struct AddrFilter(Option<Arc<AddrFilterFn>>);
244
245impl std::fmt::Debug for AddrFilter {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 if self.0.is_some() {
248 f.debug_struct("AddrFilter").finish_non_exhaustive()
249 } else {
250 write!(f, "identity")
251 }
252 }
253}
254
255impl AddrFilter {
256 pub fn new(
258 f: impl Fn(&Vec<TransportAddr>) -> Cow<'_, Vec<TransportAddr>> + Send + Sync + 'static,
259 ) -> Self {
260 Self(Some(Arc::new(f)))
261 }
262
263 pub fn unfiltered() -> Self {
265 Self::new(|addrs| Cow::Borrowed(addrs))
266 }
267
268 pub fn relay_only() -> Self {
270 Self::new(|addrs| Cow::Owned(addrs.iter().filter(|a| a.is_relay()).cloned().collect()))
271 }
272
273 pub fn ip_only() -> Self {
275 Self::new(|addrs| Cow::Owned(addrs.iter().filter(|a| !a.is_relay()).cloned().collect()))
276 }
277
278 pub fn apply<'a>(&self, addrs: &'a Vec<TransportAddr>) -> Cow<'a, Vec<TransportAddr>> {
280 match &self.0 {
281 Some(f) => f(addrs),
282 None => Cow::Borrowed(addrs),
283 }
284 }
285}
286
287impl From<EndpointAddr> for EndpointData {
288 fn from(endpoint_addr: EndpointAddr) -> Self {
289 Self {
290 addrs: endpoint_addr.addrs.into_iter().collect(),
292 user_data: None,
293 }
294 }
295}
296
297#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
306pub struct UserData(String);
307
308impl UserData {
309 pub const MAX_LENGTH: usize = 245;
315}
316
317#[allow(missing_docs)]
319#[stack_error(derive, add_meta)]
320#[error("max length exceeded")]
321pub struct MaxLengthExceededError {}
322
323impl TryFrom<String> for UserData {
324 type Error = MaxLengthExceededError;
325
326 fn try_from(value: String) -> Result<Self, Self::Error> {
327 ensure!(value.len() <= Self::MAX_LENGTH, MaxLengthExceededError);
328 Ok(Self(value))
329 }
330}
331
332impl FromStr for UserData {
333 type Err = MaxLengthExceededError;
334
335 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
336 ensure!(s.len() <= Self::MAX_LENGTH, MaxLengthExceededError);
337 Ok(Self(s.to_string()))
338 }
339}
340
341impl fmt::Display for UserData {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 write!(f, "{}", self.0)
344 }
345}
346
347impl AsRef<str> for UserData {
348 fn as_ref(&self) -> &str {
349 &self.0
350 }
351}
352
353#[derive(derive_more::Debug, Clone, Eq, PartialEq)]
357pub struct EndpointInfo {
358 pub endpoint_id: EndpointId,
360 pub data: EndpointData,
362}
363
364impl From<EndpointInfo> for EndpointAddr {
365 fn from(value: EndpointInfo) -> Self {
366 value.into_endpoint_addr()
367 }
368}
369
370impl From<EndpointAddr> for EndpointInfo {
371 fn from(addr: EndpointAddr) -> Self {
372 Self {
373 endpoint_id: addr.id,
374 data: EndpointData::from(addr.addrs),
375 }
376 }
377}
378
379impl EndpointInfo {
380 pub fn new(endpoint_id: EndpointId) -> Self {
382 Self::from_parts(endpoint_id, Default::default())
383 }
384
385 pub fn from_parts(endpoint_id: EndpointId, data: EndpointData) -> Self {
387 Self { endpoint_id, data }
388 }
389
390 pub fn with_relay_url(mut self, relay_url: RelayUrl) -> Self {
392 self.data.add_relay_url(relay_url);
393 self
394 }
395
396 pub fn with_ip_addrs(mut self, addrs: Vec<SocketAddr>) -> Self {
398 self.data.add_ip_addrs(addrs);
399 self
400 }
401
402 pub fn with_user_data(mut self, user_data: Option<UserData>) -> Self {
404 self.data.set_user_data(user_data);
405 self
406 }
407
408 pub fn to_endpoint_addr(&self) -> EndpointAddr {
410 EndpointAddr {
411 id: self.endpoint_id,
412 addrs: self.data.addrs.iter().cloned().collect(),
413 }
414 }
415
416 pub fn into_endpoint_addr(self) -> EndpointAddr {
418 let Self { endpoint_id, data } = self;
419 EndpointAddr {
420 id: endpoint_id,
421 addrs: data.addrs.into_iter().collect(),
422 }
423 }
424
425 pub(crate) fn to_attrs(&self) -> TxtAttrs<IrohAttr> {
427 endpoint_info_to_attrs(self)
428 }
429
430 pub fn addrs(&self) -> impl Iterator<Item = &TransportAddr> {
432 self.data.addrs()
433 }
434
435 pub fn relay_urls(&self) -> impl Iterator<Item = &RelayUrl> {
437 self.data.relay_urls()
438 }
439
440 pub fn user_data(&self) -> Option<&UserData> {
442 self.data.user_data()
443 }
444
445 pub fn ip_addrs(&self) -> impl Iterator<Item = &SocketAddr> {
447 self.data.ip_addrs()
448 }
449
450 pub fn from_txt_lookup(
455 domain_name: String,
456 lookup: impl Iterator<Item = impl Display>,
457 ) -> Result<Self, ParseError> {
458 let attrs: TxtAttrs<IrohAttr> = TxtAttrs::from_txt_lookup(domain_name, lookup)?;
459 Ok(endpoint_info_from_attrs(&attrs))
460 }
461
462 pub fn from_pkarr_signed_packet(packet: &pkarr::SignedPacket) -> Result<Self, ParseError> {
464 let attrs: TxtAttrs<IrohAttr> = TxtAttrs::from_pkarr_signed_packet(packet)?;
465 Ok(endpoint_info_from_attrs(&attrs))
466 }
467
468 pub fn to_pkarr_signed_packet(
472 &self,
473 secret_key: &SecretKey,
474 ttl: u32,
475 ) -> Result<pkarr::SignedPacket, EncodingError> {
476 self.to_attrs().to_pkarr_signed_packet(secret_key, ttl)
477 }
478
479 pub fn to_txt_strings(&self) -> Vec<String> {
481 self.to_attrs().to_txt_strings().collect()
482 }
483}
484
485fn endpoint_info_to_attrs(info: &EndpointInfo) -> TxtAttrs<IrohAttr> {
487 let mut attrs = vec![];
488 for addr in &info.data.addrs {
489 match addr {
490 TransportAddr::Relay(url) => attrs.push((IrohAttr::Relay, url.to_string())),
491 TransportAddr::Ip(addr) => attrs.push((IrohAttr::Addr, addr.to_string())),
492 TransportAddr::Custom(addr) => attrs.push((IrohAttr::Addr, addr.to_string())),
493 _ => {}
494 }
495 }
496
497 if let Some(user_data) = &info.data.user_data {
498 attrs.push((IrohAttr::UserData, user_data.to_string()));
499 }
500 TxtAttrs::from_parts(info.endpoint_id, attrs.into_iter())
501}
502
503fn endpoint_info_from_attrs(attrs: &TxtAttrs<IrohAttr>) -> EndpointInfo {
505 use iroh_base::CustomAddr;
506
507 let endpoint_id = attrs.endpoint_id();
508 let a = attrs.attrs();
509 let relay_urls = a
510 .get(&IrohAttr::Relay)
511 .into_iter()
512 .flatten()
513 .filter_map(|s| Url::parse(s).ok())
514 .map(|url| TransportAddr::Relay(url.into()));
515 let addrs = a
516 .get(&IrohAttr::Addr)
517 .into_iter()
518 .flatten()
519 .filter_map(|s| {
520 if let Ok(addr) = SocketAddr::from_str(s) {
521 Some(TransportAddr::Ip(addr))
522 } else if let Ok(addr) = CustomAddr::from_str(s) {
523 Some(TransportAddr::Custom(addr))
524 } else {
525 None
526 }
527 });
528
529 let user_data = a
530 .get(&IrohAttr::UserData)
531 .into_iter()
532 .flatten()
533 .next()
534 .and_then(|s| UserData::from_str(s).ok());
535 let mut data = EndpointData::default();
536 data.set_user_data(user_data);
537 data.add_addrs(relay_urls.chain(addrs));
538
539 EndpointInfo { endpoint_id, data }
540}
541
542#[cfg(test)]
543mod tests {
544 use std::str::FromStr;
545
546 use iroh_base::{EndpointId, SecretKey, TransportAddr};
547 use n0_error::Result;
548
549 use super::{EndpointData, EndpointInfo};
550 use crate::dns::TxtRecordData;
551
552 #[test]
553 fn txt_attr_roundtrip() {
554 let endpoint_data = EndpointData::from_iter([
555 TransportAddr::Relay("https://example.com".parse().unwrap()),
556 TransportAddr::Ip("127.0.0.1:1234".parse().unwrap()),
557 ])
558 .with_user_data("foobar".parse().unwrap());
559 let endpoint_id = "vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia"
560 .parse()
561 .unwrap();
562 let expected = EndpointInfo::from_parts(endpoint_id, endpoint_data);
563 let attrs = expected.to_attrs();
564 let actual = super::endpoint_info_from_attrs(&attrs);
565 assert_eq!(expected, actual);
566 }
567
568 #[test]
569 fn signed_packet_roundtrip() {
570 let secret_key =
571 SecretKey::from_str("vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia").unwrap();
572 let endpoint_data = EndpointData::from_iter([
573 TransportAddr::Relay("https://example.com".parse().unwrap()),
574 TransportAddr::Ip("127.0.0.1:1234".parse().unwrap()),
575 ])
576 .with_user_data("foobar".parse().unwrap());
577 let expected = EndpointInfo::from_parts(secret_key.public(), endpoint_data);
578 let packet = expected.to_pkarr_signed_packet(&secret_key, 30).unwrap();
579 let actual = EndpointInfo::from_pkarr_signed_packet(&packet).unwrap();
580 assert_eq!(expected, actual);
581 }
582
583 #[test]
584 fn txt_attr_roundtrip_with_custom_addr() {
585 use iroh_base::CustomAddr;
586
587 let bt_addr = CustomAddr::from_parts(1, &[0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6]);
588 let tor_addr = CustomAddr::from_parts(42, &[0xab; 32]);
589
590 let endpoint_data = EndpointData::from_iter([
591 TransportAddr::Relay("https://example.com".parse().unwrap()),
592 TransportAddr::Ip("127.0.0.1:1234".parse().unwrap()),
593 TransportAddr::Custom(bt_addr),
594 TransportAddr::Custom(tor_addr),
595 ]);
596 let endpoint_id = "vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia"
597 .parse()
598 .unwrap();
599 let expected = EndpointInfo::from_parts(endpoint_id, endpoint_data);
600 let attrs = expected.to_attrs();
601 let actual = super::endpoint_info_from_attrs(&attrs);
602 assert_eq!(expected, actual);
603 }
604
605 #[test]
606 fn signed_packet_roundtrip_with_custom_addr() {
607 use iroh_base::CustomAddr;
608
609 let secret_key =
610 SecretKey::from_str("vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia").unwrap();
611
612 let bt_addr = CustomAddr::from_parts(1, &[0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6]);
613 let tor_addr = CustomAddr::from_parts(42, &[0xab; 32]);
614
615 let endpoint_data = EndpointData::from_iter([
616 TransportAddr::Relay("https://example.com".parse().unwrap()),
617 TransportAddr::Ip("127.0.0.1:1234".parse().unwrap()),
618 TransportAddr::Custom(bt_addr),
619 TransportAddr::Custom(tor_addr),
620 ])
621 .with_user_data("foobar".parse().unwrap());
622
623 let expected = EndpointInfo::from_parts(secret_key.public(), endpoint_data);
624 let packet = expected.to_pkarr_signed_packet(&secret_key, 30).unwrap();
625 let actual = EndpointInfo::from_pkarr_signed_packet(&packet).unwrap();
626 assert_eq!(expected, actual);
627 }
628
629 #[test]
636 fn test_from_txt_lookup() -> Result {
637 let name = "_iroh.dgjpkxyn3zyrk3zfads5duwdgbqpkwbjxfj4yt7rezidr3fijccy.dns.iroh.link.";
638 let records = vec![
639 TxtRecordData::from(vec!["addr=192.168.96.145:60165".to_string()]),
640 TxtRecordData::from(vec!["addr=213.208.157.87:60165".to_string()]),
641 TxtRecordData::from(vec![
642 "relay=https://euw1-1.relay.iroh.network./".to_string(),
643 ]),
644 ];
645 let endpoint_info = EndpointInfo::from_txt_lookup(name.to_string(), records.into_iter())?;
646
647 let expected_endpoint_info = EndpointInfo::new(EndpointId::from_str(
648 "1992d53c02cdc04566e5c0edb1ce83305cd550297953a047a445ea3264b54b18",
649 )?)
650 .with_relay_url("https://euw1-1.relay.iroh.network./".parse()?)
651 .with_ip_addrs(vec![
652 "192.168.96.145:60165".parse().unwrap(),
653 "213.208.157.87:60165".parse().unwrap(),
654 ]);
655
656 assert_eq!(endpoint_info, expected_endpoint_info);
657
658 Ok(())
659 }
660}