willow-data-model 0.7.0

The core datatypes of Willow, an eventually consistent data store with improved distributed deletion.
Documentation
//! Module providing abstractions for [`PrivatePathContext`](https://willowprotocol.org/specs/encodings/index.html#PrivatePathContext)s, which facilitate [private encodings](https://willowprotocol.org/specs/encodings/index.html#enc_private). The [`PrivatePathContext`] is the common context between two parties which makes it possible to decode a private encoding. You probably don't need to interact with this unless you are building your own private encoding scheme.

use crate::paths::{Path, path_extends_path};
#[cfg(feature = "dev")]
use arbitrary::Arbitrary;
use compact_u64::{cu64_decode_standalone, cu64_encode_standalone};
use derive_more::{Display, Error};
use ufotofu::{
    codec::{Blame, DecodeError},
    codec_prelude::RelativeDecodable,
    codec_relative::RelativeEncodable,
};

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
/// The immutable [`PrivatePathContext`](https://willowprotocol.org/specs/encodings/index.html#PrivatePathContext) necessary to privately encode a [`Path`] relative to one of its prefixes, while keeping secret all Components that coincide with a third Path.
pub struct PrivatePathContext<const MCL: usize, const MCC: usize, const MPL: usize> {
    /// The Path whose Components are to be kept private.
    private: Path<MCL, MCC, MPL>,
    /// The prefix relative to which we encode.
    rel: Path<MCL, MCC, MPL>,
}

/// An error arising from trying to construct an invalid [`PrivatePathContext`]
#[derive(Debug, Display, Error, Clone, Copy)]
#[display("private and relative paths are not related")]
pub struct ComponentsNotRelatedError;

impl<const MCL: usize, const MCC: usize, const MPL: usize> PrivatePathContext<MCL, MCC, MPL> {
    /// Returns a new [`PrivatePathContext`] with the given private and relative paths.
    ///
    /// Will return a [`ComponentsNotRelatedError`] if the given private and relative paths are not [related](https://willowprotocol.org/specs/data-model/index.html#path_related).
    pub fn new(
        private: Path<MCL, MCC, MPL>,
        rel: Path<MCL, MCC, MPL>,
    ) -> Result<Self, ComponentsNotRelatedError> {
        if !private.is_related_to(&rel) {
            return Err(ComponentsNotRelatedError {});
        }

        Ok(Self { private, rel })
    }

    /// Returns a new [`PrivatePathContext`] with the given private and relative paths *without* checking if the given private and relative paths are [related](https://willowprotocol.org/specs/data-model/index.html#path_related).
    ///
    /// #### Safety
    ///
    /// Undefined behaviour if private and rel are not [related](https://willowprotocol.org/specs/data-model/index.html#path_related).
    pub unsafe fn new_unchecked(private: Path<MCL, MCC, MPL>, rel: Path<MCL, MCC, MPL>) -> Self {
        Self { private, rel }
    }

    /// Returns the private path of `&self`.
    pub fn private(&self) -> &Path<MCL, MCC, MPL> {
        &self.private
    }

    /// Returns the relative path of `&self`.
    pub fn rel(&self) -> &Path<MCL, MCC, MPL> {
        &self.rel
    }
}

#[cfg(feature = "dev")]
impl<'a, const MCL: usize, const MCC: usize, const MPL: usize> Arbitrary<'a>
    for PrivatePathContext<MCL, MCC, MPL>
{
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        let private: Path<MCL, MCC, MPL> = Arbitrary::arbitrary(u)?;
        let rel: Path<MCL, MCC, MPL> = Arbitrary::arbitrary(u)?;

        if !private.is_related_to(&rel) {
            return Err(arbitrary::Error::IncorrectFormat);
        }

        Ok(Self { private, rel })
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize>
    RelativeEncodable<PrivatePathContext<MCL, MCC, MPL>> for Path<MCL, MCC, MPL>
{
    async fn relative_encode<C>(
        &self,
        rel: &PrivatePathContext<MCL, MCC, MPL>,
        consumer: &mut C,
    ) -> Result<(), C::Error>
    where
        C: ufotofu::BulkConsumer<Item = u8> + ?Sized,
    {
        let rel_count = rel.rel.component_count();
        let private_count = rel.private.component_count();

        if private_count <= rel_count {
            path_extends_path::encode_path_extends_path(self, &rel.rel, consumer).await?;
        } else {
            let lcp = self.longest_common_prefix(&rel.private);

            let lcp_len = lcp.component_count();
            cu64_encode_standalone(lcp_len as u64, consumer).await?;

            if lcp_len >= private_count {
                path_extends_path::encode_path_extends_path(self, rel.private(), consumer).await?;
            }
        }

        Ok(())
    }

    /// Fails if the path is not a [prefix](https://willowprotocol.org/specs/data-model/index.html#path_prefix) of `rel.rel`, OR if `self` is not [related to]((https://willowprotocol.org/specs/data-model/index.html#path_related)) to `rel.private`.
    fn can_be_encoded_relative_to(&self, rel: &PrivatePathContext<MCL, MCC, MPL>) -> bool {
        if !rel.rel().is_prefix_of(self) {
            return false;
        }

        if !self.is_related_to(rel.private()) {
            return false;
        }

        true
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize>
    RelativeDecodable<PrivatePathContext<MCL, MCC, MPL>> for Path<MCL, MCC, MPL>
{
    type ErrorReason = Blame;

    async fn relative_decode<P>(
        rel: &PrivatePathContext<MCL, MCC, MPL>,
        producer: &mut P,
    ) -> Result<Self, ufotofu::codec::DecodeError<P::Final, P::Error, Self::ErrorReason>>
    where
        P: ufotofu::BulkProducer<Item = u8> + ?Sized,
        Self: Sized,
    {
        let rel_count = rel.rel.component_count();
        let private_count = rel.private.component_count();

        if private_count <= rel_count {
            path_extends_path::decode_path_extends_path(rel.rel(), producer).await
        } else {
            let private_component_count = cu64_decode_standalone(producer)
                .await
                .map_err(|err| err.map_other(|_| Blame::TheirFault))?;

            // This filters out the case where the decoded value is not a prefix of rel.rel.
            if private_component_count < rel.rel().component_count() as u64 {
                return Err(DecodeError::Other(Blame::TheirFault));
            }

            if private_component_count >= private_count as u64 {
                path_extends_path::decode_path_extends_path(rel.private(), producer).await
            } else {
                let decoded = rel
                    .private
                    .create_prefix(private_component_count as usize)
                    .ok_or(DecodeError::Other(Blame::TheirFault))?;

                Ok(decoded)
            }
        }
    }
}