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> PendingDetach<V> {
153 pub(crate) const fn participant_id(&self) -> ParticipantId {
154 self.participant_id
155 }
156
157 pub(crate) const fn request_generation(&self) -> Generation {
158 self.request_generation
159 }
160
161 pub(crate) const fn committed_binding_epoch(&self) -> BindingEpoch {
162 self.committed_binding_epoch
163 }
164
165 pub(crate) const fn admission_order(&self) -> AdmissionOrder {
166 self.admission_order
167 }
168
169 pub(crate) const fn refused_epoch(&self) -> ObserverEpoch {
170 self.refused_epoch
171 }
172}
173
174impl<V> CommittedDetach<V> {
175 pub(crate) const fn token(&self) -> DetachAttemptToken {
176 self.token
177 }
178
179 pub(crate) const fn participant_id(&self) -> ParticipantId {
180 self.participant_id
181 }
182
183 pub(crate) const fn request_generation(&self) -> Generation {
184 self.request_generation
185 }
186
187 pub(crate) const fn committed_binding_epoch(&self) -> BindingEpoch {
188 self.committed_binding_epoch
189 }
190
191 pub(crate) const fn detached_delivery_seq(&self) -> DeliverySeq {
192 self.detached_delivery_seq
193 }
194}
195
196#[derive(Clone, Copy, Debug, PartialEq, Eq)]
198pub enum DetachVerificationError {
199 Conversation,
201 Participant,
203 Generation,
205}
206
207#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub enum DetachReplayError {
210 Token,
212 Participant,
214 Generation,
216 RequestVerifier,
218 StatePair,
220 MembershipAuthority,
222 TerminalHistory(MembershipInvariantError),
224}
225
226#[derive(Clone, Copy, Debug, PartialEq, Eq)]
228pub enum DetachCommitError {
229 PendingCell,
231 CommittedCell,
233 TerminalizedCellAuthority,
235 MembershipAuthority,
237 TerminalHistory(MembershipInvariantError),
239}
240
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
243pub enum PendingDrainDecision {
244 NotAttempted,
246 StillBlocked,
248 Committed {
250 detached_delivery_seq: DeliverySeq,
252 },
253}
254
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
257pub enum PendingReplayError {
258 ObserverProgressRegression,
260 DrainRequired,
262 UnexpectedDrain,
264 StatePair,
266 MembershipAuthority,
268 TerminalHistory(MembershipInvariantError),
270}
271
272#[derive(Clone, Debug, PartialEq, Eq)]
274pub struct CommittedDetachTransition<EF, V> {
275 member: LiveMember<EF>,
276 terminal: CommittedDetachedTerminal,
277 binding_state: BindingState,
278 cell: CommittedDetach<V>,
279 outcome: DetachCommitted,
280}
281
282impl<EF, V> CommittedDetachTransition<EF, V> {
283 #[must_use]
285 pub const fn member(&self) -> &LiveMember<EF> {
286 &self.member
287 }
288
289 #[must_use]
291 pub const fn terminal(&self) -> CommittedDetachedTerminal {
292 self.terminal
293 }
294
295 #[must_use]
297 pub const fn binding_state(&self) -> BindingState {
298 self.binding_state
299 }
300
301 #[must_use]
303 pub const fn cell(&self) -> &CommittedDetach<V> {
304 &self.cell
305 }
306
307 #[must_use]
309 pub const fn outcome(&self) -> &DetachCommitted {
310 &self.outcome
311 }
312
313 #[must_use]
315 pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
316 ObserverProgressProjection::new(
317 self.terminal.conversation_id(),
318 self.terminal.delivery_seq(),
319 )
320 }
321
322 #[must_use]
324 #[allow(clippy::type_complexity)]
325 pub fn into_parts(
326 self,
327 ) -> (
328 LiveMember<EF>,
329 CommittedDetachedTerminal,
330 BindingState,
331 CommittedDetach<V>,
332 DetachCommitted,
333 ) {
334 (
335 self.member,
336 self.terminal,
337 self.binding_state,
338 self.cell,
339 self.outcome,
340 )
341 }
342}
343
344#[derive(Clone, Debug, PartialEq, Eq)]
346pub struct PendingDetachTransition<EF, V> {
347 member: LiveMember<EF>,
348 binding_state: BindingState,
349 cell: PendingDetach<V>,
350 outcome: ObserverBackpressure,
351}
352
353impl<EF, V> PendingDetachTransition<EF, V> {
354 #[must_use]
356 pub const fn member(&self) -> &LiveMember<EF> {
357 &self.member
358 }
359
360 #[must_use]
362 pub const fn binding_state(&self) -> BindingState {
363 self.binding_state
364 }
365
366 #[must_use]
368 pub const fn cell(&self) -> &PendingDetach<V> {
369 &self.cell
370 }
371
372 #[must_use]
374 pub const fn outcome(&self) -> &ObserverBackpressure {
375 &self.outcome
376 }
377
378 #[must_use]
380 pub fn into_parts(
381 self,
382 ) -> (
383 LiveMember<EF>,
384 BindingState,
385 PendingDetach<V>,
386 ObserverBackpressure,
387 ) {
388 (self.member, self.binding_state, self.cell, self.outcome)
389 }
390}
391
392#[derive(Clone, Debug, PartialEq, Eq)]
394pub enum PendingReplay<EF, V> {
395 Pending {
397 member: LiveMember<EF>,
399 binding_state: BindingState,
401 cell: PendingDetach<V>,
403 outcome: ObserverBackpressure,
405 },
406 Committed {
408 member: LiveMember<EF>,
410 terminal: CommittedDetachedTerminal,
412 binding_state: BindingState,
414 cell: CommittedDetach<V>,
416 outcome: DetachCommitted,
418 },
419}
420
421#[derive(Clone, Debug)]
423pub struct VerifiedDetachRequest<V> {
424 binding: ActiveBinding,
425 request: DetachRequest,
426 request_verifier: V,
427}
428
429impl ActiveBinding {
430 pub fn verify_detach_request<V>(
438 &self,
439 request: DetachRequest,
440 request_verifier: V,
441 ) -> Result<VerifiedDetachRequest<V>, DetachVerificationError> {
442 if request.conversation_id != self.conversation_id {
443 return Err(DetachVerificationError::Conversation);
444 }
445 if request.participant_id != self.participant_id {
446 return Err(DetachVerificationError::Participant);
447 }
448 if request.capability_generation != self.binding_epoch.capability_generation {
449 return Err(DetachVerificationError::Generation);
450 }
451 Ok(VerifiedDetachRequest {
452 binding: *self,
453 request,
454 request_verifier,
455 })
456 }
457}
458
459pub fn commit_detach<EF, V: Copy + Eq>(
466 member: LiveMember<EF>,
467 verified: VerifiedDetachRequest<V>,
468 previous_cell: DetachCell<V>,
469 position: CommittedBindingTerminalPosition,
470) -> Result<CommittedDetachTransition<EF, V>, DetachCommitError> {
471 validate_previous_cell(&member, &previous_cell)?;
472 let VerifiedDetachRequest {
473 binding,
474 request,
475 request_verifier,
476 } = verified;
477 validate_member_authority(&member, binding)
478 .map_err(|_| DetachCommitError::MembershipAuthority)?;
479 let terminal = binding.commit_clean_deregister(position);
480 let member = member
481 .with_committed_terminal(CommittedBindingTerminal::Detached(terminal))
482 .map_err(DetachCommitError::TerminalHistory)?;
483 let cell = CommittedDetach {
484 token: request.detach_attempt_token,
485 participant_id: request.participant_id,
486 request_generation: request.capability_generation,
487 request_verifier,
488 committed_binding_epoch: binding.binding_epoch,
489 detached_delivery_seq: position.delivery_seq(),
490 };
491 let outcome = cell.outcome(request.conversation_id);
492 Ok(CommittedDetachTransition {
493 member,
494 terminal,
495 binding_state: BindingState::Detached,
496 cell,
497 outcome,
498 })
499}
500
501pub fn start_blocked_detach<EF, V: Copy + Eq>(
512 member: LiveMember<EF>,
513 verified: VerifiedDetachRequest<V>,
514 previous_cell: DetachCell<V>,
515 position: PendingBindingTerminalPosition,
516 observer_progress: DeliverySeq,
517) -> Result<PendingDetachTransition<EF, V>, DetachCommitError> {
518 validate_previous_cell(&member, &previous_cell)?;
519 let VerifiedDetachRequest {
520 binding,
521 request,
522 request_verifier,
523 } = verified;
524 validate_member_authority(&member, binding)
525 .map_err(|_| DetachCommitError::MembershipAuthority)?;
526 let pending = binding.pending_clean_deregister(position);
527 let admission_order = pending.admission_order();
528 let finalization = PendingFinalization::Detached(pending);
529 let cell = PendingDetach {
530 token: request.detach_attempt_token,
531 participant_id: request.participant_id,
532 request_generation: request.capability_generation,
533 request_verifier,
534 committed_binding_epoch: binding.binding_epoch,
535 admission_order,
536 refused_epoch: observer_progress,
537 };
538 let outcome = cell.backpressure(binding.conversation_id, observer_progress);
539 Ok(PendingDetachTransition {
540 member,
541 binding_state: BindingState::PendingFinalization(finalization),
542 cell,
543 outcome,
544 })
545}
546
547pub fn complete_pending_detach<EF, V: Copy + Eq>(
554 member: LiveMember<EF>,
555 binding_state: BindingState,
556 cell: PendingDetach<V>,
557 detached_delivery_seq: DeliverySeq,
558) -> Result<CommittedDetachTransition<EF, V>, DetachReplayError> {
559 let finalization = validate_pending_pair(binding_state, &cell, None)?;
560 validate_member_finalization(&member, finalization)?;
561 let PendingFinalization::Detached(pending) = finalization else {
562 return Err(DetachReplayError::StatePair);
563 };
564 let terminal = pending.commit(detached_delivery_seq);
565 let member = member
566 .with_committed_terminal(CommittedBindingTerminal::Detached(terminal))
567 .map_err(DetachReplayError::TerminalHistory)?;
568 let committed = cell.commit(detached_delivery_seq);
569 let outcome = committed.outcome(finalization.conversation_id());
570 Ok(CommittedDetachTransition {
571 member,
572 terminal,
573 binding_state: BindingState::Detached,
574 cell: committed,
575 outcome,
576 })
577}
578
579impl<V: Copy + Eq> PendingDetach<V> {
580 pub fn verify_exact(
586 &self,
587 request: &DetachRequest,
588 request_verifier: V,
589 ) -> Result<VerifiedPendingDetach<'_, V>, DetachReplayError> {
590 verify_stored_request(
591 self.token,
592 self.participant_id,
593 self.request_generation,
594 &self.request_verifier,
595 request,
596 &request_verifier,
597 )?;
598 Ok(VerifiedPendingDetach { state: self })
599 }
600
601 #[must_use]
603 pub const fn competing_attempt(
604 &self,
605 conversation_id: ConversationId,
606 presented_token: DetachAttemptToken,
607 presented_generation: Generation,
608 ) -> DetachInProgress {
609 DetachInProgress {
610 conversation_id,
611 participant_id: self.participant_id,
612 presented_token,
613 presented_generation,
614 committed_binding_epoch: self.committed_binding_epoch,
615 }
616 }
617
618 const fn backpressure(
619 &self,
620 conversation_id: ConversationId,
621 observer_progress: DeliverySeq,
622 ) -> ObserverBackpressure {
623 ObserverBackpressure::Detach {
624 request: DetachEnvelope {
625 conversation_id,
626 participant_id: self.participant_id(),
627 capability_generation: self.request_generation(),
628 detach_attempt_token: self.token,
629 },
630 committed_binding_epoch: self.committed_binding_epoch(),
631 state: ObserverBackpressureState::initial(observer_progress),
632 }
633 }
634
635 const fn commit(self, detached_delivery_seq: DeliverySeq) -> CommittedDetach<V> {
636 CommittedDetach {
637 token: self.token,
638 participant_id: self.participant_id,
639 request_generation: self.request_generation,
640 request_verifier: self.request_verifier,
641 committed_binding_epoch: self.committed_binding_epoch,
642 detached_delivery_seq,
643 }
644 }
645
646 pub(crate) const fn terminalize_after_attach(self) -> TerminalizedDetach<V> {
647 TerminalizedDetach {
648 token: self.token,
649 participant_id: self.participant_id,
650 request_generation: self.request_generation,
651 request_verifier: self.request_verifier,
652 committed_binding_epoch: self.committed_binding_epoch,
653 }
654 }
655}
656
657#[derive(Clone, Copy, Debug)]
659pub struct VerifiedPendingDetach<'a, V> {
660 state: &'a PendingDetach<V>,
661}
662
663impl<V: Copy + Eq> VerifiedPendingDetach<'_, V> {
664 #[must_use]
666 pub const fn prepare_replay(
667 self,
668 conversation_id: ConversationId,
669 binding_state: BindingState,
670 observer_progress: DeliverySeq,
671 ) -> PendingReplayRequest<V> {
672 PendingReplayRequest {
673 conversation_id,
674 binding_state,
675 cell: *self.state,
676 observer_progress,
677 }
678 }
679}
680
681#[derive(Clone, Debug, PartialEq, Eq)]
683pub struct PendingReplayRequest<V> {
684 conversation_id: ConversationId,
685 binding_state: BindingState,
686 cell: PendingDetach<V>,
687 observer_progress: DeliverySeq,
688}
689
690impl<V: Copy + Eq> PendingReplayRequest<V> {
691 pub fn apply<EF>(
699 self,
700 member: LiveMember<EF>,
701 decision: PendingDrainDecision,
702 ) -> Result<PendingReplay<EF, V>, PendingReplayError> {
703 let finalization =
704 validate_pending_pair(self.binding_state, &self.cell, Some(self.conversation_id))
705 .map_err(|_| PendingReplayError::StatePair)?;
706 validate_member_finalization(&member, finalization)
707 .map_err(|_| PendingReplayError::MembershipAuthority)?;
708 if self.observer_progress < self.cell.refused_epoch() {
709 return Err(PendingReplayError::ObserverProgressRegression);
710 }
711 if self.observer_progress == self.cell.refused_epoch() {
712 if decision != PendingDrainDecision::NotAttempted {
713 return Err(PendingReplayError::UnexpectedDrain);
714 }
715 let outcome = self
716 .cell
717 .backpressure(self.conversation_id, self.observer_progress);
718 return Ok(PendingReplay::Pending {
719 member,
720 binding_state: self.binding_state,
721 cell: self.cell,
722 outcome,
723 });
724 }
725
726 match decision {
727 PendingDrainDecision::NotAttempted => Err(PendingReplayError::DrainRequired),
728 PendingDrainDecision::StillBlocked => {
729 let mut cell = self.cell;
730 cell.refused_epoch = self.observer_progress;
731 let outcome = cell.backpressure(self.conversation_id, self.observer_progress);
732 Ok(PendingReplay::Pending {
733 member,
734 binding_state: self.binding_state,
735 cell,
736 outcome,
737 })
738 }
739 PendingDrainDecision::Committed {
740 detached_delivery_seq,
741 } => complete_pending_detach(
742 member,
743 self.binding_state,
744 self.cell,
745 detached_delivery_seq,
746 )
747 .map(|transition| PendingReplay::Committed {
748 member: transition.member,
749 terminal: transition.terminal,
750 binding_state: transition.binding_state,
751 cell: transition.cell,
752 outcome: transition.outcome,
753 })
754 .map_err(map_pending_replay_error),
755 }
756 }
757}
758
759const fn map_pending_replay_error(error: DetachReplayError) -> PendingReplayError {
760 match error {
761 DetachReplayError::MembershipAuthority => PendingReplayError::MembershipAuthority,
762 DetachReplayError::TerminalHistory(error) => PendingReplayError::TerminalHistory(error),
763 DetachReplayError::Token
764 | DetachReplayError::Participant
765 | DetachReplayError::Generation
766 | DetachReplayError::RequestVerifier
767 | DetachReplayError::StatePair => PendingReplayError::StatePair,
768 }
769}
770
771pub(super) fn validate_pending_pair<V>(
772 binding_state: BindingState,
773 cell: &PendingDetach<V>,
774 expected_conversation_id: Option<ConversationId>,
775) -> Result<PendingFinalization, DetachReplayError> {
776 let BindingState::PendingFinalization(finalization) = binding_state else {
777 return Err(DetachReplayError::StatePair);
778 };
779 let conversation_mismatch = expected_conversation_id
780 .is_some_and(|conversation_id| finalization.conversation_id() != conversation_id);
781 let participant_mismatch = finalization.participant_id() != cell.participant_id();
782 let epoch_mismatch = finalization.binding_epoch() != cell.committed_binding_epoch();
783 let order_mismatch = finalization.admission_order() != cell.admission_order();
784 let cause_mismatch = finalization.detached_cause() != Some(DetachedCause::CleanDeregister);
785 if conversation_mismatch
786 || participant_mismatch
787 || epoch_mismatch
788 || order_mismatch
789 || cause_mismatch
790 {
791 return Err(DetachReplayError::StatePair);
792 }
793 Ok(finalization)
794}
795
796fn validate_member_authority<EF>(
797 member: &LiveMember<EF>,
798 binding: ActiveBinding,
799) -> Result<(), DetachReplayError> {
800 if member.participant_id() != binding.participant_id
801 || member.conversation_id() != binding.conversation_id
802 || member.generation() != binding.binding_epoch.capability_generation
803 {
804 return Err(DetachReplayError::MembershipAuthority);
805 }
806 Ok(())
807}
808
809fn validate_previous_cell<EF, V>(
810 member: &LiveMember<EF>,
811 cell: &DetachCell<V>,
812) -> Result<(), DetachCommitError> {
813 match cell {
814 DetachCell::Empty(_) => Ok(()),
815 DetachCell::Pending(_) => Err(DetachCommitError::PendingCell),
816 DetachCell::Committed(_) => Err(DetachCommitError::CommittedCell),
817 DetachCell::Terminalized(cell) => {
818 let participant_mismatch = cell.participant_id() != member.participant_id();
819 let generation_is_not_older = cell.request_generation() >= member.generation();
820 if participant_mismatch || generation_is_not_older {
821 Err(DetachCommitError::TerminalizedCellAuthority)
822 } else {
823 Ok(())
824 }
825 }
826 }
827}
828
829fn validate_member_finalization<EF>(
830 member: &LiveMember<EF>,
831 finalization: PendingFinalization,
832) -> Result<(), DetachReplayError> {
833 if member.participant_id() != finalization.participant_id()
834 || member.conversation_id() != finalization.conversation_id()
835 || member.generation() != finalization.binding_epoch().capability_generation
836 {
837 return Err(DetachReplayError::MembershipAuthority);
838 }
839 Ok(())
840}
841
842impl<V: Copy + Eq> CommittedDetach<V> {
843 pub fn verify_exact(
849 &self,
850 request: &DetachRequest,
851 request_verifier: V,
852 ) -> Result<VerifiedCommittedDetach<'_, V>, DetachReplayError> {
853 verify_stored_request(
854 self.token,
855 self.participant_id,
856 self.request_generation,
857 &self.request_verifier,
858 request,
859 &request_verifier,
860 )?;
861 Ok(VerifiedCommittedDetach { state: self })
862 }
863
864 pub(crate) const fn terminalize_after_attach(self) -> TerminalizedDetach<V> {
865 TerminalizedDetach {
866 token: self.token,
867 participant_id: self.participant_id,
868 request_generation: self.request_generation,
869 request_verifier: self.request_verifier,
870 committed_binding_epoch: self.committed_binding_epoch,
871 }
872 }
873
874 const fn outcome(self, conversation_id: ConversationId) -> DetachCommitted {
875 DetachCommitted::new(
876 conversation_id,
877 self.participant_id,
878 self.token,
879 self.committed_binding_epoch,
880 self.detached_delivery_seq,
881 )
882 }
883}
884
885#[derive(Clone, Copy, Debug)]
887pub struct VerifiedCommittedDetach<'a, V> {
888 state: &'a CommittedDetach<V>,
889}
890
891impl<V: Copy + Eq> VerifiedCommittedDetach<'_, V> {
892 #[must_use]
894 pub const fn outcome(self, conversation_id: ConversationId) -> DetachCommitted {
895 self.state.outcome(conversation_id)
896 }
897}
898
899impl<V> TerminalizedDetach<V> {
900 pub(crate) const fn token(&self) -> DetachAttemptToken {
901 self.token
902 }
903
904 pub(crate) const fn participant_id(&self) -> ParticipantId {
905 self.participant_id
906 }
907
908 pub(crate) const fn request_generation(&self) -> Generation {
909 self.request_generation
910 }
911
912 pub(crate) const fn committed_binding_epoch(&self) -> BindingEpoch {
913 self.committed_binding_epoch
914 }
915
916 pub fn verify_exact(
923 &self,
924 request: &DetachRequest,
925 request_verifier: V,
926 ) -> Result<VerifiedTerminalizedDetach<'_, V>, DetachReplayError>
927 where
928 V: Copy + Eq,
929 {
930 verify_stored_request(
931 self.token,
932 self.participant_id,
933 self.request_generation,
934 &self.request_verifier,
935 request,
936 &request_verifier,
937 )?;
938 Ok(VerifiedTerminalizedDetach { state: self })
939 }
940}
941
942#[derive(Clone, Copy, Debug)]
945pub struct VerifiedTerminalizedDetach<'a, V> {
946 state: &'a TerminalizedDetach<V>,
947}
948
949impl<V> VerifiedTerminalizedDetach<'_, V> {
950 #[must_use]
952 pub const fn outcome(
953 self,
954 conversation_id: ConversationId,
955 current_generation: Generation,
956 binding_state: BindingStateView,
957 ) -> TerminalizedDetachCell {
958 TerminalizedDetachCell::from_terminalized_state(
959 self.state,
960 conversation_id,
961 current_generation,
962 binding_state,
963 )
964 }
965}
966
967fn verify_stored_request<V: Eq>(
968 token: DetachAttemptToken,
969 participant_id: ParticipantId,
970 request_generation: Generation,
971 stored_verifier: &V,
972 request: &DetachRequest,
973 request_verifier: &V,
974) -> Result<(), DetachReplayError> {
975 if request.detach_attempt_token != token {
976 return Err(DetachReplayError::Token);
977 }
978 if request.participant_id != participant_id {
979 return Err(DetachReplayError::Participant);
980 }
981 if request.capability_generation != request_generation {
982 return Err(DetachReplayError::Generation);
983 }
984 if request_verifier != stored_verifier {
985 return Err(DetachReplayError::RequestVerifier);
986 }
987 Ok(())
988}