use super::*;
use crate::tsm::{CompletionOutcome, CoordinatedCompletion, CoordinatedTerminalPhase};
fn log_mismatch(outcome: &CompletionOutcome, invoke_id: u8, pdu: &str, warning: &WarnThrottle) {
if let CompletionOutcome::ServiceChoiceMismatch { expected, observed } = outcome {
warn_throttled!(
warning,
invoke_id,
pdu,
expected = expected.to_raw(),
observed = observed.to_raw(),
"Ignoring acknowledgment labelled for a different confirmed service"
);
}
}
pub(super) fn log_coordinated_mismatch(
outcome: &CoordinatedCompletion,
invoke_id: u8,
pdu: &str,
warning: &WarnThrottle,
) {
match outcome {
CoordinatedCompletion::Completed(outcome) => {
log_mismatch(outcome, invoke_id, pdu, warning);
}
CoordinatedCompletion::ServiceChoiceMismatch { expected, observed } => {
warn_throttled!(
warning,
invoke_id,
pdu,
expected = expected.to_raw(),
observed = observed.to_raw(),
"Ignoring acknowledgment labelled for a different confirmed service"
);
}
CoordinatedCompletion::Rejected => {}
}
}
fn find_reassembly_key(
seg_state: &HashMap<SegKey, SegmentedReceiveState>,
key: &SegKey,
) -> Option<SegKey> {
if seg_state.contains_key(key) {
return Some(key.clone());
}
seg_state
.keys()
.find(|existing| existing.0 == key.0 && existing.1 == key.1)
.cloned()
}
pub(super) fn find_provenance_conflict(
seg_state: &HashMap<SegKey, SegmentedReceiveState>,
tsm_mac: &MacAddr,
invoke_id: u8,
provenance: &TransportProvenance,
) -> Option<SegKey> {
seg_state
.keys()
.find(|existing| {
existing.0 == *tsm_mac && existing.1 == invoke_id && existing.2 != *provenance
})
.cloned()
}
pub(super) async fn take_current_reassembly(
tsm: &Arc<Mutex<Tsm>>,
seg_state: &mut HashMap<SegKey, SegmentedReceiveState>,
key: &SegKey,
) -> Option<SegmentedReceiveState> {
let found = find_reassembly_key(seg_state, key)?;
let state = seg_state.remove(&found)?;
if tsm
.lock()
.await
.owner_is_current(&found.0, found.1, &state.owner)
{
Some(state)
} else {
debug!(
invoke_id = found.1,
"Reclaimed stale segmented receive state"
);
None
}
}
pub(super) async fn current_reassembly_owner(
tsm: &Arc<Mutex<Tsm>>,
seg_state: &mut HashMap<SegKey, SegmentedReceiveState>,
key: &SegKey,
) -> Option<TransactionOwner> {
let found = find_reassembly_key(seg_state, key)?;
let owner = seg_state.get(&found)?.owner.clone();
if tsm.lock().await.owner_is_current(&found.0, found.1, &owner) {
Some(owner)
} else {
seg_state.remove(&found);
debug!(
invoke_id = found.1,
"Reclaimed stale segmented receive state"
);
None
}
}
pub(super) async fn admit_terminal_during_reassembly(
tsm: &Arc<Mutex<Tsm>>,
seg_state: &mut HashMap<SegKey, SegmentedReceiveState>,
key: &SegKey,
peer: &CanonicalPeer,
apdu: &Apdu,
) {
let Some(owner) = current_reassembly_owner(tsm, seg_state, key).await else {
return;
};
let _ = tsm
.lock()
.await
.try_claim_coordinated_terminal(&key.0, key.1, &owner, peer, apdu);
}
pub(super) enum TerminalDispatchOutcome {
Completion(CoordinatedCompletion),
PrematureSegmentedRequestAborted,
}
pub(super) struct TerminalResponse<'a> {
pub(super) tsm_mac: &'a MacAddr,
pub(super) invoke_id: u8,
pub(super) peer: &'a CanonicalPeer,
pub(super) apdu: &'a Apdu,
pub(super) response: TsmResponse,
pub(super) phase_gate: bool,
pub(super) owner: Option<TransactionOwner>,
}
pub(super) async fn complete_terminal_response(
tsm: &Arc<Mutex<Tsm>>,
terminal: TerminalResponse<'_>,
) -> TerminalDispatchOutcome {
let TerminalResponse {
tsm_mac,
invoke_id,
peer,
apdu,
response,
phase_gate,
owner,
} = terminal;
let mut expected_owner = owner;
let mut response = Some(response);
loop {
let mut tsm = tsm.lock().await;
let admission = if phase_gate {
tsm.coordinated_terminal_phase(tsm_mac, invoke_id, expected_owner.as_ref())
} else {
let owner = match expected_owner.clone() {
Some(owner) if tsm.owner_is_current(tsm_mac, invoke_id, &owner) => Some(owner),
Some(_) => None,
None => tsm.current_owner(tsm_mac, invoke_id),
};
match owner {
Some(owner) => CoordinatedTerminalPhase::Active(owner),
None => CoordinatedTerminalPhase::NoTransaction,
}
};
match admission {
CoordinatedTerminalPhase::FinalSegmentSendPolling { owner, issue } => {
drop(tsm);
issue.wait_until_polled().await;
expected_owner = Some(owner);
}
CoordinatedTerminalPhase::Active(owner) => {
let Some(response) = response.take() else {
return TerminalDispatchOutcome::Completion(CoordinatedCompletion::Rejected);
};
let completion = tsm.complete_coordinated_terminal_response(
tsm_mac,
invoke_id,
Some(&owner),
peer,
apdu,
response,
);
return TerminalDispatchOutcome::Completion(completion);
}
CoordinatedTerminalPhase::PrematureSegmentedRequest(owner) => {
let _ = tsm.try_claim_coordinated_terminal(tsm_mac, invoke_id, &owner, peer, apdu);
tsm.complete_transaction_for_owner(
tsm_mac,
invoke_id,
&owner,
None,
TsmResponse::remote(RemoteError::Abort {
reason: bacnet_types::enums::AbortReason::INVALID_APDU_IN_THIS_STATE,
}),
);
return TerminalDispatchOutcome::PrematureSegmentedRequestAborted;
}
CoordinatedTerminalPhase::NoTransaction => {
return TerminalDispatchOutcome::Completion(CoordinatedCompletion::Rejected);
}
}
}
}