1//! Persistent state for the IPT manager
2//!
3//! Records of our IPTs.
4//! Does *not* include private keys - those are in the `KeyMgr`.
56use super::*;
7use crate::time_store;
89/// Handle for a suitable persistent storage manager
10pub(crate) type IptStorageHandle = tor_persist::state_dir::StorageHandle<StateRecord>;
1112//---------- On disk data structures, done with serde ----------
1314/// Record of intro point establisher state, as stored on disk
15#[derive(Serialize, Deserialize, Debug)]
16pub(crate) struct StateRecord {
17/// Relays
18ipt_relays: Vec<RelayRecord>,
19/// Reference time
20stored: time_store::Reference,
21}
2223/// Record of a selected intro point relay, as stored on disk
24#[derive(Serialize, Deserialize, Debug)]
25struct RelayRecord {
26/// Which relay?
27relay: RelayIds,
28/// When do we plan to retire it?
29planned_retirement: time_store::FutureTimestamp,
30/// The IPTs, including the current one and any still-wanted old ones
31ipts: Vec<IptRecord>,
32}
3334/// Record of a single intro point, as stored on disk
35#[derive(Serialize, Deserialize, Debug)]
36struct IptRecord {
37/// Used to find the cryptographic keys, amongst other things
38lid: IptLocalId,
39/// Is this IPT current, or are we just keeping it because of old descriptors
40#[serde(default, skip_serializing_if = "<&bool as std::ops::Not>::not")]
41is_current: bool,
42}
4344//---------- Storing ----------
4546/// Store the IPTs in the persistent state
47pub(super) fn store<R: Runtime, M: Mockable<R>>(
48 imm: &Immutable<R>,
49 state: &mut State<R, M>,
50) -> Result<(), IptStoreError> {
51let tstoring = time_store::Storing::start(&imm.runtime);
5253// Convert the IPT relays (to the on-disk format)
54let ipt_relays = state
55 .irelays
56 .iter()
57 .map(|irelay| {
58// Convert one IPT relay, with its IPTs, to the on-disk format
59let relay = irelay.relay.clone();
60let planned_retirement = tstoring.store_future(irelay.planned_retirement);
61let ipts = irelay
62 .ipts
63 .iter()
64 .map(|ipt| {
65// Convert one IPT - at least, the parts we store here
66IptRecord {
67 lid: ipt.lid,
68 is_current: ipt.is_current.is_some(),
69 }
70 })
71 .collect_vec();
72 RelayRecord {
73 relay,
74 planned_retirement,
75 ipts,
76 }
77 })
78 .collect_vec();
7980let on_disk = StateRecord {
81 ipt_relays,
82 stored: tstoring.store_ref(),
83 };
84 state.storage.store(&on_disk)?;
85Ok(())
86}
8788//---------- Loading ----------
8990/// Load the IPTs from the persistent state
91///
92/// `publish_set` should already have been loaded from its persistent state.
93pub(super) fn load<R: Runtime, M: Mockable<R>>(
94 imm: &Immutable<R>,
95 storage: &IptStorageHandle,
96 config: &watch::Receiver<Arc<OnionServiceConfig>>,
97 mockable: &mut M,
98 publish_set: &PublishIptSet,
99) -> Result<Vec<IptRelay>, StartupError> {
100let on_disk = storage.load().map_err(StartupError::LoadState)?;
101102let Some(on_disk) = on_disk else {
103return Ok(vec![]);
104 };
105106// Throughout, we use exhaustive struct patterns on the data we got from disk,
107 // so we avoid missing any of the data.
108let StateRecord { ipt_relays, stored } = on_disk;
109110let tloading = time_store::Loading::start(&imm.runtime, stored);
111112// Load the IPT relays (from the on-disk to the in-memory format)
113let mut ipt_relays: Vec<_> = ipt_relays
114 .into_iter()
115 .map(|rrelay| {
116// Load one IPT relay
117let RelayRecord {
118 relay,
119 planned_retirement,
120 ipts,
121 } = rrelay;
122let planned_retirement = tloading.load_future(planned_retirement);
123// Load the IPTs at this relay, restarting their establishers, etc.
124let ipts = ipts
125 .into_iter()
126 .map(|ipt| ipt.load_restart(imm, config, mockable, &relay))
127 .try_collect()?;
128Ok::<_, StartupError>(IptRelay {
129 relay,
130 planned_retirement,
131 ipts,
132 })
133 })
134 .try_collect()?;
135136 IptManager::<R, M>::import_new_expiry_times(&mut ipt_relays, publish_set);
137138Ok(ipt_relays)
139}
140141impl IptRecord {
142/// Recreate (load) one IPT
143fn load_restart<R: Runtime, M: Mockable<R>>(
144self,
145 imm: &Immutable<R>,
146 new_configs: &watch::Receiver<Arc<OnionServiceConfig>>,
147 mockable: &mut M,
148 relay: &RelayIds,
149 ) -> Result<Ipt, StartupError> {
150let IptRecord { lid, is_current } = self;
151152let ipt = Ipt::start_establisher(
153 imm,
154 new_configs,
155 mockable,
156 relay,
157 lid,
158 is_current.then_some(IsCurrent),
159Some(IptExpectExistingKeys),
160// last_descriptor_expiry_including_slop
161 // is restored by the `import_new_expiry_times` call in `load`
162PromiseLastDescriptorExpiryNoneIsGood {},
163 )
164 .map_err(|e| match e {
165 CreateIptError::Fatal(e) => e.into(),
166// During startup we're trying to *read* the keystore;
167 // if it goes wrong, we bail rather than continuing the startup attempt.
168CreateIptError::Keystore(cause) => StartupError::Keystore {
169 action: "load IPT key(s)",
170 cause,
171 },
172 CreateIptError::OpenReplayLog { file, error } => {
173 StartupError::StateDirectoryInaccessibleIo {
174 source: error,
175 action: "opening",
176 path: file,
177 }
178 }
179 })?;
180181// We don't record whether this IPT was published, so we should assume it was.
182mockable.start_accepting(&*ipt.establisher);
183184Ok(ipt)
185 }
186}