use super::Error;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RevisionClock {
next: Option<u64>,
reserved_through: u64,
}
impl RevisionClock {
pub fn restart(previous_reservation: u64, reserved_through: u64) -> Result<Self, Error> {
if previous_reservation == u64::MAX && reserved_through == u64::MAX {
return Ok(Self {
next: None,
reserved_through,
});
}
if reserved_through <= previous_reservation {
return Err(Error::InvalidRecovery);
}
Ok(Self {
next: previous_reservation.checked_add(1),
reserved_through,
})
}
pub const fn reserved_through(self) -> u64 {
self.reserved_through
}
pub fn reserve(&mut self, through: u64) -> Result<(), Error> {
if through <= self.reserved_through {
return Err(Error::InvalidRecovery);
}
self.reserved_through = through;
Ok(())
}
pub fn available(self) -> Result<u64, Error> {
let next = self.next.ok_or(Error::Exhausted)?;
if next > self.reserved_through {
Err(Error::ReservationRequired)
} else {
Ok(next)
}
}
pub fn issue(&mut self) -> Result<u64, Error> {
let next = self.available()?;
self.next = next.checked_add(1);
Ok(next)
}
}
#[cfg(kani)]
mod proofs {
use super::*;
#[kani::proof]
fn reservation_never_reuses_an_issued_revision() {
let previous: u64 = kani::any();
let reserved: u64 = kani::any();
if let Ok(mut clock) = RevisionClock::restart(previous, reserved) {
assert!(clock.available().is_ok() || (previous == u64::MAX && reserved == u64::MAX));
let before = clock;
if let Ok(issued) = clock.issue() {
assert!(issued > previous);
assert!(issued <= reserved);
kani::cover!(issued == u64::MAX, "persistent.revision_exhaustion");
kani::cover!(issued < reserved, "persistent.reserved_capacity");
match clock.available() {
Ok(next) => assert!(next > issued),
Err(error) => {
assert!(error == Error::Exhausted || error == Error::ReservationRequired)
}
}
let extension: u64 = kani::any();
let result = clock.reserve(extension);
if result.is_err() {
assert_eq!(clock.reserved_through(), reserved);
}
if let Ok(restarted) = RevisionClock::restart(before.reserved_through(), extension)
{
if let Ok(next) = restarted.available() {
assert!(next > issued);
kani::cover!(next > issued, "persistent.restart_gap");
}
}
}
}
}
}