1
//! Types related to stream isolation
2
use downcast_rs::{impl_downcast, Downcast};
3
use dyn_clone::{clone_trait_object, DynClone};
4
use std::sync::atomic::{AtomicU64, Ordering};
5

            
6
/// 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`].
16
pub 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.
36
    fn compatible(&self, other: &dyn Isolation) -> bool;
37

            
38
    /// 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.)
56
    fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>>;
57
}
58

            
59
/// Seal preventing implementation of Isolation not relying on IsolationHelper
60
mod seal {
61
    /// Seal preventing implementation of Isolation not relying on IsolationHelper
62
    pub trait Sealed {}
63
    impl<T: super::IsolationHelper> Sealed for T {}
64
}
65

            
66
impl_downcast!(Isolation);
67
clone_trait_object!(Isolation);
68
impl<T: Isolation> From<T> for Box<dyn Isolation> {
69
52
    fn from(isolation: T) -> Self {
70
52
        Box::new(isolation)
71
52
    }
72
}
73

            
74
impl<T: IsolationHelper + Clone + std::fmt::Debug + Send + Sync + 'static> Isolation for T {
75
508
    fn compatible(&self, other: &dyn Isolation) -> bool {
76
508
        if let Some(other) = other.as_any().downcast_ref() {
77
472
            self.compatible_same_type(other)
78
        } else {
79
36
            false
80
        }
81
508
    }
82

            
83
452
    fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>> {
84
452
        if let Some(other) = other.as_any().downcast_ref() {
85
448
            self.join_same_type(other)
86
459
                .map(|res| Box::new(res) as Box<dyn Isolation>)
87
        } else {
88
4
            None
89
        }
90
452
    }
91
}
92

            
93
/// 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.)
104
pub 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.)
111
    fn compatible_same_type(&self, other: &Self) -> bool;
112

            
113
    /// Join self and other into the intersection of what they allows.
114
    ///
115
    /// (See [`Isolation::join`] for more information and requirements.)
116
    fn join_same_type(&self, other: &Self) -> Option<Self>;
117
}
118

            
119
/// 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)]
168
pub struct IsolationToken(u64);
169

            
170
#[allow(clippy::new_without_default)]
171
impl 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.
180
678
    pub fn new() -> Self {
181
        /// Internal counter used to generate different tokens each time
182
        static 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
185
678
        let token = COUNTER.fetch_add(1, Ordering::Relaxed);
186
678
        assert!(token < u64::MAX);
187
678
        IsolationToken(token)
188
678
    }
189

            
190
    /// 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.
195
624
    pub fn no_isolation() -> Self {
196
624
        IsolationToken(0)
197
624
    }
