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
12
#[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
48
#[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
36
#[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
92
pub(super) fn store<R: Runtime, M: Mockable<R>>(
48
92
    imm: &Immutable<R>,
49
92
    state: &mut State<R, M>,
50
92
) -> Result<(), IptStoreError> {
51
92
    let tstoring = time_store::Storing::start(&imm.runtime);
52
92

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

            
80
92
    let on_disk = StateRecord {
81
92
        ipt_relays,
82
92
        stored: tstoring.store_ref(),
83
92
    };
84
92
    state.storage.store(&on_disk)?;
85
92
    Ok(())
86
92
}
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
8
pub(super) fn load<R: Runtime, M: Mockable<R>>(
94
8
    imm: &Immutable<R>,
95
8
    storage: &IptStorageHandle,
96
8
    config: &watch::Receiver<Arc<OnionServiceConfig>>,
97
8
    mockable: &mut M,
98
8
    publish_set: &PublishIptSet,
99
8
) -> Result<Vec<IptRelay>, StartupError> {
100
8
    let on_disk = storage.load().map_err(StartupError::LoadState)?;
101

            
102
8
    let Some(on_disk) = on_disk else {
103
4
        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
4
    let StateRecord { ipt_relays, stored } = on_disk;
109
4

            
110
4
    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
4
    let mut ipt_relays: Vec<_> = ipt_relays
114
4
        .into_iter()
115
12
        .map(|rrelay| {
116
12
            // Load one IPT relay
117
12
            let RelayRecord {
118
12
                relay,
119
12
                planned_retirement,
120
12
                ipts,
121
12
            } = rrelay;
122
12
            let planned_retirement = tloading.load_future(planned_retirement);
123
            // Load the IPTs at this relay, restarting their establishers, etc.
124
12
            let ipts = ipts
125
12
                .into_iter()
126
12
                .map(|ipt| ipt.load_restart(imm, config, mockable, &relay))
127
12
                .try_collect()?;
128
12
            Ok::<_, StartupError>(IptRelay {
129
12
                relay,
130
12
                planned_retirement,
131
12
                ipts,
132
12
            })
133
12
        })
134
4
        .try_collect()?;
135

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

            
138
4
    Ok(ipt_relays)
139
8
}
140

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

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

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

            
184
12
        Ok(ipt)
185
12
    }
186
}