1use core::num::NonZeroU64;
2
3use crate::wire::{
4 AttachEnvelope, CredentialAttachRequest, CredentialAttachResponse, EnrollmentEnvelope,
5 EnrollmentRequest, EnrollmentResponse, IdentityCapacityExceeded, IdentityCapacityScope,
6 ParticipantId,
7};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum CapacityCounterInvariantError {
12 ZeroLimit,
14 OccupiedExceedsLimit {
16 occupied: u64,
18 limit: u64,
20 },
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct CapacityCounter {
26 limit: NonZeroU64,
27 occupied: u64,
28}
29
30impl CapacityCounter {
31 pub const fn try_new(limit: u64, occupied: u64) -> Result<Self, CapacityCounterInvariantError> {
39 let Some(limit) = NonZeroU64::new(limit) else {
40 return Err(CapacityCounterInvariantError::ZeroLimit);
41 };
42 if occupied > limit.get() {
43 return Err(CapacityCounterInvariantError::OccupiedExceedsLimit {
44 occupied,
45 limit: limit.get(),
46 });
47 }
48 Ok(Self { limit, occupied })
49 }
50
51 #[must_use]
53 pub const fn limit(self) -> u64 {
54 self.limit.get()
55 }
56
57 #[must_use]
59 pub const fn occupied(self) -> u64 {
60 self.occupied
61 }
62
63 #[must_use]
65 pub const fn is_full(self) -> bool {
66 self.occupied == self.limit.get()
67 }
68
69 const fn incremented(self) -> Option<Self> {
70 if self.is_full() {
71 return None;
72 }
73 Some(Self {
74 limit: self.limit,
75 occupied: self.occupied + 1,
76 })
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub enum FreshParticipantCapacityCounterInvariantError {
83 Capacity(CapacityCounterInvariantError),
85 Nonempty {
87 occupied: u64,
89 },
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub struct FreshParticipantCapacityCounter {
98 counter: CapacityCounter,
99}
100
101impl FreshParticipantCapacityCounter {
102 pub const fn try_new(
111 limit: u64,
112 occupied: u64,
113 ) -> Result<Self, FreshParticipantCapacityCounterInvariantError> {
114 let counter = match CapacityCounter::try_new(limit, occupied) {
115 Ok(counter) => counter,
116 Err(error) => {
117 return Err(FreshParticipantCapacityCounterInvariantError::Capacity(
118 error,
119 ));
120 }
121 };
122 if occupied != 0 {
123 return Err(FreshParticipantCapacityCounterInvariantError::Nonempty { occupied });
124 }
125 Ok(Self { counter })
126 }
127
128 #[must_use]
130 pub const fn limit(self) -> u64 {
131 self.counter.limit()
132 }
133
134 #[must_use]
136 pub const fn occupied(self) -> u64 {
137 self.counter.occupied()
138 }
139
140 const fn reserved(self) -> CapacityCounter {
141 CapacityCounter {
142 limit: self.counter.limit,
143 occupied: 1,
144 }
145 }
146
147 const fn unfilled(self) -> CapacityCounter {
154 self.counter
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
166pub enum ParticipantWindowAdmission {
167 Landed,
169 Displaced,
172}
173
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
180pub struct ParticipantWindowCommit {
181 admission: ParticipantWindowAdmission,
182 resulting: CapacityCounter,
183}
184
185impl ParticipantWindowCommit {
186 #[must_use]
188 pub const fn admission(self) -> ParticipantWindowAdmission {
189 self.admission
190 }
191
192 #[must_use]
195 pub const fn displaced(self) -> bool {
196 matches!(self.admission, ParticipantWindowAdmission::Displaced)
197 }
198
199 #[must_use]
201 pub const fn resulting(self) -> CapacityCounter {
202 self.resulting
203 }
204}
205
206#[must_use]
214pub const fn select_participant_window(current: CapacityCounter) -> ParticipantWindowCommit {
215 match current.incremented() {
216 Some(resulting) => ParticipantWindowCommit {
217 admission: ParticipantWindowAdmission::Landed,
218 resulting,
219 },
220 None => ParticipantWindowCommit {
221 admission: ParticipantWindowAdmission::Displaced,
222 resulting: current,
223 },
224 }
225}
226
227#[derive(Clone, Copy, Debug, PartialEq, Eq)]
229pub enum ConnectionConversationTracking {
230 AlreadyTracked,
232 Untracked,
234}
235
236#[derive(Clone, Copy, Debug, PartialEq, Eq)]
238pub struct ConnectionConversationCapacityCommit {
239 resulting: CapacityCounter,
240 newly_tracked: bool,
241}
242
243impl ConnectionConversationCapacityCommit {
244 #[must_use]
246 pub const fn resulting(self) -> CapacityCounter {
247 self.resulting
248 }
249
250 #[must_use]
252 pub const fn newly_tracked(self) -> bool {
253 self.newly_tracked
254 }
255}
256
257#[derive(Clone, Debug, PartialEq, Eq)]
264pub enum SemanticConnectionCapacityDecision {
265 Commit(ConnectionConversationCapacityCommit),
267 Respond {
269 limit: u64,
271 },
272}
273
274#[must_use]
281pub const fn select_semantic_connection_capacity(
282 tracking: ConnectionConversationTracking,
283 current: CapacityCounter,
284) -> SemanticConnectionCapacityDecision {
285 match tracking {
286 ConnectionConversationTracking::AlreadyTracked => {
287 SemanticConnectionCapacityDecision::Commit(ConnectionConversationCapacityCommit {
288 resulting: current,
289 newly_tracked: false,
290 })
291 }
292 ConnectionConversationTracking::Untracked => {
293 let Some(resulting) = current.incremented() else {
294 return SemanticConnectionCapacityDecision::Respond {
295 limit: current.limit(),
296 };
297 };
298 SemanticConnectionCapacityDecision::Commit(ConnectionConversationCapacityCommit {
299 resulting,
300 newly_tracked: true,
301 })
302 }
303 }
304}
305
306#[derive(Clone, Copy, Debug, PartialEq, Eq)]
308pub enum BindingSlotOccupancy {
309 Empty,
311 Occupied {
313 participant_id: ParticipantId,
315 },
316}
317
318#[derive(Clone, Debug, PartialEq, Eq)]
321pub enum BindingSlotDecision<R> {
322 Available,
324 Respond(R),
326}
327
328#[must_use]
330pub const fn select_enrollment_binding_slot(
331 request: &EnrollmentRequest,
332 occupancy: BindingSlotOccupancy,
333) -> BindingSlotDecision<EnrollmentResponse> {
334 match occupancy {
335 BindingSlotOccupancy::Empty => BindingSlotDecision::Available,
336 BindingSlotOccupancy::Occupied { .. } => BindingSlotDecision::Respond(
337 EnrollmentResponse::connection_conversation_binding_occupied(&enrollment_envelope(
338 request,
339 )),
340 ),
341 }
342}
343
344#[must_use]
347pub const fn select_credential_attach_binding_slot(
348 request: &CredentialAttachRequest,
349 occupancy: BindingSlotOccupancy,
350) -> BindingSlotDecision<CredentialAttachResponse> {
351 match occupancy {
352 BindingSlotOccupancy::Empty => BindingSlotDecision::Available,
353 BindingSlotOccupancy::Occupied { participant_id }
354 if participant_id == request.participant_id =>
355 {
356 BindingSlotDecision::Available
357 }
358 BindingSlotOccupancy::Occupied { .. } => BindingSlotDecision::Respond(
359 CredentialAttachResponse::connection_conversation_binding_occupied(&attach_envelope(
360 request,
361 )),
362 ),
363 }
364}
365
366#[derive(Clone, Copy, Debug, PartialEq, Eq)]
382pub struct EnrollmentCapacityCounters {
383 identity_server: CapacityCounter,
384 identity_conversation: CapacityCounter,
385 live_receipt_participant: FreshParticipantCapacityCounter,
386 provenance_participant: FreshParticipantCapacityCounter,
387}
388
389impl EnrollmentCapacityCounters {
390 #[must_use]
392 pub const fn new(
393 identity_server: CapacityCounter,
394 identity_conversation: CapacityCounter,
395 live_receipt_participant: FreshParticipantCapacityCounter,
396 provenance_participant: FreshParticipantCapacityCounter,
397 ) -> Self {
398 Self {
399 identity_server,
400 identity_conversation,
401 live_receipt_participant,
402 provenance_participant,
403 }
404 }
405
406 #[must_use]
408 pub const fn identity_server(self) -> CapacityCounter {
409 self.identity_server
410 }
411
412 #[must_use]
414 pub const fn identity_conversation(self) -> CapacityCounter {
415 self.identity_conversation
416 }
417
418 #[must_use]
420 pub const fn live_receipt_participant(self) -> FreshParticipantCapacityCounter {
421 self.live_receipt_participant
422 }
423
424 #[must_use]
426 pub const fn provenance_participant(self) -> FreshParticipantCapacityCounter {
427 self.provenance_participant
428 }
429}
430
431#[derive(Clone, Copy, Debug, PartialEq, Eq)]
433pub struct ResultingEnrollmentCapacityCounters {
434 identity_server: CapacityCounter,
435 identity_conversation: CapacityCounter,
436 live_receipt_participant: CapacityCounter,
437 provenance_participant: CapacityCounter,
438}
439
440impl ResultingEnrollmentCapacityCounters {
441 #[must_use]
443 pub const fn identity_server(self) -> CapacityCounter {
444 self.identity_server
445 }
446
447 #[must_use]
449 pub const fn identity_conversation(self) -> CapacityCounter {
450 self.identity_conversation
451 }
452
453 #[must_use]
455 pub const fn live_receipt_participant(self) -> CapacityCounter {
456 self.live_receipt_participant
457 }
458
459 #[must_use]
465 pub const fn provenance_participant(self) -> CapacityCounter {
466 self.provenance_participant
467 }
468}
469
470#[derive(Clone, Copy, Debug, PartialEq, Eq)]
472pub struct EnrollmentCapacityCommit {
473 resulting: ResultingEnrollmentCapacityCounters,
474}
475
476impl EnrollmentCapacityCommit {
477 #[must_use]
479 pub const fn resulting(self) -> ResultingEnrollmentCapacityCounters {
480 self.resulting
481 }
482}
483
484#[derive(Clone, Debug, PartialEq, Eq)]
486pub enum EnrollmentCapacityDecision {
487 Commit(EnrollmentCapacityCommit),
489 Respond(EnrollmentResponse),
492}
493
494#[must_use]
501pub const fn select_enrollment_capacity(
502 request: &EnrollmentRequest,
503 current: EnrollmentCapacityCounters,
504) -> EnrollmentCapacityDecision {
505 let Some(identity_server) = current.identity_server.incremented() else {
506 return enrollment_identity_refusal(
507 request,
508 IdentityCapacityScope::Server,
509 current.identity_server,
510 );
511 };
512 let Some(identity_conversation) = current.identity_conversation.incremented() else {
513 return enrollment_identity_refusal(
514 request,
515 IdentityCapacityScope::Conversation,
516 current.identity_conversation,
517 );
518 };
519
520 EnrollmentCapacityDecision::Commit(EnrollmentCapacityCommit {
521 resulting: ResultingEnrollmentCapacityCounters {
522 identity_server,
523 identity_conversation,
524 live_receipt_participant: current.live_receipt_participant.reserved(),
525 provenance_participant: current.provenance_participant.unfilled(),
526 },
527 })
528}
529
530#[derive(Clone, Copy, Debug, PartialEq, Eq)]
536pub struct CredentialAttachCapacityCounters {
537 live_receipt_participant: CapacityCounter,
538 provenance_participant: CapacityCounter,
539}
540
541impl CredentialAttachCapacityCounters {
542 #[must_use]
544 pub const fn new(
545 live_receipt_participant: CapacityCounter,
546 provenance_participant: CapacityCounter,
547 ) -> Self {
548 Self {
549 live_receipt_participant,
550 provenance_participant,
551 }
552 }
553
554 #[must_use]
556 pub const fn live_receipt_participant(self) -> CapacityCounter {
557 self.live_receipt_participant
558 }
559
560 #[must_use]
562 pub const fn provenance_participant(self) -> CapacityCounter {
563 self.provenance_participant
564 }
565}
566
567#[derive(Clone, Copy, Debug, PartialEq, Eq)]
573pub struct CredentialAttachCapacityCommit {
574 live_receipt_participant: ParticipantWindowCommit,
575 provenance_participant: ParticipantWindowCommit,
576}
577
578impl CredentialAttachCapacityCommit {
579 #[must_use]
581 pub const fn live_receipt_participant(self) -> ParticipantWindowCommit {
582 self.live_receipt_participant
583 }
584
585 #[must_use]
587 pub const fn provenance_participant(self) -> ParticipantWindowCommit {
588 self.provenance_participant
589 }
590}
591
592#[must_use]
599pub const fn select_credential_attach_capacity(
600 current: CredentialAttachCapacityCounters,
601) -> CredentialAttachCapacityCommit {
602 CredentialAttachCapacityCommit {
603 live_receipt_participant: select_participant_window(current.live_receipt_participant),
604 provenance_participant: select_participant_window(current.provenance_participant),
605 }
606}
607
608const fn enrollment_identity_refusal(
609 request: &EnrollmentRequest,
610 scope: IdentityCapacityScope,
611 counter: CapacityCounter,
612) -> EnrollmentCapacityDecision {
613 EnrollmentCapacityDecision::Respond(EnrollmentResponse::identity_capacity_exceeded(
614 IdentityCapacityExceeded {
615 request: enrollment_envelope(request),
616 scope,
617 limit: counter.limit(),
618 occupied: counter.occupied(),
619 },
620 ))
621}
622
623const fn enrollment_envelope(request: &EnrollmentRequest) -> EnrollmentEnvelope {
624 EnrollmentEnvelope {
625 conversation_id: request.conversation_id,
626 enrollment_token: request.enrollment_token,
627 }
628}
629
630const fn attach_envelope(request: &CredentialAttachRequest) -> AttachEnvelope {
631 AttachEnvelope {
632 conversation_id: request.conversation_id,
633 participant_id: request.participant_id,
634 capability_generation: request.capability_generation,
635 attach_attempt_token: request.attach_attempt_token,
636 accept_marker_delivery_seq: request.accept_marker_delivery_seq,
637 }
638}