use super::{Accuracy, record};
use crate::{Error, ErrorCode, Result};
use structured_asn1::memory::{OwnedVec, Work};
use structured_asn1::{Element, Integer, Reader, Span, Tag, universal};
const FIELD: &str = "tstInfo.accuracy";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Component {
Seconds,
Millis,
Micros,
}
impl Component {
const fn index(self) -> usize {
match self {
Self::Seconds => 0,
Self::Millis => 1,
Self::Micros => 2,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ComponentReading<'a> {
element: Element<'a>,
value: Integer<'a>,
}
impl<'a> ComponentReading<'a> {
#[must_use]
pub fn element(self) -> Element<'a> {
self.element
}
#[must_use]
pub fn value(self) -> Integer<'a> {
self.value
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviationReason {
Negative,
TooWide,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeviationError {
pub component: Component,
pub span: Span,
pub reason: DeviationReason,
}
#[derive(Debug)]
pub struct AccuracyReading<'a> {
parts: [Option<ComponentReading<'a>>; 3],
faults: OwnedVec<Error>,
span: Span,
}
impl<'a> AccuracyReading<'a> {
pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
if element.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(Error::schema(
ErrorCode::UnexpectedTag,
FIELD,
element.span().start,
));
}
let mut inside = parent
.enter(element)
.map_err(|error| Error::encoding(error, FIELD))?;
let execution = parent.execution();
let mut faults = execution.vector();
let mut parts: [Option<ComponentReading<'a>>; 3] = [None; 3];
let mut last = None;
while let Some(field) = inside
.next_element()
.map_err(|error| Error::encoding(error, FIELD))?
{
let at = field.span().start;
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, FIELD, at))?;
let component = if field.tag() == Tag::primitive(universal::INTEGER) {
Component::Seconds
} else if field.tag() == Tag::context(false, 0) {
Component::Millis
} else if field.tag() == Tag::context(false, 1) {
Component::Micros
} else {
record(
&mut faults,
Error::schema(ErrorCode::UnknownField, FIELD, at),
)?;
continue;
};
let index = component.index();
if parts[index].is_none() {
if last.is_some_and(|last| index < last) {
record(&mut faults, Error::schema(ErrorCode::FieldOrder, FIELD, at))?;
}
last = Some(last.map_or(index, |last| last.max(index)));
}
let (value, fault) = inside
.integer_reading(&field)
.map_err(|error| Error::encoding(error, FIELD))?
.into_parts();
if let Some(fault) = fault {
record(&mut faults, Error::encoding(fault, FIELD))?;
}
let number = value.as_u64();
if number.is_none()
|| (component != Component::Seconds
&& !number.is_some_and(|number| (1..=999).contains(&number)))
{
record(
&mut faults,
Error::schema(ErrorCode::MalformedValue, FIELD, at),
)?;
}
if let Some(earlier) = parts[index] {
if earlier.value.as_bytes().len() != value.as_bytes().len() {
return Err(Error::schema(ErrorCode::DuplicateField, FIELD, at));
}
execution
.charge_work(value.as_bytes().len() as u64)
.map_err(|reason| Error::resource(reason, FIELD, at))?;
if earlier.value.as_bytes() != value.as_bytes() {
return Err(Error::schema(ErrorCode::DuplicateField, FIELD, at));
}
record(
&mut faults,
Error::schema(ErrorCode::DuplicateField, FIELD, at),
)?;
continue;
}
parts[index] = Some(ComponentReading {
element: field,
value,
});
}
Ok(Self {
parts,
faults: faults.into_owned(),
span: element.span(),
})
}
#[must_use]
pub fn component(&self, component: Component) -> Option<ComponentReading<'a>> {
self.parts[component.index()]
}
#[must_use]
pub fn span(&self) -> Span {
self.span
}
#[must_use]
pub fn faults(&self) -> &[Error] {
&self.faults
}
#[must_use]
pub fn into_faults(self) -> OwnedVec<Error> {
self.faults
}
pub fn deviation(&self) -> core::result::Result<Accuracy, DeviationError> {
let mut values = [0; 3];
for component in [Component::Seconds, Component::Millis, Component::Micros] {
let index = component.index();
if let Some(part) = self.parts[index] {
values[index] = part.value.as_u64().ok_or_else(|| DeviationError {
component,
span: part.element.span(),
reason: if part.value.is_negative() {
DeviationReason::Negative
} else {
DeviationReason::TooWide
},
})?;
}
}
Ok(Accuracy::new(values[0], values[1], values[2]))
}
}