1use alloc::boxed::Box;
2
3use crate::wire::{
4 AckCommitted, AckGap, AckRegression, BindingEpoch, ConversationId, DeliverySeq, Generation,
5 ParticipantAck, ParticipantAckEnvelope, ParticipantAckResponse, ParticipantId,
6};
7
8use super::super::{
9 BindingRequiredLookupResult, BindingState, LiveMember, ObserverProgressProjection,
10 ParticipantBindingRequest, PresentedIdentity, RecipientAckObligations,
11 RecipientAckObligationsContextError, SealedBindingFateToken, lookup_binding_required,
12 membership::{LiveMemberCursorUpdate, LiveMemberCursorUpdateError},
13};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct ParticipantAckCommit {
22 outcome: AckCommitted,
23 cursor_update: LiveMemberCursorUpdate,
24 binding_epoch: BindingEpoch,
25 previous_cursor: DeliverySeq,
26}
27
28impl ParticipantAckCommit {
29 #[must_use]
31 pub const fn outcome(&self) -> &AckCommitted {
32 &self.outcome
33 }
34
35 #[must_use]
37 pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
38 let request = self.outcome.request();
39 ObserverProgressProjection::sourced(
40 request.conversation_id,
41 request.through_seq,
42 super::super::checkpoint_codec::ObserverProjectionSource::Ack {
43 binding: super::super::ActiveBinding {
44 conversation_id: request.conversation_id,
45 participant_id: request.participant_id,
46 binding_epoch: self.binding_epoch,
47 },
48 previous_cursor: self.previous_cursor,
49 },
50 )
51 }
52
53 pub fn progress_binding_fate_token(
60 &self,
61 token: SealedBindingFateToken,
62 ) -> Result<SealedBindingFateToken, Box<SealedBindingFateToken>> {
63 let request = self.outcome.request();
64 token.participant_ack_progressed(
65 request.conversation_id,
66 request.participant_id,
67 self.binding_epoch,
68 self.previous_cursor,
69 request.through_seq,
70 )
71 }
72
73 pub fn apply_to<F>(
85 self,
86 member: &mut LiveMember<F>,
87 ) -> Result<AckCommitted, ParticipantAckCommitError> {
88 member
89 .apply_cursor_update(self.cursor_update)
90 .map_err(ParticipantAckCommitError::from_member_error)?;
91 Ok(self.outcome)
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum ParticipantAckCommitError {
98 Conversation {
100 expected: ConversationId,
102 actual: ConversationId,
104 },
105 Participant {
107 expected: ParticipantId,
109 actual: ParticipantId,
111 },
112 Generation {
114 expected: Generation,
116 actual: Generation,
118 },
119 NonAdvancing {
121 from_cursor: DeliverySeq,
123 resulting_cursor: DeliverySeq,
125 },
126 CursorPrestate {
128 expected_from_cursor: DeliverySeq,
130 resulting_cursor: DeliverySeq,
132 actual_cursor: DeliverySeq,
134 },
135}
136
137impl ParticipantAckCommitError {
138 const fn from_member_error(error: LiveMemberCursorUpdateError) -> Self {
139 match error {
140 LiveMemberCursorUpdateError::Conversation { expected, actual } => {
141 Self::Conversation { expected, actual }
142 }
143 LiveMemberCursorUpdateError::Participant { expected, actual } => {
144 Self::Participant { expected, actual }
145 }
146 LiveMemberCursorUpdateError::Generation { expected, actual } => {
147 Self::Generation { expected, actual }
148 }
149 LiveMemberCursorUpdateError::NonAdvancing {
150 from_cursor,
151 resulting_cursor,
152 } => Self::NonAdvancing {
153 from_cursor,
154 resulting_cursor,
155 },
156 LiveMemberCursorUpdateError::CursorPrestate {
157 expected_from_cursor,
158 resulting_cursor,
159 actual_cursor,
160 } => Self::CursorPrestate {
161 expected_from_cursor,
162 resulting_cursor,
163 actual_cursor,
164 },
165 }
166 }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
171pub enum ParticipantAckDecision {
172 Respond(ParticipantAckResponse),
174 Commit(ParticipantAckCommit),
176}
177
178#[must_use]
186pub fn apply_participant_ack<EF, V, LF>(
187 presented_identity: PresentedIdentity<'_, EF, V, LF>,
188 binding: &BindingState,
189 receiving_binding_epoch: BindingEpoch,
190 request: &ParticipantAck,
191 contiguously_available_through: DeliverySeq,
192) -> ParticipantAckDecision {
193 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
194 let member = match lookup_binding_required(
195 presented_identity,
196 binding,
197 Some(receiving_binding_epoch),
198 &lookup_request,
199 ) {
200 BindingRequiredLookupResult::Retired(outcome) => {
201 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_retired(outcome));
202 }
203 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
204 return ParticipantAckDecision::Respond(
205 ParticipantAckResponse::from_participant_unknown(outcome),
206 );
207 }
208 BindingRequiredLookupResult::StaleAuthority(outcome) => {
209 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_stale_authority(
210 outcome,
211 ));
212 }
213 BindingRequiredLookupResult::NoBinding(outcome) => {
214 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_no_binding(
215 outcome,
216 ));
217 }
218 BindingRequiredLookupResult::Authorized { member, .. } => member,
219 };
220
221 let current_cursor = member.cursor();
222 if request.through_seq < current_cursor
223 && let Some(outcome) = AckRegression::new(ack_envelope(request), current_cursor)
224 {
225 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_regression(outcome));
226 }
227 if request.through_seq == current_cursor {
228 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_no_op(ack_envelope(
229 request,
230 )));
231 }
232 if request.through_seq > contiguously_available_through
233 && let Some(outcome) = AckGap::new(ack_envelope(request), current_cursor)
234 {
235 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_gap(outcome));
236 }
237
238 let outcome = AckCommitted::new(ack_envelope(request));
239 ParticipantAckDecision::Commit(ParticipantAckCommit {
240 binding_epoch: receiving_binding_epoch,
241 previous_cursor: current_cursor,
242 cursor_update: LiveMemberCursorUpdate::new(
243 request.conversation_id,
244 request.participant_id,
245 request.capability_generation,
246 current_cursor,
247 request.through_seq,
248 ),
249 outcome,
250 })
251}
252
253pub fn apply_participant_ack_with_obligations<EF, V, LF>(
267 presented_identity: PresentedIdentity<'_, EF, V, LF>,
268 binding: &BindingState,
269 receiving_binding_epoch: BindingEpoch,
270 request: &ParticipantAck,
271 obligations: &RecipientAckObligations,
272) -> Result<ParticipantAckDecision, RecipientAckObligationsContextError> {
273 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
274 let member = match lookup_binding_required(
275 presented_identity,
276 binding,
277 Some(receiving_binding_epoch),
278 &lookup_request,
279 ) {
280 BindingRequiredLookupResult::Retired(outcome) => {
281 return Ok(ParticipantAckDecision::Respond(
282 ParticipantAckResponse::from_retired(outcome),
283 ));
284 }
285 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
286 return Ok(ParticipantAckDecision::Respond(
287 ParticipantAckResponse::from_participant_unknown(outcome),
288 ));
289 }
290 BindingRequiredLookupResult::StaleAuthority(outcome) => {
291 return Ok(ParticipantAckDecision::Respond(
292 ParticipantAckResponse::from_stale_authority(outcome),
293 ));
294 }
295 BindingRequiredLookupResult::NoBinding(outcome) => {
296 return Ok(ParticipantAckDecision::Respond(
297 ParticipantAckResponse::from_no_binding(outcome),
298 ));
299 }
300 BindingRequiredLookupResult::Authorized { member, .. } => member,
301 };
302
303 let current_cursor = member.cursor();
304 let endpoint_is_obligation = obligations.contains_endpoint(
305 request.participant_id,
306 current_cursor,
307 request.through_seq,
308 )?;
309 if request.through_seq < current_cursor
310 && let Some(outcome) = AckRegression::new(ack_envelope(request), current_cursor)
311 {
312 return Ok(ParticipantAckDecision::Respond(
313 ParticipantAckResponse::ack_regression(outcome),
314 ));
315 }
316 if request.through_seq == current_cursor {
317 return Ok(ParticipantAckDecision::Respond(
318 ParticipantAckResponse::ack_no_op(ack_envelope(request)),
319 ));
320 }
321 if !endpoint_is_obligation
322 && let Some(outcome) = AckGap::new(ack_envelope(request), current_cursor)
323 {
324 return Ok(ParticipantAckDecision::Respond(
325 ParticipantAckResponse::ack_gap(outcome),
326 ));
327 }
328
329 let outcome = AckCommitted::new(ack_envelope(request));
330 Ok(ParticipantAckDecision::Commit(ParticipantAckCommit {
331 binding_epoch: receiving_binding_epoch,
332 previous_cursor: current_cursor,
333 cursor_update: LiveMemberCursorUpdate::new(
334 request.conversation_id,
335 request.participant_id,
336 request.capability_generation,
337 current_cursor,
338 request.through_seq,
339 ),
340 outcome,
341 }))
342}
343
344const fn ack_envelope(request: &ParticipantAck) -> ParticipantAckEnvelope {
345 ParticipantAckEnvelope {
346 conversation_id: request.conversation_id,
347 participant_id: request.participant_id,
348 capability_generation: request.capability_generation,
349 through_seq: request.through_seq,
350 }
351}