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 hickory_resolver::{
547 lookup::Lookup,
548 proto::{
549 op::Query,
550 rr::{
551 Name, RData, Record, RecordType,
552 rdata::{A, TXT},
553 },
554 },
555 };
556 use iroh_base::{EndpointId, SecretKey, TransportAddr};
557 use n0_error::{Result, StdResultExt};
558
559 use super::{EndpointData, EndpointInfo};
560 use crate::dns::TxtRecordData;
561
562 #[test]
563 fn txt_attr_roundtrip() {
564 let endpoint_data = EndpointData::from_iter([
565 TransportAddr::Relay("https://example.com".parse().unwrap()),
566 TransportAddr::Ip("127.0.0.1:1234".parse().unwrap()),
567 ])
568 .with_user_data("foobar".parse().unwrap());
569 let endpoint_id = "vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia"
570 .parse()
571 .unwrap();
572 let expected = EndpointInfo::from_parts(endpoint_id, endpoint_data);
573 let attrs = expected.to_attrs();
574 let actual = super::endpoint_info_from_attrs(&attrs);
575 assert_eq!(expected, actual);
576 }
577
578 #[test]
579 fn signed_packet_roundtrip() {
580 let secret_key =
581 SecretKey::from_str("vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia").unwrap();
582 let endpoint_data = EndpointData::from_iter([
583 TransportAddr::Relay("https://example.com".parse().unwrap()),
584 TransportAddr::Ip("127.0.0.1:1234".parse().unwrap()),
585 ])
586 .with_user_data("foobar".parse().unwrap());
587 let expected = EndpointInfo::from_parts(secret_key.public(), endpoint_data);
588 let packet = expected.to_pkarr_signed_packet(&secret_key, 30).unwrap();
589 let actual = EndpointInfo::from_pkarr_signed_packet(&packet).unwrap();
590 assert_eq!(expected, actual);
591 }
592
593 #[test]
594 fn txt_attr_roundtrip_with_custom_addr() {
595 use iroh_base::CustomAddr;
596
597 let bt_addr = CustomAddr::from_parts(1, &[0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6]);
598 let tor_addr = CustomAddr::from_parts(42, &[0xab; 32]);
599
600 let endpoint_data = EndpointData::from_iter([
601 TransportAddr::Relay("https://example.com".parse().unwrap()),
602 TransportAddr::Ip("127.0.0.1:1234".parse().unwrap()),
603 TransportAddr::Custom(bt_addr),
604 TransportAddr::Custom(tor_addr),
605 ]);
606 let endpoint_id = "vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia"
607 .parse()
608 .unwrap();
609 let expected = EndpointInfo::from_parts(endpoint_id, endpoint_data);
610 let attrs = expected.to_attrs();
611 let actual = super::endpoint_info_from_attrs(&attrs);
612 assert_eq!(expected, actual);
613 }
614
615 #[test]
616 fn signed_packet_roundtrip_with_custom_addr() {
617 use iroh_base::CustomAddr;
618
619 let secret_key =
620 SecretKey::from_str("vpnk377obfvzlipnsfbqba7ywkkenc4xlpmovt5tsfujoa75zqia").unwrap();
621
622 let bt_addr = CustomAddr::from_parts(1, &[0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6]);
623 let tor_addr = CustomAddr::from_parts(42, &[0xab; 32]);
624
625 let endpoint_data = EndpointData::from_iter([
626 TransportAddr::Relay("https://example.com".parse().unwrap()),
627 TransportAddr::Ip("127.0.0.1:1234".parse().unwrap()),
628 TransportAddr::Custom(bt_addr),
629 TransportAddr::Custom(tor_addr),
630 ])
631 .with_user_data("foobar".parse().unwrap());
632
633 let expected = EndpointInfo::from_parts(secret_key.public(), endpoint_data);
634 let packet = expected.to_pkarr_signed_packet(&secret_key, 30).unwrap();
635 let actual = EndpointInfo::from_pkarr_signed_packet(&packet).unwrap();
636 assert_eq!(expected, actual);
637 }
638
639 #[test]
646 fn test_from_hickory_lookup() -> Result {
647 let name = Name::from_utf8(
648 "_iroh.dgjpkxyn3zyrk3zfads5duwdgbqpkwbjxfj4yt7rezidr3fijccy.dns.iroh.link.",
649 )
650 .std_context("dns name")?;
651 let query = Query::query(name.clone(), RecordType::TXT);
652 let records = [
653 Record::from_rdata(
654 name.clone(),
655 30,
656 RData::TXT(TXT::new(vec!["addr=192.168.96.145:60165".to_string()])),
657 ),
658 Record::from_rdata(
659 name.clone(),
660 30,
661 RData::TXT(TXT::new(vec!["addr=213.208.157.87:60165".to_string()])),
662 ),
663 Record::from_rdata(name.clone(), 30, RData::A(A::new(127, 0, 0, 1))),
665 Record::from_rdata(
667 {
668 let other_id = EndpointId::from_str(
670 "a55f26132e5e43de834d534332f66a20d480c3e50a13a312a071adea6569981e",
671 )?;
672 Name::from_utf8(format!("_iroh.{}.dns.iroh.link.", other_id.to_z32()))
673 }
674 .std_context("name")?,
675 30,
676 RData::TXT(TXT::new(vec![
677 "relay=https://euw1-1.relay.iroh.network./".to_string(),
678 ])),
679 ),
680 Record::from_rdata(
682 Name::from_utf8("dns.iroh.link.").std_context("name")?,
683 30,
684 RData::TXT(TXT::new(vec![
685 "relay=https://euw1-1.relay.iroh.network./".to_string(),
686 ])),
687 ),
688 Record::from_rdata(
689 name.clone(),
690 30,
691 RData::TXT(TXT::new(vec![
692 "relay=https://euw1-1.relay.iroh.network./".to_string(),
693 ])),
694 ),
695 ];
696 let lookup = Lookup::new_with_max_ttl(query, records);
697 let lookup = lookup
698 .answers()
699 .iter()
700 .filter_map(|record| match &record.data {
701 RData::TXT(txt) => Some(TxtRecordData::from(txt.txt_data.to_vec())),
702 _ => None,
703 });
704
705 let endpoint_info = EndpointInfo::from_txt_lookup(name.to_string(), lookup)?;
706
707 let expected_endpoint_info = EndpointInfo::new(EndpointId::from_str(
708 "1992d53c02cdc04566e5c0edb1ce83305cd550297953a047a445ea3264b54b18",
709 )?)
710 .with_relay_url("https://euw1-1.relay.iroh.network./".parse()?)
711 .with_ip_addrs(vec![
712 "192.168.96.145:60165".parse().unwrap(),
713 "213.208.157.87:60165".parse().unwrap(),
714 ]);
715
716 assert_eq!(endpoint_info, expected_endpoint_info);
717
718 Ok(())
719 }
720}