use super::{PrivateNode, PrivateNodeHeaderSerializable, REVISION_SEGMENT_DSI, TemporalKey};
use crate::{
error::FsError,
private::{RevisionRef, forest::traits::PrivateForest},
};
use anyhow::{Result, anyhow, bail};
use futures::TryStreamExt;
use rand_core::CryptoRngCore;
use skip_ratchet::Ratchet;
use std::{collections::BTreeMap, fmt::Debug};
use wnfs_common::{BlockStore, CODEC_RAW, Cid};
use wnfs_hamt::Hasher;
use wnfs_nameaccumulator::{Name, NameSegment};
#[derive(Debug, Clone, Eq)]
pub struct PrivateNodeHeader {
pub(crate) inumber: NameSegment,
pub(crate) ratchet: Ratchet,
pub(crate) name: Name,
}
impl PrivateNodeHeader {
pub(crate) fn new(parent_name: &Name, rng: &mut impl CryptoRngCore) -> Self {
let inumber = NameSegment::new(rng);
Self {
name: parent_name.with_segments_added(Some(inumber.clone())),
ratchet: Ratchet::from_rng(rng),
inumber,
}
}
pub(crate) fn advance_ratchet(&mut self) {
self.ratchet.inc();
}
pub(crate) fn update_name(&mut self, parent_name: &Name) {
self.name = parent_name.with_segments_added(Some(self.inumber.clone()));
}
pub(crate) fn update_ratchet(&mut self, ratchet: Ratchet) {
self.ratchet = ratchet;
}
pub(crate) fn reset_ratchet(&mut self, rng: &mut impl CryptoRngCore) {
self.update_ratchet(Ratchet::from_rng(rng));
}
pub(crate) fn derive_revision_ref(&self, forest: &impl PrivateForest) -> RevisionRef {
let temporal_key = self.derive_temporal_key();
let label = blake3::Hasher::hash(&forest.get_accumulated_name(&self.get_revision_name()));
RevisionRef {
label,
temporal_key,
}
}
#[inline]
pub fn derive_temporal_key(&self) -> TemporalKey {
TemporalKey::new(&self.ratchet)
}
pub(crate) fn derive_revision_segment(&self) -> NameSegment {
NameSegment::new_hashed(REVISION_SEGMENT_DSI, self.ratchet.key_derivation_data())
}
pub fn get_revision_name(&self) -> Name {
self.name
.with_segments_added(Some(self.derive_revision_segment()))
}
pub fn get_name(&self) -> &Name {
&self.name
}
pub async fn store(&self, store: &impl BlockStore, forest: &impl PrivateForest) -> Result<Cid> {
let temporal_key = self.derive_temporal_key();
let cbor_bytes = serde_ipld_dagcbor::to_vec(&self.to_serializable(forest))?;
let ciphertext = temporal_key.key_wrap_encrypt(&cbor_bytes)?;
Ok(store.put_block(ciphertext, CODEC_RAW).await?)
}
pub(crate) fn to_serializable(
&self,
forest: &impl PrivateForest,
) -> PrivateNodeHeaderSerializable {
PrivateNodeHeaderSerializable {
inumber: self.inumber.clone(),
ratchet: self.ratchet.clone(),
name: forest.get_accumulated_name(&self.name),
}
}
pub(crate) fn from_serializable(serializable: PrivateNodeHeaderSerializable) -> Self {
Self {
inumber: serializable.inumber,
ratchet: serializable.ratchet,
name: Name::new(serializable.name, []),
}
}
pub(crate) async fn load(
cid: &Cid,
temporal_key: &TemporalKey,
forest: &impl PrivateForest,
store: &impl BlockStore,
parent_name: Option<Name>,
) -> Result<Self> {
let ciphertext = store.get_block(cid).await?;
let cbor_bytes = temporal_key.key_wrap_decrypt(&ciphertext)?;
let decoded: PrivateNodeHeaderSerializable = serde_ipld_dagcbor::from_slice(&cbor_bytes)?;
let serialized_name = decoded.name.clone();
let mut header = Self::from_serializable(decoded);
if let Some(parent_name) = parent_name {
let name = parent_name.with_segments_added([header.inumber.clone()]);
let mounted_acc = forest.get_accumulated_name(&name);
if mounted_acc != serialized_name {
bail!(FsError::MountPointAndDeserializedNameMismatch(
format!("{mounted_acc:?}"),
format!("{serialized_name:?}"),
));
}
header.name = name;
}
Ok(header)
}
pub(crate) async fn get_multivalue(
&self,
forest: &impl PrivateForest,
store: &impl BlockStore,
) -> Result<Vec<(Cid, PrivateNode)>> {
let mountpoint = self.name.parent();
let name_hash =
blake3::Hasher::hash(&forest.get_accumulated_name(&self.get_revision_name()));
forest
.get_multivalue_by_hash(&name_hash, &self.derive_temporal_key(), store, mountpoint)
.try_collect::<Vec<_>>()
.await
}
pub(crate) async fn seek_unmerged_heads(
&mut self,
forest: &impl PrivateForest,
store: &impl BlockStore,
) -> Result<BTreeMap<Cid, PrivateNode>> {
let mut previous_keys = Vec::with_capacity(1);
let mut heads = BTreeMap::new();
loop {
let nodes = self.get_multivalue(forest, store).await?;
if nodes.is_empty() {
break;
}
for (cid, node) in nodes {
for (reach_back, encrypted_cid) in node.get_previous().iter() {
if *reach_back <= previous_keys.len() {
let key = &previous_keys[previous_keys.len() - reach_back];
let previous_cid = encrypted_cid.resolve_value(key)?;
heads.remove(previous_cid);
}
}
heads.insert(cid, node);
}
previous_keys.push(TemporalKey::new(&self.ratchet));
self.advance_ratchet();
}
Ok(heads)
}
pub(crate) fn ratchet_diff_for_merge(&self, other: &Self) -> Result<usize> {
self.ratchet
.compare(&other.ratchet, 10_000_000)
.map_err(|e| {
anyhow!("merge node set on different ratchet track (or history too far away): {e}")
})?
.try_into()
.map_err(|_| anyhow!("merge node set to past revision"))
}
}
impl PartialEq for PrivateNodeHeader {
fn eq(&self, other: &Self) -> bool {
self.inumber == other.inumber && self.ratchet == other.ratchet
}
}