1use alloc::boxed::Box;
9
10use crate::wire::{
11 AttachSecret, ClosureCheckedEnvelope, EnrollmentEnvelope, EnrollmentRequest,
12 EnrollmentResponse, OrderAllocatingEnvelope, SequenceAllocatingEnvelope,
13};
14
15use super::super::{
16 AllocatedParticipantSlot, AttachedRecordPosition, BindingSlotDecision, BindingSlotOccupancy,
17 BindingState, ConnectionConversationCapacityCommit, ConnectionConversationTracking,
18 EnrollmentCapacityCommit, EnrollmentCapacityCounters, EnrollmentCapacityDecision,
19 EnrollmentCommit, EnrollmentCommitError, EnrollmentCommitParameters, EnrollmentFingerprint,
20 EnrollmentLookupResult, EnrollmentTokenPhase, InitialEnrollmentClosureError,
21 InitialEnrollmentClosureInput, InitialEnrollmentClosureProjection, ObserverCheckedOperation,
22 ObserverFloorDecision, ObserverFloorPermit, OrderAdmissionError, OrderAllocation,
23 ParticipantSlotAllocationError, ParticipantSlotAllocatorProof, RemainingClosureDecision,
24 RemainingClosurePermit, SemanticConnectionCapacityDecision, SequenceAdmission,
25 SequenceAdmissionError, admit_sequence, allocate_order, check_observer_floor,
26 commit_enrollment, lookup_enrollment, project_initial_enrollment_closure,
27 select_enrollment_binding_slot, select_enrollment_capacity,
28 select_semantic_connection_capacity,
29};
30
31pub struct InitialEnrollmentOperationInput<'a, EF, V, LF> {
33 request: &'a EnrollmentRequest,
34 token_phase: EnrollmentTokenPhase<'a, EF, V, LF>,
35 lookup_binding: &'a BindingState,
36 connection_tracking: ConnectionConversationTracking,
37 connection_capacity: super::super::CapacityCounter,
38 binding_occupancy: BindingSlotOccupancy,
39 enrollment_capacity: EnrollmentCapacityCounters,
40 closure: InitialEnrollmentClosureInput,
41}
42
43impl<'a, EF, V, LF> InitialEnrollmentOperationInput<'a, EF, V, LF> {
44 #[allow(clippy::too_many_arguments)]
46 #[must_use]
47 pub const fn new(
48 request: &'a EnrollmentRequest,
49 token_phase: EnrollmentTokenPhase<'a, EF, V, LF>,
50 lookup_binding: &'a BindingState,
51 connection_tracking: ConnectionConversationTracking,
52 connection_capacity: super::super::CapacityCounter,
53 binding_occupancy: BindingSlotOccupancy,
54 enrollment_capacity: EnrollmentCapacityCounters,
55 closure: InitialEnrollmentClosureInput,
56 ) -> Self {
57 Self {
58 request,
59 token_phase,
60 lookup_binding,
61 connection_tracking,
62 connection_capacity,
63 binding_occupancy,
64 enrollment_capacity,
65 closure,
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct ReceiptDeadlines {
77 receipt_expires_at: u128,
78 provenance_expires_at: u128,
79}
80
81impl ReceiptDeadlines {
82 pub fn try_from_ttls(
92 now_ms: u64,
93 attach_receipt_ttl_ms: u64,
94 receipt_provenance_ttl_ms: u64,
95 ) -> Result<Self, ReceiptDeadlineError> {
96 if attach_receipt_ttl_ms == 0 {
97 return Err(ReceiptDeadlineError::ZeroAttachReceiptTtl);
98 }
99 if receipt_provenance_ttl_ms == 0 {
100 return Err(ReceiptDeadlineError::ZeroReceiptProvenanceTtl);
101 }
102 if receipt_provenance_ttl_ms < attach_receipt_ttl_ms {
103 return Err(ReceiptDeadlineError::ProvenanceTtlShorterThanReceipt {
104 attach_receipt_ttl_ms,
105 receipt_provenance_ttl_ms,
106 });
107 }
108 let widened_now = u128::from(now_ms);
109 Ok(Self {
110 receipt_expires_at: widened_now + u128::from(attach_receipt_ttl_ms),
111 provenance_expires_at: widened_now + u128::from(receipt_provenance_ttl_ms),
112 })
113 }
114
115 pub const fn try_from_absolute(
127 receipt_expires_at: u128,
128 provenance_expires_at: u128,
129 ) -> Result<Self, ReceiptDeadlineError> {
130 if receipt_expires_at == 0 {
131 return Err(ReceiptDeadlineError::ZeroAbsoluteReceiptDeadline);
132 }
133 if provenance_expires_at < receipt_expires_at {
134 return Err(ReceiptDeadlineError::AbsoluteProvenanceBeforeReceipt);
135 }
136 Ok(Self {
137 receipt_expires_at,
138 provenance_expires_at,
139 })
140 }
141
142 #[must_use]
144 pub const fn receipt_expires_at(self) -> u128 {
145 self.receipt_expires_at
146 }
147
148 #[must_use]
150 pub const fn provenance_expires_at(self) -> u128 {
151 self.provenance_expires_at
152 }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
157pub enum ReceiptDeadlineError {
158 ZeroAttachReceiptTtl,
160 ZeroReceiptProvenanceTtl,
162 ProvenanceTtlShorterThanReceipt {
164 attach_receipt_ttl_ms: u64,
166 receipt_provenance_ttl_ms: u64,
168 },
169 ZeroAbsoluteReceiptDeadline,
171 AbsoluteProvenanceBeforeReceipt,
173}
174
175#[derive(Clone, Debug, PartialEq, Eq)]
177pub struct InitialEnrollmentCommitValues<F> {
178 attach_secret: AttachSecret,
179 deadlines: ReceiptDeadlines,
180 enrollment_fingerprint: EnrollmentFingerprint<F>,
181}
182
183impl<F> InitialEnrollmentCommitValues<F> {
184 #[must_use]
186 pub const fn new(
187 attach_secret: AttachSecret,
188 deadlines: ReceiptDeadlines,
189 enrollment_fingerprint: EnrollmentFingerprint<F>,
190 ) -> Self {
191 Self {
192 attach_secret,
193 deadlines,
194 enrollment_fingerprint,
195 }
196 }
197}
198
199#[derive(Clone, Debug, PartialEq, Eq)]
204pub struct InitialEnrollmentOperationCommit<F> {
205 enrollment: EnrollmentCommit<F>,
206 connection_capacity: ConnectionConversationCapacityCommit,
207 enrollment_capacity: EnrollmentCapacityCommit,
208 order: OrderAllocation,
209 sequence: SequenceAdmission,
210 observer_floor: ObserverFloorPermit,
211 closure_permit: Box<RemainingClosurePermit>,
212 closure_projection: InitialEnrollmentClosureProjection,
213}
214
215impl<F> InitialEnrollmentOperationCommit<F> {
216 #[must_use]
218 pub const fn enrollment(&self) -> &EnrollmentCommit<F> {
219 &self.enrollment
220 }
221
222 #[must_use]
224 pub const fn connection_capacity(&self) -> ConnectionConversationCapacityCommit {
225 self.connection_capacity
226 }
227
228 #[must_use]
230 pub const fn enrollment_capacity(&self) -> EnrollmentCapacityCommit {
231 self.enrollment_capacity
232 }
233
234 #[must_use]
236 pub const fn order(&self) -> OrderAllocation {
237 self.order
238 }
239
240 #[must_use]
242 pub const fn sequence(&self) -> SequenceAdmission {
243 self.sequence
244 }
245
246 #[must_use]
248 pub const fn observer_floor(&self) -> ObserverFloorPermit {
249 self.observer_floor
250 }
251
252 #[must_use]
254 pub const fn closure_permit(&self) -> &RemainingClosurePermit {
255 &self.closure_permit
256 }
257
258 #[must_use]
260 pub const fn closure_projection(&self) -> &InitialEnrollmentClosureProjection {
261 &self.closure_projection
262 }
263
264 #[must_use]
271 pub fn into_enrollment(self) -> EnrollmentCommit<F> {
272 self.enrollment
273 }
274}
275
276#[derive(Clone, Debug, PartialEq, Eq)]
278pub enum InitialEnrollmentOperationFault {
279 Closure(InitialEnrollmentClosureError),
281 Order(OrderAdmissionError),
283 Sequence(SequenceAdmissionError),
285 SlotAllocation(ParticipantSlotAllocationError),
287 AllocatedParticipantMismatch {
289 expected: u64,
291 actual: u64,
293 },
294 AllocatedIdentityLimitMismatch {
296 expected: u64,
298 actual: u64,
300 },
301 Commit(EnrollmentCommitError),
303}
304
305#[derive(Clone, Debug, PartialEq, Eq)]
307pub enum InitialEnrollmentOperationDecision<F> {
308 Respond(EnrollmentResponse),
310 Commit(Box<InitialEnrollmentOperationCommit<F>>),
312 Fault(InitialEnrollmentOperationFault),
314}
315
316struct InitialEnrollmentCapacityPermits {
317 connection: ConnectionConversationCapacityCommit,
318 enrollment: EnrollmentCapacityCommit,
319}
320
321struct OrderedInitialEnrollment {
322 projection: InitialEnrollmentClosureProjection,
323 order: OrderAllocation,
324}
325
326struct ClosedInitialEnrollment {
327 projection: InitialEnrollmentClosureProjection,
328 order: OrderAllocation,
329 sequence: SequenceAdmission,
330 observer_floor: ObserverFloorPermit,
331 closure_permit: Box<RemainingClosurePermit>,
332}
333
334enum InitialEnrollmentGateFailure {
335 Respond(Box<EnrollmentResponse>),
336 Fault(Box<InitialEnrollmentOperationFault>),
337}
338
339impl InitialEnrollmentGateFailure {
340 fn respond(value: EnrollmentResponse) -> Self {
341 Self::Respond(Box::new(value))
342 }
343
344 fn fault(value: InitialEnrollmentOperationFault) -> Self {
345 Self::Fault(Box::new(value))
346 }
347
348 fn into_decision<F>(self) -> InitialEnrollmentOperationDecision<F> {
349 match self {
350 Self::Respond(value) => InitialEnrollmentOperationDecision::Respond(*value),
351 Self::Fault(value) => InitialEnrollmentOperationDecision::Fault(*value),
352 }
353 }
354}
355
356#[must_use]
364pub fn apply_initial_enrollment<EF, V, LF, F, P, A, M>(
365 input: &InitialEnrollmentOperationInput<'_, EF, V, LF>,
366 mint_commit_values: M,
367 allocate_participant: A,
368) -> InitialEnrollmentOperationDecision<F>
369where
370 P: ParticipantSlotAllocatorProof,
371 A: FnOnce() -> Result<AllocatedParticipantSlot<P>, ParticipantSlotAllocationError>,
372 M: FnOnce() -> InitialEnrollmentCommitValues<F>,
373{
374 let envelope = enrollment_envelope(input.request);
375 if let Some(response) = initial_enrollment_lookup_response(input) {
376 return InitialEnrollmentOperationDecision::Respond(response);
377 }
378 let capacity = match admit_initial_enrollment_capacity(input, &envelope) {
379 Ok(value) => value,
380 Err(error) => return error.into_decision(),
381 };
382 let ordered = match plan_initial_enrollment_order(&input.closure, &envelope) {
383 Ok(value) => value,
384 Err(error) => return error.into_decision(),
385 };
386 let closed = match close_initial_enrollment(ordered, &envelope) {
387 Ok(value) => value,
388 Err(error) => return error.into_decision(),
389 };
390 commit_initial_enrollment(
391 input.request,
392 &capacity,
393 closed,
394 mint_commit_values,
395 allocate_participant,
396 )
397}
398
399fn initial_enrollment_lookup_response<EF, V, LF>(
400 input: &InitialEnrollmentOperationInput<'_, EF, V, LF>,
401) -> Option<EnrollmentResponse> {
402 match lookup_enrollment(input.token_phase, input.lookup_binding, input.request) {
403 EnrollmentLookupResult::Retired(value) => Some(EnrollmentResponse::from_retired(value)),
404 EnrollmentLookupResult::Bound(value) => Some(EnrollmentResponse::from_bound(value)),
405 EnrollmentLookupResult::UnboundReceipt(value) => {
406 Some(EnrollmentResponse::from_unbound_receipt(value))
407 }
408 EnrollmentLookupResult::ReceiptExpired(value) => {
409 Some(EnrollmentResponse::from_receipt_expired(value))
410 }
411 EnrollmentLookupResult::EnrollmentKnown(value) => {
412 Some(EnrollmentResponse::enrollment_known(value))
413 }
414 EnrollmentLookupResult::AuthorizedNew => None,
415 }
416}
417
418fn admit_initial_enrollment_capacity<EF, V, LF>(
419 input: &InitialEnrollmentOperationInput<'_, EF, V, LF>,
420 envelope: &EnrollmentEnvelope,
421) -> Result<InitialEnrollmentCapacityPermits, InitialEnrollmentGateFailure> {
422 let connection = match select_semantic_connection_capacity(
423 input.connection_tracking,
424 input.connection_capacity,
425 ) {
426 SemanticConnectionCapacityDecision::Commit(value) => value,
427 SemanticConnectionCapacityDecision::Respond { limit } => {
428 return Err(InitialEnrollmentGateFailure::respond(
429 EnrollmentResponse::connection_conversation_capacity_exceeded(
430 envelope.clone(),
431 limit,
432 ),
433 ));
434 }
435 };
436 if let BindingSlotDecision::Respond(value) =
437 select_enrollment_binding_slot(input.request, input.binding_occupancy)
438 {
439 return Err(InitialEnrollmentGateFailure::respond(value));
440 }
441 let enrollment = match select_enrollment_capacity(input.request, input.enrollment_capacity) {
442 EnrollmentCapacityDecision::Commit(value) => value,
443 EnrollmentCapacityDecision::Respond(value) => {
444 return Err(InitialEnrollmentGateFailure::respond(value));
445 }
446 };
447 Ok(InitialEnrollmentCapacityPermits {
448 connection,
449 enrollment,
450 })
451}
452
453fn plan_initial_enrollment_order(
454 closure: &InitialEnrollmentClosureInput,
455 envelope: &EnrollmentEnvelope,
456) -> Result<OrderedInitialEnrollment, InitialEnrollmentGateFailure> {
457 let projection = project_initial_enrollment_closure(*closure).map_err(|error| {
458 InitialEnrollmentGateFailure::fault(InitialEnrollmentOperationFault::Closure(error))
459 })?;
460 let order_plan = projection
461 .plan_order()
462 .map_err(initial_enrollment_order_failure)?;
463 let order = allocate_order(
464 OrderAllocatingEnvelope::Enrollment(envelope.clone()),
465 projection.current_order(),
466 order_plan,
467 )
468 .map_err(initial_enrollment_order_failure)?;
469 Ok(OrderedInitialEnrollment { projection, order })
470}
471
472fn initial_enrollment_order_failure(error: OrderAdmissionError) -> InitialEnrollmentGateFailure {
473 match error {
474 OrderAdmissionError::Exhausted(value) => InitialEnrollmentGateFailure::respond(
475 EnrollmentResponse::from_conversation_order_exhausted(value),
476 ),
477 other => InitialEnrollmentGateFailure::fault(InitialEnrollmentOperationFault::Order(other)),
478 }
479}
480
481fn close_initial_enrollment(
482 ordered: OrderedInitialEnrollment,
483 envelope: &EnrollmentEnvelope,
484) -> Result<ClosedInitialEnrollment, InitialEnrollmentGateFailure> {
485 let sequence_plan = ordered
486 .projection
487 .plan_sequence()
488 .map_err(initial_enrollment_sequence_failure)?;
489 let sequence = admit_sequence(
490 SequenceAllocatingEnvelope::Enrollment(envelope.clone()),
491 sequence_plan,
492 )
493 .map_err(initial_enrollment_sequence_failure)?;
494 let observer_floor = match check_observer_floor(
495 ObserverCheckedOperation::Enrollment(envelope.clone()),
496 ordered.projection.observer_progress(),
497 ordered.projection.resulting_floor(),
498 ) {
499 ObserverFloorDecision::Eligible(value) => value,
500 ObserverFloorDecision::Respond(value) => {
501 return Err(InitialEnrollmentGateFailure::respond(
502 EnrollmentResponse::from_observer_backpressure(value),
503 ));
504 }
505 };
506 let closure_permit = match ordered
507 .projection
508 .remaining_closure_decision(&ClosureCheckedEnvelope::Enrollment(envelope.clone()))
509 {
510 RemainingClosureDecision::Eligible(value) => value,
511 RemainingClosureDecision::Respond(value) => {
512 return Err(InitialEnrollmentGateFailure::respond(
513 EnrollmentResponse::from_marker_closure_capacity_exceeded(value),
514 ));
515 }
516 };
517 Ok(ClosedInitialEnrollment {
518 projection: ordered.projection,
519 order: ordered.order,
520 sequence,
521 observer_floor,
522 closure_permit,
523 })
524}
525
526fn initial_enrollment_sequence_failure(
527 error: SequenceAdmissionError,
528) -> InitialEnrollmentGateFailure {
529 match error {
530 SequenceAdmissionError::Exhausted(value) => InitialEnrollmentGateFailure::respond(
531 EnrollmentResponse::from_conversation_sequence_exhausted(value),
532 ),
533 other => {
534 InitialEnrollmentGateFailure::fault(InitialEnrollmentOperationFault::Sequence(other))
535 }
536 }
537}
538
539fn commit_initial_enrollment<F, P, A, M>(
540 request: &EnrollmentRequest,
541 capacity: &InitialEnrollmentCapacityPermits,
542 closed: ClosedInitialEnrollment,
543 mint_commit_values: M,
544 allocate_participant: A,
545) -> InitialEnrollmentOperationDecision<F>
546where
547 P: ParticipantSlotAllocatorProof,
548 A: FnOnce() -> Result<AllocatedParticipantSlot<P>, ParticipantSlotAllocationError>,
549 M: FnOnce() -> InitialEnrollmentCommitValues<F>,
550{
551 let participant_slot = match allocate_participant() {
552 Ok(value) => value,
553 Err(error) => {
554 return InitialEnrollmentOperationDecision::Fault(
555 InitialEnrollmentOperationFault::SlotAllocation(error),
556 );
557 }
558 };
559 if participant_slot.participant_id() != closed.projection.participant_index() {
560 return InitialEnrollmentOperationDecision::Fault(
561 InitialEnrollmentOperationFault::AllocatedParticipantMismatch {
562 expected: closed.projection.participant_index(),
563 actual: participant_slot.participant_id(),
564 },
565 );
566 }
567 if participant_slot.identity_limit() != closed.projection.identity_slots() {
568 return InitialEnrollmentOperationDecision::Fault(
569 InitialEnrollmentOperationFault::AllocatedIdentityLimitMismatch {
570 expected: closed.projection.identity_slots(),
571 actual: participant_slot.identity_limit(),
572 },
573 );
574 }
575 let commit_values = mint_commit_values();
576 let enrollment = match commit_enrollment(
577 request,
578 EnrollmentCommitParameters {
579 allocated_slot: participant_slot,
580 attach_secret: commit_values.attach_secret,
581 origin_binding_epoch: closed.projection.binding_epoch(),
582 attached_position: AttachedRecordPosition::new(
583 closed.order.major(),
584 closed.sequence.resulting().high_watermark(),
585 ),
586 receipt_expires_at: commit_values.deadlines.receipt_expires_at(),
587 provenance_expires_at: commit_values.deadlines.provenance_expires_at(),
588 enrollment_fingerprint: commit_values.enrollment_fingerprint,
589 },
590 ) {
591 Ok(value) => value,
592 Err(error) => {
593 return InitialEnrollmentOperationDecision::Fault(
594 InitialEnrollmentOperationFault::Commit(error),
595 );
596 }
597 };
598
599 InitialEnrollmentOperationDecision::Commit(Box::new(InitialEnrollmentOperationCommit {
600 enrollment,
601 connection_capacity: capacity.connection,
602 enrollment_capacity: capacity.enrollment,
603 order: closed.order,
604 sequence: closed.sequence,
605 observer_floor: closed.observer_floor,
606 closure_permit: closed.closure_permit,
607 closure_projection: closed.projection,
608 }))
609}
610
611const fn enrollment_envelope(request: &EnrollmentRequest) -> EnrollmentEnvelope {
612 EnrollmentEnvelope {
613 conversation_id: request.conversation_id,
614 enrollment_token: request.enrollment_token,
615 }
616}