use alloc::boxed::Box;
use crate::wire::{ConversationSequenceExhausted, SequenceAllocatingEnvelope, SequenceBudget};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum RecoverySequenceReserve {
#[default]
None,
DetachedCredentialRecovery,
}
impl RecoverySequenceReserve {
const fn claims(self) -> (u64, u64) {
match self {
Self::None => (0, 0),
Self::DetachedCredentialRecovery => (1, 1),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SequenceClaims {
live_members: u64,
binding_terminals: u64,
markers: u64,
recovery: RecoverySequenceReserve,
}
impl SequenceClaims {
#[must_use]
pub const fn new(
live_members: u64,
binding_terminals: u64,
markers: u64,
recovery: RecoverySequenceReserve,
) -> Self {
Self {
live_members,
binding_terminals,
markers,
recovery,
}
}
#[must_use]
pub const fn live_members(self) -> u64 {
self.live_members
}
#[must_use]
pub const fn binding_terminals(self) -> u64 {
self.binding_terminals
}
#[must_use]
pub const fn markers(self) -> u64 {
self.markers
}
#[must_use]
pub const fn recovery(self) -> RecoverySequenceReserve {
self.recovery
}
#[must_use]
pub fn budget(self, high_watermark: u64) -> SequenceBudget {
let live_members = u128::from(self.live_members);
let exits = self.live_members;
let exits_wide = u128::from(exits);
let terminals = u128::from(self.binding_terminals);
let (rs, rt) = self.recovery.claims();
let replacement_terminals = u128::from(rt);
let other_live_members = if live_members == 0 {
0
} else {
live_members - 1
};
SequenceBudget {
high_watermark,
remaining: u64::MAX - high_watermark,
e: exits,
t: self.binding_terminals,
m: self.markers,
rs,
rt,
l_times_t: live_members * terminals,
l_times_rt: live_members * replacement_terminals,
l_other_times_e: other_live_members * exits_wide,
}
}
#[must_use]
pub fn checked_required_reserve(self) -> Option<u128> {
checked_required_reserve(&self.budget(0))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SequenceLedgerInvariantError {
ClaimsExceedRemaining {
budget: Box<SequenceBudget>,
required_reserve: Option<u128>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SequenceLedger {
high_watermark: u64,
claims: SequenceClaims,
required_reserve: u128,
}
impl SequenceLedger {
pub fn try_new(
high_watermark: u64,
claims: SequenceClaims,
) -> Result<Self, SequenceLedgerInvariantError> {
let budget = claims.budget(high_watermark);
let required_reserve = checked_required_reserve(&budget);
let Some(required_reserve) = required_reserve else {
return Err(SequenceLedgerInvariantError::ClaimsExceedRemaining {
budget: Box::new(budget),
required_reserve,
});
};
if required_reserve > u128::from(budget.remaining) {
return Err(SequenceLedgerInvariantError::ClaimsExceedRemaining {
budget: Box::new(budget),
required_reserve: Some(required_reserve),
});
}
Ok(Self {
high_watermark,
claims,
required_reserve,
})
}
#[must_use]
pub const fn high_watermark(self) -> u64 {
self.high_watermark
}
#[must_use]
pub const fn claims(self) -> SequenceClaims {
self.claims
}
#[must_use]
pub fn budget(self) -> SequenceBudget {
self.claims.budget(self.high_watermark)
}
#[must_use]
pub const fn required_reserve(self) -> u128 {
self.required_reserve
}
#[cfg(test)]
pub(crate) fn plan_enrollment(
self,
new_markers: u64,
) -> Result<ResultingSequenceState, SequenceAdmissionError> {
self.plan_enrollment_with_recovery_quartet(new_markers, false)
}
pub(crate) fn plan_enrollment_with_recovery_quartet(
self,
new_markers: u64,
endow_recovery_quartet: bool,
) -> Result<ResultingSequenceState, SequenceAdmissionError> {
self.ensure_quartet_can_be_endowed(endow_recovery_quartet)?;
let high_watermark = self.checked_high_watermark(1)?;
let live_members = self.claims.live_members.checked_add(1).ok_or(
SequenceAdmissionError::LiveMemberClaimOverflow {
live_members: self.claims.live_members,
},
)?;
let binding_terminals = self.claims.binding_terminals.checked_add(1).ok_or(
SequenceAdmissionError::BindingTerminalClaimOverflow {
binding_terminals: self.claims.binding_terminals,
},
)?;
let markers = self.checked_markers(new_markers)?;
Ok(ResultingSequenceState {
high_watermark,
claims: SequenceClaims {
live_members,
binding_terminals,
markers,
recovery: if endow_recovery_quartet {
RecoverySequenceReserve::DetachedCredentialRecovery
} else {
self.claims.recovery
},
},
})
}
#[cfg(test)]
pub(crate) fn plan_detached_attach(
self,
new_markers: u64,
) -> Result<ResultingSequenceState, SequenceAdmissionError> {
let high_watermark = self.checked_high_watermark(1)?;
let binding_terminals = self.claims.binding_terminals.checked_add(1).ok_or(
SequenceAdmissionError::BindingTerminalClaimOverflow {
binding_terminals: self.claims.binding_terminals,
},
)?;
let markers = self.checked_markers(new_markers)?;
Ok(ResultingSequenceState {
high_watermark,
claims: SequenceClaims {
binding_terminals,
markers,
..self.claims
},
})
}
#[cfg(test)]
pub(crate) fn plan_supersession(
self,
new_markers: u64,
) -> Result<ResultingSequenceState, SequenceAdmissionError> {
let high_watermark = self.checked_high_watermark(2)?;
let markers = self.checked_markers(new_markers)?;
Ok(ResultingSequenceState {
high_watermark,
claims: SequenceClaims {
markers,
..self.claims
},
})
}
pub(crate) fn plan_ordinary_record(
self,
new_markers: u64,
) -> Result<ResultingSequenceState, SequenceAdmissionError> {
let high_watermark = self.checked_high_watermark(1)?;
let markers = self.checked_markers(new_markers)?;
Ok(ResultingSequenceState {
high_watermark,
claims: SequenceClaims {
markers,
..self.claims
},
})
}
const fn ensure_quartet_can_be_endowed(
self,
endow_recovery_quartet: bool,
) -> Result<(), SequenceAdmissionError> {
if endow_recovery_quartet
&& matches!(
self.claims.recovery,
RecoverySequenceReserve::DetachedCredentialRecovery
)
{
return Err(SequenceAdmissionError::RecoverySequenceReserveAlreadyPresent);
}
Ok(())
}
pub(in crate::lifecycle) fn apply_fenced_recovery(
self,
) -> Result<Self, SequenceAdmissionError> {
if self.claims.recovery != RecoverySequenceReserve::DetachedCredentialRecovery {
return Err(SequenceAdmissionError::RecoverySequenceReserveMissing);
}
let high_watermark = self.checked_high_watermark(1)?;
let binding_terminals = self.claims.binding_terminals.checked_add(1).ok_or(
SequenceAdmissionError::BindingTerminalClaimOverflow {
binding_terminals: self.claims.binding_terminals,
},
)?;
let claims = SequenceClaims {
binding_terminals,
recovery: RecoverySequenceReserve::None,
..self.claims
};
let budget = claims.budget(high_watermark);
let required_reserve = checked_required_reserve(&budget)
.ok_or(SequenceAdmissionError::RecoverySequenceInvariantViolation)?;
if required_reserve > u128::from(budget.remaining) {
return Err(SequenceAdmissionError::RecoverySequenceInvariantViolation);
}
Ok(Self {
high_watermark,
claims,
required_reserve,
})
}
fn checked_high_watermark(self, required_values: u64) -> Result<u64, SequenceAdmissionError> {
self.high_watermark.checked_add(required_values).ok_or(
SequenceAdmissionError::HighWatermarkOverflow {
high_watermark: self.high_watermark,
required_values,
},
)
}
fn checked_markers(self, new_markers: u64) -> Result<u64, SequenceAdmissionError> {
self.claims.markers.checked_add(new_markers).ok_or(
SequenceAdmissionError::MarkerClaimOverflow {
markers: self.claims.markers,
new_markers,
},
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ResultingSequenceState {
high_watermark: u64,
claims: SequenceClaims,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SequenceAdmission {
resulting: SequenceLedger,
}
impl SequenceAdmission {
#[must_use]
pub const fn resulting(self) -> SequenceLedger {
self.resulting
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SequenceAdmissionError {
Exhausted(Box<ConversationSequenceExhausted>),
HighWatermarkOverflow {
high_watermark: u64,
required_values: u64,
},
LiveMemberClaimOverflow {
live_members: u64,
},
BindingTerminalClaimOverflow {
binding_terminals: u64,
},
MarkerClaimOverflow {
markers: u64,
new_markers: u64,
},
RecoverySequenceReserveMissing,
RecoverySequenceReserveAlreadyPresent,
RecoverySequenceInvariantViolation,
}
pub fn admit_sequence(
request: SequenceAllocatingEnvelope,
resulting: ResultingSequenceState,
) -> Result<SequenceAdmission, SequenceAdmissionError> {
let budget = resulting.claims.budget(resulting.high_watermark);
let Some(required_reserve) = checked_required_reserve(&budget) else {
return Err(SequenceAdmissionError::Exhausted(Box::new(
ConversationSequenceExhausted {
request,
sequence_budget: budget,
},
)));
};
if required_reserve > u128::from(budget.remaining) {
return Err(SequenceAdmissionError::Exhausted(Box::new(
ConversationSequenceExhausted {
request,
sequence_budget: budget,
},
)));
}
Ok(SequenceAdmission {
resulting: SequenceLedger {
high_watermark: resulting.high_watermark,
claims: resulting.claims,
required_reserve,
},
})
}
fn checked_required_reserve(budget: &SequenceBudget) -> Option<u128> {
let mut reserve = u128::from(budget.e);
reserve = reserve.checked_add(u128::from(budget.t))?;
reserve = reserve.checked_add(u128::from(budget.m))?;
reserve = reserve.checked_add(u128::from(budget.rs))?;
reserve = reserve.checked_add(u128::from(budget.rt))?;
reserve = reserve.checked_add(budget.l_times_t)?;
reserve = reserve.checked_add(budget.l_times_rt)?;
reserve.checked_add(budget.l_other_times_e)
}