1
//! Persistent state for the IPT manager
2
//!
3
//! Records of our IPTs.
4
//! Does *not* include private keys - those are in the `KeyMgr`.
5

            
6
use super::*;
7
use crate::time_store;
8

            
9
/// Handle for a suitable persistent storage manager
10
pub(crate) type IptStorageHandle = tor_persist::state_dir::StorageHandle<StateRecord>;
11

            
12
//---------- On disk data structures, done with serde ----------
13

            
14
/// Record of intro point establisher state, as stored on disk
15
#[derive(Serialize, Deserialize, Debug)]
16
pub(crate) struct StateRecord {
17
    /// Relays
18
    ipt_relays: Vec<RelayRecord>,
19
    /// Reference time
20
    stored: time_store::Reference,
21
}
22

            
23
/// Record of a selected intro point relay, as stored on disk
24
#[derive(Serialize, Deserialize, Debug)]
25
struct RelayRecord {
26
    /// Which relay?
27
    relay: RelayIds,
28
    /// When do we plan to retire it?
29
    planned_retirement: time_store::FutureTimestamp,
30
    /// The IPTs, including the current one and any still-wanted old ones
31
    ipts: Vec<IptRecord>,
32
}
33

            
34
/// Record of a single intro point, as stored on disk
35
#[derive(Serialize, Deserialize, Debug)]
36
struct IptRecord {
37
    /// Used to find the cryptographic keys, amongst other things
38
    lid: 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")]
41
    is_current: bool,
42
}
43

            
44
//---------- Storing ----------
45

            
46
/// Store the IPTs in the persistent state
47
46
pub(super) fn store<R: Runtime, M: Mockable<R>>(
48
46
    imm: &Immutable<R>,
49
46
    state: &mut State<R, M>,
50
46
) -> Result<(), IptStoreError> {
51
46
    let tstoring = time_store::Storing::start(&imm.runtime);
52

            
53
    // Convert the IPT relays (to the on-disk format)
54
46
    let ipt_relays = state
55
46
        .irelays
56
46
        .iter()
57
138
        .map(|irelay| {
58
            // Convert one IPT relay, with its IPTs, to the on-disk format
59
138
            let relay = irelay.relay.clone();
60
138
            let planned_retirement = tstoring.store_future(irelay.planned_retirement);
61
138
            let ipts = irelay
62
138
                .ipts
63
138
                .iter()
64
138
                .map(|ipt| {
65
                    // Convert one IPT - at least, the parts we store here
66
138
                    IptRecord {
67
138
                        lid: ipt.lid,
68
138
                        is_current: ipt.is_current.is_some(),
69
138
                    }
70
138
                })
71
138
                .collect_vec();
72
138
            RelayRecord {
73
138
                relay,
74
138
                planned_retirement,
75
138
                ipts,
76
138
            }
77
138
        })
78
46
        .collect_vec();
79

            
80
46
    let on_disk = StateRecord {
81
46
        ipt_relays,
82
46
        stored: tstoring.store_ref(),
83
46
    };
84
46
    state.storage.store(&on_disk)?;
85
46
    Ok(())
86
46
}
87

            
88
//---------- Loading ----------
89

            
90
/// Load the IPTs from the persistent state
91
///
92
/// `publish_set` should already have been loaded from its persistent state.
93
4
pub(super) fn load<R: Runtime, M: Mockable<R>>(
94
4
    imm: &Immutable<R>,
95
4
    storage: &IptStorageHandle,
96
4
    config: &watch::Receiver<Arc<OnionServiceConfig>>,
97
4
    mockable: &mut M,
98
4
    publish_set: &PublishIptSet,
99
4
) -> Result<Vec<IptRelay>, StartupError> {
100
4
    let on_disk = storage.load().map_err(StartupError::LoadState)?;
101

            
102
4
    let Some(on_disk) = on_disk else {
103
2
        return Ok(vec![]);
104
    };
105

            
106
    // Throughout, we use exhaustive struct patterns on the data we got from disk,
107
    // so we avoid missing any of the data.
108
2
    let StateRecord { ipt_relays, stored } = on_disk;
109

            
110
2
    let tloading = time_store::Loading::start(&imm.runtime, stored);
111

            
112
    // Load the IPT relays (from the on-disk to the in-memory format)
113
2
    let mut ipt_relays: Vec<_> = ipt_relays
114
2
        .into_iter()
115
6
        .map(|rrelay| {
116
            // Load one IPT relay
117
            let RelayRecord {
118
6
                relay,
119
6
                planned_retirement,
120
6
                ipts,
121
6
            } = rrelay;
122
6
            let planned_retirement = tloading.load_future(planned_retirement);
123
            // Load the IPTs at this relay, restarting their establishers, etc.
124
6
            let ipts = ipts
125
6
                .into_iter()
126
6
                .map(|ipt| ipt.load_restart(imm, config, mockable, &relay))
127
6
                .try_collect()?;
128
6
            Ok::<_, StartupError>(IptRelay {
129
6
                relay,
130
6
                planned_retirement,
131
6
                ipts,
132
6
            })
133
6
        })
134
2
        .try_collect()?;
135

            
136
2
    IptManager::<R, M>::import_new_expiry_times(&mut ipt_relays, publish_set);
137

            
138
2
    Ok(ipt_relays)
139
4
}
140

            
141
impl IptRecord {
142
    /// Recreate (load) one IPT
143
6
    fn load_restart<R: Runtime, M: Mockable<R>>(
144
6
        self,
145
6
        imm: &Immutable<R>,
146
6
        new_configs: &watch::Receiver<Arc<OnionServiceConfig>>,
147
6
        mockable: &mut M,
148
6
        relay: &RelayIds,
149
6
    ) -> Result<Ipt, StartupError> {
150
6
        let IptRecord { lid, is_current } = self;
151

            
152
6
        let ipt = Ipt::start_establisher(
153
6
            imm,
154
6
            new_configs,
155
6
            mockable,
156
6
            relay,
157
6
            lid,
158
6
            is_current.then_some(IsCurrent),
159
6
            Some(IptExpectExistingKeys),
160
            // last_descriptor_expiry_including_slop
161
            // is restored by the `import_new_expiry_times` call in `load`
162
6
            PromiseLastDescriptorExpiryNoneIsGood {},
163
        )
164
6
        .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.
168
            CreateIptError::Keystore(cause) => StartupError::Keystore {
169
                action: "load IPT key(s)",
170
                cause,
171
            },
172
            CreateIptError::OpenReplayLog(OpenReplayLogError { file, error }) => {
173
                StartupError::StateDirectoryInaccessibleIo {
174
                    source: error,
175
                    action: "opening",
176
                    path: file,
177
                }
178
            }
179
        })?;
180

            
181
        // We don't record whether this IPT was published, so we should assume it was.
182
6
        mockable.start_accepting(&*ipt.establisher);
183

            
184
6
        Ok(ipt)
185
6
    }
186
}