1use crate::wire::{
2 AttachBound, AttachEnvelope, AttemptConflict, AttemptTokenBodyConflict, BindingEpoch,
3 BindingRequiredEnvelope, BindingStateView, CommonStaleAuthorityEnvelope,
4 CredentialAttachRequest, DeliverySeq, DetachCommitted, DetachEnvelope, DetachInProgress,
5 DetachRequest, DetachStaleAuthority, EnrollBound, EnrollmentEnvelope, EnrollmentKnown,
6 EnrollmentRequest, Generation, LeaveCommitted, LeaveEnvelope, LeaveRequest,
7 LeaveStaleAuthority, MarkerAck, MarkerAckEnvelope, NoBinding, ParticipantAck,
8 ParticipantAckEnvelope, ParticipantReferenceEnvelope, ParticipantUnknown, ReceiptExpired,
9 ReceiptExpiryReason, ReceiptReplay, RecordAdmission, RecordAdmissionEnvelope, Retired,
10 StaleAuthority, StaleOrUnknownReceipt,
11};
12
13use super::{
14 ActiveBinding, BindingState, DetachCell, IdentityState, LiveMember, PendingReplayRequest,
15 RetiredIdentity,
16};
17
18#[derive(Debug)]
21pub enum PresentedIdentity<'a, EF, V, LF> {
22 Absent,
24 Live(&'a LiveMember<EF>),
26 Retired(&'a RetiredIdentity<EF, V, LF>),
28}
29
30impl<EF, V, LF> Copy for PresentedIdentity<'_, EF, V, LF> {}
31
32impl<EF, V, LF> Clone for PresentedIdentity<'_, EF, V, LF> {
33 fn clone(&self) -> Self {
34 *self
35 }
36}
37
38impl<'a, EF, V, LF> From<Option<&'a IdentityState<EF, V, LF>>>
39 for PresentedIdentity<'a, EF, V, LF>
40{
41 fn from(value: Option<&'a IdentityState<EF, V, LF>>) -> Self {
42 match value {
43 None => Self::Absent,
44 Some(IdentityState::Live(member)) => Self::Live(member),
45 Some(IdentityState::Retired(tombstone)) => Self::Retired(tombstone),
46 }
47 }
48}
49
50#[derive(Debug)]
56pub enum ResolvedIdentity<'a, EF, V, LF> {
57 Live(&'a LiveMember<EF>),
59 Retired(&'a RetiredIdentity<EF, V, LF>),
61}
62
63impl<EF, V, LF> Copy for ResolvedIdentity<'_, EF, V, LF> {}
64
65impl<EF, V, LF> Clone for ResolvedIdentity<'_, EF, V, LF> {
66 fn clone(&self) -> Self {
67 *self
68 }
69}
70
71impl<'a, EF, V, LF> From<&'a IdentityState<EF, V, LF>> for ResolvedIdentity<'a, EF, V, LF> {
72 fn from(value: &'a IdentityState<EF, V, LF>) -> Self {
73 match value {
74 IdentityState::Live(member) => Self::Live(member),
75 IdentityState::Retired(tombstone) => Self::Retired(tombstone),
76 }
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub enum AttachSecretProof {
84 Mismatch,
86 Verified,
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
93pub struct EnrollmentLiveReceipt {
94 committed: EnrollBound,
95}
96
97impl EnrollmentLiveReceipt {
98 #[must_use]
100 pub const fn from_commit(committed: EnrollBound) -> Self {
101 Self { committed }
102 }
103
104 #[must_use]
106 pub const fn committed(&self) -> &EnrollBound {
107 &self.committed
108 }
109
110 #[must_use]
112 pub const fn into_committed(self) -> EnrollBound {
113 self.committed
114 }
115
116 #[must_use]
118 pub fn encode_committed_body(&self) -> alloc::vec::Vec<u8> {
119 crate::wire::encode_enrollment_committed_body(&self.committed)
120 }
121
122 pub fn decode_committed_body(bytes: &[u8]) -> Result<Self, crate::wire::CodecError> {
129 let (value, _) = crate::wire::decode_server_value_body(
130 crate::wire::ServerDiscriminant::EnrollBound,
131 crate::wire::ProtocolVersion::V1,
132 bytes,
133 )?;
134 let crate::wire::ServerValue::EnrollBound(committed) = value else {
135 return Err(crate::wire::CodecError::InvalidValue);
136 };
137 Ok(Self::from_commit(committed))
138 }
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub struct EnrollmentProvenance {
145 result_generation: Generation,
146 reason: ReceiptExpiryReason,
147}
148
149impl EnrollmentProvenance {
150 #[must_use]
152 pub const fn new(result_generation: Generation, reason: ReceiptExpiryReason) -> Self {
153 Self {
154 result_generation,
155 reason,
156 }
157 }
158}
159
160#[derive(Debug)]
163pub enum EnrollmentTokenPhase<'a, EF, V, LF> {
164 Unmapped,
166 LiveReceipt {
168 identity: ResolvedIdentity<'a, EF, V, LF>,
170 receipt: &'a EnrollmentLiveReceipt,
172 },
173 Provenance {
175 identity: ResolvedIdentity<'a, EF, V, LF>,
177 provenance: EnrollmentProvenance,
179 },
180 LifetimeMapping {
182 identity: ResolvedIdentity<'a, EF, V, LF>,
184 },
185}
186
187impl<EF, V, LF> Copy for EnrollmentTokenPhase<'_, EF, V, LF> {}
188
189impl<EF, V, LF> Clone for EnrollmentTokenPhase<'_, EF, V, LF> {
190 fn clone(&self) -> Self {
191 *self
192 }
193}
194
195#[derive(Clone, Debug, PartialEq, Eq)]
197pub enum EnrollmentLookupResult {
198 Retired(Retired),
200 Bound(ReceiptReplay),
202 UnboundReceipt(ReceiptReplay),
204 ReceiptExpired(ReceiptExpired),
206 EnrollmentKnown(EnrollmentKnown),
208 AuthorizedNew,
210}
211
212#[must_use]
215pub fn lookup_enrollment<EF, V, LF>(
216 phase: EnrollmentTokenPhase<'_, EF, V, LF>,
217 binding: &BindingState,
218 request: &EnrollmentRequest,
219) -> EnrollmentLookupResult {
220 match phase {
221 EnrollmentTokenPhase::Unmapped => EnrollmentLookupResult::AuthorizedNew,
222 EnrollmentTokenPhase::LiveReceipt { identity, receipt } => match identity {
223 ResolvedIdentity::Retired(tombstone) => {
224 EnrollmentLookupResult::Retired(retired_enrollment(tombstone, request))
225 }
226 ResolvedIdentity::Live(member) => {
227 let committed = &receipt.committed;
228 let replay = ReceiptReplay::Enrollment(committed.clone());
229 if receipt_binding_is_current(
230 binding,
231 request.conversation_id,
232 member.participant_id(),
233 committed.origin_binding_epoch(),
234 ) {
235 EnrollmentLookupResult::Bound(replay)
236 } else {
237 EnrollmentLookupResult::UnboundReceipt(replay)
238 }
239 }
240 },
241 EnrollmentTokenPhase::Provenance {
242 identity,
243 provenance,
244 } => match identity {
245 ResolvedIdentity::Retired(tombstone) => {
246 EnrollmentLookupResult::Retired(retired_enrollment(tombstone, request))
247 }
248 ResolvedIdentity::Live(member) => {
249 EnrollmentLookupResult::ReceiptExpired(ReceiptExpired::Enrollment {
250 conversation_id: request.conversation_id,
251 token: request.enrollment_token,
252 participant_id: member.participant_id(),
253 result_generation: provenance.result_generation,
254 current_generation: member.generation(),
255 reason: provenance.reason,
256 })
257 }
258 },
259 EnrollmentTokenPhase::LifetimeMapping { identity } => match identity {
260 ResolvedIdentity::Retired(tombstone) => {
261 EnrollmentLookupResult::Retired(retired_enrollment(tombstone, request))
262 }
263 ResolvedIdentity::Live(member) => {
264 EnrollmentLookupResult::EnrollmentKnown(EnrollmentKnown {
265 conversation_id: request.conversation_id,
266 token: request.enrollment_token,
267 participant_id: member.participant_id(),
268 current_generation: member.generation(),
269 })
270 }
271 },
272 }
273}
274
275#[derive(Clone, Debug, PartialEq, Eq)]
278pub struct CredentialAttachLiveReceipt {
279 committed: AttachBound,
280}
281
282impl CredentialAttachLiveReceipt {
283 #[must_use]
285 pub const fn from_commit(committed: AttachBound) -> Self {
286 Self { committed }
287 }
288
289 #[must_use]
291 pub const fn committed(&self) -> &AttachBound {
292 &self.committed
293 }
294
295 #[must_use]
297 pub const fn into_committed(self) -> AttachBound {
298 self.committed
299 }
300
301 #[must_use]
303 pub fn encode_committed_body(&self) -> alloc::vec::Vec<u8> {
304 crate::wire::encode_attach_committed_body(&self.committed)
305 }
306
307 pub fn decode_committed_body(bytes: &[u8]) -> Result<Self, crate::wire::CodecError> {
314 let (value, _) = crate::wire::decode_server_value_body(
315 crate::wire::ServerDiscriminant::AttachBound,
316 crate::wire::ProtocolVersion::V1,
317 bytes,
318 )?;
319 let crate::wire::ServerValue::AttachBound(committed) = value else {
320 return Err(crate::wire::CodecError::InvalidValue);
321 };
322 Ok(Self::from_commit(committed))
323 }
324}
325
326#[derive(Clone, Copy, Debug, PartialEq, Eq)]
329pub struct CredentialAttachProvenance {
330 result_generation: Generation,
331 reason: ReceiptExpiryReason,
332}
333
334impl CredentialAttachProvenance {
335 #[must_use]
337 pub const fn new(result_generation: Generation, reason: ReceiptExpiryReason) -> Self {
338 Self {
339 result_generation,
340 reason,
341 }
342 }
343}
344
345#[derive(Debug)]
347pub enum CredentialAttachTokenPhase<'a, EF, V, LF> {
348 NoMatch,
350 LiveReceipt {
352 identity: ResolvedIdentity<'a, EF, V, LF>,
354 receipt: &'a CredentialAttachLiveReceipt,
356 },
357 Provenance {
359 identity: ResolvedIdentity<'a, EF, V, LF>,
361 provenance: CredentialAttachProvenance,
363 },
364 AfterProvenance,
367}
368
369impl<EF, V, LF> Copy for CredentialAttachTokenPhase<'_, EF, V, LF> {}
370
371impl<EF, V, LF> Clone for CredentialAttachTokenPhase<'_, EF, V, LF> {
372 fn clone(&self) -> Self {
373 *self
374 }
375}
376
377#[derive(Clone, Debug, PartialEq, Eq)]
379pub enum CredentialAttachLookupResult<'a, EF> {
380 Retired(Retired),
382 ParticipantUnknown(ParticipantUnknown),
384 StaleAuthority(StaleAuthority),
386 AttemptTokenBodyConflict(AttemptTokenBodyConflict),
388 Bound(ReceiptReplay),
390 UnboundReceipt(ReceiptReplay),
392 ReceiptExpired(ReceiptExpired),
394 StaleOrUnknownReceipt(StaleOrUnknownReceipt),
396 AuthorizedFresh {
398 member: &'a LiveMember<EF>,
400 },
401}
402
403#[must_use]
411pub fn lookup_credential_attach<'a, EF, V, LF>(
412 token_phase: CredentialAttachTokenPhase<'a, EF, V, LF>,
413 presented_identity: PresentedIdentity<'a, EF, V, LF>,
414 binding: &BindingState,
415 request: &CredentialAttachRequest,
416 secret_proof: AttachSecretProof,
417) -> CredentialAttachLookupResult<'a, EF> {
418 match token_phase {
419 CredentialAttachTokenPhase::LiveReceipt { identity, receipt } => {
420 return match identity {
421 ResolvedIdentity::Retired(tombstone) => {
422 CredentialAttachLookupResult::Retired(retired_attach(tombstone, request))
423 }
424 ResolvedIdentity::Live(member) => {
425 lookup_live_attach_receipt(member, binding, request, secret_proof, receipt)
426 }
427 };
428 }
429 CredentialAttachTokenPhase::Provenance {
430 identity,
431 provenance,
432 } => {
433 return match identity {
434 ResolvedIdentity::Retired(tombstone) => {
435 CredentialAttachLookupResult::Retired(retired_attach(tombstone, request))
436 }
437 ResolvedIdentity::Live(member) => {
438 CredentialAttachLookupResult::ReceiptExpired(ReceiptExpired::CredentialAttach {
439 conversation_id: request.conversation_id,
440 token: request.attach_attempt_token,
441 participant_id: request.participant_id,
442 presented_generation: request.capability_generation,
443 presented_marker_delivery_seq: request.accept_marker_delivery_seq,
444 result_generation: provenance.result_generation,
445 current_generation: member.generation(),
446 reason: provenance.reason,
447 })
448 }
449 };
450 }
451 CredentialAttachTokenPhase::NoMatch | CredentialAttachTokenPhase::AfterProvenance => {}
452 }
453
454 let member = match presented_identity {
455 PresentedIdentity::Retired(tombstone) => {
456 return CredentialAttachLookupResult::Retired(retired_attach(tombstone, request));
457 }
458 PresentedIdentity::Absent => {
459 return CredentialAttachLookupResult::ParticipantUnknown(unknown_attach(request));
460 }
461 PresentedIdentity::Live(member) => {
462 if member.conversation_id() != request.conversation_id
463 || member.participant_id() != request.participant_id
464 {
465 return CredentialAttachLookupResult::ParticipantUnknown(unknown_attach(request));
466 }
467 member
468 }
469 };
470
471 if matches!(token_phase, CredentialAttachTokenPhase::AfterProvenance) {
472 return CredentialAttachLookupResult::StaleOrUnknownReceipt(StaleOrUnknownReceipt {
473 conversation_id: request.conversation_id,
474 token: request.attach_attempt_token,
475 participant_id: request.participant_id,
476 presented_generation: request.capability_generation,
477 presented_marker_delivery_seq: request.accept_marker_delivery_seq,
478 current_generation: member.generation(),
479 });
480 }
481
482 if request.capability_generation != member.generation()
483 || secret_proof == AttachSecretProof::Mismatch
484 {
485 return CredentialAttachLookupResult::StaleAuthority(stale_attach(
486 request,
487 member.generation(),
488 ));
489 }
490
491 CredentialAttachLookupResult::AuthorizedFresh { member }
492}
493
494fn lookup_live_attach_receipt<'a, EF>(
495 member: &'a LiveMember<EF>,
496 binding: &BindingState,
497 request: &CredentialAttachRequest,
498 secret_proof: AttachSecretProof,
499 receipt: &CredentialAttachLiveReceipt,
500) -> CredentialAttachLookupResult<'a, EF> {
501 if secret_proof == AttachSecretProof::Mismatch {
502 return CredentialAttachLookupResult::StaleAuthority(stale_attach(
503 request,
504 member.generation(),
505 ));
506 }
507
508 let committed = &receipt.committed;
509 let conflict = if request.capability_generation != committed.request_generation() {
510 Some(AttemptConflict::Generation)
511 } else if request.accept_marker_delivery_seq != committed.accepted_marker_delivery_seq() {
512 Some(AttemptConflict::MarkerDeliverySequence)
513 } else {
514 None
515 };
516 if let Some(conflict) = conflict {
517 return CredentialAttachLookupResult::AttemptTokenBodyConflict(
518 AttemptTokenBodyConflict::CredentialAttach {
519 token: request.attach_attempt_token,
520 conversation_id: request.conversation_id,
521 presented_participant_id: request.participant_id,
522 presented_generation: request.capability_generation,
523 presented_marker_delivery_seq: request.accept_marker_delivery_seq,
524 conflict,
525 },
526 );
527 }
528
529 let replay = ReceiptReplay::CredentialAttach(committed.clone());
530 if receipt_binding_is_current(
531 binding,
532 request.conversation_id,
533 request.participant_id,
534 committed.origin_binding_epoch(),
535 ) {
536 CredentialAttachLookupResult::Bound(replay)
537 } else {
538 CredentialAttachLookupResult::UnboundReceipt(replay)
539 }
540}
541
542#[derive(Clone, Debug, PartialEq, Eq)]
545pub enum ParticipantBindingRequest {
546 ParticipantAck(ParticipantAck),
548 MarkerAck(MarkerAck),
550 RecordAdmission(RecordAdmission),
552}
553
554#[derive(Clone, Debug, PartialEq, Eq)]
556pub enum BindingRequiredLookupResult<'a, EF> {
557 Retired(Retired),
559 ParticipantUnknown(ParticipantUnknown),
561 StaleAuthority(StaleAuthority),
563 NoBinding(NoBinding),
565 Authorized {
567 member: &'a LiveMember<EF>,
569 binding: ActiveBinding,
571 },
572}
573
574#[must_use]
577pub fn lookup_binding_required<'a, EF, V, LF>(
578 identity: PresentedIdentity<'a, EF, V, LF>,
579 binding: &BindingState,
580 receiving_binding_epoch: Option<BindingEpoch>,
581 request: &ParticipantBindingRequest,
582) -> BindingRequiredLookupResult<'a, EF> {
583 let member = match identity {
584 PresentedIdentity::Retired(tombstone) => {
585 return BindingRequiredLookupResult::Retired(Retired::Participant {
586 request: participant_reference(request),
587 retired_generation: tombstone.retired_generation(),
588 });
589 }
590 PresentedIdentity::Absent => {
591 return BindingRequiredLookupResult::ParticipantUnknown(ParticipantUnknown {
592 request: participant_reference(request),
593 });
594 }
595 PresentedIdentity::Live(member) => {
596 if member.conversation_id() != binding_request_conversation(request)
597 || member.participant_id() != binding_request_participant(request)
598 {
599 return BindingRequiredLookupResult::ParticipantUnknown(ParticipantUnknown {
600 request: participant_reference(request),
601 });
602 }
603 member
604 }
605 };
606
607 if binding_request_generation(request) != member.generation() {
608 return BindingRequiredLookupResult::StaleAuthority(StaleAuthority::Live {
609 request: common_stale_envelope(request),
610 current_generation: member.generation(),
611 });
612 }
613
614 match binding {
615 BindingState::Bound(active)
616 if active.conversation_id == member.conversation_id()
617 && active.participant_id == member.participant_id()
618 && active.binding_epoch.capability_generation == member.generation()
619 && receiving_binding_epoch == Some(active.binding_epoch) =>
620 {
621 BindingRequiredLookupResult::Authorized {
622 member,
623 binding: *active,
624 }
625 }
626 BindingState::Detached | BindingState::PendingFinalization(_) | BindingState::Bound(_) => {
627 BindingRequiredLookupResult::NoBinding(NoBinding {
628 request: binding_required_envelope(request),
629 })
630 }
631 }
632}
633
634#[derive(Debug)]
636pub enum DetachTokenResolution<'a, EF, V, LF> {
637 NoExactMatch,
639 Exact(ResolvedIdentity<'a, EF, V, LF>),
641}
642
643impl<EF, V, LF> Copy for DetachTokenResolution<'_, EF, V, LF> {}
644
645impl<EF, V, LF> Clone for DetachTokenResolution<'_, EF, V, LF> {
646 fn clone(&self) -> Self {
647 *self
648 }
649}
650
651pub struct DetachLookupContext<'a, EF, V, LF, D> {
657 pub token_resolution: DetachTokenResolution<'a, EF, V, LF>,
659 pub presented_identity: PresentedIdentity<'a, EF, V, LF>,
661 pub cell: &'a DetachCell<D>,
663 pub binding: &'a BindingState,
665 pub receiving_binding_epoch: Option<BindingEpoch>,
667 pub request: &'a DetachRequest,
669 pub request_verifier: D,
671 pub observer_progress: DeliverySeq,
673}
674
675#[derive(Clone, Debug, PartialEq, Eq)]
677pub enum DetachLookupResult<'a, EF, D> {
678 Retired(Retired),
680 ParticipantUnknown(ParticipantUnknown),
682 StaleAuthority(DetachStaleAuthority),
684 NoBinding(NoBinding),
686 PendingReplayRequired(PendingReplayRequest<D>),
688 DetachInProgress(DetachInProgress),
690 DetachCommitted(DetachCommitted),
692 Authorized {
694 member: &'a LiveMember<EF>,
696 binding: ActiveBinding,
698 },
699}
700
701#[must_use]
709pub fn lookup_detach<'a, EF, V, LF, D>(
710 context: &DetachLookupContext<'a, EF, V, LF, D>,
711) -> DetachLookupResult<'a, EF, D>
712where
713 D: Copy + Eq,
714{
715 let token_resolution = context.token_resolution;
716 let presented_identity = context.presented_identity;
717 let cell = context.cell;
718 let binding = context.binding;
719 let receiving_binding_epoch = context.receiving_binding_epoch;
720 let request = context.request;
721 let request_verifier = context.request_verifier;
722 let observer_progress = context.observer_progress;
723 let exact_member = match token_resolution {
724 DetachTokenResolution::Exact(ResolvedIdentity::Retired(tombstone)) => {
725 return DetachLookupResult::Retired(retired_detach(tombstone, request));
726 }
727 DetachTokenResolution::Exact(ResolvedIdentity::Live(member)) => Some(member),
728 DetachTokenResolution::NoExactMatch => None,
729 };
730
731 if let Some(member) = exact_member
732 && let Some(result) = lookup_exact_detach_cell(
733 member,
734 cell,
735 binding,
736 request,
737 request_verifier,
738 observer_progress,
739 )
740 {
741 return result;
742 }
743
744 let member = match presented_identity {
745 PresentedIdentity::Retired(tombstone) => {
746 return DetachLookupResult::Retired(retired_detach(tombstone, request));
747 }
748 PresentedIdentity::Absent => {
749 return DetachLookupResult::ParticipantUnknown(unknown_detach(request));
750 }
751 PresentedIdentity::Live(member) => {
752 if member.conversation_id() != request.conversation_id
753 || member.participant_id() != request.participant_id
754 {
755 return DetachLookupResult::ParticipantUnknown(unknown_detach(request));
756 }
757 member
758 }
759 };
760
761 if matches!(token_resolution, DetachTokenResolution::NoExactMatch)
762 && let DetachCell::Pending(pending) = cell
763 {
764 return DetachLookupResult::DetachInProgress(pending.competing_attempt(
765 request.conversation_id,
766 request.detach_attempt_token,
767 request.capability_generation,
768 ));
769 }
770
771 if request.capability_generation != member.generation() {
772 return stale_detach(request, member.generation());
773 }
774
775 match binding {
776 BindingState::Bound(active)
777 if active.conversation_id == request.conversation_id
778 && active.participant_id == request.participant_id
779 && active.binding_epoch.capability_generation == request.capability_generation
780 && receiving_binding_epoch == Some(active.binding_epoch) =>
781 {
782 DetachLookupResult::Authorized {
783 member,
784 binding: *active,
785 }
786 }
787 BindingState::Detached | BindingState::PendingFinalization(_) | BindingState::Bound(_) => {
788 DetachLookupResult::NoBinding(no_binding_detach(request))
789 }
790 }
791}
792
793fn lookup_exact_detach_cell<'a, EF, D>(
794 member: &'a LiveMember<EF>,
795 cell: &DetachCell<D>,
796 binding: &BindingState,
797 request: &DetachRequest,
798 request_verifier: D,
799 observer_progress: DeliverySeq,
800) -> Option<DetachLookupResult<'a, EF, D>>
801where
802 D: Copy + Eq,
803{
804 if request.conversation_id != member.conversation_id() {
808 return None;
809 }
810
811 match cell {
812 DetachCell::Empty(_) => None,
813 DetachCell::Pending(pending) => {
814 Some(pending.verify_exact(request, request_verifier).map_or_else(
815 |_| stale_detach(request, member.generation()),
816 |verified| {
817 DetachLookupResult::PendingReplayRequired(verified.prepare_replay(
818 request.conversation_id,
819 *binding,
820 observer_progress,
821 ))
822 },
823 ))
824 }
825 DetachCell::Committed(committed) => Some(
826 committed
827 .verify_exact(request, request_verifier)
828 .map_or_else(
829 |_| stale_detach(request, member.generation()),
830 |verified| {
831 DetachLookupResult::DetachCommitted(
832 verified.outcome(request.conversation_id),
833 )
834 },
835 ),
836 ),
837 DetachCell::Terminalized(terminalized) => Some(
838 terminalized
839 .verify_exact(request, request_verifier)
840 .map_or_else(
841 |_| stale_detach(request, member.generation()),
842 |verified| {
843 let outcome = verified.outcome(
844 request.conversation_id,
845 member.generation(),
846 binding_view(member, binding),
847 );
848 DetachLookupResult::StaleAuthority(
849 DetachStaleAuthority::TerminalizedDetachCell(outcome),
850 )
851 },
852 ),
853 ),
854 }
855}
856
857#[derive(Clone, Copy, Debug, PartialEq, Eq)]
860pub enum LeaveSecretProof {
861 Mismatch,
863 Verified,
865}
866
867#[derive(Clone, Debug, PartialEq, Eq)]
869pub enum LeaveLookupResult<'a, EF> {
870 LeaveCommitted(LeaveCommitted),
872 AttemptTokenBodyConflict(AttemptTokenBodyConflict),
874 StaleAuthority(LeaveStaleAuthority),
876 Retired(Retired),
878 ParticipantUnknown(ParticipantUnknown),
880 NoBinding(NoBinding),
882 AuthorizedBound {
884 member: &'a LiveMember<EF>,
886 binding: ActiveBinding,
888 },
889 AuthorizedDetached {
892 member: &'a LiveMember<EF>,
894 },
895}
896
897#[must_use]
904pub fn lookup_leave<'a, EF, V, LF>(
905 identity: PresentedIdentity<'a, EF, V, LF>,
906 binding: &BindingState,
907 request_binding_epoch: Option<crate::wire::BindingEpoch>,
908 request: &LeaveRequest,
909 secret_proof: LeaveSecretProof,
910) -> LeaveLookupResult<'a, EF> {
911 match identity {
912 PresentedIdentity::Retired(tombstone) => {
913 lookup_retired_leave(tombstone, request, secret_proof)
914 }
915 PresentedIdentity::Absent => LeaveLookupResult::ParticipantUnknown(unknown_leave(request)),
916 PresentedIdentity::Live(member) => {
917 if member.conversation_id() != request.conversation_id
918 || member.participant_id() != request.participant_id
919 {
920 return LeaveLookupResult::ParticipantUnknown(unknown_leave(request));
921 }
922
923 let generation_mismatch = request.capability_generation != member.generation();
924 let secret_mismatch = secret_proof == LeaveSecretProof::Mismatch;
925 if generation_mismatch || secret_mismatch {
926 return LeaveLookupResult::StaleAuthority(live_leave_stale(
927 request,
928 member.generation(),
929 ));
930 }
931
932 match binding {
933 BindingState::Bound(active)
934 if active.conversation_id == request.conversation_id
935 && active.participant_id == request.participant_id
936 && request_binding_epoch == Some(active.binding_epoch) =>
937 {
938 LeaveLookupResult::AuthorizedBound {
939 member,
940 binding: *active,
941 }
942 }
943 BindingState::Bound(_) => LeaveLookupResult::NoBinding(no_binding_leave(request)),
944 BindingState::Detached | BindingState::PendingFinalization(_) => {
945 LeaveLookupResult::AuthorizedDetached { member }
946 }
947 }
948 }
949 }
950}
951
952fn lookup_retired_leave<'a, EF, V, LF>(
953 tombstone: &RetiredIdentity<EF, V, LF>,
954 request: &LeaveRequest,
955 secret_proof: LeaveSecretProof,
956) -> LeaveLookupResult<'a, EF> {
957 if request.conversation_id != tombstone.conversation_id()
958 || request.participant_id != tombstone.participant_id()
959 {
960 return LeaveLookupResult::ParticipantUnknown(unknown_leave(request));
961 }
962
963 if request.leave_attempt_token != tombstone.leave_attempt_token() {
964 return LeaveLookupResult::Retired(retired_leave(tombstone, request));
965 }
966
967 if secret_proof == LeaveSecretProof::Mismatch {
968 return LeaveLookupResult::StaleAuthority(LeaveStaleAuthority::CommittedLeaveTombstone {
969 conversation_id: request.conversation_id,
970 participant_id: request.participant_id,
971 presented_generation: request.capability_generation,
972 leave_attempt_token: request.leave_attempt_token,
973 retired_generation: tombstone.retired_generation(),
974 });
975 }
976
977 if request.capability_generation != tombstone.committed_result().presented_generation() {
978 return LeaveLookupResult::AttemptTokenBodyConflict(AttemptTokenBodyConflict::Leave {
979 token: request.leave_attempt_token,
980 conversation_id: request.conversation_id,
981 presented_participant_id: request.participant_id,
982 presented_generation: request.capability_generation,
983 });
984 }
985
986 LeaveLookupResult::LeaveCommitted(tombstone.committed_result().clone())
987}
988
989fn receipt_binding_is_current(
990 binding: &BindingState,
991 conversation_id: crate::wire::ConversationId,
992 participant_id: crate::wire::ParticipantId,
993 origin_binding_epoch: BindingEpoch,
994) -> bool {
995 match binding {
996 BindingState::Bound(active) => {
997 active.conversation_id == conversation_id
998 && active.participant_id == participant_id
999 && active.binding_epoch == origin_binding_epoch
1000 }
1001 BindingState::Detached | BindingState::PendingFinalization(_) => false,
1002 }
1003}
1004
1005const fn enrollment_envelope(request: &EnrollmentRequest) -> EnrollmentEnvelope {
1006 EnrollmentEnvelope {
1007 conversation_id: request.conversation_id,
1008 enrollment_token: request.enrollment_token,
1009 }
1010}
1011
1012const fn attach_envelope(request: &CredentialAttachRequest) -> AttachEnvelope {
1013 AttachEnvelope {
1014 conversation_id: request.conversation_id,
1015 participant_id: request.participant_id,
1016 capability_generation: request.capability_generation,
1017 attach_attempt_token: request.attach_attempt_token,
1018 accept_marker_delivery_seq: request.accept_marker_delivery_seq,
1019 }
1020}
1021
1022const fn retired_enrollment<EF, V, LF>(
1023 tombstone: &RetiredIdentity<EF, V, LF>,
1024 request: &EnrollmentRequest,
1025) -> Retired {
1026 Retired::Enrollment {
1027 request: enrollment_envelope(request),
1028 participant_id: tombstone.participant_id(),
1029 retired_generation: tombstone.retired_generation(),
1030 }
1031}
1032
1033const fn retired_attach<EF, V, LF>(
1034 tombstone: &RetiredIdentity<EF, V, LF>,
1035 request: &CredentialAttachRequest,
1036) -> Retired {
1037 Retired::Participant {
1038 request: ParticipantReferenceEnvelope::CredentialAttach(attach_envelope(request)),
1039 retired_generation: tombstone.retired_generation(),
1040 }
1041}
1042
1043const fn unknown_attach(request: &CredentialAttachRequest) -> ParticipantUnknown {
1044 ParticipantUnknown {
1045 request: ParticipantReferenceEnvelope::CredentialAttach(attach_envelope(request)),
1046 }
1047}
1048
1049const fn stale_attach(
1050 request: &CredentialAttachRequest,
1051 current_generation: Generation,
1052) -> StaleAuthority {
1053 StaleAuthority::Live {
1054 request: CommonStaleAuthorityEnvelope::CredentialAttach(attach_envelope(request)),
1055 current_generation,
1056 }
1057}
1058
1059const fn binding_request_conversation(
1060 request: &ParticipantBindingRequest,
1061) -> crate::wire::ConversationId {
1062 match request {
1063 ParticipantBindingRequest::ParticipantAck(request) => request.conversation_id,
1064 ParticipantBindingRequest::MarkerAck(request) => request.conversation_id,
1065 ParticipantBindingRequest::RecordAdmission(request) => request.conversation_id,
1066 }
1067}
1068
1069const fn binding_request_participant(
1070 request: &ParticipantBindingRequest,
1071) -> crate::wire::ParticipantId {
1072 match request {
1073 ParticipantBindingRequest::ParticipantAck(request) => request.participant_id,
1074 ParticipantBindingRequest::MarkerAck(request) => request.participant_id,
1075 ParticipantBindingRequest::RecordAdmission(request) => request.participant_id,
1076 }
1077}
1078
1079const fn binding_request_generation(request: &ParticipantBindingRequest) -> Generation {
1080 match request {
1081 ParticipantBindingRequest::ParticipantAck(request) => request.capability_generation,
1082 ParticipantBindingRequest::MarkerAck(request) => request.capability_generation,
1083 ParticipantBindingRequest::RecordAdmission(request) => request.capability_generation,
1084 }
1085}
1086
1087const fn participant_ack_envelope(request: &ParticipantAck) -> ParticipantAckEnvelope {
1088 ParticipantAckEnvelope {
1089 conversation_id: request.conversation_id,
1090 participant_id: request.participant_id,
1091 capability_generation: request.capability_generation,
1092 through_seq: request.through_seq,
1093 }
1094}
1095
1096const fn marker_ack_envelope(request: &MarkerAck) -> MarkerAckEnvelope {
1097 MarkerAckEnvelope {
1098 conversation_id: request.conversation_id,
1099 participant_id: request.participant_id,
1100 capability_generation: request.capability_generation,
1101 marker_delivery_seq: request.marker_delivery_seq,
1102 }
1103}
1104
1105const fn record_admission_envelope(request: &RecordAdmission) -> RecordAdmissionEnvelope {
1106 RecordAdmissionEnvelope {
1107 conversation_id: request.conversation_id,
1108 participant_id: request.participant_id,
1109 capability_generation: request.capability_generation,
1110 record_admission_attempt_token: request.record_admission_attempt_token,
1111 }
1112}
1113
1114const fn participant_reference(
1115 request: &ParticipantBindingRequest,
1116) -> ParticipantReferenceEnvelope {
1117 match request {
1118 ParticipantBindingRequest::ParticipantAck(request) => {
1119 ParticipantReferenceEnvelope::ParticipantAck(participant_ack_envelope(request))
1120 }
1121 ParticipantBindingRequest::MarkerAck(request) => {
1122 ParticipantReferenceEnvelope::MarkerAck(marker_ack_envelope(request))
1123 }
1124 ParticipantBindingRequest::RecordAdmission(request) => {
1125 ParticipantReferenceEnvelope::RecordAdmission(record_admission_envelope(request))
1126 }
1127 }
1128}
1129
1130const fn binding_required_envelope(request: &ParticipantBindingRequest) -> BindingRequiredEnvelope {
1131 match request {
1132 ParticipantBindingRequest::ParticipantAck(request) => {
1133 BindingRequiredEnvelope::ParticipantAck(participant_ack_envelope(request))
1134 }
1135 ParticipantBindingRequest::MarkerAck(request) => {
1136 BindingRequiredEnvelope::MarkerAck(marker_ack_envelope(request))
1137 }
1138 ParticipantBindingRequest::RecordAdmission(request) => {
1139 BindingRequiredEnvelope::RecordAdmission(record_admission_envelope(request))
1140 }
1141 }
1142}
1143
1144const fn common_stale_envelope(
1145 request: &ParticipantBindingRequest,
1146) -> CommonStaleAuthorityEnvelope {
1147 match request {
1148 ParticipantBindingRequest::ParticipantAck(request) => {
1149 CommonStaleAuthorityEnvelope::ParticipantAck(participant_ack_envelope(request))
1150 }
1151 ParticipantBindingRequest::MarkerAck(request) => {
1152 CommonStaleAuthorityEnvelope::MarkerAck(marker_ack_envelope(request))
1153 }
1154 ParticipantBindingRequest::RecordAdmission(request) => {
1155 CommonStaleAuthorityEnvelope::RecordAdmission(record_admission_envelope(request))
1156 }
1157 }
1158}
1159
1160fn binding_view<EF>(member: &LiveMember<EF>, binding: &BindingState) -> BindingStateView {
1161 match binding {
1162 BindingState::Bound(active)
1163 if active.participant_id == member.participant_id()
1164 && active.conversation_id == member.conversation_id()
1165 && active.binding_epoch.capability_generation == member.generation() =>
1166 {
1167 BindingStateView::Bound {
1168 current_binding_epoch: active.binding_epoch,
1169 }
1170 }
1171 BindingState::Detached | BindingState::PendingFinalization(_) | BindingState::Bound(_) => {
1172 BindingStateView::Detached
1173 }
1174 }
1175}
1176
1177const fn detach_envelope(request: &DetachRequest) -> DetachEnvelope {
1178 DetachEnvelope {
1179 conversation_id: request.conversation_id,
1180 participant_id: request.participant_id,
1181 capability_generation: request.capability_generation,
1182 detach_attempt_token: request.detach_attempt_token,
1183 }
1184}
1185
1186const fn leave_envelope(request: &LeaveRequest) -> LeaveEnvelope {
1187 LeaveEnvelope {
1188 conversation_id: request.conversation_id,
1189 participant_id: request.participant_id,
1190 capability_generation: request.capability_generation,
1191 leave_attempt_token: request.leave_attempt_token,
1192 }
1193}
1194
1195const fn retired_detach<EF, V, LF>(
1196 tombstone: &RetiredIdentity<EF, V, LF>,
1197 request: &DetachRequest,
1198) -> Retired {
1199 Retired::Participant {
1200 request: ParticipantReferenceEnvelope::Detach(detach_envelope(request)),
1201 retired_generation: tombstone.retired_generation(),
1202 }
1203}
1204
1205const fn retired_leave<EF, V, LF>(
1206 tombstone: &RetiredIdentity<EF, V, LF>,
1207 request: &LeaveRequest,
1208) -> Retired {
1209 Retired::Participant {
1210 request: ParticipantReferenceEnvelope::Leave(leave_envelope(request)),
1211 retired_generation: tombstone.retired_generation(),
1212 }
1213}
1214
1215const fn unknown_detach(request: &DetachRequest) -> ParticipantUnknown {
1216 ParticipantUnknown {
1217 request: ParticipantReferenceEnvelope::Detach(detach_envelope(request)),
1218 }
1219}
1220
1221const fn unknown_leave(request: &LeaveRequest) -> ParticipantUnknown {
1222 ParticipantUnknown {
1223 request: ParticipantReferenceEnvelope::Leave(leave_envelope(request)),
1224 }
1225}
1226
1227const fn no_binding_detach(request: &DetachRequest) -> NoBinding {
1228 NoBinding {
1229 request: BindingRequiredEnvelope::Detach(detach_envelope(request)),
1230 }
1231}
1232
1233const fn no_binding_leave(request: &LeaveRequest) -> NoBinding {
1234 NoBinding {
1235 request: BindingRequiredEnvelope::Leave(leave_envelope(request)),
1236 }
1237}
1238
1239const fn stale_detach<'a, EF, D>(
1240 request: &DetachRequest,
1241 current_generation: Generation,
1242) -> DetachLookupResult<'a, EF, D> {
1243 DetachLookupResult::StaleAuthority(DetachStaleAuthority::Live {
1244 conversation_id: request.conversation_id,
1245 participant_id: request.participant_id,
1246 capability_generation: request.capability_generation,
1247 detach_attempt_token: request.detach_attempt_token,
1248 current_generation,
1249 })
1250}
1251
1252const fn live_leave_stale(
1253 request: &LeaveRequest,
1254 current_generation: Generation,
1255) -> LeaveStaleAuthority {
1256 LeaveStaleAuthority::Live {
1257 conversation_id: request.conversation_id,
1258 participant_id: request.participant_id,
1259 presented_generation: request.capability_generation,
1260 leave_attempt_token: request.leave_attempt_token,
1261 current_generation,
1262 }
1263}