use std::{fs::File, rc::Rc};
use crate::DIDWebVHError;
use affinidi_data_integrity::DataIntegrityProof;
use ahash::HashMap;
use serde::{Deserialize, Serialize};
use tracing::warn;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WitnessProofShadow(Vec<WitnessProof>);
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WitnessProof {
pub version_id: Rc<String>,
pub proof: Vec<Rc<DataIntegrityProof>>,
#[serde(skip)]
pub future_entry: bool,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(try_from = "WitnessProofShadow")]
pub struct WitnessProofCollection {
pub(crate) proofs: WitnessProofShadow,
#[serde(skip)]
pub(crate) witness_version: HashMap<String, (Rc<String>, u32, Rc<DataIntegrityProof>)>,
}
impl TryFrom<WitnessProofShadow> for WitnessProofCollection {
type Error = DIDWebVHError;
fn try_from(proofs: WitnessProofShadow) -> Result<Self, Self::Error> {
Ok(WitnessProofCollection {
proofs,
..Default::default()
})
}
}
impl WitnessProofCollection {
pub fn add_proof(
&mut self,
version_id: &str,
proof: &DataIntegrityProof,
future_entry: bool,
) -> Result<(), DIDWebVHError> {
let Some((id, _)) = version_id.split_once('-') else {
return Err(DIDWebVHError::WitnessProofError(format!(
"Invalid versionID ({version_id}) in witness proofs! Expected n-hash, but missing n",
)));
};
let Ok(id): Result<u32, _> = str::parse(id) else {
return Err(DIDWebVHError::WitnessProofError(format!(
"Invalid versionID ({version_id}) in witness proofs! expected n-hash, where n is a number!",
)));
};
if !future_entry {
if let Some((p_version, p_id, p)) =
self.witness_version.get_mut(&proof.verification_method)
{
if &id > p_id {
for e in self.proofs.0.iter_mut() {
if e.version_id == *p_version {
e.proof
.retain(|i| i.verification_method != p.verification_method);
}
}
}
self.proofs.0.retain(|e| !e.proof.is_empty());
}
}
let rc_proof = Rc::new(proof.clone());
let version_id = if let Some(record) = self
.proofs
.0
.iter_mut()
.find(|p| *p.version_id == version_id)
{
record.proof.push(rc_proof.clone());
record.version_id.clone()
} else {
let version_id = Rc::new(version_id.to_string());
self.proofs.0.push(WitnessProof {
version_id: version_id.clone(),
future_entry,
proof: vec![rc_proof.clone()],
});
version_id
};
self.witness_version.insert(
proof.verification_method.clone(),
(version_id, id, rc_proof),
);
Ok(())
}
pub fn remove_version_id(&mut self, version_id: &str) {
self.proofs.0.retain(|p| *p.version_id != version_id);
}
pub fn get_proof_count(&self, version_id: &str) -> usize {
self.proofs
.0
.iter()
.find(|p| *p.version_id == version_id)
.map_or(0, |p| p.proof.len())
}
pub(crate) fn read_from_file(file_path: &str) -> Result<Self, DIDWebVHError> {
let file = File::open(file_path).map_err(|e| {
DIDWebVHError::WitnessProofError(format!(
"Couldn't open Witness Proofs file ({file_path}): {e}",
))
})?;
let proofs: WitnessProofShadow = serde_json::from_reader(file).map_err(|e| {
DIDWebVHError::WitnessProofError(format!(
"Couldn't deserialize Witness Proofs Data from file ({file_path}): {e}",
))
})?;
Ok(WitnessProofCollection {
proofs,
..Default::default()
})
}
pub fn save_to_file(&self, file_path: &str) -> Result<(), DIDWebVHError> {
let json_data = serde_json::to_string(&self.proofs).map_err(|e| {
DIDWebVHError::WitnessProofError(
format!("Couldn't serialize Witness Proofs Data: {e}",),
)
})?;
std::fs::write(file_path, json_data).map_err(|e| {
DIDWebVHError::WitnessProofError(format!(
"Couldn't write to Witness Proofs file ({file_path}): {e}",
))
})?;
Ok(())
}
pub fn get_proofs(&self, version_id: &str) -> Option<&WitnessProof> {
self.proofs.0.iter().find(|p| *p.version_id == version_id)
}
pub fn generate_proof_state(
&mut self,
highest_version_number: u32,
) -> Result<(), DIDWebVHError> {
let mut new_proofs_state = WitnessProofCollection::default();
for version in &self.proofs.0 {
let version_number = if let Some((prefix, _)) = version.version_id.split_once('-') {
prefix.parse::<u32>().map_err(|_| {
DIDWebVHError::WitnessProofError(format!(
"Invalid versionID ({}) in witness proofs! expected n-hash, where n is a number!",
version.version_id
))
})?
} else {
return Err(DIDWebVHError::WitnessProofError(format!(
"Invalid versionID ({}) in witness proofs! Expected n-hash, but missing n",
version.version_id
)));
};
if version_number > highest_version_number {
continue;
}
for proof in &version.proof {
new_proofs_state
.add_proof(
&version.version_id,
proof, false,
)
.map_err(|e| {
DIDWebVHError::WitnessProofError(format!(
"Error adding witness proof state to table: {e}",
))
})?;
}
}
self.witness_version = new_proofs_state.witness_version;
Ok(())
}
pub fn write_optimise_records(&mut self) -> Result<(), DIDWebVHError> {
for v in &self.proofs.0 {
if v.future_entry {
continue;
}
let Some((id, _)) = v.version_id.split_once('-') else {
return Err(DIDWebVHError::WitnessProofError(format!(
"Invalid versionID ({}) in witness proofs! Expected n-hash, but missing n",
v.version_id
)));
};
let Ok(id): Result<u32, _> = str::parse(id) else {
return Err(DIDWebVHError::WitnessProofError(format!(
"Invalid versionID ({}) in witness proofs! expected n-hash, where n is a number!",
v.version_id
)));
};
for p in &v.proof {
if let Some((_, proof_id, _)) = self.witness_version.get_mut(&p.verification_method)
{
if &id > proof_id {
*proof_id = id;
}
} else {
self.witness_version.insert(
p.verification_method.clone(),
(v.version_id.clone(), id, p.clone()),
);
}
}
}
self.proofs.0.retain_mut(|v| {
if v.future_entry {
return true;
}
let Some((id, _)) = v.version_id.split_once('-') else {
warn!(
"Invalid versionID ({}) in witness proofs! Expected n-hash, but missing n", v.version_id);
return false;
};
let Ok(id): Result<u32, _> = str::parse(id) else {
warn!(
"Invalid versionID ({}) in witness proofs! expected n-hash, where n is a number!", v.version_id);
return false;
};
v.proof
.retain(|p| &id >= if let Some((_, proof_id, _)) = self.witness_version.get(&p.verification_method) { proof_id } else {&0});
!v.proof.is_empty()
});
Ok(())
}
}