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
pub(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
    // Convert the IPT relays (to the on-disk format)
54
    let ipt_relays = state
55
        .irelays
56
        .iter()
57
        .map(|irelay| {
58
            // Convert one IPT relay, with its IPTs, to the on-disk format
59
            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
                    // Convert one IPT - at least, the parts we store here
66
                    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

            
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
pub(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
    // Throughout, we use exhaustive struct patterns on the data we got from disk,
107
    // so we avoid missing any of the data.
108
    let StateRecord { ipt_relays, stored } = on_disk;
109

            
110
    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
    let mut ipt_relays: Vec<_> = ipt_relays
114
        .into_iter()
115
        .map(|rrelay| {
116
            // Load one IPT relay
117
            let RelayRecord {
118
                relay,
119
                planned_retirement,
120
                ipts,
121
            } = rrelay;
122
            let planned_retirement = tloading.load_future(planned_retirement);
123
            // Load the IPTs at this relay, restarting their establishers, etc.
124
            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

            
141
impl IptRecord {
142
    /// Recreate (load) one IPT
143
    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
            // last_descriptor_expiry_including_slop
161
            // is restored by the `import_new_expiry_times` call in `load`
162
            PromiseLastDescriptorExpiryNoneIsGood {},
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.
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
        })?;
180

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

            
184
        Ok(ipt)
185
    }
186
}