Skip to main content

tor_hsservice/
ipt_mgr.rs

1//! IPT Manager
2//!
3//! Maintains introduction points and publishes descriptors.
4//! Provides a stream of rendezvous requests.
5//!
6//! See [`IptManager::run_once`] for discussion of the implementation approach.
7
8use rand::RngExt;
9
10use crate::{internal_prelude::*, replay::OpenReplayLogError};
11
12use IptStatusStatus as ISS;
13use TrackedStatus as TS;
14use tor_relay_selection::{RelayExclusion, RelaySelector, RelayUsage};
15
16mod persist;
17pub(crate) use persist::IptStorageHandle;
18
19pub use crate::ipt_establish::IptError;
20
21/// Expiry time to put on an interim descriptor (IPT publication set Uncertain)
22///
23/// (Note that we use the same value in both cases, since it doesn't actually do
24/// much good to have a short expiration time. This expiration time only affects
25/// caches, and we can supersede an old descriptor just by publishing it. Thus,
26/// we pick a uniform publication time as done by the C tor implementation.)
27const IPT_PUBLISH_UNCERTAIN: Duration = Duration::from_secs(3 * 60 * 60); // 3 hours
28/// Expiry time to put on a final descriptor (IPT publication set Certain
29const IPT_PUBLISH_CERTAIN: Duration = IPT_PUBLISH_UNCERTAIN;
30
31//========== data structures ==========
32
33/// IPT Manager (for one hidden service)
34#[derive(Educe)]
35#[educe(Debug(bound))]
36pub(crate) struct IptManager<R, M> {
37    /// Immutable contents
38    imm: Immutable<R>,
39
40    /// Mutable state
41    state: State<R, M>,
42}
43
44/// Immutable contents of an IPT Manager
45///
46/// Contains things inherent to our identity, and
47/// handles to services that we'll be using.
48#[derive(Educe)]
49#[educe(Debug(bound))]
50pub(crate) struct Immutable<R> {
51    /// Runtime
52    #[educe(Debug(ignore))]
53    runtime: R,
54
55    /// Netdir provider
56    #[educe(Debug(ignore))]
57    dirprovider: Arc<dyn NetDirProvider>,
58
59    /// Nickname
60    nick: HsNickname,
61
62    /// Output MPSC for rendezvous requests
63    ///
64    /// Passed to IPT Establishers we create
65    output_rend_reqs: mpsc::Sender<RendRequest>,
66
67    /// Internal channel for updates from IPT Establishers (sender)
68    ///
69    /// When we make a new `IptEstablisher` we use this arrange for
70    /// its status updates to arrive, appropriately tagged, via `status_recv`
71    status_send: mpsc::Sender<(IptLocalId, IptStatus)>,
72
73    /// The key manager.
74    #[educe(Debug(ignore))]
75    keymgr: Arc<KeyMgr>,
76
77    /// Replay log directory
78    ///
79    /// Files are named after the (bare) IptLocalId
80    #[educe(Debug(ignore))]
81    replay_log_dir: tor_persist::state_dir::InstanceRawSubdir,
82
83    /// A sender for updating the status of the onion service.
84    #[educe(Debug(ignore))]
85    status_tx: IptMgrStatusSender,
86}
87
88/// State of an IPT Manager
89#[derive(Educe)]
90#[educe(Debug(bound))]
91pub(crate) struct State<R, M> {
92    /// Source of configuration updates
93    //
94    // TODO #1209 reject reconfigurations we can't cope with
95    // for example, state dir changes will go quite wrong
96    new_configs: watch::Receiver<Arc<OnionServiceConfig>>,
97
98    /// Last configuration update we received
99    ///
100    /// This is the snapshot of the config we are currently using.
101    /// (Doing it this way avoids running our algorithms
102    /// with a mixture of old and new config.)
103    current_config: Arc<OnionServiceConfig>,
104
105    /// Channel for updates from IPT Establishers (receiver)
106    ///
107    /// We arrange for all the updates to be multiplexed,
108    /// as that makes handling them easy in our event loop.
109    status_recv: mpsc::Receiver<(IptLocalId, IptStatus)>,
110
111    /// State: selected relays
112    ///
113    /// We append to this, and call `retain` on it,
114    /// so these are in chronological order of selection.
115    irelays: Vec<IptRelay>,
116
117    /// Did we fail to select a relay last time?
118    ///
119    /// This can only be caused (or triggered) by a busted netdir or config.
120    last_irelay_selection_outcome: Result<(), ()>,
121
122    /// Have we removed any IPTs but not yet cleaned up keys and logfiles?
123    #[educe(Debug(ignore))]
124    ipt_removal_cleanup_needed: bool,
125
126    /// Signal for us to shut down
127    shutdown: broadcast::Receiver<Void>,
128
129    /// The on-disk state storage handle.
130    #[educe(Debug(ignore))]
131    storage: IptStorageHandle,
132
133    /// Mockable state, normally [`Real`]
134    ///
135    /// This is in `State` so it can be passed mutably to tests,
136    /// even though the main code doesn't need `mut`
137    /// since `HsCircPool` is a service with interior mutability.
138    mockable: M,
139
140    /// Runtime (to placate compiler)
141    runtime: PhantomData<R>,
142}
143
144/// One selected relay, at which we are establishing (or relavantly advertised) IPTs
145struct IptRelay {
146    /// The actual relay
147    relay: RelayIds,
148
149    /// The retirement time we selected for this relay
150    planned_retirement: Instant,
151
152    /// IPTs at this relay
153    ///
154    /// At most one will have [`IsCurrent`].
155    ///
156    /// We append to this, and call `retain` on it,
157    /// so these are in chronological order of selection.
158    ipts: Vec<Ipt>,
159}
160
161/// One introduction point, representation in memory
162#[derive(Debug)]
163struct Ipt {
164    /// Local persistent identifier
165    lid: IptLocalId,
166
167    /// Handle for the establisher; we keep this here just for its `Drop` action
168    establisher: Box<ErasedIptEstablisher>,
169
170    /// `KS_hs_ipt_sid`, `KP_hs_ipt_sid`
171    ///
172    /// This is an `Arc` because:
173    ///  * The manager needs a copy so that it can save it to disk.
174    ///  * The establisher needs a copy to actually use.
175    ///  * The underlying secret key type is not `Clone`.
176    k_sid: Arc<HsIntroPtSessionIdKeypair>,
177
178    /// `KS_hss_ntor`, `KP_hss_ntor`
179    k_hss_ntor: Arc<HsSvcNtorKeypair>,
180
181    /// Last information about how it's doing including timing info
182    status_last: TrackedStatus,
183
184    /// Until when ought we to try to maintain it
185    ///
186    /// For introduction points we are publishing,
187    /// this is a copy of the value set by the publisher
188    /// in the `IptSet` we share with the publisher,
189    ///
190    /// (`None` means the IPT has not been advertised at all yet.)
191    ///
192    /// We must duplicate the information because:
193    ///
194    ///  * We can't have it just live in the shared `IptSet`
195    ///    because we need to retain it for no-longer-being published IPTs.
196    ///
197    ///  * We can't have it just live here because the publisher needs to update it.
198    ///
199    /// (An alternative would be to more seriously entangle the manager and publisher.)
200    last_descriptor_expiry_including_slop: Option<Instant>,
201
202    /// Is this IPT current - should we include it in descriptors ?
203    ///
204    /// `None` might mean:
205    ///  * WantsToRetire
206    ///  * We have >N IPTs and we have been using this IPT so long we want to rotate it out
207    ///    (the [`IptRelay`] has reached its `planned_retirement` time)
208    ///  * The IPT has wrong parameters of some kind, and needs to be replaced
209    ///    (Eg, we set it up with the wrong DOS_PARAMS extension)
210    is_current: Option<IsCurrent>,
211}
212
213/// Last information from establisher about an IPT, with timing info added by us
214#[derive(Debug)]
215enum TrackedStatus {
216    /// Corresponds to [`IptStatusStatus::Faulty`]
217    Faulty {
218        /// When we were first told this started to establish, if we know it
219        ///
220        /// This might be an early estimate, which would give an overestimate
221        /// of the establishment time, which is fine.
222        /// Or it might be `Err` meaning we don't know.
223        started: Result<Instant, ()>,
224
225        /// The error, if any.
226        error: Option<IptError>,
227    },
228
229    /// Corresponds to [`IptStatusStatus::Establishing`]
230    Establishing {
231        /// When we were told we started to establish, for calculating `time_to_establish`
232        started: Instant,
233    },
234
235    /// Corresponds to [`IptStatusStatus::Good`]
236    Good {
237        /// How long it took to establish (if we could determine that information)
238        ///
239        /// Can only be `Err` in strange situations.
240        time_to_establish: Result<Duration, ()>,
241
242        /// Details, from the Establisher
243        details: ipt_establish::GoodIptDetails,
244    },
245}
246
247/// Token indicating that this introduction point is current (not Retiring)
248#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
249struct IsCurrent;
250
251//---------- related to mockability ----------
252
253/// Type-erased version of `Box<IptEstablisher>`
254///
255/// The real type is `M::IptEstablisher`.
256/// We use `Box<dyn Any>` to avoid propagating the `M` type parameter to `Ipt` etc.
257type ErasedIptEstablisher = dyn Any + Send + Sync + 'static;
258
259/// Mockable state in an IPT Manager - real version
260#[derive(Educe)]
261#[educe(Debug)]
262pub(crate) struct Real<R: Runtime> {
263    /// Circuit pool for circuits we need to make
264    ///
265    /// Passed to the each new Establisher
266    #[educe(Debug(ignore))]
267    pub(crate) circ_pool: Arc<HsCircPool<R>>,
268}
269
270//---------- errors ----------
271
272/// An error that happened while trying to select a relay
273///
274/// Used only within the IPT manager.
275/// Can only be caused by bad netdir or maybe bad config.
276#[derive(Debug, Error)]
277enum ChooseIptError {
278    /// Bad or insufficient netdir
279    #[error("bad or insufficient netdir")]
280    NetDir(#[from] tor_netdir::Error),
281    /// Too few suitable relays
282    #[error("too few suitable relays")]
283    TooFewUsableRelays,
284    /// Time overflow
285    #[error("time overflow (system clock set wrong?)")]
286    TimeOverflow,
287    /// Internal error
288    #[error("internal error")]
289    Bug(#[from] Bug),
290}
291
292/// An error that happened while trying to crate an IPT (at a selected relay)
293///
294/// Used only within the IPT manager.
295#[derive(Clone, Debug, Error)]
296pub(crate) enum CreateIptError {
297    /// Fatal error
298    #[error("fatal error")]
299    Fatal(#[from] FatalError),
300
301    /// Error accessing keystore
302    #[error("problems with keystores")]
303    Keystore(#[from] tor_keymgr::Error),
304
305    /// Error opening the intro request replay log
306    #[error(transparent)]
307    OpenReplayLog(#[from] OpenReplayLogError),
308}
309
310//========== Relays we've chosen, and IPTs ==========
311
312impl IptRelay {
313    /// Get a reference to this IPT relay's current intro point state (if any)
314    ///
315    /// `None` means this IPT has no current introduction points.
316    /// That might be, briefly, because a new intro point needs to be created;
317    /// or it might be because we are retiring the relay.
318    fn current_ipt(&self) -> Option<&Ipt> {
319        self.ipts
320            .iter()
321            .find(|ipt| ipt.is_current == Some(IsCurrent))
322    }
323
324    /// Get a mutable reference to this IPT relay's current intro point state (if any)
325    fn current_ipt_mut(&mut self) -> Option<&mut Ipt> {
326        self.ipts
327            .iter_mut()
328            .find(|ipt| ipt.is_current == Some(IsCurrent))
329    }
330
331    /// Should this IPT Relay be retired ?
332    ///
333    /// This is determined by our IPT relay rotation time.
334    fn should_retire(&self, now: &TrackingNow) -> bool {
335        now > &self.planned_retirement
336    }
337
338    /// Make a new introduction point at this relay
339    ///
340    /// It becomes the current IPT.
341    fn make_new_ipt<R: Runtime, M: Mockable<R>>(
342        &mut self,
343        imm: &Immutable<R>,
344        new_configs: &watch::Receiver<Arc<OnionServiceConfig>>,
345        mockable: &mut M,
346    ) -> Result<(), CreateIptError> {
347        let lid: IptLocalId = mockable.thread_rng().random();
348
349        let ipt = Ipt::start_establisher(
350            imm,
351            new_configs,
352            mockable,
353            &self.relay,
354            lid,
355            Some(IsCurrent),
356            None::<IptExpectExistingKeys>,
357            // None is precisely right: the descriptor hasn't been published.
358            PromiseLastDescriptorExpiryNoneIsGood {},
359        )?;
360
361        self.ipts.push(ipt);
362
363        Ok(())
364    }
365}
366
367/// Token, representing promise by caller of `start_establisher`
368///
369/// Caller who makes one of these structs promises that it is OK for `start_establisher`
370/// to set `last_descriptor_expiry_including_slop` to `None`.
371struct PromiseLastDescriptorExpiryNoneIsGood {}
372
373/// Token telling [`Ipt::start_establisher`] to expect existing keys in the keystore
374#[derive(Debug, Clone, Copy)]
375struct IptExpectExistingKeys;
376
377impl Ipt {
378    /// Start a new IPT establisher, and create and return an `Ipt`
379    #[allow(clippy::too_many_arguments)] // There's only two call sites
380    fn start_establisher<R: Runtime, M: Mockable<R>>(
381        imm: &Immutable<R>,
382        new_configs: &watch::Receiver<Arc<OnionServiceConfig>>,
383        mockable: &mut M,
384        relay: &RelayIds,
385        lid: IptLocalId,
386        is_current: Option<IsCurrent>,
387        expect_existing_keys: Option<IptExpectExistingKeys>,
388        _: PromiseLastDescriptorExpiryNoneIsGood,
389    ) -> Result<Ipt, CreateIptError> {
390        let mut rng = tor_llcrypto::rng::CautiousRng;
391
392        /// Load (from disk) or generate an IPT key with role IptKeyRole::$role
393        ///
394        /// Ideally this would be a closure, but it has to be generic over the
395        /// returned key type.  So it's a macro.  (A proper function would have
396        /// many type parameters and arguments and be quite annoying.)
397        macro_rules! get_or_gen_key { { $Keypair:ty, $role:ident } => { (||{
398            let spec = IptKeySpecifier {
399                nick: imm.nick.clone(),
400                role: IptKeyRole::$role,
401                lid,
402            };
403            // Our desired behaviour:
404            //  expect_existing_keys == None
405            //     The keys shouldn't exist.  Generate and insert.
406            //     If they do exist then things are badly messed up
407            //     (we're creating a new IPT with a fres lid).
408            //     So, then, crash.
409            //  expect_existing_keys == Some(IptExpectExistingKeys)
410            //     The key is supposed to exist.  Load them.
411            //     We ought to have stored them before storing in our on-disk records that
412            //     this IPT exists.  But this could happen due to file deletion or something.
413            //     And we could recover by creating fresh keys, although maybe some clients
414            //     would find the previous keys in old descriptors.
415            //     So if the keys are missing, make and store new ones, logging an error msg.
416            let k: Option<$Keypair> = imm.keymgr.get(&spec)?;
417            let arti_path = || {
418                spec
419                    .arti_path()
420                    .map_err(|e| {
421                        CreateIptError::Fatal(
422                            into_internal!("bad ArtiPath from IPT key spec")(e).into()
423                        )
424                    })
425            };
426            match (expect_existing_keys, k) {
427                (None, None) => { }
428                (Some(_), Some(k)) => return Ok(Arc::new(k)),
429                (None, Some(_)) => {
430                    return Err(FatalError::IptKeysFoundUnexpectedly(arti_path()?).into())
431                },
432                (Some(_), None) => {
433                    error!("bug: HS service {} missing previous key {:?}. Regenerating.",
434                           &imm.nick, arti_path()?);
435                }
436             }
437
438            let res = imm.keymgr.generate::<$Keypair>(
439                &spec,
440                tor_keymgr::KeystoreSelector::Primary,
441                &mut rng,
442                false, /* overwrite */
443            );
444
445            match res {
446                Ok(k) => Ok::<_, CreateIptError>(Arc::new(k)),
447                Err(tor_keymgr::Error::KeyAlreadyExists) => {
448                    Err(FatalError::KeystoreRace { action: "generate", path: arti_path()? }.into() )
449                },
450                Err(e) => Err(e.into()),
451            }
452        })() } }
453
454        let k_hss_ntor = get_or_gen_key!(HsSvcNtorKeypair, KHssNtor)?;
455        let k_sid = get_or_gen_key!(HsIntroPtSessionIdKeypair, KSid)?;
456
457        // we'll treat it as Establishing until we find otherwise
458        let status_last = TS::Establishing {
459            started: imm.runtime.now(),
460        };
461
462        // TODO #1186 Support ephemeral services (without persistent replay log)
463        let replay_log = IptReplayLog::new_logged(&imm.replay_log_dir, &lid)?;
464
465        let params = IptParameters {
466            replay_log,
467            config_rx: new_configs.clone(),
468            netdir_provider: imm.dirprovider.clone(),
469            introduce_tx: imm.output_rend_reqs.clone(),
470            lid,
471            target: relay.clone(),
472            k_sid: k_sid.clone(),
473            k_ntor: Arc::clone(&k_hss_ntor),
474            accepting_requests: ipt_establish::RequestDisposition::NotAdvertised,
475        };
476        let (establisher, mut watch_rx) = mockable.make_new_ipt(imm, params)?;
477
478        // This task will shut down when self.establisher is dropped, causing
479        // watch_tx to close.
480        imm.runtime
481            .spawn({
482                let mut status_send = imm.status_send.clone();
483                async move {
484                    loop {
485                        let Some(status) = watch_rx.next().await else {
486                            trace!("HS service IPT status task: establisher went away");
487                            break;
488                        };
489                        match status_send.send((lid, status)).await {
490                            Ok(()) => {}
491                            Err::<_, mpsc::SendError>(e) => {
492                                // Not using trace_report because SendError isn't HasKind
493                                trace!("HS service IPT status task: manager went away: {e}");
494                                break;
495                            }
496                        }
497                    }
498                }
499            })
500            .map_err(|cause| FatalError::Spawn {
501                spawning: "IPT establisher watch status task",
502                cause: cause.into(),
503            })?;
504
505        let ipt = Ipt {
506            lid,
507            establisher: Box::new(establisher),
508            k_hss_ntor,
509            k_sid,
510            status_last,
511            is_current,
512            last_descriptor_expiry_including_slop: None,
513        };
514
515        debug!(
516            "Hs service {}: {lid:?} establishing {} IPT at relay {}",
517            &imm.nick,
518            match expect_existing_keys {
519                None => "new",
520                Some(_) => "previous",
521            },
522            &relay,
523        );
524
525        Ok(ipt)
526    }
527
528    /// Returns `true` if this IPT has status Good (and should perhaps be published)
529    fn is_good(&self) -> bool {
530        match self.status_last {
531            TS::Good { .. } => true,
532            TS::Establishing { .. } | TS::Faulty { .. } => false,
533        }
534    }
535
536    /// Returns the error, if any, we are currently encountering at this IPT.
537    fn error(&self) -> Option<&IptError> {
538        match &self.status_last {
539            TS::Good { .. } | TS::Establishing { .. } => None,
540            TS::Faulty { error, .. } => error.as_ref(),
541        }
542    }
543
544    /// Construct the information needed by the publisher for this intro point
545    fn for_publish(&self, details: &ipt_establish::GoodIptDetails) -> Result<ipt_set::Ipt, Bug> {
546        let k_sid: &ed25519::Keypair = (*self.k_sid).as_ref();
547        tor_netdoc::doc::hsdesc::IntroPointDesc::builder()
548            .link_specifiers(details.link_specifiers.clone())
549            .ipt_kp_ntor(details.ipt_kp_ntor)
550            .kp_hs_ipt_sid(k_sid.verifying_key().into())
551            .kp_hss_ntor(self.k_hss_ntor.public().clone())
552            .build()
553            .map_err(into_internal!("failed to construct IntroPointDesc"))
554    }
555}
556
557impl HasKind for ChooseIptError {
558    fn kind(&self) -> ErrorKind {
559        use ChooseIptError as E;
560        use ErrorKind as EK;
561        match self {
562            E::NetDir(e) => e.kind(),
563            E::TooFewUsableRelays => EK::TorDirectoryUnusable,
564            E::TimeOverflow => EK::ClockSkew,
565            E::Bug(e) => e.kind(),
566        }
567    }
568}
569
570// This is somewhat abbreviated but it is legible and enough for most purposes.
571impl Debug for IptRelay {
572    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
573        writeln!(f, "IptRelay {}", self.relay)?;
574        write!(
575            f,
576            "          planned_retirement: {:?}",
577            self.planned_retirement
578        )?;
579        for ipt in &self.ipts {
580            write!(
581                f,
582                "\n          ipt {} {} {:?} ldeis={:?}",
583                match ipt.is_current {
584                    Some(IsCurrent) => "cur",
585                    None => "old",
586                },
587                &ipt.lid,
588                &ipt.status_last,
589                &ipt.last_descriptor_expiry_including_slop,
590            )?;
591        }
592        Ok(())
593    }
594}
595
596//========== impls on IptManager and State ==========
597
598impl<R: Runtime, M: Mockable<R>> IptManager<R, M> {
599    //
600    //---------- constructor and setup ----------
601
602    /// Create a new IptManager
603    #[allow(clippy::too_many_arguments)] // this is an internal function with 1 call site
604    pub(crate) fn new(
605        runtime: R,
606        dirprovider: Arc<dyn NetDirProvider>,
607        nick: HsNickname,
608        config: watch::Receiver<Arc<OnionServiceConfig>>,
609        output_rend_reqs: mpsc::Sender<RendRequest>,
610        shutdown: broadcast::Receiver<Void>,
611        state_handle: &tor_persist::state_dir::InstanceStateHandle,
612        mockable: M,
613        keymgr: Arc<KeyMgr>,
614        status_tx: IptMgrStatusSender,
615    ) -> Result<Self, StartupError> {
616        let irelays = vec![]; // See TODO near persist::load call, in launch_background_tasks
617
618        // We don't need buffering; since this is written to by dedicated tasks which
619        // are reading watches.
620        //
621        // Internally-generated status updates (hopefully rate limited?), no need for mq.
622        let (status_send, status_recv) = mpsc_channel_no_memquota(0);
623
624        let storage = state_handle
625            .storage_handle("ipts")
626            .map_err(StartupError::StateDirectoryInaccessible)?;
627
628        let replay_log_dir = state_handle
629            .raw_subdir("iptreplay")
630            .map_err(StartupError::StateDirectoryInaccessible)?;
631
632        let imm = Immutable {
633            runtime,
634            dirprovider,
635            nick,
636            status_send,
637            output_rend_reqs,
638            keymgr,
639            replay_log_dir,
640            status_tx,
641        };
642        let current_config = config.borrow().clone();
643
644        let state = State {
645            current_config,
646            new_configs: config,
647            status_recv,
648            storage,
649            mockable,
650            shutdown,
651            irelays,
652            last_irelay_selection_outcome: Ok(()),
653            ipt_removal_cleanup_needed: false,
654            runtime: PhantomData,
655        };
656        let mgr = IptManager { imm, state };
657
658        Ok(mgr)
659    }
660
661    /// Send the IPT manager off to run and establish intro points
662    pub(crate) fn launch_background_tasks(
663        mut self,
664        mut publisher: IptsManagerView,
665    ) -> Result<(), StartupError> {
666        // TODO maybe this should be done in new(), so we don't have this dummy irelays
667        // but then new() would need the IptsManagerView
668        assert!(self.state.irelays.is_empty());
669        self.state.irelays = persist::load(
670            &self.imm,
671            &self.state.storage,
672            &self.state.new_configs,
673            &mut self.state.mockable,
674            &publisher.borrow_for_read(),
675        )?;
676
677        // Now that we've populated `irelays` and its `ipts` from the on-disk state,
678        // we should check any leftover disk files from previous runs.  Make a note.
679        self.state.ipt_removal_cleanup_needed = true;
680
681        let runtime = self.imm.runtime.clone();
682
683        self.imm.status_tx.send(IptMgrState::Bootstrapping, None);
684
685        // This task will shut down when the RunningOnionService is dropped, causing
686        // self.state.shutdown to become ready.
687        runtime
688            .spawn(self.main_loop_task(publisher))
689            .map_err(|cause| StartupError::Spawn {
690                spawning: "ipt manager",
691                cause: cause.into(),
692            })?;
693        Ok(())
694    }
695
696    //---------- internal utility and helper methods ----------
697
698    /// Iterate over *all* the IPTs we know about
699    ///
700    /// Yields each `IptRelay` at most once.
701    fn all_ipts(&self) -> impl Iterator<Item = (&IptRelay, &Ipt)> {
702        self.state
703            .irelays
704            .iter()
705            .flat_map(|ir| ir.ipts.iter().map(move |ipt| (ir, ipt)))
706    }
707
708    /// Iterate over the *current* IPTs
709    ///
710    /// Yields each `IptRelay` at most once.
711    fn current_ipts(&self) -> impl Iterator<Item = (&IptRelay, &Ipt)> {
712        self.state
713            .irelays
714            .iter()
715            .filter_map(|ir| Some((ir, ir.current_ipt()?)))
716    }
717
718    /// Iterate over the *current* IPTs in `Good` state
719    fn good_ipts(&self) -> impl Iterator<Item = (&IptRelay, &Ipt)> {
720        self.current_ipts().filter(|(_ir, ipt)| ipt.is_good())
721    }
722
723    /// Iterate over the current IPT errors.
724    ///
725    /// Used when reporting our state as [`Recovering`](crate::status::State::Recovering).
726    fn ipt_errors(&self) -> impl Iterator<Item = &IptError> {
727        self.all_ipts().filter_map(|(_ir, ipt)| ipt.error())
728    }
729
730    /// Target number of intro points
731    pub(crate) fn target_n_intro_points(&self) -> usize {
732        self.state.current_config.num_intro_points.into()
733    }
734
735    /// Maximum number of concurrent intro point relays
736    pub(crate) fn max_n_intro_relays(&self) -> usize {
737        let params = self.imm.dirprovider.params();
738        let num_extra = (*params).as_ref().hs_intro_num_extra_intropoints.get() as usize;
739        self.target_n_intro_points() + num_extra
740    }
741
742    //---------- main implementation logic ----------
743
744    /// Make some progress, if possible, and say when to wake up again
745    ///
746    /// Examines the current state and attempts to improve it.
747    ///
748    /// If `idempotently_progress_things_now` makes any changes,
749    /// it will return `None`.
750    /// It should then be called again immediately.
751    ///
752    /// Otherwise, it returns the time in the future when further work ought to be done:
753    /// i.e., the time of the earliest timeout or planned future state change -
754    /// as a [`TrackingNow`].
755    ///
756    /// In that case, the caller must call `compute_iptsetstatus_publish`,
757    /// since the IPT set etc. may have changed.
758    ///
759    /// ### Goals and algorithms
760    ///
761    /// We attempt to maintain a pool of N established and verified IPTs,
762    /// at N IPT Relays.
763    ///
764    /// When we have fewer than N IPT Relays
765    /// that have `Establishing` or `Good` IPTs (see below)
766    /// and fewer than k*N IPT Relays overall,
767    /// we choose a new IPT Relay at random from the consensus
768    /// and try to establish an IPT on it.
769    ///
770    /// (Rationale for the k*N limit:
771    /// we do want to try to replace faulty IPTs, but
772    /// we don't want an attacker to be able to provoke us into
773    /// rapidly churning through IPT candidates.)
774    ///
775    /// When we select a new IPT Relay, we randomly choose a planned replacement time,
776    /// after which it becomes `Retiring`.
777    ///
778    /// Additionally, any IPT becomes `Retiring`
779    /// after it has been used for a certain number of introductions
780    /// (c.f. C Tor `#define INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS 16384`.)
781    /// When this happens we retain the IPT Relay,
782    /// and make new parameters to make a new IPT at the same Relay.
783    ///
784    /// An IPT is removed from our records, and we give up on it,
785    /// when it is no longer `Good` or `Establishing`
786    /// and all descriptors that mentioned it have expired.
787    ///
788    /// (Until all published descriptors mentioning an IPT expire,
789    /// we consider ourselves bound by those previously-published descriptors,
790    /// and try to maintain the IPT.
791    /// TODO: Allegedly this is unnecessary, but I don't see how it could be.)
792    ///
793    /// ### Performance
794    ///
795    /// This function is at worst O(N) where N is the number of IPTs.
796    /// When handling state changes relating to a particular IPT (or IPT relay)
797    /// it needs at most O(1) calls to progress that one IPT to its proper new state.
798    ///
799    /// See the performance note on [`run_once()`](Self::run_once).
800    #[allow(clippy::redundant_closure_call)]
801    fn idempotently_progress_things_now(&mut self) -> Result<Option<TrackingNow>, FatalError> {
802        /// Return value which means "we changed something, please run me again"
803        ///
804        /// In each case, if we make any changes which indicate we might
805        /// want to restart, , we `return CONTINUE`, and
806        /// our caller will just call us again.
807        ///
808        /// This approach simplifies the logic: everything here is idempotent.
809        /// (It does mean the algorithm can be quadratic in the number of intro points,
810        /// but that number is reasonably small for a modern computer and the constant
811        /// factor is small too.)
812        const CONTINUE: Result<Option<TrackingNow>, FatalError> = Ok(None);
813
814        // This tracks everything we compare it to, using interior mutability,
815        // so that if there is no work to do and no timeouts have expired,
816        // we know when we will want to wake up.
817        let now = TrackingNow::now(&self.imm.runtime);
818
819        // ---------- collect garbage ----------
820
821        // Rotate out an old IPT(s)
822        for ir in &mut self.state.irelays {
823            if ir.should_retire(&now) {
824                if let Some(ipt) = ir.current_ipt_mut() {
825                    ipt.is_current = None;
826                    return CONTINUE;
827                }
828            }
829        }
830
831        // Forget old IPTs (after the last descriptor mentioning them has expired)
832        for ir in &mut self.state.irelays {
833            // When we drop the Ipt we drop the IptEstablisher, withdrawing the intro point
834            ir.ipts.retain(|ipt| {
835                let keep = ipt.is_current.is_some()
836                    || match ipt.last_descriptor_expiry_including_slop {
837                        None => false,
838                        Some(last) => now < last,
839                    };
840                // This is the only place in the manager where an IPT is dropped,
841                // other than when the whole service is dropped.
842                self.state.ipt_removal_cleanup_needed |= !keep;
843                keep
844            });
845            // No need to return CONTINUE, since there is no other future work implied
846            // by discarding a non-current IPT.
847        }
848
849        // Forget retired IPT relays (all their IPTs are gone)
850        self.state
851            .irelays
852            .retain(|ir| !(ir.should_retire(&now) && ir.ipts.is_empty()));
853        // If we deleted relays, we might want to select new ones.  That happens below.
854
855        // ---------- make progress ----------
856        //
857        // Consider selecting new relays and setting up new IPTs.
858
859        // Create new IPTs at already-chosen relays
860        for ir in &mut self.state.irelays {
861            if !ir.should_retire(&now) && ir.current_ipt_mut().is_none() {
862                // We don't have a current IPT at this relay, but we should.
863                match ir.make_new_ipt(&self.imm, &self.state.new_configs, &mut self.state.mockable)
864                {
865                    Ok(()) => return CONTINUE,
866                    Err(CreateIptError::Fatal(fatal)) => return Err(fatal),
867                    Err(
868                        e @ (CreateIptError::Keystore(_) | CreateIptError::OpenReplayLog { .. }),
869                    ) => {
870                        error_report!(e, "HS {}: failed to prepare new IPT", &self.imm.nick);
871                        // Let's not try any more of this.
872                        // We'll run the rest of our "make progress" algorithms,
873                        // presenting them with possibly-suboptimal state.  That's fine.
874                        // At some point we'll be poked to run again and then we'll retry.
875                        /// Retry no later than this:
876                        const STORAGE_RETRY: Duration = Duration::from_secs(60);
877                        now.update(STORAGE_RETRY);
878                        break;
879                    }
880                }
881            }
882        }
883
884        // Consider choosing a new IPT relay
885        {
886            // block {} prevents use of `n_good_ish_relays` for other (wrong) purposes
887
888            // We optimistically count an Establishing IPT as good-ish;
889            // specifically, for the purposes of deciding whether to select a new
890            // relay because we don't have enough good-looking ones.
891            let n_good_ish_relays = self
892                .current_ipts()
893                .filter(|(_ir, ipt)| match ipt.status_last {
894                    TS::Good { .. } | TS::Establishing { .. } => true,
895                    TS::Faulty { .. } => false,
896                })
897                .count();
898
899            #[allow(clippy::unused_unit, clippy::semicolon_if_nothing_returned)] // in map_err
900            if n_good_ish_relays < self.target_n_intro_points()
901                && self.state.irelays.len() < self.max_n_intro_relays()
902                && self.state.last_irelay_selection_outcome.is_ok()
903            {
904                self.state.last_irelay_selection_outcome = self
905                    .state
906                    .choose_new_ipt_relay(&self.imm, now.instant().get_now_untracked())
907                    .map_err(|error| {
908                        /// Call $report! with the message.
909                        // The macros are annoying and want a cost argument.
910                        macro_rules! report { { $report:ident } => {
911                            $report!(
912                                error,
913                                "HS service {} failed to select IPT relay",
914                                &self.imm.nick,
915                            )
916                        }}
917                        use ChooseIptError as E;
918                        match &error {
919                            E::NetDir(_) => report!(info_report),
920                            _ => report!(error_report),
921                        };
922                        ()
923                    });
924                return CONTINUE;
925            }
926        }
927
928        //---------- caller (run_once) will update publisher, and wait ----------
929
930        Ok(Some(now))
931    }
932
933    /// Import publisher's updates to latest descriptor expiry times
934    ///
935    /// Copies the `last_descriptor_expiry_including_slop` field
936    /// from each ipt in `publish_set` to the corresponding ipt in `self`.
937    ///
938    /// ### Performance
939    ///
940    /// This function is at worst O(N) where N is the number of IPTs.
941    /// See the performance note on [`run_once()`](Self::run_once).
942    fn import_new_expiry_times(irelays: &mut [IptRelay], publish_set: &PublishIptSet) {
943        // Every entry in the PublishIptSet ought to correspond to an ipt in self.
944        //
945        // If there are IPTs in publish_set.last_descriptor_expiry_including_slop
946        // that aren't in self, those are IPTs that we know were published,
947        // but can't establish since we have forgotten their details.
948        //
949        // We are not supposed to allow that to happen:
950        // we save IPTs to disk before we allow them to be published.
951        //
952        // (This invariant is across two data structures:
953        // `ipt_mgr::State` (specifically, `Ipt`) which is modified only here,
954        // and `ipt_set::PublishIptSet` which is shared with the publisher.
955        // See the comments in PublishIptSet.)
956
957        let all_ours = irelays.iter_mut().flat_map(|ir| ir.ipts.iter_mut());
958
959        for ours in all_ours {
960            if let Some(theirs) = publish_set
961                .last_descriptor_expiry_including_slop
962                .get(&ours.lid)
963            {
964                ours.last_descriptor_expiry_including_slop = Some(*theirs);
965            }
966        }
967    }
968
969    /// Expire old entries in publish_set.last_descriptor_expiry_including_slop
970    ///
971    /// Deletes entries where `now` > `last_descriptor_expiry_including_slop`,
972    /// ie, entries where the publication's validity time has expired,
973    /// meaning we don't need to maintain that IPT any more,
974    /// at least, not just because we've published it.
975    ///
976    /// We may expire even entries for IPTs that we, the manager, still want to maintain.
977    /// That's fine: this is (just) the information about what we have previously published.
978    ///
979    /// ### Performance
980    ///
981    /// This function is at worst O(N) where N is the number of IPTs.
982    /// See the performance note on [`run_once()`](Self::run_once).
983    fn expire_old_expiry_times(&self, publish_set: &mut PublishIptSet, now: &TrackingNow) {
984        // We don't want to bother waking up just to expire things,
985        // so use an untracked comparison.
986        let now = now.instant().get_now_untracked();
987
988        publish_set
989            .last_descriptor_expiry_including_slop
990            .retain(|_lid, expiry| *expiry <= now);
991    }
992
993    /// Compute the IPT set to publish, and update the data shared with the publisher
994    ///
995    /// `now` is current time and also the earliest wakeup,
996    /// which we are in the process of planning.
997    /// The noted earliest wakeup can be updated by this function,
998    /// for example, with a future time at which the IPT set ought to be published
999    /// (eg, the status goes from Unknown to Uncertain).
1000    ///
1001    /// ## IPT sets and lifetimes
1002    ///
1003    /// We remember every IPT we have published that is still valid.
1004    ///
1005    /// At each point in time we have an idea of set of IPTs we want to publish.
1006    /// The possibilities are:
1007    ///
1008    ///  * `Certain`:
1009    ///    We are sure of which IPTs we want to publish.
1010    ///    We try to do so, talking to hsdirs as necessary,
1011    ///    updating any existing information.
1012    ///    (We also republish to an hsdir if its descriptor will expire soon,
1013    ///    or we haven't published there since Arti was restarted.)
1014    ///
1015    ///  * `Unknown`:
1016    ///    We have no idea which IPTs to publish.
1017    ///    We leave whatever is on the hsdirs as-is.
1018    ///
1019    ///  * `Uncertain`:
1020    ///    We have some IPTs we could publish,
1021    ///    but we're not confident about them.
1022    ///    We publish these to a particular hsdir if:
1023    ///     - our last-published descriptor has expired
1024    ///     - or it will expire soon
1025    ///     - or if we haven't published since Arti was restarted.
1026    ///
1027    /// The idea of what to publish is calculated as follows:
1028    ///
1029    ///  * If we have at least N `Good` IPTs: `Certain`.
1030    ///    (We publish the "best" N IPTs for some definition of "best".
1031    ///    TODO: should we use the fault count?  recency?)
1032    ///
1033    ///  * Unless we have at least one `Good` IPT: `Unknown`.
1034    ///
1035    ///  * Otherwise: if there are IPTs in `Establishing`,
1036    ///    and they have been in `Establishing` only a short time \[1\]:
1037    ///    `Unknown`; otherwise `Uncertain`.
1038    ///
1039    /// The effect is that we delay publishing an initial descriptor
1040    /// by at most 1x the fastest IPT setup time,
1041    /// at most doubling the initial setup time.
1042    ///
1043    /// Each update to the IPT set that isn't `Unknown` comes with a
1044    /// proposed descriptor expiry time,
1045    /// which is used if the descriptor is to be actually published.
1046    /// The proposed descriptor lifetime for `Uncertain`
1047    /// is the minimum (30 minutes).
1048    /// Otherwise, we double the lifetime each time,
1049    /// unless any IPT in the previous descriptor was declared `Faulty`,
1050    /// in which case we reset it back to the minimum.
1051    /// TODO: Perhaps we should just pick fixed short and long lifetimes instead,
1052    /// to limit distinguishability.
1053    ///
1054    /// (Rationale: if IPTs are regularly misbehaving,
1055    /// we should be cautious and limit our exposure to the damage.)
1056    ///
1057    /// \[1\] NOTE: We wait a "short time" between establishing our first IPT,
1058    /// and publishing an incomplete (<N) descriptor -
1059    /// this is a compromise between
1060    /// availability (publishing as soon as we have any working IPT)
1061    /// and
1062    /// exposure and hsdir load
1063    /// (which would suggest publishing only when our IPT set is stable).
1064    /// One possible strategy is to wait as long again
1065    /// as the time it took to establish our first IPT.
1066    /// Another is to somehow use our circuit timing estimator.
1067    ///
1068    /// ### Performance
1069    ///
1070    /// This function is at worst O(N) where N is the number of IPTs.
1071    /// See the performance note on [`run_once()`](Self::run_once).
1072    #[allow(clippy::unnecessary_wraps)] // for regularity
1073    #[allow(clippy::cognitive_complexity)] // this function is in fact largely linear
1074    fn compute_iptsetstatus_publish(
1075        &mut self,
1076        now: &TrackingNow,
1077        publish_set: &mut PublishIptSet,
1078    ) -> Result<(), IptStoreError> {
1079        //---------- tell the publisher what to announce ----------
1080
1081        let very_recently: Option<(TrackingInstantOffsetNow, Duration)> = (|| {
1082            // on time overflow, don't treat any as started establishing very recently
1083
1084            let fastest_good_establish_time = self
1085                .current_ipts()
1086                .filter_map(|(_ir, ipt)| match ipt.status_last {
1087                    TS::Good {
1088                        time_to_establish, ..
1089                    } => Some(time_to_establish.ok()?),
1090                    TS::Establishing { .. } | TS::Faulty { .. } => None,
1091                })
1092                .min()?;
1093
1094            // Rationale:
1095            // we could use circuit timings etc., but arguably the actual time to establish
1096            // our fastest IPT is a better estimator here (and we want an optimistic,
1097            // rather than pessimistic estimate).
1098            //
1099            // This algorithm has potential to publish too early and frequently,
1100            // but our overall rate-limiting should keep it from getting out of hand.
1101            //
1102            // TODO: We might want to make this "1" tuneable, and/or tune the
1103            // algorithm as a whole based on experience.
1104            let wait_more = fastest_good_establish_time * 1;
1105            let very_recently = fastest_good_establish_time.checked_add(wait_more)?;
1106
1107            let very_recently = now.checked_sub(very_recently)?;
1108            Some((very_recently, wait_more))
1109        })();
1110
1111        let started_establishing_very_recently = || {
1112            let (very_recently, wait_more) = very_recently?;
1113            let lid = self
1114                .current_ipts()
1115                .filter_map(|(_ir, ipt)| {
1116                    let started = match ipt.status_last {
1117                        TS::Establishing { started } => Some(started),
1118                        TS::Good { .. } | TS::Faulty { .. } => None,
1119                    }?;
1120
1121                    (started > very_recently).then_some(ipt.lid)
1122                })
1123                .next()?;
1124            Some((lid, wait_more))
1125        };
1126
1127        let n_good_ipts = self.good_ipts().count();
1128        let publish_lifetime = if n_good_ipts >= self.target_n_intro_points() {
1129            // "Certain" - we are sure of which IPTs we want to publish
1130            debug!(
1131                "HS service {}: {} good IPTs, >= target {}, publishing",
1132                &self.imm.nick,
1133                n_good_ipts,
1134                self.target_n_intro_points()
1135            );
1136
1137            self.imm.status_tx.send(IptMgrState::Running, None);
1138
1139            Some(IPT_PUBLISH_CERTAIN)
1140        } else if self.good_ipts().next().is_none()
1141        /* !... .is_empty() */
1142        {
1143            // "Unknown" - we have no idea which IPTs to publish.
1144            debug!("HS service {}: no good IPTs", &self.imm.nick);
1145
1146            self.imm
1147                .status_tx
1148                .send_recovering(self.ipt_errors().cloned().collect_vec());
1149
1150            None
1151        } else if let Some((wait_for, wait_more)) = started_establishing_very_recently() {
1152            // "Unknown" - we say have no idea which IPTs to publish:
1153            // although we have *some* idea, we hold off a bit to see if things improve.
1154            // The wait_more period started counting when the fastest IPT became ready,
1155            // so the printed value isn't an offset from the message timestamp.
1156            debug!(
1157                "HS service {}: {} good IPTs, < target {}, waiting up to {}ms for {:?}",
1158                &self.imm.nick,
1159                n_good_ipts,
1160                self.target_n_intro_points(),
1161                wait_more.as_millis(),
1162                wait_for
1163            );
1164
1165            self.imm
1166                .status_tx
1167                .send_recovering(self.ipt_errors().cloned().collect_vec());
1168
1169            None
1170        } else {
1171            // "Uncertain" - we have some IPTs we could publish, but we're not confident
1172            debug!(
1173                "HS service {}: {} good IPTs, < target {}, publishing what we have",
1174                &self.imm.nick,
1175                n_good_ipts,
1176                self.target_n_intro_points()
1177            );
1178
1179            // We are close to being Running -- we just need more IPTs!
1180            let errors = self.ipt_errors().cloned().collect_vec();
1181            let errors = if errors.is_empty() {
1182                None
1183            } else {
1184                Some(errors)
1185            };
1186
1187            self.imm
1188                .status_tx
1189                .send(IptMgrState::DegradedReachable, errors.map(|e| e.into()));
1190
1191            Some(IPT_PUBLISH_UNCERTAIN)
1192        };
1193
1194        publish_set.ipts = if let Some(lifetime) = publish_lifetime {
1195            let selected = self.publish_set_select();
1196            for ipt in &selected {
1197                self.state.mockable.start_accepting(&*ipt.establisher);
1198            }
1199            Some(Self::make_publish_set(selected, lifetime)?)
1200        } else {
1201            None
1202        };
1203
1204        //---------- store persistent state ----------
1205
1206        persist::store(&self.imm, &mut self.state)?;
1207
1208        Ok(())
1209    }
1210
1211    /// Select IPTs to publish, given that we have decided to publish *something*
1212    ///
1213    /// Calculates set of ipts to publish, selecting up to the target `N`
1214    /// from the available good current IPTs.
1215    /// (Old, non-current IPTs, that we are trying to retire, are never published.)
1216    ///
1217    /// The returned list is in the same order as our data structure:
1218    /// firstly, by the ordering in `State.irelays`, and then within each relay,
1219    /// by the ordering in `IptRelay.ipts`.  Both of these are stable.
1220    ///
1221    /// ### Performance
1222    ///
1223    /// This function is at worst O(N) where N is the number of IPTs.
1224    /// See the performance note on [`run_once()`](Self::run_once).
1225    fn publish_set_select(&self) -> VecDeque<&Ipt> {
1226        /// Good candidate introduction point for publication
1227        type Candidate<'i> = &'i Ipt;
1228
1229        let target_n = self.target_n_intro_points();
1230
1231        let mut candidates: VecDeque<_> = self
1232            .state
1233            .irelays
1234            .iter()
1235            .filter_map(|ir: &_| -> Option<Candidate<'_>> {
1236                let current_ipt = ir.current_ipt()?;
1237                if !current_ipt.is_good() {
1238                    return None;
1239                }
1240                Some(current_ipt)
1241            })
1242            .collect();
1243
1244        // Take the last N good IPT relays
1245        //
1246        // The way we manage irelays means that this is always
1247        // the ones we selected most recently.
1248        //
1249        // TODO SPEC  Publication strategy when we have more than >N IPTs
1250        //
1251        // We could have a number of strategies here.  We could take some timing
1252        // measurements, or use the establishment time, or something; but we don't
1253        // want to add distinguishability.
1254        //
1255        // Another concern is manipulability, but
1256        // We can't be forced to churn because we don't remove relays
1257        // from our list of relays to try to use, other than on our own schedule.
1258        // But we probably won't want to be too reactive to the network environment.
1259        //
1260        // Since we only choose new relays when old ones are to retire, or are faulty,
1261        // choosing the most recently selected, rather than the least recently,
1262        // has the effect of preferring relays we don't know to be faulty,
1263        // to ones we have considered faulty least once.
1264        //
1265        // That's better than the opposite.  Also, choosing more recently selected relays
1266        // for publication may slightly bring forward the time at which all descriptors
1267        // mentioning that relay have expired, and then we can forget about it.
1268        while candidates.len() > target_n {
1269            // WTB: VecDeque::truncate_front
1270            let _: Candidate = candidates.pop_front().expect("empty?!");
1271        }
1272
1273        candidates
1274    }
1275
1276    /// Produce a `publish::IptSet`, from a list of IPT selected for publication
1277    ///
1278    /// Updates each chosen `Ipt`'s `last_descriptor_expiry_including_slop`
1279    ///
1280    /// The returned `IptSet` set is in the same order as `selected`.
1281    ///
1282    /// ### Performance
1283    ///
1284    /// This function is at worst O(N) where N is the number of IPTs.
1285    /// See the performance note on [`run_once()`](Self::run_once).
1286    fn make_publish_set<'i>(
1287        selected: impl IntoIterator<Item = &'i Ipt>,
1288        lifetime: Duration,
1289    ) -> Result<ipt_set::IptSet, FatalError> {
1290        let ipts = selected
1291            .into_iter()
1292            .map(|current_ipt| {
1293                let TS::Good { details, .. } = &current_ipt.status_last else {
1294                    return Err(internal!("was good but now isn't?!").into());
1295                };
1296
1297                let publish = current_ipt.for_publish(details)?;
1298
1299                // last_descriptor_expiry_including_slop was earlier merged in from
1300                // the previous IptSet, and here we copy it back
1301                let publish = ipt_set::IptInSet {
1302                    ipt: publish,
1303                    lid: current_ipt.lid,
1304                };
1305
1306                Ok::<_, FatalError>(publish)
1307            })
1308            .collect::<Result<_, _>>()?;
1309
1310        Ok(ipt_set::IptSet { ipts, lifetime })
1311    }
1312
1313    /// Delete persistent on-disk data (including keys) for old IPTs
1314    ///
1315    /// More precisely, scan places where per-IPT data files live,
1316    /// and delete anything that doesn't correspond to
1317    /// one of the IPTs in our main in-memory data structure.
1318    ///
1319    /// Does *not* deal with deletion of data handled via storage handles
1320    /// (`state_dir::StorageHandle`), `ipt_mgr/persist.rs` etc.;
1321    /// those are one file for each service, so old data is removed as we rewrite them.
1322    ///
1323    /// Does *not* deal with deletion of entire old hidden services.
1324    ///
1325    /// (This function works on the basis of the invariant that every IPT
1326    /// in [`ipt_set::PublishIptSet`] is also an [`Ipt`] in [`ipt_mgr::State`](State).
1327    /// See the comment in [`IptManager::import_new_expiry_times`].
1328    /// If that invariant is violated, we would delete on-disk files for the affected IPTs.
1329    /// That's fine since we couldn't re-establish them anyway.)
1330    #[allow(clippy::cognitive_complexity)] // Splitting this up would make it worse
1331    fn expire_old_ipts_external_persistent_state(&self) -> Result<(), StateExpiryError> {
1332        self.state
1333            .mockable
1334            .expire_old_ipts_external_persistent_state_hook();
1335
1336        let all_ipts: HashSet<_> = self.all_ipts().map(|(_, ipt)| &ipt.lid).collect();
1337
1338        // Keys
1339
1340        let pat = IptKeySpecifierPattern {
1341            nick: Some(self.imm.nick.clone()),
1342            role: None,
1343            lid: None,
1344        }
1345        .arti_pattern()?;
1346
1347        let found = self.imm.keymgr.list_matching(&pat)?;
1348
1349        for entry in found {
1350            let path = entry.key_path();
1351            // Try to identify this key (including its IptLocalId)
1352            match IptKeySpecifier::try_from(path) {
1353                Ok(spec) if all_ipts.contains(&spec.lid) => continue,
1354                Ok(_) => trace!("deleting key for old IPT: {path}"),
1355                Err(bad) => info!("deleting unrecognised IPT key: {path} ({})", bad.report()),
1356            };
1357            // Not known, remove it
1358            self.imm.keymgr.remove_entry(&entry)?;
1359        }
1360
1361        // IPT replay logs
1362
1363        let handle_rl_err = |operation, path: &Path| {
1364            let path = path.to_owned();
1365            move |source| StateExpiryError::ReplayLog {
1366                operation,
1367                path,
1368                source: Arc::new(source),
1369            }
1370        };
1371
1372        // fs-mistrust doesn't offer CheckedDir::read_this_directory.
1373        // But, we probably don't mind that we're not doing many checks here.
1374        let replay_logs = self.imm.replay_log_dir.as_path();
1375        let replay_logs_dir =
1376            fs::read_dir(replay_logs).map_err(handle_rl_err("open dir", replay_logs))?;
1377
1378        for ent in replay_logs_dir {
1379            let ent = ent.map_err(handle_rl_err("read dir", replay_logs))?;
1380            let leaf = ent.file_name();
1381            // Try to identify this replay logfile (including its IptLocalId)
1382            match IptReplayLog::parse_log_leafname(&leaf) {
1383                Ok(lid) if all_ipts.contains(&lid) => continue,
1384                Ok(_) => trace!(
1385                    leaf = leaf.to_string_lossy().as_ref(),
1386                    "deleting replay log for old IPT"
1387                ),
1388                Err(bad) => info!(
1389                    "deleting garbage in IPT replay log dir: {} ({})",
1390                    leaf.to_string_lossy(),
1391                    bad
1392                ),
1393            }
1394            // Not known, remove it
1395            let path = ent.path();
1396            fs::remove_file(&path).map_err(handle_rl_err("remove", &path))?;
1397        }
1398
1399        Ok(())
1400    }
1401
1402    /// Run one iteration of the loop
1403    ///
1404    /// Either do some work, making changes to our state,
1405    /// or, if there's nothing to be done, wait until there *is* something to do.
1406    ///
1407    /// ### Implementation approach
1408    ///
1409    /// Every time we wake up we idempotently make progress
1410    /// by searching our whole state machine, looking for something to do.
1411    /// If we find something to do, we do that one thing, and search again.
1412    /// When we're done, we unconditionally recalculate the IPTs to publish, and sleep.
1413    ///
1414    /// This approach avoids the need for complicated reasoning about
1415    /// which state updates need to trigger other state updates,
1416    /// and thereby avoids several classes of potential bugs.
1417    /// However, it has some performance implications:
1418    ///
1419    /// ### Performance
1420    ///
1421    /// Events relating to an IPT occur, at worst,
1422    /// at a rate proportional to the current number of IPTs,
1423    /// times the maximum flap rate of any one IPT.
1424    ///
1425    /// [`idempotently_progress_things_now`](Self::idempotently_progress_things_now)
1426    /// can be called more than once for each such event,
1427    /// but only a finite number of times per IPT.
1428    ///
1429    /// Therefore, overall, our work rate is O(N^2) where N is the number of IPTs.
1430    /// We think this is tolerable,
1431    /// but it does mean that the principal functions should be written
1432    /// with an eye to avoiding "accidentally quadratic" algorithms,
1433    /// because that would make the whole manager cubic.
1434    /// Ideally we would avoid O(N.log(N)) algorithms.
1435    ///
1436    /// (Note that the number of IPTs can be significantly larger than
1437    /// the maximum target of 20, if the service is very busy so the intro points
1438    /// are cycling rapidly due to the need to replace the replay database.)
1439    #[allow(clippy::cognitive_complexity)] // TODO: Refactor?
1440    async fn run_once(
1441        &mut self,
1442        // This is a separate argument for borrowck reasons
1443        publisher: &mut IptsManagerView,
1444    ) -> Result<ShutdownStatus, FatalError> {
1445        let now = {
1446            // Block to persuade borrow checker that publish_set isn't
1447            // held over an await point.
1448
1449            let mut publish_set = publisher.borrow_for_update(self.imm.runtime.clone());
1450
1451            Self::import_new_expiry_times(&mut self.state.irelays, &publish_set);
1452
1453            let mut loop_limit = 0..(
1454                // Work we do might be O(number of intro points),
1455                // but we might also have cycled the intro points due to many requests.
1456                // 10K is a guess at a stupid upper bound on the number of times we
1457                // might cycle ipts during a descriptor lifetime.
1458                // We don't need a tight bound; if we're going to crash. we can spin a bit first.
1459                (self.target_n_intro_points() + 1) * 10_000
1460            );
1461            let now = loop {
1462                let _: usize = loop_limit.next().expect("IPT manager is looping");
1463
1464                if let Some(now) = self.idempotently_progress_things_now()? {
1465                    break now;
1466                }
1467            };
1468
1469            // TODO #1214 Maybe something at level Error or Info, for example
1470            // Log an error if everything is terrilbe
1471            //   - we have >=N Faulty IPTs ?
1472            //    we have only Faulty IPTs and can't select another due to 2N limit ?
1473            // Log at info if and when we publish?  Maybe the publisher should do that?
1474
1475            if let Err(operr) = self.compute_iptsetstatus_publish(&now, &mut publish_set) {
1476                // This is not good, is it.
1477                publish_set.ipts = None;
1478                let wait = operr.log_retry_max(&self.imm.nick)?;
1479                now.update(wait);
1480            };
1481
1482            self.expire_old_expiry_times(&mut publish_set, &now);
1483
1484            drop(publish_set); // release lock, and notify publisher of any changes
1485
1486            if self.state.ipt_removal_cleanup_needed {
1487                let outcome = self.expire_old_ipts_external_persistent_state();
1488                log_ratelim!("removing state for old IPT(s)"; outcome);
1489                match outcome {
1490                    Ok(()) => self.state.ipt_removal_cleanup_needed = false,
1491                    Err(_already_logged) => {}
1492                }
1493            }
1494
1495            now
1496        };
1497
1498        assert_ne!(
1499            now.clone().shortest(),
1500            Some(Duration::ZERO),
1501            "IPT manager zero timeout, would loop"
1502        );
1503
1504        let mut new_configs = self.state.new_configs.next().fuse();
1505
1506        select_biased! {
1507            () = now.wait_for_earliest(&self.imm.runtime).fuse() => {},
1508            shutdown = self.state.shutdown.next().fuse() => {
1509                info!("HS service {}: terminating due to shutdown signal", &self.imm.nick);
1510                // We shouldn't be receiving anything on thisi channel.
1511                assert!(shutdown.is_none());
1512                return Ok(ShutdownStatus::Terminate)
1513            },
1514
1515            update = self.state.status_recv.next() => {
1516                let (lid, update) = update.ok_or_else(|| internal!("update mpsc ended!"))?;
1517                self.state.handle_ipt_status_update(&self.imm, lid, update);
1518            }
1519
1520            _dir_event = async {
1521                match self.state.last_irelay_selection_outcome {
1522                    Ok(()) => future::pending().await,
1523                    // This boxes needlessly but it shouldn't really happen
1524                    Err(()) => self.imm.dirprovider.events().next().await,
1525                }
1526            }.fuse() => {
1527                self.state.last_irelay_selection_outcome = Ok(());
1528            }
1529
1530            new_config = new_configs => {
1531                let Some(new_config) = new_config else {
1532                    trace!("HS service {}: terminating due to EOF on config updates stream",
1533                           &self.imm.nick);
1534                    return Ok(ShutdownStatus::Terminate);
1535                };
1536                if let Err(why) = (|| {
1537                    let dos = |config: &OnionServiceConfig| config.dos_extension()
1538                        .map_err(|e| e.report().to_string());
1539                    if dos(&self.state.current_config)? != dos(&new_config)? {
1540                        return Err("DOS parameters (rate limit) changed".to_string());
1541                    }
1542                    Ok(())
1543                })() {
1544                    // We need new IPTs with the new parameters.  (The previously-published
1545                    // IPTs will automatically be retained so long as needed, by the
1546                    // rest of our algorithm.)
1547                    info!("HS service {}: replacing IPTs: {}", &self.imm.nick, &why);
1548                    for ir in &mut self.state.irelays {
1549                        for ipt in &mut ir.ipts {
1550                            ipt.is_current = None;
1551                        }
1552                    }
1553                }
1554                self.state.current_config = new_config;
1555                self.state.last_irelay_selection_outcome = Ok(());
1556            }
1557        }
1558
1559        Ok(ShutdownStatus::Continue)
1560    }
1561
1562    /// IPT Manager main loop, runs as a task
1563    ///
1564    /// Contains the error handling, including catching panics.
1565    async fn main_loop_task(mut self, mut publisher: IptsManagerView) {
1566        loop {
1567            match async {
1568                AssertUnwindSafe(self.run_once(&mut publisher))
1569                    .catch_unwind()
1570                    .await
1571                    .map_err(|_: Box<dyn Any + Send>| internal!("IPT manager crashed"))?
1572            }
1573            .await
1574            {
1575                Err(crash) => {
1576                    error!("bug: HS service {} crashed! {}", &self.imm.nick, crash);
1577
1578                    self.imm.status_tx.send_broken(crash);
1579                    break;
1580                }
1581                Ok(ShutdownStatus::Continue) => continue,
1582                Ok(ShutdownStatus::Terminate) => {
1583                    self.imm.status_tx.send_shutdown();
1584
1585                    break;
1586                }
1587            }
1588        }
1589    }
1590}
1591
1592impl<R: Runtime, M: Mockable<R>> State<R, M> {
1593    /// Find the `Ipt` with persistent local id `lid`
1594    fn ipt_by_lid_mut(&mut self, needle: IptLocalId) -> Option<&mut Ipt> {
1595        self.irelays
1596            .iter_mut()
1597            .find_map(|ir| ir.ipts.iter_mut().find(|ipt| ipt.lid == needle))
1598    }
1599
1600    /// Choose a new relay to use for IPTs
1601    fn choose_new_ipt_relay(
1602        &mut self,
1603        imm: &Immutable<R>,
1604        now: Instant,
1605    ) -> Result<(), ChooseIptError> {
1606        let netdir = imm.dirprovider.timely_netdir()?;
1607
1608        let mut rng = self.mockable.thread_rng();
1609
1610        let relay = {
1611            let exclude_ids = self
1612                .irelays
1613                .iter()
1614                .flat_map(|e| e.relay.identities())
1615                .map(|id| id.to_owned())
1616                .collect();
1617            let selector = RelaySelector::new(
1618                RelayUsage::new_intro_point(),
1619                RelayExclusion::exclude_identities(exclude_ids),
1620            );
1621            selector
1622                .select_relay(&mut rng, &netdir)
1623                .0 // TODO: Someday we might want to report why we rejected everything on failure.
1624                .ok_or(ChooseIptError::TooFewUsableRelays)?
1625        };
1626
1627        let lifetime_low = netdir
1628            .params()
1629            .hs_intro_min_lifetime
1630            .try_into()
1631            .expect("Could not convert param to duration.");
1632        let lifetime_high = netdir
1633            .params()
1634            .hs_intro_max_lifetime
1635            .try_into()
1636            .expect("Could not convert param to duration.");
1637        let lifetime_range: std::ops::RangeInclusive<Duration> = lifetime_low..=lifetime_high;
1638        let retirement = rng
1639            .gen_range_checked(lifetime_range)
1640            // If the range from the consensus is invalid, just pick the high-bound.
1641            .unwrap_or(lifetime_high);
1642        let retirement = now
1643            .checked_add(retirement)
1644            .ok_or(ChooseIptError::TimeOverflow)?;
1645
1646        let new_irelay = IptRelay {
1647            relay: RelayIds::from_relay_ids(&relay),
1648            planned_retirement: retirement,
1649            ipts: vec![],
1650        };
1651        self.irelays.push(new_irelay);
1652
1653        debug!(
1654            "HS service {}: choosing new IPT relay {}",
1655            &imm.nick,
1656            relay.display_relay_ids()
1657        );
1658
1659        Ok(())
1660    }
1661
1662    /// Update `self`'s status tracking for one introduction point
1663    fn handle_ipt_status_update(&mut self, imm: &Immutable<R>, lid: IptLocalId, update: IptStatus) {
1664        let Some(ipt) = self.ipt_by_lid_mut(lid) else {
1665            // update from now-withdrawn IPT, ignore it (can happen due to the IPT being a task)
1666            return;
1667        };
1668
1669        debug!("HS service {}: {lid:?} status update {update:?}", &imm.nick);
1670
1671        let IptStatus {
1672            status: update,
1673            wants_to_retire,
1674            ..
1675        } = update;
1676
1677        #[allow(clippy::single_match)] // want to be explicit about the Ok type
1678        match wants_to_retire {
1679            Err(IptWantsToRetire) => ipt.is_current = None,
1680            Ok(()) => {}
1681        }
1682
1683        let now = || imm.runtime.now();
1684
1685        let started = match &ipt.status_last {
1686            TS::Establishing { started, .. } => Ok(*started),
1687            TS::Faulty { started, .. } => *started,
1688            TS::Good { .. } => Err(()),
1689        };
1690
1691        ipt.status_last = match update {
1692            ISS::Establishing => TS::Establishing {
1693                started: started.unwrap_or_else(|()| now()),
1694            },
1695            ISS::Good(details) => {
1696                let time_to_establish = started.and_then(|started| {
1697                    // return () at end of ok_or_else closure, for clarity
1698                    #[allow(clippy::unused_unit, clippy::semicolon_if_nothing_returned)]
1699                    now().checked_duration_since(started).ok_or_else(|| {
1700                        warn!("monotonic clock went backwards! (HS IPT)");
1701                        ()
1702                    })
1703                });
1704                TS::Good {
1705                    time_to_establish,
1706                    details,
1707                }
1708            }
1709            ISS::Faulty(error) => TS::Faulty { started, error },
1710        };
1711    }
1712}
1713
1714//========== mockability ==========
1715
1716/// Mockable state for the IPT Manager
1717///
1718/// This allows us to use a fake IPT Establisher and IPT Publisher,
1719/// so that we can unit test the Manager.
1720pub(crate) trait Mockable<R>: Debug + Send + Sync + Sized + 'static {
1721    /// IPT establisher type
1722    type IptEstablisher: Send + Sync + 'static;
1723
1724    /// A random number generator
1725    type Rng<'m>: rand::Rng + rand::CryptoRng + 'm;
1726
1727    /// Return a random number generator
1728    fn thread_rng(&mut self) -> Self::Rng<'_>;
1729
1730    /// Call `IptEstablisher::new`
1731    fn make_new_ipt(
1732        &mut self,
1733        imm: &Immutable<R>,
1734        params: IptParameters,
1735    ) -> Result<(Self::IptEstablisher, watch::Receiver<IptStatus>), FatalError>;
1736
1737    /// Call `IptEstablisher::start_accepting`
1738    fn start_accepting(&self, establisher: &ErasedIptEstablisher);
1739
1740    /// Allow tests to see when [`IptManager::expire_old_ipts_external_persistent_state`]
1741    /// is called.
1742    ///
1743    /// This lets tests see that it gets called at the right times,
1744    /// and not the wrong ones.
1745    fn expire_old_ipts_external_persistent_state_hook(&self);
1746}
1747
1748impl<R: Runtime> Mockable<R> for Real<R> {
1749    type IptEstablisher = IptEstablisher;
1750
1751    /// A random number generator
1752    type Rng<'m> = rand::rngs::ThreadRng;
1753
1754    /// Return a random number generator
1755    fn thread_rng(&mut self) -> Self::Rng<'_> {
1756        rand::rng()
1757    }
1758
1759    fn make_new_ipt(
1760        &mut self,
1761        imm: &Immutable<R>,
1762        params: IptParameters,
1763    ) -> Result<(Self::IptEstablisher, watch::Receiver<IptStatus>), FatalError> {
1764        IptEstablisher::launch(&imm.runtime, params, self.circ_pool.clone(), &imm.keymgr)
1765    }
1766
1767    fn start_accepting(&self, establisher: &ErasedIptEstablisher) {
1768        let establisher: &IptEstablisher = <dyn Any>::downcast_ref(establisher)
1769            .expect("upcast failure, ErasedIptEstablisher is not IptEstablisher!");
1770        establisher.start_accepting();
1771    }
1772
1773    fn expire_old_ipts_external_persistent_state_hook(&self) {}
1774}
1775
1776// TODO #1213 add more unit tests for IptManager
1777// Especially, we want to exercise all code paths in idempotently_progress_things_now
1778
1779#[cfg(test)]
1780mod test {
1781    // @@ begin test lint list maintained by maint/add_warning @@
1782    #![allow(clippy::bool_assert_comparison)]
1783    #![allow(clippy::clone_on_copy)]
1784    #![allow(clippy::dbg_macro)]
1785    #![allow(clippy::mixed_attributes_style)]
1786    #![allow(clippy::print_stderr)]
1787    #![allow(clippy::print_stdout)]
1788    #![allow(clippy::single_char_pattern)]
1789    #![allow(clippy::unwrap_used)]
1790    #![allow(clippy::unchecked_time_subtraction)]
1791    #![allow(clippy::useless_vec)]
1792    #![allow(clippy::needless_pass_by_value)]
1793    #![allow(clippy::string_slice)] // See arti#2571
1794    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
1795    #![allow(clippy::match_single_binding)] // false positives, need the lifetime extension
1796    use super::*;
1797
1798    use crate::config::OnionServiceConfigBuilder;
1799    use crate::ipt_establish::GoodIptDetails;
1800    use crate::status::{OnionServiceStatus, StatusSender};
1801    use crate::test::{create_keymgr, create_storage_handles_from_state_dir};
1802    use rand::SeedableRng as _;
1803    use slotmap_careful::DenseSlotMap;
1804    use std::collections::BTreeMap;
1805    use std::sync::Mutex;
1806    use test_temp_dir::{TestTempDir, test_temp_dir};
1807    use tor_basic_utils::test_rng::TestingRng;
1808    use tor_netdir::testprovider::TestNetDirProvider;
1809    use tor_rtmock::MockRuntime;
1810    use tracing_test::traced_test;
1811    use walkdir::WalkDir;
1812
1813    slotmap_careful::new_key_type! {
1814        struct MockEstabId;
1815    }
1816
1817    type MockEstabs = Arc<Mutex<DenseSlotMap<MockEstabId, MockEstabState>>>;
1818
1819    fn ms(ms: u64) -> Duration {
1820        Duration::from_millis(ms)
1821    }
1822
1823    #[derive(Debug)]
1824    struct Mocks {
1825        rng: TestingRng,
1826        estabs: MockEstabs,
1827        expect_expire_ipts_calls: Arc<Mutex<usize>>,
1828    }
1829
1830    #[derive(Debug)]
1831    struct MockEstabState {
1832        st_tx: watch::Sender<IptStatus>,
1833        params: IptParameters,
1834    }
1835
1836    #[derive(Debug)]
1837    struct MockEstab {
1838        esid: MockEstabId,
1839        estabs: MockEstabs,
1840    }
1841
1842    impl Mockable<MockRuntime> for Mocks {
1843        type IptEstablisher = MockEstab;
1844        type Rng<'m> = &'m mut TestingRng;
1845
1846        fn thread_rng(&mut self) -> Self::Rng<'_> {
1847            &mut self.rng
1848        }
1849
1850        fn make_new_ipt(
1851            &mut self,
1852            _imm: &Immutable<MockRuntime>,
1853            params: IptParameters,
1854        ) -> Result<(Self::IptEstablisher, watch::Receiver<IptStatus>), FatalError> {
1855            let (st_tx, st_rx) = watch::channel();
1856            let estab = MockEstabState { st_tx, params };
1857            let esid = self.estabs.lock().unwrap().insert(estab);
1858            let estab = MockEstab {
1859                esid,
1860                estabs: self.estabs.clone(),
1861            };
1862            Ok((estab, st_rx))
1863        }
1864
1865        fn start_accepting(&self, _establisher: &ErasedIptEstablisher) {}
1866
1867        fn expire_old_ipts_external_persistent_state_hook(&self) {
1868            let mut expect = self.expect_expire_ipts_calls.lock().unwrap();
1869            eprintln!("expire_old_ipts_external_persistent_state_hook, expect={expect}");
1870            *expect = expect.checked_sub(1).expect("unexpected expiry");
1871        }
1872    }
1873
1874    impl Drop for MockEstab {
1875        fn drop(&mut self) {
1876            let mut estabs = self.estabs.lock().unwrap();
1877            let _: MockEstabState = estabs
1878                .remove(self.esid)
1879                .expect("dropping non-recorded MockEstab");
1880        }
1881    }
1882
1883    struct MockedIptManager<'d> {
1884        estabs: MockEstabs,
1885        pub_view: ipt_set::IptsPublisherView,
1886        shut_tx: broadcast::Sender<Void>,
1887        #[allow(dead_code)]
1888        cfg_tx: watch::Sender<Arc<OnionServiceConfig>>,
1889        #[allow(dead_code)] // ensures temp dir lifetime; paths stored in self
1890        temp_dir: &'d TestTempDir,
1891        expect_expire_ipts_calls: Arc<Mutex<usize>>, // use usize::MAX to not mind
1892    }
1893
1894    impl<'d> MockedIptManager<'d> {
1895        fn startup(
1896            runtime: MockRuntime,
1897            temp_dir: &'d TestTempDir,
1898            seed: u64,
1899            expect_expire_ipts_calls: usize,
1900        ) -> Self {
1901            let dir: TestNetDirProvider = tor_netdir::testnet::construct_netdir()
1902                .unwrap_if_sufficient()
1903                .unwrap()
1904                .into();
1905
1906            let nick: HsNickname = "nick".to_string().try_into().unwrap();
1907
1908            let cfg = OnionServiceConfigBuilder::default()
1909                .nickname(nick.clone())
1910                .build()
1911                .unwrap();
1912
1913            let (cfg_tx, cfg_rx) = watch::channel_with(Arc::new(cfg));
1914
1915            let (rend_tx, _rend_rx) = mpsc::channel(10);
1916            let (shut_tx, shut_rx) = broadcast::channel::<Void>(0);
1917
1918            let estabs: MockEstabs = Default::default();
1919            let expect_expire_ipts_calls = Arc::new(Mutex::new(expect_expire_ipts_calls));
1920
1921            let mocks = Mocks {
1922                rng: TestingRng::seed_from_u64(seed),
1923                estabs: estabs.clone(),
1924                expect_expire_ipts_calls: expect_expire_ipts_calls.clone(),
1925            };
1926
1927            // Don't provide a subdir; the ipt_mgr is supposed to add any needed subdirs
1928            let state_dir = temp_dir
1929                // untracked is OK because our return value captures 'd
1930                .subdir_untracked("state_dir");
1931
1932            let (state_handle, iptpub_state_handle) =
1933                create_storage_handles_from_state_dir(&state_dir, &nick);
1934
1935            let (mgr_view, pub_view) =
1936                ipt_set::ipts_channel(&runtime, iptpub_state_handle).unwrap();
1937
1938            let keymgr = create_keymgr(temp_dir);
1939            let keymgr = keymgr.into_untracked(); // OK because our return value captures 'd
1940            let status_tx = StatusSender::new(OnionServiceStatus::new_shutdown()).into();
1941            let mgr = IptManager::new(
1942                runtime.clone(),
1943                Arc::new(dir),
1944                nick,
1945                cfg_rx,
1946                rend_tx,
1947                shut_rx,
1948                &state_handle,
1949                mocks,
1950                keymgr,
1951                status_tx,
1952            )
1953            .unwrap();
1954
1955            mgr.launch_background_tasks(mgr_view).unwrap();
1956
1957            MockedIptManager {
1958                estabs,
1959                pub_view,
1960                shut_tx,
1961                cfg_tx,
1962                temp_dir,
1963                expect_expire_ipts_calls,
1964            }
1965        }
1966
1967        async fn shutdown_check_no_tasks(self, runtime: &MockRuntime) {
1968            drop(self.shut_tx);
1969            runtime.progress_until_stalled().await;
1970            assert_eq!(runtime.mock_task().n_tasks(), 1); // just us
1971        }
1972
1973        fn estabs_inventory(&self) -> impl Eq + Debug + 'static + use<> {
1974            let estabs = self.estabs.lock().unwrap();
1975            estabs
1976                .values()
1977                .map(|MockEstabState { params: p, .. }| {
1978                    (
1979                        p.lid,
1980                        (
1981                            p.target.clone(),
1982                            // We want to check the key values, but they're very hard to get at
1983                            // in a way we can compare.  Especially the private keys, for which
1984                            // we can't getting a clone or copy of the private key material out of the Arc.
1985                            // They're keypairs, we can use the debug rep which shows the public half.
1986                            // That will have to do.
1987                            format!("{:?}", p.k_sid),
1988                            format!("{:?}", p.k_ntor),
1989                        ),
1990                    )
1991                })
1992                .collect::<BTreeMap<_, _>>()
1993        }
1994    }
1995
1996    #[test]
1997    #[traced_test]
1998    fn test_mgr_lifecycle() {
1999        MockRuntime::test_with_various(|runtime| async move {
2000            let temp_dir = test_temp_dir!();
2001
2002            let m = MockedIptManager::startup(runtime.clone(), &temp_dir, 0, 1);
2003            runtime.progress_until_stalled().await;
2004
2005            assert_eq!(*m.expect_expire_ipts_calls.lock().unwrap(), 0);
2006
2007            // We expect it to try to establish 3 IPTs
2008            const EXPECT_N_IPTS: usize = 3;
2009            const EXPECT_MAX_IPTS: usize = EXPECT_N_IPTS + 2 /* num_extra */;
2010            assert_eq!(m.estabs.lock().unwrap().len(), EXPECT_N_IPTS);
2011            assert!(m.pub_view.borrow_for_publish().ipts.is_none());
2012
2013            // Advancing time a bit and it still shouldn't publish anything
2014            runtime.advance_by(ms(500)).await;
2015            runtime.progress_until_stalled().await;
2016            assert!(m.pub_view.borrow_for_publish().ipts.is_none());
2017
2018            let good = GoodIptDetails {
2019                link_specifiers: vec![],
2020                ipt_kp_ntor: [0x55; 32].into(),
2021            };
2022
2023            // Imagine that one of our IPTs becomes good
2024            m.estabs
2025                .lock()
2026                .unwrap()
2027                .values_mut()
2028                .next()
2029                .unwrap()
2030                .st_tx
2031                .borrow_mut()
2032                .status = IptStatusStatus::Good(good.clone());
2033
2034            // TODO #1213 test that we haven't called start_accepting
2035
2036            // It won't publish until a further fastest establish time
2037            // Ie, until a further 500ms = 1000ms
2038            runtime.progress_until_stalled().await;
2039            assert!(m.pub_view.borrow_for_publish().ipts.is_none());
2040            runtime.advance_by(ms(499)).await;
2041            assert!(m.pub_view.borrow_for_publish().ipts.is_none());
2042            runtime.advance_by(ms(1)).await;
2043            match m.pub_view.borrow_for_publish().ipts.as_mut().unwrap() {
2044                pub_view => {
2045                    assert_eq!(pub_view.ipts.len(), 1);
2046                    assert_eq!(pub_view.lifetime, IPT_PUBLISH_UNCERTAIN);
2047                }
2048            };
2049
2050            // TODO #1213 test that we have called start_accepting on the right IPTs
2051
2052            // Set the other IPTs to be Good too
2053            for e in m.estabs.lock().unwrap().values_mut().skip(1) {
2054                e.st_tx.borrow_mut().status = IptStatusStatus::Good(good.clone());
2055            }
2056            runtime.progress_until_stalled().await;
2057            match m.pub_view.borrow_for_publish().ipts.as_mut().unwrap() {
2058                pub_view => {
2059                    assert_eq!(pub_view.ipts.len(), EXPECT_N_IPTS);
2060                    assert_eq!(pub_view.lifetime, IPT_PUBLISH_CERTAIN);
2061                }
2062            };
2063
2064            // TODO #1213 test that we have called start_accepting on the right IPTs
2065
2066            let estabs_inventory = m.estabs_inventory();
2067
2068            // Shut down
2069            m.shutdown_check_no_tasks(&runtime).await;
2070
2071            // ---------- restart! ----------
2072            info!("*** Restarting ***");
2073
2074            let m = MockedIptManager::startup(runtime.clone(), &temp_dir, 1, 1);
2075            runtime.progress_until_stalled().await;
2076            assert_eq!(*m.expect_expire_ipts_calls.lock().unwrap(), 0);
2077
2078            assert_eq!(estabs_inventory, m.estabs_inventory());
2079
2080            // TODO #1213 test that we have called start_accepting on all the old IPTs
2081
2082            // ---------- New IPT relay selection ----------
2083
2084            let old_lids: Vec<String> = m
2085                .estabs
2086                .lock()
2087                .unwrap()
2088                .values()
2089                .map(|ess| ess.params.lid.to_string())
2090                .collect();
2091            eprintln!("IPTs to rotate out: {old_lids:?}");
2092
2093            let old_lid_files = || {
2094                WalkDir::new(temp_dir.as_path_untracked())
2095                    .into_iter()
2096                    .map(|ent| {
2097                        ent.unwrap()
2098                            .into_path()
2099                            .into_os_string()
2100                            .into_string()
2101                            .unwrap()
2102                    })
2103                    .filter(|path| old_lids.iter().any(|lid| path.contains(lid)))
2104                    .collect_vec()
2105            };
2106
2107            let no_files: [String; 0] = [];
2108
2109            assert_ne!(old_lid_files(), no_files);
2110
2111            // It might call the expiry function once, or once per IPT.
2112            // The latter is quadratic but this is quite rare, so that's fine.
2113            *m.expect_expire_ipts_calls.lock().unwrap() = EXPECT_MAX_IPTS;
2114
2115            // wait 2 days, > hs_intro_max_lifetime
2116            runtime.advance_by(ms(48 * 60 * 60 * 1_000)).await;
2117            runtime.progress_until_stalled().await;
2118
2119            // It must have called it at least once.
2120            assert_ne!(*m.expect_expire_ipts_calls.lock().unwrap(), EXPECT_MAX_IPTS);
2121
2122            // There should now be no files names after old IptLocalIds.
2123            assert_eq!(old_lid_files(), no_files);
2124
2125            // Shut down
2126            m.shutdown_check_no_tasks(&runtime).await;
2127        });
2128    }
2129}