198
}
199

            
200
impl IsolationHelper for IsolationToken {
201
896
    fn compatible_same_type(&self, other: &Self) -> bool {
202
896
        self == other
203
896
    }
204
428
    fn join_same_type(&self, other: &Self) -> Option<Self> {
205
428
        if self.compatible_same_type(other) {
206
424
            Some(*self)
207
        } else {
208
4
            None
209
        }
210
428
    }
211
}
212

            
213
/// Helper macro to implement IsolationHelper for tuple of IsolationHelper
214
macro_rules! tuple_impls {
215
    ($(
216
        $Tuple:ident {
217
            $(($idx:tt) -> $T:ident)+
218
        }
219
    )+) => {
220
        $(
221
            impl<$($T:IsolationHelper),+> IsolationHelper for ($($T,)+) {
222
22
                fn compatible_same_type(&self, other: &Self) -> bool {
223
18
                    $(self.$idx.compatible_same_type(&other.$idx))&&+
224
22
                }
225

            
226
4
                fn join_same_type(&self, other: &Self) -> Option<Self> {
227
                    Some((
228
4
                    $(self.$idx.join_same_type(&other.$idx)?,)+
229
                    ))
230
4
                }
231
            }
232
        )+
233
    }
234
}
235

            
236
tuple_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
}
340

            
341
/// 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
684
#[derive(Clone, Debug, derive_builder::Builder)]
346
pub struct StreamIsolation {
347
    /// Any isolation set on the stream.
348
    #[builder(default = "Box::new(IsolationToken::no_isolation())")]
349
    stream_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()")]
353
    owner_token: IsolationToken,
354
}
355

            
356
impl StreamIsolation {
357
    /// Construct a new StreamIsolation with no isolation enabled.
358
292
    pub fn no_isolation() -> Self {
359
292
        StreamIsolationBuilder::new()
360
292
            .build()
361
292
            .expect("Bug constructing StreamIsolation")
362
292
    }
363

            
364
    /// Return a new StreamIsolationBuilder for constructing
365
    /// StreamIsolation objects.
366
18
    pub fn builder() -> StreamIsolationBuilder {
367
18
        StreamIsolationBuilder::new()
368
18
    }
369
}
370

            
371
impl IsolationHelper for StreamIsolation {
372
1212
    fn compatible_same_type(&self, other: &StreamIsolation) -> bool {
373
1212
        self.owner_token == other.owner_token
374
428
            && self
375
428
                .stream_isolation
376
428
                .compatible(other.stream_isolation.as_ref())
377
1212
    }
378

            
379
416
    fn join_same_type(&self, other: &StreamIsolation) -> Option<StreamIsolation> {
380
416
        if self.owner_token != other.owner_token {
381
4
            return None;
382
412
        }
383
412
        self.stream_isolation
384
412
            .join(other.stream_isolation.as_ref())
385
618
            .map(|stream_isolation| StreamIsolation {
386
412
                stream_isolation,
387
412
                owner_token: self.owner_token,
388
618
            })
389
416
    }
390
}
391

            
392
impl StreamIsolationBuilder {
393
    /// Construct a builder with no items set.
394
326
    pub fn new() -> Self {
395
326
        StreamIsolationBuilder::default()
396
326
    }
397
}
398

            
399
#[cfg(test)]
400
pub(crate) mod test {
401
    #![allow(clippy::unwrap_used)]
402
    use super::*;
403

            
404
    /// Trait for testing use only. Much like PartialEq, but for type containing an dyn Isolation
405
    /// which is known to be an IsolationToken.
406
    pub(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).
409
        fn isol_eq(&self, other: &Self) -> bool;
410
    }
411

            
412
    macro_rules! assert_isoleq {
413
        { $arg1:expr, $arg2:expr } => {
414
            assert!($arg1.isol_eq(&$arg2))
415
        }
416
    }
417
    pub(crate) use assert_isoleq;
418

            
419
    impl IsolationTokenEq for IsolationToken {
420
        fn isol_eq(&self, other: &Self) -> bool {
421
            self == other
422
        }
423
    }
424

            
425
    impl<T: IsolationTokenEq> IsolationTokenEq for Option<T> {
426
        fn isol_eq(&self, other: &Self) -> bool {
427
            match (self, other) {
428
                (Some(this), Some(other)) => this.isol_eq(other),
429
                (None, None) => true,
430
                _ => false,
431
            }
432
        }
433
    }
434

            
435
    impl<T: IsolationTokenEq + std::fmt::Debug> IsolationTokenEq for Vec<T> {
436
        fn isol_eq(&self, other: &Self) -> bool {
437
            if self.len() != other.len() {
438
                return false;
439
            }
440
            self.iter()
441
                .zip(other.iter())
442
                .all(|(this, other)| this.isol_eq(other))
443
        }
444
    }
445

            
446
    impl IsolationTokenEq for dyn Isolation {
447
        fn isol_eq(&self, other: &Self) -> bool {
448
            let this = self.as_any().downcast_ref::<IsolationToken>();
449
            let other = other.as_any().downcast_ref::<IsolationToken>();
450
            match (this, other) {
451
                (Some(this), Some(other)) => this == other,
452
                _ => false,
453
            }
454
        }
455
    }
456

            
457
    impl IsolationTokenEq for StreamIsolation {
458
        fn isol_eq(&self, other: &Self) -> bool {
459
            self.stream_isolation
460
                .isol_eq(other.stream_isolation.as_ref())
461
                && self.owner_token == other.owner_token
462
        }
463
    }
464

            
465
    #[derive(PartialEq, Clone, Copy, Debug, Eq)]
466
    struct OtherIsolation(usize);
467

            
468
    impl IsolationHelper for OtherIsolation {
469
        fn compatible_same_type(&self, other: &Self) -> bool {
470
            self == other
471
        }
472
        fn join_same_type(&self, other: &Self) -> Option<Self> {
473
            if self.compatible_same_type(other) {
474
                Some(*self)
475
            } else {
476
                None
477
            }
478
        }
479
    }
480

            
481
    #[test]
