use super::*;
#[doc(hidden)]
pub struct PacedEndpointOperation {
pub(super) inner: Arc<EndpointRequesterInner>,
pub(super) destination: EndpointApduDestination,
pub(super) data_attributes: Vec<DataAttribute>,
pub(super) request: EndpointOperationRequest,
pub(super) service_data: BytesMut,
pub(super) pace: PaceGuard,
}
impl PacedEndpointOperation {
#[doc(hidden)]
pub fn destination(&self) -> &EndpointApduDestination {
&self.destination
}
#[doc(hidden)]
pub fn prepare(self) -> Result<PreparedEndpointOperation, Error> {
let Self {
inner,
destination,
data_attributes,
request,
service_data,
pace,
} = self;
let service = request.service();
let TransactionPeer {
tsm_mac,
canonical: peer,
} = outbound_tsm_peer(&destination);
let (invoke_id, registration) = {
let mut tsm = inner
.tsm
.lock()
.map_err(|_| Error::Encoding("endpoint requester state is poisoned".into()))?;
if !inner.open.load(Ordering::Acquire) {
return Err(shutdown_error());
}
tsm.register_coordinated_transaction_with_policy(
tsm_mac.clone(),
peer,
service,
false,
request.terminal_policy(),
)
.map_err(|error| Error::Encoding(error.to_string()))?
};
let max_apdu_length = max_apdu_header_at_or_below(u32::from(inner.max_apdu_length))?;
let guard = EndpointRequestGuard {
inner,
destination: tsm_mac,
invoke_id,
owner: registration.owner.clone(),
active: true,
};
let pdu = Apdu::ConfirmedRequest(ConfirmedRequestPdu {
segmented: false,
more_follows: false,
segmented_response_accepted: false,
max_segments: None,
max_apdu_length,
invoke_id,
sequence_number: None,
proposed_window_size: None,
service_choice: service,
service_request: service_data.freeze(),
});
let mut encoded = BytesMut::new();
encode_apdu(&mut encoded, &pdu)?;
Ok(PreparedEndpointOperation {
guard,
destination,
data_attributes,
request,
encoded: encoded.to_vec(),
response: registration.response,
_pace: pace,
})
}
}
#[doc(hidden)]
pub struct PreparedEndpointOperation {
pub(super) guard: EndpointRequestGuard,
pub(super) destination: EndpointApduDestination,
pub(super) data_attributes: Vec<DataAttribute>,
pub(super) request: EndpointOperationRequest,
pub(super) encoded: Vec<u8>,
pub(super) response: tokio::sync::oneshot::Receiver<TsmResponse>,
pub(super) _pace: PaceGuard,
}
#[doc(hidden)]
pub struct EndpointOperationOutcome {
pub result: Result<EndpointOperationAck, Error>,
pub attempted: bool,
}
impl PreparedEndpointOperation {
#[doc(hidden)]
pub fn invoke_id(&self) -> u8 {
self.guard.invoke_id
}
#[doc(hidden)]
pub async fn execute(mut self) -> EndpointOperationOutcome {
let mut attempted = false;
let result = self.execute_inner(&mut attempted).await.and_then(|bytes| {
if bytes.len() + 3 > usize::from(self.guard.inner.max_apdu_length) {
return Err(bacnet_types::error::SegmentationError::EndpointAckTooLong.into());
}
self.request.decode(&bytes)
});
EndpointOperationOutcome { result, attempted }
}
fn terminal(&mut self, response: TsmResponse) -> Result<bytes::Bytes, Error> {
self.guard.active = false;
confirmed_response_result(response)
}
async fn execute_inner(&mut self, attempted: &mut bool) -> Result<bytes::Bytes, Error> {
let inner = Arc::clone(&self.guard.inner);
for attempt in 0..=inner.retries {
if !inner.open.load(Ordering::Acquire) {
return Err(shutdown_error());
}
if let Ok(response) = self.response.try_recv() {
*attempted = true;
return self.terminal(response);
}
let send = inner.egress.admit_owned_apdu(
self.encoded.clone(),
self.destination.clone(),
true,
NetworkPriority::Normal,
self.data_attributes.clone(),
);
let send_result = match send {
Ok(send) => {
let outcome = send.complete().await;
*attempted |= outcome.attempted;
outcome.result
}
Err(error) => Err(error.into()),
};
if let Ok(response) = self.response.try_recv() {
*attempted = true;
return self.terminal(response);
}
send_result?;
match tokio::time::timeout(inner.timeout, &mut self.response).await {
Ok(Ok(response)) => {
*attempted = true;
return self.terminal(response);
}
Ok(Err(_)) if !inner.open.load(Ordering::Acquire) => return Err(shutdown_error()),
Ok(Err(_)) => return Err(Error::Encoding("TSM response channel closed".into())),
Err(_) if attempt < inner.retries => {}
Err(_) => return Err(Error::Timeout(inner.timeout)),
}
}
unreachable!("inclusive retry loop always returns")
}
}