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::sourced(
353 self.terminal.conversation_id(),
354 self.terminal.delivery_seq(),
355 super::checkpoint_codec::ObserverProjectionSource::Terminal(
356 super::CommittedBindingTerminal::Detached(self.terminal).storage_description(),
357 ),
358 )
359 }
360
361 #[must_use]
363 #[allow(clippy::type_complexity)]
364 pub fn into_parts(
365 self,
366 ) -> (
367 LiveMember<EF>,
368 CommittedDetachedTerminal,
369 BindingState,
370 CommittedDetach<V>,
371 DetachCommitted,
372 ) {
373 (
374 self.member,
375 self.terminal,
376 self.binding_state,
377 self.cell,
378 self.outcome,
379 )
380 }
381}
382
383#[derive(Clone, Debug, PartialEq, Eq)]
385pub struct PendingDetachTransition<EF, V> {
386 member: LiveMember<EF>,
387 binding_state: BindingState,
388 cell: PendingDetach<V>,
389 outcome: ObserverBackpressure,
390}
391
392impl<EF, V> PendingDetachTransition<EF, V> {
393 #[must_use]
395 pub const fn member(&self) -> &LiveMember<EF> {
396 &self.member
397 }
398
399 #[must_use]
401 pub const fn binding_state(&self) -> BindingState {
402 self.binding_state
403 }
404
405 #[must_use]
407 pub const fn cell(&self) -> &PendingDetach<V> {
408 &self.cell
409 }
410
411 #[must_use]
413 pub const fn outcome(&self) -> &ObserverBackpressure {
414 &self.outcome
415 }
416
417 #[must_use]
419 pub fn into_parts(
420 self,
421 ) -> (
422 LiveMember<EF>,
423 BindingState,
424 PendingDetach<V>,
425 ObserverBackpressure,
426 ) {
427 (self.member, self.binding_state, self.cell, self.outcome)
428 }
429}
430
431#[derive(Clone, Debug, PartialEq, Eq)]
433pub enum PendingReplay<EF, V> {
434 Pending {
436 member: LiveMember<EF>,
438 binding_state: BindingState,
440 cell: PendingDetach<V>,
442 outcome: ObserverBackpressure,
444 },
445 Committed {
447 member: LiveMember<EF>,
449 terminal: CommittedDetachedTerminal,
451 binding_state: BindingState,
453 cell: CommittedDetach<V>,
455 outcome: DetachCommitted,
457 },
458}
459
460#[derive(Clone, Debug)]
462pub struct VerifiedDetachRequest<V> {
463 binding: ActiveBinding,
464 request: DetachRequest,
465 request_verifier: V,
466}
467
468impl ActiveBinding {
469 pub fn verify_detach_request<V>(
477 &self,
478 request: DetachRequest,
479 request_verifier: V,
480 ) -> Result<VerifiedDetachRequest<V>, DetachVerificationError> {
481 if request.conversation_id != self.conversation_id {
482 return Err(DetachVerificationError::Conversation);
483 }
484 if request.participant_id != self.participant_id {
485 return Err(DetachVerificationError::Participant);
486 }
487 if request.capability_generation != self.binding_epoch.capability_generation {
488 return Err(DetachVerificationError::Generation);
489 }
490 Ok(VerifiedDetachRequest {
491 binding: *self,
492 request,
493 request_verifier,
494 })
495 }
496}
497
498pub fn commit_detach<EF, V: Copy + Eq>(
505 member: LiveMember<EF>,
506 verified: VerifiedDetachRequest<V>,
507 previous_cell: DetachCell<V>,
508 position: CommittedBindingTerminalPosition,
509) -> Result<CommittedDetachTransition<EF, V>, DetachCommitError> {
510 validate_previous_cell(&member, &previous_cell)?;
511 let VerifiedDetachRequest {
512 binding,
513 request,
514 request_verifier,
515 } = verified;
516 validate_member_authority(&member, binding)
517 .map_err(|_| DetachCommitError::MembershipAuthority)?;
518 let terminal = binding.commit_clean_deregister(position);
519 let member = member
520 .with_committed_terminal(CommittedBindingTerminal::Detached(terminal))
521 .map_err(DetachCommitError::TerminalHistory)?;
522 let cell = CommittedDetach {
523 token: request.detach_attempt_token,
524 participant_id: request.participant_id,
525 request_generation: request.capability_generation,
526 request_verifier,
527 committed_binding_epoch: binding.binding_epoch,
528 detached_delivery_seq: position.delivery_seq(),
529 };
530 let outcome = cell.outcome(request.conversation_id);
531 Ok(CommittedDetachTransition {
532 member,
533 terminal,
534 binding_state: BindingState::Detached,
535 cell,
536 outcome,
537 })
538}
539
540pub fn start_blocked_detach<EF, V: Copy + Eq>(
551 member: LiveMember<EF>,
552 verified: VerifiedDetachRequest<V>,
553 previous_cell: DetachCell<V>,
554 position: PendingBindingTerminalPosition,
555 observer_progress: DeliverySeq,
556) -> Result<PendingDetachTransition<EF, V>, DetachCommitError> {
557 validate_previous_cell(&member, &previous_cell)?;
558 let VerifiedDetachRequest {
559 binding,
560 request,
561 request_verifier,
562 } = verified;
563 validate_member_authority(&member, binding)
564 .map_err(|_| DetachCommitError::MembershipAuthority)?;
565 let pending = binding.pending_clean_deregister(position);
566 let admission_order = pending.admission_order();
567 let finalization = PendingFinalization::Detached(pending);
568 let cell = PendingDetach {
569 token: request.detach_attempt_token,
570 participant_id: request.participant_id,
571 request_generation: request.capability_generation,
572 request_verifier,
573 committed_binding_epoch: binding.binding_epoch,
574 admission_order,
575 refused_epoch: observer_progress,
576 };
577 let outcome = cell.backpressure(binding.conversation_id, observer_progress);
578 Ok(PendingDetachTransition {
579 member,
580 binding_state: BindingState::PendingFinalization(finalization),
581 cell,
582 outcome,
583 })
584}
585
586pub fn complete_pending_detach<EF, V: Copy + Eq>(
593 member: LiveMember<EF>,
594 binding_state: BindingState,
595 cell: PendingDetach<V>,
596 detached_delivery_seq: DeliverySeq,
597) -> Result<CommittedDetachTransition<EF, V>, DetachReplayError> {
598 let finalization = validate_pending_pair(binding_state, &cell, None)?;
599 validate_member_finalization(&member, finalization)?;
600 let PendingFinalization::Detached(pending) = finalization else {
601 return Err(DetachReplayError::StatePair);
602 };
603 let terminal = pending.commit(detached_delivery_seq);
604 let member = member
605 .with_committed_terminal(CommittedBindingTerminal::Detached(terminal))
606 .map_err(DetachReplayError::TerminalHistory)?;
607 let committed = cell.commit(detached_delivery_seq);
608 let outcome = committed.outcome(finalization.conversation_id());
609 Ok(CommittedDetachTransition {
610 member,
611 terminal,
612 binding_state: BindingState::Detached,
613 cell: committed,
614 outcome,
615 })
616}
617
618impl<V: Copy + Eq> PendingDetach<V> {
619 pub fn verify_exact(
625 &self,
626 request: &DetachRequest,
627 request_verifier: V,
628 ) -> Result<VerifiedPendingDetach<'_, V>, DetachReplayError> {
629 verify_stored_request(
630 self.token,
631 self.participant_id,
632 self.request_generation,
633 &self.request_verifier,
634 request,
635 &request_verifier,
636 )?;
637 Ok(VerifiedPendingDetach { state: self })
638 }
639
640 #[must_use]
642 pub const fn competing_attempt(
643 &self,
644 conversation_id: ConversationId,
645 presented_token: DetachAttemptToken,
646 presented_generation: Generation,
647 ) -> DetachInProgress {
648 DetachInProgress {
649 conversation_id,
650 participant_id: self.participant_id,
651 presented_token,
652 presented_generation,
653 committed_binding_epoch: self.committed_binding_epoch,
654 }
655 }
656
657 const fn backpressure(
658 &self,
659 conversation_id: ConversationId,
660 observer_progress: DeliverySeq,
661 ) -> ObserverBackpressure {
662 ObserverBackpressure::Detach {
663 request: DetachEnvelope {
664 conversation_id,
665 participant_id: self.participant_id(),
666 capability_generation: self.request_generation(),
667 detach_attempt_token: self.token,
668 },
669 committed_binding_epoch: self.committed_binding_epoch(),
670 state: ObserverBackpressureState::initial(observer_progress),
671 }
672 }
673
674 const fn commit(self, detached_delivery_seq: DeliverySeq) -> CommittedDetach<V> {
675 CommittedDetach {
676 token: self.token,
677 participant_id: self.participant_id,
678 request_generation: self.request_generation,
679 request_verifier: self.request_verifier,
680 committed_binding_epoch: self.committed_binding_epoch,
681 detached_delivery_seq,
682 }
683 }
684
685 pub(crate) const fn terminalize_after_attach(self) -> TerminalizedDetach<V> {
686 TerminalizedDetach {
687 token: self.token,
688 participant_id: self.participant_id,
689 request_generation: self.request_generation,
690 request_verifier: self.request_verifier,
691 committed_binding_epoch: self.committed_binding_epoch,
692 }
693 }
694}
695
696#[derive(Clone, Copy, Debug)]
698pub struct VerifiedPendingDetach<'a, V> {
699 state: &'a PendingDetach<V>,
700}
701
702impl<V: Copy + Eq> VerifiedPendingDetach<'_, V> {
703 #[must_use]
705 pub const fn prepare_replay(
706 self,
707 conversation_id: ConversationId,
708 binding_state: BindingState,
709 observer_progress: DeliverySeq,
710 ) -> PendingReplayRequest<V> {
711 PendingReplayRequest {
712 conversation_id,
713 binding_state,
714 cell: *self.state,
715 observer_progress,
716 }
717 }
718}
719
720#[derive(Clone, Debug, PartialEq, Eq)]
722pub struct PendingReplayRequest<V> {
723 conversation_id: ConversationId,
724 binding_state: BindingState,
725 cell: PendingDetach<V>,
726 observer_progress: DeliverySeq,
727}
728
729impl<V: Copy + Eq> PendingReplayRequest<V> {
730 pub fn apply<EF>(
738 self,
739 member: LiveMember<EF>,
740 decision: PendingDrainDecision,
741 ) -> Result<PendingReplay<EF, V>, PendingReplayError> {
742 let finalization =
743 validate_pending_pair(self.binding_state, &self.cell, Some(self.conversation_id))
744 .map_err(|_| PendingReplayError::StatePair)?;
745 validate_member_finalization(&member, finalization)
746 .map_err(|_| PendingReplayError::MembershipAuthority)?;
747 if self.observer_progress < self.cell.refused_epoch() {
748 return Err(PendingReplayError::ObserverProgressRegression);
749 }
750 if self.observer_progress == self.cell.refused_epoch() {
751 if decision != PendingDrainDecision::NotAttempted {
752 return Err(PendingReplayError::UnexpectedDrain);
753 }
754 let outcome = self
755 .cell
756 .backpressure(self.conversation_id, self.observer_progress);
757 return Ok(PendingReplay::Pending {
758 member,
759 binding_state: self.binding_state,
760 cell: self.cell,
761 outcome,
762 });
763 }
764
765 match decision {
766 PendingDrainDecision::NotAttempted => Err(PendingReplayError::DrainRequired),
767 PendingDrainDecision::StillBlocked => {
768 let mut cell = self.cell;
769 cell.refused_epoch = self.observer_progress;
770 let outcome = cell.backpressure(self.conversation_id, self.observer_progress);
771 Ok(PendingReplay::Pending {
772 member,
773 binding_state: self.binding_state,
774 cell,
775 outcome,
776 })
777 }
778 PendingDrainDecision::Committed {
779 detached_delivery_seq,
780 } => complete_pending_detach(
781 member,
782 self.binding_state,
783 self.cell,
784 detached_delivery_seq,
785 )
786 .map(|transition| PendingReplay::Committed {
787 member: transition.member,
788 terminal: transition.terminal,
789 binding_state: transition.binding_state,
790 cell: transition.cell,
791 outcome: transition.outcome,
792 })
793 .map_err(map_pending_replay_error),
794 }
795 }
796}
797
798const fn map_pending_replay_error(error: DetachReplayError) -> PendingReplayError {
799 match error {
800 DetachReplayError::MembershipAuthority => PendingReplayError::MembershipAuthority,
801 DetachReplayError::TerminalHistory(error) => PendingReplayError::TerminalHistory(error),
802 DetachReplayError::Token
803 | DetachReplayError::Participant
804 | DetachReplayError::Generation
805 | DetachReplayError::RequestVerifier
806 | DetachReplayError::StatePair => PendingReplayError::StatePair,
807 }
808}
809
810pub(super) fn validate_pending_pair<V>(
811 binding_state: BindingState,
812 cell: &PendingDetach<V>,
813 expected_conversation_id: Option<ConversationId>,
814) -> Result<PendingFinalization, DetachReplayError> {
815 let BindingState::PendingFinalization(finalization) = binding_state else {
816 return Err(DetachReplayError::StatePair);
817 };
818 let conversation_mismatch = expected_conversation_id
819 .is_some_and(|conversation_id| finalization.conversation_id() != conversation_id);
820 let participant_mismatch = finalization.participant_id() != cell.participant_id();
821 let epoch_mismatch = finalization.binding_epoch() != cell.committed_binding_epoch();
822 let order_mismatch = finalization.admission_order() != cell.admission_order();
823 let cause_mismatch = finalization.detached_cause() != Some(DetachedCause::CleanDeregister);
824 if conversation_mismatch
825 || participant_mismatch
826 || epoch_mismatch
827 || order_mismatch
828 || cause_mismatch
829 {
830 return Err(DetachReplayError::StatePair);
831 }
832 Ok(finalization)
833}
834
835fn validate_member_authority<EF>(
836 member: &LiveMember<EF>,
837 binding: ActiveBinding,
838) -> Result<(), DetachReplayError> {
839 if member.participant_id() != binding.participant_id
840 || member.conversation_id() != binding.conversation_id
841 || member.generation() != binding.binding_epoch.capability_generation
842 {
843 return Err(DetachReplayError::MembershipAuthority);
844 }
845 Ok(())
846}
847
848fn validate_previous_cell<EF, V>(
849 member: &LiveMember<EF>,
850 cell: &DetachCell<V>,
851) -> Result<(), DetachCommitError> {
852 match cell {
853 DetachCell::Empty(_) => Ok(()),
854 DetachCell::Pending(_) => Err(DetachCommitError::PendingCell),
855 DetachCell::Committed(_) => Err(DetachCommitError::CommittedCell),
856 DetachCell::Terminalized(cell) => {
857 let participant_mismatch = cell.participant_id() != member.participant_id();
858 let generation_is_not_older = cell.request_generation() >= member.generation();
859 if participant_mismatch || generation_is_not_older {
860 Err(DetachCommitError::TerminalizedCellAuthority)
861 } else {
862 Ok(())
863 }
864 }
865 }
866}
867
868fn validate_member_finalization<EF>(
869 member: &LiveMember<EF>,
870 finalization: PendingFinalization,
871) -> Result<(), DetachReplayError> {
872 if member.participant_id() != finalization.participant_id()
873 || member.conversation_id() != finalization.conversation_id()
874 || member.generation() != finalization.binding_epoch().capability_generation
875 {
876 return Err(DetachReplayError::MembershipAuthority);
877 }
878 Ok(())
879}
880
881impl<V: Copy + Eq> CommittedDetach<V> {
882 pub fn verify_exact(
888 &self,
889 request: &DetachRequest,
890 request_verifier: V,
891 ) -> Result<VerifiedCommittedDetach<'_, V>, DetachReplayError> {
892 verify_stored_request(
893 self.token,
894 self.participant_id,
895 self.request_generation,
896 &self.request_verifier,
897 request,
898 &request_verifier,
899 )?;
900 Ok(VerifiedCommittedDetach { state: self })
901 }
902
903 pub(crate) const fn terminalize_after_attach(self) -> TerminalizedDetach<V> {
904 TerminalizedDetach {
905 token: self.token,
906 participant_id: self.participant_id,
907 request_generation: self.request_generation,
908 request_verifier: self.request_verifier,
909 committed_binding_epoch: self.committed_binding_epoch,
910 }
911 }
912
913 const fn outcome(self, conversation_id: ConversationId) -> DetachCommitted {
914 DetachCommitted::new(
915 conversation_id,
916 self.participant_id,
917 self.token,
918 self.committed_binding_epoch,
919 self.detached_delivery_seq,
920 )
921 }
922}
923
924#[derive(Clone, Copy, Debug)]
926pub struct VerifiedCommittedDetach<'a, V> {
927 state: &'a CommittedDetach<V>,
928}
929
930impl<V: Copy + Eq> VerifiedCommittedDetach<'_, V> {
931 #[must_use]
933 pub const fn outcome(self, conversation_id: ConversationId) -> DetachCommitted {
934 self.state.outcome(conversation_id)
935 }
936}
937
938impl<V> TerminalizedDetach<V> {
939 pub(crate) const fn token(&self) -> DetachAttemptToken {
940 self.token
941 }
942
943 pub(crate) const fn participant_id(&self) -> ParticipantId {
944 self.participant_id
945 }
946
947 pub(crate) const fn request_generation(&self) -> Generation {
948 self.request_generation
949 }
950
951 pub(crate) const fn committed_binding_epoch(&self) -> BindingEpoch {
952 self.committed_binding_epoch
953 }
954
955 pub fn verify_exact(
962 &self,
963 request: &DetachRequest,
964 request_verifier: V,
965 ) -> Result<VerifiedTerminalizedDetach<'_, V>, DetachReplayError>
966 where
967 V: Copy + Eq,
968 {
969 verify_stored_request(
970 self.token,
971 self.participant_id,
972 self.request_generation,
973 &self.request_verifier,
974 request,
975 &request_verifier,
976 )?;
977 Ok(VerifiedTerminalizedDetach { state: self })
978 }
979}
980
981#[derive(Clone, Copy, Debug)]
984pub struct VerifiedTerminalizedDetach<'a, V> {
985 state: &'a TerminalizedDetach<V>,
986}
987
988impl<V> VerifiedTerminalizedDetach<'_, V> {
989 #[must_use]
991 pub const fn outcome(
992 self,
993 conversation_id: ConversationId,
994 current_generation: Generation,
995 binding_state: BindingStateView,
996 ) -> TerminalizedDetachCell {
997 TerminalizedDetachCell::from_terminalized_state(
998 self.state,
999 conversation_id,
1000 current_generation,
1001 binding_state,
1002 )
1003 }
1004}
1005
1006fn verify_stored_request<V: Eq>(
1007 token: DetachAttemptToken,
1008 participant_id: ParticipantId,
1009 request_generation: Generation,
1010 stored_verifier: &V,
1011 request: &DetachRequest,
1012 request_verifier: &V,
1013) -> Result<(), DetachReplayError> {
1014 if request.detach_attempt_token != token {
1015 return Err(DetachReplayError::Token);
1016 }
1017 if request.participant_id != participant_id {
1018 return Err(DetachReplayError::Participant);
1019 }
1020 if request.capability_generation != request_generation {
1021 return Err(DetachReplayError::Generation);
1022 }
1023 if request_verifier != stored_verifier {
1024 return Err(DetachReplayError::RequestVerifier);
1025 }
1026 Ok(())
1027}