Skip to main content

structured_pades/
document.rs

1//! Immutable PDF revision index over caller-owned original range acquisition.
2
3mod catalogue;
4mod compare;
5mod controls;
6mod graph;
7mod members;
8pub(crate) mod resolve;
9#[cfg(test)]
10mod tests;
11pub(crate) use graph::Key as GraphKey;
12pub use graph::{Graph, GraphMembers, GraphObject};
13pub use resolve::Reading;
14
15use crate::Session;
16use crate::arena::{Arena, NodeId};
17use crate::error::{Error, ErrorKind, ReadFailure, Syntax};
18use crate::model::{admit, get, unsigned};
19use crate::range::{RangeReader, RangeSource, Stamp};
20use crate::source::{Location, Source};
21use crate::syntax::Cursor;
22use crate::xref::{Entry, Target};
23use structured_asn1::memory::{Memory, OwnedVec, SortError, Work};
24
25/// Historical trailer and exact terminal record belonging to one update.
26#[derive(Debug, Clone, Copy)]
27pub struct Revision {
28    /// Absolute original offset of the primary cross-reference section.
29    pub xref: u64,
30    /// First byte following this update's `%%EOF` line.
31    pub end: u64,
32}
33
34struct Historical {
35    facts: Revision,
36    trailer: NodeId,
37}
38
39struct Section {
40    offset: u64,
41    end: u64,
42    trailer: crate::source::Location,
43    table: bool,
44    ignored_footer: Option<(u64, u64)>,
45}
46
47/// A source-bound immutable index. Reading metadata is distinct from checking
48/// signatures or accepting document permissions and certificate trust.
49pub struct Document<'s, S: RangeSource> {
50    pub(crate) source: &'s S,
51    pub(crate) session: &'s Session,
52    pub(crate) stamp: Stamp<S::Document, S::Generation>,
53    arena: Arena<Memory>,
54    history: OwnedVec<Historical, Memory>,
55    entries: OwnedVec<Entry, Memory>,
56    sections: OwnedVec<Section, Memory>,
57}
58
59#[derive(Clone, Copy)]
60struct Forward {
61    main: u64,
62    upper: u64,
63    start_entries: usize,
64    section_end: u64,
65    primary: usize,
66    revision: usize,
67    location: Location,
68}
69struct Records<'a, W: Work> {
70    entries: &'a mut OwnedVec<Entry, W>,
71    sections: &'a mut OwnedVec<Section, W>,
72    lengths: &'a mut OwnedVec<crate::xref::DeferredLength, W>,
73}
74impl<W: Work> Records<'_, W> {
75    fn reborrow(&mut self) -> Records<'_, W> {
76        Records {
77            entries: self.entries,
78            sections: self.sections,
79            lengths: self.lengths,
80        }
81    }
82}
83
84async fn forward<S: RangeSource, W: Work>(
85    cursor: &mut Cursor<'_, S>,
86    mut arena: Arena<Memory>,
87    records: Records<'_, W>,
88    context: Forward,
89    work: &impl Work,
90) -> Result<(Arena<Memory>, u64), ReadFailure<S::Error>> {
91    let Forward {
92        main,
93        upper,
94        start_entries,
95        section_end,
96        primary,
97        revision,
98        location,
99    } = context;
100    let Records {
101        entries,
102        sections,
103        lengths,
104    } = records;
105    // Annex F.3.4/F.3.11 makes a first-page xref point FORWARD
106    // to the main xref. They describe one physical revision, not
107    // a newer file whose EOF occurs before its own main table.
108    if main >= upper {
109        return Err(cursor.failure(Syntax::CrossReference, main));
110    }
111    for entry in &mut entries[start_entries..] {
112        admit(work, entry.location, 1)?;
113        entry.priority += 2;
114    }
115    cursor.at = section_end;
116    cursor.skip().await?;
117    if cursor.looking(b"startxref").await? {
118        let start = cursor.at;
119        cursor.keyword(b"startxref").await?;
120        cursor.space().await?;
121        cursor.natural().await?;
122        let end = finish_footer(cursor).await?;
123        sections[primary].ignored_footer = Some((start, end));
124    }
125    cursor.at = main;
126    let table = cursor.looking(b"xref").await?;
127    let (next, main_section) = if table {
128        crate::xref::table(cursor, arena, entries, revision, 0).await?
129    } else {
130        crate::xref::stream(cursor, arena, entries, revision, 0).await?
131    };
132    arena = next;
133    if main_section.previous.is_some() {
134        return Err(cursor.failure(Syntax::Cycle, main));
135    }
136    sections
137        .try_push(Section {
138            offset: main,
139            end: main_section.end,
140            trailer: arena.nodes[main_section.trailer.0].location,
141            table,
142            ignored_footer: None,
143        })
144        .map_err(|cause| Error {
145            kind: ErrorKind::Resource(cause),
146            location,
147        })?;
148    if let Some(length) = main_section.length {
149        lengths.try_push(length).map_err(|cause| Error {
150            kind: ErrorKind::Resource(cause),
151            location: length.location,
152        })?;
153    }
154    if let Some(hybrid) = main_section.hybrid {
155        if hybrid >= upper {
156            return Err(cursor.failure(Syntax::CrossReference, hybrid));
157        }
158        cursor.at = hybrid;
159        let (next, supplement) = crate::xref::stream(cursor, arena, entries, revision, 1).await?;
160        arena = next;
161        if supplement.hybrid.is_some() {
162            return Err(cursor.failure(Syntax::CrossReference, hybrid));
163        }
164        sections
165            .try_push(Section {
166                offset: hybrid,
167                end: supplement.end,
168                trailer: arena.nodes[supplement.trailer.0].location,
169                table: false,
170                ignored_footer: None,
171            })
172            .map_err(|cause| Error {
173                kind: ErrorKind::Resource(cause),
174                location,
175            })?;
176        if let Some(length) = supplement.length {
177            lengths.try_push(length).map_err(|cause| Error {
178                kind: ErrorKind::Resource(cause),
179                location: length.location,
180            })?;
181        }
182    }
183    Ok((arena, main_section.end))
184}
185
186#[derive(Clone, Copy)]
187struct Supplement {
188    hybrid: u64,
189    upper: u64,
190    revision: usize,
191    location: Location,
192}
193
194async fn supplement<S: RangeSource, W: Work>(
195    cursor: &mut Cursor<'_, S>,
196    mut arena: Arena<Memory>,
197    records: Records<'_, W>,
198    context: Supplement,
199) -> Result<Arena<Memory>, ReadFailure<S::Error>> {
200    let Supplement {
201        hybrid,
202        upper,
203        revision,
204        location,
205    } = context;
206    let Records {
207        entries,
208        sections,
209        lengths,
210    } = records;
211    if hybrid >= upper {
212        return Err(cursor.failure(Syntax::CrossReference, hybrid));
213    }
214    cursor.at = hybrid;
215    let (next, supplemental) = crate::xref::stream(cursor, arena, entries, revision, 1).await?;
216    arena = next;
217    sections
218        .try_push(Section {
219            offset: hybrid,
220            end: supplemental.end,
221            trailer: arena.nodes[supplemental.trailer.0].location,
222            table: false,
223            ignored_footer: None,
224        })
225        .map_err(|cause| Error {
226            kind: ErrorKind::Resource(cause),
227            location,
228        })?;
229    if let Some(length) = supplemental.length {
230        lengths.try_push(length).map_err(|cause| Error {
231            kind: ErrorKind::Resource(cause),
232            location: length.location,
233        })?;
234    }
235    // The table trailer owns Prev. A supplementary stream must
236    // not redirect the primary history chain (ยง7.5.8.4).
237    if supplemental.hybrid.is_some() {
238        return Err(cursor.failure(Syntax::CrossReference, hybrid));
239    }
240    Ok(arena)
241}
242
243fn save_section<W: Work>(
244    sections: &mut OwnedVec<Section, W>,
245    lengths: &mut OwnedVec<crate::xref::DeferredLength, W>,
246    arena: &Arena<Memory>,
247    section: crate::xref::Section,
248    (offset, table): (u64, bool),
249    location: Location,
250) -> crate::Result<()> {
251    sections
252        .try_push(Section {
253            offset,
254            end: section.end,
255            trailer: arena.nodes[section.trailer.0].location,
256            table,
257            ignored_footer: None,
258        })
259        .map_err(|cause| Error {
260            kind: ErrorKind::Resource(cause),
261            location,
262        })?;
263    if let Some(length) = section.length {
264        lengths.try_push(length).map_err(|cause| Error {
265            kind: ErrorKind::Resource(cause),
266            location: length.location,
267        })?;
268    }
269    Ok(())
270}
271
272struct RevisionTail {
273    offset: u64,
274    final_section_end: u64,
275    final_end: Option<u64>,
276    trailer: NodeId,
277}
278
279async fn revision_tail<S: RangeSource>(
280    cursor: &mut Cursor<'_, S>,
281    arena: &Arena<Memory>,
282    sections: &[Section],
283    tail: RevisionTail,
284    work: &impl Work,
285) -> Result<Historical, ReadFailure<S::Error>> {
286    let RevisionTail {
287        offset,
288        final_section_end,
289        final_end,
290        trailer,
291    } = tail;
292    let location = cursor.location(offset, 0);
293    cursor.at = final_section_end;
294    let end = footer(cursor, offset).await?;
295    for section in sections {
296        admit(work, section.trailer, 1)?;
297        if section.end > end {
298            return Err(cursor.failure(Syntax::CrossReference, section.offset));
299        }
300    }
301    if final_end.is_some_and(|expected| end != expected) {
302        return Err(cursor.failure(Syntax::StartXref, end));
303    }
304    if get(arena, trailer, b"Encrypt", work)?.is_some() {
305        return Err(Error {
306            kind: ErrorKind::Unsupported(crate::error::Unsupported::Encryption),
307            location,
308        }
309        .into());
310    }
311    Ok(Historical {
312        facts: Revision { xref: offset, end },
313        trailer,
314    })
315}
316
317struct Primary {
318    offset: u64,
319    upper: u64,
320    start_entries: usize,
321    section_index: usize,
322    revision: usize,
323    location: Location,
324}
325struct Advanced {
326    arena: Arena<Memory>,
327    historical: Historical,
328    forward: Option<u64>,
329}
330
331async fn advance_revision<S: RangeSource, W: Work>(
332    cursor: &mut Cursor<'_, S>,
333    mut arena: Arena<Memory>,
334    records: Records<'_, W>,
335    context: Primary,
336    section: crate::xref::Section,
337    expected_end: Option<u64>,
338    work: &impl Work,
339) -> Result<Advanced, ReadFailure<S::Error>> {
340    let Records {
341        entries,
342        sections,
343        lengths,
344    } = records;
345    let Primary {
346        offset,
347        upper,
348        start_entries,
349        section_index: primary,
350        revision,
351        location,
352    } = context;
353    let mut final_section_end = section.end;
354    let forward = section.previous.filter(|&previous| previous > offset);
355    if let Some(main) = forward {
356        (arena, final_section_end) = self::forward(
357            cursor,
358            arena,
359            Records {
360                entries,
361                sections,
362                lengths,
363            },
364            Forward {
365                main,
366                upper,
367                start_entries,
368                section_end: section.end,
369                primary,
370                revision,
371                location,
372            },
373            work,
374        )
375        .await?;
376    }
377    let historical = revision_tail(
378        cursor,
379        &arena,
380        &sections[primary..],
381        RevisionTail {
382            offset,
383            final_section_end,
384            final_end: expected_end,
385            trailer: section.trailer,
386        },
387        work,
388    )
389    .await?;
390    Ok(Advanced {
391        arena,
392        historical,
393        forward,
394    })
395}
396
397async fn primary_section<S: RangeSource, W: Work>(
398    cursor: &mut Cursor<'_, S>,
399    arena: Arena<Memory>,
400    records: Records<'_, W>,
401    offset: u64,
402    revision: usize,
403) -> Result<(Arena<Memory>, crate::xref::Section), ReadFailure<S::Error>> {
404    // Both xref spellings share the source-qualified dictionary decoder.
405    let table = cursor.looking(b"xref").await?;
406    let (arena, section) = if table {
407        crate::xref::table(cursor, arena, records.entries, revision, 0).await?
408    } else {
409        crate::xref::stream(cursor, arena, records.entries, revision, 0).await?
410    };
411    save_section(
412        records.sections,
413        records.lengths,
414        &arena,
415        section,
416        (offset, table),
417        cursor.location(offset, 0),
418    )?;
419    Ok((arena, section))
420}
421
422fn chronological<W: Work>(
423    history: &mut [Historical],
424    entries: &mut OwnedVec<Entry, W>,
425    location: Location,
426    work: &impl Work,
427) -> crate::Result<()> {
428    // Wire discovery is newest-first. Exposed revision ordinals are
429    // chronological; each row is remapped once before building the index.
430    let count = history.len();
431    admit(work, location, count as u64)?;
432    history.reverse();
433    for entry in &mut **entries {
434        admit(work, entry.location, 1)?;
435        entry.revision = count - 1 - entry.revision;
436        if let Source::Decoded {
437            stream,
438            revision,
439            stage,
440        } = entry.location.source()
441        {
442            let span = entry.location.span();
443            entry.location = Location::new(
444                Source::Decoded {
445                    stream,
446                    revision: count - 1 - revision,
447                    stage,
448                },
449                span.start,
450                span.length,
451            )
452            .expect("unchanged validated decoded extent");
453        }
454    }
455    entries
456        .try_sort_unstable_by(|left, right| {
457            admit(work, right.location, 1)?;
458            Ok::<_, Error>(
459                left.number
460                    .cmp(&right.number)
461                    .then_with(|| left.revision.cmp(&right.revision))
462                    .then_with(|| left.priority.cmp(&right.priority)),
463            )
464        })
465        .map_err(|error| match error {
466            SortError::Resource(cause) => Error {
467                kind: ErrorKind::Resource(cause),
468                location,
469            },
470            SortError::Comparison(error) => error,
471        })?;
472    // Duplicated entries in one revision have no unique resolver result.
473    // Hybrid precedence is applied by its stream reader before this gate.
474    for pair in entries.windows(2) {
475        admit(work, pair[1].location, 1)?;
476        if pair[0].number == pair[1].number
477            && pair[0].revision == pair[1].revision
478            && pair[0].priority == pair[1].priority
479        {
480            return Err(Error {
481                kind: ErrorKind::Ambiguous(Syntax::CrossReference),
482                location: pair[1].location,
483            });
484        }
485    }
486    Ok(())
487}
488
489impl<'s, S: RangeSource> Document<'s, S> {
490    /// Follows the declared cross-reference history without reconstructing or
491    /// repairing offsets. Every await retains current and original execution.
492    ///
493    /// # Panics
494    /// Panics if internally reassigning a historical ordinal invalidates an
495    /// already checked decoded extent.
496    pub async fn read<W: Work>(
497        source: &'s S,
498        session: &'s Session,
499        operation: &W,
500    ) -> Result<Self, ReadFailure<S::Error>> {
501        let reader = RangeReader::new(source, session, operation)?;
502        let stamp = reader.stamp();
503        let work = reader.execution();
504        let mut cursor = Cursor { reader, at: 0 };
505        header(&mut cursor).await?;
506        let (mut offset, final_end) = terminal(&mut cursor).await?;
507        let mut upper = stamp.length;
508        let mut arena = Arena::new(&work).into_owned();
509        let mut history = work.vector();
510        let mut entries = work.vector();
511        let mut lengths = work.vector();
512        let mut sections = work.vector();
513        let mut records = Records {
514            entries: &mut entries,
515            sections: &mut sections,
516            lengths: &mut lengths,
517        };
518        loop {
519            let location = cursor.location(offset, 0);
520            session.revision(location)?;
521            if offset >= upper {
522                return Err(cursor.failure(Syntax::Cycle, offset));
523            }
524            cursor.at = offset;
525            let start_entries = records.entries.len();
526            let primary = records.sections.len();
527            let (next, section) = primary_section(
528                &mut cursor,
529                arena,
530                records.reborrow(),
531                offset,
532                history.len(),
533            )
534            .await?;
535            arena = next;
536            if let Some(hybrid) = section.hybrid {
537                arena = supplement(
538                    &mut cursor,
539                    arena,
540                    records.reborrow(),
541                    Supplement {
542                        hybrid,
543                        upper,
544                        revision: history.len(),
545                        location,
546                    },
547                )
548                .await?;
549            }
550            let Advanced {
551                arena: next,
552                historical,
553                forward,
554            } = advance_revision(
555                &mut cursor,
556                arena,
557                records.reborrow(),
558                Primary {
559                    offset,
560                    upper,
561                    start_entries,
562                    section_index: primary,
563                    revision: history.len(),
564                    location,
565                },
566                section,
567                if history.is_empty() {
568                    Some(final_end)
569                } else {
570                    None
571                },
572                &work,
573            )
574            .await?;
575            arena = next;
576            history.try_push(historical).map_err(|cause| Error {
577                kind: ErrorKind::Resource(cause),
578                location,
579            })?;
580            let Some(previous) = section.previous.filter(|_| forward.is_none()) else {
581                break;
582            };
583            upper = offset;
584            offset = previous;
585        }
586        chronological(&mut history, &mut entries, cursor.location(0, 0), &work)?;
587        cursor.reader.check(cursor.location(0, stamp.length))?;
588        let document = Self {
589            source,
590            session,
591            stamp,
592            arena,
593            history: history.into_owned(),
594            entries: entries.into_owned(),
595            sections: sections.into_owned(),
596        };
597        document.validate_lengths(&lengths, &work).await?;
598        Ok(document)
599    }
600
601    async fn validate_lengths<W: Work>(
602        &self,
603        lengths: &[crate::xref::DeferredLength],
604        work: &W,
605    ) -> Result<(), ReadFailure<S::Error>> {
606        let count = self.history.len();
607        for length in lengths {
608            admit(work, length.location, 1)?;
609            let owner = self
610                .object(
611                    count - 1 - length.revision,
612                    length.number,
613                    length.generation,
614                    work,
615                )
616                .await?
617                .ok_or(Error {
618                    kind: ErrorKind::Syntax(Syntax::Stream),
619                    location: length.location,
620                })?;
621            if owner.stream.is_some()
622                || unsigned(&owner.value.arena, owner.value.root, work)? != length.actual
623            {
624                return Err(Error {
625                    kind: ErrorKind::Syntax(Syntax::Stream),
626                    location: length.location,
627                }
628                .into());
629            }
630        }
631        Ok(())
632    }
633
634    /// Original immutable document identity used by every revision query.
635    #[must_use]
636    pub fn stamp(&self) -> Stamp<S::Document, S::Generation> {
637        self.stamp
638    }
639
640    pub(crate) fn check(
641        &self,
642        operation: &impl Work,
643        location: Location,
644    ) -> Result<(), crate::range::InputError<S::Error>> {
645        self.session
646            .query(operation)
647            .charge_work(1)
648            .map_err(|cause| crate::range::InputError::Resource { location, cause })?;
649        if self.source.stamp() != self.stamp {
650            return Err(crate::range::InputError::Changed { location });
651        }
652        Ok(())
653    }
654
655    fn original_reader<'q, W: Work>(
656        &'q self,
657        operation: &'q W,
658    ) -> Result<RangeReader<'q, S>, crate::range::InputError<S::Error>> {
659        let reader = RangeReader::new(self.source, self.session, operation)?;
660        if reader.stamp() != self.stamp {
661            return Err(crate::range::InputError::Changed {
662                location: reader.location(0, 0),
663            });
664        }
665        Ok(reader)
666    }
667
668    pub(crate) async fn string<W: Work>(
669        &self,
670        owner: &Reading<'_, S::Document, S::Generation>,
671        node: NodeId,
672        maximum: usize,
673        operation: &W,
674    ) -> Result<OwnedVec<u8, Memory>, ReadFailure<S::Error>> {
675        self.string_in(owner, owner.decoded.as_deref(), node, maximum, operation)
676            .await
677    }
678
679    pub(crate) async fn string_in<W: Work>(
680        &self,
681        owner: &Reading<'_, S::Document, S::Generation>,
682        decoded: Option<&[u8]>,
683        node: NodeId,
684        maximum: usize,
685        operation: &W,
686    ) -> Result<OwnedVec<u8, Memory>, ReadFailure<S::Error>> {
687        let original = owner.value.arena.nodes[node.0];
688        let crate::arena::Kind::String(form) = original.kind else {
689            return Err(Error {
690                kind: ErrorKind::Syntax(Syntax::String),
691                location: original.location,
692            }
693            .into());
694        };
695        if !core::ptr::eq(owner.value.session, self.session) || owner.value.stamp != self.stamp {
696            return Err(Error {
697                kind: ErrorKind::Foreign,
698                location: original.location,
699            }
700            .into());
701        }
702        self.check(operation, original.location)?;
703        let body = structured_asn1::Span {
704            start: original.location.span().start + 1,
705            length: original.location.span().length - 2,
706        };
707        let output = if let Some(decoded) = decoded {
708            if original.location.source() == Source::File {
709                return Err(Error {
710                    kind: ErrorKind::Foreign,
711                    location: original.location,
712                }
713                .into());
714            }
715            let slice = crate::range::Slice::new(decoded, self.stamp.snapshot);
716            let mut reader =
717                RangeReader::in_source(&slice, self.session, operation, original.location.source())
718                    .map_err(|cause| resolve::convert(ReadFailure::Input(cause)))?;
719            reader
720                .decode_bytes_limit(body, form, maximum)
721                .await
722                .map_err(resolve::convert)?
723                .into_owned()
724        } else {
725            if original.location.source() != Source::File {
726                return Err(Error {
727                    kind: ErrorKind::Foreign,
728                    location: original.location,
729                }
730                .into());
731            }
732            let mut reader = RangeReader::new(self.source, self.session, operation)?;
733            reader
734                .decode_bytes_limit(body, form, maximum)
735                .await?
736                .into_owned()
737        };
738        self.check(operation, original.location)?;
739        Ok(output)
740    }
741
742    /// Number of chronological incremental revisions.
743    #[must_use]
744    pub fn revisions(&self) -> usize {
745        self.history.len()
746    }
747
748    /// Fixed-size revision metadata. The caller must still check the live
749    /// snapshot when executing a source-dependent verification operation.
750    #[must_use]
751    pub fn revision(&self, ordinal: usize) -> Option<Revision> {
752        self.history.get(ordinal).map(|entry| entry.facts)
753    }
754
755    pub(crate) async fn root<W: Work>(
756        &self,
757        revision: usize,
758        operation: &W,
759    ) -> Result<Reading<'s, S::Document, S::Generation>, ReadFailure<S::Error>> {
760        let work = self.session.query(operation);
761        let Some(historical) = self.history.get(revision) else {
762            return Err(Error {
763                kind: ErrorKind::Foreign,
764                location: Location::new(Source::File, 0, 0).expect("file point"),
765            }
766            .into());
767        };
768        let root = crate::model::required(&self.arena, historical.trailer, b"Root", &work)?;
769        let crate::arena::Kind::Reference { number, generation } = self.arena.nodes[root.0].kind
770        else {
771            return Err(Error {
772                kind: ErrorKind::Syntax(Syntax::Trailer),
773                location: self.arena.nodes[root.0].location,
774            }
775            .into());
776        };
777        self.object(revision, number, generation, &work)
778            .await?
779            .ok_or(
780                Error {
781                    kind: ErrorKind::Syntax(Syntax::Trailer),
782                    location: self.arena.nodes[root.0].location,
783                }
784                .into(),
785            )
786    }
787
788    pub(crate) async fn select<W: Work>(
789        &self,
790        mut owner: Reading<'s, S::Document, S::Generation>,
791        key: &[u8],
792        operation: &W,
793    ) -> Result<Option<Reading<'s, S::Document, S::Generation>>, ReadFailure<S::Error>> {
794        let work = self.session.query(operation);
795        let Some(node) = get(&owner.value.arena, owner.value.root, key, &work)? else {
796            return Ok(None);
797        };
798        if matches!(
799            owner.value.arena.nodes[node.0].kind,
800            crate::arena::Kind::Reference { .. }
801        ) {
802            match self.resolve_value(&owner, node, &work).await? {
803                Some(resolve::ResolvedValue::Owned(value)) => Ok(Some(value)),
804                None => Ok(None),
805                Some(resolve::ResolvedValue::Borrowed { .. }) => {
806                    unreachable!("an indirect selection owns its resolved reading")
807                }
808            }
809        } else {
810            owner.value.root = node;
811            owner.stream = None;
812            Ok(Some(owner))
813        }
814    }
815
816    pub(crate) fn target(
817        &self,
818        revision: usize,
819        number: u64,
820        generation: u16,
821        operation: &impl Work,
822    ) -> crate::Result<Option<Target>> {
823        let location = Location::new(Source::File, 0, 0).expect("file point");
824        if revision >= self.history.len() {
825            return Err(Error {
826                kind: ErrorKind::Foreign,
827                location,
828            });
829        }
830        let work = self.session.query(operation);
831        let mut low = 0;
832        let mut high = self.entries.len();
833        while low < high {
834            let middle = low + (high - low) / 2;
835            let entry = self.entries[middle];
836            admit(&work, entry.location, 1)?;
837            if entry.number < number || (entry.number == number && entry.revision <= revision) {
838                low = middle + 1;
839            } else {
840                high = middle;
841            }
842        }
843        if low == 0 {
844            return Ok(None);
845        }
846        let entry = self.entries[low - 1];
847        admit(&work, entry.location, 1)?;
848        if entry.number != number {
849            return Ok(None);
850        }
851        Ok(match entry.target {
852            Target::File {
853                generation: actual, ..
854            } if generation == actual => Some(entry.target),
855            Target::Compressed { .. } if generation == 0 => Some(entry.target),
856            _ => None,
857        })
858    }
859}
860
861async fn header<S: RangeSource>(cursor: &mut Cursor<'_, S>) -> Result<(), ReadFailure<S::Error>> {
862    if !cursor.looking(b"%PDF-1.").await? {
863        return Err(cursor.failure(Syntax::Header, 0));
864    }
865    cursor.at = 7;
866    if !matches!(cursor.take().await?, Some(b'0'..=b'7'))
867        || !matches!(cursor.take().await?, Some(b'\r' | b'\n'))
868    {
869        return Err(cursor.failure(Syntax::Header, cursor.at));
870    }
871    Ok(())
872}
873
874async fn terminal<S: RangeSource>(
875    cursor: &mut Cursor<'_, S>,
876) -> Result<(u64, u64), ReadFailure<S::Error>> {
877    // Search from EOF, not a caller-chosen fixed tail window. Work is admitted
878    // by each access, and the cache makes reverse access a bounded range read.
879    let mut at = cursor.reader.stamp().length;
880    while at > 0 {
881        at -= 1;
882        if cursor.reader.byte_at(at).await? != Some(b's') {
883            continue;
884        }
885        cursor.at = at;
886        if !cursor.looking(b"startxref").await? {
887            continue;
888        }
889        cursor.keyword(b"startxref").await?;
890        cursor.space().await?;
891        let offset = cursor.natural().await?;
892        let end = finish_footer(cursor).await?;
893        cursor.space().await?;
894        if cursor.peek().await?.is_some() {
895            return Err(cursor.failure(Syntax::StartXref, cursor.at));
896        }
897        return Ok((offset, end));
898    }
899    Err(cursor.failure(Syntax::StartXref, 0))
900}
901
902async fn footer<S: RangeSource>(
903    cursor: &mut Cursor<'_, S>,
904    expected: u64,
905) -> Result<u64, ReadFailure<S::Error>> {
906    cursor.skip().await?;
907    cursor.keyword(b"startxref").await?;
908    cursor.space().await?;
909    let at = cursor.at;
910    if cursor.natural().await? != expected {
911        return Err(cursor.failure(Syntax::StartXref, at));
912    }
913    finish_footer(cursor).await
914}
915
916async fn finish_footer<S: RangeSource>(
917    cursor: &mut Cursor<'_, S>,
918) -> Result<u64, ReadFailure<S::Error>> {
919    cursor.space().await?;
920    if !cursor.looking(b"%%EOF").await? {
921        return Err(cursor.failure(Syntax::StartXref, cursor.at));
922    }
923    cursor.at += 5;
924    match cursor.peek().await? {
925        Some(b'\r') => {
926            cursor.at += 1;
927            if cursor.peek().await? == Some(b'\n') {
928                cursor.at += 1;
929            }
930        }
931        Some(b'\n') => cursor.at += 1,
932        None => {}
933        _ => return Err(cursor.failure(Syntax::StartXref, cursor.at)),
934    }
935    Ok(cursor.at)
936}