use alloc::boxed::Box;
use crate::wire::{ConversationOrderExhausted, OrderAllocatingEnvelope, TransactionOrder};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OrderClaims {
active_binding_terminals: u64,
membership_exits: u64,
recovery_operation: bool,
recovery_replacement_terminal: bool,
}
impl OrderClaims {
pub const fn new(
active_binding_terminals: u64,
membership_exits: u64,
recovery_operation: bool,
recovery_replacement_terminal: bool,
) -> Result<Self, OrderClaimsInvariantError> {
if recovery_operation != recovery_replacement_terminal {
return Err(OrderClaimsInvariantError::RecoveryPairMismatch {
recovery_operation,
recovery_replacement_terminal,
});
}
Ok(Self {
active_binding_terminals,
membership_exits,
recovery_operation,
recovery_replacement_terminal,
})
}
#[must_use]
pub const fn active_binding_terminals(self) -> u64 {
self.active_binding_terminals
}
#[must_use]
pub const fn membership_exits(self) -> u64 {
self.membership_exits
}
#[must_use]
pub const fn recovery_operation(self) -> bool {
self.recovery_operation
}
#[must_use]
pub const fn recovery_replacement_terminal(self) -> bool {
self.recovery_replacement_terminal
}
#[must_use]
pub fn total(self) -> u128 {
u128::from(self.active_binding_terminals)
+ u128::from(self.membership_exits)
+ u128::from(self.recovery_operation)
+ u128::from(self.recovery_replacement_terminal)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OrderClaimsInvariantError {
RecoveryPairMismatch {
recovery_operation: bool,
recovery_replacement_terminal: bool,
},
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum OrderHigh {
#[default]
Empty,
Allocated(TransactionOrder),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OrderLedgerInvariantError {
ClaimsExceedRemaining {
remaining: u128,
claims: u128,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OrderLedger {
high: OrderHigh,
claims: OrderClaims,
}
impl OrderLedger {
pub fn try_new(
high: OrderHigh,
claims: OrderClaims,
) -> Result<Self, OrderLedgerInvariantError> {
let remaining = remaining_after(high);
let claim_count = claims.total();
if claim_count > remaining {
return Err(OrderLedgerInvariantError::ClaimsExceedRemaining {
remaining,
claims: claim_count,
});
}
Ok(Self { high, claims })
}
#[must_use]
pub const fn high(self) -> OrderHigh {
self.high
}
#[must_use]
pub const fn claims(self) -> OrderClaims {
self.claims
}
#[must_use]
pub fn remaining(self) -> u128 {
remaining_after(self.high)
}
#[cfg(test)]
pub(crate) fn plan_enrollment(self) -> Result<ResultingOrderClaims, OrderAdmissionError> {
self.plan_enrollment_with_recovery_quartet(false)
}
pub(crate) fn plan_enrollment_with_recovery_quartet(
self,
endow_recovery_quartet: bool,
) -> Result<ResultingOrderClaims, OrderAdmissionError> {
self.ensure_quartet_can_be_endowed(endow_recovery_quartet)?;
let active_binding_terminals = self
.claims
.active_binding_terminals
.checked_add(1)
.ok_or(OrderAdmissionError::ActiveBindingClaimOverflow)?;
let membership_exits = self
.claims
.membership_exits
.checked_add(1)
.ok_or(OrderAdmissionError::MembershipExitClaimOverflow)?;
Ok(ResultingOrderClaims(OrderClaims {
active_binding_terminals,
membership_exits,
recovery_operation: self.claims.recovery_operation || endow_recovery_quartet,
recovery_replacement_terminal: self.claims.recovery_replacement_terminal
|| endow_recovery_quartet,
}))
}
#[cfg(test)]
pub(crate) fn plan_detached_attach(self) -> Result<ResultingOrderClaims, OrderAdmissionError> {
let active_binding_terminals = self
.claims
.active_binding_terminals
.checked_add(1)
.ok_or(OrderAdmissionError::ActiveBindingClaimOverflow)?;
Ok(ResultingOrderClaims(OrderClaims {
active_binding_terminals,
..self.claims
}))
}
#[cfg(test)]
#[must_use]
pub(crate) const fn plan_supersession(self) -> ResultingOrderClaims {
ResultingOrderClaims(self.claims)
}
#[must_use]
pub(crate) const fn plan_ordinary_record(self) -> ResultingOrderClaims {
ResultingOrderClaims(self.claims)
}
const fn ensure_quartet_can_be_endowed(
self,
endow_recovery_quartet: bool,
) -> Result<(), OrderAdmissionError> {
if endow_recovery_quartet
&& (self.claims.recovery_operation || self.claims.recovery_replacement_terminal)
{
return Err(OrderAdmissionError::RecoveryOrderReserveAlreadyPresent);
}
Ok(())
}
pub(in crate::lifecycle) fn apply_fenced_recovery(self) -> Result<Self, OrderAdmissionError> {
if !self.claims.recovery_operation || !self.claims.recovery_replacement_terminal {
return Err(OrderAdmissionError::RecoveryOrderReserveMissing {
recovery_operation: self.claims.recovery_operation,
recovery_replacement_terminal: self.claims.recovery_replacement_terminal,
});
}
let active_binding_terminals = self
.claims
.active_binding_terminals
.checked_add(1)
.ok_or(OrderAdmissionError::ActiveBindingClaimOverflow)?;
Ok(Self {
high: self.high,
claims: OrderClaims {
active_binding_terminals,
membership_exits: self.claims.membership_exits,
recovery_operation: false,
recovery_replacement_terminal: false,
},
})
}
pub(in crate::lifecycle) fn apply_fenced_recovery_finalizing_pending(
self,
) -> Result<Self, OrderAdmissionError> {
self.apply_fenced_recovery()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ResultingOrderClaims(OrderClaims);
impl ResultingOrderClaims {
const fn claims(self) -> OrderClaims {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OrderAllocation {
major: TransactionOrder,
resulting: OrderLedger,
}
impl OrderAllocation {
#[must_use]
pub const fn major(self) -> TransactionOrder {
self.major
}
#[must_use]
pub const fn resulting(self) -> OrderLedger {
self.resulting
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OrderAdmissionError {
Exhausted(Box<ConversationOrderExhausted>),
InitialClaimsExceedRemaining {
remaining: u128,
resulting_claims: u128,
},
ActiveBindingClaimOverflow,
MembershipExitClaimOverflow,
RecoveryOrderReserveAlreadyPresent,
RecoveryOrderReserveMissing {
recovery_operation: bool,
recovery_replacement_terminal: bool,
},
}
pub fn allocate_order(
request: OrderAllocatingEnvelope,
current: OrderLedger,
resulting_claims: ResultingOrderClaims,
) -> Result<OrderAllocation, OrderAdmissionError> {
let resulting_claims = resulting_claims.claims();
let resulting_claim_count = resulting_claims.total();
match current.high {
OrderHigh::Empty => {
let major = 0;
let resulting_remaining = u128::from(u64::MAX);
if resulting_claim_count > resulting_remaining {
return Err(OrderAdmissionError::InitialClaimsExceedRemaining {
remaining: resulting_remaining,
resulting_claims: resulting_claim_count,
});
}
Ok(OrderAllocation {
major,
resulting: OrderLedger {
high: OrderHigh::Allocated(major),
claims: resulting_claims,
},
})
}
OrderHigh::Allocated(high) => {
let order_remaining = current.remaining();
let reserved_claims = current.claims.total();
let next = high.checked_add(1);
let resulting_order_remaining = next.map_or(0, |_| order_remaining - 1);
let Some(major) = next else {
return Err(OrderAdmissionError::Exhausted(Box::new(
ConversationOrderExhausted::new(
request,
high,
order_remaining,
reserved_claims,
resulting_order_remaining,
resulting_claim_count,
),
)));
};
if resulting_claim_count > resulting_order_remaining {
return Err(OrderAdmissionError::Exhausted(Box::new(
ConversationOrderExhausted::new(
request,
high,
order_remaining,
reserved_claims,
resulting_order_remaining,
resulting_claim_count,
),
)));
}
Ok(OrderAllocation {
major,
resulting: OrderLedger {
high: OrderHigh::Allocated(major),
claims: resulting_claims,
},
})
}
}
}
fn remaining_after(high: OrderHigh) -> u128 {
match high {
OrderHigh::Empty => u128::from(u64::MAX) + 1,
OrderHigh::Allocated(high) => u128::from(u64::MAX - high),
}
}