use core::fmt::Debug;
use anyhash::Hasher;
#[cfg(feature = "dev")]
use arbitrary::Arbitrary;
use bab_rs::{HasherWrite, William3Hasher};
use compact_u64::{cu64_decode_canonic_standalone, cu64_decode_standalone};
use ufotofu::codec_prelude::*;
use crate::{authorisation::PossiblyAuthorisedEntry, groupings::Coordinatelike, prelude::*};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, bon::Builder)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
#[builder(state_mod(vis = "pub"), on(_, overwritable))]
pub struct Entry {
pub(crate) namespace_id: NamespaceId,
pub(crate) subspace_id: SubspaceId,
pub(crate) path: Path,
#[builder(into)]
pub(crate) timestamp: Timestamp,
pub(crate) payload_length: u64,
pub(crate) payload_digest: PayloadDigest,
}
#[doc(hidden)]
pub type BuilderComplete = entry_builder::SetPayloadLength<
entry_builder::SetPayloadDigest<
entry_builder::SetTimestamp<
entry_builder::SetPath<entry_builder::SetSubspaceId<entry_builder::SetNamespaceId>>,
>,
>,
>;
impl Entry {
pub fn prefilled_builder<E>(source: &E) -> EntryBuilder<BuilderComplete>
where
E: Entrylike + ?Sized,
{
Self::builder()
.namespace_id(source.namespace_id().clone())
.subspace_id(source.subspace_id().clone())
.path(source.path().clone())
.timestamp(source.timestamp())
.payload_digest(source.payload_digest().clone())
.payload_length(source.payload_length())
}
pub fn from_entrylike<E>(entrylike_to_clone: &E) -> Self
where
E: Entrylike + ?Sized,
{
Self::prefilled_builder(entrylike_to_clone).build()
}
pub fn into_authorised_entry(
self,
write_capability: &WriteCapability,
secret: &SubspaceSecret,
) -> Result<AuthorisedEntry, DoesNotAuthorise> {
let authorisation_token =
AuthorisationToken::new_for_entry(&self, write_capability, secret)?;
Ok(PossiblyAuthorisedEntry {
entry: self,
authorisation_token,
}
.into_authorised_entry().expect("`AuthorisationToken::new_for_entry` must produce an authorisation token that authorises the entry"))
}
}
impl Keylike for Entry {
fn subspace_id(&self) -> &SubspaceId {
&self.subspace_id
}
fn path(&self) -> &Path {
&self.path
}
}
impl Coordinatelike for Entry {
fn timestamp(&self) -> Timestamp {
self.timestamp
}
}
impl Namespaced for Entry {
fn namespace_id(&self) -> &NamespaceId {
&self.namespace_id
}
}
impl Entrylike for Entry {
fn payload_length(&self) -> u64 {
self.payload_length
}
fn payload_digest(&self) -> &PayloadDigest {
&self.payload_digest
}
}
impl Encodable for Entry {
async fn encode<C>(&self, consumer: &mut C) -> Result<(), C::Error>
where
C: BulkConsumer<Item = u8> + ?Sized,
{
self.encode_entry(consumer).await
}
}
impl EncodableKnownLength for Entry {
fn len_of_encoding(&self) -> usize {
self.length_of_entry_encoding()
}
}
impl Decodable for Entry {
type ErrorReason = Blame;
async fn decode<P>(
producer: &mut P,
) -> Result<Self, DecodeError<P::Final, P::Error, Self::ErrorReason>>
where
P: BulkProducer<Item = u8> + ?Sized,
Self: Sized,
{
Ok(Self {
namespace_id: producer
.produce_decoded_canonic()
.await
.map_err(|err| err.map_other(Into::into))?,
subspace_id: producer
.produce_decoded_canonic()
.await
.map_err(|err| err.map_other(Into::into))?,
path: producer.produce_decoded().await?,
timestamp: cu64_decode_standalone(producer)
.await
.map_err(|err| err.map_other(|_| unreachable!()))?
.into(),
payload_length: cu64_decode_standalone(producer)
.await
.map_err(|err| err.map_other(|_| unreachable!()))?,
payload_digest: producer
.produce_decoded_canonic()
.await
.map_err(|err| err.map_other(Into::into))?,
})
}
}
impl DecodableCanonic for Entry {
type ErrorCanonic = Blame;
async fn decode_canonic<P>(
producer: &mut P,
) -> Result<Self, DecodeError<P::Final, P::Error, Self::ErrorCanonic>>
where
P: BulkProducer<Item = u8> + ?Sized,
Self: Sized,
{
Ok(Self {
namespace_id: producer
.produce_decoded_canonic()
.await
.map_err(|err| err.map_other(Into::into))?,
subspace_id: producer
.produce_decoded_canonic()
.await
.map_err(|err| err.map_other(Into::into))?,
path: producer.produce_decoded_canonic().await?,
timestamp: cu64_decode_canonic_standalone(producer)
.await
.map_err(|err| err.map_other(|_| unreachable!()))?
.into(),
payload_length: cu64_decode_canonic_standalone(producer)
.await
.map_err(|err| err.map_other(|_| unreachable!()))?,
payload_digest: producer
.produce_decoded_canonic()
.await
.map_err(|err| err.map_other(Into::into))?,
})
}
}
impl<State: entry_builder::State> EntryBuilder<State> {
#[cfg(feature = "std")]
pub fn now(self) -> Result<EntryBuilder<entry_builder::SetTimestamp<State>>, HifitimeError> {
Timestamp::now().map(|ts| self.timestamp(ts))
}
pub fn payload<Payload>(
self,
payload: Payload,
) -> EntryBuilder<entry_builder::SetPayloadLength<entry_builder::SetPayloadDigest<State>>>
where
Payload: AsRef<[u8]>,
{
let mut hasher = William3Hasher::default();
hasher.write(payload.as_ref());
let digest = hasher.finish().into();
let length = u64::try_from(payload.as_ref().len()).expect("payload too long");
self.payload_digest(digest).payload_length(length)
}
pub async fn payload_async<P>(
self,
payload_producer: &mut P,
) -> Result<
EntryBuilder<entry_builder::SetPayloadLength<entry_builder::SetPayloadDigest<State>>>,
P::Error,
>
where
P: BulkProducer<Item = u8, Final = ()>,
{
let mut hasher = William3Hasher::default();
let mut payload_len = 0;
loop {
match payload_producer
.expose_items_sync(|partial_payload| {
hasher.write(partial_payload);
payload_len += partial_payload.len();
(partial_payload.len(), ())
})
.await?
{
Either::Left(_) => {}
Either::Right(_) => {
let payload_digest = hasher.finish().into();
let payload_length = u64::try_from(payload_len).expect("payload too long");
return Ok(self
.payload_digest(payload_digest)
.payload_length(payload_length));
}
}
}
}
pub fn default_namespace_id(self) -> EntryBuilder<entry_builder::SetNamespaceId<State>> {
self.namespace_id(crate::defaults::default_namespace_id())
}
pub fn default_subspace_id(self) -> EntryBuilder<entry_builder::SetSubspaceId<State>> {
self.subspace_id(crate::defaults::default_subspace_id())
}
pub fn default_payload(
self,
) -> EntryBuilder<entry_builder::SetPayloadLength<entry_builder::SetPayloadDigest<State>>> {
self.payload_digest(crate::defaults::default_payload_digest())
.payload_length(crate::defaults::DEFAULT_PAYLOAD_LENGTH)
}
}