use super::{
ClientParticipantAggregate, LostAuthorityTestimony,
barrier::LostAuthorityResolutionRefusalReason,
};
use crate::outcome::{ReconnectDelayResult, ReconnectRequiredEvent, ReconnectState};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EstablishedConnectionTransportFate {
Lost,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProvedOnlineTransition {
ProvedOnline,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExplicitReconnectAction {
ReconnectNow,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReconnectFreshEvent {
TransportFate(EstablishedConnectionTransportFate),
OnlineTransition(ProvedOnlineTransition),
ExplicitCallerAction(ExplicitReconnectAction),
}
impl ReconnectFreshEvent {
const fn required_event(self) -> ReconnectRequiredEvent {
match self {
Self::TransportFate(_) => ReconnectRequiredEvent::TransportFate,
Self::OnlineTransition(_) => ReconnectRequiredEvent::OnlineTransition,
Self::ExplicitCallerAction(_) => ReconnectRequiredEvent::ExplicitCallerAction,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum ReconnectMachineState {
Parked,
Permit {
authorization: u64,
event: ReconnectFreshEvent,
issued: bool,
},
Attempt {
authorization: u64,
event: ReconnectFreshEvent,
},
Online,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ReconnectAggregate {
pub(super) state: ReconnectMachineState,
pub(super) next_authorization: u64,
pub(super) lost: Option<LostAuthorityTestimony>,
}
impl ReconnectAggregate {
pub(super) const fn new() -> Self {
Self {
state: ReconnectMachineState::Parked,
next_authorization: 0,
lost: None,
}
}
#[must_use]
pub const fn state(&self) -> ReconnectState {
match self.state {
ReconnectMachineState::Parked => ReconnectState::Parked,
ReconnectMachineState::Permit { .. } => ReconnectState::PermitOutstanding,
ReconnectMachineState::Attempt { .. } => ReconnectState::AttemptInProgress,
ReconnectMachineState::Online => ReconnectState::Online,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ReconnectAttemptPermit {
authorization: u64,
event: ReconnectFreshEvent,
}
impl ReconnectAttemptPermit {
#[must_use]
pub const fn event(&self) -> ReconnectFreshEvent {
self.event
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReconnectPermitRefusalReason {
AuthorizationOutstanding,
AuthorizationExhausted,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ReconnectPermitRefusal {
aggregate: ClientParticipantAggregate,
event: ReconnectFreshEvent,
reason: ReconnectPermitRefusalReason,
result: ReconnectDelayResult,
}
impl ReconnectPermitRefusal {
#[must_use]
pub const fn reason(&self) -> ReconnectPermitRefusalReason {
self.reason
}
#[must_use]
pub const fn result(&self) -> ReconnectDelayResult {
self.result
}
#[must_use]
pub fn into_parts(self) -> (ClientParticipantAggregate, ReconnectFreshEvent) {
(self.aggregate, self.event)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ReconnectPermitDecision {
Permitted {
aggregate: ClientParticipantAggregate,
permit: ReconnectAttemptPermit,
result: ReconnectDelayResult,
},
Refused(ReconnectPermitRefusal),
}
#[must_use]
pub const fn record_transport_fate(
aggregate: ClientParticipantAggregate,
fate: EstablishedConnectionTransportFate,
) -> ReconnectPermitDecision {
record_fresh_event(aggregate, ReconnectFreshEvent::TransportFate(fate))
}
#[must_use]
pub const fn record_online_transition(
aggregate: ClientParticipantAggregate,
transition: ProvedOnlineTransition,
) -> ReconnectPermitDecision {
record_fresh_event(aggregate, ReconnectFreshEvent::OnlineTransition(transition))
}
#[must_use]
pub const fn record_explicit_reconnect(
aggregate: ClientParticipantAggregate,
action: ExplicitReconnectAction,
) -> ReconnectPermitDecision {
record_fresh_event(aggregate, ReconnectFreshEvent::ExplicitCallerAction(action))
}
const fn record_fresh_event(
mut aggregate: ClientParticipantAggregate,
event: ReconnectFreshEvent,
) -> ReconnectPermitDecision {
if !matches!(
aggregate.reconnect.state,
ReconnectMachineState::Parked | ReconnectMachineState::Online
) {
let state = aggregate.reconnect.state();
return ReconnectPermitDecision::Refused(ReconnectPermitRefusal {
aggregate,
event,
reason: ReconnectPermitRefusalReason::AuthorizationOutstanding,
result: ReconnectDelayResult::ReconnectNotArmed {
state,
required_event: event.required_event(),
},
});
}
let Some(authorization) = aggregate.reconnect.next_authorization.checked_add(1) else {
let state = aggregate.reconnect.state();
return ReconnectPermitDecision::Refused(ReconnectPermitRefusal {
aggregate,
event,
reason: ReconnectPermitRefusalReason::AuthorizationExhausted,
result: ReconnectDelayResult::ReconnectNotArmed {
state,
required_event: event.required_event(),
},
});
};
aggregate.reconnect.next_authorization = authorization;
aggregate.reconnect.state = ReconnectMachineState::Permit {
authorization,
event,
issued: true,
};
ReconnectPermitDecision::Permitted {
aggregate,
permit: ReconnectAttemptPermit {
authorization,
event,
},
result: ReconnectDelayResult::ReconnectArmed {
event: event.required_event(),
},
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum RecoveredReconnectPermitDecision {
Recovered {
aggregate: ClientParticipantAggregate,
permit: ReconnectAttemptPermit,
},
NotAvailable {
aggregate: ClientParticipantAggregate,
state: ReconnectState,
},
}
#[must_use]
pub const fn recover_reconnect_permit(
mut aggregate: ClientParticipantAggregate,
) -> RecoveredReconnectPermitDecision {
match aggregate.reconnect.state {
ReconnectMachineState::Permit {
authorization,
event,
issued: false,
} => {
aggregate.reconnect.state = ReconnectMachineState::Permit {
authorization,
event,
issued: true,
};
RecoveredReconnectPermitDecision::Recovered {
aggregate,
permit: ReconnectAttemptPermit {
authorization,
event,
},
}
}
ReconnectMachineState::Parked
| ReconnectMachineState::Permit { .. }
| ReconnectMachineState::Attempt { .. }
| ReconnectMachineState::Online => {
let state = aggregate.reconnect.state();
RecoveredReconnectPermitDecision::NotAvailable { aggregate, state }
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum LostReconnectAuthorityDecision {
Recorded {
aggregate: ClientParticipantAggregate,
testimony: LostAuthorityTestimony,
},
Refused {
aggregate: ClientParticipantAggregate,
reason: LostAuthorityResolutionRefusalReason,
},
}
#[must_use]
pub const fn resolve_lost_reconnect_authority(
mut aggregate: ClientParticipantAggregate,
) -> LostReconnectAuthorityDecision {
match aggregate.reconnect.lost.take() {
Some(testimony) => {
aggregate.reconnect.state = ReconnectMachineState::Parked;
LostReconnectAuthorityDecision::Recorded {
aggregate,
testimony,
}
}
None => LostReconnectAuthorityDecision::Refused {
aggregate,
reason: LostAuthorityResolutionRefusalReason::NoPendingTestimony,
},
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ReconnectInProgressAttempt {
authorization: u64,
event: ReconnectFreshEvent,
}
impl ReconnectInProgressAttempt {
#[must_use]
pub const fn event(&self) -> ReconnectFreshEvent {
self.event
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReconnectAttemptRefusalReason {
NoPermit,
StalePermit,
LostAuthorityPending,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ReconnectAttemptDecision {
Started {
aggregate: ClientParticipantAggregate,
attempt: ReconnectInProgressAttempt,
},
Refused {
aggregate: ClientParticipantAggregate,
permit: ReconnectAttemptPermit,
reason: ReconnectAttemptRefusalReason,
},
}
#[must_use]
pub fn redeem_attempt(
mut aggregate: ClientParticipantAggregate,
permit: ReconnectAttemptPermit,
) -> ReconnectAttemptDecision {
if aggregate.reconnect.lost.is_some() {
return ReconnectAttemptDecision::Refused {
aggregate,
permit,
reason: ReconnectAttemptRefusalReason::LostAuthorityPending,
};
}
match aggregate.reconnect.state {
ReconnectMachineState::Permit {
authorization,
event,
..
} if authorization == permit.authorization && event == permit.event => {
aggregate.reconnect.state = ReconnectMachineState::Attempt {
authorization,
event,
};
ReconnectAttemptDecision::Started {
aggregate,
attempt: ReconnectInProgressAttempt {
authorization,
event,
},
}
}
ReconnectMachineState::Permit { .. } => ReconnectAttemptDecision::Refused {
aggregate,
permit,
reason: ReconnectAttemptRefusalReason::StalePermit,
},
ReconnectMachineState::Parked
| ReconnectMachineState::Attempt { .. }
| ReconnectMachineState::Online => ReconnectAttemptDecision::Refused {
aggregate,
permit,
reason: ReconnectAttemptRefusalReason::NoPermit,
},
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReconnectAttemptFate {
Connected,
Failed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReconnectAttemptFateRefusalReason {
NoAttempt,
StaleAttempt,
LostAuthorityPending,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ReconnectAttemptFateDecision {
Recorded(ClientParticipantAggregate),
Refused {
aggregate: ClientParticipantAggregate,
attempt: ReconnectInProgressAttempt,
fate: ReconnectAttemptFate,
reason: ReconnectAttemptFateRefusalReason,
},
}
#[must_use]
pub fn record_attempt_fate(
mut aggregate: ClientParticipantAggregate,
attempt: ReconnectInProgressAttempt,
fate: ReconnectAttemptFate,
) -> ReconnectAttemptFateDecision {
if aggregate.reconnect.lost.is_some() {
return ReconnectAttemptFateDecision::Refused {
aggregate,
attempt,
fate,
reason: ReconnectAttemptFateRefusalReason::LostAuthorityPending,
};
}
match aggregate.reconnect.state {
ReconnectMachineState::Attempt {
authorization,
event,
} if authorization == attempt.authorization && event == attempt.event => {
aggregate.reconnect.state = match fate {
ReconnectAttemptFate::Connected => ReconnectMachineState::Online,
ReconnectAttemptFate::Failed => ReconnectMachineState::Parked,
};
ReconnectAttemptFateDecision::Recorded(aggregate)
}
ReconnectMachineState::Attempt { .. } => ReconnectAttemptFateDecision::Refused {
aggregate,
attempt,
fate,
reason: ReconnectAttemptFateRefusalReason::StaleAttempt,
},
ReconnectMachineState::Parked
| ReconnectMachineState::Permit { .. }
| ReconnectMachineState::Online => ReconnectAttemptFateDecision::Refused {
aggregate,
attempt,
fate,
reason: ReconnectAttemptFateRefusalReason::NoAttempt,
},
}
}