Skip to main content

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