Skip to main content

willow_data_model/entry/
entry.rs

1use core::fmt::Debug;
2
3use anyhash::Hasher;
4#[cfg(feature = "dev")]
5use arbitrary::Arbitrary;
6
7use compact_u64::{cu64_decode_canonic_standalone, cu64_decode_standalone};
8use ufotofu::codec_prelude::*;
9
10use crate::{groupings::Coordinatelike, prelude::*};
11
12/// The metadata associated with each Willow [Payload](https://willowprotocol.org/specs/data-model/index.html#Payload) string.
13///
14/// [Entries](https://willowprotocol.org/specs/data-model/index.html#Entry) are the central concept in Willow. In order to make any bytestring of data accessible to Willow, you need to create an Entry describing its metadata. Specifically, an Entry consists of
15///
16/// - a [namespace_id](https://willowprotocol.org/specs/data-model/index.html#entry_namespace_id) (roughly, this addresses a universe of Willow data, fully independent from all data (i.e., Entries) of different namespace ids) of type `N`,
17/// - a [subspace_id](https://willowprotocol.org/specs/data-model/index.html#entry_subspace_id) (roughly, a fully indendent part of a namespace, typically subspaces correspond to individual users) of type `S`,
18/// - a [path](https://willowprotocol.org/specs/data-model/index.html#entry_path) (roughly, a file-system-like way of arranging payloads hierarchically within a subspace) of type [`Path`],
19/// - a [timestamp](https://willowprotocol.org/specs/data-model/index.html#entry_timestamp) (newer Entries can overwrite certain older Entries),
20/// - a [payload_length](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) (the length of the payload string), and
21/// - a [payload_digest](https://willowprotocol.org/specs/data-model/index.html#entry_payload_digest) (a secure hash of the payload string being inserted into Willow).
22///
23/// To access these six fields, use the methods of the [`Entrylike`] trait (which [`Entry`] implements). The [`EntrylikeExt`] trait provides additional helper methods, for example, methods to check which Entries can delete which other Entries.
24///
25/// To create Entries, use the [`Entry::builder`] or [`Entry::prefilled_builder`] functions.
26///
27/// # Example
28///
29/// ```
30/// use willow_data_model::prelude::*;
31///
32/// let entry = Entry::builder()
33///     .namespace_id("family")
34///     .subspace_id("alfie")
35///     .path(Path::<4, 4, 4>::new())
36///     .timestamp(12345)
37///     .payload_digest("some_hash")
38///     .payload_length(17)
39///     .build();
40///
41/// assert_eq!(*entry.wdm_subspace_id(), "alfie");
42///
43/// let newer = Entry::prefilled_builder(&entry).timestamp(99999).build();
44/// assert!(newer.wdm_prunes(&entry));
45/// ```
46///
47/// [Spec definition](https://willowprotocol.org/specs/data-model/index.html#Entry).
48#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, bon::Builder)]
49#[cfg_attr(feature = "dev", derive(Arbitrary))]
50#[builder(state_mod(vis = "pub"), on(_, overwritable))]
51pub struct Entry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> {
52    pub(crate) namespace_id: N,
53    pub(crate) subspace_id: S,
54    pub(crate) path: Path<MCL, MCC, MPL>,
55    #[builder(into)]
56    pub(crate) timestamp: Timestamp,
57    pub(crate) payload_length: u64,
58    pub(crate) payload_digest: PD,
59}
60
61#[doc(hidden)]
62pub type BuilderComplete = entry_builder::SetPayloadLength<
63    entry_builder::SetPayloadDigest<
64        entry_builder::SetTimestamp<
65            entry_builder::SetPath<entry_builder::SetSubspaceId<entry_builder::SetNamespaceId>>,
66        >,
67    >,
68>;
69
70impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
71    Entry<MCL, MCC, MPL, N, S, PD>
72{
73    /// Creates a builder which is prefilled with the data from some other entry.
74    ///
75    /// Use this function to create modified copies of entries.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use willow_data_model::prelude::*;
81    ///
82    /// // Supplying all necessary data yields an entry.
83    /// let first_entry = Entry::builder()
84    ///     .namespace_id("family")
85    ///     .subspace_id("alfie")
86    ///     .path(Path::<4, 4, 4>::new())
87    ///     .timestamp(12345)
88    ///     .payload_digest("some_hash")
89    ///     .payload_length(17)
90    ///     .build();
91    ///
92    /// assert_eq!(*first_entry.wdm_payload_digest(), "some_hash");
93    ///
94    /// let second_entry = Entry::prefilled_builder(&first_entry)
95    ///     .timestamp(67890)
96    ///     .payload_digest("another_hash")
97    ///     .payload_length(4)
98    ///     .build();
99    ///
100    /// assert_eq!(*second_entry.wdm_payload_digest(), "another_hash");
101    /// ```
102    pub fn prefilled_builder<E>(
103        source: &E,
104    ) -> EntryBuilder<MCL, MCC, MPL, N, S, PD, BuilderComplete>
105    where
106        E: Entrylike<MCL, MCC, MPL, N, S, PD> + ?Sized,
107        N: Clone,
108        S: Clone,
109        PD: Clone,
110    {
111        Self::builder()
112            .namespace_id(source.wdm_namespace_id().clone())
113            .subspace_id(source.wdm_subspace_id().clone())
114            .path(source.wdm_path().clone())
115            .timestamp(source.wdm_timestamp())
116            .payload_digest(source.wdm_payload_digest().clone())
117            .payload_length(source.wdm_payload_length())
118    }
119
120    /// Creates an [`Entry`] with [equal data](EntrylikeExt::wdm_entry_eq) to that of the given [`Entrylike`].
121    ///
122    /// ```
123    /// # #[cfg(feature = "dev")] {
124    /// use willow_data_model::prelude::*;
125    /// use willow_data_model::test_parameters::*;
126    ///
127    /// let entry1 = Entry::builder()
128    ///     .namespace_id(Family)
129    ///     .subspace_id(Alfie)
130    ///     .path(Path::<4, 4, 4>::new())
131    ///     .timestamp(12345)
132    ///     .payload_digest(Spades)
133    ///     .payload_length(2)
134    ///     .build();
135    ///
136    /// let entry2 = Entry::from_entrylike(&entry1);
137    /// assert_eq!(entry1, entry2);
138    /// # }
139    /// ```
140    pub fn from_entrylike<E>(entrylike_to_clone: &E) -> Self
141    where
142        E: Entrylike<MCL, MCC, MPL, N, S, PD> + ?Sized,
143        N: Clone,
144        S: Clone,
145        PD: Clone,
146    {
147        Self::prefilled_builder(entrylike_to_clone).build()
148    }
149
150    /// Turns `self` into an [`AuthorisedEntry`] by creating an authorisation token for it.
151    ///
152    /// ```
153    /// # #[cfg(feature = "dev")] {
154    /// use willow_data_model::prelude::*;
155    /// use willow_data_model::test_parameters::*;
156    ///
157    /// let entry = Entry::builder()
158    ///     .namespace_id(Family)
159    ///     .subspace_id(Alfie)
160    ///     .path(Path::<4, 4, 4>::new())
161    ///     .timestamp(12345)
162    ///     .payload_digest(Spades)
163    ///     .payload_length(2)
164    ///     .build();
165    ///
166    /// let authed = entry.into_authorised_entry::<TestSubspaceSignature>(&AlfieSecret).unwrap();
167    /// assert_eq!(authed.authorisation_token(), &AlfieSignature);
168    /// # }
169    /// ```
170    pub fn into_authorised_entry<AT>(
171        self,
172        ingredients: &AT::Ingredients,
173    ) -> Result<AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>, AT::CreationError>
174    where
175        AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD> + Debug,
176        N: Clone + Debug,
177        S: Clone + Debug,
178        PD: Clone + Debug,
179    {
180        let authorisation_token = AuthorisationToken::new_for_entry(&self, ingredients)?;
181
182        Ok(PossiblyAuthorisedEntry {
183                entry: self,
184                authorisation_token,
185            }
186            .into_authorised_entry().expect("`AuthorisationToken::new_for_entry` must produce an authorisation token that authorises the entry"))
187    }
188}
189
190impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Keylike<MCL, MCC, MPL, S>
191    for Entry<MCL, MCC, MPL, N, S, PD>
192{
193    fn wdm_subspace_id(&self) -> &S {
194        &self.subspace_id
195    }
196
197    fn wdm_path(&self) -> &Path<MCL, MCC, MPL> {
198        &self.path
199    }
200}
201
202impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
203    Coordinatelike<MCL, MCC, MPL, S> for Entry<MCL, MCC, MPL, N, S, PD>
204{
205    fn wdm_timestamp(&self) -> Timestamp {
206        self.timestamp
207    }
208}
209
210impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Namespaced<N>
211    for Entry<MCL, MCC, MPL, N, S, PD>
212{
213    fn wdm_namespace_id(&self) -> &N {
214        &self.namespace_id
215    }
216}
217
218impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
219    Entrylike<MCL, MCC, MPL, N, S, PD> for Entry<MCL, MCC, MPL, N, S, PD>
220{
221    fn wdm_payload_length(&self) -> u64 {
222        self.payload_length
223    }
224
225    fn wdm_payload_digest(&self) -> &PD {
226        &self.payload_digest
227    }
228}
229
230/// Implements [encode_entry](https://willowprotocol.org/specs/encodings/index.html#encode_entry).
231impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Encodable
232    for Entry<MCL, MCC, MPL, N, S, PD>
233where
234    N: Encodable,
235    S: Encodable,
236    PD: Encodable,
237{
238    async fn encode<C>(&self, consumer: &mut C) -> Result<(), C::Error>
239    where
240        C: BulkConsumer<Item = u8> + ?Sized,
241    {
242        self.wdm_encode_entry(consumer).await
243    }
244}
245
246/// Implements [encode_entry](https://willowprotocol.org/specs/encodings/index.html#encode_entry).
247impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> EncodableKnownLength
248    for Entry<MCL, MCC, MPL, N, S, PD>
249where
250    N: EncodableKnownLength,
251    S: EncodableKnownLength,
252    PD: EncodableKnownLength,
253{
254    fn len_of_encoding(&self) -> usize {
255        self.wdm_length_of_entry_encoding()
256    }
257}
258
259/// Implements [EncodeEntry](https://willowprotocol.org/specs/encodings/index.html#EncodeEntry).
260impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Decodable
261    for Entry<MCL, MCC, MPL, N, S, PD>
262where
263    N: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
264    S: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
265    PD: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
266{
267    type ErrorReason = Blame;
268
269    async fn decode<P>(
270        producer: &mut P,
271    ) -> Result<Self, DecodeError<P::Final, P::Error, Self::ErrorReason>>
272    where
273        P: BulkProducer<Item = u8> + ?Sized,
274        Self: Sized,
275    {
276        Ok(Self {
277            namespace_id: producer
278                .produce_decoded_canonic()
279                .await
280                .map_err(|err| err.map_other(Into::into))?,
281            subspace_id: producer
282                .produce_decoded_canonic()
283                .await
284                .map_err(|err| err.map_other(Into::into))?,
285            path: producer.produce_decoded().await?,
286            timestamp: cu64_decode_standalone(producer)
287                .await
288                .map_err(|err| err.map_other(|_| unreachable!()))?
289                .into(),
290            payload_length: cu64_decode_standalone(producer)
291                .await
292                .map_err(|err| err.map_other(|_| unreachable!()))?,
293            payload_digest: producer
294                .produce_decoded_canonic()
295                .await
296                .map_err(|err| err.map_other(Into::into))?,
297        })
298    }
299}
300
301/// Implements [encode_entry](https://willowprotocol.org/specs/encodings/index.html#encode_entry).
302impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> DecodableCanonic
303    for Entry<MCL, MCC, MPL, N, S, PD>
304where
305    N: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
306    S: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
307    PD: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
308{
309    type ErrorCanonic = Blame;
310
311    async fn decode_canonic<P>(
312        producer: &mut P,
313    ) -> Result<Self, DecodeError<P::Final, P::Error, Self::ErrorCanonic>>
314    where
315        P: BulkProducer<Item = u8> + ?Sized,
316        Self: Sized,
317    {
318        Ok(Self {
319            namespace_id: producer
320                .produce_decoded_canonic()
321                .await
322                .map_err(|err| err.map_other(Into::into))?,
323            subspace_id: producer
324                .produce_decoded_canonic()
325                .await
326                .map_err(|err| err.map_other(Into::into))?,
327            path: producer.produce_decoded_canonic().await?,
328            timestamp: cu64_decode_canonic_standalone(producer)
329                .await
330                .map_err(|err| err.map_other(|_| unreachable!()))?
331                .into(),
332            payload_length: cu64_decode_canonic_standalone(producer)
333                .await
334                .map_err(|err| err.map_other(|_| unreachable!()))?,
335            payload_digest: producer
336                .produce_decoded_canonic()
337                .await
338                .map_err(|err| err.map_other(Into::into))?,
339        })
340    }
341}
342
343impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, State: entry_builder::State>
344    EntryBuilder<MCL, MCC, MPL, N, S, PD, State>
345{
346    /// Sets the [timestamp](https://willowprotocol.org/specs/data-model/index.html#entry_timestamp) of the entry being built to the current time.
347    #[cfg(feature = "std")]
348    pub fn now(
349        self,
350    ) -> Result<
351        EntryBuilder<MCL, MCC, MPL, N, S, PD, entry_builder::SetTimestamp<State>>,
352        HifitimeError,
353    > {
354        Timestamp::now().map(|ts| self.timestamp(ts))
355    }
356
357    /// Sets the [payload_length](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) and [payload_digest](https://willowprotocol.org/specs/data-model/index.html#entry_payload_digest) of the entry being built to those of the given [Payload](https://willowprotocol.org/specs/data-model/index.html#Payload).
358    ///
359    /// The type parameter `H` is the type of the [`Hasher`] which hashes the payload into a payload digest (of type `Digest: Into<PD>`). Its [`Default`] impl provides the initial state of the hasher.
360    pub fn payload<Payload, H, Digest>(
361        self,
362        payload: Payload,
363    ) -> EntryBuilder<
364        MCL,
365        MCC,
366        MPL,
367        N,
368        S,
369        PD,
370        entry_builder::SetPayloadLength<entry_builder::SetPayloadDigest<State>>,
371    >
372    where
373        Payload: AsRef<[u8]>,
374        H: Default + Hasher<Digest>,
375        Digest: Into<PD>,
376    {
377        let mut hasher = H::default();
378        hasher.write(payload.as_ref());
379
380        let digest = hasher.finish().into();
381        // realistically this shouldn't panic; a payload should never be longer than `u64::MAX` bytes
382        let length = u64::try_from(payload.as_ref().len()).expect("payload too long");
383
384        self.payload_digest(digest).payload_length(length)
385    }
386
387    /// Sets the [payload_length](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) and [payload_digest](https://willowprotocol.org/specs/data-model/index.html#entry_payload_digest) of the entry being built to those of the payload given by the producer.
388    ///
389    /// The type parameter `H` is the type of the [`Hasher`] which hashes the payload into a payload digest (of type `Digest: Into<PD>`). Its [`Default`] impl provides the initial state of the hasher.
390    pub async fn payload_async<P, H, Digest>(
391        self,
392        payload_producer: &mut P,
393    ) -> Result<
394        EntryBuilder<
395            MCL,
396            MCC,
397            MPL,
398            N,
399            S,
400            PD,
401            entry_builder::SetPayloadLength<entry_builder::SetPayloadDigest<State>>,
402        >,
403        P::Error,
404    >
405    where
406        P: BulkProducer<Item = u8, Final = ()>,
407        H: Default + Hasher<Digest>,
408        Digest: Into<PD>,
409    {
410        let mut hasher = H::default();
411        let mut payload_len = 0;
412        loop {
413            match payload_producer
414                .expose_items_sync(|partial_payload| {
415                    hasher.write(partial_payload);
416                    payload_len += partial_payload.len();
417                    (partial_payload.len(), ())
418                })
419                .await?
420            {
421                Either::Left(_) => {}
422                Either::Right(_) => {
423                    let payload_digest = hasher.finish().into();
424                    // realistically this shouldn't panic; a payload should never be longer than `u64::MAX` bytes
425                    let payload_length = u64::try_from(payload_len).expect("payload too long");
426                    return Ok(self
427                        .payload_digest(payload_digest)
428                        .payload_length(payload_length));
429                }
430            }
431        }
432    }
433}