Skip to main content

prns_runtime/runtime/
persistence_snapshots.rs

1use alloc::string::String;
2use alloc::vec;
3use alloc::vec::Vec;
4
5use prns_core::crypto::ratchets::LastRotated;
6use prns_core::crypto::X25519SecretKey;
7use prns_core::engine::{EngineState, InstantMillis};
8use prns_core::identity::vault::{IdentityLabel, IdentityVault};
9use prns_core::identity::Zeroizing;
10use prns_core::storage::StorageLayout;
11use prns_core::wire::DestinationHash;
12
13/// One sealed self-ratchet blob per tracked destination, zeroized on drop.
14pub struct SelfRatchetsSnapshot {
15    pub blobs: Vec<(DestinationHash, Zeroizing<Vec<u8>>)>,
16}
17
18pub struct SelfRatchetSnapshot {
19    pub destination: DestinationHash,
20    pub sealed: Zeroizing<Vec<u8>>,
21}
22
23/// The sealed region images of one snapshot pass and the engine instant they were taken at — the timebase high-water a flush of these images should record.
24pub struct PersistedStateSnapshot {
25    pub routing_table: Vec<u8>,
26    pub tunnels: Vec<u8>,
27    pub destination_identities: Vec<u8>,
28    pub taken_at: InstantMillis,
29}
30
31pub fn snapshot_persisted_state<S: StorageLayout>(
32    engine: &EngineState<S>,
33    taken_at: InstantMillis,
34) -> Option<PersistedStateSnapshot> {
35    let mut routing_table =
36        vec![
37            0u8;
38            prns_core::persistence::routing_table_snapshot_len(engine.persisted_route_rows())
39        ];
40    let mut tunnels =
41        vec![
42            0u8;
43            prns_core::persistence::tunnels_snapshot_len(engine.persisted_tunnel_rows().count())
44        ];
45    let mut destination_identities = vec![
46        0u8;
47        prns_core::persistence::destination_identities_snapshot_len(
48            engine.destination_identities(),
49        )
50    ];
51
52    let (Ok(routes_len), Ok(tunnels_len), Ok(destination_identities_len)) = (
53        prns_core::persistence::write_routing_table_snapshot(
54            engine.persisted_route_rows(),
55            &mut routing_table,
56        ),
57        prns_core::persistence::write_tunnels_snapshot(
58            engine.persisted_tunnel_rows(),
59            &mut tunnels,
60        ),
61        prns_core::persistence::write_destination_identities_snapshot(
62            engine.destination_identities(),
63            &mut destination_identities,
64        ),
65    ) else {
66        return None;
67    };
68
69    routing_table.truncate(routes_len);
70    tunnels.truncate(tunnels_len);
71    destination_identities.truncate(destination_identities_len);
72    Some(PersistedStateSnapshot {
73        routing_table,
74        tunnels,
75        destination_identities,
76        taken_at,
77    })
78}
79
80pub fn snapshot_self_ratchets<S: StorageLayout>(engine: &EngineState<S>) -> SelfRatchetsSnapshot {
81    let blobs = engine
82        .persisted_self_ratchet_rows()
83        .filter_map(|(destination, last_rotated, secrets)| {
84            seal_self_ratchet(last_rotated, secrets).map(|sealed| (destination, sealed))
85        })
86        .collect();
87    SelfRatchetsSnapshot { blobs }
88}
89
90pub fn snapshot_self_ratchet<S: StorageLayout>(
91    engine: &EngineState<S>,
92    destination: DestinationHash,
93) -> Option<SelfRatchetSnapshot> {
94    let (last_rotated, secrets) = engine.persisted_self_ratchet_row(&destination)?;
95    let sealed = seal_self_ratchet(last_rotated, secrets)?;
96    Some(SelfRatchetSnapshot {
97        destination,
98        sealed,
99    })
100}
101
102fn seal_self_ratchet(
103    last_rotated: LastRotated,
104    secrets: &[X25519SecretKey],
105) -> Option<Zeroizing<Vec<u8>>> {
106    let mut sealed = Zeroizing::new(vec![
107        0u8;
108        prns_core::persistence::self_ratchets_snapshot_len(
109            secrets.len()
110        )
111    ]);
112    let written =
113        prns_core::persistence::write_self_ratchets_snapshot(last_rotated, secrets, &mut sealed)
114            .ok()?;
115    sealed.truncate(written);
116    Some(sealed)
117}
118
119#[allow(clippy::expect_used)]
120#[must_use]
121pub fn self_ratchet_identity_label(destination: &DestinationHash) -> IdentityLabel {
122    let mut label = String::with_capacity("ratchets.".len() + destination.as_bytes().len() * 2);
123    label.push_str("ratchets.");
124    for byte in destination.as_bytes() {
125        let _ = core::fmt::Write::write_fmt(&mut label, format_args!("{byte:02x}"));
126    }
127    IdentityLabel::new(&label).expect("a hex destination under a fixed prefix is label-lawful")
128}
129
130impl SelfRatchetsSnapshot {
131    pub fn store_into<V: IdentityVault>(self, vault: &mut V) -> Result<u32, V::Error> {
132        let mut flushed_count = 0u32;
133        for (destination, sealed) in self.blobs {
134            vault.store_blob(&self_ratchet_identity_label(&destination), &sealed)?;
135            flushed_count = flushed_count.saturating_add(1);
136        }
137        Ok(flushed_count)
138    }
139}
140
141impl SelfRatchetSnapshot {
142    pub fn store_into<V: IdentityVault>(self, vault: &mut V) -> Result<(), V::Error> {
143        vault.store_blob(
144            &self_ratchet_identity_label(&self.destination),
145            &self.sealed,
146        )
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use prns_core::identity::vault::{IdentitySecretKey, Removal};
154    use prns_core::identity::IDENTITY_SECRET_KEY_LEN;
155
156    #[derive(Default)]
157    struct CountingVault {
158        labels: Vec<String>,
159    }
160
161    impl IdentityVault for CountingVault {
162        type Error = core::convert::Infallible;
163
164        fn load(&self, _label: &IdentityLabel) -> Result<Option<IdentitySecretKey>, Self::Error> {
165            Ok(None)
166        }
167
168        fn store(
169            &mut self,
170            _label: &IdentityLabel,
171            _secret: &[u8; IDENTITY_SECRET_KEY_LEN],
172        ) -> Result<(), Self::Error> {
173            Ok(())
174        }
175
176        fn remove(&mut self, _label: &IdentityLabel) -> Result<Removal, Self::Error> {
177            Ok(Removal::NothingStored)
178        }
179
180        fn stored_blob_len(&self, _label: &IdentityLabel) -> Result<Option<usize>, Self::Error> {
181            Ok(None)
182        }
183
184        fn load_blob<'b>(
185            &self,
186            _label: &IdentityLabel,
187            _buf: &'b mut [u8],
188        ) -> Result<Option<&'b [u8]>, Self::Error> {
189            Ok(None)
190        }
191
192        fn store_blob(&mut self, label: &IdentityLabel, _blob: &[u8]) -> Result<(), Self::Error> {
193            self.labels.push(label.as_str().into());
194            Ok(())
195        }
196    }
197
198    #[test]
199    fn one_ratchet_snapshot_stores_under_its_destination_label() {
200        let destination = DestinationHash::new([0x5A; 16]);
201        let snapshot = SelfRatchetSnapshot {
202            destination,
203            sealed: Zeroizing::new(vec![0xA5; 64]),
204        };
205        let mut vault = CountingVault::default();
206
207        assert_eq!(snapshot.store_into(&mut vault), Ok(()));
208        assert_eq!(
209            vault.labels,
210            vec![self_ratchet_identity_label(&destination).to_string()]
211        );
212    }
213}