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