use super::ArtifactRef;
use super::index::Key;
use super::status::{ArtifactVisitError, member_source, visit_artifacts};
use crate::pki::{AuthorityUses, ParticipantAuthority, ParticipantClosure, Uses, UsesError};
use crate::xml::{Access, Xml};
use structured_asn1::memory::{MemoryError, OwnedMap, OwnedVec, Work};
pub struct ArtifactUse<'certificate, 'document> {
pub(super) artifact: ArtifactRef<'certificate, 'document>,
pub(super) uses: Uses,
pub(super) targets: Option<usize>,
}
impl<'certificate, 'document> ArtifactUse<'certificate, 'document> {
#[must_use]
pub fn artifact(&self) -> ArtifactRef<'certificate, 'document> {
self.artifact
}
#[must_use]
pub fn uses(&self) -> Uses {
self.uses
}
}
pub struct ArtifactUses<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G> {
inputs: &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G>,
entries: OwnedVec<ArtifactUse<'certificate, 'document>>,
pub(super) targets: OwnedVec<StatusTarget>,
}
pub(super) struct StatusTarget {
pub(super) certificate: usize,
pub(super) next: Option<usize>,
}
impl<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G>
ArtifactUses<'closure, 'requirements, 'view, 'certificate, 'document, N, D, G>
{
#[must_use]
pub fn inputs(
&self,
) -> &'closure ParticipantClosure<'requirements, 'view, 'certificate, 'document, N, D, G> {
self.inputs
}
#[must_use]
pub fn entries(&self) -> &[ArtifactUse<'certificate, 'document>] {
&self.entries
}
}
pub type ArtifactUsesResult<'closure, 'requirements, 'view, 'certificate, 'document, X> = Result<
ArtifactUses<
'closure,
'requirements,
'view,
'certificate,
'document,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>,
UsesError<<X as Xml>::Error, <X as Xml>::Id>,
>;
struct Census<'certificate, 'document, W: Work> {
index: OwnedMap<Key<'document>, usize, W>,
entries: OwnedVec<ArtifactUse<'certificate, 'document>, W>,
targets: OwnedVec<StatusTarget, W>,
}
impl<'certificate, 'document, W: Work> Census<'certificate, 'document, W> {
fn record(
&mut self,
artifact: ArtifactRef<'certificate, 'document>,
uses: Uses,
work: &W,
) -> Result<usize, MemoryError> {
work.charge_work(1)?;
let key = artifact.identity();
if let Some(&index) = self.index.get(&key, work)? {
self.entries[index].uses.merge(uses);
return Ok(index);
}
let index = self.entries.len();
self.index.try_insert(key, index)?;
self.entries.try_push(ArtifactUse {
artifact,
uses,
targets: None,
})?;
Ok(index)
}
}
pub fn artifact_uses<'closure, 'requirements, 'view, 'certificate, 'document, X: Xml, W: Work>(
authorities: &AuthorityUses<
'closure,
'requirements,
'view,
'certificate,
'document,
X::Id,
X::Document,
X::Generation,
>,
access: &Access<'_, X, W>,
) -> ArtifactUsesResult<'closure, 'requirements, 'view, 'certificate, 'document, X> {
let inputs = authorities.inputs();
let requirements = inputs.requirements();
if requirements.scope().location().snapshot != access.snapshot() {
return Err(UsesError::Foreign);
}
access
.query(requirements.signature(), |_, _, _| Ok(()))
.map_err(UsesError::Access)?;
let work = access.execution();
let resource = |cause| UsesError::Closure(crate::pki::AuthorityClosureError::Resource(cause));
let mut census = Census {
index: work.map(),
entries: work.vector(),
targets: work.vector(),
};
for authority in authorities.entries() {
work.charge_work(1).map_err(resource)?;
let path = authority.node().statuses.path();
let uses = authority.uses();
if let Some(certificate) = path.anchor().certificate() {
census
.record(ArtifactRef::Certificate(certificate), uses, work)
.map_err(resource)?;
}
for member in path.members() {
census
.record(ArtifactRef::Certificate(member.certificate()), uses, work)
.map_err(resource)?;
}
for input in authority.node().statuses.entries() {
work.charge_work(1).map_err(resource)?;
let Some(source) = member_source(input) else {
continue;
};
let certificate = census
.record(ArtifactRef::Certificate(source.target()), uses, work)
.map_err(resource)?;
visit_artifacts(
source,
requirements.signature(),
access,
|artifact, _, _| {
let index = census.record(artifact, uses, work)?;
if !matches!(artifact, ArtifactRef::Certificate(_)) {
let next = census.entries[index].targets;
let target = census.targets.len();
census
.targets
.try_push(StatusTarget { certificate, next })?;
census.entries[index].targets = Some(target);
}
Ok::<_, MemoryError>(())
},
)
.map_err(|cause| match cause {
ArtifactVisitError::Access(cause) => UsesError::Access(cause),
ArtifactVisitError::Resource(cause) | ArtifactVisitError::Visitor(cause) => {
resource(cause)
}
})?;
}
}
for entry in requirements.entries() {
work.charge_work(1).map_err(resource)?;
let ParticipantAuthority::Anchor(anchor) = entry.authority() else {
continue;
};
if let Some(certificate) = anchor.anchor().certificate() {
census
.record(
ArtifactRef::Certificate(certificate),
Uses::participant(entry.participant()),
work,
)
.map_err(resource)?;
}
}
Ok(ArtifactUses {
inputs,
entries: census.entries.into_owned(),
targets: census.targets.into_owned(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::material::fixture;
use crate::pki::Participant;
use alloc::sync::Arc;
use structured_asn1::{
Parser,
memory::{Memory, MemoryAccount},
};
use structured_pkix::{Certificate, Version};
fn funded() -> Memory {
Memory::new(Arc::new(MemoryAccount::new(65_536, 1_000_000)))
}
#[test]
fn independent_parsed_copies_merge_roles_by_original_der() {
let first_bytes = fixture::certificate(1, Version::V1, None);
let copy_bytes = first_bytes.clone();
let changed_bytes = fixture::certificate(2, Version::V1, None);
assert_ne!(first_bytes.as_ptr(), copy_bytes.as_ptr());
let source = funded();
let first_parser = Parser::der(source.clone());
let copy_parser = Parser::der(source.clone());
let first = Certificate::read(&first_parser, &first_bytes, 100).unwrap();
let copy = Certificate::read(©_parser, ©_bytes, 900).unwrap();
let changed = Certificate::read(©_parser, &changed_bytes, 1800).unwrap();
let source_used = source.used();
let work = funded();
let mut census = Census {
index: work.map(),
entries: work.vector(),
targets: work.vector(),
};
let timestamp = Uses::participant(Participant::Timestamp {
property: 0usize,
token: 1,
});
let signature = Uses::participant(Participant::Signer(0usize));
let credential = Uses::participant(Participant::Credential {
signature: 0usize,
node: 3,
authority: 0,
});
census
.record(ArtifactRef::Certificate(&first), timestamp, &work)
.unwrap();
census
.record(ArtifactRef::Certificate(©), signature, &work)
.unwrap();
census
.record(ArtifactRef::Certificate(©), credential, &work)
.unwrap();
census
.record(ArtifactRef::Certificate(&changed), credential, &work)
.unwrap();
assert_eq!(census.entries.len(), 2);
assert!(census.entries[0].uses.signature());
assert!(census.entries[0].uses.credential());
assert!(census.entries[0].uses.timestamp());
assert!(!census.entries[1].uses.signature());
assert!(census.entries[1].uses.credential());
assert!(!census.entries[1].uses.timestamp());
assert!(matches!(census.entries[0].artifact,
ArtifactRef::Certificate(value) if core::ptr::eq(value, &raw const first)));
drop(census);
assert_eq!(work.used(), 0);
assert_eq!(source.used(), source_used);
}
#[test]
fn refused_merge_preserves_first_roles_and_releases_scratch() {
let bytes = fixture::certificate(1, Version::V1, None);
let source = funded();
let parser = Parser::der(source.clone());
let value = Certificate::read(&parser, &bytes, 100).unwrap();
let source_used = source.used();
let work = funded();
let mut census = Census {
index: work.map(),
entries: work.vector(),
targets: work.vector(),
};
census
.record(
ArtifactRef::Certificate(&value),
Uses::participant(Participant::Timestamp {
property: 0usize,
token: 1,
}),
&work,
)
.unwrap();
work.charge_work(u64::try_from(work.work_remaining()).unwrap())
.unwrap();
let held = work.used();
assert_eq!(
census.record(
ArtifactRef::Certificate(&value),
Uses::participant(Participant::Signer(0usize)),
&work
),
Err(MemoryError::WorkExceeded)
);
assert_eq!(census.entries.len(), 1);
assert!(census.entries[0].uses.timestamp());
assert!(!census.entries[0].uses.signature());
assert_eq!(work.used(), held);
drop(census);
assert_eq!(work.used(), 0);
assert_eq!(source.used(), source_used);
}
}