willow_data_model/groupings/
area.rs

1use core::cmp::min;
2use core::fmt::Debug;
3use core::hash::Hash;
4use core::{cmp::Ordering, ops::RangeBounds};
5
6#[cfg(feature = "dev")]
7use arbitrary::Arbitrary;
8
9use order_theory::{GreatestElement, UpperSemilattice};
10
11use compact_u64::*;
12use ufotofu::codec_prelude::*;
13
14use crate::paths::path_extends_path::*;
15use crate::prelude::*;
16
17/// An [`Area`](https://willowprotocol.org/specs/grouping-entries/#Area) is a box in three-dimensional willow space, consisting of all entries matching either a single subspace id or entries of arbitrary subspace ids, prefixed by some [`Path`], and a [`TimeRange`](super::TimeRange).
18///
19/// Areas are the default way by which application developers should aggregate entries. See [the specification](https://willowprotocol.org/specs/grouping-entries/#areas) for more details.
20///
21/// ```
22/// use willow_data_model::prelude::*;
23///
24/// let a1 = Area::<2, 2, 2, u8>::new(None, Path::new(), TimeRange::new_closed(0.into(), 17.into()));
25///
26/// assert!(a1.wdm_includes(&(6, Path::new(), Timestamp::from(9))));
27/// assert_eq!(a1.subspace(), None);
28///
29/// let a2 = Area::<2, 2, 2, u8>::new(Some(42), Path::new(), TimeRange::new_open(15.into()));
30/// assert_eq!(
31///     a1.wdm_intersection(&a2),
32///     Ok(Area::<2, 2, 2, u8>::new(
33///         Some(42),
34///         Path::new(),
35///         TimeRange::new_closed(15.into(), 17.into()),
36///     )),
37/// );
38/// ```
39///
40/// [Specification](https://willowprotocol.org/specs/grouping-entries/#Area)
41#[derive(Clone, Debug, PartialEq, Eq, Hash)]
42#[cfg_attr(feature = "dev", derive(Arbitrary))]
43pub struct Area<const MCL: usize, const MCC: usize, const MPL: usize, S> {
44    subspace: Option<S>,
45    path: Path<MCL, MCC, MPL>,
46    times: TimeRange,
47}
48
49impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Area<MCL, MCC, MPL, S> {
50    /// Creates a new `Area` from its constituent optional subspace id, [`Path`], and [`TimeRange`](super::TimeRange).
51    ///
52    /// ```
53    /// use willow_data_model::prelude::*;
54    ///
55    /// let a = Area::<2, 2, 2, u8>::new(None, Path::new(), TimeRange::new_closed(0.into(), 17.into()));
56    ///
57    /// assert!(a.wdm_includes(&(6, Path::new(), Timestamp::from(9))));
58    /// assert_eq!(a.subspace(), None);
59    /// ```
60    pub fn new(subspace: Option<S>, path: Path<MCL, MCC, MPL>, times: TimeRange) -> Self {
61        Self {
62            subspace,
63            path,
64            times,
65        }
66    }
67
68    /// Returns a reference to the inner subspace id, if any.
69    ///
70    /// ```
71    /// use willow_data_model::prelude::*;
72    ///
73    /// let a1 = Area::<2, 2, 2, u8>::new(Some(17), Path::new(), TimeRange::new_closed(0.into(), 17.into()));
74    /// assert_eq!(a1.subspace(), Some(&17));
75    ///
76    /// let a2 = Area::<2, 2, 2, u8>::new(None, Path::new(), TimeRange::new_closed(0.into(), 17.into()));
77    /// assert_eq!(a2.subspace(), None);
78    /// ```
79    ///
80    /// [Definition](https://willowprotocol.org/specs/grouping-entries/#AreaSubspace).
81    pub fn subspace(&self) -> Option<&S> {
82        self.subspace.as_ref()
83    }
84
85    /// Returns a reference to the inner [`Path`].
86    ///
87    /// ```
88    /// use willow_data_model::prelude::*;
89    ///
90    /// let a = Area::<2, 2, 2, u8>::new(Some(17), Path::new(), TimeRange::new_closed(0.into(), 17.into()));
91    /// assert_eq!(a.path(), &Path::new());
92    /// ```
93    ///
94    /// [Definition](https://willowprotocol.org/specs/grouping-entries/#AreaPath).
95    pub fn path(&self) -> &Path<MCL, MCC, MPL> {
96        &self.path
97    }
98
99    /// Returns a reference to the inner [`TimeRange`](super::TimeRange).
100    ///
101    /// ```
102    /// use willow_data_model::prelude::*;
103    ///
104    /// let a = Area::<2, 2, 2, u8>::new(Some(17), Path::new(), TimeRange::new_closed(0.into(), 17.into()));
105    /// assert_eq!(a.times(), &WillowRange::from(TimeRange::new_closed(0.into(), 17.into())));
106    /// ```
107    ///
108    /// [Definition](https://willowprotocol.org/specs/grouping-entries/#AreaTime).
109    pub fn times(&self) -> &TimeRange {
110        &self.times
111    }
112
113    /// Sets the inner subspace id.
114    pub fn set_subspace(&mut self, new_subspace: Option<S>) {
115        self.subspace = new_subspace;
116    }
117
118    /// Sets the inner [`Path`].
119    pub fn set_path(&mut self, new_path: Path<MCL, MCC, MPL>) {
120        self.path = new_path;
121    }
122
123    /// Sets the inner [`TimeRange`].
124    pub fn set_times<TR>(&mut self, new_range: TR)
125    where
126        TR: Into<TimeRange>,
127    {
128        self.times = new_range.into();
129    }
130
131    /// Returns the [subspace area](https://willowprotocol.org/specs/grouping-entries/#subspace_area) for the given subspace id, i.e., the area which includes exactly the entries of the given subspace id.
132    ///
133    /// ```
134    /// use willow_data_model::prelude::*;
135    ///
136    /// let a = Area::<2, 2, 2, u8>::new_subspace_area(17);
137    ///
138    /// assert!(a.wdm_includes(&(17, Path::new(), Timestamp::from(9))));
139    /// assert!(!a.wdm_includes(&(18, Path::new(), Timestamp::from(9))));
140    /// assert_eq!(a.subspace(), Some(&17));
141    /// ```
142    pub fn new_subspace_area(subspace_id: S) -> Self {
143        Self::new(Some(subspace_id), Path::new(), TimeRange::full())
144    }
145
146    /// Turns an `&Area<MCL, MCC, MPL, S>` into an `Area<MCL, MCC, MPL, &S>`.
147    ///
148    /// Works by using `Option::as_ref` on the subspace and cloning everything else.
149    pub fn as_ref_subspace(&self) -> Area<MCL, MCC, MPL, &S> {
150        Area::new(self.subspace.as_ref(), self.path.clone(), self.times)
151    }
152}
153
154impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Grouping<MCL, MCC, MPL, S>
155    for Area<MCL, MCC, MPL, S>
156where
157    S: PartialEq,
158{
159    fn wdm_includes<Coord>(&self, coord: &Coord) -> bool
160    where
161        Coord: Coordinatelike<MCL, MCC, MPL, S> + ?Sized,
162    {
163        self.times().contains(&coord.wdm_timestamp())
164            && self
165                .subspace()
166                .map(|subspace_id| subspace_id == coord.wdm_subspace_id())
167                .unwrap_or(true)
168            && coord.wdm_path().is_prefixed_by(self.path())
169    }
170
171    fn wdm_intersection(&self, other: &Self) -> Result<Self, EmptyGrouping>
172    where
173        S: Clone,
174    {
175        if self.path.is_related_to(other.path()) {
176            let times = self.times().intersection_willow_range(other.times())?;
177            let path = self.path().least_upper_bound(other.path());
178
179            match (self.subspace(), other.subspace()) {
180                (None, None) => Ok(Self::new(None, path, times)),
181                (Some(subspace), None) | (None, Some(subspace)) => {
182                    Ok(Area::new(Some(subspace.clone()), path, times))
183                }
184                (Some(self_subspace), Some(other_subspace)) => {
185                    if self_subspace == other_subspace {
186                        Ok(Area::new(Some(self_subspace.clone()), path, times))
187                    } else {
188                        Err(EmptyGrouping)
189                    }
190                }
191            }
192        } else {
193            Err(EmptyGrouping)
194        }
195    }
196}
197
198/// An area is less than another iff all values included in the first are also included in the other.
199///
200/// This implementation assumes that `S` is inhabited by more than one value.
201impl<const MCL: usize, const MCC: usize, const MPL: usize, S> PartialOrd<Self>
202    for Area<MCL, MCC, MPL, S>
203where
204    S: PartialEq,
205{
206    /// An area is less than another iff all values included in the first are also included in the other.
207    ///
208    /// This implementation assumes that `S` is inhabited by more than one value.
209    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
210        match (
211            cmp_subspace(self.subspace(), other.subspace())?,
212            other.path().prefix_cmp(self.path())?,
213            self.times().partial_cmp(other.times())?,
214        ) {
215            (Ordering::Equal, Ordering::Equal, Ordering::Equal) => Some(Ordering::Equal),
216            (subspaces_cmp, paths_cmp, times_cmp) => {
217                if subspaces_cmp.is_le() && paths_cmp.is_le() && times_cmp.is_le() {
218                    Some(Ordering::Less)
219                } else if subspaces_cmp.is_ge() && paths_cmp.is_ge() && times_cmp.is_ge() {
220                    Some(Ordering::Greater)
221                } else {
222                    None
223                }
224            }
225        }
226    }
227}
228
229fn cmp_subspace<S: PartialEq>(s1: Option<&S>, s2: Option<&S>) -> Option<Ordering> {
230    match (s1, s2) {
231        (None, None) => Some(Ordering::Equal),
232        (Some(_), None) => Some(Ordering::Less),
233        (None, Some(_)) => Some(Ordering::Greater),
234        (Some(s1), Some(s2)) => {
235            if s1 == s2 {
236                Some(Ordering::Equal)
237            } else {
238                None
239            }
240        }
241    }
242}
243
244impl<const MCL: usize, const MCC: usize, const MPL: usize, S> GreatestElement
245    for Area<MCL, MCC, MPL, S>
246where
247    S: PartialOrd,
248{
249    fn greatest() -> Self {
250        Self::new(None, Path::new(), TimeRange::full())
251    }
252
253    fn is_greatest(&self) -> bool {
254        self.subspace().is_none() && self.times().is_full() && self.path().is_empty()
255    }
256}
257
258impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Area<MCL, MCC, MPL, S>
259where
260    S: PartialEq,
261{
262    /// Returns whether an [`Entry`] of the given [coordinate](Coordinatelike) could possibly cause [prefix pruning](https://willowprotocol.org/specs/data-model/index.html#prefix_pruning) in this area.
263    ///
264    /// ```
265    /// use willow_data_model::prelude::*;
266    ///
267    /// let a = Area::<2, 2, 2, u8>::new(Some(17), Path::new(), TimeRange::new_closed(0.into(), 17.into()));
268    ///
269    /// assert!(a.admits_pruning_by(&(17, Path::new(), Timestamp::from(9))));
270    /// assert!(!a.admits_pruning_by(&(18, Path::new(), Timestamp::from(9))));
271    /// ```
272    pub fn admits_pruning_by<Coord>(&self, coord: &Coord) -> bool
273    where
274        Coord: Coordinatelike<MCL, MCC, MPL, S>,
275    {
276        if let Some(s) = self.subspace() {
277            if s != coord.wdm_subspace_id() {
278                return false;
279            }
280        }
281
282        if coord.wdm_timestamp() < *self.times().start() {
283            return false;
284        }
285
286        coord.wdm_path().is_related_to(self.path())
287    }
288}
289
290impl<const MCL: usize, const MCC: usize, const MPL: usize, S> Area<MCL, MCC, MPL, S> {
291    /// Returns the [`Area`] which [includes](Grouping::wdm_includes) every [coordinate](Coordinatelike).
292    ///
293    /// ```
294    /// use willow_data_model::prelude::*;
295    ///
296    /// let a = Area::<2, 2, 2, u8>::full();
297    ///
298    /// assert!(a.wdm_includes(&(6, Path::new(), Timestamp::from(9))));
299    /// assert!(a.wdm_includes(&(16, Path::new(), Timestamp::from(9))));
300    /// ```
301    pub fn full() -> Self {
302        Self {
303            subspace: None,
304            path: Path::new(),
305            times: WillowRange::full(),
306        }
307    }
308
309    /// Returns whether `self` is the full area, i.e., the area which [includes](Grouping::wdm_includes) every [coordinate](Coordinatelike).
310    ///
311    /// ```
312    /// use willow_data_model::prelude::*;
313    ///
314    /// assert!(Area::<2, 2, 2, u8>::full().is_full());
315    /// assert!(!Area::<2, 2, 2, u8>::new(Some(17), Path::new(), TimeRange::new_closed(0.into(), 17.into())).is_full());
316    /// ```
317    pub fn is_full(&self) -> bool {
318        self.subspace().is_none() && self.path.is_empty() && self.times.is_full()
319    }
320}
321
322///////////////////////
323// Codec Stuff Below //
324///////////////////////
325
326/// Implements [encode_area_in_area](https://willowprotocol.org/specs/encodings/index.html#encode_area_in_area).
327impl<const MCL: usize, const MCC: usize, const MPL: usize, S>
328    RelativeEncodable<Area<MCL, MCC, MPL, S>> for Area<MCL, MCC, MPL, S>
329where
330    S: PartialEq + Encodable,
331{
332    async fn relative_encode<C>(
333        &self,
334        rel: &Area<MCL, MCC, MPL, S>,
335        consumer: &mut C,
336    ) -> Result<(), C::Error>
337    where
338        C: BulkConsumer<Item = u8> + ?Sized,
339    {
340        debug_assert!(self.can_be_encoded_relative_to(rel));
341
342        let self_times_start = u64::from(*self.times().start());
343        let self_times_end = self.times().end().map(|t| u64::from(*t));
344        let rel_times_start = u64::from(*rel.times().start());
345        let rel_times_end = rel.times().end().map(|t| u64::from(*t));
346
347        let (start_diff, start_from_start) = match rel_times_end {
348            None => (self_times_start - rel_times_start, true),
349            Some(rel_times_end) => {
350                if self_times_start - rel_times_start < rel_times_end - self_times_start {
351                    (self_times_start - rel_times_start, true)
352                } else {
353                    (rel_times_end - self_times_start, false)
354                }
355            }
356        };
357
358        let (end_diff, end_from_start) = match rel_times_end {
359            None => match self.times.end() {
360                None => (None, false),
361                Some(self_times_end) => (Some(u64::from(*self_times_end) - rel_times_start), true),
362            },
363
364            Some(rel_times_end) => {
365                // self is contained in rel, so self_time_end is `Some`.
366                let self_times_end: u64 = self_times_end.unwrap();
367
368                if self_times_end - rel_times_start < rel_times_end - self_times_end {
369                    (Some(self_times_end - rel_times_start), true)
370                } else {
371                    (Some(rel_times_end - self_times_end), false)
372                }
373            }
374        };
375
376        let mut header = 0;
377
378        if self.subspace() != rel.subspace() {
379            header |= 0b1000_0000;
380        }
381
382        if self.times().is_open() {
383            header |= 0b0100_0000;
384        }
385
386        if start_from_start {
387            header |= 0b0010_0000;
388        }
389
390        if self.times().is_closed() && end_from_start {
391            header |= 0b0001_0000;
392        }
393
394        write_tag(&mut header, 2, 4, start_diff);
395        write_tag(&mut header, 2, 6, end_diff.unwrap_or(0));
396
397        consumer.consume_item(header).await?;
398
399        if let (Some(self_subspace_id), None) = (self.subspace(), rel.subspace()) {
400            consumer.consume_encoded(self_subspace_id).await?;
401        }
402
403        cu64_encode(start_diff, 2, consumer).await?;
404
405        if let Some(end_diff) = end_diff {
406            cu64_encode(end_diff, 2, consumer).await?;
407        }
408
409        encode_path_extends_path(self.path(), rel.path(), consumer).await?;
410
411        Ok(())
412    }
413
414    /// Returns `true` iff `rel` includes `self`.
415    fn can_be_encoded_relative_to(&self, rel: &Area<MCL, MCC, MPL, S>) -> bool {
416        rel.wdm_includes_grouping(self)
417    }
418}
419
420/// Implements [EncodeAreaInArea](https://willowprotocol.org/specs/encodings/index.html#EncodeAreaInArea).
421impl<const MCL: usize, const MCC: usize, const MPL: usize, S>
422    RelativeDecodable<Area<MCL, MCC, MPL, S>> for Area<MCL, MCC, MPL, S>
423where
424    S: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>> + Clone + PartialEq,
425{
426    type ErrorReason = Blame;
427
428    async fn relative_decode<P>(
429        rel: &Area<MCL, MCC, MPL, S>,
430        producer: &mut P,
431    ) -> Result<Self, DecodeError<P::Final, P::Error, Blame>>
432    where
433        P: BulkProducer<Item = u8> + ?Sized,
434    {
435        relative_decode_maybe_canonic::<false, MCL, MCC, MPL, S, P>(rel, producer).await
436    }
437}
438
439/// Implements [encode_area_in_area](https://willowprotocol.org/specs/encodings/index.html#encode_area_in_area).
440impl<const MCL: usize, const MCC: usize, const MPL: usize, S>
441    RelativeDecodableCanonic<Area<MCL, MCC, MPL, S>> for Area<MCL, MCC, MPL, S>
442where
443    S: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>> + Clone + PartialEq,
444{
445    type ErrorCanonic = Blame;
446
447    async fn relative_decode_canonic<P>(
448        rel: &Area<MCL, MCC, MPL, S>,
449        producer: &mut P,
450    ) -> Result<Self, DecodeError<P::Final, P::Error, Blame>>
451    where
452        P: BulkProducer<Item = u8> + ?Sized,
453        Self: Sized,
454    {
455        relative_decode_maybe_canonic::<true, MCL, MCC, MPL, S, P>(rel, producer).await
456    }
457}
458
459/// Implements [encode_area_in_area](https://willowprotocol.org/specs/encodings/index.html#encode_area_in_area).
460impl<const MCL: usize, const MCC: usize, const MPL: usize, S>
461    RelativeEncodableKnownLength<Area<MCL, MCC, MPL, S>> for Area<MCL, MCC, MPL, S>
462where
463    S: PartialEq + EncodableKnownLength,
464{
465    fn len_of_relative_encoding(&self, rel: &Area<MCL, MCC, MPL, S>) -> usize {
466        let mut encoding_len = 1; // The header byte.
467
468        let self_times_start = u64::from(*self.times().start());
469        let self_times_end = self.times().end().map(|t| u64::from(*t));
470        let rel_times_start = u64::from(*rel.times().start());
471        let rel_times_end = rel.times().end().map(|t| u64::from(*t));
472
473        let start_diff = match rel_times_end {
474            None => self_times_start - rel_times_start,
475            Some(rel_times_end) => min(
476                self_times_start - rel_times_start,
477                rel_times_end - self_times_start,
478            ),
479        };
480
481        let end_diff = match rel_times_end {
482            None => self_times_end.map(|self_times_end| self_times_end - rel_times_start),
483            Some(rel_times_end) => {
484                // self is contained in rel, so self_time_end is `Some`.
485                let self_times_end: u64 = self_times_end.unwrap();
486                Some(min(
487                    self_times_end - rel_times_start,
488                    rel_times_end - self_times_end,
489                ))
490            }
491        };
492
493        if let (Some(self_subspace_id), None) = (self.subspace(), rel.subspace()) {
494            encoding_len += self_subspace_id.len_of_encoding();
495        }
496
497        encoding_len += cu64_len_of_encoding(2, start_diff);
498
499        if let Some(end_diff) = end_diff {
500            encoding_len += cu64_len_of_encoding(2, end_diff);
501        }
502
503        encoding_len += path_extends_path_encoding_len(self.path(), rel.path());
504
505        encoding_len
506    }
507}
508
509async fn relative_decode_maybe_canonic<
510    const CANONIC: bool,
511    const MCL: usize,
512    const MCC: usize,
513    const MPL: usize,
514    S,
515    P,
516>(
517    rel: &Area<MCL, MCC, MPL, S>,
518    producer: &mut P,
519) -> Result<Area<MCL, MCC, MPL, S>, DecodeError<P::Final, P::Error, Blame>>
520where
521    P: BulkProducer<Item = u8> + ?Sized,
522    S: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>> + Clone + PartialEq,
523{
524    let header = producer.produce_item().await?;
525
526    // Decode subspace?
527    let is_subspace_encoded = is_bitflagged(header, 0);
528
529    // Decode end value of times?
530    let is_times_end_open = is_bitflagged(header, 1);
531
532    // Add start_diff to rel.get_times().start, or subtract from rel.get_times().end?
533    let start_from_start = is_bitflagged(header, 2);
534
535    // Add end_diff to rel.get_times().start, or subtract from rel.get_times().end?
536    let end_from_start = is_bitflagged(header, 3);
537
538    // === Necessary to produce canonic encodings. ===
539    // Verify the last two header bits are zero if is_times_end_open
540    if CANONIC && is_times_end_open && (is_bitflagged(header, 6) || is_bitflagged(header, 7)) {
541        return Err(DecodeError::Other(Blame::TheirFault));
542    }
543    // ===============================================
544
545    let subspace = if is_subspace_encoded {
546        let subspace_id = producer
547            .produce_decoded_canonic()
548            .await
549            .map_err(|err| err.map_other(Into::into))?;
550
551        if let Some(rel_subspace_id) = rel.subspace() {
552            if &subspace_id != rel_subspace_id {
553                // They encoded an area not contained in rel.
554                return Err(DecodeError::Other(Blame::TheirFault));
555            } else if CANONIC {
556                // They should not have encoded the subspace id explicitly.
557                return Err(DecodeError::Other(Blame::TheirFault));
558            }
559        }
560
561        Some(subspace_id)
562    } else {
563        rel.subspace.clone()
564    };
565
566    let start_diff = if CANONIC {
567        cu64_decode_canonic(header, 2, 4, producer)
568            .await
569            .map_err(|err| err.map_other(|_| Blame::TheirFault))?
570    } else {
571        cu64_decode(header, 2, 4, producer)
572            .await
573            .map_err(|err| err.map_other(|_| Blame::TheirFault))?
574    };
575
576    let rel_times_start = u64::from(*rel.times().start());
577    let rel_times_end = rel.times().end().map(|t| u64::from(*t));
578
579    let start = if start_from_start {
580        rel_times_start
581            .checked_add(start_diff)
582            .ok_or(DecodeError::Other(Blame::TheirFault))?
583    } else {
584        match rel_times_end {
585            None => {
586                // They should have set `start_from_start` to true.
587                return Err(DecodeError::Other(Blame::TheirFault));
588            }
589            Some(rel_times_end) => rel_times_end
590                .checked_sub(start_diff)
591                .ok_or(DecodeError::Other(Blame::TheirFault))?,
592        }
593    };
594
595    if CANONIC {
596        let should_have_set_start_from_start = match rel_times_end {
597            None => true,
598            Some(rel_times_end) => {
599                let start_diff = start
600                    .checked_sub(rel_times_start)
601                    .ok_or(DecodeError::Other(Blame::TheirFault))?;
602                let end_diff = rel_times_end
603                    .checked_sub(start)
604                    .ok_or(DecodeError::Other(Blame::TheirFault))?;
605                start_diff < end_diff
606            }
607        };
608
609        if start_from_start != should_have_set_start_from_start {
610            return Err(DecodeError::Other(Blame::TheirFault));
611        }
612    }
613
614    let end = if is_times_end_open {
615        if end_from_start {
616            return Err(DecodeError::Other(Blame::TheirFault));
617        }
618
619        None
620    } else {
621        let end_diff = if CANONIC {
622            cu64_decode_canonic(header, 2, 6, producer)
623                .await
624                .map_err(|err| err.map_other(|_| Blame::TheirFault))?
625        } else {
626            cu64_decode(header, 2, 6, producer)
627                .await
628                .map_err(|err| err.map_other(|_| Blame::TheirFault))?
629        };
630
631        let end = if end_from_start {
632            rel_times_start
633                .checked_add(end_diff)
634                .ok_or(DecodeError::Other(Blame::TheirFault))?
635        } else {
636            match rel_times_end {
637                None => {
638                    // They should have set `end_from_start` to true.
639                    return Err(DecodeError::Other(Blame::TheirFault));
640                }
641                Some(rel_times_end) => rel_times_end
642                    .checked_sub(end_diff)
643                    .ok_or(DecodeError::Other(Blame::TheirFault))?,
644            }
645        };
646
647        if CANONIC {
648            let should_have_set_end_from_start = match rel_times_end {
649                None => true,
650                Some(rel_times_end) => {
651                    let start_diff = end
652                        .checked_sub(rel_times_start)
653                        .ok_or(DecodeError::Other(Blame::TheirFault))?;
654                    let end_diff = rel_times_end
655                        .checked_sub(end)
656                        .ok_or(DecodeError::Other(Blame::TheirFault))?;
657                    start_diff < end_diff
658                }
659            };
660
661            if end_from_start != should_have_set_end_from_start {
662                return Err(DecodeError::Other(Blame::TheirFault));
663            }
664        }
665
666        Some(end)
667    };
668
669    let times = WillowRange::try_new(Timestamp::from(start), end.map(Timestamp::from))
670        .map_err(|_| DecodeError::Other(Blame::TheirFault))?;
671
672    let path = if CANONIC {
673        decode_path_extends_path_canonic(rel.path(), producer)
674            .await
675            .map_err(|err| err.map_other(|_| Blame::TheirFault))?
676    } else {
677        decode_path_extends_path(rel.path(), producer)
678            .await
679            .map_err(|err| err.map_other(|_| Blame::TheirFault))?
680    };
681
682    if !rel.times().includes_willow_range(&times) {
683        return Err(DecodeError::Other(Blame::TheirFault));
684    }
685
686    Ok(Area::new(subspace, path, times))
687}
688
689fn is_bitflagged(byte: u8, index: usize) -> bool {
690    byte & (1u8 << (7 - index)) != 0u8
691}
692
693/// Returns an [`Arbitrary`] area which is guaranteed to be included in the reference area.
694#[cfg(feature = "dev")]
695pub fn arbitrary_area_in_area<'a, const MCL: usize, const MCC: usize, const MPL: usize, S>(
696    reference: &Area<MCL, MCC, MPL, S>,
697    u: &mut arbitrary::Unstructured<'a>,
698) -> arbitrary::Result<Area<MCL, MCC, MPL, S>>
699where
700    S: Arbitrary<'a> + Clone,
701{
702    let subspace = match reference.subspace() {
703        None => Option::<S>::arbitrary(u)?,
704        Some(s) => Some(s.clone()),
705    };
706
707    let path_suffix = Path::<MCL, MCC, MPL>::arbitrary(u)?;
708    let path = reference
709        .path()
710        .append_path(&path_suffix)
711        .unwrap_or_else(|_| reference.path().clone());
712
713    let times_candidate = TimeRange::arbitrary(u)?;
714    let times = if reference.times().includes_willow_range(&times_candidate) {
715        times_candidate
716    } else {
717        *reference.times()
718    };
719
720    Ok(Area {
721        subspace,
722        path,
723        times,
724    })
725}