1use super::{
2 ClientBindingState, ClientParticipantAggregate, ClientResponseCorrelation, correlation,
3};
4use crate::wire::{AttachBound, ReceiptReplay, ServerValue};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum ClientInboundRefusalReason {
9 AlreadyDead,
11 ForeignResponse,
13 DelayedResponse,
15 AmbiguousResponse,
17 MissingResponseAuthority,
19 LostAuthorityPending,
22}
23
24#[derive(Debug, PartialEq, Eq)]
26pub struct ClientInboundApplied {
27 aggregate: ClientParticipantAggregate,
28 value: ServerValue,
29}
30
31impl ClientInboundApplied {
32 #[must_use]
34 pub fn into_parts(self) -> (ClientParticipantAggregate, ServerValue) {
35 (self.aggregate, self.value)
36 }
37}
38
39#[derive(Debug, PartialEq, Eq)]
41pub struct ClientInboundRefusal {
42 aggregate: ClientParticipantAggregate,
43 value: ServerValue,
44 reason: ClientInboundRefusalReason,
45}
46
47impl ClientInboundRefusal {
48 #[must_use]
50 pub const fn reason(&self) -> ClientInboundRefusalReason {
51 self.reason
52 }
53
54 #[must_use]
56 pub fn into_parts(self) -> (ClientParticipantAggregate, ServerValue) {
57 (self.aggregate, self.value)
58 }
59}
60
61#[derive(Debug, PartialEq, Eq)]
63pub enum ClientInboundDecision {
64 Applied(ClientInboundApplied),
66 Refused(ClientInboundRefusal),
68}
69
70#[derive(Debug, PartialEq, Eq)]
72pub struct ClientCorrelatedInboundRefusal {
73 aggregate: ClientParticipantAggregate,
74 value: ServerValue,
75 correlation: ClientResponseCorrelation,
76 reason: ClientInboundRefusalReason,
77}
78
79impl ClientCorrelatedInboundRefusal {
80 #[must_use]
82 pub const fn reason(&self) -> ClientInboundRefusalReason {
83 self.reason
84 }
85
86 #[must_use]
88 pub fn into_parts(
89 self,
90 ) -> (
91 ClientParticipantAggregate,
92 ServerValue,
93 ClientResponseCorrelation,
94 ) {
95 (self.aggregate, self.value, self.correlation)
96 }
97}
98
99#[derive(Debug, PartialEq, Eq)]
101pub enum ClientCorrelatedInboundDecision {
102 Applied(ClientInboundApplied),
104 Refused(ClientCorrelatedInboundRefusal),
106}
107
108#[must_use]
110pub fn decide_inbound(
111 aggregate: ClientParticipantAggregate,
112 value: ServerValue,
113) -> ClientInboundDecision {
114 decide_inbound_inner(aggregate, value, false)
115}
116
117#[must_use]
119pub fn decide_correlated_inbound(
120 aggregate: ClientParticipantAggregate,
121 value: ServerValue,
122 correlation: ClientResponseCorrelation,
123) -> ClientCorrelatedInboundDecision {
124 let current_authority = aggregate.expected.as_ref().is_some_and(|expected| {
125 expected.issued && expected.authorization == correlation.authorization
126 });
127 if !current_authority {
128 return ClientCorrelatedInboundDecision::Refused(ClientCorrelatedInboundRefusal {
129 aggregate,
130 value,
131 correlation,
132 reason: ClientInboundRefusalReason::DelayedResponse,
133 });
134 }
135 match decide_inbound_inner(aggregate, value, true) {
136 ClientInboundDecision::Applied(applied) => {
137 ClientCorrelatedInboundDecision::Applied(applied)
138 }
139 ClientInboundDecision::Refused(refusal) => {
140 let reason = refusal.reason();
141 let (aggregate, value) = refusal.into_parts();
142 ClientCorrelatedInboundDecision::Refused(ClientCorrelatedInboundRefusal {
143 aggregate,
144 value,
145 correlation,
146 reason,
147 })
148 }
149 }
150}
151
152fn decide_inbound_inner(
153 mut aggregate: ClientParticipantAggregate,
154 value: ServerValue,
155 has_response_authority: bool,
156) -> ClientInboundDecision {
157 if aggregate.binding.is_left() {
158 return inbound_refusal(aggregate, value, ClientInboundRefusalReason::AlreadyDead);
159 }
160
161 if let Some(request) = correlation::participant_ack_request(&value) {
162 if aggregate.binding.matches_ack(request) {
163 return ClientInboundDecision::Applied(ClientInboundApplied { aggregate, value });
164 }
165 return inbound_refusal(
166 aggregate,
167 value,
168 ClientInboundRefusalReason::ForeignResponse,
169 );
170 }
171
172 if matches!(value, ServerValue::ParticipantTransportRejected(_)) {
173 return ClientInboundDecision::Applied(ClientInboundApplied { aggregate, value });
174 }
175
176 let Some(expected) = aggregate.expected.as_ref() else {
177 return inbound_refusal(
178 aggregate,
179 value,
180 ClientInboundRefusalReason::DelayedResponse,
181 );
182 };
183
184 if expected.lost.is_some() {
185 return inbound_refusal(
186 aggregate,
187 value,
188 ClientInboundRefusalReason::LostAuthorityPending,
189 );
190 }
191
192 if !has_response_authority {
193 return inbound_refusal(
194 aggregate,
195 value,
196 ClientInboundRefusalReason::MissingResponseAuthority,
197 );
198 }
199
200 if !aggregate.binding.accepts_request(&expected.request) {
201 return inbound_refusal(
202 aggregate,
203 value,
204 ClientInboundRefusalReason::ForeignResponse,
205 );
206 }
207
208 if !correlation::matches_request(&value, &expected.request) {
209 let same_request_class = value.originating_request()
210 == Some(expected.request.discriminant())
211 || matches!(
212 (&value, &expected.request),
213 (
214 ServerValue::ObserverRecoveryAccepted(_)
215 | ServerValue::InvalidObserverEpoch(_)
216 | ServerValue::InvalidObserverEpochList(_),
217 crate::wire::ClientRequest::ObserverRecovery(_)
218 )
219 );
220 let same_identity = correlation::same_identity(&value, &expected.request);
221 let reason = if same_request_class && same_identity {
222 if matches!(
223 expected.request,
224 crate::wire::ClientRequest::RecordAdmission(_)
225 ) {
226 ClientInboundRefusalReason::AmbiguousResponse
227 } else {
228 ClientInboundRefusalReason::DelayedResponse
229 }
230 } else {
231 ClientInboundRefusalReason::ForeignResponse
232 };
233 return inbound_refusal(aggregate, value, reason);
234 }
235
236 aggregate.expected = None;
237 apply_correlated_value(&mut aggregate, &value);
238 ClientInboundDecision::Applied(ClientInboundApplied { aggregate, value })
239}
240
241const fn inbound_refusal(
242 aggregate: ClientParticipantAggregate,
243 value: ServerValue,
244 reason: ClientInboundRefusalReason,
245) -> ClientInboundDecision {
246 ClientInboundDecision::Refused(ClientInboundRefusal {
247 aggregate,
248 value,
249 reason,
250 })
251}
252
253fn apply_correlated_value(aggregate: &mut ClientParticipantAggregate, value: &ServerValue) {
254 match value {
255 ServerValue::EnrollBound(value) => apply_enroll_bound(aggregate, value),
256 ServerValue::Bound(ReceiptReplay::Enrollment(value)) => {
257 apply_enroll_bound(aggregate, value);
258 }
259 ServerValue::AttachBound(value)
260 | ServerValue::Bound(ReceiptReplay::CredentialAttach(value)) => {
261 apply_attach_bound(aggregate, value);
262 aggregate.detach_replay.apply_attach(value);
263 }
264 ServerValue::DetachCommitted(value) => {
265 let attach_secret = match aggregate.binding {
266 ClientBindingState::Bound { attach_secret, .. }
267 | ClientBindingState::Detached { attach_secret, .. } => attach_secret,
268 ClientBindingState::Unbound | ClientBindingState::Left { .. } => return,
269 };
270 aggregate.binding = ClientBindingState::Detached {
271 conversation_id: value.conversation_id(),
272 participant_id: value.participant_id(),
273 generation: value.capability_generation(),
274 attach_secret,
275 };
276 aggregate.detach_replay.apply_detach_committed(value);
277 }
278 ServerValue::DetachInProgress(value) => {
279 aggregate.detach_replay.apply_detach_in_progress(value);
280 }
281 ServerValue::StaleAuthority(crate::wire::StaleAuthority::Detach(
282 crate::wire::DetachStaleAuthority::TerminalizedDetachCell(value),
283 )) => {
284 aggregate
285 .detach_replay
286 .apply_terminalized_detach_cell(value);
287 }
288 ServerValue::LeaveCommitted(value) => {
289 aggregate.binding = ClientBindingState::Left {
290 conversation_id: value.conversation_id(),
291 participant_id: value.participant_id(),
292 generation: value.retired_generation(),
293 };
294 aggregate.detach_replay.apply_leave(value);
295 }
296 ServerValue::Retired(value) => {
297 apply_retired(aggregate, value);
298 }
299 ServerValue::ParticipantTransportRejected(_)
300 | ServerValue::AttemptTokenBodyConflict(_)
301 | ServerValue::ConnectionConversationCapacityExceeded(_)
302 | ServerValue::ConnectionConversationBindingOccupied(_)
303 | ServerValue::ConversationOrderExhausted(_)
304 | ServerValue::ParticipantUnknown(_)
305 | ServerValue::NoBinding(_)
306 | ServerValue::StaleAuthority(_)
307 | ServerValue::MarkerClosureCapacityExceeded(_)
308 | ServerValue::EnrollmentKnown(_)
309 | ServerValue::ReceiptExpired(_)
310 | ServerValue::ReceiptCapacityExceeded(_)
311 | ServerValue::IdentityCapacityExceeded(_)
312 | ServerValue::ObserverBackpressure(_)
313 | ServerValue::ConversationSequenceExhausted(_)
314 | ServerValue::StaleOrUnknownReceipt(_)
315 | ServerValue::MarkerNotDelivered(_)
316 | ServerValue::MarkerMismatch(_)
317 | ServerValue::UnboundReceipt(_)
318 | ServerValue::AckCommitted(_)
319 | ServerValue::AckNoOp(_)
320 | ServerValue::AckGap(_)
321 | ServerValue::AckRegression(_)
322 | ServerValue::MarkerAckCommitted(_)
323 | ServerValue::RecordCommitted(_)
324 | ServerValue::RecordTooLarge(_)
325 | ServerValue::ObserverRecoveryAccepted(_)
326 | ServerValue::InvalidObserverEpoch(_)
327 | ServerValue::InvalidObserverEpochList(_) => {}
328 }
329}
330
331const fn apply_enroll_bound(
332 aggregate: &mut ClientParticipantAggregate,
333 value: &crate::wire::EnrollBound,
334) {
335 aggregate.binding = ClientBindingState::Bound {
336 conversation_id: value.conversation_id(),
337 participant_id: value.participant_id(),
338 generation: value.capability_generation(),
339 attach_secret: value.attach_secret(),
340 binding_epoch: value.origin_binding_epoch(),
341 };
342}
343
344const fn apply_attach_bound(aggregate: &mut ClientParticipantAggregate, value: &AttachBound) {
345 aggregate.binding = ClientBindingState::Bound {
346 conversation_id: value.conversation_id(),
347 participant_id: value.participant_id(),
348 generation: value.capability_generation(),
349 attach_secret: value.attach_secret(),
350 binding_epoch: value.origin_binding_epoch(),
351 };
352}
353
354fn apply_retired(aggregate: &mut ClientParticipantAggregate, value: &crate::wire::Retired) {
355 let (conversation_id, participant_id, generation) = match value {
356 crate::wire::Retired::Enrollment {
357 request,
358 participant_id,
359 retired_generation,
360 } => (
361 request.conversation_id,
362 *participant_id,
363 *retired_generation,
364 ),
365 crate::wire::Retired::Participant {
366 request,
367 retired_generation,
368 } => {
369 let (conversation_id, participant_id) = participant_reference_identity(request);
370 (conversation_id, participant_id, *retired_generation)
371 }
372 };
373 aggregate.binding = ClientBindingState::Left {
374 conversation_id,
375 participant_id,
376 generation,
377 };
378 aggregate
379 .detach_replay
380 .apply_retired(conversation_id, participant_id, generation);
381}
382
383const fn participant_reference_identity(
384 request: &crate::wire::ParticipantReferenceEnvelope,
385) -> (u64, u64) {
386 match request {
387 crate::wire::ParticipantReferenceEnvelope::CredentialAttach(value) => {
388 (value.conversation_id, value.participant_id)
389 }
390 crate::wire::ParticipantReferenceEnvelope::Detach(value) => {
391 (value.conversation_id, value.participant_id)
392 }
393 crate::wire::ParticipantReferenceEnvelope::ParticipantAck(value) => {
394 (value.conversation_id, value.participant_id)
395 }
396 crate::wire::ParticipantReferenceEnvelope::Leave(value) => {
397 (value.conversation_id, value.participant_id)
398 }
399 crate::wire::ParticipantReferenceEnvelope::MarkerAck(value) => {
400 (value.conversation_id, value.participant_id)
401 }
402 crate::wire::ParticipantReferenceEnvelope::RecordAdmission(value) => {
403 (value.conversation_id, value.participant_id)
404 }
405 }
406}