1use super::{
2 ClientParticipantAggregate, LostAuthorityTestimony,
3 barrier::LostAuthorityResolutionRefusalReason,
4};
5use crate::outcome::{ReconnectDelayResult, ReconnectRequiredEvent, ReconnectState};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum EstablishedConnectionTransportFate {
10 Lost,
12}
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum ProvedOnlineTransition {
17 ProvedOnline,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum ExplicitReconnectAction {
24 ReconnectNow,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum ReconnectFreshEvent {
31 TransportFate(EstablishedConnectionTransportFate),
33 OnlineTransition(ProvedOnlineTransition),
35 ExplicitCallerAction(ExplicitReconnectAction),
37}
38
39impl ReconnectFreshEvent {
40 const fn required_event(self) -> ReconnectRequiredEvent {
41 match self {
42 Self::TransportFate(_) => ReconnectRequiredEvent::TransportFate,
43 Self::OnlineTransition(_) => ReconnectRequiredEvent::OnlineTransition,
44 Self::ExplicitCallerAction(_) => ReconnectRequiredEvent::ExplicitCallerAction,
45 }
46 }
47}
48
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub(super) enum ReconnectMachineState {
51 Parked,
52 Permit {
53 authorization: u64,
54 event: ReconnectFreshEvent,
55 issued: bool,
56 },
57 Attempt {
58 authorization: u64,
59 event: ReconnectFreshEvent,
60 },
61 Online,
62}
63
64#[derive(Debug, PartialEq, Eq)]
70pub struct ReconnectAggregate {
71 pub(super) state: ReconnectMachineState,
72 pub(super) next_authorization: u64,
73 pub(super) lost: Option<LostAuthorityTestimony>,
74}
75
76impl ReconnectAggregate {
77 pub(super) const fn new() -> Self {
78 Self {
79 state: ReconnectMachineState::Parked,
80 next_authorization: 0,
81 lost: None,
82 }
83 }
84
85 #[must_use]
87 pub const fn state(&self) -> ReconnectState {
88 match self.state {
89 ReconnectMachineState::Parked => ReconnectState::Parked,
90 ReconnectMachineState::Permit { .. } => ReconnectState::PermitOutstanding,
91 ReconnectMachineState::Attempt { .. } => ReconnectState::AttemptInProgress,
92 ReconnectMachineState::Online => ReconnectState::Online,
93 }
94 }
95}
96
97#[derive(Debug, PartialEq, Eq)]
119pub struct ReconnectAttemptPermit {
120 authorization: u64,
121 event: ReconnectFreshEvent,
122}
123
124impl ReconnectAttemptPermit {
125 #[must_use]
127 pub const fn event(&self) -> ReconnectFreshEvent {
128 self.event
129 }
130}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq)]
134pub enum ReconnectPermitRefusalReason {
135 AuthorizationOutstanding,
137 AuthorizationExhausted,
139}
140
141#[derive(Debug, PartialEq, Eq)]
143pub struct ReconnectPermitRefusal {
144 aggregate: ClientParticipantAggregate,
145 event: ReconnectFreshEvent,
146 reason: ReconnectPermitRefusalReason,
147 result: ReconnectDelayResult,
148}
149
150impl ReconnectPermitRefusal {
151 #[must_use]
153 pub const fn reason(&self) -> ReconnectPermitRefusalReason {
154 self.reason
155 }
156
157 #[must_use]
159 pub const fn result(&self) -> ReconnectDelayResult {
160 self.result
161 }
162
163 #[must_use]
165 pub fn into_parts(self) -> (ClientParticipantAggregate, ReconnectFreshEvent) {
166 (self.aggregate, self.event)
167 }
168}
169
170#[derive(Debug, PartialEq, Eq)]
172pub enum ReconnectPermitDecision {
173 Permitted {
175 aggregate: ClientParticipantAggregate,
177 permit: ReconnectAttemptPermit,
179 result: ReconnectDelayResult,
181 },
182 Refused(ReconnectPermitRefusal),
184}
185
186#[must_use]
188pub const fn record_transport_fate(
189 aggregate: ClientParticipantAggregate,
190 fate: EstablishedConnectionTransportFate,
191) -> ReconnectPermitDecision {
192 record_fresh_event(aggregate, ReconnectFreshEvent::TransportFate(fate))
193}
194
195#[must_use]
197pub const fn record_online_transition(
198 aggregate: ClientParticipantAggregate,
199 transition: ProvedOnlineTransition,
200) -> ReconnectPermitDecision {
201 record_fresh_event(aggregate, ReconnectFreshEvent::OnlineTransition(transition))
202}
203
204#[must_use]
206pub const fn record_explicit_reconnect(
207 aggregate: ClientParticipantAggregate,
208 action: ExplicitReconnectAction,
209) -> ReconnectPermitDecision {
210 record_fresh_event(aggregate, ReconnectFreshEvent::ExplicitCallerAction(action))
211}
212
213const fn record_fresh_event(
214 mut aggregate: ClientParticipantAggregate,
215 event: ReconnectFreshEvent,
216) -> ReconnectPermitDecision {
217 if !matches!(
218 aggregate.reconnect.state,
219 ReconnectMachineState::Parked | ReconnectMachineState::Online
220 ) {
221 let state = aggregate.reconnect.state();
222 return ReconnectPermitDecision::Refused(ReconnectPermitRefusal {
223 aggregate,
224 event,
225 reason: ReconnectPermitRefusalReason::AuthorizationOutstanding,
226 result: ReconnectDelayResult::ReconnectNotArmed {
227 state,
228 required_event: event.required_event(),
229 },
230 });
231 }
232 let Some(authorization) = aggregate.reconnect.next_authorization.checked_add(1) else {
233 let state = aggregate.reconnect.state();
234 return ReconnectPermitDecision::Refused(ReconnectPermitRefusal {
235 aggregate,
236 event,
237 reason: ReconnectPermitRefusalReason::AuthorizationExhausted,
238 result: ReconnectDelayResult::ReconnectNotArmed {
239 state,
240 required_event: event.required_event(),
241 },
242 });
243 };
244 aggregate.reconnect.next_authorization = authorization;
245 aggregate.reconnect.state = ReconnectMachineState::Permit {
246 authorization,
247 event,
248 issued: true,
249 };
250 ReconnectPermitDecision::Permitted {
251 aggregate,
252 permit: ReconnectAttemptPermit {
253 authorization,
254 event,
255 },
256 result: ReconnectDelayResult::ReconnectArmed {
257 event: event.required_event(),
258 },
259 }
260}
261
262#[derive(Debug, PartialEq, Eq)]
264pub enum RecoveredReconnectPermitDecision {
265 Recovered {
267 aggregate: ClientParticipantAggregate,
269 permit: ReconnectAttemptPermit,
271 },
272 NotAvailable {
274 aggregate: ClientParticipantAggregate,
276 state: ReconnectState,
278 },
279}
280
281#[must_use]
288pub const fn recover_reconnect_permit(
289 mut aggregate: ClientParticipantAggregate,
290) -> RecoveredReconnectPermitDecision {
291 match aggregate.reconnect.state {
292 ReconnectMachineState::Permit {
293 authorization,
294 event,
295 issued: false,
296 } => {
297 aggregate.reconnect.state = ReconnectMachineState::Permit {
298 authorization,
299 event,
300 issued: true,
301 };
302 RecoveredReconnectPermitDecision::Recovered {
303 aggregate,
304 permit: ReconnectAttemptPermit {
305 authorization,
306 event,
307 },
308 }
309 }
310 ReconnectMachineState::Parked
311 | ReconnectMachineState::Permit { .. }
312 | ReconnectMachineState::Attempt { .. }
313 | ReconnectMachineState::Online => {
314 let state = aggregate.reconnect.state();
315 RecoveredReconnectPermitDecision::NotAvailable { aggregate, state }
316 }
317 }
318}
319
320#[derive(Debug, PartialEq, Eq)]
328pub enum LostReconnectAuthorityDecision {
329 Recorded {
332 aggregate: ClientParticipantAggregate,
334 testimony: LostAuthorityTestimony,
336 },
337 Refused {
339 aggregate: ClientParticipantAggregate,
341 reason: LostAuthorityResolutionRefusalReason,
343 },
344}
345
346#[must_use]
350pub const fn resolve_lost_reconnect_authority(
351 mut aggregate: ClientParticipantAggregate,
352) -> LostReconnectAuthorityDecision {
353 match aggregate.reconnect.lost.take() {
354 Some(testimony) => {
355 aggregate.reconnect.state = ReconnectMachineState::Parked;
356 LostReconnectAuthorityDecision::Recorded {
357 aggregate,
358 testimony,
359 }
360 }
361 None => LostReconnectAuthorityDecision::Refused {
362 aggregate,
363 reason: LostAuthorityResolutionRefusalReason::NoPendingTestimony,
364 },
365 }
366}
367
368#[derive(Debug, PartialEq, Eq)]
370pub struct ReconnectInProgressAttempt {
371 authorization: u64,
372 event: ReconnectFreshEvent,
373}
374
375impl ReconnectInProgressAttempt {
376 #[must_use]
378 pub const fn event(&self) -> ReconnectFreshEvent {
379 self.event
380 }
381}
382
383#[derive(Clone, Copy, Debug, PartialEq, Eq)]
385pub enum ReconnectAttemptRefusalReason {
386 NoPermit,
388 StalePermit,
390 LostAuthorityPending,
393}
394
395#[derive(Debug, PartialEq, Eq)]
397pub enum ReconnectAttemptDecision {
398 Started {
400 aggregate: ClientParticipantAggregate,
402 attempt: ReconnectInProgressAttempt,
404 },
405 Refused {
407 aggregate: ClientParticipantAggregate,
409 permit: ReconnectAttemptPermit,
411 reason: ReconnectAttemptRefusalReason,
413 },
414}
415
416#[must_use]
418pub fn redeem_attempt(
419 mut aggregate: ClientParticipantAggregate,
420 permit: ReconnectAttemptPermit,
421) -> ReconnectAttemptDecision {
422 if aggregate.reconnect.lost.is_some() {
423 return ReconnectAttemptDecision::Refused {
424 aggregate,
425 permit,
426 reason: ReconnectAttemptRefusalReason::LostAuthorityPending,
427 };
428 }
429 match aggregate.reconnect.state {
430 ReconnectMachineState::Permit {
431 authorization,
432 event,
433 ..
434 } if authorization == permit.authorization && event == permit.event => {
435 aggregate.reconnect.state = ReconnectMachineState::Attempt {
436 authorization,
437 event,
438 };
439 ReconnectAttemptDecision::Started {
440 aggregate,
441 attempt: ReconnectInProgressAttempt {
442 authorization,
443 event,
444 },
445 }
446 }
447 ReconnectMachineState::Permit { .. } => ReconnectAttemptDecision::Refused {
448 aggregate,
449 permit,
450 reason: ReconnectAttemptRefusalReason::StalePermit,
451 },
452 ReconnectMachineState::Parked
453 | ReconnectMachineState::Attempt { .. }
454 | ReconnectMachineState::Online => ReconnectAttemptDecision::Refused {
455 aggregate,
456 permit,
457 reason: ReconnectAttemptRefusalReason::NoPermit,
458 },
459 }
460}
461
462#[derive(Clone, Copy, Debug, PartialEq, Eq)]
464pub enum ReconnectAttemptFate {
465 Connected,
467 Failed,
469}
470
471#[derive(Clone, Copy, Debug, PartialEq, Eq)]
473pub enum ReconnectAttemptFateRefusalReason {
474 NoAttempt,
476 StaleAttempt,
478 LostAuthorityPending,
481}
482
483#[derive(Debug, PartialEq, Eq)]
485pub enum ReconnectAttemptFateDecision {
486 Recorded(ClientParticipantAggregate),
488 Refused {
490 aggregate: ClientParticipantAggregate,
492 attempt: ReconnectInProgressAttempt,
494 fate: ReconnectAttemptFate,
496 reason: ReconnectAttemptFateRefusalReason,
498 },
499}
500
501#[must_use]
503pub fn record_attempt_fate(
504 mut aggregate: ClientParticipantAggregate,
505 attempt: ReconnectInProgressAttempt,
506 fate: ReconnectAttemptFate,
507) -> ReconnectAttemptFateDecision {
508 if aggregate.reconnect.lost.is_some() {
509 return ReconnectAttemptFateDecision::Refused {
510 aggregate,
511 attempt,
512 fate,
513 reason: ReconnectAttemptFateRefusalReason::LostAuthorityPending,
514 };
515 }
516 match aggregate.reconnect.state {
517 ReconnectMachineState::Attempt {
518 authorization,
519 event,
520 } if authorization == attempt.authorization && event == attempt.event => {
521 aggregate.reconnect.state = match fate {
522 ReconnectAttemptFate::Connected => ReconnectMachineState::Online,
523 ReconnectAttemptFate::Failed => ReconnectMachineState::Parked,
524 };
525 ReconnectAttemptFateDecision::Recorded(aggregate)
526 }
527 ReconnectMachineState::Attempt { .. } => ReconnectAttemptFateDecision::Refused {
528 aggregate,
529 attempt,
530 fate,
531 reason: ReconnectAttemptFateRefusalReason::StaleAttempt,
532 },
533 ReconnectMachineState::Parked
534 | ReconnectMachineState::Permit { .. }
535 | ReconnectMachineState::Online => ReconnectAttemptFateDecision::Refused {
536 aggregate,
537 attempt,
538 fate,
539 reason: ReconnectAttemptFateRefusalReason::NoAttempt,
540 },
541 }
542}