prns_runtime/runtime/
persistence_snapshots.rs1use 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
13pub 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
23pub 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}