willow25 0.7.0

A ready-to-use implementation of the Willow specifications.
Documentation
#[cfg(feature = "dev")]
use arbitrary::Arbitrary;

use crate::prelude::*;

/// An entry, together with an authorisation token that may or may not [authorise](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the entry.
///
/// ```
/// use rand::rngs::OsRng;
/// use willow25::prelude::*;
/// use willow25::authorisation::PossiblyAuthorisedEntry;
///
/// # #[cfg(feature = "dev")] {
/// let mut csprng = OsRng;
/// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
/// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
///
/// let my_entry = Entry::builder()
///     .namespace_id(namespace_id.clone())
///     .subspace_id(subspace_id.clone())
///     .path(path!("/ideas"))
///     .timestamp(12345)
///     .payload(b"chocolate with mustard")
///     .build();
///
/// let mut cap = WriteCapability::new_communal(
///     namespace_id.clone(),
///     subspace_id.clone(),
/// );
///
/// let auth_token = AuthorisationToken::new_for_entry(&my_entry, &cap, &secret).unwrap();
///
/// let pae = PossiblyAuthorisedEntry::new(
///     my_entry.clone(),
///     auth_token,
/// );
/// assert!(pae.into_authorised_entry().is_ok());
/// # }
/// ```
///
/// [Specification](https://willowprotocol.org/specs/data-model/index.html#PossiblyAuthorisedEntry)
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct PossiblyAuthorisedEntry {
    /// The entry.
    pub entry: Entry,
    /// The authorisation token, which may or may not [authorise](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the entry.
    pub authorisation_token: AuthorisationToken,
}

impl PossiblyAuthorisedEntry {
    /// Creates a new [`PossiblyAuthorisedEntry`] from an [`Entry`] and an [`AuthorisationToken`].
    pub fn new(entry: Entry, authorisation_token: AuthorisationToken) -> Self {
        Self {
            entry,
            authorisation_token,
        }
    }

    /// Checks whether `self.authorisation_token` [authorises](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) `self.entry`. If so, converts self into an [`AuthorisedEntry`], otherwise returns `Err(self)`.
    ///
    /// ```
    /// use rand::rngs::OsRng;
    /// use willow25::prelude::*;
    /// use willow25::authorisation::PossiblyAuthorisedEntry;
    ///
    /// # #[cfg(feature = "dev")] {
    /// let mut csprng = OsRng;
    /// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
    /// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
    ///
    /// let my_entry = Entry::builder()
    ///     .namespace_id(namespace_id.clone())
    ///     .subspace_id(subspace_id.clone())
    ///     .path(path!("/ideas"))
    ///     .timestamp(12345)
    ///     .payload(b"chocolate with mustard")
    ///     .build();
    ///
    /// let mut cap = WriteCapability::new_communal(
    ///     namespace_id.clone(),
    ///     subspace_id.clone(),
    /// );
    ///
    /// let auth_token = AuthorisationToken::new_for_entry(&my_entry, &cap, &secret).unwrap();
    ///
    /// let pae = PossiblyAuthorisedEntry::new(
    ///     my_entry.clone(),
    ///     auth_token.clone(),
    /// );
    /// assert!(pae.into_authorised_entry().is_ok());
    ///
    /// let someone_elses_entry = Entry::prefilled_builder(&my_entry)
    ///     .subspace_id(SubspaceId::from_bytes(&[18; 32]))
    ///     .build();
    ///
    /// let pae2 = PossiblyAuthorisedEntry::new(
    ///     someone_elses_entry.clone(),
    ///     auth_token,
    /// );
    /// assert!(pae2.into_authorised_entry().is_err());
    /// # }
    /// ```
    #[allow(clippy::result_large_err)]
    pub fn into_authorised_entry(self) -> Result<AuthorisedEntry, Self> {
        if self.authorisation_token.does_authorise(&self.entry) {
            Ok(AuthorisedEntry {
                entry: self.entry,
                authorisation_token: self.authorisation_token,
            })
        } else {
            Err(self)
        }
    }

