use super::{Coverage, CoverageError};
use crate::pki::CheckedPath;
use crate::source::{Location, Query, ScopeError, Snapshot};
use crate::timestamp::{Purpose, Verified};
use core::hash::Hash;
use core::marker::PhantomData;
use structured_asn1::memory::{HashWork, MapExecution, MemoryError, OwnedMap, OwnedVec, Work};
use structured_pkix::{Certificate, crl::CertificateList, ocsp::BasicResponse};
mod storage;
#[cfg(test)]
mod tests;
use storage::Storage;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum Kind {
Certificate,
Crl,
Basic,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ArtifactKey<'source> {
pub(super) kind: Kind,
pub(super) bytes: &'source [u8],
}
impl HashWork for ArtifactKey<'_> {
fn hash_work(&self) -> Result<u64, MemoryError> {
u64::try_from(self.bytes.len())
.map_err(|_| MemoryError::WorkExceeded)?
.checked_add(2)
.ok_or(MemoryError::WorkExceeded)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CarriedOrigin {
pub token: usize,
pub choice: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArtifactSource {
Material(usize),
TokenCertificate {
token: usize,
certificate: usize,
},
TokenRevocation(CarriedOrigin),
}
#[derive(Debug, Clone, Copy)]
pub struct Occurrence<N, D, G, P> {
source: ArtifactSource,
location: Location<N, D, G>,
placement: P,
embedded_certificate: Option<usize>,
}
type OccurrenceItem<N, D, G, P> = Result<Occurrence<N, D, G, P>, IndexError<N>>;
impl<N: Copy, D: Copy, G: Copy, P: Copy> Occurrence<N, D, G, P> {
#[must_use]
pub fn new(source: ArtifactSource, location: Location<N, D, G>, placement: P) -> Self {
Self {
source,
location,
placement,
embedded_certificate: None,
}
}
#[must_use]
pub fn source(&self) -> ArtifactSource {
self.source
}
#[must_use]
pub fn location(&self) -> Location<N, D, G> {
self.location
}
#[must_use]
pub fn placement(&self) -> P {
self.placement
}
#[must_use]
pub fn embedded_certificate(&self) -> Option<usize> {
self.embedded_certificate
}
}
struct Entry<N, D, G, P> {
value: Occurrence<N, D, G, P>,
next: Option<usize>,
}
#[derive(Hash, PartialEq, Eq)]
struct Bucket {
kind: Kind,
hash: u64,
}
impl HashWork for Bucket {
fn hash_work(&self) -> Result<u64, MemoryError> {
Ok(2)
}
}
struct Class<'source> {
key: ArtifactKey<'source>,
producer: structured_pkix::algorithm::Producer<'source>,
head: usize,
tail: usize,
next: Option<usize>,
}
pub struct Index<'source, N, D, G, P, O, W: Work, I> {
snapshot: Snapshot<D, G>,
signature: N,
keys: Option<OwnedMap<Bucket, usize, W>>,
classes: Storage<Class<'source>, W>,
entries: Storage<Entry<N, D, G, P>, W>,
open: &'source [O],
issuer: PhantomData<I>,
refused: Option<IndexError<N>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexError<N> {
Scope(ScopeError<N>),
Foreign {
signature: N,
},
Resource {
signature: N,
cause: MemoryError,
},
}
#[derive(Debug)]
pub enum ProjectionError<N, E> {
Index(IndexError<N>),
Projection(E),
}
pub type Projection<'s, N, D, G, P, O, W, I, E> =
Result<Index<'s, N, D, G, P, O, W, I>, ProjectionError<N, E>>;
#[derive(Debug, Clone, Copy)]
pub enum ArtifactRef<'view, 'source> {
Certificate(&'view Certificate<'source>),
Crl(&'view CertificateList<'source>),
Ocsp(&'view BasicResponse<'source>),
}
impl<'source> ArtifactRef<'_, 'source> {
fn producer(self) -> structured_pkix::algorithm::Producer<'source> {
match self {
Self::Certificate(value) => value.signature_algorithm().execution_origin(),
Self::Crl(value) => value.signature_algorithm().execution_origin(),
Self::Ocsp(value) => value.signature_algorithm().execution_origin(),
}
}
#[must_use]
pub fn execution_in<'operation, W: Work>(
&self,
work: &'operation W,
) -> structured_asn1::memory::Execution<'operation>
where
'source: 'operation,
{
match self {
Self::Certificate(value) => value.execution_in(work),
Self::Crl(value) => value.execution_in(work),
Self::Ocsp(value) => value.execution_in(work),
}
}
#[must_use]
pub fn identity(self) -> ArtifactKey<'source> {
match self {
Self::Certificate(value) => ArtifactKey {
kind: Kind::Certificate,
bytes: value.as_bytes(),
},
Self::Crl(value) => ArtifactKey {
kind: Kind::Crl,
bytes: value.as_bytes(),
},
Self::Ocsp(value) => ArtifactKey {
kind: Kind::Basic,
bytes: value.as_bytes(),
},
}
}
}
#[derive(Debug)]
pub enum Archived<N, D, G, P> {
Missing,
Uncovered,
Present(Occurrence<N, D, G, P>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathCertificate {
Anchor,
Member(usize),
}
#[derive(Debug)]
pub struct ArchivedCertificate<N, D, G, P> {
pub role: PathCertificate,
pub availability: Archived<N, D, G, P>,
}
pub struct PathCertificates<'path, 'document, N, D, G, P> {
path: &'path CheckedPath<'document>,
archive: Location<N, D, G>,
entries: OwnedVec<ArchivedCertificate<N, D, G, P>>,
all_covered: bool,
}
impl<'path, 'document, N: Copy, D: Copy, G: Copy, P>
PathCertificates<'path, 'document, N, D, G, P>
{
#[must_use]
pub fn path(&self) -> &'path CheckedPath<'document> {
self.path
}
#[must_use]
pub fn archive(&self) -> Location<N, D, G> {
self.archive
}
#[must_use]
pub fn entries(&self) -> &[ArchivedCertificate<N, D, G, P>] {
&self.entries
}
#[must_use]
pub fn all_certificates_covered(&self) -> bool {
self.all_covered
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArchiveError<N> {
Index(IndexError<N>),
Coverage(CoverageError<N>),
NotArchive {
property: N,
},
}
impl<'source, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, I>
Index<'source, N, D, G, P, O, W, I>
{
pub fn new<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
_issuer: I,
signature: N,
open: &'source [O],
query: &Q,
) -> Result<Self, IndexError<N>> {
query.admit(signature, 1).map_err(IndexError::Scope)?;
let execution = query.execution();
Ok(Self {
snapshot: query.snapshot(),
signature,
keys: Some(execution.map()),
classes: Storage::new(execution.vector()),
entries: Storage::new(execution.vector()),
open,
issuer: PhantomData,
refused: None,
})
}
pub fn insert<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
&mut self,
_issuer: I,
artifact: ArtifactRef<'_, 'source>,
occurrence: Occurrence<N, D, G, P>,
query: &Q,
) -> Result<(), IndexError<N>> {
if occurrence.location.snapshot != self.snapshot || query.snapshot() != self.snapshot {
return Err(IndexError::Foreign {
signature: self.signature,
});
}
let result = self.insert_artifact(artifact, occurrence, query);
if let Err(cause) = result {
self.refused = Some(cause);
}
result
}
fn insert_artifact<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
&mut self,
artifact: ArtifactRef<'_, 'source>,
occurrence: Occurrence<N, D, G, P>,
query: &Q,
) -> Result<(), IndexError<N>> {
self.admit(query)?;
self.insert_key(artifact.identity(), artifact.producer(), occurrence, query)?;
if let ArtifactRef::Ocsp(response) = artifact {
for certificate in 0..response.certificates().len() {
self.admit(query)?;
self.insert_key(
ArtifactKey {
kind: Kind::Certificate,
bytes: response.certificates()[certificate].as_bytes(),
},
response.certificates()[certificate]
.signature_algorithm()
.execution_origin(),
Occurrence {
embedded_certificate: Some(certificate),
..occurrence
},
query,
)?;
}
}
Ok(())
}
#[must_use]
pub fn open(&self) -> &'source [O] {
self.open
}
pub fn into_projected<N2, D2, G2, P2, I2, Q, E>(
self,
_issuer: I2,
signature: N2,
query: &Q,
mut project: impl FnMut(
Occurrence<N, D, G, P>,
) -> Result<(ArtifactSource, Location<N2, D2, G2>, P2), E>,
) -> Projection<'source, N2, D2, G2, P2, O, Q::Work, I2, E>
where
N2: Copy + Eq,
D2: Copy + Eq,
G2: Copy + Eq,
P2: Copy,
Q: Query<Id = N2, Document = D2, Generation = G2>,
{
query
.admit(signature, 1)
.map_err(|cause| ProjectionError::Index(IndexError::Scope(cause)))?;
let execution = query.execution();
let Index {
keys,
classes,
entries,
open,
refused,
..
} = self;
if refused.is_some() {
return Err(ProjectionError::Index(IndexError::Foreign { signature }));
}
let keys = keys
.expect("restored artifact map")
.into_owned()
.with_execution(execution)
.map_err(|_| ProjectionError::Index(IndexError::Foreign { signature }))?;
let classes = classes
.into_inner()
.into_owned()
.with_execution(execution)
.map_err(|_| ProjectionError::Index(IndexError::Foreign { signature }))?;
let mut projected = execution.vector();
projected
.try_reserve_exact(entries.len())
.map_err(|cause| ProjectionError::Index(IndexError::Resource { signature, cause }))?;
query
.admit(
signature,
u64::try_from(entries.len()).map_err(|_| {
ProjectionError::Index(IndexError::Resource {
signature,
cause: MemoryError::WorkExceeded,
})
})?,
)
.map_err(|cause| ProjectionError::Index(IndexError::Scope(cause)))?;
for entry in entries.into_inner() {
let (source, location, placement) =
project(entry.value).map_err(ProjectionError::Projection)?;
if location.snapshot != query.snapshot() {
return Err(ProjectionError::Index(IndexError::Foreign { signature }));
}
projected
.try_push(Entry {
value: Occurrence {
source,
location,
placement,
embedded_certificate: entry.value.embedded_certificate,
},
next: entry.next,
})
.map_err(|cause| {
ProjectionError::Index(IndexError::Resource { signature, cause })
})?;
}
Ok(Index {
snapshot: query.snapshot(),
signature,
keys: Some(keys),
classes: Storage::new(classes),
entries: Storage::new(projected),
open,
issuer: PhantomData,
refused: None,
})
}
pub fn all_occurrences<'view, 'query, Q>(
&'view self,
query: &'query Q,
) -> Result<
impl Iterator<Item = OccurrenceItem<N, D, G, P>>
+ use<'view, 'query, 'source, N, D, G, P, O, W, Q, I>,
IndexError<N>,
>
where
Q: Query<Id = N, Document = D, Generation = G>,
{
self.admit(query)?;
let mut next = if self.entries.is_empty() {
None
} else {
Some(0)
};
Ok(core::iter::from_fn(move || {
let ordinal = next.take()?;
if let Err(cause) = self.admit(query) {
return Some(Err(cause));
}
let value = self.entries[ordinal].value;
if ordinal + 1 < self.entries.len() {
next = Some(ordinal + 1);
}
Some(Ok(value))
}))
}
#[must_use]
pub fn signature(&self) -> N {
self.signature
}
#[must_use]
pub fn snapshot(&self) -> Snapshot<D, G> {
self.snapshot
}
pub fn certificate<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
&'view self,
value: &Certificate<'_>,
query: &'query Q,
) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
self.find(ArtifactRef::Certificate(value), query)
}
pub fn crl<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
&'view self,
value: &CertificateList<'_>,
query: &'query Q,
) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
self.find(ArtifactRef::Crl(value), query)
}
pub fn ocsp<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
&'view self,
value: &BasicResponse<'_>,
query: &'query Q,
) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
self.find(ArtifactRef::Ocsp(value), query)
}
pub fn artifact<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
&'view self,
artifact: ArtifactRef<'_, '_>,
query: &'query Q,
) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
self.find(artifact, query)
}
pub fn require_signature<Q: Query<Id = N, Document = D, Generation = G>>(
&self,
signature: N,
query: &Q,
) -> Result<(), IndexError<N>> {
self.admit(query)?;
let same =
query
.equal(self.signature, signature)
.map_err(|cause| IndexError::Resource {
signature: self.signature,
cause,
})?;
if !same {
return Err(IndexError::Foreign { signature });
}
Ok(())
}
pub fn archived<Q: Query<Id = N, Document = D, Generation = G>, C: Work>(
&self,
artifact: ArtifactRef<'_, '_>,
coverage: &Coverage<N, D, G, C, I>,
evidence: &Verified<N, D, G, I>,
query: &Q,
) -> Result<Archived<N, D, G, P>, ArchiveError<N>>
where
N: HashWork,
{
if evidence.purpose() != Purpose::Archive {
return Err(ArchiveError::NotArchive {
property: evidence.property(),
});
}
if evidence.token().snapshot != self.snapshot {
return Err(ArchiveError::Index(IndexError::Foreign {
signature: self.signature,
}));
}
self.require_signature(evidence.signature(), query)
.map_err(ArchiveError::Index)?;
coverage
.require_evidence(evidence, evidence.property(), query)
.map_err(ArchiveError::Coverage)?;
let copies = self.find(artifact, query).map_err(ArchiveError::Index)?;
let mut found = false;
for copy in copies {
let copy = copy.map_err(ArchiveError::Index)?;
found = true;
if coverage
.selects(copy.location.node, query)
.map_err(ArchiveError::Coverage)?
{
return Ok(Archived::Present(copy));
}
}
Ok(if found {
Archived::Uncovered
} else {
Archived::Missing
})
}
pub fn archived_path<'path, 'document, Q, C: Work>(
&self,
path: &'path CheckedPath<'document>,
coverage: &Coverage<N, D, G, C, I>,
evidence: &Verified<N, D, G, I>,
query: &Q,
) -> Result<PathCertificates<'path, 'document, N, D, G, P>, ArchiveError<N>>
where
N: HashWork,
Q: Query<Id = N, Document = D, Generation = G>,
{
query
.admit(self.signature, 1)
.map_err(|cause| ArchiveError::Index(IndexError::Scope(cause)))?;
let mut entries = query.execution().vector();
let mut all_covered = true;
let mut record = |certificate, role| {
let availability = self.archived(
ArtifactRef::Certificate(certificate),
coverage,
evidence,
query,
)?;
if !matches!(availability, Archived::Present(_)) {
all_covered = false;
}
entries
.try_push(ArchivedCertificate { role, availability })
.map_err(|cause| {
ArchiveError::Index(IndexError::Resource {
signature: self.signature,
cause,
})
})
};
if let Some(certificate) = path.anchor().certificate() {
record(certificate, PathCertificate::Anchor)?;
}
for (ordinal, member) in path.members().iter().enumerate() {
self.admit(query).map_err(ArchiveError::Index)?;
record(member.certificate(), PathCertificate::Member(ordinal))?;
}
Ok(PathCertificates {
path,
archive: evidence.token(),
entries: entries.into_owned(),
all_covered,
})
}
pub fn admit<Q: Query<Id = N, Document = D, Generation = G>>(
&self,
query: &Q,
) -> Result<(), IndexError<N>> {
if let Some(cause) = self.refused {
return Err(cause);
}
if self.snapshot != query.snapshot() {
return Err(IndexError::Foreign {
signature: self.signature,
});
}
query.admit(self.signature, 2).map_err(IndexError::Scope)
}
fn insert_key<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
&mut self,
key: ArtifactKey<'source>,
producer: structured_pkix::algorithm::Producer<'source>,
value: Occurrence<N, D, G, P>,
query: &Q,
) -> Result<(), IndexError<N>> {
self.admit(query)?;
let signature = self.signature;
let resource = |cause| IndexError::Resource { signature, cause };
let storage = |cause| match cause {
storage::Error::Resource(cause) => resource(cause),
storage::Error::Foreign => IndexError::Foreign { signature },
};
let work = producer.execution_in(query.execution());
work.charge_work(1).map_err(resource)?;
let bucket = Bucket {
kind: key.kind,
hash: self
.keys
.as_ref()
.expect("restored artifact map")
.fingerprint(&key, &work)
.map_err(resource)?,
};
let previous = self.class(&bucket, key, producer, query)?;
if previous.is_some() {
work.charge_work(2).map_err(resource)?;
}
let next = self.entries.len();
self.entries
.scoped(work, |entries| {
entries.try_push(Entry { value, next: None })
})
.map_err(storage)?;
if let Some(ordinal) = previous {
let tail = self.classes[ordinal].tail;
self.entries[tail].next = Some(next);
self.classes[ordinal].tail = next;
} else {
let table = self.keys.as_ref().expect("restored artifact map");
let head = table.get(&bucket, &work).map_err(resource)?.copied();
let ordinal = self.classes.len();
self.classes
.scoped(work, |classes| {
classes.try_push(Class {
key,
producer,
head: next,
tail: next,
next: head,
})
})
.map_err(storage)?;
let original = self.keys.take().expect("restored artifact map");
let (mut table, previous) = match original.rebind(work) {
MapExecution::Changed { map, previous } => (map, previous),
MapExecution::Foreign { map, .. } => {
self.keys = Some(map);
return Err(IndexError::Foreign { signature });
}
};
let result = table.try_insert(bucket, ordinal);
self.keys = Some(match table.rebind(previous) {
MapExecution::Changed { map, .. } => map,
MapExecution::Foreign { .. } => {
unreachable!("restore uses the original table account")
}
});
result.map_err(resource)?;
}
Ok(())
}
fn class<Q: Query<Id = N, Document = D, Generation = G>>(
&self,
bucket: &Bucket,
key: ArtifactKey<'_>,
producer: structured_pkix::algorithm::Producer<'_>,
query: &Q,
) -> Result<Option<usize>, IndexError<N>> {
let resource = |cause| IndexError::Resource {
signature: self.signature,
cause,
};
let work = producer.execution_in(query.execution());
let mut next = self
.keys
.as_ref()
.expect("restored artifact map")
.get(bucket, &work)
.map_err(resource)?
.copied();
while let Some(ordinal) = next {
self.admit(query)?;
let class = &self.classes[ordinal];
let original = class.producer.execution_in(query.execution());
original.charge_work(1).map_err(resource)?;
if key.kind == class.key.kind && key.bytes.len() == class.key.bytes.len() {
if core::ptr::eq(key.bytes, class.key.bytes) {
return Ok(Some(ordinal));
}
let length = u64::try_from(key.bytes.len())
.map_err(|_| resource(MemoryError::WorkExceeded))?;
work.charge_work(length).map_err(resource)?;
original.charge_work(length).map_err(resource)?;
if key.bytes == class.key.bytes {
return Ok(Some(ordinal));
}
}
next = class.next;
}
Ok(None)
}
fn find<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
&'view self,
artifact: ArtifactRef<'_, '_>,
query: &'query Q,
) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
self.admit(query)?;
query.admit(self.signature, 1).map_err(IndexError::Scope)?;
let work = artifact.execution_in(query.execution());
work.charge_work(1).map_err(|cause| IndexError::Resource {
signature: self.signature,
cause,
})?;
let key = artifact.identity();
let bucket = Bucket {
kind: key.kind,
hash: self
.keys
.as_ref()
.expect("restored artifact map")
.fingerprint(&key, &work)
.map_err(|cause| IndexError::Resource {
signature: self.signature,
cause,
})?,
};
let next = self
.class(&bucket, key, artifact.producer(), query)?
.map(|ordinal| self.classes[ordinal].head);
Ok(Occurrences {
index: self,
query,
next,
})
}
}
pub type Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> =
Result<Occurrences<'view, 'query, 'source, N, D, G, P, O, W, Q, I>, IndexError<N>>;
pub struct Occurrences<'view, 'query, 'source, N, D, G, P, O, W: Work, Q, I> {
index: &'view Index<'source, N, D, G, P, O, W, I>,
query: &'query Q,
next: Option<usize>,
}
impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> Iterator
for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
where
Q: Query<Id = N, Document = D, Generation = G>,
{
type Item = Result<Occurrence<N, D, G, P>, IndexError<N>>;
fn next(&mut self) -> Option<Self::Item> {
let next = self.next.take()?;
if let Err(error) = self.index.admit(self.query) {
return Some(Err(error));
}
let entry = &self.index.entries[next];
self.next = entry.next;
Some(Ok(entry.value))
}
}
impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> core::iter::FusedIterator
for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
where
Q: Query<Id = N, Document = D, Generation = G>,
{
}