1
//! Simple implementation for the internal map state of a ChanMgr.
2

            
3
use std::time::Duration;
4

            
5
use super::AbstractChannelFactory;
6
use super::{AbstractChannel, Pending};
7
use crate::{ChannelConfig, Dormancy, Result};
8

            
9
use std::result::Result as StdResult;
10
use std::sync::Arc;
11
use tor_cell::chancell::msg::PaddingNegotiate;
12
use tor_config::PaddingLevel;
13
use tor_error::{internal, into_internal};
14
use tor_linkspec::ByRelayIds;
15
use tor_linkspec::HasRelayIds;
16
use tor_linkspec::RelayIds;
17
use tor_netdir::{params::NetParameters, params::CHANNEL_PADDING_TIMEOUT_UPPER_BOUND};
18
use tor_proto::channel::padding::Parameters as PaddingParameters;
19
use tor_proto::channel::padding::ParametersBuilder as PaddingParametersBuilder;
20
use tor_proto::channel::ChannelPaddingInstructionsUpdates;
21
use tor_proto::ChannelPaddingInstructions;
22
use tor_units::{BoundedInt32, IntegerMilliseconds};
23
use tracing::info;
24
use void::{ResultVoidExt as _, Void};
25

            
26
#[cfg(test)]
27
mod padding_test;
28

            
29
/// All mutable state held by an `AbstractChannelMgr`.
30
///
31
/// One reason that this is an isolated type is that we want to
32
/// to limit the amount of code that can see and
33
/// lock the Mutex here.  (We're using a blocking mutex close to async
34
/// code, so we need to be careful.)
35
pub(crate) struct MgrState<C: AbstractChannelFactory> {
36
    /// The data, within a lock
37
    ///
38
    /// (Danger: this uses a blocking mutex close to async code.  This mutex
39
    /// must never be held while an await is happening.)
40
    inner: std::sync::Mutex<Inner<C>>,
41
}
42

            
43
/// A map from channel id to channel state, plus necessary auxiliary state - inside lock
44
struct Inner<C: AbstractChannelFactory> {
45
    /// The channel factory type that we store.
46
    ///
47
    /// In this module we never use this _as_ an AbstractChannelFactory: we just
48
    /// hand out clones of it when asked.
49
    builder: C,
50

            
51
    /// A map from identity to channel, or to pending channel status.
52
    channels: ByRelayIds<ChannelState<C::Channel>>,
53

            
54
    /// Parameters for channels that we create, and that all existing channels are using
55
    ///
56
    /// Will be updated by a background task, which also notifies all existing
57
    /// `Open` channels via `channels`.
58
    ///
59
    /// (Must be protected by the same lock as `channels`, or a channel might be
60
    /// created using being-replaced parameters, but not get an update.)
61
    channels_params: ChannelPaddingInstructions,
62

            
63
    /// The configuration (from the config file or API caller)
64
    config: ChannelConfig,
65

            
66
    /// Dormancy
67
    ///
68
    /// The last dormancy information we have been told about and passed on to our channels.
69
    /// Updated via `MgrState::set_dormancy` and hence `MgrState::reconfigure_general`,
70
    /// which then uses it to calculate how to reconfigure the channels.
71
    dormancy: Dormancy,
72
}
73

            
74
/// The state of a channel (or channel build attempt) within a map.
75
///
76
/// A ChannelState can be Open (representing a fully negotiated channel) or
77
/// Building (representing a pending attempt to build a channel). Both states
78
/// have a set of RelayIds, but these RelayIds represent slightly different
79
/// things:
80
///  * On a Building channel, the set of RelayIds is all the identities that we
81
///    require the peer to have. (The peer may turn out to have _more_
82
///    identities than this.)
83
///  * On an Open channel, the set of RelayIds is all the identities that
84
///    we were able to successfully authenticate for the peer.
85
pub(crate) enum ChannelState<C> {
86
    /// An open channel.
87
    ///
88
    /// This channel might not be usable: it might be closing or
89
    /// broken.  We need to check its is_usable() method before
90
    /// yielding it to the user.
91
    Open(OpenEntry<C>),
92
    /// A channel that's getting built.
93
    Building(PendingEntry),
94
}
95

            
96
/// An open channel entry.
97
#[derive(Clone)]
98
pub(crate) struct OpenEntry<C> {
99
    /// The underlying open channel.
100
    pub(crate) channel: C,
101
    /// The maximum unused duration allowed for this channel.
102
    pub(crate) max_unused_duration: Duration,
103
}
104

            
105
/// An entry for a not-yet-build channel
106
#[derive(Clone)]
107
pub(crate) struct PendingEntry {
108
    /// The keys of the relay to which we're trying to open a channel.
109
    pub(crate) ids: RelayIds,
110

            
111
    /// A future we can clone and listen on to learn when this channel attempt
112
    /// is successful or failed.
113
    ///
114
    /// This entry will be removed from the map (and possibly replaced with an
115
    /// `OpenEntry`) _before_ this future becomes ready.
116
    pub(crate) pending: Pending,
117
}
118

            
119
impl<C> HasRelayIds for ChannelState<C>
120
where
121
    C: HasRelayIds,
