tor_hsservice/ipt_mgr/
persist.rs1use super::*;
7use crate::time_store;
8
9pub(crate) type IptStorageHandle = tor_persist::state_dir::StorageHandle<StateRecord>;
11
12#[derive(Serialize, Deserialize, Debug)]
16pub(crate) struct StateRecord {
17 ipt_relays: Vec<RelayRecord>,
19 stored: time_store::Reference,
21}
22
23#[derive(Serialize, Deserialize, Debug)]
25struct RelayRecord {
26 relay: RelayIds,
28 planned_retirement: time_store::FutureTimestamp,
30 ipts: Vec<IptRecord>,
32}
33
34#[derive(Serialize, Deserialize, Debug)]
36struct IptRecord {
37 lid: IptLocalId,
39 #[serde(default, skip_serializing_if = "<&bool as std::ops::Not>::not")]
41 is_current: bool,
42}
43
44pub(super) fn store<R: Runtime, M: Mockable<R>>(
48 imm: &Immutable<R>,
49 state: &mut State<R, M>,
50) -> Result<(), IptStoreError> {
51 let tstoring = time_store::Storing::start(&imm.runtime);
52
53 let ipt_relays = state
55 .irelays
56 .iter()
57 .map(|irelay| {
58 let relay = irelay.relay.clone();
60 let planned_retirement = tstoring.store_future(irelay.planned_retirement);
61 let ipts = irelay
62 .ipts
63 .iter()
64 .map(|ipt| {
65 IptRecord {
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();
79
80 let on_disk = StateRecord {
81 ipt_relays,
82 stored: tstoring.store_ref(),
83 };
84 state.storage.store(&on_disk)?;
85 Ok(())
86}
87
88pub(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> {
100 let on_disk = storage.load().map_err(StartupError::LoadState)?;
101
102 let Some(on_disk) = on_disk else {
103 return Ok(vec![]);
104 };
105
106 let StateRecord { ipt_relays, stored } = on_disk;
109
110 let tloading = time_store::Loading::start(&imm.runtime, stored);
111
112 let mut ipt_relays: Vec<_> = ipt_relays
114 .into_iter()
115 .map(|rrelay| {
116 let RelayRecord {
118 relay,
119 planned_retirement,
120 ipts,
121 } = rrelay;
122 let planned_retirement = tloading.load_future(planned_retirement);
123 let ipts = ipts
125 .into_iter()
126 .map(|ipt| ipt.load_restart(imm, config, mockable, &relay))
127 .try_collect()?;
128 Ok::<_, StartupError>(IptRelay {
129 relay,
130 planned_retirement,
131 ipts,
132 })
133 })
134 .try_collect()?;
135
136 IptManager::<R, M>::import_new_expiry_times(&mut ipt_relays, publish_set);
137
138 Ok(ipt_relays)
139}
140
141impl IptRecord {
142 fn load_restart<R: Runtime, M: Mockable<R>>(
144 self,
145 imm: &Immutable<R>,
146 new_configs: &watch::Receiver<Arc<OnionServiceConfig>>,
147 mockable: &mut M,
148 relay: &RelayIds,
149 ) -> Result<Ipt, StartupError> {
150 let IptRecord { lid, is_current } = self;
151
152 let ipt = Ipt::start_establisher(
153 imm,
154 new_configs,
155 mockable,
156 relay,
157 lid,
158 is_current.then_some(IsCurrent),
159 Some(IptExpectExistingKeys),
160 PromiseLastDescriptorExpiryNoneIsGood {},
163 )
164 .map_err(|e| match e {
165 CreateIptError::Fatal(e) => e.into(),
166 CreateIptError::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 })?;
180
181 mockable.start_accepting(&*ipt.establisher);
183
184 Ok(ipt)
185 }
186}