1use std::collections::HashMap;
2
3use eyre::Result;
4use serde::{Deserialize, Serialize};
5use typed_builder::TypedBuilder;
6use uuid::Uuid;
7
8#[derive(Clone, Debug, PartialEq)]
9pub struct DecryptedData(pub Vec<u8>);
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct EncryptedData {
13 pub data: String,
14 pub content_encryption_key: String,
15}
16
17#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)]
18pub struct Diff {
19 pub host: HostId,
20 pub tag: String,
21 pub local: Option<RecordIdx>,
22 pub remote: Option<RecordIdx>,
23}
24
25#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
26pub struct Host {
27 pub id: HostId,
28 pub name: String,
29}
30
31impl Host {
32 pub fn new(id: HostId) -> Self {
33 Host {
34 id,
35 name: String::new(),
36 }
37 }
38}
39
40new_uuid!(RecordId);
41new_uuid!(HostId);
42
43pub type RecordIdx = u64;
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TypedBuilder)]
47pub struct Record<Data> {
48 #[builder(default = RecordId(crate::utils::uuid_v7()))]
50 pub id: RecordId,
51
52 pub idx: RecordIdx,
54
55 pub host: Host,
60
61 #[builder(default = time::OffsetDateTime::now_utc().unix_timestamp_nanos() as u64)]
63 pub timestamp: u64,
64
65 pub version: String,
68
69 pub tag: String,
71
72 pub data: Data,
74}
75
76#[derive(Debug, Copy, Clone)]
78pub struct AdditionalData<'a> {
79 pub id: &'a RecordId,
80 pub idx: &'a u64,
81 pub version: &'a str,
82 pub tag: &'a str,
83 pub host: &'a HostId,
84}
85
86impl<Data> Record<Data> {
87 pub fn append(&self, data: Vec<u8>) -> Record<DecryptedData> {
88 Record::builder()
89 .host(self.host.clone())
90 .version(self.version.clone())
91 .idx(self.idx + 1)
92 .tag(self.tag.clone())
93 .data(DecryptedData(data))
94 .build()
95 }
96}
97
98#[derive(Debug, Serialize, Deserialize)]
101pub struct RecordStatus {
102 pub hosts: HashMap<HostId, HashMap<String, RecordIdx>>,
104}
105
106impl Default for RecordStatus {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl Extend<(HostId, String, RecordIdx)> for RecordStatus {
113 fn extend<T: IntoIterator<Item = (HostId, String, RecordIdx)>>(&mut self, iter: T) {
114 for (host, tag, tail_idx) in iter {
115 self.set_raw(host, tag, tail_idx);
116 }
117 }
118}
119
120impl RecordStatus {
121 pub fn new() -> RecordStatus {
122 RecordStatus {
123 hosts: HashMap::new(),
124 }
125 }
126
127 pub fn set(&mut self, tail: Record<DecryptedData>) {
129 self.set_raw(tail.host.id, tail.tag, tail.idx)
130 }
131
132 pub fn set_raw(&mut self, host: HostId, tag: String, tail_id: RecordIdx) {
133 self.hosts.entry(host).or_default().insert(tag, tail_id);
134 }
135
136 pub fn get(&self, host: HostId, tag: String) -> Option<RecordIdx> {
137 self.hosts.get(&host).and_then(|v| v.get(&tag)).cloned()
138 }
139
140 pub fn diff(&self, other: &Self) -> Vec<Diff> {
148 let mut ret = Vec::new();
149
150 for (host, tag_map) in self.hosts.iter() {
152 for (tag, idx) in tag_map.iter() {
153 match other.get(*host, tag.clone()) {
154 Some(t) if t.eq(idx) => continue,
156
157 Some(t) => ret.push(Diff {
159 host: *host,
160 tag: tag.clone(),
161 local: Some(*idx),
162 remote: Some(t),
163 }),
164
165 None => ret.push(Diff {
167 host: *host,
168 tag: tag.clone(),
169 local: Some(*idx),
170 remote: None,
171 }),
172 };
173 }
174 }
175
176 for (host, tag_map) in other.hosts.iter() {
181 for (tag, idx) in tag_map.iter() {
182 match self.get(*host, tag.clone()) {
183 Some(_) => continue,
185
186 None => ret.push(Diff {
187 host: *host,
188 tag: tag.clone(),
189 remote: Some(*idx),
190 local: None,
191 }),
192 };
193 }
194 }
195
196 ret.sort();
198 ret
199 }
200}
201
202pub trait Encryption {
203 fn re_encrypt(
204 data: EncryptedData,
205 ad: AdditionalData,
206 old_key: &[u8; 32],
207 new_key: &[u8; 32],
208 ) -> Result<EncryptedData> {
209 let data = Self::decrypt(data, ad, old_key)?;
210 Ok(Self::encrypt(data, ad, new_key))
211 }
212 fn encrypt(data: DecryptedData, ad: AdditionalData, key: &[u8; 32]) -> EncryptedData;
213 fn decrypt(data: EncryptedData, ad: AdditionalData, key: &[u8; 32]) -> Result<DecryptedData>;
214}
215
216impl Record<DecryptedData> {
217 pub fn encrypt<E: Encryption>(self, key: &[u8; 32]) -> Record<EncryptedData> {
218 let ad = AdditionalData {
219 id: &self.id,
220 version: &self.version,
221 tag: &self.tag,
222 host: &self.host.id,
223 idx: &self.idx,
224 };
225 Record {
226 data: E::encrypt(self.data, ad, key),
227 id: self.id,
228 host: self.host,
229 idx: self.idx,
230 timestamp: self.timestamp,
231 version: self.version,
232 tag: self.tag,
233 }
234 }
235}
236
237impl Record<EncryptedData> {
238 pub fn decrypt<E: Encryption>(self, key: &[u8; 32]) -> Result<Record<DecryptedData>> {
239 let ad = AdditionalData {
240 id: &self.id,
241 version: &self.version,
242 tag: &self.tag,
243 host: &self.host.id,
244 idx: &self.idx,
245 };
246 Ok(Record {
247 data: E::decrypt(self.data, ad, key)?,
248 id: self.id,
249 host: self.host,
250 idx: self.idx,
251 timestamp: self.timestamp,
252 version: self.version,
253 tag: self.tag,
254 })
255 }
256
257 pub fn re_encrypt<E: Encryption>(
258 self,
259 old_key: &[u8; 32],
260 new_key: &[u8; 32],
261 ) -> Result<Record<EncryptedData>> {
262 let ad = AdditionalData {
263 id: &self.id,
264 version: &self.version,
265 tag: &self.tag,
266 host: &self.host.id,
267 idx: &self.idx,
268 };
269 Ok(Record {
270 data: E::re_encrypt(self.data, ad, old_key, new_key)?,
271 id: self.id,
272 host: self.host,
273 idx: self.idx,
274 timestamp: self.timestamp,
275 version: self.version,
276 tag: self.tag,
277 })
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use crate::record::{Host, HostId};
284
285 use super::{DecryptedData, Diff, Record, RecordStatus};
286 use pretty_assertions::assert_eq;
287
288 fn test_record() -> Record<DecryptedData> {
289 Record::builder()
290 .host(Host::new(HostId(crate::utils::uuid_v7())))
291 .version("v1".into())
292 .tag(crate::utils::uuid_v7().simple().to_string())
293 .data(DecryptedData(vec![0, 1, 2, 3]))
294 .idx(0)
295 .build()
296 }
297
298 #[test]
299 fn record_index() {
300 let mut index = RecordStatus::new();
301 let record = test_record();
302
303 index.set(record.clone());
304
305 let tail = index.get(record.host.id, record.tag);
306
307 assert_eq!(
308 record.idx,
309 tail.expect("tail not in store"),
310 "tail in store did not match"
311 );
312 }
313
314 #[test]
315 fn record_index_overwrite() {
316 let mut index = RecordStatus::new();
317 let record = test_record();
318 let child = record.append(vec![1, 2, 3]);
319
320 index.set(record.clone());
321 index.set(child.clone());
322
323 let tail = index.get(record.host.id, record.tag);
324
325 assert_eq!(
326 child.idx,
327 tail.expect("tail not in store"),
328 "tail in store did not match"
329 );
330 }
331
332 #[test]
333 fn record_index_no_diff() {
334 let mut index1 = RecordStatus::new();
337 let mut index2 = RecordStatus::new();
338
339 let record1 = test_record();
340
341 index1.set(record1.clone());
342 index2.set(record1);
343
344 let diff = index1.diff(&index2);
345
346 assert_eq!(0, diff.len(), "expected empty diff");
347 }
348
349 #[test]
350 fn record_index_single_diff() {
351 let mut index1 = RecordStatus::new();
354 let mut index2 = RecordStatus::new();
355
356 let record1 = test_record();
357 let record2 = record1.append(vec![1, 2, 3]);
358
359 index1.set(record1);
360 index2.set(record2.clone());
361
362 let diff = index1.diff(&index2);
363
364 assert_eq!(1, diff.len(), "expected single diff");
365 assert_eq!(
366 diff[0],
367 Diff {
368 host: record2.host.id,
369 tag: record2.tag,
370 remote: Some(1),
371 local: Some(0)
372 }
373 );
374 }
375
376 #[test]
377 fn record_index_multi_diff() {
378 let mut index1 = RecordStatus::new();
380 let mut index2 = RecordStatus::new();
381
382 let store1record1 = test_record();
383 let store1record2 = store1record1.append(vec![1, 2, 3]);
384
385 let store2record1 = test_record();
386 let store2record2 = store2record1.append(vec![1, 2, 3]);
387
388 let store3record1 = test_record();
389
390 let store4record1 = test_record();
391
392 index1.set(store1record1);
394 index1.set(store2record1);
395
396 index2.set(store1record2);
398 index2.set(store2record2);
399 index2.set(store3record1);
400
401 index1.set(store4record1);
403
404 let diff1 = index1.diff(&index2);
405 let diff2 = index2.diff(&index1);
406
407 assert_eq!(4, diff1.len());
409 assert_eq!(4, diff2.len());
410
411 dbg!(&diff1, &diff2);
412
413 let smol_diff_1: Vec<(HostId, String)> =
416 diff1.iter().map(|v| (v.host, v.tag.clone())).collect();
417 let smol_diff_2: Vec<(HostId, String)> =
418 diff1.iter().map(|v| (v.host, v.tag.clone())).collect();
419
420 assert_eq!(smol_diff_1, smol_diff_2);
421
422 assert_eq!(index1.diff(&index1).len(), 0);
424 assert_eq!(index2.diff(&index2).len(), 0);
425 }
426}