122
{
123
1072
    fn identity(
124
1072
        &self,
125
1072
        key_type: tor_linkspec::RelayIdType,
126
1072
    ) -> Option<tor_linkspec::RelayIdRef<'_>> {
127
1072
        match self {
128
474
            ChannelState::Open(OpenEntry { channel, .. }) => channel.identity(key_type),
129
598
            ChannelState::Building(PendingEntry { ids, .. }) => ids.identity(key_type),
130
        }
131
1072
    }
132
}
133

            
134
impl<C: Clone> ChannelState<C> {
135
    /// For testing: either give the Open channel inside this state,
136
    /// or panic if there is none.
137
    #[cfg(test)]
138
4
    fn unwrap_open(&self) -> &C {
139
4
        match self {
140
4
            ChannelState::Open(ent) => &ent.channel,
141
            _ => panic!("Not an open channel"),
142
        }
143
4
    }
144
}
145

            
146
/// Type of the `nf_ito_*` netdir parameters, convenience alias
147
type NfIto = IntegerMilliseconds<BoundedInt32<0, CHANNEL_PADDING_TIMEOUT_UPPER_BOUND>>;
148

            
149
/// Extract from a `NetParameters` which we need, conveniently organized for our processing
150
///
151
/// This type serves two functions at once:
152
///
153
///  1. Being a subset of the parameters, we can copy it out of
154
///     the netdir, before we do more complex processing - and, in particular,
155
///     before we obtain the lock on `inner` (which we need to actually handle the update,
156
///     because we need to combine information from the config with that from the netdir).
157
///
158
///  2. Rather than four separate named fields, it has arrays, so that it is easy to
159
///     select the values without error-prone recapitulation of field names.
160
#[derive(Debug, Clone)]
161
struct NetParamsExtract {
162
    /// `nf_ito_*`, the padding timeout parameters from the netdir consensus
163
    ///
164
    /// `nf_ito[ 0=normal, 1=reduced ][ 0=low, 1=high ]`
165
    /// are `nf_ito_{low,high}{,_reduced` from `NetParameters`.
166
    // TODO we could use some enum or IndexVec or something to make this less `0` and `1`
167
    nf_ito: [[NfIto; 2]; 2],
168
}
169

            
170
impl From<&NetParameters> for NetParamsExtract {
171
757
    fn from(p: &NetParameters) -> Self {
172
757
        NetParamsExtract {
173
757
            nf_ito: [
174
757
                [p.nf_ito_low, p.nf_ito_high],
175
757
                [p.nf_ito_low_reduced, p.nf_ito_high_reduced],
176
757
            ],
177
757
        }
178
757
    }
179
}
180

            
181
impl NetParamsExtract {
182
    /// Return the padding timer parameter low end, for reduced-ness `reduced`, as a `u32`
183
1508
    fn pad_low(&self, reduced: bool) -> IntegerMilliseconds<u32> {
184
1508
        self.pad_get(reduced, 0)
185
1508
    }
186
    /// Return the padding timer parameter high end, for reduced-ness `reduced`, as a `u32`
187
1508
    fn pad_high(&self, reduced: bool) -> IntegerMilliseconds<u32> {
188
1508
        self.pad_get(reduced, 1)
189
1508
    }
190

            
191
    /// Return and converts one padding parameter timer
192
    ///
193
    /// Internal function.
194
3016
    fn pad_get(&self, reduced: bool, low_or_high: usize) -> IntegerMilliseconds<u32> {
195
3016
        self.nf_ito[usize::from(reduced)][low_or_high]
196
3186
            .try_map(|v| Ok::<_, Void>(v.into()))
197
3016
            .void_unwrap()
198
3016
    }
199
}
200

            
201
impl<C: AbstractChannel> ChannelState<C> {
202
    /// Return true if a channel is ready to expire.
203
    /// Update `expire_after` if a smaller duration than
204
    /// the given value is required to expire this channel.
205
10
    fn ready_to_expire(&self, expire_after: &mut Duration) -> bool {
206
10
        let ChannelState::Open(ent) = self else {
207
            return false;
208
        };
209
10
        let Some(unused_duration) = ent.channel.duration_unused() else {
210
            // still in use
211
2
            return false;
212
        };
213
8
        let max_unused_duration = ent.max_unused_duration;
214
8
        let Some(remaining) = max_unused_duration.checked_sub(unused_duration) else {
215
            // no time remaining; drop now.
216
4
            return true;
217
        };
218
4
        if remaining.is_zero() {
219
            // Ignoring this edge case would result in a fairly benign race
220
            // condition outside of Shadow, but deadlock in Shadow.
221
            return true;
222
4
        }
223
4
        *expire_after = std::cmp::min(*expire_after, remaining);
224
4
        false
225
10
    }
226
}
227

            
228
impl<C: AbstractChannelFactory> MgrState<C> {
229
    /// Create a new empty `MgrState`.
230
61
    pub(crate) fn new(
231
61
        builder: C,
232
61
        config: ChannelConfig,
233
61
        dormancy: Dormancy,
234
61
        netparams: &NetParameters,
235
61
    ) -> Self {
236
61
        let mut channels_params = ChannelPaddingInstructions::default();
237
61
        let netparams = NetParamsExtract::from(netparams);
238
61
        let update = parameterize(&mut channels_params, &config, dormancy, &netparams)
239
61
            .unwrap_or_else(|e: tor_error::Bug| panic!("bug detected on startup: {:?}", e));
240
61
        let _: Option<_> = update; // there are no channels yet, that would need to be told
241
61

            
242
61
        MgrState {
243
61
            inner: std::sync::Mutex::new(Inner {
244
61
                builder,
245
61
                channels: ByRelayIds::new(),
246
61
                config,
247
61
                channels_params,
248
61
                dormancy,
249
61
            }),
250
61
        }
251
61
    }
252

            
253
    /// Run a function on the `ByRelayIds` that implements the map in this `MgrState`.
254
    ///
255
    /// This function grabs a mutex: do not provide a slow function.
256
    ///
257
    /// We provide this function rather than exposing the channels set directly,
258
    /// to make sure that the calling code doesn't await while holding the lock.
259
190
    pub(crate) fn with_channels<F, T>(&self, func: F) -> Result<T>
260
190
    where
261
190
        F: FnOnce(&mut ByRelayIds<ChannelState<C::Channel>>) -> T,
262
190
    {
263
190
        let mut inner = self.inner.lock()?;
264
190
        Ok(func(&mut inner.channels))
265
190
    }
266

            
267
    /// Return a copy of the builder stored in this state.
268
68
    pub(crate) fn builder(&self) -> C
269
68
    where
270
68
        C: Clone,
271
68
    {
272
68
        let inner = self.inner.lock().expect("lock poisoned");
273
68
        inner.builder.clone()
274
68
    }
275

            
276
    /// Run a function to modify the builder stored in this state.
277
    #[allow(dead_code)]
278
13
    pub(crate) fn with_mut_builder<F>(&self, func: F)
279
13
    where
280
13
        F: FnOnce(&mut C),
281
13
    {
282
13
        let mut inner = self.inner.lock().expect("lock poisoned");
283
13
        func(&mut inner.builder);
284
13
    }
285

            
286
    /// Run a function on the `ByRelayIds` that implements the map in this `MgrState`.
287
    ///
288
    /// This function grabs a mutex: do not provide a slow function.
289
    ///
290
    /// We provide this function rather than exposing the channels set directly,
291
    /// to make sure that the calling code doesn't await while holding the lock.
292
60
    pub(crate) fn with_channels_and_params<F, T>(&self, func: F) -> Result<T>
293
60
    where
294
60
        F: FnOnce(&mut ByRelayIds<ChannelState<C::Channel>>, &ChannelPaddingInstructions) -> T,
295
60
    {
296
60
        let mut inner = self.inner.lock()?;
297
        // We need this silly destructuring syntax so that we don't seem to be
298
        // borrowing the structure mutably and immutably at the same time.
299
        let Inner {
300
60
            ref mut channels,
301
60
            ref channels_params,
302
60
            ..
303
60
        } = &mut *inner;
304
60
        Ok(func(channels, channels_params))
305
60
    }
306

            
307
    /// Remove every unusable state from the map in this state..
308
    #[cfg(test)]
309
4
    pub(crate) fn remove_unusable(&self) -> Result<()> {
310
4
        let mut inner = self.inner.lock()?;
311
14
        inner.channels.retain(|state| match state {
312
14
            ChannelState::Open(ent) => ent.channel.is_usable(),
313
            ChannelState::Building(_) => true,
314
14
        });
315
4
        Ok(())
316
4
    }
317

            
318
    /// Reconfigure all channels as necessary
319
    ///
320
    /// (By reparameterizing channels as needed)
321
    /// This function will handle
322
    ///   - netdir update
323
    ///   - a reconfiguration
324
    ///   - dormancy
325
    ///
326
    /// For `new_config` and `new_dormancy`, `None` means "no change to previous info".
327
36
    pub(super) fn reconfigure_general(
328
36
        &self,
329
36
        new_config: Option<&ChannelConfig>,
330
36
        new_dormancy: Option<Dormancy>,
331
36
        netparams: Arc<dyn AsRef<NetParameters>>,
332
36
    ) -> StdResult<(), tor_error::Bug> {
333
36
        use ChannelState as CS;
334
36

            
335
36
        // TODO when we support operation as a relay, inter-relay channels ought
336
36
        // not to get padding.
337
36
        let netdir = {
338
36
            let extract = NetParamsExtract::from((*netparams).as_ref());
339
36
            drop(netparams);
340
36
            extract
341
        };
342

            
343
36
        let mut inner = self
344
36
            .inner
345
36
            .lock()
346
36
            .map_err(|_| internal!("poisoned channel manager"))?;
347
36
        let inner = &mut *inner;
348

            
349
36
        if let Some(new_config) = new_config {
350
8
            inner.config = new_config.clone();
351
30
        }
352
36
        if let Some(new_dormancy) = new_dormancy {
353
13
            inner.dormancy = new_dormancy;
354
23
        }
355

            
356
36
        let update = parameterize(
357
36
            &mut inner.channels_params,
358
36
            &inner.config,
359
36
            inner.dormancy,
360
36
            &netdir,
361
36
        )?;
362

            
363
36
        let update = if let Some(u) = update {
364
12
            u
365
        } else {
366
24
            return Ok(());
367
        };
368
12
        let update = Arc::new(update);
369

            
370
12
        for channel in inner.channels.values() {
371
12
            let channel = match channel {
372
12
                CS::Open(OpenEntry { channel, .. }) => channel,
373
                CS::Building(_) => continue,
374
            };
375
            // Ignore error (which simply means the channel is closed or gone)
376
12
            let _ = channel.reparameterize(update.clone());
377
        }
378
12
        Ok(())
379
36
    }
380

            
381
    /// Expire all channels that have been unused for too long.
382
    ///
383
    /// Return a Duration until the next time at which
384
    /// a channel _could_ expire.
385
26
    pub(crate) fn expire_channels(&self) -> Duration {
386
26
        let mut ret = Duration::from_secs(180);
387
26
        self.inner
388
26
            .lock()
389
26
            .expect("Poisoned lock")
390
26
            .channels
391
32
            .retain(|chan| !chan.ready_to_expire(&mut ret));
392
26
        ret
393
26
    }
394
}
395

            
396
/// Converts config, dormancy, and netdir, into parameter updates
397
///
398
/// Calculates new parameters, updating `channels_params` as appropriate.
399
/// If anything changed, the corresponding update instruction is returned.
400
///
401
/// `channels_params` is updated with the new parameters,
402
/// and the update message, if one is needed, is returned.
403
///
404
/// This is called in two places:
405
///
406
///  1. During chanmgr creation, it is called once to analyze the initial state
407
///     and construct a corresponding ChannelPaddingInstructions.
408
///
409
///  2. During reconfiguration.
410
751
fn parameterize(
411
751
    channels_params: &mut ChannelPaddingInstructions,
412
751
    config: &ChannelConfig,
413
751
    dormancy: Dormancy,
414
751
    netdir: &NetParamsExtract,
415
751
) -> StdResult<Option<ChannelPaddingInstructionsUpdates>, tor_error::Bug> {
416
751
    // Everything in this calculation applies to *all* channels, disregarding
417
751
    // channel usage.  Usage is handled downstream, in the channel frontend.
418
751
    // See the module doc in `crates/tor-proto/src/channel/padding.rs`.
419
751

            
420
1543
    let padding_of_level = |level| padding_parameters(level, netdir);
421
751
    let send_padding = padding_of_level(config.padding)?;
422
751
    let padding_default = padding_of_level(PaddingLevel::default())?;
423

            
424
751
    let send_padding = match dormancy {
425
601
        Dormancy::Active => send_padding,
426
150
        Dormancy::Dormant => None,
427
    };
428

            
429
751
    let recv_padding = match config.padding {
430
8
        PaddingLevel::Reduced => None,
431
743
        PaddingLevel::Normal => send_padding,
432
        PaddingLevel::None => None,
433
    };
434

            
435
    // Whether the inbound padding approach we are to use, is the same as the default
436
    // derived from the netdir (disregarding our config and dormancy).
437
    //
438
    // Ie, whether the parameters we want are precisely those that a peer would
439
    // use by default (assuming they have the same view of the netdir as us).
440
751
    let recv_equals_default = recv_padding == padding_default;
441

            
442
751
    let padding_negotiate = if recv_equals_default {
443
        // Our padding approach is the same as peers' defaults.  So the PADDING_NEGOTIATE
444
        // message we need to send is the START(0,0).  (The channel frontend elides an
445
        // initial message of this form, - see crates/tor-proto/src/channel.rs::note_usage.)
446
        //
447
        // If the netdir default is no padding, and we previously negotiated
448
        // padding being enabled, and now want to disable it, we would send
449
        // START(0,0) rather than STOP.  That is OK (even, arguably, right).
450
595
        PaddingNegotiate::start_default()
451
    } else {
452
156
        match recv_padding {
453
156
            None => PaddingNegotiate::stop(),
454
            Some(params) => params.padding_negotiate_cell()?,
455
        }
456
    };
457

            
458
751
    let mut update = channels_params
459
751
        .start_update()
460
751
        .padding_enable(send_padding.is_some())
461
751
        .padding_negotiate(padding_negotiate);
462
751
    if let Some(params) = send_padding {
463
599
        update = update.padding_parameters(params);
464
599
    }
465
751
    let update = update.finish();
466
751

            
467
751
    Ok(update)
468
751
}
469

            
470
/// Given a `NetDirExtract` and whether we're reducing padding, return a `PaddingParameters`
471
///
472
/// With `PaddingLevel::None`, or the consensus specifies no padding, will return `None`;
473
/// but does not account for other reasons why padding might be enabled/disabled.
474
1508
fn padding_parameters(
475
1508
    config: PaddingLevel,
476
1508
    netdir: &NetParamsExtract,
477
1508
) -> StdResult<Option<PaddingParameters>, tor_error::Bug> {
478
1508
    let reduced = match config {
479
10
        PaddingLevel::Reduced => true,
480
1498
        PaddingLevel::Normal => false,
481
        PaddingLevel::None => return Ok(None),
482
    };
483

            
484
1508
    padding_parameters_builder(reduced, netdir)
485
1509
        .unwrap_or_else(|e: &str| {
486
2
            info!(
487
                "consensus channel padding parameters wrong, using defaults: {}",
488
                &e,
489
            );
490
2
            Some(PaddingParametersBuilder::default())
491
1509
        })
492
1591
        .map(|p| {
493
1504
            p.build()
494
1504
                .map_err(into_internal!("failed to build padding parameters"))
495
1591
        })
496
1508
        .transpose()
497
1508
}
498

            
499
/// Given a `NetDirExtract` and whether we're reducing padding,
500
/// return a `PaddingParametersBuilder`
501
///
502
/// If the consensus specifies no padding, will return `None`;
503
/// but does not account for other reasons why padding might be enabled/disabled.
504
///
505
/// If `Err`, the string is a description of what is wrong with the parameters;
506
/// the caller should use `PaddingParameters::Default`.
507
1508
fn padding_parameters_builder(
508
1508
    reduced: bool,
509
1508
    netdir: &NetParamsExtract,
510
1508
) -> StdResult<Option<PaddingParametersBuilder>, &'static str> {
511
1508
    let mut p = PaddingParametersBuilder::default();
