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
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub struct EnrollmentProvenance {
109 result_generation: Generation,
110 reason: ReceiptExpiryReason,
111}
112
113impl EnrollmentProvenance {
114 #[must_use]
116 pub const fn new(result_generation: Generation, reason: ReceiptExpiryReason) -> Self {
117 Self {
118 result_generation,
119 reason,
120 }
121 }
122}
123
124#[derive(Debug)]
127pub enum EnrollmentTokenPhase<'a, EF, V, LF> {
128 Unmapped,
130 LiveReceipt {
132 identity: ResolvedIdentity<'a, EF, V, LF>,
134 receipt: &'a EnrollmentLiveReceipt,
136 },
137 Provenance {
139 identity: ResolvedIdentity<'a, EF, V, LF>,
141 provenance: EnrollmentProvenance,
143 },
144 LifetimeMapping {
146 identity: ResolvedIdentity<'a, EF, V, LF>,
148 },
149}
150
151impl<EF, V, LF> Copy for EnrollmentTokenPhase<'_, EF, V, LF> {}
152
153impl<EF, V, LF> Clone for EnrollmentTokenPhase<'_, EF, V, LF> {
154 fn clone(&self) -> Self {
155 *self
156 }
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
161pub enum EnrollmentLookupResult {
162 Retired(Retired),
164 Bound(ReceiptReplay),
166 UnboundReceipt(ReceiptReplay),
168 ReceiptExpired(ReceiptExpired),
170 EnrollmentKnown(EnrollmentKnown),
172 AuthorizedNew,
174}
175
176#[must_use]
179pub fn lookup_enrollment<EF, V, LF>(
180 phase: EnrollmentTokenPhase<'_, EF, V, LF>,
181 binding: &BindingState,
182 request: &EnrollmentRequest,
183) -> EnrollmentLookupResult {
184 match phase {
185 EnrollmentTokenPhase::Unmapped => EnrollmentLookupResult::AuthorizedNew,
186 EnrollmentTokenPhase::LiveReceipt { identity, receipt } => match identity {
187 ResolvedIdentity::Retired(tombstone) => {
188 EnrollmentLookupResult::Retired(retired_enrollment(tombstone, request))
189 }
190 ResolvedIdentity::Live(member) => {
191 let committed = &receipt.committed;
192 let replay = ReceiptReplay::Enrollment(committed.clone());
193 if receipt_binding_is_current(
194 binding,
195 request.conversation_id,
196 member.participant_id(),
197 committed.origin_binding_epoch(),
198 ) {
199 EnrollmentLookupResult::Bound(replay)
200 } else {
201 EnrollmentLookupResult::UnboundReceipt(replay)
202 }
203 }
204 },
205 EnrollmentTokenPhase::Provenance {
206 identity,
207 provenance,
208 } => match identity {
209 ResolvedIdentity::Retired(tombstone) => {
210 EnrollmentLookupResult::Retired(retired_enrollment(tombstone, request))
211 }
212 ResolvedIdentity::Live(member) => {
213 EnrollmentLookupResult::ReceiptExpired(ReceiptExpired::Enrollment {
214 conversation_id: request.conversation_id,
215 token: request.enrollment_token,
216 participant_id: member.participant_id(),
217 result_generation: provenance.result_generation,
218 current_generation: member.generation(),
219 reason: provenance.reason,
220 })
221 }
222 },
223 EnrollmentTokenPhase::LifetimeMapping { identity } => match identity {
224 ResolvedIdentity::Retired(tombstone) => {
225 EnrollmentLookupResult::Retired(retired_enrollment(tombstone, request))
226 }
227 ResolvedIdentity::Live(member) => {
228 EnrollmentLookupResult::EnrollmentKnown(EnrollmentKnown {
229 conversation_id: request.conversation_id,
230 token: request.enrollment_token,
231 participant_id: member.participant_id(),
232 current_generation: member.generation(),
233 })
234 }
235 },
236 }
237}
238
239#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct CredentialAttachLiveReceipt {
243 committed: AttachBound,
244}
245
246impl CredentialAttachLiveReceipt {
247 #[must_use]
249 pub const fn from_commit(committed: AttachBound) -> Self {
250 Self { committed }
251 }
252}
253
254#[derive(Clone, Copy, Debug, PartialEq, Eq)]
257pub struct CredentialAttachProvenance {
258 result_generation: Generation,
259 reason: ReceiptExpiryReason,
260}
261
262impl CredentialAttachProvenance {
263 #[must_use]
265 pub const fn new(result_generation: Generation, reason: ReceiptExpiryReason) -> Self {
266 Self {
267 result_generation,
268 reason,
269 }
270 }
271}
272
273#[derive(Debug)]
275pub enum CredentialAttachTokenPhase<'a, EF, V, LF> {
276 NoMatch,
278 LiveReceipt {
280 identity: ResolvedIdentity<'a, EF, V, LF>,
282 receipt: &'a CredentialAttachLiveReceipt,
284 },
285 Provenance {
287 identity: ResolvedIdentity<'a, EF, V, LF>,
289 provenance: CredentialAttachProvenance,
291 },
292 AfterProvenance,
295}
296
297impl<EF, V, LF> Copy for CredentialAttachTokenPhase<'_, EF, V, LF> {}
298
299impl<EF, V, LF> Clone for CredentialAttachTokenPhase<'_, EF, V, LF> {
300 fn clone(&self) -> Self {
301 *self
302 }
303}
304
305#[derive(Clone, Debug, PartialEq, Eq)]
307pub enum CredentialAttachLookupResult<'a, EF> {
308 Retired(Retired),
310 ParticipantUnknown(ParticipantUnknown),
312 StaleAuthority(StaleAuthority),
314 AttemptTokenBodyConflict(AttemptTokenBodyConflict),
316 Bound(ReceiptReplay),
318 UnboundReceipt(ReceiptReplay),
320 ReceiptExpired(ReceiptExpired),
322 StaleOrUnknownReceipt(StaleOrUnknownReceipt),
324 AuthorizedFresh {
326 member: &'a LiveMember<EF>,
328 },
329}
330
331#[must_use]
339pub fn lookup_credential_attach<'a, EF, V, LF>(
340 token_phase: CredentialAttachTokenPhase<'a, EF, V, LF>,
341 presented_identity: PresentedIdentity<'a, EF, V, LF>,
342 binding: &BindingState,
343 request: &CredentialAttachRequest,
344 secret_proof: AttachSecretProof,
345) -> CredentialAttachLookupResult<'a, EF> {
346 match token_phase {
347 CredentialAttachTokenPhase::LiveReceipt { identity, receipt } => {
348 return match identity {
349 ResolvedIdentity::Retired(tombstone) => {
350 CredentialAttachLookupResult::Retired(retired_attach(tombstone, request))
351 }
352 ResolvedIdentity::Live(member) => {
353 lookup_live_attach_receipt(member, binding, request, secret_proof, receipt)
354 }
355 };
356 }
357 CredentialAttachTokenPhase::Provenance {
358 identity,
359 provenance,
360 } => {
361 return match identity {
362 ResolvedIdentity::Retired(tombstone) => {
363 CredentialAttachLookupResult::Retired(retired_attach(tombstone, request))
364 }
365 ResolvedIdentity::Live(member) => {
366 CredentialAttachLookupResult::ReceiptExpired(ReceiptExpired::CredentialAttach {
367 conversation_id: request.conversation_id,
368 token: request.attach_attempt_token,
369 participant_id: request.participant_id,
370 presented_generation: request.capability_generation,
371 presented_marker_delivery_seq: request.accept_marker_delivery_seq,
372 result_generation: provenance.result_generation,
373 current_generation: member.generation(),
374 reason: provenance.reason,
375 })
376 }
377 };
378 }
379 CredentialAttachTokenPhase::NoMatch | CredentialAttachTokenPhase::AfterProvenance => {}
380 }
381
382 let member = match presented_identity {
383 PresentedIdentity::Retired(tombstone) => {
384 return CredentialAttachLookupResult::Retired(retired_attach(tombstone, request));
385 }
386 PresentedIdentity::Absent => {
387 return CredentialAttachLookupResult::ParticipantUnknown(unknown_attach(request));
388 }
389 PresentedIdentity::Live(member) => {
390 if member.conversation_id() != request.conversation_id
391 || member.participant_id() != request.participant_id
392 {
393 return CredentialAttachLookupResult::ParticipantUnknown(unknown_attach(request));
394 }
395 member
396 }
397 };
398
399 if matches!(token_phase, CredentialAttachTokenPhase::AfterProvenance) {
400 return CredentialAttachLookupResult::StaleOrUnknownReceipt(StaleOrUnknownReceipt {
401 conversation_id: request.conversation_id,
402 token: request.attach_attempt_token,
403 participant_id: request.participant_id,
404 presented_generation: request.capability_generation,
405 presented_marker_delivery_seq: request.accept_marker_delivery_seq,
406 current_generation: member.generation(),
407 });
408 }
409
410 if request.capability_generation != member.generation()
411 || secret_proof == AttachSecretProof::Mismatch
412 {
413 return CredentialAttachLookupResult::StaleAuthority(stale_attach(
414 request,
415 member.generation(),
416 ));
417 }
418
419 CredentialAttachLookupResult::AuthorizedFresh { member }
420}
421
422fn lookup_live_attach_receipt<'a, EF>(
423 member: &'a LiveMember<EF>,
424 binding: &BindingState,
425 request: &CredentialAttachRequest,
426 secret_proof: AttachSecretProof,
427 receipt: &CredentialAttachLiveReceipt,
428) -> CredentialAttachLookupResult<'a, EF> {
429 if secret_proof == AttachSecretProof::Mismatch {
430 return CredentialAttachLookupResult::StaleAuthority(stale_attach(
431 request,
432 member.generation(),
433 ));
434 }
435
436 let committed = &receipt.committed;
437 let conflict = if request.capability_generation != committed.request_generation() {
438 Some(AttemptConflict::Generation)
439 } else if request.accept_marker_delivery_seq != committed.accepted_marker_delivery_seq() {
440 Some(AttemptConflict::MarkerDeliverySequence)
441 } else {
442 None
443 };
444 if let Some(conflict) = conflict {
445 return CredentialAttachLookupResult::AttemptTokenBodyConflict(
446 AttemptTokenBodyConflict::CredentialAttach {
447 token: request.attach_attempt_token,
448 conversation_id: request.conversation_id,
449 presented_participant_id: request.participant_id,
450 presented_generation: request.capability_generation,
451 presented_marker_delivery_seq: request.accept_marker_delivery_seq,
452 conflict,
453 },
454 );
455 }
456
457 let replay = ReceiptReplay::CredentialAttach(committed.clone());
458 if receipt_binding_is_current(
459 binding,
460 request.conversation_id,
461 request.participant_id,
462 committed.origin_binding_epoch(),
463 ) {
464 CredentialAttachLookupResult::Bound(replay)
465 } else {
466 CredentialAttachLookupResult::UnboundReceipt(replay)
467 }
468}
469
470#[derive(Clone, Debug, PartialEq, Eq)]
473pub enum ParticipantBindingRequest {
474 ParticipantAck(ParticipantAck),
476 MarkerAck(MarkerAck),
478 RecordAdmission(RecordAdmission),
480}
481
482#[derive(Clone, Debug, PartialEq, Eq)]
484pub enum BindingRequiredLookupResult<'a, EF> {
485 Retired(Retired),
487 ParticipantUnknown(ParticipantUnknown),
489 StaleAuthority(StaleAuthority),
491 NoBinding(NoBinding),
493 Authorized {
495 member: &'a LiveMember<EF>,
497 binding: ActiveBinding,
499 },
500}
501
502#[must_use]
505pub fn lookup_binding_required<'a, EF, V, LF>(
506 identity: PresentedIdentity<'a, EF, V, LF>,
507 binding: &BindingState,
508 receiving_binding_epoch: Option<BindingEpoch>,
509 request: &ParticipantBindingRequest,
510) -> BindingRequiredLookupResult<'a, EF> {
511 let member = match identity {
512 PresentedIdentity::Retired(tombstone) => {
513 return BindingRequiredLookupResult::Retired(Retired::Participant {
514 request: participant_reference(request),
515 retired_generation: tombstone.retired_generation(),
516 });
517 }
518 PresentedIdentity::Absent => {
519 return BindingRequiredLookupResult::ParticipantUnknown(ParticipantUnknown {
520 request: participant_reference(request),
521 });
522 }
523 PresentedIdentity::Live(member) => {
524 if member.conversation_id() != binding_request_conversation(request)
525 || member.participant_id() != binding_request_participant(request)
526 {
527 return BindingRequiredLookupResult::ParticipantUnknown(ParticipantUnknown {
528 request: participant_reference(request),
529 });
530 }
531 member
532 }
533 };
534
535 if binding_request_generation(request) != member.generation() {
536 return BindingRequiredLookupResult::StaleAuthority(StaleAuthority::Live {
537 request: common_stale_envelope(request),
538 current_generation: member.generation(),
539 });
540 }
541
542 match binding {
543 BindingState::Bound(active)
544 if active.conversation_id == member.conversation_id()
545 && active.participant_id == member.participant_id()
546 && active.binding_epoch.capability_generation == member.generation()
547 && receiving_binding_epoch == Some(active.binding_epoch) =>
548 {
549 BindingRequiredLookupResult::Authorized {
550 member,
551 binding: *active,
552 }
553 }
554 BindingState::Detached | BindingState::PendingFinalization(_) | BindingState::Bound(_) => {
555 BindingRequiredLookupResult::NoBinding(NoBinding {
556 request: binding_required_envelope(request),
557 })
558 }
559 }
560}
561
562#[derive(Debug)]
564pub enum DetachTokenResolution<'a, EF, V, LF> {
565 NoExactMatch,
567 Exact(ResolvedIdentity<'a, EF, V, LF>),
569}
570
571impl<EF, V, LF> Copy for DetachTokenResolution<'_, EF, V, LF> {}
572
573impl<EF, V, LF> Clone for DetachTokenResolution<'_, EF, V, LF> {
574 fn clone(&self) -> Self {
575 *self
576 }
577}
578
579pub struct DetachLookupContext<'a, EF, V, LF, D> {
585 pub token_resolution: DetachTokenResolution<'a, EF, V, LF>,
587 pub presented_identity: PresentedIdentity<'a, EF, V, LF>,
589 pub cell: &'a DetachCell<D>,
591 pub binding: &'a BindingState,
593 pub receiving_binding_epoch: Option<BindingEpoch>,
595 pub request: &'a DetachRequest,
597 pub request_verifier: D,
599 pub observer_progress: DeliverySeq,
601}
602
603#[derive(Clone, Debug, PartialEq, Eq)]
605pub enum DetachLookupResult<'a, EF, D> {
606 Retired(Retired),
608 ParticipantUnknown(ParticipantUnknown),
610 StaleAuthority(DetachStaleAuthority),
612 NoBinding(NoBinding),
614 PendingReplayRequired(PendingReplayRequest<D>),
616 DetachInProgress(DetachInProgress),
618 DetachCommitted(DetachCommitted),
620 Authorized {
622 member: &'a LiveMember<EF>,
624 binding: ActiveBinding,
626 },
627}
628
629#[must_use]
637pub fn lookup_detach<'a, EF, V, LF, D>(
638 context: &DetachLookupContext<'a, EF, V, LF, D>,
639) -> DetachLookupResult<'a, EF, D>
640where
641 D: Copy + Eq,
642{
643 let token_resolution = context.token_resolution;
644 let presented_identity = context.presented_identity;
645 let cell = context.cell;
646 let binding = context.binding;
647 let receiving_binding_epoch = context.receiving_binding_epoch;
648 let request = context.request;
649 let request_verifier = context.request_verifier;
650 let observer_progress = context.observer_progress;
651 let exact_member = match token_resolution {
652 DetachTokenResolution::Exact(ResolvedIdentity::Retired(tombstone)) => {
653 return DetachLookupResult::Retired(retired_detach(tombstone, request));
654 }
655 DetachTokenResolution::Exact(ResolvedIdentity::Live(member)) => Some(member),
656 DetachTokenResolution::NoExactMatch => None,
657 };
658
659 if let Some(member) = exact_member
660 && let Some(result) = lookup_exact_detach_cell(
661 member,
662 cell,
663 binding,
664 request,
665 request_verifier,
666 observer_progress,
667 )
668 {
669 return result;
670 }
671
672 let member = match presented_identity {
673 PresentedIdentity::Retired(tombstone) => {
674 return DetachLookupResult::Retired(retired_detach(tombstone, request));
675 }
676 PresentedIdentity::Absent => {
677 return DetachLookupResult::ParticipantUnknown(unknown_detach(request));
678 }
679 PresentedIdentity::Live(member) => {
680 if member.conversation_id() != request.conversation_id
681 || member.participant_id() != request.participant_id
682 {
683 return DetachLookupResult::ParticipantUnknown(unknown_detach(request));
684 }
685 member
686 }
687 };
688
689 if matches!(token_resolution, DetachTokenResolution::NoExactMatch)
690 && let DetachCell::Pending(pending) = cell
691 {
692 return DetachLookupResult::DetachInProgress(pending.competing_attempt(
693 request.conversation_id,
694 request.detach_attempt_token,
695 request.capability_generation,
696 ));
697 }
698
699 if request.capability_generation != member.generation() {
700 return stale_detach(request, member.generation());
701 }
702
703 match binding {
704 BindingState::Bound(active)
705 if active.conversation_id == request.conversation_id
706 && active.participant_id == request.participant_id
707 && active.binding_epoch.capability_generation == request.capability_generation
708 && receiving_binding_epoch == Some(active.binding_epoch) =>
709 {
710 DetachLookupResult::Authorized {
711 member,
712 binding: *active,
713 }
714 }
715 BindingState::Detached | BindingState::PendingFinalization(_) | BindingState::Bound(_) => {
716 DetachLookupResult::NoBinding(no_binding_detach(request))
717 }
718 }
719}
720
721fn lookup_exact_detach_cell<'a, EF, D>(
722 member: &'a LiveMember<EF>,
723 cell: &DetachCell<D>,
724 binding: &BindingState,
725 request: &DetachRequest,
726 request_verifier: D,
727 observer_progress: DeliverySeq,
728) -> Option<DetachLookupResult<'a, EF, D>>
729where
730 D: Copy + Eq,
731{
732 if request.conversation_id != member.conversation_id() {
736 return None;
737 }
738
739 match cell {
740 DetachCell::Empty(_) => None,
741 DetachCell::Pending(pending) => {
742 Some(pending.verify_exact(request, request_verifier).map_or_else(
743 |_| stale_detach(request, member.generation()),
744 |verified| {
745 DetachLookupResult::PendingReplayRequired(verified.prepare_replay(
746 request.conversation_id,
747 *binding,
748 observer_progress,
749 ))
750 },
751 ))
752 }
753 DetachCell::Committed(committed) => Some(
754 committed
755 .verify_exact(request, request_verifier)
756 .map_or_else(
757 |_| stale_detach(request, member.generation()),
758 |verified| {
759 DetachLookupResult::DetachCommitted(
760 verified.outcome(request.conversation_id),
761 )
762 },
763 ),
764 ),
765 DetachCell::Terminalized(terminalized) => Some(
766 terminalized
767 .verify_exact(request, request_verifier)
768 .map_or_else(
769 |_| stale_detach(request, member.generation()),
770 |verified| {
771 let outcome = verified.outcome(
772 request.conversation_id,
773 member.generation(),
774 binding_view(member, binding),
775 );
776 DetachLookupResult::StaleAuthority(
777 DetachStaleAuthority::TerminalizedDetachCell(outcome),
778 )
779 },
780 ),
781 ),
782 }
783}
784
785#[derive(Clone, Copy, Debug, PartialEq, Eq)]
788pub enum LeaveSecretProof {
789 Mismatch,
791 Verified,
793}
794
795#[derive(Clone, Debug, PartialEq, Eq)]
797pub enum LeaveLookupResult<'a, EF> {
798 LeaveCommitted(LeaveCommitted),
800 AttemptTokenBodyConflict(AttemptTokenBodyConflict),
802 StaleAuthority(LeaveStaleAuthority),
804 Retired(Retired),
806 ParticipantUnknown(ParticipantUnknown),
808 NoBinding(NoBinding),
810 AuthorizedBound {
812 member: &'a LiveMember<EF>,
814 binding: ActiveBinding,
816 },
817 AuthorizedDetached {
820 member: &'a LiveMember<EF>,
822 },
823}
824
825#[must_use]
832pub fn lookup_leave<'a, EF, V, LF>(
833 identity: PresentedIdentity<'a, EF, V, LF>,
834 binding: &BindingState,
835 request_binding_epoch: Option<crate::wire::BindingEpoch>,
836 request: &LeaveRequest,
837 secret_proof: LeaveSecretProof,
838) -> LeaveLookupResult<'a, EF> {
839 match identity {
840 PresentedIdentity::Retired(tombstone) => {
841 lookup_retired_leave(tombstone, request, secret_proof)
842 }
843 PresentedIdentity::Absent => LeaveLookupResult::ParticipantUnknown(unknown_leave(request)),
844 PresentedIdentity::Live(member) => {
845 if member.conversation_id() != request.conversation_id
846 || member.participant_id() != request.participant_id
847 {
848 return LeaveLookupResult::ParticipantUnknown(unknown_leave(request));
849 }
850
851 let generation_mismatch = request.capability_generation != member.generation();
852 let secret_mismatch = secret_proof == LeaveSecretProof::Mismatch;
853 if generation_mismatch || secret_mismatch {
854 return LeaveLookupResult::StaleAuthority(live_leave_stale(
855 request,
856 member.generation(),
857 ));
858 }
859
860 match binding {
861 BindingState::Bound(active)
862 if active.conversation_id == request.conversation_id
863 && active.participant_id == request.participant_id
864 && request_binding_epoch == Some(active.binding_epoch) =>
865 {
866 LeaveLookupResult::AuthorizedBound {
867 member,
868 binding: *active,
869 }
870 }
871 BindingState::Bound(_) => LeaveLookupResult::NoBinding(no_binding_leave(request)),
872 BindingState::Detached | BindingState::PendingFinalization(_) => {
873 LeaveLookupResult::AuthorizedDetached { member }
874 }
875 }
876 }
877 }
878}
879
880fn lookup_retired_leave<'a, EF, V, LF>(
881 tombstone: &RetiredIdentity<EF, V, LF>,
882 request: &LeaveRequest,
883 secret_proof: LeaveSecretProof,
884) -> LeaveLookupResult<'a, EF> {
885 if request.conversation_id != tombstone.conversation_id()
886 || request.participant_id != tombstone.participant_id()
887 {
888 return LeaveLookupResult::ParticipantUnknown(unknown_leave(request));
889 }
890
891 if request.leave_attempt_token != tombstone.leave_attempt_token() {
892 return LeaveLookupResult::Retired(retired_leave(tombstone, request));
893 }
894
895 if secret_proof == LeaveSecretProof::Mismatch {
896 return LeaveLookupResult::StaleAuthority(LeaveStaleAuthority::CommittedLeaveTombstone {
897 conversation_id: request.conversation_id,
898 participant_id: request.participant_id,
899 presented_generation: request.capability_generation,
900 leave_attempt_token: request.leave_attempt_token,
901 retired_generation: tombstone.retired_generation(),
902 });
903 }
904
905 if request.capability_generation != tombstone.committed_result().presented_generation() {
906 return LeaveLookupResult::AttemptTokenBodyConflict(AttemptTokenBodyConflict::Leave {
907 token: request.leave_attempt_token,
908 conversation_id: request.conversation_id,
909 presented_participant_id: request.participant_id,
910 presented_generation: request.capability_generation,
911 });
912 }
913
914 LeaveLookupResult::LeaveCommitted(tombstone.committed_result().clone())
915}
916
917fn receipt_binding_is_current(
918 binding: &BindingState,
919 conversation_id: crate::wire::ConversationId,
920 participant_id: crate::wire::ParticipantId,
921 origin_binding_epoch: BindingEpoch,
922) -> bool {
923 match binding {
924 BindingState::Bound(active) => {
925 active.conversation_id == conversation_id
926 && active.participant_id == participant_id
927 && active.binding_epoch == origin_binding_epoch
928 }
929 BindingState::Detached | BindingState::PendingFinalization(_) => false,
930 }
931}
932
933const fn enrollment_envelope(request: &EnrollmentRequest) -> EnrollmentEnvelope {
934 EnrollmentEnvelope {
935 conversation_id: request.conversation_id,
936 enrollment_token: request.enrollment_token,
937 }
938}
939
940const fn attach_envelope(request: &CredentialAttachRequest) -> AttachEnvelope {
941 AttachEnvelope {
942 conversation_id: request.conversation_id,
943 participant_id: request.participant_id,
944 capability_generation: request.capability_generation,
945 attach_attempt_token: request.attach_attempt_token,
946 accept_marker_delivery_seq: request.accept_marker_delivery_seq,
947 }
948}
949
950const fn retired_enrollment<EF, V, LF>(
951 tombstone: &RetiredIdentity<EF, V, LF>,
952 request: &EnrollmentRequest,
953) -> Retired {
954 Retired::Enrollment {
955 request: enrollment_envelope(request),
956 participant_id: tombstone.participant_id(),
957 retired_generation: tombstone.retired_generation(),
958 }
959}
960
961const fn retired_attach<EF, V, LF>(
962 tombstone: &RetiredIdentity<EF, V, LF>,
963 request: &CredentialAttachRequest,
964) -> Retired {
965 Retired::Participant {
966 request: ParticipantReferenceEnvelope::CredentialAttach(attach_envelope(request)),
967 retired_generation: tombstone.retired_generation(),
968 }
969}
970
971const fn unknown_attach(request: &CredentialAttachRequest) -> ParticipantUnknown {
972 ParticipantUnknown {
973 request: ParticipantReferenceEnvelope::CredentialAttach(attach_envelope(request)),
974 }
975}
976
977const fn stale_attach(
978 request: &CredentialAttachRequest,
979 current_generation: Generation,
980) -> StaleAuthority {
981 StaleAuthority::Live {
982 request: CommonStaleAuthorityEnvelope::CredentialAttach(attach_envelope(request)),
983 current_generation,
984 }
985}
986
987const fn binding_request_conversation(
988 request: &ParticipantBindingRequest,
989) -> crate::wire::ConversationId {
990 match request {
991 ParticipantBindingRequest::ParticipantAck(request) => request.conversation_id,
992 ParticipantBindingRequest::MarkerAck(request) => request.conversation_id,
993 ParticipantBindingRequest::RecordAdmission(request) => request.conversation_id,
994 }
995}
996
997const fn binding_request_participant(
998 request: &ParticipantBindingRequest,
999) -> crate::wire::ParticipantId {
1000 match request {
1001 ParticipantBindingRequest::ParticipantAck(request) => request.participant_id,
1002 ParticipantBindingRequest::MarkerAck(request) => request.participant_id,
1003 ParticipantBindingRequest::RecordAdmission(request) => request.participant_id,
1004 }
1005}
1006
1007const fn binding_request_generation(request: &ParticipantBindingRequest) -> Generation {
1008 match request {
1009 ParticipantBindingRequest::ParticipantAck(request) => request.capability_generation,
1010 ParticipantBindingRequest::MarkerAck(request) => request.capability_generation,
1011 ParticipantBindingRequest::RecordAdmission(request) => request.capability_generation,
1012 }
1013}
1014
1015const fn participant_ack_envelope(request: &ParticipantAck) -> ParticipantAckEnvelope {
1016 ParticipantAckEnvelope {
1017 conversation_id: request.conversation_id,
1018 participant_id: request.participant_id,
1019 capability_generation: request.capability_generation,
1020 through_seq: request.through_seq,
1021 }
1022}
1023
1024const fn marker_ack_envelope(request: &MarkerAck) -> MarkerAckEnvelope {
1025 MarkerAckEnvelope {
1026 conversation_id: request.conversation_id,
1027 participant_id: request.participant_id,
1028 capability_generation: request.capability_generation,
1029 marker_delivery_seq: request.marker_delivery_seq,
1030 }
1031}
1032
1033const fn record_admission_envelope(request: &RecordAdmission) -> RecordAdmissionEnvelope {
1034 RecordAdmissionEnvelope {
1035 conversation_id: request.conversation_id,
1036 participant_id: request.participant_id,
1037 capability_generation: request.capability_generation,
1038 record_admission_attempt_token: request.record_admission_attempt_token,
1039 }
1040}
1041
1042const fn participant_reference(
1043 request: &ParticipantBindingRequest,
1044) -> ParticipantReferenceEnvelope {
1045 match request {
1046 ParticipantBindingRequest::ParticipantAck(request) => {
1047 ParticipantReferenceEnvelope::ParticipantAck(participant_ack_envelope(request))
1048 }
1049 ParticipantBindingRequest::MarkerAck(request) => {
1050 ParticipantReferenceEnvelope::MarkerAck(marker_ack_envelope(request))
1051 }
1052 ParticipantBindingRequest::RecordAdmission(request) => {
1053 ParticipantReferenceEnvelope::RecordAdmission(record_admission_envelope(request))
1054 }
1055 }
1056}
1057
1058const fn binding_required_envelope(request: &ParticipantBindingRequest) -> BindingRequiredEnvelope {
1059 match request {
1060 ParticipantBindingRequest::ParticipantAck(request) => {
1061 BindingRequiredEnvelope::ParticipantAck(participant_ack_envelope(request))
1062 }
1063 ParticipantBindingRequest::MarkerAck(request) => {
1064 BindingRequiredEnvelope::MarkerAck(marker_ack_envelope(request))
1065 }
1066 ParticipantBindingRequest::RecordAdmission(request) => {
1067 BindingRequiredEnvelope::RecordAdmission(record_admission_envelope(request))
1068 }
1069 }
1070}
1071
1072const fn common_stale_envelope(
1073 request: &ParticipantBindingRequest,
1074) -> CommonStaleAuthorityEnvelope {
1075 match request {
1076 ParticipantBindingRequest::ParticipantAck(request) => {
1077 CommonStaleAuthorityEnvelope::ParticipantAck(participant_ack_envelope(request))
1078 }
1079 ParticipantBindingRequest::MarkerAck(request) => {
1080 CommonStaleAuthorityEnvelope::MarkerAck(marker_ack_envelope(request))
1081 }
1082 ParticipantBindingRequest::RecordAdmission(request) => {
1083 CommonStaleAuthorityEnvelope::RecordAdmission(record_admission_envelope(request))
1084 }
1085 }
1086}
1087
1088fn binding_view<EF>(member: &LiveMember<EF>, binding: &BindingState) -> BindingStateView {
1089 match binding {
1090 BindingState::Bound(active)
1091 if active.participant_id == member.participant_id()
1092 && active.conversation_id == member.conversation_id()
1093 && active.binding_epoch.capability_generation == member.generation() =>
1094 {
1095 BindingStateView::Bound {
1096 current_binding_epoch: active.binding_epoch,
1097 }
1098 }
1099 BindingState::Detached | BindingState::PendingFinalization(_) | BindingState::Bound(_) => {
1100 BindingStateView::Detached
1101 }
1102 }
1103}
1104
1105const fn detach_envelope(request: &DetachRequest) -> DetachEnvelope {
1106 DetachEnvelope {
1107 conversation_id: request.conversation_id,
1108 participant_id: request.participant_id,
1109 capability_generation: request.capability_generation,
1110 detach_attempt_token: request.detach_attempt_token,
1111 }
1112}
1113
1114const fn leave_envelope(request: &LeaveRequest) -> LeaveEnvelope {
1115 LeaveEnvelope {
1116 conversation_id: request.conversation_id,
1117 participant_id: request.participant_id,
1118 capability_generation: request.capability_generation,
1119 leave_attempt_token: request.leave_attempt_token,
1120 }
1121}
1122
1123const fn retired_detach<EF, V, LF>(
1124 tombstone: &RetiredIdentity<EF, V, LF>,
1125 request: &DetachRequest,
1126) -> Retired {
1127 Retired::Participant {
1128 request: ParticipantReferenceEnvelope::Detach(detach_envelope(request)),
1129 retired_generation: tombstone.retired_generation(),
1130 }
1131}
1132
1133const fn retired_leave<EF, V, LF>(
1134 tombstone: &RetiredIdentity<EF, V, LF>,
1135 request: &LeaveRequest,
1136) -> Retired {
1137 Retired::Participant {
1138 request: ParticipantReferenceEnvelope::Leave(leave_envelope(request)),
1139 retired_generation: tombstone.retired_generation(),
1140 }
1141}
1142
1143const fn unknown_detach(request: &DetachRequest) -> ParticipantUnknown {
1144 ParticipantUnknown {
1145 request: ParticipantReferenceEnvelope::Detach(detach_envelope(request)),
1146 }
1147}
1148
1149const fn unknown_leave(request: &LeaveRequest) -> ParticipantUnknown {
1150 ParticipantUnknown {
1151 request: ParticipantReferenceEnvelope::Leave(leave_envelope(request)),
1152 }
1153}
1154
1155const fn no_binding_detach(request: &DetachRequest) -> NoBinding {
1156 NoBinding {
1157 request: BindingRequiredEnvelope::Detach(detach_envelope(request)),
1158 }
1159}
1160
1161const fn no_binding_leave(request: &LeaveRequest) -> NoBinding {
1162 NoBinding {
1163 request: BindingRequiredEnvelope::Leave(leave_envelope(request)),
1164 }
1165}
1166
1167const fn stale_detach<'a, EF, D>(
1168 request: &DetachRequest,
1169 current_generation: Generation,
1170) -> DetachLookupResult<'a, EF, D> {
1171 DetachLookupResult::StaleAuthority(DetachStaleAuthority::Live {
1172 conversation_id: request.conversation_id,
1173 participant_id: request.participant_id,
1174 capability_generation: request.capability_generation,
1175 detach_attempt_token: request.detach_attempt_token,
1176 current_generation,
1177 })
1178}
1179
1180const fn live_leave_stale(
1181 request: &LeaveRequest,
1182 current_generation: Generation,
1183) -> LeaveStaleAuthority {
1184 LeaveStaleAuthority::Live {
1185 conversation_id: request.conversation_id,
1186 participant_id: request.participant_id,
1187 presented_generation: request.capability_generation,
1188 leave_attempt_token: request.leave_attempt_token,
1189 current_generation,
1190 }
1191}