use super::*;
#[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>,
}
#[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(Error::Segmentation(
"endpoint operation ACK exceeds configured max APDU".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")
}
}