512
1508

            
513
1508
    let low = netdir.pad_low(reduced);
514
1508
    let high = netdir.pad_high(reduced);
515
1508
    if low > high {
516
2
        return Err("low > high");
517
1506
    }
518
1506
    if low.as_millis() == 0 && high.as_millis() == 0 {
519
        // Zeroes for both channel padding consensus parameters means "don't send padding".
520
        // padding-spec.txt s2.6, see description of `nf_ito_high`.
521
4
        return Ok(None);
522
1502
    }
523
1502
    p.low(low);
524
1502
    p.high(high);
525
1502
    Ok::<_, &'static str>(Some(p))
526
1508
}
527

            
528
#[cfg(test)]
529
mod test {
530
    // @@ begin test lint list maintained by maint/add_warning @@
531
    #![allow(clippy::bool_assert_comparison)]
532
    #![allow(clippy::clone_on_copy)]
533
    #![allow(clippy::dbg_macro)]
534
    #![allow(clippy::print_stderr)]
535
    #![allow(clippy::print_stdout)]
536
    #![allow(clippy::single_char_pattern)]
537
    #![allow(clippy::unwrap_used)]
538
    #![allow(clippy::unchecked_duration_subtraction)]
539
    #![allow(clippy::useless_vec)]
540
    #![allow(clippy::needless_pass_by_value)]
541
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
542

            
543
    use super::*;
544
    use crate::factory::BootstrapReporter;
545
    use async_trait::async_trait;
546
    use std::sync::{Arc, Mutex};
547
    use tor_llcrypto::pk::ed25519::Ed25519Identity;
548
    use tor_proto::channel::params::ChannelPaddingInstructionsUpdates;
549

            
550
    fn new_test_state() -> MgrState<FakeChannelFactory> {
551
        MgrState::new(
552
            FakeChannelFactory::default(),
553
            ChannelConfig::default(),
554
            Default::default(),
555
            &Default::default(),
556
        )
557
    }
558

            
559
    #[derive(Clone, Debug, Default)]
560
    struct FakeChannelFactory {}
561

            
562
    #[allow(clippy::diverging_sub_expression)] // for unimplemented!() + async_trait
563
    #[async_trait]
564
    impl AbstractChannelFactory for FakeChannelFactory {
565
        type Channel = FakeChannel;
566

            
567
        type BuildSpec = tor_linkspec::OwnedChanTarget;
568

            
569
        async fn build_channel(
570
            &self,
571
            _target: &Self::BuildSpec,
572
            _reporter: BootstrapReporter,
573
        ) -> Result<FakeChannel> {
574
            unimplemented!()
575
        }
576
    }
577

            
578
    #[derive(Clone, Debug)]
579
    struct FakeChannel {
580
        ed_ident: Ed25519Identity,
581
        usable: bool,
582
        unused_duration: Option<u64>,
583
        params_update: Arc<Mutex<Option<Arc<ChannelPaddingInstructionsUpdates>>>>,
584
    }
585
    impl AbstractChannel for FakeChannel {
586
        fn is_usable(&self) -> bool {
587
            self.usable
588
        }
589
        fn duration_unused(&self) -> Option<Duration> {
590
            self.unused_duration.map(Duration::from_secs)
591
        }
592
        fn reparameterize(
593
            &self,
594
            update: Arc<ChannelPaddingInstructionsUpdates>,
595
        ) -> tor_proto::Result<()> {
596
            *self.params_update.lock().unwrap() = Some(update);
597
            Ok(())
598
        }
599
        fn engage_padding_activities(&self) {}
600
    }
601
    impl tor_linkspec::HasRelayIds for FakeChannel {
602
        fn identity(
603
            &self,
604
            key_type: tor_linkspec::RelayIdType,
605
        ) -> Option<tor_linkspec::RelayIdRef<'_>> {
606
            match key_type {
607
                tor_linkspec::RelayIdType::Ed25519 => Some((&self.ed_ident).into()),
608
                _ => None,
609
            }
610
        }
611
    }
