From 8dc6e958aa305236054a43c0db74dda9176e0702 Mon Sep 17 00:00:00 2001
From: trinity-1686a <trinity@deuxfleurs.fr>
Date: Thu, 24 Mar 2022 19:43:54 +0100
Subject: [PATCH] move isolation in separate module

---
 crates/tor-circmgr/src/isolation.rs  | 329 +++++++++++++++++++++++++++
 crates/tor-circmgr/src/lib.rs        |   8 +-
 crates/tor-circmgr/src/mgr.rs        |   2 +-
 crates/tor-circmgr/src/preemptive.rs |   2 +-
 crates/tor-circmgr/src/usage.rs      | 325 +-------------------------
 5 files changed, 336 insertions(+), 330 deletions(-)
 create mode 100644 crates/tor-circmgr/src/isolation.rs

diff --git a/crates/tor-circmgr/src/isolation.rs b/crates/tor-circmgr/src/isolation.rs
new file mode 100644
index 0000000000..05a0d423e8
--- /dev/null
+++ b/crates/tor-circmgr/src/isolation.rs
@@ -0,0 +1,329 @@
+//! Types related to stream isolation
+use downcast_rs::{impl_downcast, Downcast};
+use dyn_clone::{clone_trait_object, DynClone};
+use std::sync::atomic::{AtomicU64, Ordering};
+
+/// A type that can make isolation decisions about streams it is attached to.
+///
+/// Types that implement `Isolation` contain properties about a stream that are
+/// used to make decisions about whether that stream can share the same circuit
+/// as other streams. You may pass in any type implementing `Isolation` when
+/// creating a stream via `TorClient::connect_with_prefs`, or constructing a
+/// circuit with [`CircMgr::get_or_launch_exit()`](crate::CircMgr::get_or_launch_exit).
+///
+/// You typically do not want to implement this trait directly.  Instead, most
+/// users should implement [`IsolationHelper`].
+///
+// TODO this trait should probably be sealed so the same-type requirement can't be bypassed
+pub trait Isolation:
+    seal::Sealed + Downcast + DynClone + std::fmt::Debug + Send + Sync + 'static
+{
+    /// Return true if this Isolation is compatible with another.
+    ///
+    /// Two streams may share a circuit if and only if they have compatible
+    /// `Isolation`s.
+    ///
+    /// # Requirements
+    ///
+    /// For correctness, this relation must be symmetrical and reflexive:
+    /// `self.compatible(other)` must equal `other.compatible(self)`, and
+    /// `self.compatible(self)` must be true.
+    ///
+    /// For correctness, this function must always give the same result as
+    /// `self.join(other).is_some()`.
+    ///
+    /// This relationship does **not** have to be transitive: it's possible that
+    /// stream A can share a circuit with either stream B or stream C, but not
+    /// with both.
+    fn compatible(&self, other: &dyn Isolation) -> bool;
+
+    /// Join two [`Isolation`] into the intersection of what each allows.
+    ///
+    /// A circuit's isolation is the `join` of the isolation values of all of
+    /// the streams that have _ever_ used that circuit.  A circuit's isolation
+    /// can never be `None`: streams that would cause it to be `None` can't be
+    /// attached to the circuit.
+    ///
+    /// When a stream is added to a circuit, `join` is used to calculate the
+    /// circuit's new isolation.
+    ///
+    /// # Requirements
+    ///
+    /// For correctness, this function must be commutative: `self.join(other)`
+    /// must equal `other.join(self)`.  Also, it must be idempotent:
+    /// `self.join(self)` must equal self.
+    //
+    // TODO: (This function probably should be associative too, but we haven't done
+    // all the math.)
+    fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>>;
+}
+
+/// Seal preventing implementation of Isolation not relying on IsolationHelper
+mod seal {
+    /// Seal preventing implementation of Isolation not relying on IsolationHelper
+    pub trait Sealed {}
+    impl<T: super::IsolationHelper> Sealed for T {}
+}
+
+impl_downcast!(Isolation);
+clone_trait_object!(Isolation);
+impl<T: Isolation> From<T> for Box<dyn Isolation> {
+    fn from(isolation: T) -> Self {
+        Box::new(isolation)
+    }
+}
+
+impl<T: IsolationHelper + Clone + std::fmt::Debug + Send + Sync + 'static> Isolation for T {
+    fn compatible(&self, other: &dyn Isolation) -> bool {
+        if let Some(other) = other.as_any().downcast_ref() {
+            self.compatible_same_type(other)
+        } else {
+            false
+        }
+    }
+
+    fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>> {
+        if let Some(other) = other.as_any().downcast_ref() {
+            self.join_same_type(other)
+                .map(|res| Box::new(res) as Box<dyn Isolation>)
+        } else {
+            None
+        }
+    }
+}
+
+/// Trait to help implement [`Isolation`].
+///
+/// You should generally implement this trait whenever you need to implement a
+/// new set of stream isolation rules: it takes care of down-casting and type
+/// checking for you.
+///
+/// When you implement this trait for some type T, isolation objects of that
+/// type will be incompatible (unable to share circuits) with objects of _any
+/// other type_.  (That's usually what you want; if you're defining a new type
+/// of Isolation rules, then you probably don't want streams using different
+/// rules to share circuits with yours.)
+pub trait IsolationHelper: Sized {
+    /// Returns whether self and other are compatible.
+    ///
+    /// Two streams may share a circuit if and only if they have compatible
+    /// `Isolation`s.
+    ///
+    /// (See [`Isolation::compatible`] for more information and requirements.)
+    fn compatible_same_type(&self, other: &Self) -> bool;
+
+    /// Join self and other into the intersection of what they allows.
+    ///
+    /// (See [`Isolation::join`] for more information and requirements.)
+    fn join_same_type(&self, other: &Self) -> Option<Self>;
+}
+
+/// A token used to isolate unrelated streams on different circuits.
+///
+/// When two streams are associated with different isolation tokens, they
+/// can never share the same circuit.
+///
+/// Tokens created with [`IsolationToken::new`] are all different from
+/// one another, and different from tokens created with
+/// [`IsolationToken::no_isolation`]. However, tokens created with
+/// [`IsolationToken::no_isolation`] are all equal to one another.
+///
+/// # Examples
+///
+/// Creating distinct isolation tokens:
+///
+/// ```rust
+/// # use tor_circmgr::IsolationToken;
+/// let token_1 = IsolationToken::new();
+/// let token_2 = IsolationToken::new();
+///
+/// assert_ne!(token_1, token_2);
+///
+/// // Demonstrating the behaviour of no_isolation() tokens:
+/// assert_ne!(token_1, IsolationToken::no_isolation());
+/// assert_eq!(IsolationToken::no_isolation(), IsolationToken::no_isolation());
+/// ```
+///
+/// Using an isolation token to route streams differently over the Tor network:
+///
+/// ```ignore
+/// use arti_client::StreamPrefs;
+///
+/// let token_1 = IsolationToken::new();
+/// let token_2 = IsolationToken::new();
+///
+/// let mut prefs_1 = StreamPrefs::new();
+/// prefs_1.set_isolation(token_1);
+///
+/// let mut prefs_2 = StreamPrefs::new();
+/// prefs_2.set_isolation(token_2);
+///
+/// // These two connections will come from different source IP addresses.
+/// tor_client.connect(("example.com", 80), Some(prefs_1)).await?;
+/// tor_client.connect(("example.com", 80), Some(prefs_2)).await?;
+/// ```
+// # Semver note
+//
+// This type is re-exported by `arti-client`: any changes to it must be
+// reflected in `arti-client`'s version.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct IsolationToken(u64);
+
+#[allow(clippy::new_without_default)]
+impl IsolationToken {
+    /// Create a new IsolationToken, unequal to any other token this function
+    /// has created.
+    ///
+    /// # Panics
+    ///
+    /// Panics if we have already allocated 2^64 isolation tokens: in that
+    /// case, we have exhausted the space of possible tokens, and it is
+    /// no longer possible to ensure isolation.
+    pub fn new() -> Self {
+        /// Internal counter used to generate different tokens each time
+        static COUNTER: AtomicU64 = AtomicU64::new(1);
+        // Ordering::Relaxed is fine because we don't care about causality, we just want a
+        // different number each time
+        let token = COUNTER.fetch_add(1, Ordering::Relaxed);
+        assert!(token < u64::MAX);
+        IsolationToken(token)
+    }
+
+    /// Create a new IsolationToken equal to every other token created
+    /// with this function, but different from all tokens created with
+    /// `new`.
+    ///
+    /// This can be used when no isolation is wanted for some streams.
+    pub fn no_isolation() -> Self {
+        IsolationToken(0)
+    }
+}
+
+impl IsolationHelper for IsolationToken {
+    fn compatible_same_type(&self, other: &Self) -> bool {
+        self == other
+    }
+    fn join_same_type(&self, other: &Self) -> Option<Self> {
+        if self.compatible_same_type(other) {
+            Some(*self)
+        } else {
+            None
+        }
+    }
+}
+
+#[cfg(test)]
+pub(crate) mod test {
+    use super::*;
+
+    /// Trait for testing use only. Much like PartialEq, but for type containing an dyn Isolation
+    /// which is known to be an IsolationToken.
+    pub(crate) trait IsolationTokenEq {
+        /// Compare two values, returning true if they are equals and all dyn Isolation they contain
+        /// are IsolationToken (which are equal too).
+        fn isol_eq(&self, other: &Self) -> bool;
+    }
+
+    macro_rules! assert_isoleq {
+        { $arg1:expr, $arg2:expr } => {
+            assert!($arg1.isol_eq(&$arg2))
+        }
+    }
+    pub(crate) use assert_isoleq;
+
+    impl IsolationTokenEq for IsolationToken {
+        fn isol_eq(&self, other: &Self) -> bool {
+            self == other
+        }
+    }
+
+    impl<T: IsolationTokenEq> IsolationTokenEq for Option<T> {
+        fn isol_eq(&self, other: &Self) -> bool {
+            match (self, other) {
+                (Some(this), Some(other)) => this.isol_eq(other),
+                (None, None) => true,
+                _ => false,
+            }
+        }
+    }
+
+    impl<T: IsolationTokenEq + std::fmt::Debug> IsolationTokenEq for Vec<T> {
+        fn isol_eq(&self, other: &Self) -> bool {
+            if self.len() != other.len() {
+                return false;
+            }
+            self.iter()
+                .zip(other.iter())
+                .all(|(this, other)| this.isol_eq(other))
+        }
+    }
+
+    impl IsolationTokenEq for dyn Isolation {
+        fn isol_eq(&self, other: &Self) -> bool {
+            let this = self.as_any().downcast_ref::<IsolationToken>();
+            let other = other.as_any().downcast_ref::<IsolationToken>();
+            match (this, other) {
+                (Some(this), Some(other)) => this == other,
+                _ => false,
+            }
+        }
+    }
+
+    #[derive(PartialEq, Clone, Copy, Debug)]
+    struct OtherIsolation(usize);
+
+    impl IsolationHelper for OtherIsolation {
+        fn compatible_same_type(&self, other: &Self) -> bool {
+            self == other
+        }
+        fn join_same_type(&self, other: &Self) -> Option<Self> {
+            if self.compatible_same_type(other) {
+                Some(*self)
+            } else {
+                None
+            }
+        }
+    }
+
+    #[test]
+    fn isolation_token() {
+        let token_1 = IsolationToken::new();
+        let token_2 = IsolationToken::new();
+
+        assert!(token_1.compatible_same_type(&token_1));
+        assert!(token_2.compatible_same_type(&token_2));
+        assert!(!token_1.compatible_same_type(&token_2));
+
+        assert_eq!(token_1.join_same_type(&token_1), Some(token_1));
+        assert_eq!(token_2.join_same_type(&token_2), Some(token_2));
+        assert_eq!(token_1.join_same_type(&token_2), None);
+    }
+
+    #[test]
+    fn isolation_trait() {
+        let token_1: Box<dyn Isolation> = Box::new(IsolationToken::new());
+        let token_2: Box<dyn Isolation> = Box::new(IsolationToken::new());
+        let other_1: Box<dyn Isolation> = Box::new(OtherIsolation(0));
+        let other_2: Box<dyn Isolation> = Box::new(OtherIsolation(1));
+
+        assert!(token_1.compatible(token_1.as_ref()));
+        assert!(token_2.compatible(token_2.as_ref()));
+        assert!(!token_1.compatible(token_2.as_ref()));
+
+        assert!(other_1.compatible(other_1.as_ref()));
+        assert!(other_2.compatible(other_2.as_ref()));
+        assert!(!other_1.compatible(other_2.as_ref()));
+
+        assert!(!token_1.compatible(other_1.as_ref()));
+        assert!(!other_1.compatible(token_1.as_ref()));
+
+        assert!(token_1.join(token_1.as_ref()).is_some());
+        assert!(token_1.join(token_2.as_ref()).is_none());
+
+        assert!(other_1.join(other_1.as_ref()).is_some());
+        assert!(other_1.join(other_2.as_ref()).is_none());
+
+        assert!(token_1.join(other_1.as_ref()).is_none());
+        assert!(other_1.join(token_1.as_ref()).is_none());
+    }
+}
diff --git a/crates/tor-circmgr/src/lib.rs b/crates/tor-circmgr/src/lib.rs
index f1897785c4..13e330b0e0 100644
--- a/crates/tor-circmgr/src/lib.rs
+++ b/crates/tor-circmgr/src/lib.rs
@@ -64,6 +64,7 @@ pub mod build;
 mod config;
 mod err;
 mod impls;
