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, lookup_binding_required,
12 membership::{LiveMemberCursorUpdate, LiveMemberCursorUpdateError},
13};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum NonzeroAckEpisodePosition {
18 Before,
20 Resulting,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct NonzeroParticipantAckCommit {
33 outcome: AckCommitted,
34 from_cursor: DeliverySeq,
35 before_episode: NonzeroDebtCursorEpisode,
36 resulting_episode: NonzeroDebtCursorEpisode,
37 cursor_update: LiveMemberCursorUpdate,
38}
39
40impl NonzeroParticipantAckCommit {
41 #[must_use]
43 pub const fn outcome(&self) -> &AckCommitted {
44 &self.outcome
45 }
46
47 #[must_use]
49 pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
50 let request = self.outcome.request();
51 ObserverProgressProjection::new(request.conversation_id, request.through_seq)
52 }
53
54 #[must_use]
56 pub const fn resulting_episode(&self) -> &NonzeroDebtCursorEpisode {
57 &self.resulting_episode
58 }
59
60 pub fn apply_to<F>(
73 self,
74 member: &mut LiveMember<F>,
75 episode: &mut NonzeroDebtCursorEpisode,
76 ) -> Result<AckCommitted, NonzeroParticipantAckCommitError> {
77 let request = self.outcome.request();
78 if member.conversation_id() != request.conversation_id {
79 return Err(NonzeroParticipantAckCommitError::Conversation {
80 expected: request.conversation_id,
81 actual: member.conversation_id(),
82 });
83 }
84 if member.participant_id() != request.participant_id {
85 return Err(NonzeroParticipantAckCommitError::Participant {
86 expected: request.participant_id,
87 actual: member.participant_id(),
88 });
89 }
90 if member.generation() != request.capability_generation {
91 return Err(NonzeroParticipantAckCommitError::Generation {
92 expected: request.capability_generation,
93 actual: member.generation(),
94 });
95 }
96
97 let (position, expected_cursor) = if *episode == self.before_episode {
98 (NonzeroAckEpisodePosition::Before, self.from_cursor)
99 } else if *episode == self.resulting_episode {
100 (NonzeroAckEpisodePosition::Resulting, request.through_seq)
101 } else {
102 return Err(NonzeroParticipantAckCommitError::EpisodePrestate);
103 };
104 if member.cursor() != expected_cursor {
105 return Err(NonzeroParticipantAckCommitError::AggregateCursorPrestate {
106 episode_position: position,
107 expected_cursor,
108 actual_cursor: member.cursor(),
109 });
110 }
111
112 member
113 .apply_cursor_update(self.cursor_update)
114 .map_err(NonzeroParticipantAckCommitError::from_member_error)?;
115 if position == NonzeroAckEpisodePosition::Before {
116 *episode = self.resulting_episode;
117 }
118 Ok(self.outcome)
119 }
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub enum NonzeroParticipantAckCommitError {
125 Conversation {
127 expected: ConversationId,
129 actual: ConversationId,
131 },
132 Participant {
134 expected: ParticipantId,
136 actual: ParticipantId,
138 },
139 Generation {
141 expected: Generation,
143 actual: Generation,
145 },
146 EpisodePrestate,
148 AggregateCursorPrestate {
150 episode_position: NonzeroAckEpisodePosition,
152 expected_cursor: DeliverySeq,
154 actual_cursor: DeliverySeq,
156 },
157 NonAdvancing {
159 from_cursor: DeliverySeq,
161 resulting_cursor: DeliverySeq,
163 },
164 CursorPrestate {
166 expected_from_cursor: DeliverySeq,
168 resulting_cursor: DeliverySeq,
170 actual_cursor: DeliverySeq,
172 },
173}
174
175impl NonzeroParticipantAckCommitError {
176 const fn from_member_error(error: LiveMemberCursorUpdateError) -> Self {
177 match error {
178 LiveMemberCursorUpdateError::Conversation { expected, actual } => {
179 Self::Conversation { expected, actual }
180 }
181 LiveMemberCursorUpdateError::Participant { expected, actual } => {
182 Self::Participant { expected, actual }
183 }
184 LiveMemberCursorUpdateError::Generation { expected, actual } => {
185 Self::Generation { expected, actual }
186 }
187 LiveMemberCursorUpdateError::NonAdvancing {
188 from_cursor,
189 resulting_cursor,
190 } => Self::NonAdvancing {
191 from_cursor,
192 resulting_cursor,
193 },
194 LiveMemberCursorUpdateError::CursorPrestate {
195 expected_from_cursor,
196 resulting_cursor,
197 actual_cursor,
198 } => Self::CursorPrestate {
199 expected_from_cursor,
200 resulting_cursor,
201 actual_cursor,
202 },
203 }
204 }
205}
206
207#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub enum NonzeroParticipantAckInvariantError {
210 Conversation {
212 member: ConversationId,
214 episode: ConversationId,
216 },
217 ParticipantMissing {
219 participant_id: ParticipantId,
221 },
222 Generation {
224 member: Generation,
226 episode: Generation,
228 },
229 Cursor {
231 member: DeliverySeq,
233 episode: DeliverySeq,
235 },
236 BindingEpoch {
238 active: BindingEpoch,
240 episode: BindingEpoch,
242 },
243 EpisodeTransition(CumulativeAckAuthorizationError),
245}
246
247#[derive(Clone, Debug, PartialEq, Eq)]
249pub enum NonzeroParticipantAckDecision {
250 Respond(ParticipantAckResponse),
252 Invariant(NonzeroParticipantAckInvariantError),
254 Commit(Box<NonzeroParticipantAckCommit>),
256}
257
258#[must_use]
267pub fn apply_nonzero_participant_ack<EF, V, LF>(
268 presented_identity: PresentedIdentity<'_, EF, V, LF>,
269 binding: &BindingState,
270 receiving_binding_epoch: BindingEpoch,
271 request: &ParticipantAck,
272 contiguously_available_through: DeliverySeq,
273 episode: &NonzeroDebtCursorEpisode,
274) -> NonzeroParticipantAckDecision {
275 apply_nonzero_participant_ack_by_availability(
276 presented_identity,
277 binding,
278 receiving_binding_epoch,
279 request,
280 &AckAvailability::Contiguous(contiguously_available_through),
281 episode,
282 )
283}
284
285#[must_use]
291pub fn apply_nonzero_participant_ack_with_obligations<EF, V, LF>(
292 presented_identity: PresentedIdentity<'_, EF, V, LF>,
293 binding: &BindingState,
294 receiving_binding_epoch: BindingEpoch,
295 request: &ParticipantAck,
296 obligations: &RecipientAckObligations,
297 episode: &NonzeroDebtCursorEpisode,
298) -> NonzeroParticipantAckDecision {
299 apply_nonzero_participant_ack_by_availability(
300 presented_identity,
301 binding,
302 receiving_binding_epoch,
303 request,
304 &AckAvailability::Obligations(obligations),
305 episode,
306 )
307}
308
309enum AckAvailability<'a> {
310 Contiguous(DeliverySeq),
311 Obligations(&'a RecipientAckObligations),
312}
313
314fn apply_nonzero_participant_ack_by_availability<EF, V, LF>(
315 presented_identity: PresentedIdentity<'_, EF, V, LF>,
316 binding: &BindingState,
317 receiving_binding_epoch: BindingEpoch,
318 request: &ParticipantAck,
319 availability: &AckAvailability<'_>,
320 episode: &NonzeroDebtCursorEpisode,
321) -> NonzeroParticipantAckDecision {
322 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
323 let (member, active_binding) = match lookup_binding_required(
324 presented_identity,
325 binding,
326 Some(receiving_binding_epoch),
327 &lookup_request,
328 ) {
329 BindingRequiredLookupResult::Retired(outcome) => {
330 return NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::from_retired(
331 outcome,
332 ));
333 }
334 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
335 return NonzeroParticipantAckDecision::Respond(
336 ParticipantAckResponse::from_participant_unknown(outcome),
337 );
338 }
339 BindingRequiredLookupResult::StaleAuthority(outcome) => {
340 return NonzeroParticipantAckDecision::Respond(
341 ParticipantAckResponse::from_stale_authority(outcome),
342 );
343 }
344 BindingRequiredLookupResult::NoBinding(outcome) => {
345 return NonzeroParticipantAckDecision::Respond(
346 ParticipantAckResponse::from_no_binding(outcome),
347 );
348 }
349 BindingRequiredLookupResult::Authorized { member, binding } => (member, binding),
350 };
351
352 if let Err(error) = validate_aggregate(member, active_binding.binding_epoch, episode) {
353 return NonzeroParticipantAckDecision::Invariant(error);
354 }
355
356 let mut resulting_episode = episode.clone();
357 let selected = match availability {
358 AckAvailability::Contiguous(available_through) => resulting_episode.acknowledge(
359 member.participant_id(),
360 active_binding.binding_epoch,
361 request,
362 *available_through,
363 ),
364 AckAvailability::Obligations(obligations) => resulting_episode
365 .acknowledge_with_obligations(
366 member.participant_id(),
367 active_binding.binding_epoch,
368 request,
369 obligations,
370 ),
371 };
372 let outcome = match selected {
373 Ok(outcome) => outcome,
374 Err(error) => {
375 return NonzeroParticipantAckDecision::Invariant(
376 NonzeroParticipantAckInvariantError::EpisodeTransition(error),
377 );
378 }
379 };
380 match outcome {
381 CumulativeAckOutcome::Committed(outcome) => {
382 NonzeroParticipantAckDecision::Commit(Box::new(NonzeroParticipantAckCommit {
383 cursor_update: LiveMemberCursorUpdate::new(
384 request.conversation_id,
385 request.participant_id,
386 request.capability_generation,
387 member.cursor(),
388 request.through_seq,
389 ),
390 outcome,
391 from_cursor: member.cursor(),
392 before_episode: episode.clone(),
393 resulting_episode,
394 }))
395 }
396 CumulativeAckOutcome::NoOp(outcome) => {
397 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::from_ack_no_op(outcome))
398 }
399 CumulativeAckOutcome::Gap(outcome) => {
400 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::ack_gap(outcome))
401 }
402 CumulativeAckOutcome::Regression(outcome) => {
403 NonzeroParticipantAckDecision::Respond(ParticipantAckResponse::ack_regression(outcome))
404 }
405 }
406}
407
408fn validate_aggregate<F>(
409 member: &LiveMember<F>,
410 active_binding_epoch: BindingEpoch,
411 episode: &NonzeroDebtCursorEpisode,
412) -> Result<(), NonzeroParticipantAckInvariantError> {
413 if member.conversation_id() != episode.conversation_id() {
414 return Err(NonzeroParticipantAckInvariantError::Conversation {
415 member: member.conversation_id(),
416 episode: episode.conversation_id(),
417 });
418 }
419 let Some(episode_participant) = episode.participant(member.participant_id()) else {
420 return Err(NonzeroParticipantAckInvariantError::ParticipantMissing {
421 participant_id: member.participant_id(),
422 });
423 };
424 let episode_generation = episode_participant
425 .active_binding_epoch()
426 .capability_generation;
427 if episode_generation != member.generation() {
428 return Err(NonzeroParticipantAckInvariantError::Generation {
429 member: member.generation(),
430 episode: episode_generation,
431 });
432 }
433 if episode_participant.cursor() != member.cursor() {
434 return Err(NonzeroParticipantAckInvariantError::Cursor {
435 member: member.cursor(),
436 episode: episode_participant.cursor(),
437 });
438 }
439 if episode_participant.active_binding_epoch() != active_binding_epoch {
440 return Err(NonzeroParticipantAckInvariantError::BindingEpoch {
441 active: active_binding_epoch,
442 episode: episode_participant.active_binding_epoch(),
443 });
444 }
445 Ok(())
446}