liminal_protocol/lifecycle/operations/
participant_ack.rs1use crate::wire::{
2 AckCommitted, AckGap, AckRegression, BindingEpoch, ConversationId, DeliverySeq, Generation,
3 ParticipantAck, ParticipantAckEnvelope, ParticipantAckResponse, ParticipantId,
4};
5
6use super::super::{
7 BindingRequiredLookupResult, BindingState, LiveMember, ObserverProgressProjection,
8 ParticipantBindingRequest, PresentedIdentity, RecipientAckObligations,
9 RecipientAckObligationsContextError, lookup_binding_required,
10 membership::{LiveMemberCursorUpdate, LiveMemberCursorUpdateError},
11};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct ParticipantAckCommit {
20 outcome: AckCommitted,
21 cursor_update: LiveMemberCursorUpdate,
22}
23
24impl ParticipantAckCommit {
25 #[must_use]
27 pub const fn outcome(&self) -> &AckCommitted {
28 &self.outcome
29 }
30
31 #[must_use]
33 pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
34 let request = self.outcome.request();
35 ObserverProgressProjection::new(request.conversation_id, request.through_seq)
36 }
37
38 pub fn apply_to<F>(
50 self,
51 member: &mut LiveMember<F>,
52 ) -> Result<AckCommitted, ParticipantAckCommitError> {
53 member
54 .apply_cursor_update(self.cursor_update)
55 .map_err(ParticipantAckCommitError::from_member_error)?;
56 Ok(self.outcome)
57 }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum ParticipantAckCommitError {
63 Conversation {
65 expected: ConversationId,
67 actual: ConversationId,
69 },
70 Participant {
72 expected: ParticipantId,
74 actual: ParticipantId,
76 },
77 Generation {
79 expected: Generation,
81 actual: Generation,
83 },
84 NonAdvancing {
86 from_cursor: DeliverySeq,
88 resulting_cursor: DeliverySeq,
90 },
91 CursorPrestate {
93 expected_from_cursor: DeliverySeq,
95 resulting_cursor: DeliverySeq,
97 actual_cursor: DeliverySeq,
99 },
100}
101
102impl ParticipantAckCommitError {
103 const fn from_member_error(error: LiveMemberCursorUpdateError) -> Self {
104 match error {
105 LiveMemberCursorUpdateError::Conversation { expected, actual } => {
106 Self::Conversation { expected, actual }
107 }
108 LiveMemberCursorUpdateError::Participant { expected, actual } => {
109 Self::Participant { expected, actual }
110 }
111 LiveMemberCursorUpdateError::Generation { expected, actual } => {
112 Self::Generation { expected, actual }
113 }
114 LiveMemberCursorUpdateError::NonAdvancing {
115 from_cursor,
116 resulting_cursor,
117 } => Self::NonAdvancing {
118 from_cursor,
119 resulting_cursor,
120 },
121 LiveMemberCursorUpdateError::CursorPrestate {
122 expected_from_cursor,
123 resulting_cursor,
124 actual_cursor,
125 } => Self::CursorPrestate {
126 expected_from_cursor,
127 resulting_cursor,
128 actual_cursor,
129 },
130 }
131 }
132}
133
134#[derive(Clone, Debug, PartialEq, Eq)]
136pub enum ParticipantAckDecision {
137 Respond(ParticipantAckResponse),
139 Commit(ParticipantAckCommit),
141}
142
143#[must_use]
151pub fn apply_participant_ack<EF, V, LF>(
152 presented_identity: PresentedIdentity<'_, EF, V, LF>,
153 binding: &BindingState,
154 receiving_binding_epoch: BindingEpoch,
155 request: &ParticipantAck,
156 contiguously_available_through: DeliverySeq,
157) -> ParticipantAckDecision {
158 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
159 let member = match lookup_binding_required(
160 presented_identity,
161 binding,
162 Some(receiving_binding_epoch),
163 &lookup_request,
164 ) {
165 BindingRequiredLookupResult::Retired(outcome) => {
166 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_retired(outcome));
167 }
168 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
169 return ParticipantAckDecision::Respond(
170 ParticipantAckResponse::from_participant_unknown(outcome),
171 );
172 }
173 BindingRequiredLookupResult::StaleAuthority(outcome) => {
174 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_stale_authority(
175 outcome,
176 ));
177 }
178 BindingRequiredLookupResult::NoBinding(outcome) => {
179 return ParticipantAckDecision::Respond(ParticipantAckResponse::from_no_binding(
180 outcome,
181 ));
182 }
183 BindingRequiredLookupResult::Authorized { member, .. } => member,
184 };
185
186 let current_cursor = member.cursor();
187 if request.through_seq < current_cursor
188 && let Some(outcome) = AckRegression::new(ack_envelope(request), current_cursor)
189 {
190 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_regression(outcome));
191 }
192 if request.through_seq == current_cursor {
193 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_no_op(ack_envelope(
194 request,
195 )));
196 }
197 if request.through_seq > contiguously_available_through
198 && let Some(outcome) = AckGap::new(ack_envelope(request), current_cursor)
199 {
200 return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_gap(outcome));
201 }
202
203 let outcome = AckCommitted::new(ack_envelope(request));
204 ParticipantAckDecision::Commit(ParticipantAckCommit {
205 cursor_update: LiveMemberCursorUpdate::new(
206 request.conversation_id,
207 request.participant_id,
208 request.capability_generation,
209 current_cursor,
210 request.through_seq,
211 ),
212 outcome,
213 })
214}
215
216pub fn apply_participant_ack_with_obligations<EF, V, LF>(
230 presented_identity: PresentedIdentity<'_, EF, V, LF>,
231 binding: &BindingState,
232 receiving_binding_epoch: BindingEpoch,
233 request: &ParticipantAck,
234 obligations: &RecipientAckObligations,
235) -> Result<ParticipantAckDecision, RecipientAckObligationsContextError> {
236 let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
237 let member = match lookup_binding_required(
238 presented_identity,
239 binding,
240 Some(receiving_binding_epoch),
241 &lookup_request,
242 ) {
243 BindingRequiredLookupResult::Retired(outcome) => {
244 return Ok(ParticipantAckDecision::Respond(
245 ParticipantAckResponse::from_retired(outcome),
246 ));
247 }
248 BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
249 return Ok(ParticipantAckDecision::Respond(
250 ParticipantAckResponse::from_participant_unknown(outcome),
251 ));
252 }
253 BindingRequiredLookupResult::StaleAuthority(outcome) => {
254 return Ok(ParticipantAckDecision::Respond(
255 ParticipantAckResponse::from_stale_authority(outcome),
256 ));
257 }
258 BindingRequiredLookupResult::NoBinding(outcome) => {
259 return Ok(ParticipantAckDecision::Respond(
260 ParticipantAckResponse::from_no_binding(outcome),
261 ));
262 }
263 BindingRequiredLookupResult::Authorized { member, .. } => member,
264 };
265
266 let current_cursor = member.cursor();
267 let endpoint_is_obligation = obligations.contains_endpoint(
268 request.participant_id,
269 current_cursor,
270 request.through_seq,
271 )?;
272 if request.through_seq < current_cursor
273 && let Some(outcome) = AckRegression::new(ack_envelope(request), current_cursor)
274 {
275 return Ok(ParticipantAckDecision::Respond(
276 ParticipantAckResponse::ack_regression(outcome),
277 ));
278 }
279 if request.through_seq == current_cursor {
280 return Ok(ParticipantAckDecision::Respond(
281 ParticipantAckResponse::ack_no_op(ack_envelope(request)),
282 ));
283 }
284 if !endpoint_is_obligation
285 && let Some(outcome) = AckGap::new(ack_envelope(request), current_cursor)
286 {
287 return Ok(ParticipantAckDecision::Respond(
288 ParticipantAckResponse::ack_gap(outcome),
289 ));
290 }
291
292 let outcome = AckCommitted::new(ack_envelope(request));
293 Ok(ParticipantAckDecision::Commit(ParticipantAckCommit {
294 cursor_update: LiveMemberCursorUpdate::new(
295 request.conversation_id,
296 request.participant_id,
297 request.capability_generation,
298 current_cursor,
299 request.through_seq,
300 ),
301 outcome,
302 }))
303}
304
305const fn ack_envelope(request: &ParticipantAck) -> ParticipantAckEnvelope {
306 ParticipantAckEnvelope {
307 conversation_id: request.conversation_id,
308 participant_id: request.participant_id,
309 capability_generation: request.capability_generation,
310 through_seq: request.through_seq,
311 }
312}