1use crate::wire::{
2 BindingEpoch, BindingStateView, ConversationId, DeliverySeq, DetachAttemptToken,
3 DetachCommitted, DetachEnvelope, DetachInProgress, DetachRequest, DetachedCause, Generation,
4 ObserverBackpressure, ObserverBackpressureState, ObserverEpoch, ParticipantId,
5 TerminalizedDetachCell,
6};
7
8use super::ObserverProgressProjection;
9use super::binding::{
10 ActiveBinding, AdmissionOrder, BindingState, CommittedBindingTerminal,
11 CommittedBindingTerminalPosition, CommittedDetachedTerminal, PendingBindingTerminalPosition,
12 PendingFinalization,
13};
14use super::membership::{LiveMember, MembershipInvariantError};
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
18pub struct EmptyDetach;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct PendingDetach<V> {
23 token: DetachAttemptToken,
24 participant_id: ParticipantId,
25 request_generation: Generation,
26 request_verifier: V,
27 committed_binding_epoch: BindingEpoch,
28 admission_order: AdmissionOrder,
29 refused_epoch: ObserverEpoch,
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct CommittedDetach<V> {
35 token: DetachAttemptToken,
36 participant_id: ParticipantId,
37 request_generation: Generation,
38 request_verifier: V,
39 committed_binding_epoch: BindingEpoch,
40 detached_delivery_seq: DeliverySeq,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct TerminalizedDetach<V> {
46 token: DetachAttemptToken,
47 participant_id: ParticipantId,
48 request_generation: Generation,
49 request_verifier: V,
50 committed_binding_epoch: BindingEpoch,
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum DetachCell<V> {
56 Empty(EmptyDetach),
58 Pending(PendingDetach<V>),
60 Committed(CommittedDetach<V>),
62 Terminalized(TerminalizedDetach<V>),
64}
65
66impl<V> Default for DetachCell<V> {
67 fn default() -> Self {
68 Self::Empty(EmptyDetach)
69 }
70}
71
72pub(super) fn restore_pending_detach<V>(
73 token: DetachAttemptToken,
74 participant_id: ParticipantId,
75 request_generation: Generation,
76 request_verifier: V,
77 committed_binding_epoch: BindingEpoch,
78 admission_order: AdmissionOrder,
79 refused_epoch: ObserverEpoch,
80) -> Option<PendingDetach<V>> {
81 if committed_binding_epoch.capability_generation != request_generation
82 || admission_order.participant_index() != participant_id
83 || !matches!(
84 admission_order.candidate_phase(),
85 crate::outcome::CandidatePhase::BindingTerminal
86 )
87 {
88 return None;
89 }
90 Some(PendingDetach {
91 token,
92 participant_id,
93 request_generation,
94 request_verifier,
95 committed_binding_epoch,
96 admission_order,
97 refused_epoch,
98 })
99}
100
101pub(super) fn restore_committed_detach<V>(
102 token: DetachAttemptToken,
103 participant_id: ParticipantId,
104 request_generation: Generation,
105 request_verifier: V,
106 committed_binding_epoch: BindingEpoch,
107 detached_delivery_seq: DeliverySeq,
108) -> Option<CommittedDetach<V>> {
109 if committed_binding_epoch.capability_generation != request_generation {
110 return None;
111 }
112 Some(CommittedDetach {
113 token,
114 participant_id,
115 request_generation,
116 request_verifier,
117 committed_binding_epoch,
118 detached_delivery_seq,
119 })
120}
121
122pub(super) fn restore_terminalized_detach<V>(
123 token: DetachAttemptToken,
124 participant_id: ParticipantId,
125 request_generation: Generation,
126 request_verifier: V,
127 committed_binding_epoch: BindingEpoch,
128) -> Option<TerminalizedDetach<V>> {
129 if committed_binding_epoch.capability_generation != request_generation {
130 return None;
131 }
132 Some(TerminalizedDetach {
133 token,
134 participant_id,
135 request_generation,
136 request_verifier,
137 committed_binding_epoch,
138 })
139}
140
141impl<V> DetachCell<V> {
142 #[must_use]
144 pub fn into_terminalized(self) -> Option<TerminalizedDetach<V>> {
145 match self {
146 Self::Terminalized(cell) => Some(cell),
147 Self::Empty(_) | Self::Pending(_) | Self::Committed(_) => None,
148 }
149 }
150}
151
152impl<V: Clone> DetachCell<V> {
153 pub(super) fn storage_description(&self) -> super::storage::DetachCellRestore<V> {
157 use super::storage::DetachCellRestore;
158 match self {
159 Self::Empty(_) => DetachCellRestore::Empty,
160 Self::Pending(cell) => DetachCellRestore::Pending {
161 token: cell.token,
162 participant_id: cell.participant_id,
163 request_generation: cell.request_generation,
164 request_verifier: cell.request_verifier.clone(),
165 committed_binding_epoch: cell.committed_binding_epoch,
166 admission_order: cell.admission_order,
167 refused_epoch: cell.refused_epoch,
168 },
169 Self::Committed(cell) => DetachCellRestore::Committed {
170 token: cell.token,
171 participant_id: cell.participant_id,
172 request_generation: cell.request_generation,
173 request_verifier: cell.request_verifier.clone(),
174 committed_binding_epoch: cell.committed_binding_epoch,
175 detached_delivery_seq: cell.detached_delivery_seq,
176 },
177 Self::Terminalized(cell) => DetachCellRestore::Terminalized {
178 token: cell.token,
179 participant_id: cell.participant_id,
180 request_generation: cell.request_generation,
181 request_verifier: cell.request_verifier.clone(),
182 committed_binding_epoch: cell.committed_binding_epoch,
183 },
184 }
185 }
186}
187
188impl<V> PendingDetach<V> {
189 pub(crate) const fn participant_id(&self) -> ParticipantId {
190 self.participant_id
191 }
192
193 pub(crate) const fn request_generation(&self) -> Generation {
194 self.request_generation
195 }
196
197 pub(crate) const fn committed_binding_epoch(&self) -> BindingEpoch {
198 self.committed_binding_epoch
199 }
200
201 pub(crate) const fn admission_order(&self) -> AdmissionOrder {
202 self.admission_order
203 }
204
205 pub(crate) const fn refused_epoch(&self) -> ObserverEpoch {
206 self.refused_epoch
207 }
208}
209
210impl<V> CommittedDetach<V> {
211 pub(crate) const fn token(&self) -> DetachAttemptToken {
212 self.token
213 }
214
215 pub(crate) const fn participant_id(&self) -> ParticipantId {
216 self.participant_id
217 }
218
219 pub(crate) const fn request_generation(&self) -> Generation {
220 self.request_generation
221 }
222
223 pub(crate) const fn committed_binding_epoch(&self) -> BindingEpoch {
224 self.committed_binding_epoch
225 }
226
227 pub(crate) const fn detached_delivery_seq(&self) -> DeliverySeq {
228 self.detached_delivery_seq
229 }
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq)]
234pub enum DetachVerificationError {
235 Conversation,
237 Participant,
239 Generation,
241}
242
243#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245pub enum DetachReplayError {
246 Token,
248 Participant,
250 Generation,
252 RequestVerifier,
254 StatePair,
256 MembershipAuthority,
258 TerminalHistory(MembershipInvariantError),
260}
261
262#[derive(Clone, Copy, Debug, PartialEq, Eq)]
264pub enum DetachCommitError {
265 PendingCell,
267 CommittedCell,
269 TerminalizedCellAuthority,
271 MembershipAuthority,
273 TerminalHistory(MembershipInvariantError),
275}
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq)]
279pub enum PendingDrainDecision {
280 NotAttempted,
282 StillBlocked,
284 Committed {
286 detached_delivery_seq: DeliverySeq,
288 },
289}
290
291#[derive(Clone, Copy, Debug, PartialEq, Eq)]
293pub enum PendingReplayError {
294 ObserverProgressRegression,
296 DrainRequired,
298 UnexpectedDrain,
300 StatePair,
302 MembershipAuthority,
304 TerminalHistory(MembershipInvariantError),
306}
307
308#[derive(Clone, Debug, PartialEq, Eq)]
310pub struct CommittedDetachTransition<EF, V> {
311 member: LiveMember<EF>,
312 terminal: CommittedDetachedTerminal,
313 binding_state: BindingState,
314 cell: CommittedDetach<V>,
315 outcome: DetachCommitted,
316}
317
318impl<EF, V> CommittedDetachTransition<EF, V> {
319 #[must_use]
321 pub const fn member(&self) -> &LiveMember<EF> {
322 &self.member
323 }
324
325 #[must_use]
327 pub const fn terminal(&self) -> CommittedDetachedTerminal {
328 self.terminal
329 }
330
331 #[must_use]
333 pub const fn binding_state(&self) -> BindingState {
334 self.binding_state
335 }
336
337 #[must_use]
339 pub const fn cell(&self) -> &CommittedDetach<V> {
340 &self.cell
341 }
342
343 #[must_use]
345 pub const fn outcome(&self) -> &DetachCommitted {
346 &self.outcome
347 }
348
349 #[must_use]
351 pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
352 ObserverProgressProjection::new(
353 self.terminal.conversation_id(),
354 self.terminal.delivery_seq(),
355 )
356 }
357
358 #[must_use]
360 #[allow(clippy::type_complexity)]
361 pub fn into_parts(
362 self,
363 ) -> (
364 LiveMember<EF>,
365 CommittedDetachedTerminal,
366 BindingState,
367 CommittedDetach<V>,
368 DetachCommitted,
369 ) {
370 (
371 self.member,
372 self.terminal,
373 self.binding_state,
374 self.cell,
375 self.outcome,
376 )
377 }
378}
379
380#[derive(Clone, Debug, PartialEq, Eq)]
382pub struct PendingDetachTransition<EF, V> {
383 member: LiveMember<EF>,
384 binding_state: BindingState,
385 cell: PendingDetach<V>,
386 outcome: ObserverBackpressure,
387}
388
389impl<EF, V> PendingDetachTransition<EF, V> {
390 #[must_use]
392 pub const fn member(&self) -> &LiveMember<EF> {
393 &self.member
394 }
395
396 #[must_use]
398 pub const fn binding_state(&self) -> BindingState {
399 self.binding_state
400 }
401
402 #[must_use]
404 pub const fn cell(&self) -> &PendingDetach<V> {
405 &self.cell
406 }
407
408 #[must_use]
410 pub const fn outcome(&self) -> &ObserverBackpressure {
411 &self.outcome
412 }
413
414 #[must_use]
416 pub fn into_parts(
417 self,
418 ) -> (
419 LiveMember<EF>,
420 BindingState,
421 PendingDetach<V>,
422 ObserverBackpressure,
423 ) {
424 (self.member, self.binding_state, self.cell, self.outcome)
425 }
426}
427
428#[derive(Clone, Debug, PartialEq, Eq)]
430pub enum PendingReplay<EF, V> {
431 Pending {
433 member: LiveMember<EF>,
435 binding_state: BindingState,
437 cell: PendingDetach<V>,
439 outcome: ObserverBackpressure,
441 },
442 Committed {
444 member: LiveMember<EF>,
446 terminal: CommittedDetachedTerminal,
448 binding_state: BindingState,
450 cell: CommittedDetach<V>,
452 outcome: DetachCommitted,
454 },
455}
456
457#[derive(Clone, Debug)]
459pub struct VerifiedDetachRequest<V> {
460 binding: ActiveBinding,
461 request: DetachRequest,
462 request_verifier: V,
463}
464
465impl ActiveBinding {
466 pub fn verify_detach_request<V>(
474 &self,
475 request: DetachRequest,
476 request_verifier: V,
477 ) -> Result<VerifiedDetachRequest<V>, DetachVerificationError> {
478 if request.conversation_id != self.conversation_id {
479 return Err(DetachVerificationError::Conversation);
480 }
481 if request.participant_id != self.participant_id {
482 return Err(DetachVerificationError::Participant);
483 }
484 if request.capability_generation != self.binding_epoch.capability_generation {
485 return Err(DetachVerificationError::Generation);
486 }
487 Ok(VerifiedDetachRequest {
488 binding: *self,
489 request,
490 request_verifier,
491 })
492 }
493}
494
495pub fn commit_detach<EF, V: Copy + Eq>(
502 member: LiveMember<EF>,
503 verified: VerifiedDetachRequest<V>,
504 previous_cell: DetachCell<V>,
505 position: CommittedBindingTerminalPosition,
506) -> Result<CommittedDetachTransition<EF, V>, DetachCommitError> {
507 validate_previous_cell(&member, &previous_cell)?;
508 let VerifiedDetachRequest {
509 binding,
510 request,
511 request_verifier,
512 } = verified;
513 validate_member_authority(&member, binding)
514 .map_err(|_| DetachCommitError::MembershipAuthority)?;
515 let terminal = binding.commit_clean_deregister(position);
516 let member = member
517 .with_committed_terminal(CommittedBindingTerminal::Detached(terminal))
518 .map_err(DetachCommitError::TerminalHistory)?;
519 let cell = CommittedDetach {
520 token: request.detach_attempt_token,
521 participant_id: request.participant_id,
522 request_generation: request.capability_generation,
523 request_verifier,
524 committed_binding_epoch: binding.binding_epoch,
525 detached_delivery_seq: position.delivery_seq(),
526 };
527 let outcome = cell.outcome(request.conversation_id);
528 Ok(CommittedDetachTransition {
529 member,
530 terminal,
531 binding_state: BindingState::Detached,
532 cell,
533 outcome,
534 })
535}
536
537pub fn start_blocked_detach<EF, V: Copy + Eq>(
548 member: LiveMember<EF>,
549 verified: VerifiedDetachRequest<V>,
550 previous_cell: DetachCell<V>,
551 position: PendingBindingTerminalPosition,
552 observer_progress: DeliverySeq,
553) -> Result<PendingDetachTransition<EF, V>, DetachCommitError> {
554 validate_previous_cell(&member, &previous_cell)?;
555 let VerifiedDetachRequest {
556 binding,
557 request,
558 request_verifier,
559 } = verified;
560 validate_member_authority(&member, binding)
561 .map_err(|_| DetachCommitError::MembershipAuthority)?;
562 let pending = binding.pending_clean_deregister(position);
563 let admission_order = pending.admission_order();
564 let finalization = PendingFinalization::Detached(pending);
565 let cell = PendingDetach {
566 token: request.detach_attempt_token,
567 participant_id: request.participant_id,
568 request_generation: request.capability_generation,
569 request_verifier,
570 committed_binding_epoch: binding.binding_epoch,
571 admission_order,
572 refused_epoch: observer_progress,
573 };
574 let outcome = cell.backpressure(binding.conversation_id, observer_progress);
575 Ok(PendingDetachTransition {
576 member,
577 binding_state: BindingState::PendingFinalization(finalization),
578 cell,
579 outcome,
580 })
581}
582
583pub fn complete_pending_detach<EF, V: Copy + Eq>(
590 member: LiveMember<EF>,
591 binding_state: BindingState,
592 cell: PendingDetach<V>,
593 detached_delivery_seq: DeliverySeq,
594) -> Result<CommittedDetachTransition<EF, V>, DetachReplayError> {
595 let finalization = validate_pending_pair(binding_state, &cell, None)?;
596 validate_member_finalization(&member, finalization)?;
597 let PendingFinalization::Detached(pending) = finalization else {
598 return Err(DetachReplayError::StatePair);
599 };
600 let terminal = pending.commit(detached_delivery_seq);
601 let member = member
602 .with_committed_terminal(CommittedBindingTerminal::Detached(terminal))
603 .map_err(DetachReplayError::TerminalHistory)?;
604 let committed = cell.commit(detached_delivery_seq);
605 let outcome = committed.outcome(finalization.conversation_id());
606 Ok(CommittedDetachTransition {
607 member,
608 terminal,
609 binding_state: BindingState::Detached,
610 cell: committed,
611 outcome,
612 })
613}
614
615impl<V: Copy + Eq> PendingDetach<V> {
616 pub fn verify_exact(
622 &self,
623 request: &DetachRequest,
624 request_verifier: V,
625 ) -> Result<VerifiedPendingDetach<'_, V>, DetachReplayError> {
626 verify_stored_request(
627 self.token,
628 self.participant_id,
629 self.request_generation,
630 &self.request_verifier,
631 request,
632 &request_verifier,
633 )?;
634 Ok(VerifiedPendingDetach { state: self })
635 }
636
637 #[must_use]
639 pub const fn competing_attempt(
640 &self,
641 conversation_id: ConversationId,
642 presented_token: DetachAttemptToken,
643 presented_generation: Generation,
644 ) -> DetachInProgress {
645 DetachInProgress {
646 conversation_id,
647 participant_id: self.participant_id,
648 presented_token,
649 presented_generation,
650 committed_binding_epoch: self.committed_binding_epoch,
651 }
652 }
653
654 const fn backpressure(
655 &self,
656 conversation_id: ConversationId,
657 observer_progress: DeliverySeq,
658 ) -> ObserverBackpressure {
659 ObserverBackpressure::Detach {
660 request: DetachEnvelope {
661 conversation_id,
662 participant_id: self.participant_id(),
663 capability_generation: self.request_generation(),
664 detach_attempt_token: self.token,
665 },
666 committed_binding_epoch: self.committed_binding_epoch(),
667 state: ObserverBackpressureState::initial(observer_progress),
668 }
669 }
670
671 const fn commit(self, detached_delivery_seq: DeliverySeq) -> CommittedDetach<V> {
672 CommittedDetach {
673 token: self.token,
674 participant_id: self.participant_id,
675 request_generation: self.request_generation,
676 request_verifier: self.request_verifier,
677 committed_binding_epoch: self.committed_binding_epoch,
678 detached_delivery_seq,
679 }
680 }
681
682 pub(crate) const fn terminalize_after_attach(self) -> TerminalizedDetach<V> {
683 TerminalizedDetach {
684 token: self.token,
685 participant_id: self.participant_id,
686 request_generation: self.request_generation,
687 request_verifier: self.request_verifier,
688 committed_binding_epoch: self.committed_binding_epoch,
689 }
690 }
691}
692
693#[derive(Clone, Copy, Debug)]
695pub struct VerifiedPendingDetach<'a, V> {
696 state: &'a PendingDetach<V>,
697}
698
699impl<V: Copy + Eq> VerifiedPendingDetach<'_, V> {
700 #[must_use]
702 pub const fn prepare_replay(
703 self,
704 conversation_id: ConversationId,
705 binding_state: BindingState,
706 observer_progress: DeliverySeq,
707 ) -> PendingReplayRequest<V> {
708 PendingReplayRequest {
709 conversation_id,
710 binding_state,
711 cell: *self.state,
712 observer_progress,
713 }
714 }
715}
716
717#[derive(Clone, Debug, PartialEq, Eq)]
719pub struct PendingReplayRequest<V> {
720 conversation_id: ConversationId,
721 binding_state: BindingState,
722 cell: PendingDetach<V>,
723 observer_progress: DeliverySeq,
724}
725
726impl<V: Copy + Eq> PendingReplayRequest<V> {
727 pub fn apply<EF>(
735 self,
736 member: LiveMember<EF>,
737 decision: PendingDrainDecision,
738 ) -> Result<PendingReplay<EF, V>, PendingReplayError> {
739 let finalization =
740 validate_pending_pair(self.binding_state, &self.cell, Some(self.conversation_id))
741 .map_err(|_| PendingReplayError::StatePair)?;
742 validate_member_finalization(&member, finalization)
743 .map_err(|_| PendingReplayError::MembershipAuthority)?;
744 if self.observer_progress < self.cell.refused_epoch() {
745 return Err(PendingReplayError::ObserverProgressRegression);
746 }
747 if self.observer_progress == self.cell.refused_epoch() {
748 if decision != PendingDrainDecision::NotAttempted {
749 return Err(PendingReplayError::UnexpectedDrain);
750 }
751 let outcome = self
752 .cell
753 .backpressure(self.conversation_id, self.observer_progress);
754 return Ok(PendingReplay::Pending {
755 member,
756 binding_state: self.binding_state,
757 cell: self.cell,
758 outcome,
759 });
760 }
761
762 match decision {
763 PendingDrainDecision::NotAttempted => Err(PendingReplayError::DrainRequired),
764 PendingDrainDecision::StillBlocked => {
765 let mut cell = self.cell;
766 cell.refused_epoch = self.observer_progress;
767 let outcome = cell.backpressure(self.conversation_id, self.observer_progress);
768 Ok(PendingReplay::Pending {
769 member,
770 binding_state: self.binding_state,
771 cell,
772 outcome,
773 })
774 }
775 PendingDrainDecision::Committed {
776 detached_delivery_seq,
777 } => complete_pending_detach(
778 member,
779 self.binding_state,
780 self.cell,
781 detached_delivery_seq,
782 )
783 .map(|transition| PendingReplay::Committed {
784 member: transition.member,
785 terminal: transition.terminal,
786 binding_state: transition.binding_state,
787 cell: transition.cell,
788 outcome: transition.outcome,
789 })
790 .map_err(map_pending_replay_error),
791 }
792 }
793}
794
795const fn map_pending_replay_error(error: DetachReplayError) -> PendingReplayError {
796 match error {
797 DetachReplayError::MembershipAuthority => PendingReplayError::MembershipAuthority,
798 DetachReplayError::TerminalHistory(error) => PendingReplayError::TerminalHistory(error),
799 DetachReplayError::Token
800 | DetachReplayError::Participant
801 | DetachReplayError::Generation
802 | DetachReplayError::RequestVerifier
803 | DetachReplayError::StatePair => PendingReplayError::StatePair,
804 }
805}
806
807pub(super) fn validate_pending_pair<V>(
808 binding_state: BindingState,
809 cell: &PendingDetach<V>,
810 expected_conversation_id: Option<ConversationId>,
811) -> Result<PendingFinalization, DetachReplayError> {
812 let BindingState::PendingFinalization(finalization) = binding_state else {
813 return Err(DetachReplayError::StatePair);
814 };
815 let conversation_mismatch = expected_conversation_id
816 .is_some_and(|conversation_id| finalization.conversation_id() != conversation_id);
817 let participant_mismatch = finalization.participant_id() != cell.participant_id();
818 let epoch_mismatch = finalization.binding_epoch() != cell.committed_binding_epoch();
819 let order_mismatch = finalization.admission_order() != cell.admission_order();
820 let cause_mismatch = finalization.detached_cause() != Some(DetachedCause::CleanDeregister);
821 if conversation_mismatch
822 || participant_mismatch
823 || epoch_mismatch
824 || order_mismatch
825 || cause_mismatch
826 {
827 return Err(DetachReplayError::StatePair);
828 }
829 Ok(finalization)
830}
831
832fn validate_member_authority<EF>(
833 member: &LiveMember<EF>,
834 binding: ActiveBinding,
835) -> Result<(), DetachReplayError> {
836 if member.participant_id() != binding.participant_id
837 || member.conversation_id() != binding.conversation_id
838 || member.generation() != binding.binding_epoch.capability_generation
839 {
840 return Err(DetachReplayError::MembershipAuthority);
841 }
842 Ok(())
843}
844
845fn validate_previous_cell<EF, V>(
846 member: &LiveMember<EF>,
847 cell: &DetachCell<V>,
848) -> Result<(), DetachCommitError> {
849 match cell {
850 DetachCell::Empty(_) => Ok(()),
851 DetachCell::Pending(_) => Err(DetachCommitError::PendingCell),
852 DetachCell::Committed(_) => Err(DetachCommitError::CommittedCell),
853 DetachCell::Terminalized(cell) => {
854 let participant_mismatch = cell.participant_id() != member.participant_id();
855 let generation_is_not_older = cell.request_generation() >= member.generation();
856 if participant_mismatch || generation_is_not_older {
857 Err(DetachCommitError::TerminalizedCellAuthority)
858 } else {
859 Ok(())
860 }
861 }
862 }
863}
864
865fn validate_member_finalization<EF>(
866 member: &LiveMember<EF>,
867 finalization: PendingFinalization,
868) -> Result<(), DetachReplayError> {
869 if member.participant_id() != finalization.participant_id()
870 || member.conversation_id() != finalization.conversation_id()
871 || member.generation() != finalization.binding_epoch().capability_generation
872 {
873 return Err(DetachReplayError::MembershipAuthority);
874 }
875 Ok(())
876}
877
878impl<V: Copy + Eq> CommittedDetach<V> {
879 pub fn verify_exact(
885 &self,
886 request: &DetachRequest,
887 request_verifier: V,
888 ) -> Result<VerifiedCommittedDetach<'_, V>, DetachReplayError> {
889 verify_stored_request(
890 self.token,
891 self.participant_id,
892 self.request_generation,
893 &self.request_verifier,
894 request,
895 &request_verifier,
896 )?;
897 Ok(VerifiedCommittedDetach { state: self })
898 }
899
900 pub(crate) const fn terminalize_after_attach(self) -> TerminalizedDetach<V> {
901 TerminalizedDetach {
902 token: self.token,
903 participant_id: self.participant_id,
904 request_generation: self.request_generation,
905 request_verifier: self.request_verifier,
906 committed_binding_epoch: self.committed_binding_epoch,
907 }
908 }
909
910 const fn outcome(self, conversation_id: ConversationId) -> DetachCommitted {
911 DetachCommitted::new(
912 conversation_id,
913 self.participant_id,
914 self.token,
915 self.committed_binding_epoch,
916 self.detached_delivery_seq,
917 )
918 }
919}
920
921#[derive(Clone, Copy, Debug)]
923pub struct VerifiedCommittedDetach<'a, V> {
924 state: &'a CommittedDetach<V>,
925}
926
927impl<V: Copy + Eq> VerifiedCommittedDetach<'_, V> {
928 #[must_use]
930 pub const fn outcome(self, conversation_id: ConversationId) -> DetachCommitted {
931 self.state.outcome(conversation_id)
932 }
933}
934
935impl<V> TerminalizedDetach<V> {
936 pub(crate) const fn token(&self) -> DetachAttemptToken {
937 self.token
938 }
939
940 pub(crate) const fn participant_id(&self) -> ParticipantId {
941 self.participant_id
942 }
943
944 pub(crate) const fn request_generation(&self) -> Generation {
945 self.request_generation
946 }
947
948 pub(crate) const fn committed_binding_epoch(&self) -> BindingEpoch {
949 self.committed_binding_epoch
950 }
951
952 pub fn verify_exact(
959 &self,
960 request: &DetachRequest,
961 request_verifier: V,
962 ) -> Result<VerifiedTerminalizedDetach<'_, V>, DetachReplayError>
963 where
964 V: Copy + Eq,
965 {
966 verify_stored_request(
967 self.token,
968 self.participant_id,
969 self.request_generation,
970 &self.request_verifier,
971 request,
972 &request_verifier,
973 )?;
974 Ok(VerifiedTerminalizedDetach { state: self })
975 }
976}
977
978#[derive(Clone, Copy, Debug)]
981pub struct VerifiedTerminalizedDetach<'a, V> {
982 state: &'a TerminalizedDetach<V>,
983}
984
985impl<V> VerifiedTerminalizedDetach<'_, V> {
986 #[must_use]
988 pub const fn outcome(
989 self,
990 conversation_id: ConversationId,
991 current_generation: Generation,
992 binding_state: BindingStateView,
993 ) -> TerminalizedDetachCell {
994 TerminalizedDetachCell::from_terminalized_state(
995 self.state,
996 conversation_id,
997 current_generation,
998 binding_state,
999 )
1000 }
1001}
1002
1003fn verify_stored_request<V: Eq>(
1004 token: DetachAttemptToken,
1005 participant_id: ParticipantId,
1006 request_generation: Generation,
1007 stored_verifier: &V,
1008 request: &DetachRequest,
1009 request_verifier: &V,
1010) -> Result<(), DetachReplayError> {
1011 if request.detach_attempt_token != token {
1012 return Err(DetachReplayError::Token);
1013 }
1014 if request.participant_id != participant_id {
1015 return Err(DetachReplayError::Participant);
1016 }
1017 if request.capability_generation != request_generation {
1018 return Err(DetachReplayError::Generation);
1019 }
1020 if request_verifier != stored_verifier {
1021 return Err(DetachReplayError::RequestVerifier);
1022 }
1023 Ok(())
1024}