1use std::{
36 borrow::Cow,
37 collections::{BTreeSet, HashSet},
38 fmt::{self, Display},
39 hash::Hash,
40 net::SocketAddr,
41 str::FromStr,
42 sync::Arc,
43};
44
45use iroh_base::{EndpointAddr, EndpointId, RelayUrl, SecretKey, TransportAddr};
46pub use iroh_dns::attrs::{EncodingError, IROH_TXT_NAME, ParseError};
47pub(crate) use iroh_dns::attrs::{IrohAttr, TxtAttrs};
48use iroh_dns::pkarr;
49use n0_error::{ensure, stack_error};
50use url::Url;
51
52#[derive(Debug, Clone, Default, Eq, PartialEq)]
61pub struct EndpointData {
62 addrs: Vec<TransportAddr>,
64 user_data: Option<UserData>,
66}
67
68fn dedup<T: Eq + Hash + Clone>(items: &mut Vec<T>) -> HashSet<T> {
69 let mut seen = HashSet::new();
71 items.retain(|item| seen.insert(item.clone()));
72 seen
73}
74
75impl EndpointData {
76 pub fn new(mut addrs: Vec<TransportAddr>) -> Self {
83 dedup(&mut addrs);
84 Self {
85 addrs,
86 user_data: None,
87 }
88 }
89
90 pub fn with_user_data(mut self, user_data: UserData) -> Self {
96 self.user_data = Some(user_data);
97 self
98 }
99
100 pub fn add_relay_url(&mut self, relay_url: RelayUrl) {
102 let addr = TransportAddr::Relay(relay_url);
103 if !self.addrs.contains(&addr) {
104 self.addrs.push(addr);
105 }
106 }
107
108 pub fn add_ip_addrs(&mut self, addresses: Vec<SocketAddr>) {
110 self.add_addrs(addresses.into_iter().map(TransportAddr::Ip))
111 }
112
113 pub fn add_addrs(&mut self, addrs: impl IntoIterator<Item = TransportAddr>) {
115 let mut addr_set = dedup(&mut self.addrs);
116 for addr in addrs.into_iter() {
117 if !addr_set.contains(&addr) {
118 self.addrs.push(addr.clone());
119 addr_set.insert(addr);
120 }
121 }
122 }
123
124 pub fn set_user_data(&mut self, user_data: Option<UserData>) {
126 self.user_data = user_data;
127 }
128
129 pub fn clear_ip_addrs(&mut self) {
131 self.addrs
132 .retain(|addr| !matches!(addr, TransportAddr::Ip(_)));
133 }
134
135 pub fn clear_relay_urls(&mut self) {
137 self.addrs
138 .retain(|addr| !matches!(addr, TransportAddr::Relay(_)));
139 }
140
141 pub fn relay_urls(&self) -> impl Iterator<Item = &RelayUrl> {
143 self.addrs.iter().filter_map(|addr| match addr {
144 TransportAddr::Relay(url) => Some(url),
145 _ => None,
146 })
147 }
148
149 pub fn user_data(&self) -> Option<&UserData> {
151 self.user_data.as_ref()
152 }
153
154 pub fn ip_addrs(&self) -> impl Iterator<Item = &SocketAddr> {
156 self.addrs.iter().filter_map(|addr| match addr {
157 TransportAddr::Ip(addr) => Some(addr),
158 _ => None,
159 })
160 }
161
162 pub fn addrs(&self) -> impl Iterator<Item = &TransportAddr> {
164 self.addrs.iter()
165 }
166
167 pub fn has_addrs(&self) -> bool {
169 !self.addrs.is_empty()
170 }
171
172 pub fn filtered_addrs(&self, filter: &AddrFilter) -> Cow<'_, Vec<TransportAddr>> {
176 filter.apply(&self.addrs)
177 }
178
179 pub fn apply_filter(&self, filter: &AddrFilter) -> Cow<'_, Self> {
181 match self.filtered_addrs(filter) {
182 Cow::Borrowed(_) => Cow::Borrowed(self),
183 Cow::Owned(addrs) => {
184 let mut data = EndpointData::new(addrs);
185 data.set_user_data(self.user_data.clone());
186 Cow::Owned(data)
187 }
188 }
189 }
190}
191
192impl From<BTreeSet<TransportAddr>> for EndpointData {
195 fn from(addrs: BTreeSet<TransportAddr>) -> Self {
196 Self {
197 addrs: addrs.into_iter().collect(),
198 user_data: None,
199 }
200 }
201}
202
203impl From<BTreeSet<SocketAddr>> for EndpointData {
204 fn from(addrs: BTreeSet<SocketAddr>) -> Self {
205 Self {
206 addrs: addrs.into_iter().map(TransportAddr::Ip).collect(),
207 user_data: None,
208 }
209 }
210}
211
212impl FromIterator<TransportAddr> for EndpointData {
213 fn from_iter<T: IntoIterator<Item = TransportAddr>>(iter: T) -> Self {
214 Self::new(iter.into_iter().collect())
215 }
216}
217
218type AddrFilterFn =
220 dyn Fn(&Vec<TransportAddr>) -> Cow<'_, Vec<TransportAddr>> + Send + Sync + 'static;
221
222#[derive(Clone, Default)]
233pub struct AddrFilter(Option<Arc<AddrFilterFn>>);
234
235impl std::fmt::Debug for AddrFilter {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 if self.0.is_some() {
238 f.debug_struct("AddrFilter").finish_non_exhaustive()
239 } else {
240 write!(f, "identity")
241 }
242 }
243}
244
245impl AddrFilter {
246 pub fn new(
248 f: impl Fn(&Vec<TransportAddr>) -> Cow<'_, Vec<TransportAddr>> + Send + Sync + 'static,
249 ) -> Self {
250 Self(Some(Arc::new(f)))
251 }
252
253 pub fn unfiltered() -> Self {
255 Self::new(|addrs| Cow::Borrowed(addrs))
256 }
257
258 pub fn relay_only() -> Self {
260 Self::new(|addrs| Cow::Owned(addrs.iter().filter(|a| a.is_relay()).cloned().collect()))
261 }
262
263 pub fn ip_only() -> Self {
265 Self::new(|addrs| Cow::Owned(addrs.iter().filter(|a| !a.is_relay()).cloned().collect()))
266 }
267
268 pub fn apply<'a>(&self, addrs: &'a Vec<TransportAddr>) -> Cow<'a, Vec<TransportAddr>> {
270 match &self.0 {
271 Some(f) => f(addrs),
272 None => Cow::Borrowed(addrs),
273 }
274 }
275}
276
277impl From<EndpointAddr> for EndpointData {
278 fn from(endpoint_addr: EndpointAddr) -> Self {
279 Self {
280 addrs: endpoint_addr.addrs.into_iter().collect(),
282 user_data: None,
283 }
284 }
285}
286
287#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
296pub struct UserData(String);
297
298impl UserData {
299 pub const MAX_LENGTH: usize = 245;
305}
306
307#[allow(missing_docs)]
309#[stack_error(derive, add_meta)]
310#[error("max length exceeded")]
311pub struct MaxLengthExceededError {}
312
313impl TryFrom<String> for UserData {
314 type Error = MaxLengthExceededError;
315
316 fn try_from(value: String) -> Result<Self, Self::Error> {
317 ensure!(value.len() <= Self::MAX_LENGTH, MaxLengthExceededError);
318 Ok(Self(value))
319 }
320}
321
322impl FromStr for UserData {
323 type Err = MaxLengthExceededError;
324
325 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
326 ensure!(s.len() <= Self::MAX_LENGTH, MaxLengthExceededError);
327 Ok(Self(s.to_string()))
328 }
329}
330
331impl fmt::Display for UserData {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 write!(f, "{}", self.0)
334 }
335}
336
337impl AsRef<str> for UserData {
338 fn as_ref(&self) -> &str {
339 &self.0
340 }
341}
342
343#[derive(derive_more::Debug, Clone, Eq, PartialEq)]
347pub struct EndpointInfo {
348 pub endpoint_id: EndpointId,
350 pub data: EndpointData,
352}
353
354impl From<EndpointInfo> for EndpointAddr {
355 fn from(value: EndpointInfo) -> Self {
356 value.into_endpoint_addr()
357 }
358}
359
360impl From<EndpointAddr> for EndpointInfo {
361 fn from(addr: EndpointAddr) -> Self {
362 Self {
363 endpoint_id: addr.id,
364 data: EndpointData::from(addr.addrs),
365 }
366 }
367}
368
369impl EndpointInfo {
370 pub fn new(endpoint_id: EndpointId) -> Self {
372 Self::from_parts(endpoint_id, Default::default())
373 }
374
375 pub fn from_parts(endpoint_id: EndpointId, data: EndpointData) -> Self {
377 Self { endpoint_id, data }
378 }
379
380 pub fn with_relay_url(mut self, relay_url: RelayUrl) -> Self {
382 self.data.add_relay_url(relay_url);
383 self
384 }
385
386 pub fn with_ip_addrs(mut self, addrs: Vec<SocketAddr>) -> Self {
388 self.data.add_ip_addrs(addrs);
389 self
390 }
391
392 pub fn with_user_data(mut self, user_data: Option<UserData>) -> Self {
394 self.data.set_user_data(user_data);
395 self
396 }
397
398 pub fn to_endpoint_addr(&self) -> EndpointAddr {
400 EndpointAddr {
401 id: self.endpoint_id,
402 addrs: self.data.addrs.iter().cloned().collect(),
403 }
404 }
405
406 pub fn into_endpoint_addr(self) -> EndpointAddr {
408 let Self { endpoint_id, data } = self;
409 EndpointAddr {
410 id: endpoint_id,
411 addrs: data.addrs.into_iter().collect(),
412 }
413 }
414
415 pub(crate) fn to_attrs(&self) -> TxtAttrs<IrohAttr> {
417 endpoint_info_to_attrs(self)
418 }
419
420 pub fn addrs(&self) -> impl Iterator<Item = &TransportAddr> {
422 self.data.addrs()
423 }
424
425 pub fn relay_urls(&self) -> impl Iterator<Item = &RelayUrl> {
427 self.data.relay_urls()
428 }
429
430 pub fn user_data(&self) -> Option<&UserData> {
432 self.data.user_data()
433 }
434
435 pub fn ip_addrs(&self) -> impl Iterator<Item = &SocketAddr> {
437 self.data.ip_addrs()
438 }
439
440 pub fn from_txt_lookup(
445 domain_name: String,
446 lookup: impl Iterator<Item = impl Display>,
447 ) -> Result<Self, ParseError> {
448 let attrs: TxtAttrs<IrohAttr> = TxtAttrs::from_txt_lookup(domain_name, lookup)?;
449 Ok(endpoint_info_from_attrs(&attrs))
450 }
451
452 pub fn from_pkarr_signed_packet(packet: &pkarr::SignedPacket) -> Result<Self, ParseError> {
454 let attrs: TxtAttrs<IrohAttr> = TxtAttrs::from_pkarr_signed_packet(packet)?;
455 Ok(endpoint_info_from_attrs(&attrs))
456 }
457
458 pub fn to_pkarr_signed_packet(
462 &self,
463 secret_key: &SecretKey,
464 ttl: u32,
465 ) -> Result<pkarr::SignedPacket, EncodingError> {
466 self.to_attrs().to_pkarr_signed_packet(secret_key, ttl)
467 }
468
469 pub fn to_txt_strings(&self) -> Vec<String> {
471 self.to_attrs().to_txt_strings().collect()
472 }
473}
474
475fn endpoint_info_to_attrs(info: &EndpointInfo) -> TxtAttrs<IrohAttr> {
477 let mut attrs = vec![];
478 for addr in &info.data.addrs {
479 match addr {
480 TransportAddr::Relay(url) => attrs.push((IrohAttr::Relay, url.to_string())),
481 TransportAddr::Ip(addr) => attrs.push((IrohAttr::Addr, addr.to_string())),
482 TransportAddr::Custom(addr) => attrs.push((IrohAttr::Addr, addr.to_string())),
483 _ => {}
484 }
485 }
486
487 if let Some(user_data) = &info.data.user_data {
488 attrs.push((IrohAttr::UserData, user_data.to_string()));
489 }
490 TxtAttrs::from_parts(info.endpoint_id, attrs.into_iter())
491}
492
493fn endpoint_info_from_attrs(attrs: &TxtAttrs<IrohAttr>) -> EndpointInfo {
495 use iroh_base::CustomAddr;
496
497 let endpoint_id = attrs.endpoint_id();
498 let a = attrs.attrs();
499 let relay_urls = a
500 .get(&IrohAttr::Relay)
501 .into_iter()
502 .flatten()
503 .filter_map(|s| Url::parse(s).ok())
504 .map(|url| TransportAddr::Relay(url.into()));
505 let addrs = a
506 .get(&IrohAttr::Addr)
507 .into_iter()
508 .flatten()
509 .filter_map(|s| {
510 if let Ok(addr) = SocketAddr::from_str(s) {
511 Some(TransportAddr::Ip(addr))
512 } else if let Ok(addr) = CustomAddr::from_str(s) {
513 Some(TransportAddr::Custom(addr))
514 } else {
515 None
516 }
517 });
518
519 let user_data = a
520 .get(&IrohAttr::UserData)
521 .into_iter()
522 .flatten()
523 .next()
524 .and_then(|s| UserData::from_str(s).ok());
525 let mut data = EndpointData::default();
526 data.set_user_data(user_data);
527 data.add_addrs(relay_urls.chain(addrs));
528
529 EndpointInfo { endpoint_id, data }
530}
531
532#[cfg(test)]
533mod tests {
534 use std::str::FromStr;
535
536 use hickory_resolver::{
537 lookup::Lookup,
538 proto::{
539 op::Query,
540 rr::{
541 Name, RData, Record, RecordType,
542 rdata::{A, TXT},
543 },
544 },
545 };
546 use iroh_base::{EndpointId, SecretKey, TransportAddr};
547 use n0_error::{Result, StdResultExt};
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_hickory_lookup() -> Result {
637 let name = Name::from_utf8(
638 "_iroh.dgjpkxyn3zyrk3zfads5duwdgbqpkwbjxfj4yt7rezidr3fijccy.dns.iroh.link.",
639 )
640 .std_context("dns name")?;
641 let query = Query::query(name.clone(), RecordType::TXT);
642 let records = [
643 Record::from_rdata(
644 name.clone(),
645 30,
646 RData::TXT(TXT::new(vec!["addr=192.168.96.145:60165".to_string()])),
647 ),
648 Record::from_rdata(
649 name.clone(),
650 30,
651 RData::TXT(TXT::new(vec!["addr=213.208.157.87:60165".to_string()])),
652 ),
653 Record::from_rdata(name.clone(), 30, RData::A(A::new(127, 0, 0, 1))),
655 Record::from_rdata(
657 {
658 let other_id = EndpointId::from_str(
660 "a55f26132e5e43de834d534332f66a20d480c3e50a13a312a071adea6569981e",
661 )?;
662 Name::from_utf8(format!("_iroh.{}.dns.iroh.link.", other_id.to_z32()))
663 }
664 .std_context("name")?,
665 30,
666 RData::TXT(TXT::new(vec![
667 "relay=https://euw1-1.relay.iroh.network./".to_string(),
668 ])),
669 ),
670 Record::from_rdata(
672 Name::from_utf8("dns.iroh.link.").std_context("name")?,
673 30,
674 RData::TXT(TXT::new(vec![
675 "relay=https://euw1-1.relay.iroh.network./".to_string(),
676 ])),
677 ),
678 Record::from_rdata(
679 name.clone(),
680 30,
681 RData::TXT(TXT::new(vec![
682 "relay=https://euw1-1.relay.iroh.network./".to_string(),
683 ])),
684 ),
685 ];
686 let lookup = Lookup::new_with_max_ttl(query, records);
687 let lookup = lookup
688 .answers()
689 .iter()
690 .filter_map(|record| match &record.data {
691 RData::TXT(txt) => Some(TxtRecordData::from(txt.txt_data.to_vec())),
692 _ => None,
693 });
694
695 let endpoint_info = EndpointInfo::from_txt_lookup(name.to_string(), lookup)?;
696
697 let expected_endpoint_info = EndpointInfo::new(EndpointId::from_str(
698 "1992d53c02cdc04566e5c0edb1ce83305cd550297953a047a445ea3264b54b18",
699 )?)
700 .with_relay_url("https://euw1-1.relay.iroh.network./".parse()?)
701 .with_ip_addrs(vec![
702 "192.168.96.145:60165".parse().unwrap(),
703 "213.208.157.87:60165".parse().unwrap(),
704 ]);
705
706 assert_eq!(endpoint_info, expected_endpoint_info);
707
708 Ok(())
709 }
710}