willow_data_model/
entry.rs

1#[cfg(feature = "dev")]
2use arbitrary::{size_hint::and_all, Arbitrary};
3
4use crate::{
5    parameters::{AuthorisationToken, NamespaceId, PayloadDigest, SubspaceId},
6    path::Path,
7};
8
9/// A Timestamp is a 64-bit unsigned integer, that is, a natural number between zero (inclusive) and 2^64 (exclusive).
10/// Timestamps are to be interpreted as a time in microseconds since the Unix epoch.
11/// [Definition](https://willowprotocol.org/specs/data-model/index.html#Timestamp).
12pub type Timestamp = u64;
13
14/// The metadata associated with each Payload.
15/// [Definition](https://willowprotocol.org/specs/data-model/index.html#Entry).
16#[derive(Debug, PartialEq, Eq, Clone)]
17pub struct Entry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
18where
19    N: NamespaceId,
20    S: SubspaceId,
21    PD: PayloadDigest,
22{
23    /// The identifier of the namespace to which the [`Entry`] belongs.
24    namespace_id: N,
25    /// The identifier of the subspace to which the [`Entry`] belongs.
26    subspace_id: S,
27    /// The [`Path`] to which the [`Entry`] was written.
28    path: Path<MCL, MCC, MPL>,
29    /// The claimed creation time of the [`Entry`].
30    timestamp: Timestamp,
31    /// The length of the Payload in bytes.
32    payload_length: u64,
33    /// The result of applying hash_payload to the Payload.
34    payload_digest: PD,
35}
36
37impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Entry<MCL, MCC, MPL, N, S, PD>
38where
39    N: NamespaceId,
40    S: SubspaceId,
41    PD: PayloadDigest,
42{
43    /// Create a new [`Entry`].
44    pub fn new(
45        namespace_id: N,
46        subspace_id: S,
47        path: Path<MCL, MCC, MPL>,
48        timestamp: Timestamp,
49        payload_length: u64,
50        payload_digest: PD,
51    ) -> Self {
52        Entry {
53            namespace_id,
54            subspace_id,
55            path,
56            timestamp,
57            payload_length,
58            payload_digest,
59        }
60    }
61
62    /// Return a reference to the identifier of the namespace to which the [`Entry`] belongs.
63    pub fn namespace_id(&self) -> &N {
64        &self.namespace_id
65    }
66
67    /// Return a reference to the identifier of the subspace_id to which the [`Entry`] belongs.
68    pub fn subspace_id(&self) -> &S {
69        &self.subspace_id
70    }
71
72    /// Return a reference to the [`Path`] to which the [`Entry`] was written.
73    pub fn path(&self) -> &Path<MCL, MCC, MPL> {
74        &self.path
75    }
76
77    /// Return the claimed creation time of the [`Entry`].
78    pub fn timestamp(&self) -> Timestamp {
79        self.timestamp
80    }
81
82    /// Return the length of the Payload in bytes.
83    pub fn payload_length(&self) -> u64 {
84        self.payload_length
85    }
86
87    /// Return a reference to the result of applying hash_payload to the Payload.
88    pub fn payload_digest(&self) -> &PD {
89        &self.payload_digest
90    }
91
92    /// Return if this [`Entry`] is newer than another using their timestamps.
93    /// Tie-breaks using the Entries' payload digest and payload length otherwise.
94    /// [Definition](https://willowprotocol.org/specs/data-model/index.html#entry_newer).
95    pub fn is_newer_than(&self, other: &Self) -> bool {
96        other.timestamp < self.timestamp
97            || (other.timestamp == self.timestamp && other.payload_digest < self.payload_digest)
98            || (other.timestamp == self.timestamp
99                && other.payload_digest == self.payload_digest
100                && other.payload_length < self.payload_length)
101    }
102}
103
104use syncify::syncify;
105use syncify::syncify_replace;
106
107#[syncify(encoding_sync)]
108mod encoding {
109    use super::*;
110
111    #[syncify_replace(use ufotofu::sync::{BulkConsumer, BulkProducer};)]
112    use ufotofu::local_nb::{BulkConsumer, BulkProducer};
113
114    use willow_encoding::{DecodeError, U64BE};
115
116    #[syncify_replace(use willow_encoding::sync::{Decodable, Encodable};)]
117    use willow_encoding::{Decodable, Encodable};
118
119    impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Encodable
120        for Entry<MCL, MCC, MPL, N, S, PD>
121    where
122        N: NamespaceId + Encodable,
123        S: SubspaceId + Encodable,
124        PD: PayloadDigest + Encodable,
125    {
126        async fn encode<C>(&self, consumer: &mut C) -> Result<(), <C>::Error>
127        where
128            C: BulkConsumer<Item = u8>,
129        {
130            self.namespace_id.encode(consumer).await?;
131            self.subspace_id.encode(consumer).await?;
132            self.path.encode(consumer).await?;
133
134            U64BE::from(self.timestamp).encode(consumer).await?;
135            U64BE::from(self.payload_length).encode(consumer).await?;
136
137            self.payload_digest.encode(consumer).await?;
138
139            Ok(())
140        }
141    }
142
143    impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Decodable
144        for Entry<MCL, MCC, MPL, N, S, PD>
145    where
146        N: NamespaceId + Decodable,
147        S: SubspaceId + Decodable,
148        PD: PayloadDigest + Decodable,
149    {
150        async fn decode<Prod>(producer: &mut Prod) -> Result<Self, DecodeError<Prod::Error>>
151        where
152            Prod: BulkProducer<Item = u8>,
153        {
154            let namespace_id = N::decode(producer).await?;
155            let subspace_id = S::decode(producer).await?;
156            let path = Path::<MCL, MCC, MPL>::decode(producer).await?;
157            let timestamp = U64BE::decode(producer).await?.into();
158            let payload_length = U64BE::decode(producer).await?.into();
159            let payload_digest = PD::decode(producer).await?;
160
161            Ok(Entry {
162                namespace_id,
163                subspace_id,
164                path,
165                timestamp,
166                payload_length,
167                payload_digest,
168            })
169        }
170    }
171}
172
173#[cfg(feature = "dev")]
174impl<'a, const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Arbitrary<'a>
175    for Entry<MCL, MCC, MPL, N, S, PD>
176where
177    N: NamespaceId + Arbitrary<'a>,
178    S: SubspaceId + Arbitrary<'a>,
179    PD: PayloadDigest + Arbitrary<'a>,
180{
181    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
182        let namespace_id: N = Arbitrary::arbitrary(u)?;
183
184        let subspace_id: S = Arbitrary::arbitrary(u)?;
185
186        let path: Path<MCL, MCC, MPL> = Arbitrary::arbitrary(u)?;
187
188        let payload_digest: PD = Arbitrary::arbitrary(u)?;
189
190        Ok(Self {
191            namespace_id,
192            subspace_id,
193            path,
194            payload_digest,
195            payload_length: Arbitrary::arbitrary(u)?,
196            timestamp: Arbitrary::arbitrary(u)?,
197        })
198    }
199
200    fn size_hint(depth: usize) -> (usize, Option<usize>) {
201        and_all(&[
202            N::size_hint(depth),
203            S::size_hint(depth),
204            Path::<MCL, MCC, MPL>::size_hint(depth),
205            PD::size_hint(depth),
206            u64::size_hint(depth),
207            u64::size_hint(depth),
208        ])
209    }
210}
211
212/// An error indicating an [`AuthorisationToken`](https://willowprotocol.org/specs/data-model/index.html#AuthorisationToken) does not authorise the writing of this entry.
213#[derive(Debug)]
214pub struct UnauthorisedWriteError;
215
216impl core::fmt::Display for UnauthorisedWriteError {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        write!(
219            f,
220            "Tried to authorise the writing of an entry using an AuthorisationToken which does not permit it."
221        )
222    }
223}
224
225impl std::error::Error for UnauthorisedWriteError {}
226
227/// An AuthorisedEntry is a pair of an [`Entry`] and [`AuthorisationToken`] for which [`Entry::is_authorised_write`] returns true.
228/// [Definition](https://willowprotocol.org/specs/data-model/index.html#AuthorisedEntry).
229///
230/// [Definition](https://willowprotocol.org/specs/data-model/index.html#AuthorisedEntry).
231pub struct AuthorisedEntry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>(
232    pub Entry<MCL, MCC, MPL, N, S, PD>,
233    pub AT,
234)
235where
236    N: NamespaceId,
237    S: SubspaceId,
238    PD: PayloadDigest;
239
240impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD, AT>
241    AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>
242where
243    N: NamespaceId,
244    S: SubspaceId,
245    PD: PayloadDigest,
246{
247    /// Construct an [`AuthorisedEntry`] if the token permits the writing of this entry, otherwise return an [`UnauthorisedWriteError`]
248
249    pub fn new(
250        entry: Entry<MCL, MCC, MPL, N, S, PD>,
251        token: AT,
252    ) -> Result<Self, UnauthorisedWriteError>
253    where
254        AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD>,
255    {
256        if token.is_authorised_write(&entry) {
257            return Ok(Self(entry, token));
258        }
259
260        Err(UnauthorisedWriteError)
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use crate::path::Component;
267
268    use super::*;
269
270    #[derive(Default, PartialEq, Eq, Clone)]
271    struct FakeNamespaceId(usize);
272    impl NamespaceId for FakeNamespaceId {}
273
274    #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
275    struct FakeSubspaceId(usize);
276    impl SubspaceId for FakeSubspaceId {
277        fn successor(&self) -> Option<Self> {
278            Some(FakeSubspaceId(self.0 + 1))
279        }
280    }
281
282    #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
283    struct FakePayloadDigest(usize);
284    impl PayloadDigest for FakePayloadDigest {}
285
286    const MCL: usize = 8;
287    const MCC: usize = 4;
288    const MPL: usize = 16;
289
290    #[test]
291    fn entry_newer_than() {
292        let e_a1 = Entry {
293            namespace_id: FakeNamespaceId::default(),
294            subspace_id: FakeSubspaceId::default(),
295            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
296            payload_digest: FakePayloadDigest::default(),
297            payload_length: 0,
298            timestamp: 20,
299        };
300
301        let e_a2 = Entry {
302            namespace_id: FakeNamespaceId::default(),
303            subspace_id: FakeSubspaceId::default(),
304            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
305            payload_digest: FakePayloadDigest::default(),
306            payload_length: 0,
307            timestamp: 10,
308        };
309
310        assert!(e_a1.is_newer_than(&e_a2));
311
312        let e_b1 = Entry {
313            namespace_id: FakeNamespaceId::default(),
314            subspace_id: FakeSubspaceId::default(),
315            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
316            payload_digest: FakePayloadDigest(2),
317            payload_length: 0,
318            timestamp: 10,
319        };
320
321        let e_b2 = Entry {
322            namespace_id: FakeNamespaceId::default(),
323            subspace_id: FakeSubspaceId::default(),
324            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
325            payload_digest: FakePayloadDigest(1),
326            payload_length: 0,
327            timestamp: 10,
328        };
329
330        assert!(e_b1.is_newer_than(&e_b2));
331
332        let e_c1 = Entry {
333            namespace_id: FakeNamespaceId::default(),
334            subspace_id: FakeSubspaceId::default(),
335            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
336            payload_digest: FakePayloadDigest::default(),
337            payload_length: 2,
338            timestamp: 20,
339        };
340
341        let e_c2 = Entry {
342            namespace_id: FakeNamespaceId::default(),
343            subspace_id: FakeSubspaceId::default(),
344            path: Path::<MCL, MCC, MPL>::new_from_slice(&[Component::new(b"a").unwrap()]).unwrap(),
345            payload_digest: FakePayloadDigest::default(),
346            payload_length: 1,
347            timestamp: 20,
348        };
349
350        assert!(e_c1.is_newer_than(&e_c2));
351    }
352}