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::sourced(
54 request.conversation_id,
55 request.through_seq,
56 super::super::checkpoint_codec::ObserverProjectionSource::Ack {
57 binding: super::super::ActiveBinding {
58 conversation_id: request.conversation_id,
59 participant_id: request.participant_id,
60 binding_epoch: self.binding_epoch,
61 },
62 previous_cursor: self.from_cursor,
63 },
64 )
65 }
66
67 #[must_use]
69 pub const fn resulting_episode(&self) -> &NonzeroDebtCursorEpisode {
70 &self.resulting_episode
71 }
72
73 pub fn progress_binding_fate_token(
80 &self,
81 token: SealedBindingFateToken,
82 ) -> Result<SealedBindingFateToken, Box<SealedBindingFateToken>> {
83 let request = self.outcome.request();
84 token.participant_ack_progressed(
85 request.conversation_id,
86 request.participant_id,
87 self.binding_epoch,
88 self.from_cursor,
89 request.through_seq,
90 )
91 }
92
93 pub fn apply_to<F>(
106 self,
107 member: &mut LiveMember<F>,
108 episode: &mut NonzeroDebtCursorEpisode,
109 ) -> Result<AckCommitted, NonzeroParticipantAckCommitError> {
110 let request = self.outcome.request();
111 if member.conversation_id() != request.conversation_id {
112 return Err(NonzeroParticipantAckCommitError::Conversation {
113 expected: request.conversation_id,
114 actual: member.conversation_id(),
115 });
116 }
117 if member.participant_id() != request.participant_id {
118 return Err(NonzeroParticipantAckCommitError::Participant {
119 expected: request.participant_id,
120 actual: member.participant_id(),
121 });
122 }
123 if member.generation() != request.capability_generation {
124 return Err(NonzeroParticipantAckCommitError::Generation {
125 expected: request.capability_generation,
126 actual: member.generation(),
127 });
128 }
129
130 let (position, expected_cursor) = if *episode == self.before_episode {
131 (NonzeroAckEpisodePosition::Before, self.from_cursor)
132 } else if *episode == self.resulting_episode {
133 (NonzeroAckEpisodePosition::Resulting, request.through_seq)
134 } else {
135 return Err(NonzeroParticipantAckCommitError::EpisodePrestate);
136 };
137 if member.cursor() != expected_cursor {
138 return Err(NonzeroParticipantAckCommitError::AggregateCursorPrestate {
139 episode_position: position,
140 expected_cursor,
141 actual_cursor: member.cursor(),
142 });
143 }
144
145 member
146 .apply_cursor_update(self.cursor_update)
147 .map_err(NonzeroParticipantAckCommitError::from_member_error)?;
148 if position == NonzeroAckEpisodePosition::Before {
149 *episode = self.resulting_episode;
150 }
151 Ok(self.outcome)
152 }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq, Eq)]
157pub enum NonzeroParticipantAckCommitError {
158 Conversation {
160 expected: ConversationId,
162 actual: ConversationId,
164 },
165 Participant {
167 expected: ParticipantId,
169 actual: ParticipantId,
171 },
172 Generation {
174 expected: Generation,
176 actual: Generation,
178 },
179 EpisodePrestate,
181 AggregateCursorPrestate {
183 episode_position: NonzeroAckEpisodePosition,
185 expected_cursor: DeliverySeq,
187 actual_cursor: DeliverySeq,
189 },
190 NonAdvancing {
192 from_cursor: DeliverySeq,
194 resulting_cursor: DeliverySeq,
196 },
197 CursorPrestate {
199 expected_from_cursor: DeliverySeq,
201 resulting_cursor: DeliverySeq,
203 actual_cursor: DeliverySeq,
205 },
206}
207
208impl NonzeroParticipantAckCommitError {
209 const fn from_member_error(error: LiveMemberCursorUpdateError) -> Self {
210 match error {
211 LiveMemberCursorUpdateError::Conversation { expected, actual } => {
212 Self::Conversation { expected, actual }
213 }
214 LiveMemberCursorUpdateError::Participant { expected, actual } => {
215 Self::Participant { expected, actual }
216 }
217 LiveMemberCursorUpdateError::Generation { expected, actual } => {
218 Self::Generation { expected, actual }
219 }
220 LiveMemberCursorUpdateError::NonAdvancing {
221 from_cursor,
222 resulting_cursor,
223 } => Self::NonAdvancing {
224 from_cursor,
225 resulting_cursor,
226 },
227 LiveMemberCursorUpdateError::CursorPrestate {
228 expected_from_cursor,
229 resulting_cursor,
230 actual_cursor,
231 } => Self::CursorPrestate {
232 expected_from_cursor,
233 resulting_cursor,
234 actual_cursor,
235 },
236 }
237 }
238}
239
240#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242pub enum NonzeroParticipantAckInvariantError {
243 Conversation {
245 member: ConversationId,
247 episode: ConversationId,
249 },
250 ParticipantMissing {
252 participant_id: ParticipantId,
254 },
255 Generation {
257 member: Generation,
259 episode: Generation,
261 },
262 Cursor {
264 member: DeliverySeq,
266 episode: DeliverySeq,
268 },
269 BindingEpoch {
271 active: BindingEpoch,
273 episode: BindingEpoch,
275 },
276 EpisodeTransition(CumulativeAckAuthorizationError),
278}
279
280#[derive(Clone, Debug, PartialEq, Eq)]
282pub enum NonzeroParticipantAckDecision {
283 Respond(ParticipantAckResponse),
285 Invariant(NonzeroParticipantAckInvariantError),
287 Commit(Box<NonzeroParticipantAckCommit>),
289}
290
291#[must_use]
300pub fn apply_nonzero_participant_ack<EF, V, LF>(
301 presented_identity: PresentedIdentity<'_, EF, V, LF>,
302 binding: &BindingState,
303 receiving_binding_epoch: BindingEpoch,
304 request: &ParticipantAck,
305 contiguously_available_through: DeliverySeq,
306 episode: &NonzeroDebtCursorEpisode,
307) -> NonzeroParticipantAckDecision {
308 apply_nonzero_participant_ack_by_availability(
309 presented_identity,
310 binding,
311 receiving_binding_epoch,
312 request,
313 &AckAvailability::Contiguous(contiguously_available_through),
314 episode,
315 )
316}
317
318#[must_use]
324pub fn apply_nonzero_participant_ack_with_obligations<EF, V, LF>(
325 presented_identity: PresentedIdentity<'_, EF, V, LF>,
326 binding: &BindingState,
327 receiving_binding_epoch: BindingEpoch,
328 request: &ParticipantAck,
329 obligations: &RecipientAckObligations,
330 episode: &NonzeroDebtCursorEpisode,
331) -> NonzeroParticipantAckDecision {
332 apply_nonzero_participant_ack_by_availability(
333 presented_identity,
334 binding,
335 receiving_binding_epoch,
336 request,
337 &AckAvailability::Obligations(obligations),
338 episode,
339 )
340}
341
342pub fn scalar_audit_for_recipient_endpoint(
352 obligations: &RecipientAckObligations,
353 participant_id: ParticipantId,
354 acknowledged_through: DeliverySeq,
355 endpoint: DeliverySeq,
356 scalar_audit: DeliverySeq,
357) -> Result<Option<DeliverySeq>, RecipientAckObligationsContextError> {
358 obligations
359 .contains_endpoint(participant_id, acknowledged_through, endpoint)
360 .map(|testified| testified.then_some(scalar_audit))
361}
362
363enum AckAvailability<'a> {
364 Contiguous(DeliverySeq),
365 Obligations(&'a RecipientAckObligations),
366}
367
368fn apply_nonzero_participant_ack_by_availability<EF, V, LF>(
369 presented_identity: PresentedIdentity<'_, EF, V, LF>,
370 binding: &BindingState,
371 receiving_binding_epoch: BindingEpoch,
372 request: &ParticipantAck,
373 availability: &AckAvailability<'_>,
374 episode: &NonzeroDebtCursorEpisode,
375) -> NonzeroParticipantAckDecision {
376 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
377 let (member, active_binding) = match lookup_binding_required(
378 presented_identity,
379 binding,
380 Some(receiving_binding_epoch),
381 &lookup_request,
382 ) {
383 BindingRequiredLookupResult::Retired(outcome) => {
384 return NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::from_retired(
385 outcome,
386 ));
387 }
388 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
389 return NonzeroParticipantAckDecision::Respond(
390 ParticipantAckResponse::from_participant_unknown(outcome),
391 );
392 }
393 BindingRequiredLookupResult::StaleAuthority(outcome) => {
394 return NonzeroParticipantAckDecision::Respond(
395 ParticipantAckResponse::from_stale_authority(outcome),
396 );
397 }
398 BindingRequiredLookupResult::NoBinding(outcome) => {
399 return NonzeroParticipantAckDecision::Respond(
400 ParticipantAckResponse::from_no_binding(outcome),
401 );
402 }
403 BindingRequiredLookupResult::Authorized { member, binding } => (member, binding),
404 };
405
406 if let Err(error) = validate_aggregate(member, active_binding.binding_epoch, episode) {
407 return NonzeroParticipantAckDecision::Invariant(error);
408 }
409
410 let mut resulting_episode = episode.clone();
411 let selected = match availability {
412 AckAvailability::Contiguous(available_through) => resulting_episode.acknowledge(
413 member.participant_id(),
414 active_binding.binding_epoch,
415 request,
416 *available_through,
417 ),
418 AckAvailability::Obligations(obligations) => resulting_episode
419 .acknowledge_with_obligations(
420 member.participant_id(),
421 active_binding.binding_epoch,
422 request,
423 obligations,
424 ),
425 };
426 let outcome = match selected {
427 Ok(outcome) => outcome,
428 Err(error) => {
429 return NonzeroParticipantAckDecision::Invariant(
430 NonzeroParticipantAckInvariantError::EpisodeTransition(error),
431 );
432 }
433 };
434 match outcome {
435 CumulativeAckOutcome::Committed(outcome) => {
436 NonzeroParticipantAckDecision::Commit(Box::new(NonzeroParticipantAckCommit {
437 cursor_update: LiveMemberCursorUpdate::new(
438 request.conversation_id,
439 request.participant_id,
440 request.capability_generation,
441 member.cursor(),
442 request.through_seq,
443 ),
444 outcome,
445 binding_epoch: active_binding.binding_epoch,
446 from_cursor: member.cursor(),
447 before_episode: episode.clone(),
448 resulting_episode,
449 }))
450 }
451 CumulativeAckOutcome::NoOp(outcome) => {
452 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::from_ack_no_op(outcome))
453 }
454 CumulativeAckOutcome::Gap(outcome) => {
455 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::ack_gap(outcome))
456 }
457 CumulativeAckOutcome::Regression(outcome) => {
458 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::ack_regression(outcome))
459 }
460 }
461}
462
463fn validate_aggregate<F>(
464 member: &LiveMember<F>,
465 active_binding_epoch: BindingEpoch,
466 episode: &NonzeroDebtCursorEpisode,
467) -> Result<(), NonzeroParticipantAckInvariantError> {
468 if member.conversation_id() != episode.conversation_id() {
469 return Err(NonzeroParticipantAckInvariantError::Conversation {
470 member: member.conversation_id(),
471 episode: episode.conversation_id(),
472 });
473 }
474 let Some(episode_participant) = episode.participant(member.participant_id()) else {
475 return Err(NonzeroParticipantAckInvariantError::ParticipantMissing {
476 participant_id: member.participant_id(),
477 });
478 };
479 let episode_generation = episode_participant
480 .active_binding_epoch()
481 .capability_generation;
482 if episode_generation != member.generation() {
483 return Err(NonzeroParticipantAckInvariantError::Generation {
484 member: member.generation(),
485 episode: episode_generation,
486 });
487 }
488 if episode_participant.cursor() != member.cursor() {
489 return Err(NonzeroParticipantAckInvariantError::Cursor {
490 member: member.cursor(),
491 episode: episode_participant.cursor(),
492 });
493 }
494 if episode_participant.active_binding_epoch() != active_binding_epoch {
495 return Err(NonzeroParticipantAckInvariantError::BindingEpoch {
496 active: active_binding_epoch,
497 episode: episode_participant.active_binding_epoch(),
498 });
499 }
500 Ok(())
501}