+pub mod isolation;
 mod mgr;
 pub mod path;
 mod preemptive;
@@ -71,11 +72,8 @@ mod timeouts;
 mod usage;
 
 pub use err::Error;
-pub use usage::{IsolationToken, StreamIsolation, StreamIsolationBuilder, TargetPort, TargetPorts};
-/// Types related to stream isolation
-pub mod isolation {
-    pub use crate::usage::{Isolation, IsolationHelper, IsolationToken};
-}
+pub use isolation::IsolationToken;
+pub use usage::{StreamIsolation, StreamIsolationBuilder, TargetPort, TargetPorts};
 
 pub use config::{
     CircMgrConfig, CircuitTiming, CircuitTimingBuilder, PathConfig, PathConfigBuilder,
diff --git a/crates/tor-circmgr/src/mgr.rs b/crates/tor-circmgr/src/mgr.rs
index e82014e5cc..08ec587d9a 100644
--- a/crates/tor-circmgr/src/mgr.rs
+++ b/crates/tor-circmgr/src/mgr.rs
@@ -1332,7 +1332,7 @@ fn spawn_expiration_task<B, R>(
 mod test {
     #![allow(clippy::unwrap_used)]
     use super::*;
-    use crate::usage::test::{assert_isoleq, IsolationTokenEq};
+    use crate::isolation::test::{assert_isoleq, IsolationTokenEq};
     use crate::usage::{ExitPolicy, SupportedCircUsage};
     use crate::{Error, StreamIsolation, TargetCircUsage, TargetPort};
     use std::collections::BTreeSet;
diff --git a/crates/tor-circmgr/src/preemptive.rs b/crates/tor-circmgr/src/preemptive.rs
index 533e61f971..7d8db2f872 100644
--- a/crates/tor-circmgr/src/preemptive.rs
+++ b/crates/tor-circmgr/src/preemptive.rs
@@ -90,7 +90,7 @@ mod test {
     use crate::{PreemptiveCircuitConfig, PreemptiveCircuitPredictor, TargetCircUsage, TargetPort};
     use std::time::{Duration, Instant};
 
-    use crate::usage::test::{assert_isoleq, IsolationTokenEq};
+    use crate::isolation::test::{assert_isoleq, IsolationTokenEq};
 
     #[test]
     fn predicts_starting_ports() {
diff --git a/crates/tor-circmgr/src/usage.rs b/crates/tor-circmgr/src/usage.rs
index 0b78b4cb77..91975a36c4 100644
--- a/crates/tor-circmgr/src/usage.rs
+++ b/crates/tor-circmgr/src/usage.rs
@@ -1,11 +1,8 @@
 //! Code related to tracking what activities a circuit can be used for.
 
-use downcast_rs::{impl_downcast, Downcast};
-use dyn_clone::{clone_trait_object, DynClone};
 use rand::Rng;
 use serde::{Deserialize, Serialize};
 use std::fmt::{self, Display};
-use std::sync::atomic::{AtomicU64, Ordering};
 use std::sync::Arc;
 use tor_error::bad_api_usage;
 use tracing::debug;
@@ -16,6 +13,7 @@ use tor_netdir::Relay;
 use tor_netdoc::types::policy::PortPolicy;
 use tor_rtcompat::Runtime;
 
+use crate::isolation::{Isolation, IsolationToken};
 use crate::mgr::{abstract_spec_find_supported, AbstractCirc, OpenEntry};
 use crate::Result;
 
@@ -102,215 +100,6 @@ impl Display for TargetPorts {
     }
 }
 
-/// A type that can make isolation decisions about streams it is attached to.
-///
-/// Types that implement `Isolation` contain properties about a stream that are
-/// used to make decisions about whether that stream can share the same circuit
-/// as other streams. You may pass in any type implementing `Isolation` when
-/// creating a stream via `TorClient::connect_with_prefs`, or constructing a
-/// circuit with [`CircMgr::get_or_launch_exit()`](crate::CircMgr::get_or_launch_exit).
-///
-/// You typically do not want to implement this trait directly.  Instead, most
-/// users should implement [`IsolationHelper`].
-///
-// TODO this trait should probably be sealed so the same-type requirement can't be bypassed
-pub trait Isolation:
-    seal::Sealed + Downcast + DynClone + std::fmt::Debug + Send + Sync + 'static
-{
-    /// Return true if this Isolation is compatible with another.
-    ///
-    /// Two streams may share a circuit if and only if they have compatible
-    /// `Isolation`s.
-    ///
-    /// # Requirements
-    ///
-    /// For correctness, this relation must be symmetrical and reflexive:
-    /// `self.compatible(other)` must equal `other.compatible(self)`, and
-    /// `self.compatible(self)` must be true.
-    ///
-    /// For correctness, this function must always give the same result as
-    /// `self.join(other).is_some()`.
-    ///
-    /// This relationship does **not** have to be transitive: it's possible that
-    /// stream A can share a circuit with either stream B or stream C, but not
-    /// with both.
-    fn compatible(&self, other: &dyn Isolation) -> bool;
-
-    /// Join two [`Isolation`] into the intersection of what each allows.
-    ///
-    /// A circuit's isolation is the `join` of the isolation values of all of
-    /// the streams that have _ever_ used that circuit.  A circuit's isolation
-    /// can never be `None`: streams that would cause it to be `None` can't be
-    /// attached to the circuit.
-    ///
-    /// When a stream is added to a circuit, `join` is used to calculate the
-    /// circuit's new isolation.
-    ///
-    /// # Requirements
-    ///
-    /// For correctness, this function must be commutative: `self.join(other)`
-    /// must equal `other.join(self)`.  Also, it must be idempotent:
-    /// `self.join(self)` must equal self.
-    //
-    // TODO: (This function probably should be associative too, but we haven't done
-    // all the math.)
-    fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>>;
-}
-
-/// Seal preventing implementation of Isolation not relying on IsolationHelper
-mod seal {
-    /// Seal preventing implementation of Isolation not relying on IsolationHelper
-    pub trait Sealed {}
-    impl<T: super::IsolationHelper> Sealed for T {}
-}
-
-impl_downcast!(Isolation);
-clone_trait_object!(Isolation);
-impl<T: Isolation> From<T> for Box<dyn Isolation> {
-    fn from(isolation: T) -> Self {
-        Box::new(isolation)
-    }
-}
-
-impl<T: IsolationHelper + Clone + std::fmt::Debug + Send + Sync + 'static> Isolation for T {
-    fn compatible(&self, other: &dyn Isolation) -> bool {
-        if let Some(other) = other.as_any().downcast_ref() {
-            self.compatible_same_type(other)
-        } else {
-            false
-        }
-    }
-
-    fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>> {
-        if let Some(other) = other.as_any().downcast_ref() {
-            self.join_same_type(other)
-                .map(|res| Box::new(res) as Box<dyn Isolation>)
-        } else {
-            None
-        }
-    }
-}
-
-/// Trait to help implement [`Isolation`].
-///
-/// You should generally implement this trait whenever you need to implement a
-/// new set of stream isolation rules: it takes care of down-casting and type
-/// checking for you.
-///
-/// When you implement this trait for some type T, isolation objects of that
-/// type will be incompatible (unable to share circuits) with objects of _any
-/// other type_.  (That's usually what you want; if you're defining a new type
-/// of Isolation rules, then you probably don't want streams using different
-/// rules to share circuits with yours.)
-pub trait IsolationHelper: Sized {
-    /// Returns whether self and other are compatible.
-    ///
-    /// Two streams may share a circuit if and only if they have compatible
-    /// `Isolation`s.
-    ///
-    /// (See [`Isolation::compatible`] for more information and requirements.)
-    fn compatible_same_type(&self, other: &Self) -> bool;
-
-    /// Join self and other into the intersection of what they allows.
-    ///
-    /// (See [`Isolation::join`] for more information and requirements.)
-    fn join_same_type(&self, other: &Self) -> Option<Self>;
-}
-
-/// A token used to isolate unrelated streams on different circuits.
-///
-/// When two streams are associated with different isolation tokens, they
-/// can never share the same circuit.
-///
-/// Tokens created with [`IsolationToken::new`] are all different from
-/// one another, and different from tokens created with
-/// [`IsolationToken::no_isolation`]. However, tokens created with
-/// [`IsolationToken::no_isolation`] are all equal to one another.
-///
-/// # Examples
-///
-/// Creating distinct isolation tokens:
-///
-/// ```rust
-/// # use tor_circmgr::IsolationToken;
-/// let token_1 = IsolationToken::new();
-/// let token_2 = IsolationToken::new();
-///
-/// assert_ne!(token_1, token_2);
-///
-/// // Demonstrating the behaviour of no_isolation() tokens:
-/// assert_ne!(token_1, IsolationToken::no_isolation());
-/// assert_eq!(IsolationToken::no_isolation(), IsolationToken::no_isolation());
-/// ```
-///
-/// Using an isolation token to route streams differently over the Tor network:
-///
-/// ```ignore
-/// use arti_client::StreamPrefs;
-///
-/// let token_1 = IsolationToken::new();
-/// let token_2 = IsolationToken::new();
-///
-/// let mut prefs_1 = StreamPrefs::new();
-/// prefs_1.set_isolation(token_1);
-///
-/// let mut prefs_2 = StreamPrefs::new();
-/// prefs_2.set_isolation(token_2);
-///
-/// // These two connections will come from different source IP addresses.
-/// tor_client.connect(("example.com", 80), Some(prefs_1)).await?;
-/// tor_client.connect(("example.com", 80), Some(prefs_2)).await?;
-/// ```
-// # Semver note
-//
-// This type is re-exported by `arti-client`: any changes to it must be
-// reflected in `arti-client`'s version.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct IsolationToken(u64);
-
-#[allow(clippy::new_without_default)]
-impl IsolationToken {
-    /// Create a new IsolationToken, unequal to any other token this function
-    /// has created.
-    ///
-    /// # Panics
-    ///
-    /// Panics if we have already allocated 2^64 isolation tokens: in that
-    /// case, we have exhausted the space of possible tokens, and it is
-    /// no longer possible to ensure isolation.
-    pub fn new() -> Self {
-        /// Internal counter used to generate different tokens each time
-        static COUNTER: AtomicU64 = AtomicU64::new(1);
-        // Ordering::Relaxed is fine because we don't care about causality, we just want a
-        // different number each time
-        let token = COUNTER.fetch_add(1, Ordering::Relaxed);
-        assert!(token < u64::MAX);
-        IsolationToken(token)
-    }
-
-    /// Create a new IsolationToken equal to every other token created
-    /// with this function, but different from all tokens created with
-    /// `new`.
-    ///
-    /// This can be used when no isolation is wanted for some streams.
-    pub fn no_isolation() -> Self {
-        IsolationToken(0)
-    }
-}
-
-impl IsolationHelper for IsolationToken {
-    fn compatible_same_type(&self, other: &Self) -> bool {
-        self == other
-    }
-    fn join_same_type(&self, other: &Self) -> Option<Self> {
-        if self.compatible_same_type(other) {
-            Some(*self)
-        } else {
-            None
-        }
-    }
-}
-
 /// A set of information about how a stream should be isolated.
 ///
 /// If two streams are isolated from one another, they may not share
@@ -626,58 +415,13 @@ impl crate::mgr::AbstractSpec for SupportedCircUsage {
 pub(crate) mod test {
     #![allow(clippy::unwrap_used)]
     use super::*;
+    use crate::isolation::test::{assert_isoleq, IsolationTokenEq};
     use crate::path::OwnedPath;
     use crate::test::OptDummyGuardMgr;
     use std::convert::TryFrom;
     use tor_linkspec::ChanTarget;
     use tor_netdir::testnet;
 
-    /// Trait for testing use only. Much like PartialEq, but for type containing an dyn Isolation
-    /// which is known to be an IsolationToken.
-    pub(crate) trait IsolationTokenEq {
-        /// Compare two values, returning true if they are equals and all dyn Isolation they contain
-        /// are IsolationToken (which are equal too).
-        fn isol_eq(&self, other: &Self) -> bool;
-    }
-
-    impl IsolationTokenEq for IsolationToken {
-        fn isol_eq(&self, other: &Self) -> bool {
-            self == other
-        }
-    }
-
-    impl<T: IsolationTokenEq> IsolationTokenEq for Option<T> {
-        fn isol_eq(&self, other: &Self) -> bool {
-            match (self, other) {
-                (Some(this), Some(other)) => this.isol_eq(other),
-                (None, None) => true,
-                _ => false,
-            }
-        }
-    }
-
-    impl<T: IsolationTokenEq + std::fmt::Debug> IsolationTokenEq for Vec<T> {
-        fn isol_eq(&self, other: &Self) -> bool {
-            if self.len() != other.len() {
-                return false;
-            }
-            self.iter()
-                .zip(other.iter())
-                .all(|(this, other)| this.isol_eq(other))
-        }
-    }
-
-    impl IsolationTokenEq for dyn Isolation {
-        fn isol_eq(&self, other: &Self) -> bool {
-            let this = self.as_any().downcast_ref::<IsolationToken>();
-            let other = other.as_any().downcast_ref::<IsolationToken>();
-            match (this, other) {
-                (Some(this), Some(other)) => this == other,
-                _ => false,
-            }
-        }
-    }
-
     impl IsolationTokenEq for StreamIsolation {
         fn isol_eq(&self, other: &Self) -> bool {
             self.stream_token.isol_eq(other.stream_token.as_ref())
@@ -737,13 +481,6 @@ pub(crate) mod test {
         }
     }
 
-    macro_rules! assert_isoleq {
-        { $arg1:expr, $arg2:expr } => {
-            assert!($arg1.isol_eq(&$arg2))
-        }
-    }
-    pub(crate) use assert_isoleq;
-
     #[test]
     fn exit_policy() {
         use tor_netdir::testnet::construct_custom_netdir;
@@ -1121,62 +858,4 @@ pub(crate) mod test {
         let ports = [TargetPort::ipv4(80), TargetPort::ipv6(443)];
         assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[80v4,443v6]");
     }
-
-    #[test]
-    fn isolation_token() {
-        let token_1 = IsolationToken::new();
-        let token_2 = IsolationToken::new();
-
-        assert!(token_1.compatible_same_type(&token_1));
-        assert!(token_2.compatible_same_type(&token_2));
-        assert!(!token_1.compatible_same_type(&token_2));
-
-        assert_eq!(token_1.join_same_type(&token_1), Some(token_1));
-        assert_eq!(token_2.join_same_type(&token_2), Some(token_2));
-        assert_eq!(token_1.join_same_type(&token_2), None);
-    }
-
-    #[derive(PartialEq, Clone, Copy, Debug)]
-    struct OtherIsolation(usize);
-
-    impl IsolationHelper for OtherIsolation {
-        fn compatible_same_type(&self, other: &Self) -> bool {
-            self == other
-        }
-        fn join_same_type(&self, other: &Self) -> Option<Self> {
-            if self.compatible_same_type(other) {
-                Some(*self)
-            } else {
-                None
-            }
-        }
-    }
-
-    #[test]
-    fn isolation_trait() {
-        let token_1: Box<dyn Isolation> = Box::new(IsolationToken::new());
-        let token_2: Box<dyn Isolation> = Box::new(IsolationToken::new());
-        let other_1: Box<dyn Isolation> = Box::new(OtherIsolation(0));
-        let other_2: Box<dyn Isolation> = Box::new(OtherIsolation(1));
-
-        assert!(token_1.compatible(token_1.as_ref()));
-        assert!(token_2.compatible(token_2.as_ref()));
-        assert!(!token_1.compatible(token_2.as_ref()));
-
-        assert!(other_1.compatible(other_1.as_ref()));
-        assert!(other_2.compatible(other_2.as_ref()));
-        assert!(!other_1.compatible(other_2.as_ref()));
-
-        assert!(!token_1.compatible(other_1.as_ref()));
-        assert!(!other_1.compatible(token_1.as_ref()));
-
-        assert!(token_1.join(token_1.as_ref()).is_some());
-        assert!(token_1.join(token_2.as_ref()).is_none());
-
-        assert!(other_1.join(other_1.as_ref()).is_some());
-        assert!(other_1.join(other_2.as_ref()).is_none());
-
-        assert!(token_1.join(other_1.as_ref()).is_none());
-        assert!(other_1.join(token_1.as_ref()).is_none());
-    }
 }
-- 
GitLab