use std::collections::HashMap;
use eyre::Result;
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq)]
pub struct DecryptedData(pub Vec<u8>);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EncryptedData {
pub data: String,
pub content_encryption_key: String,
}
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)]
pub struct Diff {
pub host: HostId,
pub tag: String,
pub local: Option<RecordIdx>,
pub remote: Option<RecordIdx>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Host {
pub id: HostId,
pub name: String,
}
impl Host {
pub fn new(id: HostId) -> Self {
Host {
id,
name: String::new(),
}
}
}
new_uuid!(RecordId);
new_uuid!(HostId);
pub type RecordIdx = u64;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TypedBuilder)]
pub struct Record<Data> {
#[builder(default = RecordId(crate::utils::uuid_v7()))]
pub id: RecordId,
pub idx: RecordIdx,
pub host: Host,
#[builder(default = time::OffsetDateTime::now_utc().unix_timestamp_nanos() as u64)]
pub timestamp: u64,
pub version: String,
pub tag: String,
pub data: Data,
}
#[derive(Debug, Copy, Clone)]
pub struct AdditionalData<'a> {
pub id: &'a RecordId,
pub idx: &'a u64,
pub version: &'a str,
pub tag: &'a str,
pub host: &'a HostId,
}
impl<Data> Record<Data> {
pub fn append(&self, data: Vec<u8>) -> Record<DecryptedData> {
Record::builder()
.host(self.host.clone())
.version(self.version.clone())
.idx(self.idx + 1)
.tag(self.tag.clone())
.data(DecryptedData(data))
.build()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RecordStatus {
pub hosts: HashMap<HostId, HashMap<String, RecordIdx>>,
}
impl Default for RecordStatus {
fn default() -> Self {
Self::new()
}
}
impl Extend<(HostId, String, RecordIdx)> for RecordStatus {
fn extend<T: IntoIterator<Item = (HostId, String, RecordIdx)>>(&mut self, iter: T) {
for (host, tag, tail_idx) in iter {
self.set_raw(host, tag, tail_idx);
}
}
}
impl RecordStatus {
pub fn new() -> RecordStatus {
RecordStatus {
hosts: HashMap::new(),
}
}
pub fn set(&mut self, tail: Record<DecryptedData>) {
self.set_raw(tail.host.id, tail.tag, tail.idx)
}
pub fn set_raw(&mut self, host: HostId, tag: String, tail_id: RecordIdx) {
self.hosts.entry(host).or_default().insert(tag, tail_id);
}
pub fn get(&self, host: HostId, tag: String) -> Option<RecordIdx> {
self.hosts.get(&host).and_then(|v| v.get(&tag)).cloned()
}
pub fn diff(&self, other: &Self) -> Vec<Diff> {
let mut ret = Vec::new();
for (host, tag_map) in self.hosts.iter() {
for (tag, idx) in tag_map.iter() {
match other.get(*host, tag.clone()) {
Some(t) if t.eq(idx) => continue,
Some(t) => ret.push(Diff {
host: *host,
tag: tag.clone(),
local: Some(*idx),
remote: Some(t),
}),
None => ret.push(Diff {
host: *host,
tag: tag.clone(),
local: Some(*idx),
remote: None,
}),
};
}
}
for (host, tag_map) in other.hosts.iter() {
for (tag, idx) in tag_map.iter() {
match self.get(*host, tag.clone()) {
Some(_) => continue,
None => ret.push(Diff {
host: *host,
tag: tag.clone(),
remote: Some(*idx),
local: None,
}),
};
}
}
ret.sort();
ret
}
}
pub trait Encryption {
fn re_encrypt(
data: EncryptedData,
ad: AdditionalData,
old_key: &[u8; 32],
new_key: &[u8; 32],
) -> Result<EncryptedData> {
let data = Self::decrypt(data, ad, old_key)?;
Ok(Self::encrypt(data, ad, new_key))
}
fn encrypt(data: DecryptedData, ad: AdditionalData, key: &[u8; 32]) -> EncryptedData;
fn decrypt(data: EncryptedData, ad: AdditionalData, key: &[u8; 32]) -> Result<DecryptedData>;
}
impl Record<DecryptedData> {
pub fn encrypt<E: Encryption>(self, key: &[u8; 32]) -> Record<EncryptedData> {
let ad = AdditionalData {
id: &self.id,
version: &self.version,
tag: &self.tag,
host: &self.host.id,
idx: &self.idx,
};
Record {
data: E::encrypt(self.data, ad, key),
id: self.id,
host: self.host,
idx: self.idx,
timestamp: self.timestamp,
version: self.version,
tag: self.tag,
}
}
}
impl Record<EncryptedData> {
pub fn decrypt<E: Encryption>(self, key: &[u8; 32]) -> Result<Record<DecryptedData>> {
let ad = AdditionalData {
id: &self.id,
version: &self.version,
tag: &self.tag,
host: &self.host.id,
idx: &self.idx,
};
Ok(Record {
data: E::decrypt(self.data, ad, key)?,
id: self.id,
host: self.host,
idx: self.idx,
timestamp: self.timestamp,
version: self.version,
tag: self.tag,
})
}
pub fn re_encrypt<E: Encryption>(
self,
old_key: &[u8; 32],
new_key: &[u8; 32],
) -> Result<Record<EncryptedData>> {
let ad = AdditionalData {
id: &self.id,
version: &self.version,
tag: &self.tag,
host: &self.host.id,
idx: &self.idx,
};
Ok(Record {
data: E::re_encrypt(self.data, ad, old_key, new_key)?,
id: self.id,
host: self.host,
idx: self.idx,
timestamp: self.timestamp,
version: self.version,
tag: self.tag,
})
}
}
#[cfg(test)]
mod tests {
use crate::record::{Host, HostId};
use super::{DecryptedData, Diff, Record, RecordStatus};
use pretty_assertions::assert_eq;
fn test_record() -> Record<DecryptedData> {
Record::builder()
.host(Host::new(HostId(crate::utils::uuid_v7())))
.version("v1".into())
.tag(crate::utils::uuid_v7().simple().to_string())
.data(DecryptedData(vec![0, 1, 2, 3]))
.idx(0)
.build()
}
#[test]
fn record_index() {
let mut index = RecordStatus::new();
let record = test_record();
index.set(record.clone());
let tail = index.get(record.host.id, record.tag);
assert_eq!(
record.idx,
tail.expect("tail not in store"),
"tail in store did not match"
);
}
#[test]
fn record_index_overwrite() {
let mut index = RecordStatus::new();
let record = test_record();
let child = record.append(vec![1, 2, 3]);
index.set(record.clone());
index.set(child.clone());
let tail = index.get(record.host.id, record.tag);
assert_eq!(
child.idx,
tail.expect("tail not in store"),
"tail in store did not match"
);
}
#[test]
fn record_index_no_diff() {
let mut index1 = RecordStatus::new();
let mut index2 = RecordStatus::new();
let record1 = test_record();
index1.set(record1.clone());
index2.set(record1);
let diff = index1.diff(&index2);
assert_eq!(0, diff.len(), "expected empty diff");
}
#[test]
fn record_index_single_diff() {
let mut index1 = RecordStatus::new();
let mut index2 = RecordStatus::new();
let record1 = test_record();
let record2 = record1.append(vec![1, 2, 3]);
index1.set(record1);
index2.set(record2.clone());
let diff = index1.diff(&index2);
assert_eq!(1, diff.len(), "expected single diff");
assert_eq!(
diff[0],
Diff {
host: record2.host.id,
tag: record2.tag,
remote: Some(1),
local: Some(0)
}
);
}
#[test]
fn record_index_multi_diff() {
let mut index1 = RecordStatus::new();
let mut index2 = RecordStatus::new();
let store1record1 = test_record();
let store1record2 = store1record1.append(vec![1, 2, 3]);
let store2record1 = test_record();
let store2record2 = store2record1.append(vec![1, 2, 3]);
let store3record1 = test_record();
let store4record1 = test_record();
index1.set(store1record1);
index1.set(store2record1);
index2.set(store1record2);
index2.set(store2record2);
index2.set(store3record1);
index1.set(store4record1);
let diff1 = index1.diff(&index2);
let diff2 = index2.diff(&index1);
assert_eq!(4, diff1.len());
assert_eq!(4, diff2.len());
dbg!(&diff1, &diff2);
let smol_diff_1: Vec<(HostId, String)> =
diff1.iter().map(|v| (v.host, v.tag.clone())).collect();
let smol_diff_2: Vec<(HostId, String)> =
diff1.iter().map(|v| (v.host, v.tag.clone())).collect();
assert_eq!(smol_diff_1, smol_diff_2);
assert_eq!(index1.diff(&index1).len(), 0);
assert_eq!(index2.diff(&index2).len(), 0);
}
}