612
    /// Get a fake ed25519 identity from the first byte of a string.
613
    fn str_to_ed(s: &str) -> Ed25519Identity {
614
        let byte = s.as_bytes()[0];
615
        [byte; 32].into()
616
    }
617
    fn ch(ident: &'static str) -> ChannelState<FakeChannel> {
618
        let channel = FakeChannel {
619
            ed_ident: str_to_ed(ident),
620
            usable: true,
621
            unused_duration: None,
622
            params_update: Arc::new(Mutex::new(None)),
623
        };
624
        ChannelState::Open(OpenEntry {
625
            channel,
626
            max_unused_duration: Duration::from_secs(180),
627
        })
628
    }
629
    fn ch_with_details(
630
        ident: &'static str,
631
        max_unused_duration: Duration,
632
        unused_duration: Option<u64>,
633
    ) -> ChannelState<FakeChannel> {
634
        let channel = FakeChannel {
635
            ed_ident: str_to_ed(ident),
636
            usable: true,
637
            unused_duration,
638
            params_update: Arc::new(Mutex::new(None)),
639
        };
640
        ChannelState::Open(OpenEntry {
641
            channel,
642
            max_unused_duration,
643
        })
644
    }
645
    fn closed(ident: &'static str) -> ChannelState<FakeChannel> {
646
        let channel = FakeChannel {
647
            ed_ident: str_to_ed(ident),
648
            usable: false,
649
            unused_duration: None,
650
            params_update: Arc::new(Mutex::new(None)),
651
        };
652
        ChannelState::Open(OpenEntry {
653
            channel,
654
            max_unused_duration: Duration::from_secs(180),
655
        })
656
    }
