willow_data_model/private_encodings/
area.rs

1#[cfg(feature = "dev")]
2use arbitrary::Arbitrary;
3use compact_u64::{CompactU64, Tag, TagWidth};
4use ufotofu_codec::{
5    Blame, Decodable, DecodeError, Encodable, RelativeDecodable, RelativeEncodable,
6};
7use willow_encoding::is_bitflagged;
8
9use crate::{
10    grouping::{Area, AreaSubspace, Range, RangeEnd},
11    Entry, NamespaceId, Path, PayloadDigest, PrivatePathContext, SubspaceId,
12};
13
14#[derive(Debug, Clone)]
15/// Confidential data that relates to determining the AreasOfInterest that peers might be interested in synchronising.
16// TODO: Move this all to WGPS?
17pub struct PrivateInterest<
18    const MCL: usize,
19    const MCC: usize,
20    const MPL: usize,
21    N: NamespaceId,
22    S: SubspaceId,
23> {
24    namespace_id: N,
25    subspace_id: AreaSubspace<S>,
26    path: Path<MCL, MCC, MPL>,
27}
28
29impl<const MCL: usize, const MCC: usize, const MPL: usize, N: NamespaceId, S: SubspaceId>
30    PrivateInterest<MCL, MCC, MPL, N, S>
31{
32    pub fn namespace_id(&self) -> &N {
33        &self.namespace_id
34    }
35
36    pub fn subspace_id(&self) -> &AreaSubspace<S> {
37        &self.subspace_id
38    }
39
40    pub fn path(&self) -> &Path<MCL, MCC, MPL> {
41        &self.path
42    }
43
44    pub fn is_more_specific(&self, other: &Self) -> bool {
45        self.namespace_id == other.namespace_id
46            && other.subspace_id.includes_area_subspace(&self.subspace_id)
47            && self.path.is_prefixed_by(&other.path)
48    }
49
50    pub fn is_less_specific(&self, other: &Self) -> bool {
51        other.is_more_specific(self)
52    }
53
54    pub fn is_comparable(&self, other: &Self) -> bool {
55        self.is_more_specific(other) || other.is_more_specific(other)
56    }
57
58    pub fn includes_entry<PD: PayloadDigest>(
59        &self,
60        entry: &Entry<MCL, MCC, MPL, N, S, PD>,
61    ) -> bool {
62        &self.namespace_id == entry.namespace_id()
63            && self.subspace_id.includes(entry.subspace_id())
64            && self.path.is_prefix_of(entry.path())
65    }
66
67    pub fn is_disjoint(&self, other: &Self) -> bool {
68        // We say that p1 and p2 are disjoint there can be no Entry which is included in both p1 and p2.
69
70        if self.namespace_id != other.namespace_id {
71            return true;
72        }
73
74        if !self.subspace_id.includes_area_subspace(&other.subspace_id)
75            && !self.subspace_id.includes_area_subspace(&other.subspace_id)
76        {
77            return true;
78        }
79
80        if !self.path.is_related(&other.path) {
81            return true;
82        }
83
84        false
85    }
86
87    pub fn awkward(&self, other: &Self) -> bool {
88        // We say that p1 and p2 are awkward if they are neither comparable nor disjoint. This is the case if and only if one of them has subspace_id any and a path p, and the other has a non-any subspace_id and a path which is a strict prefix of p.
89        !self.is_comparable(other) && !self.is_disjoint(other)
90    }
91
92    pub fn includes_area(&self, area: &Area<MCL, MCC, MPL, S>) -> bool {
93        self.subspace_id.includes_area_subspace(area.subspace())
94            && self.path.is_prefix_of(area.path())
95    }
96
97    pub fn is_related_to_area(&self, area: &Area<MCL, MCC, MPL, S>) -> bool {
98        self.subspace_id.includes_area_subspace(area.subspace())
99            && self.path.is_related(area.path())
100    }
101
102    pub fn almost_includes_area(&self, area: &Area<MCL, MCC, MPL, S>) -> bool {
103        let subspace_is_fine = match (self.subspace_id(), area.subspace()) {
104            (AreaSubspace::Id(self_id), AreaSubspace::Id(other_id)) => self_id == other_id,
105            _ => true,
106        };
107
108        subspace_is_fine && self.path.is_related(area.path())
109    }
110}
111
112#[cfg(feature = "dev")]
113impl<'a, const MCL: usize, const MCC: usize, const MPL: usize, N, S> Arbitrary<'a>
114    for PrivateInterest<MCL, MCC, MPL, N, S>
115where
116    N: NamespaceId + Arbitrary<'a>,
117    S: SubspaceId + Arbitrary<'a>,
118{
119    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
120        let namespace_id: N = Arbitrary::arbitrary(u)?;
121        let subspace_id: AreaSubspace<S> = Arbitrary::arbitrary(u)?;
122        let path: Path<MCL, MCC, MPL> = Arbitrary::arbitrary(u)?;
123
124        Ok(Self {
125            namespace_id,
126            subspace_id,
127            path,
128        })
129    }
130}
131
132#[derive(Debug)]
133/// The context necessary to privately encode Areas.
134pub struct PrivateAreaContext<
135    const MCL: usize,
136    const MCC: usize,
137    const MPL: usize,
138    N: NamespaceId,
139    S: SubspaceId,
140> {
141    /// The PrivateInterest to be kept private.
142    private: PrivateInterest<MCL, MCC, MPL, N, S>,
143
144    /// The almost containing Area relative to which we encode.
145    rel: Area<MCL, MCC, MPL, S>,
146}
147
148#[derive(Debug)]
149pub struct AreaNotAlmostIncludedError;
150
151impl<const MCL: usize, const MCC: usize, const MPL: usize, N: NamespaceId, S: SubspaceId>
152    PrivateAreaContext<MCL, MCC, MPL, N, S>
153{
154    pub fn new(
155        private: PrivateInterest<MCL, MCC, MPL, N, S>,
156        rel: Area<MCL, MCC, MPL, S>,
157    ) -> Result<Self, AreaNotAlmostIncludedError> {
158        if !private.path.is_prefix_of(rel.path()) {
159            // not almost included, wa
160        }
161
162        Ok(Self { private, rel })
163    }
164
165    pub fn private(&self) -> &PrivateInterest<MCL, MCC, MPL, N, S> {
166        &self.private
167    }
168
169    pub fn rel(&self) -> &Area<MCL, MCC, MPL, S> {
170        &self.rel
171    }
172}
173
174#[cfg(feature = "dev")]
175impl<'a, const MCL: usize, const MCC: usize, const MPL: usize, N: NamespaceId, S: SubspaceId>
176    Arbitrary<'a> for PrivateAreaContext<MCL, MCC, MPL, N, S>
177where
178    N: NamespaceId + Arbitrary<'a>,
179    S: SubspaceId + Arbitrary<'a>,
180{
181    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
182        Ok(Self {
183            private: Arbitrary::arbitrary(u)?,
184            rel: Arbitrary::arbitrary(u)?,
185        })
186    }
187}
188
189impl<const MCL: usize, const MCC: usize, const MPL: usize, N: NamespaceId, S: SubspaceId>
190    RelativeEncodable<PrivateAreaContext<MCL, MCC, MPL, N, S>> for Area<MCL, MCC, MPL, S>
191where
192    S: Encodable,
193{
194    async fn relative_encode<C>(
195        &self,
196        consumer: &mut C,
197        r: &PrivateAreaContext<MCL, MCC, MPL, N, S>,
198    ) -> Result<(), C::Error>
199    where
200        C: ufotofu::BulkConsumer<Item = u8>,
201    {
202        if !r.rel.almost_includes_area(self) || !r.private.almost_includes_area(self) {
203            panic!(
204                "Tried to encode an Area relative to a PrivateAreaContext it is not almost included by."
205            )
206        }
207
208        let (start_from_start, end_from_start) = match (r.rel.times().end, self.times().end) {
209            (RangeEnd::Open, RangeEnd::Open) => (true, false),
210            (RangeEnd::Open, RangeEnd::Closed(_)) => (true, true),
211            (RangeEnd::Closed(rel_end), RangeEnd::Open) => {
212                let start_from_start =
213                    self.times().start - r.rel().times().start <= rel_end - self.times().start;
214
215                (start_from_start, false)
216            }
217            (RangeEnd::Closed(rel_end), RangeEnd::Closed(val_end)) => {
218                let start_from_start =
219                    self.times().start - r.rel().times().start <= rel_end - self.times().start;
220
221                let end_from_start = val_end - r.rel().times().start <= rel_end - val_end;
222
223                (start_from_start, end_from_start)
224            }
225        };
226
227        let mut header = 0b0000_0000;
228
229        if self.subspace() != r.rel().subspace() {
230            header |= 0b1000_0000;
231        }
232
233        if self.subspace() == &AreaSubspace::Any {
234            header |= 0b0100_0000
235        }
236
237        if start_from_start {
238            header |= 0b0010_0000
239        }
240
241        if end_from_start {
242            header |= 0b0001_0000
243        }
244
245        let start_diff = match (r.rel.times().end, self.times().end) {
246            (RangeEnd::Open, RangeEnd::Open) => self.times().start - r.rel.times().start,
247            (RangeEnd::Open, RangeEnd::Closed(_)) => self.times().start - r.rel.times().start,
248            (RangeEnd::Closed(rel_end), RangeEnd::Open) => {
249                let start_from_start_diff = self.times().start - r.rel().times().start;
250
251                let start_from_end_diff = rel_end - self.times().start;
252
253                if start_from_start_diff <= start_from_end_diff {
254                    start_from_start_diff
255                } else {
256                    start_from_end_diff
257                }
258            }
259            (RangeEnd::Closed(rel_end), RangeEnd::Closed(_)) => {
260                let start_from_start_diff = self.times().start - r.rel().times().start;
261                let start_from_end_diff = rel_end - self.times().start;
262
263                if start_from_start_diff <= start_from_end_diff {
264                    start_from_start_diff
265                } else {
266                    start_from_end_diff
267                }
268            }
269        };
270
271        let start_tag = Tag::min_tag(start_diff, TagWidth::two());
272        header |= start_tag.data_at_offset(4);
273
274        let end_diff = match (r.rel.times().end, self.times().end) {
275            (RangeEnd::Open, RangeEnd::Open) | (RangeEnd::Closed(_), RangeEnd::Open) => None,
276            (RangeEnd::Open, RangeEnd::Closed(val_end)) => {
277                let diff = val_end - r.rel.times().start;
278
279                let tag = Tag::min_tag(diff, TagWidth::two());
280                header |= tag.data_at_offset(6);
281                Some(diff)
282            }
283            (RangeEnd::Closed(rel_end), RangeEnd::Closed(val_end)) => {
284                let end_from_start = val_end - r.rel().times().start <= rel_end - val_end;
285                //   println!("end_from_start: {:?}", end_from_start);
286
287                let diff = if end_from_start {
288                    val_end - r.rel.times().start
289                } else {
290                    rel_end - val_end
291                };
292
293                let tag = Tag::min_tag(diff, TagWidth::two());
294                header |= tag.data_at_offset(6);
295                Some(diff)
296            }
297        };
298
299        consumer.consume(header).await?;
300
301        if let AreaSubspace::Id(id) = self.subspace() {
302            if r.rel().subspace() != id {
303                id.encode(consumer).await?;
304            }
305        }
306
307        CompactU64(start_diff)
308            .relative_encode(consumer, &start_tag.encoding_width())
309            .await?;
310
311        if let Some(end_diff) = end_diff {
312            let end_from_start_tag = Tag::min_tag(end_diff, TagWidth::two());
313            CompactU64(end_diff)
314                .relative_encode(consumer, &end_from_start_tag.encoding_width())
315                .await?;
316        }
317
318        // We can use new_unchecked because we know the paths from the context prefix the area's path.
319        let private_path_context =
320            PrivatePathContext::new_unchecked(r.private().path().clone(), r.rel().path().clone());
321
322        self.path()
323            .relative_encode(consumer, &private_path_context)
324            .await?;
325
326        Ok(())
327    }
328}
329
330impl<const MCL: usize, const MCC: usize, const MPL: usize, N: NamespaceId, S: SubspaceId>
331    RelativeDecodable<PrivateAreaContext<MCL, MCC, MPL, N, S>, Blame> for Area<MCL, MCC, MPL, S>
332where
333    S: Decodable,
334    Blame: From<S::ErrorReason>,
335{
336    async fn relative_decode<P>(
337        producer: &mut P,
338        r: &PrivateAreaContext<MCL, MCC, MPL, N, S>,
339    ) -> Result<Self, ufotofu_codec::DecodeError<P::Final, P::Error, Blame>>
340    where
341        P: ufotofu::BulkProducer<Item = u8>,
342        Self: Sized,
343    {
344        let header = producer.produce_item().await?;
345
346        let is_subspace_not_equal = is_bitflagged(header, 0);
347        let is_subspace_any = is_bitflagged(header, 1);
348        let is_start_from_start = is_bitflagged(header, 2);
349        let is_end_from_start = is_bitflagged(header, 3);
350        let start_diff_tag = Tag::from_raw(header, TagWidth::two(), 4);
351        let end_diff_tag = Tag::from_raw(header, TagWidth::two(), 6);
352
353        let subspace = if !is_subspace_any && is_subspace_not_equal {
354            let id = S::decode(producer)
355                .await
356                .map_err(DecodeError::map_other_from)?;
357
358            AreaSubspace::Id(id)
359        } else if is_subspace_any {
360            AreaSubspace::Any
361        } else {
362            r.rel().subspace().clone()
363        };
364
365        let start_diff = CompactU64::relative_decode(producer, &start_diff_tag)
366            .await
367            .map_err(DecodeError::map_other_from)?;
368
369        let time_start = if is_start_from_start {
370            start_diff
371                .0
372                .checked_add(r.rel().times().start)
373                .ok_or(DecodeError::Other(Blame::TheirFault))?
374        } else {
375            match r.rel().times().end {
376                RangeEnd::Closed(end) => end
377                    .checked_sub(start_diff.0)
378                    .ok_or(DecodeError::Other(Blame::TheirFault))?,
379                RangeEnd::Open => {
380                    return Err(DecodeError::Other(Blame::TheirFault));
381                }
382            }
383        };
384
385        let is_time_end_open = match r.rel.times().end {
386            RangeEnd::Closed(_) => false,
387            RangeEnd::Open => !is_end_from_start,
388        };
389
390        let time_end = if is_time_end_open {
391            RangeEnd::Open
392        } else {
393            let end_diff = CompactU64::relative_decode(producer, &end_diff_tag)
394                .await
395                .map_err(DecodeError::map_other_from)?;
396
397            let end = if is_end_from_start {
398                end_diff
399                    .0
400                    .checked_add(r.rel.times().start)
401                    .ok_or(DecodeError::Other(Blame::TheirFault))?
402            } else {
403                match r.rel.times().end {
404                    RangeEnd::Closed(end) => end
405                        .checked_sub(end_diff.0)
406                        .ok_or(DecodeError::Other(Blame::TheirFault))?,
407                    RangeEnd::Open => {
408                        return Err(DecodeError::Other(Blame::TheirFault));
409                    }
410                }
411            };
412
413            RangeEnd::Closed(end)
414        };
415
416        // We can use new_unchecked because we know the paths from the context prefix the area's path.
417        let private_path_context =
418            PrivatePathContext::new_unchecked(r.private().path().clone(), r.rel().path().clone());
419
420        let path = Path::relative_decode(producer, &private_path_context).await?;
421
422        if time_start > time_end {
423            return Err(DecodeError::Other(Blame::TheirFault));
424        }
425
426        let times = Range::new(time_start, time_end);
427
428        Ok(Area::new(subspace, path, times))
429    }
430}