use crate::contract::{InterfaceNo, Ordinal, Timing};
use crate::payload::Violation;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Timestamp(pub i64);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Duration(pub i64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Envelope {
pub stamp: Timestamp,
pub seq: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Provenance {
Init,
Live,
Invalid(Cause),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cause {
Declared,
Detected(Detection),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Detection {
InvalidValue(Violation),
Corrupt,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Freshness {
Fresh,
Stale {
by: Duration,
},
Unbounded,
}
impl Freshness {
pub fn of(envelope: &Envelope, now: Timestamp, timing: Option<Timing>) -> Freshness {
let Some(max) = timing.and_then(|timing| timing.max) else {
return Freshness::Unbounded;
};
let age = now.0.saturating_sub(envelope.stamp.0);
if age <= max.0 {
Freshness::Fresh
} else {
Freshness::Stale {
by: Duration(age.saturating_sub(max.0)),
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Sample<T> {
pub value: T,
pub provenance: Provenance,
pub freshness: Freshness,
pub envelope: Envelope,
}
impl<T> Sample<T> {
pub fn usable(&self) -> bool {
matches!(self.provenance, Provenance::Live)
&& !matches!(self.freshness, Freshness::Stale { .. })
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Occurrence<T> {
pub payload: Result<T, Detection>,
pub envelope: Envelope,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Continuity {
First,
Next,
Lost {
count: u64,
},
NotNewer {
last: u64,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrackerFull;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EventSeqTracker<const N: usize> {
slots: [Option<Channel>; N],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Channel {
interface: InterfaceNo,
ordinal: Ordinal,
last: u64,
}
impl<const N: usize> EventSeqTracker<N> {
pub const fn new() -> Self {
EventSeqTracker { slots: [None; N] }
}
pub fn observe(
&mut self,
interface: InterfaceNo,
ordinal: Ordinal,
seq: u64,
) -> Result<Continuity, TrackerFull> {
let mut free = None;
for (index, slot) in self.slots.iter_mut().enumerate() {
match slot {
Some(channel) if channel.interface == interface && channel.ordinal == ordinal => {
let last = channel.last;
if seq <= last {
return Ok(Continuity::NotNewer { last });
}
channel.last = seq;
let count = seq - last - 1;
return Ok(if count == 0 {
Continuity::Next
} else {
Continuity::Lost { count }
});
}
None if free.is_none() => free = Some(index),
_ => {}
}
}
let index = free.ok_or(TrackerFull)?;
self.slots[index] = Some(Channel {
interface,
ordinal,
last: seq,
});
Ok(Continuity::First)
}
pub fn forget(&mut self, interface: InterfaceNo, ordinal: Ordinal) {
for slot in &mut self.slots {
if matches!(slot, Some(channel) if channel.interface == interface && channel.ordinal == ordinal)
{
*slot = None;
}
}
}
}
impl<const N: usize> Default for EventSeqTracker<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::{Cause, Detection, Duration, Envelope, Freshness, Provenance, Sample, Timestamp};
use crate::payload::{Rule, Violation};
fn sample(provenance: Provenance, freshness: Freshness) -> Sample<u8> {
Sample {
value: 0,
provenance,
freshness,
envelope: Envelope {
stamp: Timestamp(0),
seq: 0,
},
}
}
#[test]
fn only_a_live_value_that_is_not_stale_is_usable() {
let violation = Violation {
type_name: "Speed",
rule: Rule::Range,
};
let provenances = [
Provenance::Init,
Provenance::Live,
Provenance::Invalid(Cause::Declared),
Provenance::Invalid(Cause::Detected(Detection::InvalidValue(violation))),
Provenance::Invalid(Cause::Detected(Detection::Corrupt)),
];
let freshnesses = [
Freshness::Fresh,
Freshness::Stale { by: Duration(1) },
Freshness::Unbounded,
];
for provenance in provenances {
for freshness in freshnesses {
let expected =
provenance == Provenance::Live && !matches!(freshness, Freshness::Stale { .. });
assert_eq!(
sample(provenance, freshness).usable(),
expected,
"{provenance:?} with {freshness:?}"
);
}
}
}
}