657

            
658
    #[test]
659
    fn rmv_unusable() -> Result<()> {
660
        let map = new_test_state();
661

            
662
        map.with_channels(|map| {
663
            map.insert(closed("machen"));
664
            map.insert(ch("feinen"));
665
            map.insert(closed("wir"));
666
            map.insert(ch("Fug"));
667
        })?;
668

            
669
        map.remove_unusable().unwrap();
670

            
671
        map.with_channels(|map| {
672
            assert!(map.by_id(&str_to_ed("m")).is_none());
673
            assert!(map.by_id(&str_to_ed("w")).is_none());
674
            assert!(map.by_id(&str_to_ed("f")).is_some());
675
            assert!(map.by_id(&str_to_ed("F")).is_some());
676
        })?;
677

            
678
        Ok(())
679
    }
680

            
681
    #[test]
682
    fn reparameterize_via_netdir() -> Result<()> {
683
        let map = new_test_state();
684

            
685
        // Set some non-default parameters so that we can tell when an update happens
686
        let _ = map
687
            .inner
688
            .lock()
689
            .unwrap()
690
            .channels_params
691
            .start_update()
692
            .padding_parameters(
693
                PaddingParametersBuilder::default()
694
                    .low(1234.into())
695
                    .build()
696
                    .unwrap(),
697
            )
698
            .finish();
699

            
700
        map.with_channels(|map| {
701
            map.insert(ch("track"));
702
        })?;
703

            
704
        let netdir = tor_netdir::testnet::construct_netdir()
705
            .unwrap_if_sufficient()
706
            .unwrap();
707
        let netdir = Arc::new(netdir);
708

            
709
        let with_ch = |f: &dyn Fn(&FakeChannel)| {
710
            let inner = map.inner.lock().unwrap();
711
            let ch = inner.channels.by_ed25519(&str_to_ed("t"));
712
            let ch = ch.unwrap().unwrap_open();
713
            f(ch);
714
        };
715

            
716
        eprintln!("-- process a default netdir, which should send an update --");
717
        map.reconfigure_general(None, None, netdir.clone()).unwrap();
718
        with_ch(&|ch| {
719
            assert_eq!(
720
                format!("{:?}", ch.params_update.lock().unwrap().take().unwrap()),
721
                // evade field visibility by (ab)using Debug impl
722
                "ChannelPaddingInstructionsUpdates { padding_enable: None, \
723
                    padding_parameters: Some(Parameters { \
724
                        low: IntegerMilliseconds { value: 1500 }, \
725
                        high: IntegerMilliseconds { value: 9500 } }), \
726
                    padding_negotiate: None }"
727
            );
728
        });