    /// Converts self into an [`AuthorisedEntry`], without checking if `self.authorisation_token` [authorise](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) `self.entry`.
    ///
    /// ```
    /// use rand::rngs::OsRng;
    /// use willow25::prelude::*;
    /// use willow25::authorisation::PossiblyAuthorisedEntry;
    ///
    /// # #[cfg(feature = "dev")] {
    /// let mut csprng = OsRng;
    /// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
    /// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
    ///
    /// let entry = Entry::builder()
    ///     .namespace_id(namespace_id.clone())
    ///     .subspace_id(subspace_id.clone())
    ///     .path(path!("/ideas"))
    ///     .timestamp(12345)
    ///     .payload(b"chocolate with mustard")
    ///     .build();
    ///
    /// let mut cap = WriteCapability::new_communal(
    ///     namespace_id.clone(),
    ///     subspace_id.clone(),
    /// );
    ///
    /// let auth_token = AuthorisationToken::new_for_entry(&entry, &cap, &secret).unwrap();
    ///
    /// let pae = PossiblyAuthorisedEntry::new(
    ///     entry.clone(),
    ///     auth_token.clone(),
    /// );
    ///
    /// let authed = unsafe { pae.into_authorised_entry_unchecked() };
    /// assert_eq!(authed.entry(), &entry);
    /// assert_eq!(authed.authorisation_token(), &auth_token);
    /// # }
    /// ```
    ///
    /// #### Safety
    ///
    /// Undefined behaviour may occur if `self.authorisation_token.does_authorise(&self.entry)` is `false`. If it returns `true`, this method is safe to call.
    pub unsafe fn into_authorised_entry_unchecked(self) -> AuthorisedEntry {
        AuthorisedEntry {
            entry: self.entry,
            authorisation_token: self.authorisation_token,
        }
    }
}

impl Keylike for PossiblyAuthorisedEntry {
    fn subspace_id(&self) -> &SubspaceId {
        self.entry.subspace_id()
    }

    fn path(&self) -> &Path {
        self.entry.path()
    }
}

impl Coordinatelike for PossiblyAuthorisedEntry {
    fn timestamp(&self) -> Timestamp {
        self.entry.timestamp()
    }
}

impl Namespaced for PossiblyAuthorisedEntry {
    fn namespace_id(&self) -> &NamespaceId {
        self.entry.namespace_id()
    }
}

impl Entrylike for PossiblyAuthorisedEntry {
    fn payload_length(&self) -> u64 {
        self.entry.payload_length()
    }

    fn payload_digest(&self) -> &PayloadDigest {
        self.entry.payload_digest()
    }
}

/// An entry, together with an authorisation token that [authorises](https://willowprotocol.org/specs/data-model/index.html#is_authorised_write) the entry.
///
/// There are two typical scenarios for creating these: authorising an [`Entry`] yourself (via [`Entry::into_authorised_entry`] or [`crate::entry::EntrylikeExt::authorise`]), or receiving and verifying an untrusted authorisation token ([`PossiblyAuthorisedEntry::into_authorised_entry`]).
///
/// ```
/// use rand::rngs::OsRng;
/// use willow25::prelude::*;
/// use willow25::authorisation::PossiblyAuthorisedEntry;
///
/// # #[cfg(feature = "dev")] {
/// let mut csprng = OsRng;
/// let (subspace_id, secret) = randomly_generate_subspace(&mut csprng);
/// let namespace_id = NamespaceId::from_bytes(&[17; 32]);
///
/// let entry = Entry::builder()
///     .namespace_id(namespace_id.clone())
///     .subspace_id(subspace_id.clone())
///     .path(path!("/ideas"))
///     .timestamp(12345)
///     .payload(b"chocolate with mustard")
///     .build();
///
/// let mut cap = WriteCapability::new_communal(
///     namespace_id.clone(),
///     subspace_id.clone(),
/// );
///
/// let authed = entry.authorise(&cap, &secret);
/// let authed_alternative = entry.into_authorised_entry(&cap, &secret);
///
/// assert_eq!(authed, authed_alternative);
/// # }
/// ```
///
/// [Specification](https://willowprotocol.org/specs/data-model/index.html#AuthorisedEntry)
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct AuthorisedEntry {
    entry: Entry,
    authorisation_token: AuthorisationToken,
}

impl AuthorisedEntry {
    /// Consumes self, returning the entry and the authorisation token.
    pub fn into_parts(self) -> (Entry, AuthorisationToken) {
        (self.entry, self.authorisation_token)
    }

    /// Returns a reference to the entry.
    pub fn entry(&self) -> &Entry {
        &self.entry
    }

    /// Returns a reference to the authorisation token.
    pub fn authorisation_token(&self) -> &AuthorisationToken {
        &self.authorisation_token
    }
}

impl Keylike for AuthorisedEntry {
    fn subspace_id(&self) -> &SubspaceId {
        self.entry.subspace_id()
    }

    fn path(&self) -> &Path {
        self.entry.path()
    }
}

impl Coordinatelike for AuthorisedEntry {
    fn timestamp(&self) -> Timestamp {
        self.entry.timestamp()
    }
}

impl Namespaced for AuthorisedEntry {
    fn namespace_id(&self) -> &NamespaceId {
        self.entry.namespace_id()
    }
}

impl Entrylike for AuthorisedEntry {
    fn payload_length(&self) -> u64 {
        self.entry.payload_length()
    }

    fn payload_digest(&self) -> &PayloadDigest {
        self.entry.payload_digest()
    }
}