use super::{ReferenceIdentifier, ReferenceProperty};
use crate::{
properties::{CalendarFact, CalendarValue, CalendarXml},
reference::VisitError,
xml::{Access, AccessError},
};
use structured_asn1::{
ScopedReader,
memory::{MemoryError, Work},
};
use structured_pkix::Time;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReferenceTime {
Matches,
Differs,
TimezoneUnstated,
Malformed,
Unsupported,
}
#[derive(Debug)]
pub enum ReferenceTimeError<E, N> {
Access(AccessError<E, N>),
Xml(E),
Index {
index: usize,
},
Resource {
node: N,
cause: MemoryError,
},
}
pub fn matches_reference_time<X: CalendarXml>(
xml: &X,
references: &ReferenceProperty<X::Id, X::Document, X::Generation>,
index: usize,
actual: Time,
operation: &ScopedReader<'_, '_>,
) -> Result<Option<ReferenceTime>, ReferenceTimeError<X::Error, X::Id>> {
let work = operation.execution();
let access = Access::new(xml, references.snapshot(), &work);
access
.query(references.node(), |_, _, _| Ok(()))
.map_err(ReferenceTimeError::Access)?;
let Some(entry) = references.entries().get(index) else {
return Err(ReferenceTimeError::Index { index });
};
let (ReferenceIdentifier::Crl { time: node, .. }
| ReferenceIdentifier::Ocsp { time: node, .. }) = entry.identifier()
else {
return Ok(None);
};
let compared = access
.query(node, |xml, snapshot, work| {
Ok(xml.with_calendar(snapshot, node, work, |value| {
let answer = match value {
CalendarFact::Malformed => ReferenceTime::Malformed,
CalendarFact::Unsupported => ReferenceTime::Unsupported,
CalendarFact::Value(value) => compare(value, actual, work)
.map_err(|cause| ReferenceTimeError::Resource { node, cause })?,
};
Ok(Some(answer))
}))
})
.map_err(ReferenceTimeError::Access)?;
compared.map_err(|cause| match cause {
VisitError::Processing(cause) => ReferenceTimeError::Xml(cause),
VisitError::Visitor(cause) => cause,
})
}
fn compare<W: Work>(
value: CalendarValue<'_>,
actual: Time,
work: &W,
) -> Result<ReferenceTime, MemoryError> {
let length = value
.year_digits
.len()
.checked_add(value.fraction_digits.map_or(0, str::len))
.and_then(|length| length.checked_add(32))
.ok_or(MemoryError::WorkExceeded)?;
work.charge_work(u64::try_from(length).map_err(|_| MemoryError::WorkExceeded)?)?;
let Some(offset) = value.timezone_minutes else {
return Ok(ReferenceTime::TimezoneUnstated);
};
if !(-840..=840).contains(&offset) {
return Ok(ReferenceTime::Malformed);
}
if value.year_digits.len() > 5 {
return Ok(ReferenceTime::Differs);
}
let mut year = 0i64;
for digit in value.year_digits.bytes() {
if !digit.is_ascii_digit() {
return Ok(ReferenceTime::Malformed);
}
year = year * 10 + i64::from(digit - b'0');
}
let mut nanos = 0u32;
let mut digits = 0;
if let Some(fraction) = value.fraction_digits {
for digit in fraction.bytes() {
if !digit.is_ascii_digit() {
return Ok(ReferenceTime::Malformed);
}
if digits < 9 {
nanos = nanos * 10 + u32::from(digit - b'0');
} else if digit != b'0' {
return Ok(ReferenceTime::Differs);
}
digits += 1;
}
}
while digits < 9 {
nanos *= 10;
digits += 1;
}
let written = structured_pkix::time::Xsd10Label {
year: if value.negative_year { -year } else { year },
month: value.month,
day: value.day,
hour: value.hour,
minute: value.minute,
second: value.second,
nanos,
};
let Some(written) = written.at_utc(offset, work)? else {
return Ok(ReferenceTime::Malformed);
};
let Some(expected) = actual.xsd10_label(work)?.at_utc(0, work)? else {
return Ok(ReferenceTime::Differs);
};
Ok(if written == expected {
ReferenceTime::Matches
} else {
ReferenceTime::Differs
})
}