Skip to main content

structured_ltv/material/
index.rs

1//! Exact original artifact availability, distinct from validation and trust.
2
3use super::{Coverage, CoverageError};
4use crate::pki::CheckedPath;
5use crate::source::{Location, Query, ScopeError, Snapshot};
6use crate::timestamp::{Purpose, Verified};
7use core::hash::Hash;
8use core::marker::PhantomData;
9use structured_asn1::memory::{HashWork, MemoryError, OwnedMap, OwnedVec, Work};
10use structured_pkix::{Certificate, crl::CertificateList, ocsp::BasicResponse};
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash)]
13pub(super) enum Kind {
14    Certificate,
15    Crl,
16    Basic,
17}
18
19/// Exact identity of one original artifact: its kind and original DER. Two
20/// separately parsed copies of the same value have the same identity.
21#[derive(Clone, Copy, PartialEq, Eq, Hash)]
22pub struct ArtifactKey<'source> {
23    pub(super) kind: Kind,
24    pub(super) bytes: &'source [u8],
25}
26
27impl HashWork for ArtifactKey<'_> {
28    fn hash_work(&self) -> Result<u64, MemoryError> {
29        u64::try_from(self.bytes.len())
30            .map_err(|_| MemoryError::WorkExceeded)?
31            .checked_add(2)
32            .ok_or(MemoryError::WorkExceeded)
33    }
34}
35
36/// Exact identity of a revocation choice carried in a timestamp token. These
37/// ordinals do not address the format's own material pool.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct CarriedOrigin {
40    /// Ordinal in the frozen timestamp token-source set.
41    pub token: usize,
42    /// Original choice ordinal inside that token's `SignedData` revocation set.
43    pub choice: usize,
44}
45
46/// One actual location of an original certificate or status artifact. No path,
47/// authorization, freshness, reason coverage or attained level is asserted.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum ArtifactSource {
50    /// Ordinal in the format's frozen material sources/values pool.
51    Material(usize),
52    /// Certificate already parsed in an original timestamp `SignedData`.
53    TokenCertificate {
54        /// Ordinal in the frozen timestamp source pool.
55        token: usize,
56        /// Ordinal in that message's parsed X.509 certificate catalogue.
57        certificate: usize,
58    },
59    /// Original revocation choice in the separate timestamp source pool.
60    TokenRevocation(CarriedOrigin),
61}
62
63/// One original occurrence, with an explicit source-pool discriminator and the
64/// format's own placement of the value.
65#[derive(Debug, Clone, Copy)]
66pub struct Occurrence<N, D, G, P> {
67    source: ArtifactSource,
68    location: Location<N, D, G>,
69    placement: P,
70    embedded_certificate: Option<usize>,
71}
72
73/// One paid catalogue step: an original occurrence or its source/query refusal.
74type OccurrenceItem<N, D, G, P> = Result<Occurrence<N, D, G, P>, IndexError<N>>;
75
76impl<N: Copy, D: Copy, G: Copy, P: Copy> Occurrence<N, D, G, P> {
77    /// A direct occurrence of a certificate, CRL or whole OCSP value.
78    #[must_use]
79    pub fn new(source: ArtifactSource, location: Location<N, D, G>, placement: P) -> Self {
80        Self {
81            source,
82            location,
83            placement,
84            embedded_certificate: None,
85        }
86    }
87    /// Actual original source pool and its ordinal, never a shared ambiguous
88    /// integer for format material and timestamp token sources.
89    #[must_use]
90    pub fn source(&self) -> ArtifactSource {
91        self.source
92    }
93    /// Original enclosure of the whole material value.
94    #[must_use]
95    pub fn location(&self) -> Location<N, D, G> {
96        self.location
97    }
98    /// Actual container role, not a permission to use it for every actor.
99    #[must_use]
100    pub fn placement(&self) -> P {
101        self.placement
102    }
103    /// Certificate ordinal inside an OCSP basic response, if the artifact is
104    /// embedded there. None means a direct certificate/status value.
105    #[must_use]
106    pub fn embedded_certificate(&self) -> Option<usize> {
107        self.embedded_certificate
108    }
109}
110
111struct Entry<N, D, G, P> {
112    value: Occurrence<N, D, G, P>,
113    next: Option<usize>,
114}
115
116/// Accounted scoped index of every original occurrence. Keys borrow original
117/// DER, not copied/normalized certificate names or unqualified fingerprints.
118/// The same artifact in several containers keeps ALL locations, so later
119/// archive/actor checks cannot mistake a late or disallowed copy for coverage.
120///
121/// `P` is the format's placement of an occurrence and `O` an entry of the
122/// format's schema-open catalogue; the index stores and returns both unread.
123/// Only the holder of issuer `I` builds it from the format's own pools, and it
124/// locates material only under coverage and evidence of the same issuer.
125pub struct Index<'source, N, D, G, P, O, W: Work, I> {
126    snapshot: Snapshot<D, G>,
127    signature: N,
128    keys: OwnedMap<ArtifactKey<'source>, (usize, usize), W>,
129    entries: OwnedVec<Entry<N, D, G, P>, W>,
130    open: &'source [O],
131    issuer: PhantomData<I>,
132}
133
134/// Original source refusal is independent of index capacity/work refusal.
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum IndexError<N> {
137    /// Source generation or current-operation failure.
138    Scope(ScopeError<N>),
139    /// A supplied view/index belongs to another original source generation.
140    Foreign {
141        /// Original selected signature.
142        signature: N,
143    },
144    /// Index hashing/probes/storage or occurrence traversal was not admitted.
145    Resource {
146        /// Original selected signature.
147        signature: N,
148        /// Original resource cause.
149        cause: MemoryError,
150    },
151}
152
153/// One original parsed artifact to locate. This is a source reference, not a
154/// receipt of authentication, applicability, freshness or actor membership.
155#[derive(Debug, Clone, Copy)]
156pub enum ArtifactRef<'view, 'source> {
157    /// Exact original certificate, including a separately supplied anchor.
158    Certificate(&'view Certificate<'source>),
159    /// Exact whole complete or delta CRL. A bound pair requires both artifacts.
160    Crl(&'view CertificateList<'source>),
161    /// Exact basic response from its format-defined retained container. CMS
162    /// RFC5940 uses a whole `OCSPResponse`; legacy `CAdES` Annex A.1.4 explicitly
163    /// carries `BasicOCSPResponse`. The format issuer admits the REAL placement;
164    /// an arbitrary caller body cannot mint an indexed occurrence or evidence.
165    Ocsp(&'view BasicResponse<'source>),
166}
167
168impl<'source> ArtifactRef<'_, 'source> {
169    /// Exact identity of the referenced artifact, borrowing its original DER.
170    #[must_use]
171    pub fn identity(self) -> ArtifactKey<'source> {
172        match self {
173            Self::Certificate(value) => ArtifactKey {
174                kind: Kind::Certificate,
175                bytes: value.as_bytes(),
176            },
177            Self::Crl(value) => ArtifactKey {
178                kind: Kind::Crl,
179                bytes: value.as_bytes(),
180            },
181            Self::Ocsp(value) => ArtifactKey {
182                kind: Kind::Basic,
183                bytes: value.as_bytes(),
184            },
185        }
186    }
187}
188
189/// Exact original artifact availability under one checked archive input.
190#[derive(Debug)]
191pub enum Archived<N, D, G, P> {
192    /// No exact artifact was incorporated in either original source pool.
193    Missing,
194    /// Exact copies exist, but none is covered by this particular archive input.
195    /// A later property or the archive token's own material cannot repair it.
196    Uncovered,
197    /// At least one exact incorporated copy is mathematically covered.
198    Present(Occurrence<N, D, G, P>),
199}
200
201/// Exact position of a certificate in a supplied, actually checked path.
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum PathCertificate {
204    /// Explicitly supplied trust-anchor certificate, if one exists. This is
205    /// material availability only, never a requirement to revoke an anchor.
206    Anchor,
207    /// Issuer-to-subject ordinal in the checked non-anchor member catalogue.
208    Member(usize),
209}
210
211/// One checked-path certificate's mathematical availability before an archive.
212#[derive(Debug)]
213pub struct ArchivedCertificate<N, D, G, P> {
214    /// Original role; no caller-supplied certificate count is used.
215    pub role: PathCertificate,
216    /// Exact copies are searched using the same archive coverage receipt.
217    pub availability: Archived<N, D, G, P>,
218}
219
220/// Complete certificate census for one actual checked path and archive input.
221/// It deliberately retains its path: ordinals cannot be relabelled as another
222/// actor after this check. Status artifacts and actor association are separate.
223pub struct PathCertificates<'path, 'document, N, D, G, P> {
224    path: &'path CheckedPath<'document>,
225    archive: Location<N, D, G>,
226    entries: OwnedVec<ArchivedCertificate<N, D, G, P>>,
227    all_covered: bool,
228}
229
230impl<'path, 'document, N: Copy, D: Copy, G: Copy, P>
231    PathCertificates<'path, 'document, N, D, G, P>
232{
233    /// Same actual common-validator path, not an independent artifact list.
234    #[must_use]
235    pub fn path(&self) -> &'path CheckedPath<'document> {
236        self.path
237    }
238    /// Original archive token whose actual input coverage was used.
239    #[must_use]
240    pub fn archive(&self) -> Location<N, D, G> {
241        self.archive
242    }
243    /// Every existing anchor/member certificate, including missing late copies.
244    #[must_use]
245    pub fn entries(&self) -> &[ArchivedCertificate<N, D, G, P>] {
246        &self.entries
247    }
248    /// Whether every certificate in this path has an exact covered copy.
249    /// This alone proves neither status completeness nor any baseline level.
250    #[must_use]
251    pub fn all_certificates_covered(&self) -> bool {
252        self.all_covered
253    }
254}
255
256/// Original source/resource failure, separately from missing archive material.
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub enum ArchiveError<N> {
259    /// Exact artifact hashing, lookup or source traversal refused.
260    Index(IndexError<N>),
261    /// The mathematical input coverage/source binding could not be established.
262    Coverage(CoverageError<N>),
263    /// The supplied successful token covers a different kind of input.
264    NotArchive {
265        /// Actual original timestamp property.
266        property: N,
267    },
268}
269
270impl<'source, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, I>
271    Index<'source, N, D, G, P, O, W, I>
272{
273    /// An empty index for this signature in the query's source generation.
274    ///
275    /// `open` is the format's schema-open material catalogue, retained only so
276    /// that format checks find it beside the index. Requiring the issuer value
277    /// keeps a caller of the format from building an index of its own.
278    pub fn new<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
279        _issuer: I,
280        signature: N,
281        open: &'source [O],
282        query: &Q,
283    ) -> Result<Self, IndexError<N>> {
284        query.admit(signature, 1).map_err(IndexError::Scope)?;
285        let execution = query.execution();
286        Ok(Self {
287            snapshot: query.snapshot(),
288            signature,
289            keys: execution.map(),
290            entries: execution.vector(),
291            open,
292            issuer: PhantomData,
293        })
294    }
295
296    /// Records one occurrence of an exact original artifact. For an OCSP value
297    /// every certificate embedded in its basic response is recorded too, with
298    /// the same location and placement. Hashing, probes and storage are
299    /// admitted first; a refusal leaves the index for the caller to drop. The
300    /// builder pays its own traversal of the format's pools with [`Self::admit`].
301    /// Requiring the issuer value keeps a caller of the format, which may own
302    /// the index, from adding material the document does not carry.
303    pub fn insert<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
304        &mut self,
305        _issuer: I,
306        artifact: ArtifactRef<'_, 'source>,
307        occurrence: Occurrence<N, D, G, P>,
308        query: &Q,
309    ) -> Result<(), IndexError<N>> {
310        // Coverage lookups compare only the occurrence's node, so its source
311        // generation must already be this index's.
312        if occurrence.location.snapshot != self.snapshot || query.snapshot() != self.snapshot {
313            return Err(IndexError::Foreign {
314                signature: self.signature,
315            });
316        }
317        self.insert_key(artifact.identity(), occurrence, query)?;
318        if let ArtifactRef::Ocsp(response) = artifact {
319            for certificate in 0..response.certificates().len() {
320                self.admit(query)?;
321                self.insert_key(
322                    ArtifactKey {
323                        kind: Kind::Certificate,
324                        bytes: response.certificates()[certificate].as_bytes(),
325                    },
326                    Occurrence {
327                        embedded_certificate: Some(certificate),
328                        ..occurrence
329                    },
330                    query,
331                )?;
332            }
333        }
334        Ok(())
335    }
336
337    /// The format's schema-open catalogue, as supplied.
338    #[must_use]
339    pub fn open(&self) -> &'source [O] {
340        self.open
341    }
342
343    /// Every original indexed occurrence in source-catalogue insertion order,
344    /// including duplicate copies. No hashing, lookup, allocation or clone is
345    /// needed. Current query admission precedes each actual entry access;
346    /// the first refusal is returned once and permanently fuses the traversal.
347    /// Format recipes use this to derive coverage from their already parsed
348    /// material index rather than parsing or enumerating material again.
349    pub fn all_occurrences<'view, 'query, Q>(
350        &'view self,
351        query: &'query Q,
352    ) -> Result<
353        impl Iterator<Item = OccurrenceItem<N, D, G, P>>
354        + use<'view, 'query, 'source, N, D, G, P, O, W, Q, I>,
355        IndexError<N>,
356    >
357    where
358        Q: Query<Id = N, Document = D, Generation = G>,
359    {
360        self.admit(query)?;
361        let mut next = if self.entries.is_empty() {
362            None
363        } else {
364            Some(0)
365        };
366        Ok(core::iter::from_fn(move || {
367            let ordinal = next.take()?;
368            if let Err(cause) = self.admit(query) {
369                return Some(Err(cause));
370            }
371            let value = self.entries[ordinal].value;
372            if ordinal + 1 < self.entries.len() {
373                next = Some(ordinal + 1);
374            }
375            Some(Ok(value))
376        }))
377    }
378
379    /// Original selected signature of this index.
380    #[must_use]
381    pub fn signature(&self) -> N {
382        self.signature
383    }
384
385    /// Source generation of every occurrence.
386    #[must_use]
387    pub fn snapshot(&self) -> Snapshot<D, G> {
388        self.snapshot
389    }
390
391    /// Finds all copies of this exact original certificate, including copies
392    /// inside OCSP. A match is availability only, never authentication/trust.
393    pub fn certificate<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
394        &'view self,
395        value: &Certificate<'_>,
396        query: &'query Q,
397    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
398        self.find(Kind::Certificate, value.as_bytes(), query)
399    }
400    /// Finds all whole CRL copies. An authenticated delta still needs its
401    /// authenticated/bound complete source; finding either alone proves no pair.
402    pub fn crl<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
403        &'view self,
404        value: &CertificateList<'_>,
405        query: &'query Q,
406    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
407        self.find(Kind::Crl, value.as_bytes(), query)
408    }
409    /// Finds the exact basic response in format-issued retained occurrences.
410    /// Good-looking status or a successful wire code does not mint evidence.
411    pub fn ocsp<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
412        &'view self,
413        value: &BasicResponse<'_>,
414        query: &'query Q,
415    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
416        self.find(Kind::Basic, value.as_bytes(), query)
417    }
418
419    /// Finds all copies of this exact artifact, in source catalogue order.
420    pub fn artifact<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
421        &'view self,
422        artifact: ArtifactRef<'_, '_>,
423        query: &'query Q,
424    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
425        let key = artifact.identity();
426        self.find(key.kind, key.bytes, query)
427    }
428
429    /// Requires that this index was built for `signature`.
430    pub fn require_signature<Q: Query<Id = N, Document = D, Generation = G>>(
431        &self,
432        signature: N,
433        query: &Q,
434    ) -> Result<(), IndexError<N>> {
435        self.admit(query)?;
436        let same =
437            query
438                .equal(self.signature, signature)
439                .map_err(|cause| IndexError::Resource {
440                    signature: self.signature,
441                    cause,
442                })?;
443        if !same {
444            return Err(IndexError::Foreign { signature });
445        }
446        Ok(())
447    }
448
449    /// Locates a whole original artifact in this exact checked archive input.
450    ///
451    /// EN319132-1 §6.3(aa)/§5.5.2.2 requires the necessary material to have
452    /// been incorporated before generating each archive timestamp. This uses
453    /// the same selection the format used for actual timestamp input, so merely
454    /// finding a late or self-carried copy cannot count as covered. All copies
455    /// remain available; an early uncovered duplicate does not hide a covered
456    /// one. This proves mathematical availability only. The caller must derive
457    /// required artifacts from actual actor/path/status checks and must
458    /// authenticate the TSA separately before claiming a baseline level.
459    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
460    pub fn archived<Q: Query<Id = N, Document = D, Generation = G>, C: Work>(
461        &self,
462        artifact: ArtifactRef<'_, '_>,
463        coverage: &Coverage<N, D, G, C, I>,
464        evidence: &Verified<N, D, G, I>,
465        query: &Q,
466    ) -> Result<Archived<N, D, G, P>, ArchiveError<N>>
467    where
468        N: HashWork,
469    {
470        if evidence.purpose() != Purpose::Archive {
471            return Err(ArchiveError::NotArchive {
472                property: evidence.property(),
473            });
474        }
475        // Validate even empty/missing catalogues before deriving an absence.
476        if evidence.token().snapshot != self.snapshot {
477            return Err(ArchiveError::Index(IndexError::Foreign {
478                signature: self.signature,
479            }));
480        }
481        self.require_signature(evidence.signature(), query)
482            .map_err(ArchiveError::Index)?;
483        coverage
484            .require_evidence(evidence, evidence.property(), query)
485            .map_err(ArchiveError::Coverage)?;
486        let key = artifact.identity();
487        let copies = self
488            .find(key.kind, key.bytes, query)
489            .map_err(ArchiveError::Index)?;
490        let mut found = false;
491        for copy in copies {
492            let copy = copy.map_err(ArchiveError::Index)?;
493            found = true;
494            // The immutable coverage/evidence pair was bound once above;
495            // each occurrence still checks the current source and lookup.
496            if coverage
497                .selects(copy.location.node, query)
498                .map_err(ArchiveError::Coverage)?
499            {
500                return Ok(Archived::Present(copy));
501            }
502        }
503        Ok(if found {
504            Archived::Uncovered
505        } else {
506            Archived::Missing
507        })
508    }
509
510    /// Derives certificate requirements from the actual common-validator path,
511    /// including an existing anchor certificate, then checks each exact artifact
512    /// against this archive's mathematical input. Missing/late copies remain
513    /// facts; resource/source failure returns no successful partial census.
514    ///
515    /// EN319132-1 V1.3.1 §§5.4.2/5.5.2.2/6.3(aa) require the material of the
516    /// actual actors before each archive. This certificate component does not
517    /// assert that status, credential, countersigner or previous-TSA processing
518    /// completed. The caller must supply their separately bound paths.
519    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
520    pub fn archived_path<'path, 'document, Q, C: Work>(
521        &self,
522        path: &'path CheckedPath<'document>,
523        coverage: &Coverage<N, D, G, C, I>,
524        evidence: &Verified<N, D, G, I>,
525        query: &Q,
526    ) -> Result<PathCertificates<'path, 'document, N, D, G, P>, ArchiveError<N>>
527    where
528        N: HashWork,
529        Q: Query<Id = N, Document = D, Generation = G>,
530    {
531        query
532            .admit(self.signature, 1)
533            .map_err(|cause| ArchiveError::Index(IndexError::Scope(cause)))?;
534        let mut entries = query.execution().vector();
535        let mut all_covered = true;
536        let mut record = |certificate, role| {
537            let availability = self.archived(
538                ArtifactRef::Certificate(certificate),
539                coverage,
540                evidence,
541                query,
542            )?;
543            if !matches!(availability, Archived::Present(_)) {
544                all_covered = false;
545            }
546            entries
547                .try_push(ArchivedCertificate { role, availability })
548                .map_err(|cause| {
549                    ArchiveError::Index(IndexError::Resource {
550                        signature: self.signature,
551                        cause,
552                    })
553                })
554        };
555        if let Some(certificate) = path.anchor().certificate() {
556            record(certificate, PathCertificate::Anchor)?;
557        }
558        for (ordinal, member) in path.members().iter().enumerate() {
559            self.admit(query).map_err(ArchiveError::Index)?;
560            record(member.certificate(), PathCertificate::Member(ordinal))?;
561        }
562        Ok(PathCertificates {
563            path,
564            archive: evidence.token(),
565            entries: entries.into_owned(),
566            all_covered,
567        })
568    }
569
570    /// Admits one traversal step over this index's source generation.
571    pub fn admit<Q: Query<Id = N, Document = D, Generation = G>>(
572        &self,
573        query: &Q,
574    ) -> Result<(), IndexError<N>> {
575        if self.snapshot != query.snapshot() {
576            return Err(IndexError::Foreign {
577                signature: self.signature,
578            });
579        }
580        query.admit(self.signature, 2).map_err(IndexError::Scope)
581    }
582
583    fn insert_key<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
584        &mut self,
585        key: ArtifactKey<'source>,
586        value: Occurrence<N, D, G, P>,
587        query: &Q,
588    ) -> Result<(), IndexError<N>> {
589        query.admit(self.signature, 1).map_err(IndexError::Scope)?;
590        let resource = |cause| IndexError::Resource {
591            signature: self.signature,
592            cause,
593        };
594        let previous = self
595            .keys
596            .get(&key, query.execution())
597            .map(Option::<&(usize, usize)>::copied)
598            .map_err(resource)?;
599        let next = self.entries.len();
600        self.entries
601            .try_push(Entry { value, next: None })
602            .map_err(resource)?;
603        let head = previous.map_or(next, |(head, _)| head);
604        self.keys.try_insert(key, (head, next)).map_err(resource)?;
605        if let Some((_, tail)) = previous {
606            self.entries[tail].next = Some(next);
607        }
608        Ok(())
609    }
610
611    fn find<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
612        &'view self,
613        kind: Kind,
614        bytes: &[u8],
615        query: &'query Q,
616    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
617        self.admit(query)?;
618        query.admit(self.signature, 1).map_err(IndexError::Scope)?;
619        let next = self
620            .keys
621            .get(&ArtifactKey { kind, bytes }, query.execution())
622            .map(|value| value.map(|&(head, _)| head))
623            .map_err(|cause| IndexError::Resource {
624                signature: self.signature,
625                cause,
626            })?;
627        Ok(Occurrences {
628            index: self,
629            query,
630            next,
631        })
632    }
633}
634
635/// Every copy of one artifact, or the refusal to look it up.
636pub type Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> =
637    Result<Occurrences<'view, 'query, 'source, N, D, G, P, O, W, Q, I>, IndexError<N>>;
638
639/// Source-catalogue-order traversal of every copy; current Work pays metadata
640/// access even when index storage belongs to an earlier execution. The first
641/// source/resource refusal is returned once and fuses the iterator.
642pub struct Occurrences<'view, 'query, 'source, N, D, G, P, O, W: Work, Q, I> {
643    index: &'view Index<'source, N, D, G, P, O, W, I>,
644    query: &'query Q,
645    next: Option<usize>,
646}
647
648impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> Iterator
649    for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
650where
651    Q: Query<Id = N, Document = D, Generation = G>,
652{
653    type Item = Result<Occurrence<N, D, G, P>, IndexError<N>>;
654    fn next(&mut self) -> Option<Self::Item> {
655        let next = self.next.take()?;
656        if let Err(error) = self.index.admit(self.query) {
657            return Some(Err(error));
658        }
659        let entry = &self.index.entries[next];
660        self.next = entry.next;
661        Some(Ok(entry.value))
662    }
663}
664
665impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> core::iter::FusedIterator
666    for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
667where
668    Q: Query<Id = N, Document = D, Generation = G>,
669{
670}