liminal_protocol/lifecycle/operations/
participant_ack.rs1use 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::new(request.conversation_id, request.through_seq)
40 }
41
42 pub fn progress_binding_fate_token(
49 &self,
50 token: SealedBindingFateToken,
51 ) -> Result<SealedBindingFateToken, Box<SealedBindingFateToken>> {
52 let request = self.outcome.request();
53 token.participant_ack_progressed(
54 request.conversation_id,
55 request.participant_id,
56 self.binding_epoch,
57 self.previous_cursor,
58 request.through_seq,
59 )
60 }
61
62 pub fn apply_to<F>(
74 self,
75 member: &mut LiveMember<F>,
76 ) -> Result<AckCommitted, ParticipantAckCommitError> {
77 member
78 .apply_cursor_update(self.cursor_update)
79 .map_err(ParticipantAckCommitError::from_member_error)?;
80 Ok(self.outcome)
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum ParticipantAckCommitError {
87 Conversation {
89 expected: ConversationId,
91 actual: ConversationId,
93 },
94 Participant {
96 expected: ParticipantId,
98 actual: ParticipantId,
100 },
101 Generation {
103 expected: Generation,
105 actual: Generation,
107 },
108 NonAdvancing {
110 from_cursor: DeliverySeq,
112 resulting_cursor: DeliverySeq,
114 },
115 CursorPrestate {
117 expected_from_cursor: DeliverySeq,
119 resulting_cursor: DeliverySeq,
121 actual_cursor: DeliverySeq,
123 },
124}
125
126impl ParticipantAckCommitError {
127 const fn from_member_error(error: LiveMemberCursorUpdateError) -> Self {
128 match error {
129 LiveMemberCursorUpdateError::Conversation { expected, actual } => {
130 Self::Conversation { expected, actual }
131 }
132 LiveMemberCursorUpdateError::Participant { expected, actual } => {
133 Self::Participant { expected, actual }
134 }
135 LiveMemberCursorUpdateError::Generation { expected, actual } => {
136 Self::Generation { expected, actual }
137 }
138 LiveMemberCursorUpdateError::NonAdvancing {
139 from_cursor,
140 resulting_cursor,
141 } => Self::NonAdvancing {
142 from_cursor,
143 resulting_cursor,
144 },
145 LiveMemberCursorUpdateError::CursorPrestate {
146 expected_from_cursor,
147 resulting_cursor,
148 actual_cursor,
149 } => Self::CursorPrestate {
150 expected_from_cursor,
151 resulting_cursor,
152 actual_cursor,
153 },
154 }
155 }
156}
157
158#[derive(Clone, Debug, PartialEq, Eq)]
160pub enum ParticipantAckDecision {
161 Respond(ParticipantAckResponse),
163 Commit(ParticipantAckCommit),
165}
166
167#[must_use]
175pub fn apply_participant_ack<EF, V, LF>(
176 presented_identity: PresentedIdentity<'_, EF, V, LF>,
177 binding: &BindingState,
178 receiving_binding_epoch: BindingEpoch,
179 request: &ParticipantAck,
180 contiguously_available_through: DeliverySeq,
181) -> ParticipantAckDecision {
182 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
183 let member = match lookup_binding_required(
184 presented_identity,
185 binding,
186 Some(receiving_binding_epoch),
187 &lookup_request,
188 ) {
189 BindingRequiredLookupResult::Retired(outcome) => {
190 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_retired(outcome));
191 }
192 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
193 return ParticipantAckDecision::Respond(
194 ParticipantAckResponse::from_participant_unknown(outcome),
195 );
196 }
197 BindingRequiredLookupResult::StaleAuthority(outcome) => {
198 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_stale_authority(
199 outcome,
200 ));
201 }
202 BindingRequiredLookupResult::NoBinding(outcome) => {
203 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_no_binding(
204 outcome,
205 ));
206 }
207 BindingRequiredLookupResult::Authorized { member, .. } => member,
208 };
209
210 let current_cursor = member.cursor();
211 if request.through_seq < current_cursor
212 && let Some(outcome) = AckRegression::new(ack_envelope(request), current_cursor)
213 {
214 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_regression(outcome));
215 }
216 if request.through_seq == current_cursor {
217 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_no_op(ack_envelope(
218 request,
219 )));
220 }
221 if request.through_seq > contiguously_available_through
222 && let Some(outcome) = AckGap::new(ack_envelope(request), current_cursor)
223 {
224 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_gap(outcome));
225 }
226
227 let outcome = AckCommitted::new(ack_envelope(request));
228 ParticipantAckDecision::Commit(ParticipantAckCommit {
229 binding_epoch: receiving_binding_epoch,
230 previous_cursor: current_cursor,
231 cursor_update: LiveMemberCursorUpdate::new(
232 request.conversation_id,
233 request.participant_id,
234 request.capability_generation,
235 current_cursor,
236 request.through_seq,
237 ),
238 outcome,
239 })
240}
241
242pub fn apply_participant_ack_with_obligations<EF, V, LF>(
256 presented_identity: PresentedIdentity<'_, EF, V, LF>,
257 binding: &BindingState,
258 receiving_binding_epoch: BindingEpoch,
259 request: &ParticipantAck,
260 obligations: &RecipientAckObligations,
261) -> Result<ParticipantAckDecision, RecipientAckObligationsContextError> {
262 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
263 let member = match lookup_binding_required(
264 presented_identity,
265 binding,
266 Some(receiving_binding_epoch),
267 &lookup_request,
268 ) {
269 BindingRequiredLookupResult::Retired(outcome) => {
270 return Ok(ParticipantAckDecision::Respond(
271 ParticipantAckResponse::from_retired(outcome),
272 ));
273 }
274 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
275 return Ok(ParticipantAckDecision::Respond(
276 ParticipantAckResponse::from_participant_unknown(outcome),
277 ));
278 }
279 BindingRequiredLookupResult::StaleAuthority(outcome) => {
280 return Ok(ParticipantAckDecision::Respond(
281 ParticipantAckResponse::from_stale_authority(outcome),
282 ));
283 }
284 BindingRequiredLookupResult::NoBinding(outcome) => {
285 return Ok(ParticipantAckDecision::Respond(
286 ParticipantAckResponse::from_no_binding(outcome),
287 ));
288 }
289 BindingRequiredLookupResult::Authorized { member, .. } => member,
290 };
291
292 let current_cursor = member.cursor();
293 let endpoint_is_obligation = obligations.contains_endpoint(
294 request.participant_id,
295 current_cursor,
296 request.through_seq,
297 )?;
298 if request.through_seq < current_cursor
299 && let Some(outcome) = AckRegression::new(ack_envelope(request), current_cursor)
300 {
301 return Ok(ParticipantAckDecision::Respond(
302 ParticipantAckResponse::ack_regression(outcome),
303 ));
304 }
305 if request.through_seq == current_cursor {
306 return Ok(ParticipantAckDecision::Respond(
307 ParticipantAckResponse::ack_no_op(ack_envelope(request)),
308 ));
309 }
310 if !endpoint_is_obligation
311 && let Some(outcome) = AckGap::new(ack_envelope(request), current_cursor)
312 {
313 return Ok(ParticipantAckDecision::Respond(
314 ParticipantAckResponse::ack_gap(outcome),
315 ));
316 }
317
318 let outcome = AckCommitted::new(ack_envelope(request));
319 Ok(ParticipantAckDecision::Commit(ParticipantAckCommit {
320 binding_epoch: receiving_binding_epoch,
321 previous_cursor: current_cursor,
322 cursor_update: LiveMemberCursorUpdate::new(
323 request.conversation_id,
324 request.participant_id,
325 request.capability_generation,
326 current_cursor,
327 request.through_seq,
328 ),
329 outcome,
330 }))
331}
332
333const fn ack_envelope(request: &ParticipantAck) -> ParticipantAckEnvelope {
334 ParticipantAckEnvelope {
335 conversation_id: request.conversation_id,
336 participant_id: request.participant_id,
337 capability_generation: request.capability_generation,
338 through_seq: request.through_seq,
339 }
340}