1use alloc::boxed::Box;
2
3use crate::wire::{
4 AckCommitted, BindingEpoch, ConversationId, DeliverySeq, Generation, ParticipantAck,
5 ParticipantAckResponse, ParticipantId,
6};
7
8use super::super::{
9 BindingRequiredLookupResult, BindingState, CumulativeAckAuthorizationError,
10 CumulativeAckOutcome, LiveMember, NonzeroDebtCursorEpisode, ObserverProgressProjection,
11 ParticipantBindingRequest, PresentedIdentity, RecipientAckObligations,
12 RecipientAckObligationsContextError, SealedBindingFateToken, lookup_binding_required,
13 membership::{LiveMemberCursorUpdate, LiveMemberCursorUpdateError},
14};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum NonzeroAckEpisodePosition {
19 Before,
21 Resulting,
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct NonzeroParticipantAckCommit {
34 outcome: AckCommitted,
35 from_cursor: DeliverySeq,
36 binding_epoch: BindingEpoch,
37 before_episode: NonzeroDebtCursorEpisode,
38 resulting_episode: NonzeroDebtCursorEpisode,
39 cursor_update: LiveMemberCursorUpdate,
40}
41
42impl NonzeroParticipantAckCommit {
43 #[must_use]
45 pub const fn outcome(&self) -> &AckCommitted {
46 &self.outcome
47 }
48
49 #[must_use]
51 pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
52 let request = self.outcome.request();
53 ObserverProgressProjection::new(request.conversation_id, request.through_seq)
54 }
55
56 #[must_use]
58 pub const fn resulting_episode(&self) -> &NonzeroDebtCursorEpisode {
59 &self.resulting_episode
60 }
61
62 pub fn progress_binding_fate_token(
69 &self,
70 token: SealedBindingFateToken,
71 ) -> Result<SealedBindingFateToken, Box<SealedBindingFateToken>> {
72 let request = self.outcome.request();
73 token.participant_ack_progressed(
74 request.conversation_id,
75 request.participant_id,
76 self.binding_epoch,
77 self.from_cursor,
78 request.through_seq,
79 )
80 }
81
82 pub fn apply_to<F>(
95 self,
96 member: &mut LiveMember<F>,
97 episode: &mut NonzeroDebtCursorEpisode,
98 ) -> Result<AckCommitted, NonzeroParticipantAckCommitError> {
99 let request = self.outcome.request();
100 if member.conversation_id() != request.conversation_id {
101 return Err(NonzeroParticipantAckCommitError::Conversation {
102 expected: request.conversation_id,
103 actual: member.conversation_id(),
104 });
105 }
106 if member.participant_id() != request.participant_id {
107 return Err(NonzeroParticipantAckCommitError::Participant {
108 expected: request.participant_id,
109 actual: member.participant_id(),
110 });
111 }
112 if member.generation() != request.capability_generation {
113 return Err(NonzeroParticipantAckCommitError::Generation {
114 expected: request.capability_generation,
115 actual: member.generation(),
116 });
117 }
118
119 let (position, expected_cursor) = if *episode == self.before_episode {
120 (NonzeroAckEpisodePosition::Before, self.from_cursor)
121 } else if *episode == self.resulting_episode {
122 (NonzeroAckEpisodePosition::Resulting, request.through_seq)
123 } else {
124 return Err(NonzeroParticipantAckCommitError::EpisodePrestate);
125 };
126 if member.cursor() != expected_cursor {
127 return Err(NonzeroParticipantAckCommitError::AggregateCursorPrestate {
128 episode_position: position,
129 expected_cursor,
130 actual_cursor: member.cursor(),
131 });
132 }
133
134 member
135 .apply_cursor_update(self.cursor_update)
136 .map_err(NonzeroParticipantAckCommitError::from_member_error)?;
137 if position == NonzeroAckEpisodePosition::Before {
138 *episode = self.resulting_episode;
139 }
140 Ok(self.outcome)
141 }
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub enum NonzeroParticipantAckCommitError {
147 Conversation {
149 expected: ConversationId,
151 actual: ConversationId,
153 },
154 Participant {
156 expected: ParticipantId,
158 actual: ParticipantId,
160 },
161 Generation {
163 expected: Generation,
165 actual: Generation,
167 },
168 EpisodePrestate,
170 AggregateCursorPrestate {
172 episode_position: NonzeroAckEpisodePosition,
174 expected_cursor: DeliverySeq,
176 actual_cursor: DeliverySeq,
178 },
179 NonAdvancing {
181 from_cursor: DeliverySeq,
183 resulting_cursor: DeliverySeq,
185 },
186 CursorPrestate {
188 expected_from_cursor: DeliverySeq,
190 resulting_cursor: DeliverySeq,
192 actual_cursor: DeliverySeq,
194 },
195}
196
197impl NonzeroParticipantAckCommitError {
198 const fn from_member_error(error: LiveMemberCursorUpdateError) -> Self {
199 match error {
200 LiveMemberCursorUpdateError::Conversation { expected, actual } => {
201 Self::Conversation { expected, actual }
202 }
203 LiveMemberCursorUpdateError::Participant { expected, actual } => {
204 Self::Participant { expected, actual }
205 }
206 LiveMemberCursorUpdateError::Generation { expected, actual } => {
207 Self::Generation { expected, actual }
208 }
209 LiveMemberCursorUpdateError::NonAdvancing {
210 from_cursor,
211 resulting_cursor,
212 } => Self::NonAdvancing {
213 from_cursor,
214 resulting_cursor,
215 },
216 LiveMemberCursorUpdateError::CursorPrestate {
217 expected_from_cursor,
218 resulting_cursor,
219 actual_cursor,
220 } => Self::CursorPrestate {
221 expected_from_cursor,
222 resulting_cursor,
223 actual_cursor,
224 },
225 }
226 }
227}
228
229#[derive(Clone, Copy, Debug, PartialEq, Eq)]
231pub enum NonzeroParticipantAckInvariantError {
232 Conversation {
234 member: ConversationId,
236 episode: ConversationId,
238 },
239 ParticipantMissing {
241 participant_id: ParticipantId,
243 },
244 Generation {
246 member: Generation,
248 episode: Generation,
250 },
251 Cursor {
253 member: DeliverySeq,
255 episode: DeliverySeq,
257 },
258 BindingEpoch {
260 active: BindingEpoch,
262 episode: BindingEpoch,
264 },
265 EpisodeTransition(CumulativeAckAuthorizationError),
267}
268
269#[derive(Clone, Debug, PartialEq, Eq)]
271pub enum NonzeroParticipantAckDecision {
272 Respond(ParticipantAckResponse),
274 Invariant(NonzeroParticipantAckInvariantError),
276 Commit(Box<NonzeroParticipantAckCommit>),
278}
279
280#[must_use]
289pub fn apply_nonzero_participant_ack<EF, V, LF>(
290 presented_identity: PresentedIdentity<'_, EF, V, LF>,
291 binding: &BindingState,
292 receiving_binding_epoch: BindingEpoch,
293 request: &ParticipantAck,
294 contiguously_available_through: DeliverySeq,
295 episode: &NonzeroDebtCursorEpisode,
296) -> NonzeroParticipantAckDecision {
297 apply_nonzero_participant_ack_by_availability(
298 presented_identity,
299 binding,
300 receiving_binding_epoch,
301 request,
302 &AckAvailability::Contiguous(contiguously_available_through),
303 episode,
304 )
305}
306
307#[must_use]
313pub fn apply_nonzero_participant_ack_with_obligations<EF, V, LF>(
314 presented_identity: PresentedIdentity<'_, EF, V, LF>,
315 binding: &BindingState,
316 receiving_binding_epoch: BindingEpoch,
317 request: &ParticipantAck,
318 obligations: &RecipientAckObligations,
319 episode: &NonzeroDebtCursorEpisode,
320) -> NonzeroParticipantAckDecision {
321 apply_nonzero_participant_ack_by_availability(
322 presented_identity,
323 binding,
324 receiving_binding_epoch,
325 request,
326 &AckAvailability::Obligations(obligations),
327 episode,
328 )
329}
330
331pub fn scalar_audit_for_recipient_endpoint(
341 obligations: &RecipientAckObligations,
342 participant_id: ParticipantId,
343 acknowledged_through: DeliverySeq,
344 endpoint: DeliverySeq,
345 scalar_audit: DeliverySeq,
346) -> Result<Option<DeliverySeq>, RecipientAckObligationsContextError> {
347 obligations
348 .contains_endpoint(participant_id, acknowledged_through, endpoint)
349 .map(|testified| testified.then_some(scalar_audit))
350}
351
352enum AckAvailability<'a> {
353 Contiguous(DeliverySeq),
354 Obligations(&'a RecipientAckObligations),
355}
356
357fn apply_nonzero_participant_ack_by_availability<EF, V, LF>(
358 presented_identity: PresentedIdentity<'_, EF, V, LF>,
359 binding: &BindingState,
360 receiving_binding_epoch: BindingEpoch,
361 request: &ParticipantAck,
362 availability: &AckAvailability<'_>,
363 episode: &NonzeroDebtCursorEpisode,
364) -> NonzeroParticipantAckDecision {
365 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
366 let (member, active_binding) = match lookup_binding_required(
367 presented_identity,
368 binding,
369 Some(receiving_binding_epoch),
370 &lookup_request,
371 ) {
372 BindingRequiredLookupResult::Retired(outcome) => {
373 return NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::from_retired(
374 outcome,
375 ));
376 }
377 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
378 return NonzeroParticipantAckDecision::Respond(
379 ParticipantAckResponse::from_participant_unknown(outcome),
380 );
381 }
382 BindingRequiredLookupResult::StaleAuthority(outcome) => {
383 return NonzeroParticipantAckDecision::Respond(
384 ParticipantAckResponse::from_stale_authority(outcome),
385 );
386 }
387 BindingRequiredLookupResult::NoBinding(outcome) => {
388 return NonzeroParticipantAckDecision::Respond(
389 ParticipantAckResponse::from_no_binding(outcome),
390 );
391 }
392 BindingRequiredLookupResult::Authorized { member, binding } => (member, binding),
393 };
394
395 if let Err(error) = validate_aggregate(member, active_binding.binding_epoch, episode) {
396 return NonzeroParticipantAckDecision::Invariant(error);
397 }
398
399 let mut resulting_episode = episode.clone();
400 let selected = match availability {
401 AckAvailability::Contiguous(available_through) => resulting_episode.acknowledge(
402 member.participant_id(),
403 active_binding.binding_epoch,
404 request,
405 *available_through,
406 ),
407 AckAvailability::Obligations(obligations) => resulting_episode
408 .acknowledge_with_obligations(
409 member.participant_id(),
410 active_binding.binding_epoch,
411 request,
412 obligations,
413 ),
414 };
415 let outcome = match selected {
416 Ok(outcome) => outcome,
417 Err(error) => {
418 return NonzeroParticipantAckDecision::Invariant(
419 NonzeroParticipantAckInvariantError::EpisodeTransition(error),
420 );
421 }
422 };
423 match outcome {
424 CumulativeAckOutcome::Committed(outcome) => {
425 NonzeroParticipantAckDecision::Commit(Box::new(NonzeroParticipantAckCommit {
426 cursor_update: LiveMemberCursorUpdate::new(
427 request.conversation_id,
428 request.participant_id,
429 request.capability_generation,
430 member.cursor(),
431 request.through_seq,
432 ),
433 outcome,
434 binding_epoch: active_binding.binding_epoch,
435 from_cursor: member.cursor(),
436 before_episode: episode.clone(),
437 resulting_episode,
438 }))
439 }
440 CumulativeAckOutcome::NoOp(outcome) => {
441 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::from_ack_no_op(outcome))
442 }
443 CumulativeAckOutcome::Gap(outcome) => {
444 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::ack_gap(outcome))
445 }
446 CumulativeAckOutcome::Regression(outcome) => {
447 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::ack_regression(outcome))
448 }
449 }
450}
451
452fn validate_aggregate<F>(
453 member: &LiveMember<F>,
454 active_binding_epoch: BindingEpoch,
455 episode: &NonzeroDebtCursorEpisode,
456) -> Result<(), NonzeroParticipantAckInvariantError> {
457 if member.conversation_id() != episode.conversation_id() {
458 return Err(NonzeroParticipantAckInvariantError::Conversation {
459 member: member.conversation_id(),
460 episode: episode.conversation_id(),
461 });
462 }
463 let Some(episode_participant) = episode.participant(member.participant_id()) else {
464 return Err(NonzeroParticipantAckInvariantError::ParticipantMissing {
465 participant_id: member.participant_id(),
466 });
467 };
468 let episode_generation = episode_participant
469 .active_binding_epoch()
470 .capability_generation;
471 if episode_generation != member.generation() {
472 return Err(NonzeroParticipantAckInvariantError::Generation {
473 member: member.generation(),
474 episode: episode_generation,
475 });
476 }
477 if episode_participant.cursor() != member.cursor() {
478 return Err(NonzeroParticipantAckInvariantError::Cursor {
479 member: member.cursor(),
480 episode: episode_participant.cursor(),
481 });
482 }
483 if episode_participant.active_binding_epoch() != active_binding_epoch {
484 return Err(NonzeroParticipantAckInvariantError::BindingEpoch {
485 active: active_binding_epoch,
486 episode: episode_participant.active_binding_epoch(),
487 });
488 }
489 Ok(())
490}