Skip to main content

dash_runtime/address/
plan.rs

1//! The lazy Segment plan of one Representation within one Period.
2//!
3//! Formulas (all integer, `i128` microseconds; ticks via `@timescale`, which
4//! defaults to 1):
5//!
6//! * `$Number$` with `@duration`: `MPDstart(n) = (n - startNumber) * d`,
7//!   `n = startNumber + k - 1` (5.3.9.5.3; the printed `(kstart-1)` variant
8//!   is a misprint settled by the real captures, DESIGN section 11 item 1).
9//!   The last segment of a Period is `PeriodEnd - start` (A.3.3 case 1);
10//!   `@endNumber` caps the count; neither `@duration` nor a timeline is one
11//!   segment starting at 0 (A.3.3 case 3).
12//! * Timeline: `$Time$` is the raw `S@t` (decision D3: the standard's three
13//!   statements disagree when `@presentationTimeOffset != 0`); the
14//!   Period-relative start is `(time - PTO) / timescale` (5.3.9.6.1).
15//! * Availability (dynamic only): [`crate::timing::availability`].
16
17use transmux::SegmentIndexBox;
18
19use super::AddressError;
20use super::base_index::{self, IndexedSegment};
21use super::list;
22use super::template::{self, TemplateError, TemplateFault, TemplateUse};
23use super::timeline::TimelineIndex;
24use crate::error::Unsupported;
25use crate::timing::availability::{
26    init_available_from, segment_available_from_ticks, segment_available_until_ticks, ticks_to_us,
27};
28use broadcast_common::timescale::{Rounding, from_micros};
29use broadcast_dash::inherit::{ListInfo, ResolvedMulti, SegmentInfo, TemplateInfo};
30use broadcast_dash::{BaseInfo, ByteRange, UrlType};
31
32const MICROS: i128 = 1_000_000;
33
34fn us_to_ticks_ceil(us: i128, timescale: u64) -> u64 {
35    u64::try_from(from_micros(us.max(0), timescale, Rounding::Ceil)).unwrap_or(u64::MAX)
36}
37
38/// What a dynamic MPD adds to the plan.
39#[derive(Debug, Clone, PartialEq, Eq)]
40#[non_exhaustive]
41pub struct DynamicContext {
42    /// `MPD@availabilityStartTime` as Unix microseconds.
43    pub availability_start_us: i128,
44    /// The effective `@timeShiftBufferDepth` (Representation level over MPD
45    /// level), microseconds; `None` means unbounded.
46    pub time_shift_buffer_depth_us: Option<i128>,
47    /// Sum of `@availabilityTimeOffset` over every level that built the URL,
48    /// microseconds; zero unless the caller opted into early requests.
49    pub availability_time_offset_us: i128,
50    /// `FetchTime + minimumUpdatePeriod` in UTC microseconds: where a trailing
51    /// negative `@r` runs to when the Period has no known end (A.3.2).
52    pub horizon_utc_us: Option<i128>,
53}
54
55impl DynamicContext {
56    /// A context with no ATO and no horizon.
57    #[must_use]
58    pub fn new(availability_start_us: i128, time_shift_buffer_depth_us: Option<i128>) -> Self {
59        Self {
60            availability_start_us,
61            time_shift_buffer_depth_us,
62            availability_time_offset_us: 0,
63            horizon_utc_us: None,
64        }
65    }
66}
67
68/// Everything [`SegmentPlan::build`] needs.
69#[derive(Debug, Clone)]
70#[non_exhaustive]
71pub struct PlanInput<'a> {
72    /// `Representation@id` (`$RepresentationID$`).
73    pub representation_id: &'a str,
74    /// `Representation@bandwidth` (`$Bandwidth$`).
75    pub bandwidth: u64,
76    /// The resolved Segment information.
77    pub info: &'a SegmentInfo,
78    /// PeriodStart in microseconds from the presentation start.
79    pub period_start_us: i128,
80    /// The Period end in the same domain, when known.
81    pub period_end_us: Option<i128>,
82    /// Dynamic-MPD context; `None` for a static MPD.
83    pub dynamic: Option<DynamicContext>,
84    /// The parsed `sidx` and the anchor offset (SegmentBase only).
85    pub index: Option<(&'a SegmentIndexBox, u64)>,
86}
87
88impl<'a> PlanInput<'a> {
89    /// A static plan starting at 0 with no known end.
90    #[must_use]
91    pub fn new(representation_id: &'a str, bandwidth: u64, info: &'a SegmentInfo) -> Self {
92        Self {
93            representation_id,
94            bandwidth,
95            info,
96            period_start_us: 0,
97            period_end_us: None,
98            dynamic: None,
99            index: None,
100        }
101    }
102}
103
104/// One Media Segment.
105#[derive(Debug, Clone, PartialEq, Eq)]
106#[non_exhaustive]
107pub struct SegmentRef {
108    /// Zero-based position in the plan.
109    pub index: u64,
110    /// `$Number$`.
111    pub number: u64,
112    /// `$Time$` value in ticks.
113    pub time_ticks: u64,
114    /// Duration in ticks.
115    pub duration_ticks: u64,
116    /// Ticks per second.
117    pub timescale: u64,
118    /// Start relative to the Period start, microseconds
119    /// (`(time - PTO) / timescale` for a timeline).
120    pub start_us: i128,
121    /// Duration in microseconds.
122    pub duration_us: i128,
123    /// The URL reference to resolve against the BaseURL chain; empty means the
124    /// BaseURL itself.
125    pub reference: String,
126    /// The byte range, when the segment is a part of a file.
127    pub byte_range: Option<ByteRange>,
128    /// Availability start (dynamic MPD).
129    pub available_from_us: Option<i128>,
130    /// Availability end (dynamic MPD with a time-shift buffer).
131    pub available_until_us: Option<i128>,
132}
133
134/// An Initialization or Index Segment address.
135#[derive(Debug, Clone, PartialEq, Eq)]
136#[non_exhaustive]
137pub struct ResourceRef {
138    /// URL reference (empty: the BaseURL itself).
139    pub reference: String,
140    /// Byte range.
141    pub byte_range: Option<ByteRange>,
142    /// Availability start (dynamic MPD).
143    pub available_from_us: Option<i128>,
144    /// Availability end: that of the last Media Segment.
145    pub available_until_us: Option<i128>,
146}
147
148#[derive(Debug, Clone)]
149struct Fixed {
150    number: u64,
151    time: u64,
152    start_ticks: i128,
153    duration: u64,
154    reference: String,
155    range: Option<ByteRange>,
156}
157
158#[derive(Debug, Clone)]
159enum Kind {
160    Duration {
161        d: u64,
162        start_number: u64,
163        count: Option<u64>,
164        last_duration: u64,
165    },
166    Timeline(TimelineIndex),
167    Fixed(Vec<Fixed>),
168}
169
170#[derive(Debug, Clone)]
171struct Avail {
172    ast_us: i128,
173    ato_us: i128,
174    tsb_us: Option<i128>,
175}
176
177#[derive(Debug, Clone)]
178struct MediaTemplate {
179    template: String,
180    representation_id: String,
181    bandwidth: u64,
182}
183
184/// The ordered Media Segments of one Representation in one Period.
185#[derive(Debug, Clone)]
186pub struct SegmentPlan {
187    kind: Kind,
188    timescale: u64,
189    pto: u64,
190    period_start_us: i128,
191    avail: Option<Avail>,
192    media: Option<MediaTemplate>,
193    init: Option<(String, Option<ByteRange>)>,
194    index: Option<(String, Option<ByteRange>)>,
195    len_cap: Option<u64>,
196}
197
198fn fault(template: &str, f: TemplateFault) -> AddressError {
199    AddressError::InvalidTemplate {
200        template: template.to_owned(),
201        fault: f,
202    }
203}
204
205fn reject_sequences(used: TemplateUse) -> Result<(), AddressError> {
206    if used.sub_number {
207        Err(AddressError::Unsupported(Unsupported::SegmentSequence))
208    } else {
209        Ok(())
210    }
211}
212
213fn url_ref(u: &UrlType) -> (String, Option<ByteRange>) {
214    (u.source_url.clone().unwrap_or_default(), u.range)
215}
216
217fn period_end_media_ticks(
218    multi: &ResolvedMulti,
219    input: &PlanInput<'_>,
220    period_duration_ticks: Option<u64>,
221) -> Option<u64> {
222    let pto = multi.presentation_time_offset;
223    if let Some(pd) = period_duration_ticks {
224        return Some(pd.saturating_add(pto));
225    }
226    let dynamic = input.dynamic.as_ref()?;
227    let horizon = dynamic.horizon_utc_us?;
228    let rel = horizon
229        .saturating_sub(dynamic.availability_start_us)
230        .saturating_sub(input.period_start_us);
231    Some(us_to_ticks_ceil(rel, multi.timescale).saturating_add(pto))
232}
233
234impl SegmentPlan {
235    /// Build the plan.
236    ///
237    /// # Errors
238    /// [`AddressError`]: the Representation is to be dropped (or, for
239    /// [`AddressError::IndexRequired`], planned again once the `sidx` is
240    /// fetched).
241    pub fn build(input: &PlanInput<'_>) -> Result<Self, AddressError> {
242        let avail = input.dynamic.as_ref().map(|d| Avail {
243            ast_us: d.availability_start_us,
244            ato_us: d.availability_time_offset_us,
245            tsb_us: d.time_shift_buffer_depth_us,
246        });
247        let mut plan = match input.info {
248            SegmentInfo::Template(t) => Self::from_template(input, t)?,
249            SegmentInfo::List(l) => Self::from_list(input, l)?,
250            SegmentInfo::Base(b) => Self::from_base(input, b)?,
251            _ => return Err(AddressError::UnsupportedFeature),
252        };
253        plan.avail = avail;
254        plan.period_start_us = input.period_start_us;
255        Ok(plan)
256    }
257
258    fn period_duration_us(input: &PlanInput<'_>) -> Option<i128> {
259        input
260            .period_end_us
261            .map(|e| e.saturating_sub(input.period_start_us).max(0))
262    }
263
264    fn from_template(input: &PlanInput<'_>, t: &TemplateInfo) -> Result<Self, AddressError> {
265        let media = t
266            .media
267            .as_deref()
268            .ok_or(AddressError::MissingMediaTemplate)?;
269        let used = template::scan_media(media).map_err(|f| fault(media, f))?;
270        reject_sequences(used)?;
271        let mut init = t.multi.base.initialization.as_ref().map(url_ref);
272        if let Some(tpl) = &t.initialization {
273            let used = template::scan_initialization(tpl).map_err(|f| fault(tpl, f))?;
274            reject_sequences(used)?;
275            init = Some((
276                template::substitute(tpl, input.representation_id, None, None, input.bandwidth)
277                    .map_err(|e| fault(tpl, e.fault()))?,
278                None,
279            ));
280        }
281        let mut index = t.multi.base.representation_index.as_ref().map(url_ref);
282        if let Some(tpl) = &t.index {
283            let used = template::scan(tpl).map_err(|f| fault(tpl, f))?;
284            reject_sequences(used)?;
285            if !used.number && !used.time {
286                index = Some((
287                    template::substitute(tpl, input.representation_id, None, None, input.bandwidth)
288                        .map_err(|e| fault(tpl, e.fault()))?,
289                    None,
290                ));
291            }
292        }
293        let m = &t.multi;
294        let ts = m.timescale;
295        let pd_us = Self::period_duration_us(input);
296        let pd_ticks = pd_us.map(|d| us_to_ticks_ceil(d, ts));
297        let (kind, len_cap) = if let Some(tl) = &m.timeline {
298            let end = period_end_media_ticks(m, input, pd_ticks);
299            let idx = TimelineIndex::build(tl, m.start_number, end)?;
300            let cap = m.end_number.map(|en| idx.count_up_to_number(en));
301            (Kind::Timeline(idx), cap)
302        } else if let Some(d) = m.duration {
303            let mut count = pd_us.map(|dur| {
304                let num = dur.saturating_mul(i128::from(ts));
305                let den = i128::from(d).saturating_mul(MICROS);
306                let c = num.div_euclid(den) + i128::from(num.rem_euclid(den) != 0);
307                u64::try_from(c).unwrap_or(u64::MAX)
308            });
309            if let Some(en) = m.end_number {
310                let cap = if en < m.start_number {
311                    0
312                } else {
313                    en.saturating_sub(m.start_number).saturating_add(1)
314                };
315                count = Some(count.map_or(cap, |c| c.min(cap)));
316            }
317            let last_duration = match (count, pd_ticks) {
318                (Some(c), Some(pd)) if c > 0 => {
319                    let full = (c - 1).saturating_mul(d);
320                    pd.saturating_sub(full).clamp(1, d)
321                }
322                _ => d,
323            };
324            (
325                Kind::Duration {
326                    d,
327                    start_number: m.start_number,
328                    count,
329                    last_duration,
330                },
331                None,
332            )
333        } else {
334            let seg = Fixed {
335                number: m.start_number,
336                time: 0,
337                start_ticks: 0,
338                duration: pd_ticks.unwrap_or(0),
339                reference: String::new(),
340                range: None,
341            };
342            (Kind::Fixed(vec![seg]), None)
343        };
344        Ok(Self {
345            kind,
346            timescale: ts,
347            pto: m.presentation_time_offset,
348            period_start_us: 0,
349            avail: None,
350            media: Some(MediaTemplate {
351                template: media.to_owned(),
352                representation_id: input.representation_id.to_owned(),
353                bandwidth: input.bandwidth,
354            }),
355            init,
356            index,
357            len_cap,
358        })
359    }
360
361    fn from_list(input: &PlanInput<'_>, l: &ListInfo) -> Result<Self, AddressError> {
362        let m = &l.multi;
363        let ts = m.timescale;
364        let pd_ticks = Self::period_duration_us(input).map(|d| us_to_ticks_ceil(d, ts));
365        let timeline = match &m.timeline {
366            Some(tl) => {
367                let end = period_end_media_ticks(m, input, pd_ticks);
368                Some(TimelineIndex::build(tl, m.start_number, end)?)
369            }
370            None => None,
371        };
372        let segs = list::expand(l, timeline.as_ref(), pd_ticks)?;
373        let pto = i128::from(m.presentation_time_offset);
374        let fixed = segs
375            .into_iter()
376            .map(|s| Fixed {
377                number: s.number,
378                time: s.time,
379                start_ticks: if timeline.is_some() {
380                    i128::from(s.time) - pto
381                } else {
382                    i128::from(s.time)
383                },
384                duration: s.duration,
385                reference: s.reference,
386                range: s.range,
387            })
388            .collect();
389        Ok(Self {
390            kind: Kind::Fixed(fixed),
391            timescale: ts,
392            pto: m.presentation_time_offset,
393            period_start_us: 0,
394            avail: None,
395            media: None,
396            init: m.base.initialization.as_ref().map(url_ref),
397            index: m.base.representation_index.as_ref().map(url_ref),
398            len_cap: None,
399        })
400    }
401
402    fn from_base(input: &PlanInput<'_>, b: &BaseInfo) -> Result<Self, AddressError> {
403        let init = b.initialization.as_ref().map(url_ref);
404        let index = b
405            .representation_index
406            .as_ref()
407            .map(url_ref)
408            .or_else(|| b.index_range.map(|r| (String::new(), Some(r))));
409        let pto = b.presentation_time_offset.unwrap_or(0);
410        let base_ts = b.timescale.unwrap_or(1);
411        if let Some(r) = &b.index_range {
412            let (sidx, anchor) = input.index.ok_or(AddressError::IndexRequired)?;
413            let _ = base_index::anchor_after(r)?;
414            let ts = u64::from(sidx.timescale).max(1);
415            let segs: Vec<IndexedSegment> = base_index::segments(sidx, anchor)?;
416            let fixed = segs
417                .into_iter()
418                .enumerate()
419                .map(|(i, s)| Fixed {
420                    number: i as u64 + 1,
421                    time: s.time,
422                    start_ticks: i128::from(s.time) - i128::from(pto),
423                    duration: s.duration,
424                    reference: String::new(),
425                    range: Some(s.range),
426                })
427                .collect();
428            return Ok(Self {
429                kind: Kind::Fixed(fixed),
430                timescale: ts,
431                pto,
432                period_start_us: 0,
433                avail: None,
434                media: None,
435                init,
436                index,
437                len_cap: None,
438            });
439        }
440        let pd_ticks = Self::period_duration_us(input).map(|d| us_to_ticks_ceil(d, base_ts));
441        let seg = Fixed {
442            number: 1,
443            time: 0,
444            start_ticks: 0,
445            duration: pd_ticks.unwrap_or(0),
446            reference: String::new(),
447            range: None,
448        };
449        Ok(Self {
450            kind: Kind::Fixed(vec![seg]),
451            timescale: base_ts,
452            pto,
453            period_start_us: 0,
454            avail: None,
455            media: None,
456            init,
457            index,
458            len_cap: None,
459        })
460    }
461
462    /// `@timescale` in effect.
463    #[must_use]
464    pub fn timescale(&self) -> u64 {
465        self.timescale
466    }
467
468    /// `@presentationTimeOffset` in ticks.
469    #[must_use]
470    pub fn presentation_time_offset(&self) -> u64 {
471        self.pto
472    }
473
474    /// PeriodStart in microseconds.
475    #[must_use]
476    pub fn period_start_us(&self) -> i128 {
477        self.period_start_us
478    }
479
480    /// Whether availability data is attached (dynamic MPD).
481    #[must_use]
482    pub fn is_dynamic(&self) -> bool {
483        self.avail.is_some()
484    }
485
486    /// Number of segments; `None` when the plan is open-ended (a duration
487    /// template of a Period with no known end).
488    #[must_use]
489    pub fn len(&self) -> Option<u64> {
490        let raw = match &self.kind {
491            Kind::Duration { count, .. } => *count,
492            Kind::Timeline(t) => Some(t.len()),
493            Kind::Fixed(v) => Some(v.len() as u64),
494        };
495        match (raw, self.len_cap) {
496            (Some(a), Some(b)) => Some(a.min(b)),
497            (a, None) => a,
498            (None, b) => b,
499        }
500    }
501
502    /// Whether the plan has no segment.
503    #[must_use]
504    pub fn is_empty(&self) -> bool {
505        self.len() == Some(0)
506    }
507
508    fn reference(
509        &self,
510        number: u64,
511        time: u64,
512        fixed: Option<&str>,
513    ) -> Result<String, AddressError> {
514        match (&self.media, fixed) {
515            (Some(m), _) => template::substitute(
516                &m.template,
517                &m.representation_id,
518                Some(number),
519                Some(time),
520                m.bandwidth,
521            )
522            .map_err(|e| match e {
523                TemplateError::MissingNumber => AddressError::MissingTemplateValue {
524                    template: m.template.clone(),
525                    which: "number",
526                },
527                TemplateError::MissingTime => AddressError::MissingTemplateValue {
528                    template: m.template.clone(),
529                    which: "time",
530                },
531                TemplateError::Invalid(f) => fault(&m.template, f),
532                _ => AddressError::UnsupportedFeature,
533            }),
534            (None, Some(r)) => Ok(r.to_owned()),
535            (None, None) => Ok(String::new()),
536        }
537    }
538
539    /// The segment at zero-based `index`; `Ok(None)` past the end.
540    ///
541    /// # Errors
542    /// [`AddressError`] when a media template cannot be rendered (a template
543    /// naming a value the plan has none for, which [`SegmentPlan::build`]
544    /// already refuses) or the index arithmetic overflows.
545    pub fn get(&self, index: u64) -> Result<Option<SegmentRef>, AddressError> {
546        if self.len().is_some_and(|n| index >= n) {
547            return Ok(None);
548        }
549        let (number, time, start_ticks, duration, reference, range) = match &self.kind {
550            Kind::Duration {
551                d,
552                start_number,
553                count,
554                last_duration,
555            } => {
556                let Some(start) = index.checked_mul(*d) else {
557                    return Err(AddressError::Overflow);
558                };
559                let dur = if count.is_some_and(|c| index + 1 == c) {
560                    *last_duration
561                } else {
562                    *d
563                };
564                let number = start_number.saturating_add(index);
565                (
566                    number,
567                    start,
568                    i128::from(start),
569                    dur,
570                    self.reference(number, start, None)?,
571                    None,
572                )
573            }
574            Kind::Timeline(t) => {
575                let Some(s) = t.segment(index) else {
576                    return Ok(None);
577                };
578                (
579                    s.number,
580                    s.time,
581                    i128::from(s.time) - i128::from(self.pto),
582                    s.duration,
583                    self.reference(s.number, s.time, None)?,
584                    None,
585                )
586            }
587            Kind::Fixed(v) => {
588                let Some(f) = usize::try_from(index).ok().and_then(|i| v.get(i)) else {
589                    return Ok(None);
590                };
591                (
592                    f.number,
593                    f.time,
594                    f.start_ticks,
595                    f.duration,
596                    self.reference(f.number, f.time, Some(&f.reference))?,
597                    f.range,
598                )
599            }
600        };
601        let start_us = ticks_to_us(start_ticks, self.timescale);
602        let duration_us = ticks_to_us(i128::from(duration), self.timescale);
603        let (available_from_us, available_until_us) = match &self.avail {
604            Some(a) => (
605                Some(segment_available_from_ticks(
606                    a.ast_us,
607                    self.period_start_us,
608                    start_ticks,
609                    i128::from(duration),
610                    self.timescale,
611                    a.ato_us,
612                )),
613                segment_available_until_ticks(
614                    a.ast_us,
615                    self.period_start_us,
616                    start_ticks,
617                    i128::from(duration),
618                    self.timescale,
619                    a.tsb_us,
620                ),
621            ),
622            None => (None, None),
623        };
624        Ok(Some(SegmentRef {
625            index,
626            number,
627            time_ticks: time,
628            duration_ticks: duration,
629            timescale: self.timescale,
630            start_us,
631            duration_us,
632            reference,
633            byte_range: range,
634            available_from_us,
635            available_until_us,
636        }))
637    }
638
639    /// The segments in order. For an open-ended plan this never ends; bound it
640    /// with `take`.
641    pub fn iter(&self) -> impl Iterator<Item = SegmentRef> + '_ {
642        let mut i = 0u64;
643        std::iter::from_fn(move || {
644            let r = match self.get(i) {
645                Ok(Some(s)) => s,
646                _ => return None,
647            };
648            i += 1;
649            Some(r)
650        })
651    }
652
653    /// The Initialization Segment address, with its availability window.
654    #[must_use]
655    pub fn init(&self) -> Option<ResourceRef> {
656        self.init
657            .as_ref()
658            .map(|(reference, range)| self.resource(reference, *range))
659    }
660
661    /// The Index Segment address (the `sidx` of a SegmentBase Representation,
662    /// or a `RepresentationIndex`).
663    #[must_use]
664    pub fn index(&self) -> Option<ResourceRef> {
665        self.index
666            .as_ref()
667            .map(|(reference, range)| self.resource(reference, *range))
668    }
669
670    fn resource(&self, reference: &str, range: Option<ByteRange>) -> ResourceRef {
671        let from = self
672            .avail
673            .as_ref()
674            .map(|a| init_available_from(a.ast_us, self.period_start_us));
675        let until =
676            self.len()
677                .and_then(|n| n.checked_sub(1))
678                .and_then(|last| match self.get(last) {
679                    Ok(Some(s)) => s.available_until_us,
680                    _ => None,
681                });
682        ResourceRef {
683            reference: reference.to_owned(),
684            byte_range: range,
685            available_from_us: from,
686            available_until_us: until,
687        }
688    }
689
690    /// Index of the last segment whose Period-relative start is at or before
691    /// `period_time_us`; `None` before the first segment.
692    pub fn index_at_or_before(&self, period_time_us: i128) -> Result<Option<u64>, AddressError> {
693        let n = self.len();
694        let (mut lo, mut hi) = (0u64, n.unwrap_or(u64::MAX / 2));
695        while lo < hi {
696            let mid = lo + (hi - lo) / 2;
697            match self.get(mid) {
698                Ok(Some(s)) if s.start_us <= period_time_us => lo = mid + 1,
699                Ok(Some(_)) | Ok(None) | Err(_) => hi = mid,
700            }
701        }
702        Ok(lo.checked_sub(1))
703    }
704
705    /// Availability start of the segment at `index`.
706    pub fn available_from_us(&self, index: u64) -> Result<Option<i128>, AddressError> {
707        Ok(self.get(index)?.and_then(|s| s.available_from_us))
708    }
709
710    /// Availability end of the segment at `index`.
711    pub fn available_until_us(&self, index: u64) -> Result<Option<i128>, AddressError> {
712        Ok(self.get(index)?.and_then(|s| s.available_until_us))
713    }
714
715    /// The first index, and the number of segments, for which `pred` holds on
716    /// a monotone-in-index predicate over `[0, bound)`: the count of leading
717    /// indices where `pred` is true.
718    pub(crate) fn leading_true(
719        &self,
720        bound: u64,
721        pred: impl Fn(&SegmentRef) -> bool,
722    ) -> Result<u64, AddressError> {
723        let (mut lo, mut hi) = (0u64, bound);
724        while lo < hi {
725            let mid = lo + (hi - lo) / 2;
726            match self.get(mid) {
727                Ok(Some(s)) if pred(&s) => lo = mid + 1,
728                Ok(Some(_)) | Ok(None) | Err(_) => hi = mid,
729            }
730        }
731        Ok(lo)
732    }
733
734    pub(crate) fn constant_duration(&self) -> Option<u64> {
735        match &self.kind {
736            Kind::Duration { d, .. } => Some(*d),
737            _ => None,
738        }
739    }
740
741    pub(crate) fn avail_parts(&self) -> Option<(i128, i128)> {
742        self.avail.as_ref().map(|a| (a.ast_us, a.ato_us))
743    }
744}