729
        eprintln!();
730

            
731
        eprintln!("-- process a default netdir again, which should *not* send an update --");
732
        map.reconfigure_general(None, None, netdir).unwrap();
733
        with_ch(&|ch| assert!(ch.params_update.lock().unwrap().is_none()));
734

            
735
        Ok(())
736
    }
737

            
738
    #[test]
739
    fn expire_channels() -> Result<()> {
740
        let map = new_test_state();
741

            
742
        // Channel that has been unused beyond max duration allowed is expired
743
        map.with_channels(|map| {
744
            map.insert(ch_with_details(
745
                "wello",
746
                Duration::from_secs(180),
747
                Some(181),
748
            ))
749
        })?;
750

            
751
        // Minimum value of max unused duration is 180 seconds
752
        assert_eq!(180, map.expire_channels().as_secs());
753
        map.with_channels(|map| {
754
            assert!(map.by_ed25519(&str_to_ed("w")).is_none());
755
        })?;
756

            
757
        let map = new_test_state();
758

            
759
        // Channel that has been unused for shorter than max unused duration
760
        map.with_channels(|map| {
761
            map.insert(ch_with_details(
762
                "wello",
763
                Duration::from_secs(180),
764
                Some(120),
765
            ));
766

            
767
            map.insert(ch_with_details(
768
                "yello",
769
                Duration::from_secs(180),
770
                Some(170),
771
            ));
772

            
773
            // Channel that has been unused beyond max duration allowed is expired
774
            map.insert(ch_with_details(
775
                "gello",
776
                Duration::from_secs(180),
777
                Some(181),
778
            ));
779

            
780
            // Closed channel should be retained
781
            map.insert(closed("hello"));
782
        })?;
783

            
784
        // Return duration until next channel expires
785
        assert_eq!(10, map.expire_channels().as_secs());
786
        map.with_channels(|map| {
787
            assert!(map.by_ed25519(&str_to_ed("w")).is_some());
788
            assert!(map.by_ed25519(&str_to_ed("y")).is_some());
789
            assert!(map.by_ed25519(&str_to_ed("h")).is_some());
790
            assert!(map.by_ed25519(&str_to_ed("g")).is_none());
791
        })?;
792
        Ok(())
793
    }
794
}