Skip to main content

structured_cades/timestamp/
archive.rs

1//! Original `ATSv3` input/index composition over the existing CMS archive core.
2
3use super::{
4    CheckError, CheckTime, Checked, Context, InputError, ProfileFailure, ProfileSource, tsp,
5};
6use crate::{
7    Cades, Error, ISSUER,
8    prepare::Preparation,
9    source::{DocumentId, Node},
10};
11use core::cell::Cell;
12use structured_asn1::digest::Resolution;
13use structured_asn1::memory::Memory;
14use structured_asn1::{
15    Reader,
16    memory::{Execution, OwnedVec, Work},
17};
18use structured_cms::{
19    Octets,
20    archive::{self, HashIndex, IndexCoverage},
21    digest::{DigestBatch, DigestError, PassError},
22};
23use structured_cms::{
24    provider::SignatureProvider,
25    stream::{Receiver, Refused, Source},
26};
27use structured_ltv::{
28    material::Coverage,
29    source::{Query, Scope, Source as _},
30};
31use structured_ltv::{pki::CheckedPath, timestamp::Purpose};
32
33/// No detached source exists for a carried-content operation. This
34/// uninhabited type cannot supply a fabricated empty-input fallback.
35#[derive(Debug)]
36pub enum NoDetached {}
37impl Source for NoDetached {
38    type Error = core::convert::Infallible;
39    fn stream<W: Work>(&mut self, _: &mut impl Receiver, _: &W) -> Result<(), Self::Error> {
40        match *self {}
41    }
42}
43
44/// Actual `ATSv3` preflight/content refusal, distinct from absent proof.
45#[derive(Debug)]
46pub enum ArchiveError<SE, PE> {
47    /// Original source/schema/encoding/resource refusal.
48    Processing(Error),
49    /// Original archive-index digest/refusal/output-contract cause.
50    Digest(DigestError<PE>),
51    /// An index or imprint mechanism could not be resolved.
52    Unsupported,
53    /// An index or imprint identifier violates its algorithm's schema.
54    MalformedAlgorithm,
55    /// Actual MD5 use is forbidden by the baseline format before index hashing.
56    ForbiddenDigest {
57        /// Original index algorithm enclosure.
58        at: u64,
59    },
60    /// An index hash has no original root value to which it refers.
61    UnmatchedIndex,
62    /// The index and timestamp imprint do not use the same digest mechanism.
63    AlgorithmMismatch,
64    /// Original content pass failed without a complete digest.
65    Content(PassError<SE, PE>),
66    /// Private prepared mapping did not produce its sole completed request.
67    MissingDigest,
68}
69
70/// Sticky original range delivery, with no document buffering or second pass.
71#[derive(Debug)]
72pub enum ArchiveStreamError {
73    /// Metadata admission failed before the next original range.
74    Resource(structured_asn1::memory::MemoryError),
75    /// The common driver retains the original receiver refusal.
76    Receiver(Refused),
77    /// No complete original content digest was prepared; never hash empty bytes.
78    MissingDigest,
79    /// The original archive range helper refused source membership.
80    Source(structured_cms::Error),
81}
82
83enum Content<'s, 'd, S> {
84    Carried(&'s Octets<'d>),
85    Detached(&'s mut S),
86}
87impl<S: Source> Source for Content<'_, '_, S> {
88    type Error = InputError<S::Error>;
89    fn stream<W: Work>(
90        &mut self,
91        receiver: &mut impl Receiver,
92        work: &W,
93    ) -> Result<(), Self::Error> {
94        match self {
95            Self::Carried(source) => source.stream(receiver, work).map_err(InputError::Carried),
96            Self::Detached(source) => source.stream(receiver, work).map_err(InputError::Detached),
97        }
98    }
99}
100
101/// Independently checked archive token, exact original index coverage and its
102/// derived whole-item selection. Application chronology/status/trust remain
103/// separate; coverage is usable only with this exact successful token actor.
104pub struct ArchiveChecked<'model, 'document, 'path> {
105    timestamp: Checked<'model, 'document, 'path>,
106    index: IndexCoverage,
107    coverage: Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
108}
109
110/// Borrowed common facts of an actual modern or historical archive receipt.
111/// Private fields prevent callers from pairing one token with another input's
112/// coverage. Negative checks remain negative; material requires its own actor.
113#[derive(Clone, Copy)]
114pub struct ArchiveView<'view, 'model, 'document, 'path> {
115    timestamp: &'view Checked<'model, 'document, 'path>,
116    coverage: &'view Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
117}
118impl<'v, 'm, 'd, 'p> From<&'v ArchiveChecked<'m, 'd, 'p>> for ArchiveView<'v, 'm, 'd, 'p> {
119    fn from(value: &'v ArchiveChecked<'m, 'd, 'p>) -> Self {
120        Self {
121            timestamp: value.timestamp(),
122            coverage: value.coverage(),
123        }
124    }
125}
126impl<'v, 'm, 'd, 'p> From<&'v super::historical::HistoricalChecked<'m, 'd, 'p>>
127    for ArchiveView<'v, 'm, 'd, 'p>
128{
129    fn from(value: &'v super::historical::HistoricalChecked<'m, 'd, 'p>) -> Self {
130        Self {
131            timestamp: value.timestamp(),
132            coverage: value.coverage(),
133        }
134    }
135}
136impl<'m, 'd, 'p> ArchiveView<'_, 'm, 'd, 'p> {
137    pub(crate) fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
138        self.timestamp
139    }
140    pub(crate) fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
141        self.coverage
142    }
143}
144impl<'m, 'd, 'p> ArchiveChecked<'m, 'd, 'p> {
145    /// Complete original common timestamp facts, including negative outcomes.
146    #[must_use]
147    pub fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
148        &self.timestamp
149    }
150    /// Actual original certificate/revocation/unsigned-value index matches.
151    #[must_use]
152    pub fn index(&self) -> &IndexCoverage {
153        &self.index
154    }
155    /// Mathematical input selection; bind it to this timestamp's actual
156    /// Verified receipt before claiming archived material or earlier actors.
157    #[must_use]
158    pub fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
159        &self.coverage
160    }
161
162    /// Derives the complete earlier-actor census from THIS archive's actual
163    /// mathematical index coverage. Source order and genTime are not used as
164    /// substitutes for coverage; the archive's own TSA is excluded. The full
165    /// original counter census is retained with its explicit inclusion mask.
166    pub fn requirements(
167        &self,
168        prepared: &Preparation<'m, 'd>,
169        operation: &Reader<'d>,
170    ) -> Result<
171        structured_ltv::timestamp::TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
172        ScopeError,
173    > {
174        super::requirements::derive(&self.timestamp, &self.coverage, prepared, operation)
175    }
176}
177
178/// An earlier archive scope requires this exact successful token and source.
179#[derive(Debug)]
180pub enum ScopeError {
181    /// Original session/work/storage refusal.
182    Processing(Error),
183    /// Original archive has no complete common mathematical evidence.
184    Unproven,
185    /// Another parallel signer/preparation in the same original `SignedData`.
186    Foreign,
187    /// Actual coverage/source/current-query refusal, not a missing actor.
188    Coverage(structured_ltv::material::CoverageError<Node>),
189}
190
191struct Input<'s, 'm, 'd> {
192    prepared: &'s Preparation<'m, 'd>,
193    index: &'s HashIndex<'d>,
194    hash: &'s Cell<Option<OwnedVec<u8>>>,
195}
196impl Source for Input<'_, '_, '_> {
197    type Error = ArchiveStreamError;
198    fn stream<W: Work>(
199        &mut self,
200        receiver: &mut impl Receiver,
201        work: &W,
202    ) -> Result<(), Self::Error> {
203        let hash = self.hash.take().ok_or(ArchiveStreamError::MissingDigest)?;
204        let ranges = archive::imprint_input(
205            self.prepared.document().message(),
206            self.prepared.signer(),
207            self.index,
208            &hash,
209        )
210        .map_err(ArchiveStreamError::Source)?;
211        for range in ranges {
212            work.charge_work(1).map_err(ArchiveStreamError::Resource)?;
213            receiver
214                .write(range)
215                .map_err(ArchiveStreamError::Receiver)?;
216        }
217        Ok(())
218    }
219}
220
221/// Full original archive check including its preflight, with original source
222/// map preservation for failures from the carried `TSTInfo` profile callback.
223#[derive(Debug)]
224pub enum ArchiveCheckError<SE, PE, RE> {
225    /// Original index/source failure before common timestamp processing.
226    Before(ArchiveError<SE, PE>),
227    /// Original timestamp/profile/source/provider failure and its source maps.
228    Timestamp(CheckError<ArchiveStreamError, PE, RE, ArchiveError<SE, PE>>),
229}
230
231impl<'m, 'd> Context<'_, 'm, 'd> {
232    /// Checks `ATSv3` against ORIGINAL attached eContent. The imprint input
233    /// contains its content digest, never a copied/normalized whole document.
234    /// This delegates index matching and range construction to the existing
235    /// CMS archive kernel (EN319122-1 §§5.5.2–5.5.3).
236    #[expect(
237        clippy::result_large_err,
238        reason = "index refusal and the original mapped token cause are returned without allocating on an already exhausted account"
239    )]
240    pub fn check_archive_carried<'a, 'p, P, PE, RE>(
241        &'a self,
242        provider: &'a P,
243        path: &'p CheckedPath<'d>,
244        certificate_parent: &Reader<'d>,
245        time: &CheckTime<'_, 'd>,
246        recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
247    ) -> Result<
248        ArchiveChecked<'m, 'd, 'p>,
249        ArchiveCheckError<InputError<core::convert::Infallible>, PE, RE>,
250    >
251    where
252        P: for<'scope> SignatureProvider<'scope, Error = PE>,
253    {
254        let source = self
255            .prepared
256            .document()
257            .message()
258            .content()
259            .content()
260            .ok_or_else(|| {
261                ArchiveCheckError::Before(ArchiveError::Processing(Error::Foreign {
262                    at: self.entry.property().span().start,
263                }))
264            })?;
265        self.archive(
266            provider,
267            path,
268            certificate_parent,
269            time,
270            &mut Content::<NoDetached>::Carried(source),
271            recognizes,
272        )
273    }
274
275    /// Checks the explicit detached input only when original eContent is
276    /// absent. The common source driver preserves incomplete/refused EOF;
277    /// detached acquisition is never a substitute for a carried document.
278    #[expect(
279        clippy::result_large_err,
280        reason = "detached acquisition and nested archive token failures keep their distinct original provenance without boxing"
281    )]
282    pub fn check_archive_detached<'a, 'p, P, S: Source, PE, RE>(
283        &'a self,
284        provider: &'a P,
285        path: &'p CheckedPath<'d>,
286        certificate_parent: &Reader<'d>,
287        time: &CheckTime<'_, 'd>,
288        detached: &mut S,
289        recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
290    ) -> Result<ArchiveChecked<'m, 'd, 'p>, ArchiveCheckError<InputError<S::Error>, PE, RE>>
291    where
292        P: for<'scope> SignatureProvider<'scope, Error = PE>,
293    {
294        if !self.prepared.document().message().content().is_detached() {
295            return Err(ArchiveCheckError::Before(ArchiveError::Processing(
296                Error::Foreign {
297                    at: self.entry.property().span().start,
298                },
299            )));
300        }
301        self.archive(
302            provider,
303            path,
304            certificate_parent,
305            time,
306            &mut Content::Detached(detached),
307            recognizes,
308        )
309    }
310
311    #[expect(
312        clippy::result_large_err,
313        reason = "one archive transaction propagates owned source maps and opaque source/provider causes without a refusal-time allocation"
314    )]
315    fn archive<'a, 'p, P, S: Source, PE, RE>(
316        &'a self,
317        provider: &'a P,
318        path: &'p CheckedPath<'d>,
319        certificate_parent: &Reader<'d>,
320        time: &CheckTime<'_, 'd>,
321        original: &mut S,
322        recognizes: impl FnMut(&tsp::TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
323    ) -> Result<ArchiveChecked<'m, 'd, 'p>, ArchiveCheckError<S::Error, PE, RE>>
324    where
325        P: for<'scope> SignatureProvider<'scope, Error = PE>,
326    {
327        let before = ArchiveCheckError::Before;
328        let processing = |cause| before(ArchiveError::Processing(cause));
329        self.require_archive().map_err(processing)?;
330        let index = HashIndex::of_token(self.token, self.reader)
331            .map_err(|cause| processing(Error::Cms(cause)))?;
332        let coverage = self.matched_index(&index, provider).map_err(before)?;
333        let hash = Cell::new(None);
334        let mut covered = Input {
335            prepared: self.prepared,
336            index: &index,
337            hash: &hash,
338        };
339        let checked = self
340            .check_profile(
341                provider,
342                path,
343                certificate_parent,
344                time,
345                &mut covered,
346                (recognizes, |input| {
347                    let failure = |cause| ProfileFailure {
348                        source: ProfileSource::Information,
349                        cause,
350                    };
351                    let tsp::HashAlgorithmReading::Identifier(algorithm) =
352                        input.info.imprint().algorithm()
353                    else {
354                        return Err(failure(ArchiveError::MalformedAlgorithm));
355                    };
356                    match archive::same_algorithm(&index, algorithm, &input.reader.execution())
357                        .map_err(|cause| failure(ArchiveError::Processing(Error::Cms(cause))))?
358                    {
359                        Resolution::Supported(true) => {}
360                        Resolution::Supported(false) => {
361                            return Err(failure(ArchiveError::AlgorithmMismatch));
362                        }
363                        Resolution::Unsupported => return Err(failure(ArchiveError::Unsupported)),
364                        Resolution::Malformed => {
365                            return Err(failure(ArchiveError::MalformedAlgorithm));
366                        }
367                    }
368                    let work = input.reader.execution();
369                    let identity = match structured_pkix::algorithm::digest::resolve(
370                        provider, algorithm, &work,
371                    )
372                    .map_err(|cause| failure(ArchiveError::Processing(Error::Pkix(cause))))?
373                    {
374                        Resolution::Supported(value) => value,
375                        Resolution::Unsupported => return Err(failure(ArchiveError::Unsupported)),
376                        Resolution::Malformed => {
377                            return Err(failure(ArchiveError::MalformedAlgorithm));
378                        }
379                    };
380                    let mut identities = work.vector();
381                    identities.try_push(identity).map_err(|cause| {
382                        failure(ArchiveError::Processing(Error::resource(
383                            cause,
384                            "cades.archive",
385                            input.info.imprint().span().start,
386                        )))
387                    })?;
388                    let batch = DigestBatch::prepare(
389                        provider,
390                        identities.into_owned(),
391                        &work,
392                        "cades.archive.content",
393                        self.entry.property().span().start,
394                    )
395                    .map_err(|cause| failure(ArchiveError::Digest(cause)))?;
396                    let memo = batch
397                        .run(original, &work)
398                        .map_err(|cause| failure(ArchiveError::Content(cause)))?;
399                    let digest = memo
400                        .for_request(0, &work)
401                        .map_err(|cause| failure(ArchiveError::Digest(cause)))?
402                        .ok_or_else(|| failure(ArchiveError::MissingDigest))?;
403                    // P::Output may borrow the scoped information source. Retain only
404                    // the finished hash bytes across that HRTB boundary; never retain
405                    // a view of the temporary joined TSTInfo, state, or identity.
406                    let mut retained = work.vector();
407                    retained
408                        .try_extend_from_slice(digest.as_bytes())
409                        .map_err(|cause| {
410                            failure(ArchiveError::Processing(Error::resource(
411                                cause,
412                                "cades.archive.content",
413                                self.entry.property().span().start,
414                            )))
415                        })?;
416                    hash.set(Some(retained.into_owned()));
417                    Ok(())
418                }),
419            )
420            .map_err(ArchiveCheckError::Timestamp)?;
421        let selected = self.coverage(&coverage).map_err(processing)?;
422        Ok(ArchiveChecked {
423            timestamp: checked,
424            index: coverage,
425            coverage: selected,
426        })
427    }
428
429    fn matched_index<'a, P, SE, PE>(
430        &'a self,
431        index: &HashIndex<'d>,
432        provider: &'a P,
433    ) -> Result<IndexCoverage, ArchiveError<SE, PE>>
434    where
435        P: for<'scope> SignatureProvider<'scope, Error = PE>,
436    {
437        let processing = ArchiveError::Processing;
438        self.reader
439            .charge(1 + index.algorithm().algorithm().as_bytes().len().min(8) as u64)
440            .map_err(|cause| processing(Error::encoding(cause, "cades.archive.index")))?;
441        if index.algorithm().algorithm()
442            == structured_asn1::Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 2, 5])
443        {
444            return Err(ArchiveError::ForbiddenDigest {
445                at: index.algorithm().offset(),
446            });
447        }
448        let coverage = match archive::check_index(
449            index,
450            self.prepared.document().message(),
451            self.prepared.signer(),
452            provider,
453            self.reader,
454        )
455        .map_err(ArchiveError::Digest)?
456        {
457            Resolution::Supported(value) => value,
458            Resolution::Unsupported => return Err(ArchiveError::Unsupported),
459            Resolution::Malformed => return Err(ArchiveError::MalformedAlgorithm),
460        };
461        if !coverage.is_valid() {
462            return Err(ArchiveError::UnmatchedIndex);
463        }
464        Ok(coverage)
465    }
466    fn coverage(
467        &self,
468        index: &IndexCoverage,
469    ) -> crate::Result<Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades>> {
470        let work = self.reader.execution();
471        let resource = |cause| {
472            Error::resource(
473                cause,
474                "cades.archive.coverage",
475                self.entry.property().span().start,
476            )
477        };
478        let mut items = work.map();
479        let message = self.prepared.document().message();
480        for (choice, covered) in message
481            .certificate_choices()
482            .iter()
483            .zip(index.certificates())
484        {
485            work.charge_work(1).map_err(resource)?;
486            let span = match choice {
487                structured_cms::signed::CertificateChoice::X509(ordinal) => {
488                    message.certificates()[*ordinal].span()
489                }
490                structured_cms::signed::CertificateChoice::Extended(value)
491                | structured_cms::signed::CertificateChoice::AttributeV1(value)
492                | structured_cms::signed::CertificateChoice::AttributeV2(value)
493                | structured_cms::signed::CertificateChoice::Other(value) => value.span(),
494            };
495            items
496                .try_insert(Node::of(span), *covered)
497                .map_err(resource)?;
498        }
499        for (value, covered) in message.revocations().iter().zip(index.revocations()) {
500            work.charge_work(1).map_err(resource)?;
501            items
502                .try_insert(Node::of(value.span()), *covered)
503                .map_err(resource)?;
504        }
505        let mut ranges = work.vector();
506        if let Some(attributes) = self.prepared.signer().signed_attributes() {
507            // EN319122-1 §5.5.3 input3 includes the original signedAttrs,
508            // independently of the index of UNSIGNED attribute values. Their
509            // root content timestamps therefore belong to the covered census.
510            // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
511            ranges
512                .try_push(attributes.element().span())
513                .map_err(resource)?;
514            for attribute in attributes.items() {
515                work.charge_work(1).map_err(resource)?;
516                items
517                    .try_insert(Node::of(attribute.span()), true)
518                    .map_err(resource)?;
519            }
520        }
521
522        if let Some(attributes) = self.prepared.signer().unsigned_attributes() {
523            for attribute in attributes.items() {
524                work.charge_work(1).map_err(resource)?;
525                // check_index constructs the complete original projection.
526                // Each uncovered value clears its owning attribute below;
527                // a genuinely empty set never establishes whole coverage.
528                let all = !attribute.values().is_empty();
529                items
530                    .try_insert(Node::of(attribute.span()), all)
531                    .map_err(resource)?;
532            }
533            for (position, covered) in index.attribute_values() {
534                work.charge_work(1).map_err(resource)?;
535                let attribute = &attributes.items()[position.attribute];
536                let value = &attribute.values()[position.value];
537                items
538                    .try_insert(Node::of(value.span()), *covered)
539                    .map_err(resource)?;
540                if *covered {
541                    ranges.try_push(value.span()).map_err(resource)?;
542                } else if let Some(all) = items
543                    .get_mut(&Node::of(attribute.span()), &work)
544                    .map_err(resource)?
545                {
546                    *all = false;
547                }
548            }
549        }
550        self.include_nested(&mut items, &mut ranges, &work)?;
551        let source = self.prepared.document();
552        let query = Scope::new(source, source.snapshot(), &work);
553        query
554            .admit(self.entry.property(), 1)
555            .map_err(|cause| match cause {
556                structured_ltv::source::ScopeError::Changed { node } => Error::Foreign {
557                    at: node.span().start,
558                },
559                structured_ltv::source::ScopeError::Resource { cause, .. } => resource(cause),
560            })?;
561        Ok(Coverage::for_token(
562            ISSUER,
563            source.snapshot(),
564            Node::of(self.prepared.signer().span()),
565            self.entry.property(),
566            Node::of(
567                self.entry
568                    .value()
569                    .expect("archive requires original token")
570                    .span(),
571            ),
572            Purpose::Archive,
573            items.into_owned(),
574        ))
575    }
576    fn include_nested<W: Work>(
577        &self,
578        items: &mut structured_asn1::memory::OwnedMap<Node, bool, W>,
579        ranges: &mut OwnedVec<structured_asn1::Span, W>,
580        work: &W,
581    ) -> crate::Result<()> {
582        let resource = |cause| {
583            Error::resource(
584                cause,
585                "cades.archive.coverage",
586                self.entry.property().span().start,
587            )
588        };
589        ranges
590            .try_sort_unstable_by(|left, right| {
591                work.charge_work(1)?;
592                Ok::<_, structured_asn1::memory::MemoryError>(left.start.cmp(&right.start))
593            })
594            .map_err(|cause| match cause {
595                structured_asn1::memory::SortError::Resource(cause)
596                | structured_asn1::memory::SortError::Comparison(cause) => resource(cause),
597            })?;
598        let mut maximum = 0;
599        for span in &mut *ranges {
600            work.charge_work(1).map_err(resource)?;
601            maximum = maximum.max(span.start + span.length);
602            span.length = maximum - span.start;
603        }
604        for counter in self.prepared.counters() {
605            let covered = contains(ranges, counter.node(), work).map_err(resource)?;
606            items
607                .try_insert(counter.node(), covered)
608                .map_err(resource)?;
609        }
610        for token in self.prepared.timestamps() {
611            let covered = contains(ranges, token.property(), work).map_err(resource)?;
612            if token.owner() != self.entry.owner() {
613                items
614                    .try_insert(token.property(), covered)
615                    .map_err(resource)?;
616            }
617            if let Some(value) = token.value() {
618                items
619                    .try_insert(
620                        Node::of(value.span()),
621                        contains(ranges, Node::of(value.span()), work).map_err(resource)?,
622                    )
623                    .map_err(resource)?;
624            }
625        }
626        Ok(())
627    }
628
629    fn require_archive(&self) -> crate::Result<()> {
630        // The common ATSv3 input selects a root SignedData.signerInfos item
631        // (EN319122-1 §5.5.3(3)). Preparation retains counter-owned occurrences
632        // with an AttributePlacement finding, never as conforming obligations
633        // whose mathematical actor this root-input operation could establish.
634        if self.entry.purpose() != Purpose::Archive
635            || self.entry.preservation_kind().is_some()
636            || self.entry.owner() != Node::of(self.prepared.signer().span())
637        {
638            return Err(Error::Foreign {
639                at: self.entry.property().span().start,
640            });
641        }
642        Ok(())
643    }
644}
645
646fn contains(
647    ranges: &[structured_asn1::Span],
648    node: Node,
649    work: &impl Work,
650) -> Result<bool, structured_asn1::memory::MemoryError> {
651    let mut from = 0;
652    let mut to = ranges.len();
653    let span = node.span();
654    while from < to {
655        work.charge_work(1)?;
656        let middle = from + (to - from) / 2;
657        if ranges[middle].start <= span.start {
658            from = middle + 1;
659        } else {
660            to = middle;
661        }
662    }
663    if from == 0 {
664        return Ok(false);
665    }
666    let range = ranges[from - 1];
667    Ok(range.start + range.length >= span.start + span.length)
668}