1//! Types related to stream isolation
2use downcast_rs::{impl_downcast, Downcast};
3use dyn_clone::{clone_trait_object, DynClone};
4use std::sync::atomic::{AtomicU64, Ordering};
56/// A type that can make isolation decisions about streams it is attached to.
7///
8/// Types that implement `Isolation` contain properties about a stream that are
9/// used to make decisions about whether that stream can share the same circuit
10/// as other streams. You may pass in any type implementing `Isolation` when
11/// creating a stream via `TorClient::connect_with_prefs`, or constructing a
12/// circuit with [`CircMgr::get_or_launch_exit()`](crate::CircMgr::get_or_launch_exit).
13///
14/// You typically do not want to implement this trait directly. Instead, most
15/// users should implement [`IsolationHelper`].
16pub trait Isolation:
17 seal::Sealed + Downcast + DynClone + std::fmt::Debug + Send + Sync + 'static
18{
19/// Return true if this Isolation is compatible with another.
20 ///
21 /// Two streams may share a circuit if and only if they have compatible
22 /// `Isolation`s.
23 ///
24 /// # Requirements
25 ///
26 /// For correctness, this relation must be symmetrical and reflexive:
27 /// `self.compatible(other)` must equal `other.compatible(self)`, and
28 /// `self.compatible(self)` must be true.
29 ///
30 /// For correctness, this function must always give the same result as
31 /// `self.join(other).is_some()`.
32 ///
33 /// This relationship does **not** have to be transitive: it's possible that
34 /// stream A can share a circuit with either stream B or stream C, but not
35 /// with both.
36fn compatible(&self, other: &dyn Isolation) -> bool;
3738/// Join two [`Isolation`] into the intersection of what each allows.
39 ///
40 /// A circuit's isolation is the `join` of the isolation values of all of
41 /// the streams that have _ever_ used that circuit. A circuit's isolation
42 /// can never be `None`: streams that would cause it to be `None` can't be
43 /// attached to the circuit.
44 ///
45 /// When a stream is added to a circuit, `join` is used to calculate the
46 /// circuit's new isolation.
47 ///
48 /// # Requirements
49 ///
50 /// For correctness, this function must be commutative: `self.join(other)`
51 /// must equal `other.join(self)`. Also, it must be idempotent:
52 /// `self.join(self)` must equal self.
53//
54 // TODO: (This function probably should be associative too, but we haven't done
55 // all the math.)
56fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>>;
57}
5859/// Seal preventing implementation of Isolation not relying on IsolationHelper
60mod seal {
61/// Seal preventing implementation of Isolation not relying on IsolationHelper
62pub trait Sealed {}
63impl<T: super::IsolationHelper> Sealed for T {}
64}
6566impl_downcast!(Isolation);
67clone_trait_object!(Isolation);
68impl<T: Isolation> From<T> for Box<dyn Isolation> {
69fn from(isolation: T) -> Self {
70 Box::new(isolation)
71 }
72}
7374impl<T: IsolationHelper + Clone + std::fmt::Debug + Send + Sync + 'static> Isolation for T {
75fn compatible(&self, other: &dyn Isolation) -> bool {
76if let Some(other) = other.as_any().downcast_ref() {
77self.compatible_same_type(other)
78 } else {
79false
80}
81 }
8283fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>> {
84if let Some(other) = other.as_any().downcast_ref() {
85self.join_same_type(other)
86 .map(|res| Box::new(res) as Box<dyn Isolation>)
87 } else {
88None
89}
90 }
91}
9293/// Trait to help implement [`Isolation`].
94///
95/// You should generally implement this trait whenever you need to implement a
96/// new set of stream isolation rules: it takes care of down-casting and type
97/// checking for you.
98///
99/// When you implement this trait for some type T, isolation objects of that
100/// type will be incompatible (unable to share circuits) with objects of _any
101/// other type_. (That's usually what you want; if you're defining a new type
102/// of Isolation rules, then you probably don't want streams using different
103/// rules to share circuits with yours.)
104pub trait IsolationHelper: Sized {
105/// Returns whether self and other are compatible.
106 ///
107 /// Two streams may share a circuit if and only if they have compatible
108 /// `Isolation`s.
109 ///
110 /// (See [`Isolation::compatible`] for more information and requirements.)
111fn compatible_same_type(&self, other: &Self) -> bool;
112113/// Join self and other into the intersection of what they allows.
114 ///
115 /// (See [`Isolation::join`] for more information and requirements.)
116fn join_same_type(&self, other: &Self) -> Option<Self>;
117}
118119/// A token used to isolate unrelated streams on different circuits.
120///
121/// When two streams are associated with different isolation tokens, they
122/// can never share the same circuit.
123///
124/// Tokens created with [`IsolationToken::new`] are all different from
125/// one another, and different from tokens created with
126/// [`IsolationToken::no_isolation`]. However, tokens created with
127/// [`IsolationToken::no_isolation`] are all equal to one another.
128///
129/// # Examples
130///
131/// Creating distinct isolation tokens:
132///
133/// ```rust
134/// # use tor_circmgr::IsolationToken;
135/// let token_1 = IsolationToken::new();
136/// let token_2 = IsolationToken::new();
137///
138/// assert_ne!(token_1, token_2);
139///
140/// // Demonstrating the behaviour of no_isolation() tokens:
141/// assert_ne!(token_1, IsolationToken::no_isolation());
142/// assert_eq!(IsolationToken::no_isolation(), IsolationToken::no_isolation());
143/// ```
144///
145/// Using an isolation token to route streams differently over the Tor network:
146///
147/// ```ignore
148/// use arti_client::StreamPrefs;
149///
150/// let token_1 = IsolationToken::new();
151/// let token_2 = IsolationToken::new();
152///
153/// let mut prefs_1 = StreamPrefs::new();
154/// prefs_1.set_isolation(token_1);
155///
156/// let mut prefs_2 = StreamPrefs::new();
157/// prefs_2.set_isolation(token_2);
158///
159/// // These two connections will come from different source IP addresses.
160/// tor_client.connect(("example.com", 80), Some(prefs_1)).await?;
161/// tor_client.connect(("example.com", 80), Some(prefs_2)).await?;
162/// ```
163// # Semver note
164//
165// This type is re-exported by `arti-client`: any changes to it must be
166// reflected in `arti-client`'s version.
167#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
168pub struct IsolationToken(u64);
169170#[allow(clippy::new_without_default)]
171impl IsolationToken {
172/// Create a new IsolationToken, unequal to any other token this function
173 /// has created.
174 ///
175 /// # Panics
176 ///
177 /// Panics if we have already allocated 2^64 isolation tokens: in that
178 /// case, we have exhausted the space of possible tokens, and it is
179 /// no longer possible to ensure isolation.
180pub fn new() -> Self {
181/// Internal counter used to generate different tokens each time
182static COUNTER: AtomicU64 = AtomicU64::new(1);
183// Ordering::Relaxed is fine because we don't care about causality, we just want a
184 // different number each time
185let token = COUNTER.fetch_add(1, Ordering::Relaxed);
186assert!(token < u64::MAX);
187 IsolationToken(token)
188 }
189190/// Create a new IsolationToken equal to every other token created
191 /// with this function, but different from all tokens created with
192 /// `new`.
193 ///
194 /// This can be used when no isolation is wanted for some streams.
195pub fn no_isolation() -> Self {
196 IsolationToken(0)
197 }
198}
199200impl IsolationHelper for IsolationToken {
201fn compatible_same_type(&self, other: &Self) -> bool {
202self == other
203 }
204fn join_same_type(&self, other: &Self) -> Option<Self> {
205if self.compatible_same_type(other) {
206Some(*self)
207 } else {
208None
209}
210 }
211}
212213/// Helper macro to implement IsolationHelper for tuple of IsolationHelper
214macro_rules! tuple_impls {
215 ($(
216$Tuple:ident {
217 $(($idx:tt) -> $T:ident)+
218 }
219 )+) => {
220 $(
221impl<$($T:IsolationHelper),+> IsolationHelper for ($($T,)+) {
222fn compatible_same_type(&self, other: &Self) -> bool {
223 $(self.$idx.compatible_same_type(&other.$idx))&&+
224 }
225226fn join_same_type(&self, other: &Self) -> Option<Self> {
227Some((
228 $(self.$idx.join_same_type(&other.$idx)?,)+
229 ))
230 }
231 }
232 )+
233 }
234}
235236tuple_impls! {
237 Tuple1 {
238 (0) -> A
239 }
240 Tuple2 {
241 (0) -> A
242 (1) -> B
243 }
244 Tuple3 {
245 (0) -> A
246 (1) -> B
247 (2) -> C
248 }
249 Tuple4 {
250 (0) -> A
251 (1) -> B
252 (2) -> C
253 (3) -> D
254 }
255 Tuple5 {
256 (0) -> A
257 (1) -> B
258 (2) -> C
259 (3) -> D
260 (4) -> E
261 }
262 Tuple6 {
263 (0) -> A
264 (1) -> B
265 (2) -> C
266 (3) -> D
267 (4) -> E
268 (5) -> F
269 }
270 Tuple7 {
271 (0) -> A
272 (1) -> B
273 (2) -> C
274 (3) -> D
275 (4) -> E
276 (5) -> F
277 (6) -> G
278 }
279 Tuple8 {
280 (0) -> A
281 (1) -> B
282 (2) -> C
283 (3) -> D
284 (4) -> E
285 (5) -> F
286 (6) -> G
287 (7) -> H
288 }
289 Tuple9 {
290 (0) -> A
291 (1) -> B
292 (2) -> C
293 (3) -> D
294 (4) -> E
295 (5) -> F
296 (6) -> G
297 (7) -> H
298 (8) -> I
299 }
300 Tuple10 {
301 (0) -> A
302 (1) -> B
303 (2) -> C
304 (3) -> D
305 (4) -> E
306 (5) -> F
307 (6) -> G
308 (7) -> H
309 (8) -> I
310 (9) -> J
311 }
312 Tuple11 {
313 (0) -> A
314 (1) -> B
315 (2) -> C
316 (3) -> D
317 (4) -> E
318 (5) -> F
319 (6) -> G
320 (7) -> H
321 (8) -> I
322 (9) -> J
323 (10) -> K
324 }
325 Tuple12 {
326 (0) -> A
327 (1) -> B
328 (2) -> C
329 (3) -> D
330 (4) -> E
331 (5) -> F
332 (6) -> G
333 (7) -> H
334 (8) -> I
335 (9) -> J
336 (10) -> K
337 (11) -> L
338 }
339}
340341/// A set of information about how a stream should be isolated.
342///
343/// If two streams are isolated from one another, they may not share
344/// a circuit.
345#[derive(Clone, Debug, derive_builder::Builder)]
346pub struct StreamIsolation {
347/// Any isolation set on the stream.
348#[builder(default = "Box::new(IsolationToken::no_isolation())")]
349stream_isolation: Box<dyn Isolation>,
350/// Any additional isolation token set on an object that "owns" this
351 /// stream. This is typically owned by a `TorClient`.
352#[builder(default = "IsolationToken::no_isolation()")]
353owner_token: IsolationToken,
354}
355356impl StreamIsolation {
357/// Construct a new StreamIsolation with no isolation enabled.
358pub fn no_isolation() -> Self {
359 StreamIsolationBuilder::new()
360 .build()
361 .expect("Bug constructing StreamIsolation")
362 }
363364/// Return a new StreamIsolationBuilder for constructing
365 /// StreamIsolation objects.
366pub fn builder() -> StreamIsolationBuilder {
367 StreamIsolationBuilder::new()
368 }
369}
370371impl IsolationHelper for StreamIsolation {
372fn compatible_same_type(&self, other: &StreamIsolation) -> bool {
373self.owner_token == other.owner_token
374 && self
375.stream_isolation
376 .compatible(other.stream_isolation.as_ref())
377 }
378379fn join_same_type(&self, other: &StreamIsolation) -> Option<StreamIsolation> {
380if self.owner_token != other.owner_token {
381return None;
382 }
383self.stream_isolation
384 .join(other.stream_isolation.as_ref())
385 .map(|stream_isolation| StreamIsolation {
386 stream_isolation,
387 owner_token: self.owner_token,
388 })
389 }
390}
391392impl StreamIsolationBuilder {
393/// Construct a builder with no items set.
394pub fn new() -> Self {
395 StreamIsolationBuilder::default()
396 }
397}
398399#[cfg(test)]
400pub(crate) mod test {
401#![allow(clippy::unwrap_used)]
402use super::*;
403404/// Trait for testing use only. Much like PartialEq, but for type containing an dyn Isolation
405 /// which is known to be an IsolationToken.
406pub(crate) trait IsolationTokenEq {
407/// Compare two values, returning true if they are equals and all dyn Isolation they contain
408 /// are IsolationToken (which are equal too).
409fn isol_eq(&self, other: &Self) -> bool;
410 }
411412macro_rules! assert_isoleq {
413 { $arg1:expr, $arg2:expr } => {
414assert!($arg1.isol_eq(&$arg2))
415 }
416 }
417pub(crate) use assert_isoleq;
418419impl IsolationTokenEq for IsolationToken {
420fn isol_eq(&self, other: &Self) -> bool {
421self == other
422 }
423 }
424425impl<T: IsolationTokenEq> IsolationTokenEq for Option<T> {
426fn isol_eq(&self, other: &Self) -> bool {
427match (self, other) {
428 (Some(this), Some(other)) => this.isol_eq(other),
429 (None, None) => true,
430_ => false,
431 }
432 }
433 }
434435impl<T: IsolationTokenEq + std::fmt::Debug> IsolationTokenEq for Vec<T> {
436fn isol_eq(&self, other: &Self) -> bool {
437if self.len() != other.len() {
438return false;
439 }
440self.iter()
441 .zip(other.iter())
442 .all(|(this, other)| this.isol_eq(other))
443 }
444 }
445446impl IsolationTokenEq for dyn Isolation {
447fn isol_eq(&self, other: &Self) -> bool {
448let this = self.as_any().downcast_ref::<IsolationToken>();
449let other = other.as_any().downcast_ref::<IsolationToken>();
450match (this, other) {
451 (Some(this), Some(other)) => this == other,
452_ => false,
453 }
454 }
455 }
456457impl IsolationTokenEq for StreamIsolation {
458fn isol_eq(&self, other: &Self) -> bool {
459self.stream_isolation
460 .isol_eq(other.stream_isolation.as_ref())
461 && self.owner_token == other.owner_token
462 }
463 }
464465#[derive(PartialEq, Clone, Copy, Debug, Eq)]
466struct OtherIsolation(usize);
467468impl IsolationHelper for OtherIsolation {
469fn compatible_same_type(&self, other: &Self) -> bool {
470self == other
471 }
472fn join_same_type(&self, other: &Self) -> Option<Self> {
473if self.compatible_same_type(other) {
474Some(*self)
475 } else {
476None
477}
478 }
479 }
480481#[test]
482fn isolation_token() {
483let token_1 = IsolationToken::new();
484let token_2 = IsolationToken::new();
485486assert!(token_1.compatible_same_type(&token_1));
487assert!(token_2.compatible_same_type(&token_2));
488assert!(!token_1.compatible_same_type(&token_2));
489490assert_eq!(token_1.join_same_type(&token_1), Some(token_1));
491assert_eq!(token_2.join_same_type(&token_2), Some(token_2));
492assert_eq!(token_1.join_same_type(&token_2), None);
493 }
494495#[test]
496fn isolation_trait() {
497let token_1: Box<dyn Isolation> = Box::new(IsolationToken::new());
498let token_2: Box<dyn Isolation> = Box::new(IsolationToken::new());
499let other_1: Box<dyn Isolation> = Box::new(OtherIsolation(0));
500let other_2: Box<dyn Isolation> = Box::new(OtherIsolation(1));
501502assert!(token_1.compatible(token_1.as_ref()));
503assert!(token_2.compatible(token_2.as_ref()));
504assert!(!token_1.compatible(token_2.as_ref()));
505506assert!(other_1.compatible(other_1.as_ref()));
507assert!(other_2.compatible(other_2.as_ref()));
508assert!(!other_1.compatible(other_2.as_ref()));
509510assert!(!token_1.compatible(other_1.as_ref()));
511assert!(!other_1.compatible(token_1.as_ref()));
512513assert!(token_1.join(token_1.as_ref()).is_some());
514assert!(token_1.join(token_2.as_ref()).is_none());
515516assert!(other_1.join(other_1.as_ref()).is_some());
517assert!(other_1.join(other_2.as_ref()).is_none());
518519assert!(token_1.join(other_1.as_ref()).is_none());
520assert!(other_1.join(token_1.as_ref()).is_none());
521 }
522523#[test]
524fn isolation_tuple() {
525let token_1 = IsolationToken::new();
526let token_2 = IsolationToken::new();
527let other_1 = OtherIsolation(0);
528let other_2 = OtherIsolation(1);
529530let token_12: Box<dyn Isolation> = Box::new((token_1, token_2));
531let token_21: Box<dyn Isolation> = Box::new((token_2, token_1));
532let mix_11: Box<dyn Isolation> = Box::new((token_1, other_1));
533let mix_12: Box<dyn Isolation> = Box::new((token_1, other_2));
534let revmix_11: Box<dyn Isolation> = Box::new((other_1, token_1));
535536let join_token = token_12.join(token_12.as_ref()).unwrap();
537assert!(join_token.compatible(token_12.as_ref()));
538let join_mix = mix_12.join(mix_12.as_ref()).unwrap();
539assert!(join_mix.compatible(mix_12.as_ref()));
540541let isol_list = [token_12, token_21, mix_11, mix_12, revmix_11];
542543for (i, isol1) in isol_list.iter().enumerate() {
544for (j, isol2) in isol_list.iter().enumerate() {
545assert_eq!(isol1.compatible(isol2.as_ref()), i == j);
546 }
547 }
548 }
549550#[test]
551fn build_isolation() {
552let no_isolation = StreamIsolation::no_isolation();
553let no_isolation2 = StreamIsolation::builder()
554 .owner_token(IsolationToken::no_isolation())
555 .stream_isolation(Box::new(IsolationToken::no_isolation()))
556 .build()
557 .unwrap();
558assert_eq!(no_isolation.owner_token, no_isolation2.owner_token);
559assert_eq!(
560 no_isolation
561 .stream_isolation
562 .as_ref()
563 .as_any()
564 .downcast_ref::<IsolationToken>(),
565 no_isolation2
566 .stream_isolation
567 .as_ref()
568 .as_any()
569 .downcast_ref::<IsolationToken>()
570 );
571assert!(no_isolation.compatible(&no_isolation2));
572573let tok = IsolationToken::new();
574let some_isolation = StreamIsolation::builder().owner_token(tok).build().unwrap();
575let some_isolation2 = StreamIsolation::builder()
576 .stream_isolation(Box::new(tok))
577 .build()
578 .unwrap();
579assert!(!no_isolation.compatible(&some_isolation));
580assert!(!no_isolation.compatible(&some_isolation2));
581assert!(!some_isolation.compatible(&some_isolation2));
582assert!(some_isolation.compatible(&some_isolation));
583 }
584}