extern crate alloc;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use super::{Cut, VersionVector};
mod abandonment;
mod departure;
mod unknown;
pub use abandonment::{AbandonRefusal, Abandoned};
pub use departure::{Departed, Departure, DepartureRefusal, Fenced};
pub use unknown::UnknownStation;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Stability {
reports: BTreeMap<u32, VersionVector>,
abandoned: BTreeSet<u32>,
resurgent: BTreeSet<u32>,
attested: BTreeMap<u32, Departed>,
unwitnessed: BTreeSet<u32>,
}
impl Stability {
#[must_use]
pub fn new(roster: impl IntoIterator<Item = u32>) -> Self {
Self {
reports: roster
.into_iter()
.map(|station| (station, VersionVector::new()))
.collect(),
abandoned: BTreeSet::new(),
resurgent: BTreeSet::new(),
attested: BTreeMap::new(),
unwitnessed: BTreeSet::new(),
}
}
pub fn report(
&mut self,
station: u32,
delivered: &VersionVector,
) -> Result<(), UnknownStation> {
self.fold(station, delivered)?;
let _ = self.unwitnessed.insert(station);
Ok(())
}
pub fn report_cut(&mut self, station: u32, cut: &Cut) -> Result<(), UnknownStation> {
self.fold(station, cut.as_vector())
}
fn fold(&mut self, station: u32, delivered: &VersionVector) -> Result<(), UnknownStation> {
let vouched = self
.reports
.get_mut(&station)
.ok_or(UnknownStation { station })?;
*vouched = vouched.merge(delivered);
if self.abandoned.contains(&station) {
let _ = self.resurgent.insert(station);
}
Ok(())
}
#[must_use]
pub fn watermark(&self) -> VersionVector {
let mut vouched = self.surviving().map(|(_, cut)| cut);
let Some(first) = vouched.next() else {
return VersionVector::new();
};
vouched.fold(first.clone(), |acc, cut| acc.meet(cut))
}
pub(crate) fn surviving_family(&self) -> impl Iterator<Item = u32> + '_ {
self.surviving().map(|(station, _)| station)
}
fn surviving(&self) -> impl Iterator<Item = (u32, &VersionVector)> {
self.reports
.iter()
.filter(|(station, _)| !self.abandoned.contains(*station))
.map(|(&station, cut)| (station, cut))
}
#[must_use]
pub fn watermark_cut(&self) -> Option<Cut> {
self.surviving()
.all(|(station, _)| !self.unwitnessed.contains(&station))
.then(|| Cut::from_witnessed(self.watermark()))
}
pub fn reports(&self) -> impl Iterator<Item = (u32, &VersionVector)> {
self.reports.iter().map(|(&station, cut)| (station, cut))
}
pub fn abandon(&mut self, station: u32) -> Result<Abandoned, AbandonRefusal> {
let bound = self.narrow(station, None)?;
Ok(Abandoned::new(station, bound))
}
pub fn abandon_attested(&mut self, departed: &Departed) -> Result<Abandoned, AbandonRefusal> {
let station = departed.station();
if !self.reports.contains_key(&station) {
return Err(AbandonRefusal::UnknownStation { station });
}
if let Some(held) = self.attested(station)
&& held != departed.bound()
{
return Err(AbandonRefusal::Reattested {
station,
held,
offered: departed.bound(),
});
}
let bound = self.narrow(station, Some(departed))?;
let _ = self.attested.insert(station, departed.clone());
Ok(Abandoned::new(station, bound))
}
fn narrow(
&mut self,
station: u32,
installing: Option<&Departed>,
) -> Result<VersionVector, AbandonRefusal> {
if !self.reports.contains_key(&station) {
return Err(AbandonRefusal::UnknownStation { station });
}
if self.surviving().all(|(surviving, _)| surviving == station) {
return Err(AbandonRefusal::LastSurvivor { station });
}
if let Some(departed) = installing
&& !departed.binds(
self.surviving()
.map(|(surviving, _)| surviving)
.filter(|&surviving| surviving != station),
)
{
return Err(AbandonRefusal::FamilyMismatch { station });
}
let remaining: Vec<u32> = self
.surviving()
.map(|(surviving, _)| surviving)
.filter(|&surviving| surviving != station)
.collect();
if let Some((&stranded, _)) = self
.attested
.iter()
.find(|(_, departed)| !self.servable(departed, remaining.iter().copied()))
{
return Err(AbandonRefusal::StrandsAttestation { station, stranded });
}
let bound = self.abandonment_bound(station);
let _ = self.abandoned.insert(station);
Ok(bound)
}
#[must_use]
pub fn attested(&self, station: u32) -> Option<u64> {
self.attested.get(&station).map(Departed::bound)
}
fn servable(&self, departed: &Departed, family: impl IntoIterator<Item = u32>) -> bool {
let bound = departed.bound();
bound == 0
|| family.into_iter().any(|member| {
departed
.proposal_of(member)
.is_some_and(|held| held >= bound)
|| self.reports[&member].get(departed.station()) >= bound
})
}
#[must_use]
pub fn abandonment_bound(&self, station: u32) -> VersionVector {
if !self.reports.contains_key(&station) {
return VersionVector::new();
}
self.surviving()
.filter(|&(surviving, _)| surviving != station)
.fold(VersionVector::new(), |join, (_, cut)| join.merge(cut))
.restrict([station])
}
#[must_use]
pub fn is_abandoned(&self, station: u32) -> bool {
self.abandoned.contains(&station)
}
pub fn abandoned(&self) -> impl Iterator<Item = u32> + '_ {
self.abandoned.iter().copied()
}
pub fn resurgent(&self) -> impl Iterator<Item = u32> + '_ {
self.resurgent.iter().copied()
}
}