Skip to main content

tor_linkspec/
traits.rs

1//! Declare traits to be implemented by types that describe a place
2//! that Tor can connect to, directly or indirectly.
3
4use derive_deftly::derive_deftly_adhoc;
5use itertools::Itertools;
6use safelog::Redactable;
7use std::{
8    fmt,
9    iter::FusedIterator,
10    net::{IpAddr, SocketAddr},
11};
12use tor_llcrypto::pk;
13
14use crate::{ChannelMethod, RelayIdRef, RelayIdType, RelayIdTypeIter};
15
16#[cfg(feature = "pt-client")]
17use crate::PtTargetAddr;
18
19/// Legacy implementation helper for HasRelayIds.
20///
21/// Previously, we assumed that everything had these two identity types, which
22/// is not an assumption we want to keep making in the future.
23pub trait HasRelayIdsLegacy {
24    /// Return the ed25519 identity for this relay.
25    fn ed_identity(&self) -> &pk::ed25519::Ed25519Identity;
26    /// Return the RSA identity for this relay.
27    fn rsa_identity(&self) -> &pk::rsa::RsaIdentity;
28}
29
30/// An object containing information about a relay's identity keys.
31///
32/// This trait has a fairly large number of methods, most of which you're not
33/// actually expected to implement.  The only one that you need to provide is
34/// [`identity`](HasRelayIds::identity).
35pub trait HasRelayIds {
36    /// Return the identity of this relay whose type is `key_type`, or None if
37    /// the relay has no such identity.
38    ///
39    /// (Currently all relays have all recognized identity types, but we might
40    /// implement or deprecate an identity type in the future.)
41    fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>>;
42
43    /// Return an iterator over all of the identities held by this object.
44    fn identities(&self) -> RelayIdIter<'_, Self> {
45        RelayIdIter {
46            info: self,
47            next_key: RelayIdType::all_types(),
48        }
49    }
50
51    /// Return the ed25519 identity for this relay if it has one.
52    fn ed_identity(&self) -> Option<&pk::ed25519::Ed25519Identity> {
53        self.identity(RelayIdType::Ed25519)
54            .map(RelayIdRef::unwrap_ed25519)
55    }
56
57    /// Return the RSA identity for this relay if it has one.
58    fn rsa_identity(&self) -> Option<&pk::rsa::RsaIdentity> {
59        self.identity(RelayIdType::Rsa).map(RelayIdRef::unwrap_rsa)
60    }
61
62    /// Check whether the provided Id is a known identity of this relay.
63    ///
64    /// Remember that a given set of identity keys may be incomplete: some
65    /// objects that represent a relay have only a subset of the relay's
66    /// identities. Therefore, a "true" answer means that the relay has this
67    /// identity,  but a "false" answer could mean that the relay has a
68    /// different identity of this type, or that it has _no_ known identity of
69    /// this type.
70    fn has_identity(&self, id: RelayIdRef<'_>) -> bool {
71        self.identity(id.id_type()).map(|my_id| my_id == id) == Some(true)
72    }
73
74    /// Return true if this object has any known identity.
75    fn has_any_identity(&self) -> bool {
76        RelayIdType::all_types().any(|id_type| self.identity(id_type).is_some())
77    }
78
79    /// Return true if this object has exactly the same relay IDs as `other`.
80    //
81    // TODO: Once we make it so particular identity key types are optional, we
82    // should add a note saying that this function is usually not what you want
83    // for many cases, since you might want to know "could this be the same
84    // relay" vs "is this definitely the same relay."
85    //
86    // NOTE: We don't make this an `Eq` method, since we want to make callers
87    // choose carefully among this method, `has_all_relay_ids_from`, and any
88    // similar methods we add in the future.
89    #[allow(clippy::nonminimal_bool)] // rust-clippy/issues/12627
90    fn same_relay_ids<T: HasRelayIds + ?Sized>(&self, other: &T) -> bool {
91        // We use derive-deftly to iterate over the id types, rather than strum
92        //
93        // Empirically, with rustc 1.77.0-beta.5, this arranges that
94        //     <tor_netdir::Relay as HasRelayIds>::same_relay_ids
95        // compiles to the same asm (on amd64) as the open-coded inherent
96        //     tor_netdir::Relay::has_same_relay_ids
97        //
98        // The problem with the strum approach seems to be that the compiler doesn't inline
99        //     <RelayIdTypeIter as Iterator>::next
100        // and unroll the loop.
101        // Adding `#[inline]` and even `#[inline(always)]` to the strum output didn't help.
102        //
103        // When `next()` isn't inlined and the loop unrolled,
104        // the compiler can't inline the matching on the id type,
105        // and generate the obvious simple function.
106        //
107        // Empirically, the same results with non-inlined next() and non-unrolled loop,
108        // were obtained with:
109        //   - a simpler hand-coded Iterator struct
110        //   - that hand-coded Iterator struct locally present in tor-netdir,
111        //   - using `<[RelayIdType; ] as IntoIterator>`
112        //
113        // I experimented to see if this was a general problem with `strum`'s iterator.
114        // In a smaller test program the compiler *does* unroll and inline.
115        // I suspect that the compiler is having trouble with the complexities
116        // of disentangling `HasLegacyRelayIds` and/or comparing `Option<RelayIdRef>`.
117        //
118        // TODO: do we want to replace RelayIdType::all_types with derive-deftly
119        // in RelayIdIter, has_all_relay_ids_from, has_any_relay_id_from, etc.?
120        // If so, search this crate for all_types.
121        derive_deftly_adhoc! {
122            RelayIdType:
123            $(
124                self.identity($vtype) == other.identity($vtype) &&
125            )
126                true
127        }
128    }
129
130    /// Return true if this object has every relay ID that `other` does.
131    ///
132    /// (It still returns true if there are some IDs in this object that are not
133    /// present in `other`.)
134    fn has_all_relay_ids_from<T: HasRelayIds + ?Sized>(&self, other: &T) -> bool {
135        RelayIdType::all_types().all(|key_type| {
136            match (self.identity(key_type), other.identity(key_type)) {
137                // If we both have the same key for this type, great.
138                (Some(mine), Some(theirs)) if mine == theirs => true,
139                // Uh oh. They do have a key for his type, but it's not ours.
140                (_, Some(_theirs)) => false,
141                // If they don't care what we have for this type, great.
142                (_, None) => true,
143            }
144        })
145    }
146
147    /// Return true if this object has any relay ID that `other` has.
148    ///
149    /// This is symmetrical:
150    /// it returns true if the two objects have any overlap in their identities.
151    fn has_any_relay_id_from<T: HasRelayIds + ?Sized>(&self, other: &T) -> bool {
152        RelayIdType::all_types()
153            .filter_map(|key_type| Some((self.identity(key_type)?, other.identity(key_type)?)))
154            .any(|(self_id, other_id)| self_id == other_id)
155    }
156
157    /// Compare this object to another HasRelayIds.
158    ///
159    /// Objects are sorted by Ed25519 identities, with ties decided by RSA
160    /// identities. An absent identity of a given type is sorted before a
161    /// present identity of that type.
162    ///
163    /// If additional identities are added in the future, they may taken into
164    /// consideration before _or_ after the current identity types.
165    fn cmp_by_relay_ids<T: HasRelayIds + ?Sized>(&self, other: &T) -> std::cmp::Ordering {
166        for key_type in RelayIdType::all_types() {
167            let ordering = Ord::cmp(&self.identity(key_type), &other.identity(key_type));
168            if ordering.is_ne() {
169                return ordering;
170            }
171        }
172        std::cmp::Ordering::Equal
173    }
174
175    /// Return a reference to this object suitable for formatting its
176    /// [`HasRelayIds`] members.
177    fn display_relay_ids(&self) -> DisplayRelayIds<'_, Self> {
178        DisplayRelayIds { inner: self }
179    }
180}
181
182impl<T: HasRelayIdsLegacy> HasRelayIds for T {
183    fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
184        match key_type {
185            RelayIdType::Rsa => Some(self.rsa_identity().into()),
186            RelayIdType::Ed25519 => Some(self.ed_identity().into()),
187        }
188    }
189}
190
191/// A helper type used to format the [`RelayId`](crate::RelayId)s in a
192/// [`HasRelayIds`].
193#[derive(Clone)]
194pub struct DisplayRelayIds<'a, T: HasRelayIds + ?Sized> {
195    /// The HasRelayIds that we're displaying.
196    inner: &'a T,
197}
198// Redactable must implement Debug.
199impl<'a, T: HasRelayIds + ?Sized> fmt::Debug for DisplayRelayIds<'a, T> {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        f.debug_struct("DisplayRelayIds").finish_non_exhaustive()
202    }
203}
204
205impl<'a, T: HasRelayIds + ?Sized> DisplayRelayIds<'a, T> {
206    /// Helper: output `self` in a possibly redacted way.
207    fn fmt_impl(&self, f: &mut fmt::Formatter<'_>, redact: bool) -> fmt::Result {
208        let mut iter = self.inner.identities();
209        if let Some(ident) = iter.next() {
210            write!(f, "{}", ident.maybe_redacted(redact))?;
211        }
212        if redact {
213            return Ok(());
214        }
215        for ident in iter {
216            write!(f, " {}", ident.maybe_redacted(redact))?;
217        }
218        Ok(())
219    }
220}
221impl<'a, T: HasRelayIds + ?Sized> fmt::Display for DisplayRelayIds<'a, T> {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        self.fmt_impl(f, false)
224    }
225}
226impl<'a, T: HasRelayIds + ?Sized> Redactable for DisplayRelayIds<'a, T> {
227    fn display_redacted(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        self.fmt_impl(f, true)
229    }
230}
231
232/// An iterator over all of the relay identities held by a [`HasRelayIds`]
233#[derive(Clone)]
234pub struct RelayIdIter<'a, T: HasRelayIds + ?Sized> {
235    /// The object holding the keys
236    info: &'a T,
237    /// The next key type to yield
238    next_key: RelayIdTypeIter,
239}
240
241impl<'a, T: HasRelayIds + ?Sized> Iterator for RelayIdIter<'a, T> {
242    type Item = RelayIdRef<'a>;
243
244    fn next(&mut self) -> Option<Self::Item> {
245        for key_type in &mut self.next_key {
246            if let Some(key) = self.info.identity(key_type) {
247                return Some(key);
248            }
249        }
250        None
251    }
252}
253// RelayIdIter is fused since next_key is fused.
254impl<'a, T: HasRelayIds + ?Sized> FusedIterator for RelayIdIter<'a, T> {}
255
256/// An object that represents a host on the network which may have known IP addresses.
257pub trait HasAddrs {
258    /// Return the addresses listed for this server.
259    ///
260    /// NOTE that these addresses are not necessarily ones that we should
261    /// connect to directly!  They can be useful for telling where a server is
262    /// located, or whether it is "close" to another server, but without knowing
263    /// the associated protocols you cannot use these to launch a connection.
264    ///
265    /// Also, for some servers, we may not actually have any relevant addresses;
266    /// in that case, the returned slice is empty.
267    ///
268    /// To see how to _connect_ to a relay, use [`HasChanMethod::chan_method`]
269    //
270    // TODO: This is a questionable API. I'd rather return an iterator
271    // of addresses or references to addresses, but both of those options
272    // make defining the right associated types rather tricky.
273    fn addrs(&self) -> impl Iterator<Item = SocketAddr>;
274}
275
276impl<T: HasAddrs> HasAddrs for &T {
277    fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
278        // Be explicit about the type here so that we don't end up in an infinite loop by accident.
279        <T as HasAddrs>::addrs(self)
280    }
281}
282
283/// An object that can be connected to via [`ChannelMethod`]s.
284pub trait HasChanMethod {
285    /// Return the known ways to contact this
286    // TODO: See notes on HasAddrs above.
287    // TODO: I don't like having this return a new ChannelMethod, but I
288    // don't see a great alternative. Let's revisit that.-nickm.
289    fn chan_method(&self) -> ChannelMethod;
290}
291
292/// Implement `HasChanMethods` for an object with `HasAddr` whose addresses
293/// _all_ represent a host we can connect to by a direct Tor connection at its
294/// IP addresses.
295pub trait DirectChanMethodsHelper: HasAddrs {}
296
297impl<D: DirectChanMethodsHelper> HasChanMethod for D {
298    fn chan_method(&self) -> ChannelMethod {
299        ChannelMethod::Direct(self.addrs().collect_vec())
300    }
301}
302
303/// Information about a Tor relay used to connect to it.
304///
305/// Anything that implements 'ChanTarget' can be used as the
306/// identity of a relay for the purposes of launching a new
307/// channel.
308pub trait ChanTarget: HasRelayIds + HasAddrs + HasChanMethod {
309    /// Return a reference to this object suitable for formatting its
310    /// [`ChanTarget`]-specific members.
311    ///
312    /// The display format is not exhaustive, but tries to give enough
313    /// information to identify which channel target we're talking about.
314    fn display_chan_target(&self) -> DisplayChanTarget<'_, Self>
315    where
316        Self: Sized,
317    {
318        DisplayChanTarget { inner: self }
319    }
320
321    /// Return true if we think all addresses are allowed to be used for a relay outgoing channel.
322    ///
323    /// If no address are found, true is returned.
324    ///
325    /// NOTE: The set of RFCs checked here are not expected to change over time and so this should
326    /// be a check that yields the same result regardless of the Rust library version. HOWEVER, it
327    /// doesn't mean that each relay/client on the network uses the same set of checks.
328    fn all_addrs_allowed_for_outgoing_channels(&self) -> bool {
329        self.addrs().all(|addr| {
330            // We need to canonicalize any IPv4-mapped IPv6 addresses,
331            // since for example:
332            //   IpAddr::V6(Ipv4Addr::LOCALHOST.to_ipv6_mapped()).is_loopback()
333            // returns `false`,
334            // even though a `connect()` to that address would connect to loopback.
335            let addr = addr.ip().to_canonical();
336
337            match addr {
338                IpAddr::V4(v4) => {
339                    !(v4.is_loopback() // RFC 1122 (127.0.0.0/8)
340                        || v4.is_private() // RFC1918
341                        || v4.is_unspecified() // 0.0.0.0
342                        || v4.is_documentation() // RFC 5737
343                        || v4.is_multicast() // RFC 5771 (224.0.0.0/4)
344                        || v4.is_link_local()) // RFC 3927 (169.254.0.0/16)
345                }
346                IpAddr::V6(v6) => {
347                    !(v6.is_loopback() // RFC 4291 (::1)
348                        || v6.is_multicast() // RFC 4291 (ff00::/8)
349                        || v6.is_unspecified() // RFC 4291 (::)
350                        || v6.is_unique_local() // RFC 4193 (fc00::/7)
351                        || v6.is_unicast_link_local()) // RFC 4291 (fe80::/10)
352                }
353            }
354        })
355    }
356
357    /// Return true iff all addresses' ports are non-zero, or there are no addresses.
358    fn has_all_nonzero_port(&self) -> bool {
359        self.addrs().all(|addr| addr.port() != 0)
360    }
361}
362
363/// Information about a Tor relay used to extend a circuit to it.
364///
365/// Anything that implements 'CircTarget' can be used as the
366/// identity of a relay for the purposes of extending a circuit.
367pub trait CircTarget: ChanTarget {
368    /// Return a new vector of encoded link specifiers for this relay.
369    ///
370    /// Note that, outside of this method, nothing in Arti should be re-ordering
371    /// the link specifiers returned by this method.  It is this method's
372    /// responsibility to return them in the correct order.
373    ///
374    /// The default implementation for this method builds a list of link
375    /// specifiers from this object's identities and IP addresses, and sorts
376    /// them into the order specified in tor-spec to avoid implementation
377    /// fingerprinting attacks.
378    //
379    // TODO: This is a questionable API. I'd rather return an iterator
380    // of link specifiers, but that's not so easy to do, since it seems
381    // doing so correctly would require default associated types.
382    fn linkspecs(&self) -> tor_bytes::EncodeResult<Vec<crate::EncodedLinkSpec>> {
383        let mut result: Vec<_> = self.identities().map(|id| id.to_owned().into()).collect();
384        #[allow(irrefutable_let_patterns)]
385        if let ChannelMethod::Direct(addrs) = self.chan_method() {
386            result.extend(addrs.into_iter().map(crate::LinkSpec::from));
387        }
388        crate::LinkSpec::sort_by_type(&mut result[..]);
389        result.into_iter().map(|ls| ls.encode()).collect()
390    }
391    /// Return the ntor onion key for this relay
392    fn ntor_onion_key(&self) -> &pk::curve25519::PublicKey;
393    /// Return the subprotocols implemented by this relay.
394    fn protovers(&self) -> &tor_protover::Protocols;
395}
396
397/// A reference to a ChanTarget that implements Display using a hopefully useful
398/// format.
399#[derive(Debug, Clone)]
400pub struct DisplayChanTarget<'a, T> {
401    /// The ChanTarget that we're formatting.
402    inner: &'a T,
403}
404
405impl<'a, T: ChanTarget> DisplayChanTarget<'a, T> {
406    /// helper: output `self` in a possibly redacted way.
407    fn fmt_impl(&self, f: &mut fmt::Formatter<'_>, redact: bool) -> fmt::Result {
408        write!(f, "[")?;
409        // We look at the chan_method() (where we would connect to) rather than
410        // the addrs() (where the relay is, nebulously, "located").  This lets us
411        // give a less surprising description.
412        match self.inner.chan_method() {
413            ChannelMethod::Direct(v) if v.is_empty() => write!(f, "?")?,
414            ChannelMethod::Direct(v) if v.len() == 1 => {
415                write!(f, "{}", v[0].maybe_redacted(redact))?;
416            }
417            ChannelMethod::Direct(v) => write!(f, "{}+", v[0].maybe_redacted(redact))?,
418            #[cfg(feature = "pt-client")]
419            ChannelMethod::Pluggable(target) => {
420                match target.addr() {
421                    PtTargetAddr::None => {}
422                    other => write!(f, "{} ", other.maybe_redacted(redact))?,
423                }
424                write!(f, "via {}", target.transport())?;
425                // This deliberately doesn't include the PtTargetSettings, since
426                // they can be large, and they're typically unnecessary.
427            }
428        }
429
430        write!(f, " ")?;
431        self.inner.display_relay_ids().fmt_impl(f, redact)?;
432
433        write!(f, "]")
434    }
435}
436
437impl<'a, T: ChanTarget> fmt::Display for DisplayChanTarget<'a, T> {
438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439        self.fmt_impl(f, false)
440    }
441}
442
443impl<'a, T: ChanTarget + fmt::Debug> safelog::Redactable for DisplayChanTarget<'a, T> {
444    fn display_redacted(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445        self.fmt_impl(f, true)
446    }
447    fn debug_redacted(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448        write!(f, "ChanTarget({:?})", self.redacted().to_string())
449    }
450}
451
452#[cfg(test)]
453mod test {
454    // @@ begin test lint list maintained by maint/add_warning @@
455    #![allow(clippy::bool_assert_comparison)]
456    #![allow(clippy::clone_on_copy)]
457    #![allow(clippy::dbg_macro)]
458    #![allow(clippy::mixed_attributes_style)]
459    #![allow(clippy::print_stderr)]
460    #![allow(clippy::print_stdout)]
461    #![allow(clippy::single_char_pattern)]
462    #![allow(clippy::unwrap_used)]
463    #![allow(clippy::unchecked_time_subtraction)]
464    #![allow(clippy::useless_vec)]
465    #![allow(clippy::needless_pass_by_value)]
466    #![allow(clippy::string_slice)] // See arti#2571
467    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
468    use super::*;
469    use crate::{OwnedChanTarget, RelayIds};
470    use hex_literal::hex;
471    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
472    use tor_llcrypto::pk::{self, ed25519::Ed25519Identity, rsa::RsaIdentity};
473
474    struct Example {
475        addrs: Vec<SocketAddr>,
476        ed_id: pk::ed25519::Ed25519Identity,
477        rsa_id: pk::rsa::RsaIdentity,
478        ntor: pk::curve25519::PublicKey,
479        pv: tor_protover::Protocols,
480    }
481    impl HasAddrs for Example {
482        fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
483            self.addrs.iter().copied()
484        }
485    }
486    impl DirectChanMethodsHelper for Example {}
487    impl HasRelayIdsLegacy for Example {
488        fn ed_identity(&self) -> &pk::ed25519::Ed25519Identity {
489            &self.ed_id
490        }
491        fn rsa_identity(&self) -> &pk::rsa::RsaIdentity {
492            &self.rsa_id
493        }
494    }
495    impl ChanTarget for Example {}
496    impl CircTarget for Example {
497        fn ntor_onion_key(&self) -> &pk::curve25519::PublicKey {
498            &self.ntor
499        }
500        fn protovers(&self) -> &tor_protover::Protocols {
501            &self.pv
502        }
503    }
504
505    /// Return an `Example` object, for use in tests below.
506    fn example() -> Example {
507        Example {
508            addrs: vec![
509                "127.0.0.1:99".parse::<SocketAddr>().unwrap(),
510                "[::1]:909".parse::<SocketAddr>().unwrap(),
511            ],
512            ed_id: pk::ed25519::PublicKey::from_bytes(&hex!(
513                "fc51cd8e6218a1a38da47ed00230f058
514                 0816ed13ba3303ac5deb911548908025"
515            ))
516            .unwrap()
517            .into(),
518            rsa_id: pk::rsa::RsaIdentity::from_bytes(&hex!(
519                "1234567890abcdef12341234567890abcdef1234"
520            ))
521            .unwrap(),
522            ntor: pk::curve25519::PublicKey::from(hex!(
523                "e6db6867583030db3594c1a424b15f7c
524                 726624ec26b3353b10a903a6d0ab1c4c"
525            )),
526            pv: tor_protover::Protocols::default(),
527        }
528    }
529
530    #[test]
531    fn test_linkspecs() {
532        let ex = example();
533        let specs = ex
534            .linkspecs()
535            .unwrap()
536            .into_iter()
537            .map(|ls| ls.parse())
538            .collect::<Result<Vec<_>, _>>()
539            .unwrap();
540        assert_eq!(4, specs.len());
541
542        use crate::ls::LinkSpec;
543        assert_eq!(
544            specs[0],
545            LinkSpec::OrPort("127.0.0.1".parse::<IpAddr>().unwrap(), 99)
546        );
547        assert_eq!(
548            specs[1],
549            LinkSpec::RsaId(
550                pk::rsa::RsaIdentity::from_bytes(&hex!("1234567890abcdef12341234567890abcdef1234"))
551                    .unwrap()
552            )
553        );
554        assert_eq!(
555            specs[2],
556            LinkSpec::Ed25519Id(
557                pk::ed25519::PublicKey::from_bytes(&hex!(
558                    "fc51cd8e6218a1a38da47ed00230f058
559                     0816ed13ba3303ac5deb911548908025"
560                ))
561                .unwrap()
562                .into()
563            )
564        );
565        assert_eq!(
566            specs[3],
567            LinkSpec::OrPort("::1".parse::<IpAddr>().unwrap(), 909)
568        );
569    }
570
571    #[test]
572    fn cmp_by_ids() {
573        use crate::RelayIds;
574        use std::cmp::Ordering;
575        fn b(ed: Option<Ed25519Identity>, rsa: Option<RsaIdentity>) -> RelayIds {
576            let mut b = RelayIds::builder();
577            if let Some(ed) = ed {
578                b.ed_identity(ed);
579            }
580            if let Some(rsa) = rsa {
581                b.rsa_identity(rsa);
582            }
583            b.build().unwrap()
584        }
585        // Assert that v is strictly ascending.
586        fn assert_sorted(v: &[RelayIds]) {
587            for slice in v.windows(2) {
588                assert_eq!(slice[0].cmp_by_relay_ids(&slice[1]), Ordering::Less);
589                assert_eq!(slice[1].cmp_by_relay_ids(&slice[0]), Ordering::Greater);
590                assert_eq!(slice[0].cmp_by_relay_ids(&slice[0]), Ordering::Equal);
591            }
592        }
593
594        let ed1 = hex!("0a54686973206973207468652043656e7472616c205363727574696e697a6572").into();
595        let ed2 = hex!("6962696c69747920746f20656e666f72636520616c6c20746865206c6177730a").into();
596        let ed3 = hex!("73736564207965740a497420697320616c736f206d7920726573706f6e736962").into();
597        let rsa1 = hex!("2e2e2e0a4974206973206d7920726573706f6e73").into();
598        let rsa2 = hex!("5468617420686176656e2774206265656e207061").into();
599        let rsa3 = hex!("696c69747920746f20616c65727420656163680a").into();
600
601        assert_sorted(&[
602            b(Some(ed1), None),
603            b(Some(ed2), None),
604            b(Some(ed3), None),
605            b(Some(ed3), Some(rsa1)),
606        ]);
607        assert_sorted(&[
608            b(Some(ed1), Some(rsa3)),
609            b(Some(ed2), Some(rsa2)),
610            b(Some(ed3), Some(rsa1)),
611            b(Some(ed3), Some(rsa2)),
612        ]);
613        assert_sorted(&[
614            b(Some(ed1), Some(rsa1)),
615            b(Some(ed1), Some(rsa2)),
616            b(Some(ed1), Some(rsa3)),
617        ]);
618        assert_sorted(&[
619            b(None, Some(rsa1)),
620            b(None, Some(rsa2)),
621            b(None, Some(rsa3)),
622        ]);
623        assert_sorted(&[
624            b(None, Some(rsa1)),
625            b(Some(ed1), None),
626            b(Some(ed1), Some(rsa1)),
627        ]);
628    }
629
630    #[test]
631    fn compare_id_sets() {
632        // TODO somehow nicely unify these repeated predefined examples
633        let ed1 = hex!("0a54686973206973207468652043656e7472616c205363727574696e697a6572").into();
634        let rsa1 = hex!("2e2e2e0a4974206973206d7920726573706f6e73").into();
635        let rsa2 = RsaIdentity::from(hex!("5468617420686176656e2774206265656e207061"));
636
637        let both1 = RelayIds::builder()
638            .ed_identity(ed1)
639            .rsa_identity(rsa1)
640            .build()
641            .unwrap();
642        let mixed = RelayIds::builder()
643            .ed_identity(ed1)
644            .rsa_identity(rsa2)
645            .build()
646            .unwrap();
647        let ed1 = RelayIds::builder().ed_identity(ed1).build().unwrap();
648        let rsa1 = RelayIds::builder().rsa_identity(rsa1).build().unwrap();
649        let rsa2 = RelayIds::builder().rsa_identity(rsa2).build().unwrap();
650
651        fn chk_equal(v: &impl HasRelayIds) {
652            assert!(v.same_relay_ids(v));
653            assert!(v.has_all_relay_ids_from(v));
654            assert!(v.has_any_relay_id_from(v));
655        }
656        fn chk_strict_subset(bigger: &impl HasRelayIds, smaller: &impl HasRelayIds) {
657            assert!(!bigger.same_relay_ids(smaller));
658            assert!(bigger.has_all_relay_ids_from(smaller));
659            assert!(bigger.has_any_relay_id_from(smaller));
660            assert!(!smaller.same_relay_ids(bigger));
661            assert!(!smaller.has_all_relay_ids_from(bigger));
662            assert!(smaller.has_any_relay_id_from(bigger));
663        }
664        fn chk_nontrivially_overlapping_one_way(a: &impl HasRelayIds, b: &impl HasRelayIds) {
665            assert!(!a.same_relay_ids(b));
666            assert!(!a.has_all_relay_ids_from(b));
667            assert!(a.has_any_relay_id_from(b));
668        }
669        fn chk_nontrivially_overlapping(a: &impl HasRelayIds, b: &impl HasRelayIds) {
670            chk_nontrivially_overlapping_one_way(a, b);
671            chk_nontrivially_overlapping_one_way(b, a);
672        }
673
674        chk_equal(&ed1);
675        chk_equal(&rsa1);
676        chk_equal(&both1);
677
678        chk_strict_subset(&both1, &ed1);
679        chk_strict_subset(&both1, &rsa1);
680        chk_strict_subset(&mixed, &ed1);
681        chk_strict_subset(&mixed, &rsa2);
682
683        chk_nontrivially_overlapping(&both1, &mixed);
684    }
685
686    #[test]
687    fn display() {
688        let e1 = example();
689        assert_eq!(
690            e1.display_chan_target().to_string(),
691            "[127.0.0.1:99+ ed25519:/FHNjmIYoaONpH7QAjDwWAgW7RO6MwOsXeuRFUiQgCU \
692              $1234567890abcdef12341234567890abcdef1234]"
693        );
694
695        #[cfg(feature = "pt-client")]
696        {
697            use crate::PtTarget;
698
699            let rsa = hex!("234461644a6f6b6523436f726e794f6e4d61696e").into();
700            let mut b = crate::OwnedChanTarget::builder();
701            b.ids().rsa_identity(rsa);
702            let e2 = b
703                .method(ChannelMethod::Pluggable(PtTarget::new(
704                    "obfs4".parse().unwrap(),
705                    "127.0.0.1:99".parse().unwrap(),
706                )))
707                .build()
708                .unwrap();
709            assert_eq!(
710                e2.to_string(),
711                "[127.0.0.1:99 via obfs4 $234461644a6f6b6523436f726e794f6e4d61696e]"
712            );
713        }
714    }
715
716    #[test]
717    fn has_id() {
718        use crate::RelayIds;
719        assert!(example().has_any_identity());
720        assert!(!RelayIds::empty().has_any_identity());
721    }
722
723    #[test]
724    fn allowed_for_outgoing_channels() {
725        fn build_target(addrs: &[SocketAddr]) -> OwnedChanTarget {
726            OwnedChanTarget::builder()
727                .addrs(addrs.to_vec())
728                .build()
729                .unwrap()
730        }
731
732        /// Convert an IPv4 socket address to an IPv4-mapped IPv6 socket address.
733        fn to_mapped(addr: &SocketAddrV4) -> SocketAddrV6 {
734            SocketAddrV6::new(addr.ip().to_ipv6_mapped(), addr.port(), 0, 0)
735        }
736
737        // Some not-allowed addresses.
738        let localhost_v4 = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1234);
739        let localhost_v4_mapped = to_mapped(&localhost_v4);
740        let localhost_v6 = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 1234, 0, 0);
741        let unspecified_v4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 1234);
742        let unspecified_v4_mapped = to_mapped(&unspecified_v4);
743        let unspecified_v6 = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 1234, 0, 0);
744        let private_v4 = SocketAddrV4::new(Ipv4Addr::new(192, 168, 1, 1), 1234);
745        let private_v4_mapped = to_mapped(&private_v4);
746
747        let not_allowed = [
748            localhost_v4.into(),
749            localhost_v6.into(),
750            unspecified_v4.into(),
751            unspecified_v6.into(),
752            private_v4.into(),
753            localhost_v4_mapped.into(),
754            unspecified_v4_mapped.into(),
755            private_v4_mapped.into(),
756        ];
757
758        // Some globally accessible addresses.
759        let google_dns_v4 = SocketAddrV4::new(Ipv4Addr::new(8, 8, 8, 8), 1234);
760        let google_dns_v4_mapped = to_mapped(&google_dns_v4);
761
762        let allowed = [google_dns_v4.into(), google_dns_v4_mapped.into()];
763
764        let target = build_target(&[]);
765        assert!(target.all_addrs_allowed_for_outgoing_channels());
766
767        for addr in not_allowed {
768            let target = build_target(&[addr]);
769            assert!(
770                !target.all_addrs_allowed_for_outgoing_channels(),
771                "addr: {addr}",
772            );
773        }
774
775        for addr in allowed {
776            let target = build_target(&[addr]);
777            assert!(
778                target.all_addrs_allowed_for_outgoing_channels(),
779                "addr: {addr}",
780            );
781        }
782
783        // Try some combinations of multiple addresses.
784
785        let target = build_target(&[google_dns_v4.into(), google_dns_v4_mapped.into()]);
786        assert!(target.all_addrs_allowed_for_outgoing_channels());
787
788        let target = build_target(&[google_dns_v4.into(), localhost_v4_mapped.into()]);
789        assert!(!target.all_addrs_allowed_for_outgoing_channels());
790
791        let target = build_target(&[localhost_v4.into(), localhost_v4_mapped.into()]);
792        assert!(!target.all_addrs_allowed_for_outgoing_channels());
793    }
794}