use crate::wire::{
AckCommitted, AckGap, AckRegression, BindingEpoch, ConversationId, DeliverySeq, Generation,
ParticipantAck, ParticipantAckEnvelope, ParticipantAckResponse, ParticipantId,
};
use super::super::{
BindingRequiredLookupResult, BindingState, LiveMember, ObserverProgressProjection,
ParticipantBindingRequest, PresentedIdentity, RecipientAckObligations,
RecipientAckObligationsContextError, lookup_binding_required,
membership::{LiveMemberCursorUpdate, LiveMemberCursorUpdateError},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParticipantAckCommit {
outcome: AckCommitted,
cursor_update: LiveMemberCursorUpdate,
}
impl ParticipantAckCommit {
#[must_use]
pub const fn outcome(&self) -> &AckCommitted {
&self.outcome
}
#[must_use]
pub const fn observer_progress_projection(&self) -> ObserverProgressProjection {
let request = self.outcome.request();
ObserverProgressProjection::new(request.conversation_id, request.through_seq)
}
pub fn apply_to<F>(
self,
member: &mut LiveMember<F>,
) -> Result<AckCommitted, ParticipantAckCommitError> {
member
.apply_cursor_update(self.cursor_update)
.map_err(ParticipantAckCommitError::from_member_error)?;
Ok(self.outcome)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParticipantAckCommitError {
Conversation {
expected: ConversationId,
actual: ConversationId,
},
Participant {
expected: ParticipantId,
actual: ParticipantId,
},
Generation {
expected: Generation,
actual: Generation,
},
NonAdvancing {
from_cursor: DeliverySeq,
resulting_cursor: DeliverySeq,
},
CursorPrestate {
expected_from_cursor: DeliverySeq,
resulting_cursor: DeliverySeq,
actual_cursor: DeliverySeq,
},
}
impl ParticipantAckCommitError {
const fn from_member_error(error: LiveMemberCursorUpdateError) -> Self {
match error {
LiveMemberCursorUpdateError::Conversation { expected, actual } => {
Self::Conversation { expected, actual }
}
LiveMemberCursorUpdateError::Participant { expected, actual } => {
Self::Participant { expected, actual }
}
LiveMemberCursorUpdateError::Generation { expected, actual } => {
Self::Generation { expected, actual }
}
LiveMemberCursorUpdateError::NonAdvancing {
from_cursor,
resulting_cursor,
} => Self::NonAdvancing {
from_cursor,
resulting_cursor,
},
LiveMemberCursorUpdateError::CursorPrestate {
expected_from_cursor,
resulting_cursor,
actual_cursor,
} => Self::CursorPrestate {
expected_from_cursor,
resulting_cursor,
actual_cursor,
},
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParticipantAckDecision {
Respond(ParticipantAckResponse),
Commit(ParticipantAckCommit),
}
#[must_use]
pub fn apply_participant_ack<EF, V, LF>(
presented_identity: PresentedIdentity<'_, EF, V, LF>,
binding: &BindingState,
receiving_binding_epoch: BindingEpoch,
request: &ParticipantAck,
contiguously_available_through: DeliverySeq,
) -> ParticipantAckDecision {
let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
let member = match lookup_binding_required(
presented_identity,
binding,
Some(receiving_binding_epoch),
&lookup_request,
) {
BindingRequiredLookupResult::Retired(outcome) => {
return ParticipantAckDecision::Respond(ParticipantAckResponse::from_retired(outcome));
}
BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
return ParticipantAckDecision::Respond(
ParticipantAckResponse::from_participant_unknown(outcome),
);
}
BindingRequiredLookupResult::StaleAuthority(outcome) => {
return ParticipantAckDecision::Respond(ParticipantAckResponse::from_stale_authority(
outcome,
));
}
BindingRequiredLookupResult::NoBinding(outcome) => {
return ParticipantAckDecision::Respond(ParticipantAckResponse::from_no_binding(
outcome,
));
}
BindingRequiredLookupResult::Authorized { member, .. } => member,
};
let current_cursor = member.cursor();
if request.through_seq < current_cursor
&& let Some(outcome) = AckRegression::new(ack_envelope(request), current_cursor)
{
return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_regression(outcome));
}
if request.through_seq == current_cursor {
return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_no_op(ack_envelope(
request,
)));
}
if request.through_seq > contiguously_available_through
&& let Some(outcome) = AckGap::new(ack_envelope(request), current_cursor)
{
return ParticipantAckDecision::Respond(ParticipantAckResponse::ack_gap(outcome));
}
let outcome = AckCommitted::new(ack_envelope(request));
ParticipantAckDecision::Commit(ParticipantAckCommit {
cursor_update: LiveMemberCursorUpdate::new(
request.conversation_id,
request.participant_id,
request.capability_generation,
current_cursor,
request.through_seq,
),
outcome,
})
}
pub fn apply_participant_ack_with_obligations<EF, V, LF>(
presented_identity: PresentedIdentity<'_, EF, V, LF>,
binding: &BindingState,
receiving_binding_epoch: BindingEpoch,
request: &ParticipantAck,
obligations: &RecipientAckObligations,
) -> Result<ParticipantAckDecision, RecipientAckObligationsContextError> {
let lookup_request = ParticipantBindingRequest::ParticipantAck(request.clone());
let member = match lookup_binding_required(
presented_identity,
binding,
Some(receiving_binding_epoch),
&lookup_request,
) {
BindingRequiredLookupResult::Retired(outcome) => {
return Ok(ParticipantAckDecision::Respond(
ParticipantAckResponse::from_retired(outcome),
));
}
BindingRequiredLookupResult::ParticipantUnknown(outcome) => {
return Ok(ParticipantAckDecision::Respond(
ParticipantAckResponse::from_participant_unknown(outcome),
));
}
BindingRequiredLookupResult::StaleAuthority(outcome) => {
return Ok(ParticipantAckDecision::Respond(
ParticipantAckResponse::from_stale_authority(outcome),
));
}
BindingRequiredLookupResult::NoBinding(outcome) => {
return Ok(ParticipantAckDecision::Respond(
ParticipantAckResponse::from_no_binding(outcome),
));
}
BindingRequiredLookupResult::Authorized { member, .. } => member,
};
let current_cursor = member.cursor();
let endpoint_is_obligation = obligations.contains_endpoint(
request.participant_id,
current_cursor,
request.through_seq,
)?;
if request.through_seq < current_cursor
&& let Some(outcome) = AckRegression::new(ack_envelope(request), current_cursor)
{
return Ok(ParticipantAckDecision::Respond(
ParticipantAckResponse::ack_regression(outcome),
));
}
if request.through_seq == current_cursor {
return Ok(ParticipantAckDecision::Respond(
ParticipantAckResponse::ack_no_op(ack_envelope(request)),
));
}
if !endpoint_is_obligation
&& let Some(outcome) = AckGap::new(ack_envelope(request), current_cursor)
{
return Ok(ParticipantAckDecision::Respond(
ParticipantAckResponse::ack_gap(outcome),
));
}
let outcome = AckCommitted::new(ack_envelope(request));
Ok(ParticipantAckDecision::Commit(ParticipantAckCommit {
cursor_update: LiveMemberCursorUpdate::new(
request.conversation_id,
request.participant_id,
request.capability_generation,
current_cursor,
request.through_seq,
),
outcome,
}))
}
const fn ack_envelope(request: &ParticipantAck) -> ParticipantAckEnvelope {
ParticipantAckEnvelope {
conversation_id: request.conversation_id,
participant_id: request.participant_id,
capability_generation: request.capability_generation,
through_seq: request.through_seq,
}
}