willow_data_model/
entry.rs

1#[cfg(feature = "dev")]
2use arbitrary::Arbitrary;
3use compact_u64::CompactU64;
4
5use crate::{parameters::AuthorisationToken, path::Path};
6
7/// A Timestamp is a 64-bit unsigned integer, that is, a natural number between zero (inclusive) and 2^64 (exclusive).
8/// Timestamps are to be interpreted as a time in microseconds since the Unix epoch.
9///
10/// [Definition](https://willowprotocol.org/specs/data-model/index.html#Timestamp).
11pub type Timestamp = u64;
12
13/// The metadata associated with each Payload.
14///
15/// [Definition](https://willowprotocol.org/specs/data-model/index.html#Entry).
16#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
17#[cfg_attr(feature = "dev", derive(Arbitrary))]
18pub struct Entry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> {
19    /// The identifier of the namespace to which the [`Entry`] belongs.
20    namespace_id: N,
21    /// The identifier of the subspace to which the [`Entry`] belongs.
22    subspace_id: S,
23    /// The [`Path`] to which the [`Entry`] was written.
24    path: Path<MCL, MCC, MPL>,
25    /// The claimed creation time of the [`Entry`].
26    timestamp: Timestamp,
27    /// The result of applying hash_payload to the Payload.
28    payload_digest: PD,
29    /// The length of the Payload in bytes.
30    payload_length: u64,
31}
32
33impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
34    Entry<MCL, MCC, MPL, N, S, PD>
35{
36    /// Creates a new [`Entry`].
37    pub fn new(
38        namespace_id: N,
39        subspace_id: S,
40        path: Path<MCL, MCC, MPL>,
41        timestamp: Timestamp,
42        payload_length: u64,
43        payload_digest: PD,
44    ) -> Self {
45        Entry {
46            namespace_id,
47            subspace_id,
48            path,
49            timestamp,
50            payload_length,
51            payload_digest,
52        }
53    }
54
55    /// Returns a reference to the identifier of the namespace to which the [`Entry`] belongs.
56    pub fn namespace_id(&self) -> &N {
57        &self.namespace_id
58    }
59
60    /// Returns a reference to the identifier of the subspace_id to which the [`Entry`] belongs.
61    pub fn subspace_id(&self) -> &S {
62        &self.subspace_id
63    }
64
65    /// Returns a reference to the [`Path`] to which the [`Entry`] was written.
66    pub fn path(&self) -> &Path<MCL, MCC, MPL> {
67        &self.path
68    }
69
70    /// Returns the claimed creation time of the [`Entry`].
71    pub fn timestamp(&self) -> Timestamp {
72        self.timestamp
73    }
74
75    /// Returns the length of the Payload in bytes.
76    pub fn payload_length(&self) -> u64 {
77        self.payload_length
78    }
79
80    /// Returns a reference to the result of applying hash_payload to the Payload.
81    pub fn payload_digest(&self) -> &PD {
82        &self.payload_digest
83    }
84}
85
86impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Entry<MCL, MCC, MPL, N, S, PD>
87where
88    PD: PartialOrd,
89{
90    /// Returns if this [`Entry`] is newer than another using their timestamps.
91    /// Tie-breaks using the Entries' payload digest and payload length otherwise.
92    ///
93    /// [Definition](https://willowprotocol.org/specs/data-model/index.html#entry_newer).
94    pub fn is_newer_than(&self, other: &Self) -> bool {
95        other.timestamp < self.timestamp
96            || (other.timestamp == self.timestamp && other.payload_digest < self.payload_digest)
97            || (other.timestamp == self.timestamp
98                && other.payload_digest == self.payload_digest
99                && other.payload_length < self.payload_length)
100    }
101}
102
103use ufotofu::{BulkConsumer, BulkProducer};
104
105use ufotofu_codec::{
106    Blame, Decodable, DecodableCanonic, DecodableSync, DecodeError, Encodable, EncodableKnownSize,
107    EncodableSync,
108};
109
110impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Encodable
111    for Entry<MCL, MCC, MPL, N, S, PD>
112where
113    N: Encodable,
114    S: Encodable,
115    PD: Encodable,
116{
117    async fn encode<C>(&self, consumer: &mut C) -> Result<(), <C>::Error>
118    where
119        C: BulkConsumer<Item = u8>,
120    {
121        self.namespace_id.encode(consumer).await?;
122        self.subspace_id.encode(consumer).await?;
123        self.path.encode(consumer).await?;
124
125        CompactU64(self.timestamp).encode(consumer).await?;
126        CompactU64(self.payload_length).encode(consumer).await?;
127
128        self.payload_digest.encode(consumer).await?;
129
130        Ok(())
131    }
132}
133
134impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Decodable
135    for Entry<MCL, MCC, MPL, N, S, PD>
136where
137    N: Decodable,
138    S: Decodable,
139    PD: Decodable,
140    Blame: From<N::ErrorReason> + From<S::ErrorReason> + From<PD::ErrorReason>,
141{
142    type ErrorReason = Blame;
143
144    async fn decode<P>(
145        producer: &mut P,
146    ) -> Result<Self, ufotofu_codec::DecodeError<P::Final, P::Error, Self::ErrorReason>>
147    where
148        P: BulkProducer<Item = u8>,
149        Self: Sized,
150    {
151        let namespace_id = N::decode(producer)
152            .await
153            .map_err(DecodeError::map_other_from)?;
154        let subspace_id = S::decode(producer)
155            .await
156            .map_err(DecodeError::map_other_from)?;
157        let path = Path::<MCL, MCC, MPL>::decode(producer).await?;
158        let timestamp = CompactU64::decode(producer)
159            .await
160            .map_err(DecodeError::map_other_from)?
161            .0;
162        let payload_length = CompactU64::decode(producer)
163            .await
164            .map_err(DecodeError::map_other_from)?
165            .0;
166        let payload_digest = PD::decode(producer)
167            .await
168            .map_err(DecodeError::map_other_from)?;
169
170        Ok(Entry {
171            namespace_id,
172            subspace_id,
173            path,
174            timestamp,
175            payload_length,
176            payload_digest,
177        })
178    }
179}
180
181impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> DecodableCanonic
182    for Entry<MCL, MCC, MPL, N, S, PD>
183where
184    N: DecodableCanonic,
185    S: DecodableCanonic,
186    PD: DecodableCanonic,
187    Blame: From<N::ErrorReason>
188        + From<S::ErrorReason>
189        + From<PD::ErrorReason>
190        + From<N::ErrorCanonic>
191        + From<S::ErrorCanonic>
192        + From<PD::ErrorCanonic>,
193{
194    type ErrorCanonic = Blame;
195
196    async fn decode_canonic<P>(
197        producer: &mut P,
198    ) -> Result<Self, ufotofu_codec::DecodeError<P::Final, P::Error, Self::ErrorCanonic>>
199    where
200        P: BulkProducer<Item = u8>,
201        Self: Sized,
202    {
203        let namespace_id = N::decode_canonic(producer)
204            .await
205            .map_err(DecodeError::map_other_from)?;
206        let subspace_id = S::decode_canonic(producer)
207            .await
208            .map_err(DecodeError::map_other_from)?;
209        let path = Path::<MCL, MCC, MPL>::decode_canonic(producer).await?;
210        let timestamp = CompactU64::decode_canonic(producer)
211            .await
212            .map_err(DecodeError::map_other_from)?
213            .0;
214        let payload_length = CompactU64::decode_canonic(producer)
215            .await
216            .map_err(DecodeError::map_other_from)?
217            .0;
218        let payload_digest = PD::decode_canonic(producer)
219            .await
220            .map_err(DecodeError::map_other_from)?;
221
222        Ok(Entry {
223            namespace_id,
224            subspace_id,
225            path,
226            timestamp,
227            payload_length,
228            payload_digest,
229        })
230    }
231}
232
233impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> EncodableKnownSize
234    for Entry<MCL, MCC, MPL, N, S, PD>
235where
236    N: EncodableKnownSize,
237    S: EncodableKnownSize,
238    PD: EncodableKnownSize,
239{
240    fn len_of_encoding(&self) -> usize {
241        self.namespace_id.len_of_encoding()
242            + self.subspace_id.len_of_encoding()
243            + self.path.len_of_encoding()
244            + CompactU64(self.timestamp).len_of_encoding()
245            + CompactU64(self.payload_length).len_of_encoding()
246            + self.payload_digest.len_of_encoding()
247    }
248}
249
250impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> EncodableSync
251    for Entry<MCL, MCC, MPL, N, S, PD>
252where
253    N: EncodableSync,
254    S: EncodableSync,
255    PD: EncodableSync,
256{
257}
258
259impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> DecodableSync
260    for Entry<MCL, MCC, MPL, N, S, PD>
261where
262    N: DecodableSync,
263    S: DecodableSync,
264    PD: DecodableSync,
265    Blame: From<N::ErrorReason> + From<S::ErrorReason> + From<PD::ErrorReason>,
266{
267}
268
269/// An error indicating an [`AuthorisationToken`](https://willowprotocol.org/specs/data-model/index.html#AuthorisationToken) does not authorise the writing of this entry.
270#[derive(Debug)]
271pub struct UnauthorisedWriteError;
272
273impl core::fmt::Display for UnauthorisedWriteError {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        write!(
276            f,
277            "Tried to authorise the writing of an entry using an AuthorisationToken which does not permit it."
278        )
279    }
280}
281
282impl std::error::Error for UnauthorisedWriteError {}
283
284/// An AuthorisedEntry is a pair of an [`Entry`] and [`AuthorisationToken`] for which [`AuthorisationToken::is_authorised_write`] returns true.
285///
286/// [Definition](https://willowprotocol.org/specs/data-model/index.html#AuthorisedEntry).
287#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
288pub struct AuthorisedEntry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>(
289    Entry<MCL, MCC, MPL, N, S, PD>,
290    AT,
291);
292
293impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
294    AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
295where
296    AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD>,
297{
298    /// Returns an [`AuthorisedEntry`] if the token permits the writing of this entry, otherwise returns an [`UnauthorisedWriteError`].
299    pub fn new(
300        entry: Entry<MCL, MCC, MPL, N, S, PD>,
301        token: AT,
302    ) -> Result<Self, UnauthorisedWriteError>
303    where
304        AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD>,
305    {
306        if token.is_authorised_write(&entry) {
307            return Ok(Self(entry, token));
308        }
309
310        Err(UnauthorisedWriteError)
311    }
312}
313
314impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
315    AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
316{
317    /// Returns an [`AuthorisedEntry`] without checking if the token permits the writing of this entry.
318    ///
319    /// # Safety
320    /// Calling this method when `token.is_authorised_write(&entry)` would return `false` is immediate undefined behaviour!
321    pub unsafe fn new_unchecked(entry: Entry<MCL, MCC, MPL, N, S, PD>, token: AT) -> Self {
322        Self(entry, token)
323    }
324
325    /// Splits this into [`Entry`] and [`AuthorisationToken`] halves.
326    pub fn into_parts(self) -> (Entry<MCL, MCC, MPL, N, S, PD>, AT) {
327        (self.0, self.1)
328    }
329
330    /// Gets a reference to the [`Entry`].
331    pub fn entry(&self) -> &Entry<MCL, MCC, MPL, N, S, PD> {
332        &self.0
333    }
334
335    /// Gets a reference to the [`AuthorisationToken`].
336    pub fn token(&self) -> &AT {
337        &self.1
338    }
339}
340
341#[cfg(feature = "dev")]
342impl<'a, const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT> Arbitrary<'a>
343    for AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
344where
345    N: Arbitrary<'a>,
346    S: Arbitrary<'a>,
347    PD: Arbitrary<'a>,
348    AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD> + Arbitrary<'a>,
349{
350    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
351        let entry: Entry<MCL, MCC, MPL, N, S, PD> = Arbitrary::arbitrary(u)?;
352        let token: AT = Arbitrary::arbitrary(u)?;
353
354        if !token.is_authorised_write(&entry) {
355            arbitrary::Result::Err(arbitrary::Error::IncorrectFormat)
356        } else {
357            Ok(unsafe { Self::new_unchecked(entry, token) })
358        }
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use crate::path::Component;
365
366    use super::*;
367
368    const MCL: usize = 8;
369    const MCC: usize = 4;
370    const MPL: usize = 16;
371
372    #[test]
373    fn entry_newer_than() {
374        let e_a1 = Entry {
375            namespace_id: 0,
376            subspace_id: 0,
377            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
378            payload_digest: 0,
379            payload_length: 0,
380            timestamp: 20,
381        };
382
383        let e_a2 = Entry {
384            namespace_id: 0,
385            subspace_id: 0,
386            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
387            payload_digest: 0,
388            payload_length: 0,
389            timestamp: 10,
390        };
391
392        assert!(e_a1.is_newer_than(&e_a2));
393
394        let e_b1 = Entry {
395            namespace_id: 0,
396            subspace_id: 0,
397            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
398            payload_digest: 2,
399            payload_length: 0,
400            timestamp: 10,
401        };
402
403        let e_b2 = Entry {
404            namespace_id: 0,
405            subspace_id: 0,
406            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
407            payload_digest: 1,
408            payload_length: 0,
409            timestamp: 10,
410        };
411
412        assert!(e_b1.is_newer_than(&e_b2));
413
414        let e_c1 = Entry {
415            namespace_id: 0,
416            subspace_id: 0,
417            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
418            payload_digest: 0,
419            payload_length: 2,
420            timestamp: 20,
421        };
422
423        let e_c2 = Entry {
424            namespace_id: 0,
425            subspace_id: 0,
426            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
427            payload_digest: 0,
428            payload_length: 1,
429            timestamp: 20,
430        };
431
432        assert!(e_c1.is_newer_than(&e_c2));
433    }
434}