482
    fn isolation_token() {
483
        let token_1 = IsolationToken::new();
484
        let token_2 = IsolationToken::new();
485

            
486
        assert!(token_1.compatible_same_type(&token_1));
487
        assert!(token_2.compatible_same_type(&token_2));
488
        assert!(!token_1.compatible_same_type(&token_2));
489

            
490
        assert_eq!(token_1.join_same_type(&token_1), Some(token_1));
491
        assert_eq!(token_2.join_same_type(&token_2), Some(token_2));
492
        assert_eq!(token_1.join_same_type(&token_2), None);
493
    }
494

            
495
    #[test]
496
    fn isolation_trait() {
497
        let token_1: Box<dyn Isolation> = Box::new(IsolationToken::new());
498
        let token_2: Box<dyn Isolation> = Box::new(IsolationToken::new());
499
        let other_1: Box<dyn Isolation> = Box::new(OtherIsolation(0));
500
        let other_2: Box<dyn Isolation> = Box::new(OtherIsolation(1));
501

            
502
        assert!(token_1.compatible(token_1.as_ref()));
503
        assert!(token_2.compatible(token_2.as_ref()));
504
        assert!(!token_1.compatible(token_2.as_ref()));
505

            
506
        assert!(other_1.compatible(other_1.as_ref()));
507
        assert!(other_2.compatible(other_2.as_ref()));
508
        assert!(!other_1.compatible(other_2.as_ref()));
509

            
510
        assert!(!token_1.compatible(other_1.as_ref()));
511
        assert!(!other_1.compatible(token_1.as_ref()));
512

            
513
        assert!(token_1.join(token_1.as_ref()).is_some());
514
        assert!(token_1.join(token_2.as_ref()).is_none());
515

            
516
        assert!(other_1.join(other_1.as_ref()).is_some());
517
        assert!(other_1.join(other_2.as_ref()).is_none());
518

            
519
        assert!(token_1.join(other_1.as_ref()).is_none());
520
        assert!(other_1.join(token_1.as_ref()).is_none());
521
    }
522

            
523
    #[test]
524
    fn isolation_tuple() {
525
        let token_1 = IsolationToken::new();
526
        let token_2 = IsolationToken::new();
527
        let other_1 = OtherIsolation(0);
528
        let other_2 = OtherIsolation(1);
529

            
530
        let token_12: Box<dyn Isolation> = Box::new((token_1, token_2));
531
        let token_21: Box<dyn Isolation> = Box::new((token_2, token_1));
532
        let mix_11: Box<dyn Isolation> = Box::new((token_1, other_1));
533
        let mix_12: Box<dyn Isolation> = Box::new((token_1, other_2));
534
        let revmix_11: Box<dyn Isolation> = Box::new((other_1, token_1));
535

            
536
        let join_token = token_12.join(token_12.as_ref()).unwrap();
537
        assert!(join_token.compatible(token_12.as_ref()));
538
        let join_mix = mix_12.join(mix_12.as_ref()).unwrap();
539
        assert!(join_mix.compatible(mix_12.as_ref()));
540

            
541
        let isol_list = [token_12, token_21, mix_11, mix_12, revmix_11];
542

            
543
        for (i, isol1) in isol_list.iter().enumerate() {
544
            for (j, isol2) in isol_list.iter().enumerate() {
545
                assert_eq!(isol1.compatible(isol2.as_ref()), i == j);
546
            }
547
        }
548
    }
549

            
550
    #[test]
551
    fn build_isolation() {
552
        let no_isolation = StreamIsolation::no_isolation();
553
        let no_isolation2 = StreamIsolation::builder()
554
            .owner_token(IsolationToken::no_isolation())
555
            .stream_isolation(Box::new(IsolationToken::no_isolation()))
556
            .build()
557
            .unwrap();
558
        assert_eq!(no_isolation.owner_token, no_isolation2.owner_token);
559
        assert_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
        );
571
        assert!(no_isolation.compatible(&no_isolation2));
572

            
573
        let tok = IsolationToken::new();
574
        let some_isolation = StreamIsolation::builder().owner_token(tok).build().unwrap();
575
        let some_isolation2 = StreamIsolation::builder()
576
            .stream_isolation(Box::new(tok))
577
            .build()
578
            .unwrap();
579
        assert!(!no_isolation.compatible(&some_isolation));
580
        assert!(!no_isolation.compatible(&some_isolation2));
581
        assert!(!some_isolation.compatible(&some_isolation2));
582
        assert!(some_isolation.compatible(&some_isolation));
583
    }
584
}