use std::sync::{Arc, Mutex};
use super::request_peer::{canonical_requester, CanonicalRequester};
use bacnet_encoding::apdu::ConfirmedRequest;
use bacnet_encoding::npdu::NpduAddress;
use bacnet_transport::port::{DirectScIdentity, TransportProvenance};
const MAX_ENTRIES: usize = 256;
const MAX_TRACKED_SERVICE_REQUEST_BYTES: usize = 64 * 1024;
struct Entry {
id: u64,
requester: CanonicalRequester,
direct_identity: Option<DirectScIdentity>,
invoke_id: u8,
request: ConfirmedRequest,
}
#[derive(Default)]
struct TrackerState {
next_id: u64,
entries: Vec<Entry>,
}
#[derive(Default)]
pub(super) struct ConfirmedRequestTracker {
state: Mutex<TrackerState>,
pub(super) lso: Arc<super::lso_replay::LsoReplayCache>,
}
pub(super) enum ConfirmedRequestAdmission {
Duplicate,
New(PendingConfirmedRequest),
}
pub(super) struct PendingConfirmedRequest {
tracker: Arc<ConfirmedRequestTracker>,
id: Option<u64>,
}
impl ConfirmedRequestTracker {
pub(super) fn begin(
self: &Arc<Self>,
source_mac: &[u8],
source_network: Option<&NpduAddress>,
provenance: TransportProvenance,
request: ConfirmedRequest,
) -> ConfirmedRequestAdmission {
if request.service_request.len() > MAX_TRACKED_SERVICE_REQUEST_BYTES {
return ConfirmedRequestAdmission::New(PendingConfirmedRequest::untracked(self));
}
let requester = canonical_requester(source_mac, source_network);
let direct_identity = provenance.direct_sc_identity();
let invoke_id = request.invoke_id;
let mut state = self
.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
if state.entries.iter().any(|entry| {
entry.requester == requester
&& entry.direct_identity == direct_identity
&& entry.invoke_id == invoke_id
&& entry.request == request
}) {
return ConfirmedRequestAdmission::Duplicate;
}
if state.entries.len() >= MAX_ENTRIES {
return ConfirmedRequestAdmission::New(PendingConfirmedRequest::untracked(self));
}
let id = state.next_id;
let Some(next_id) = id.checked_add(1) else {
return ConfirmedRequestAdmission::New(PendingConfirmedRequest::untracked(self));
};
state.next_id = next_id;
state.entries.push(Entry {
id,
requester,
direct_identity,
invoke_id,
request,
});
ConfirmedRequestAdmission::New(PendingConfirmedRequest {
tracker: Arc::clone(self),
id: Some(id),
})
}
}
impl PendingConfirmedRequest {
fn untracked(tracker: &Arc<ConfirmedRequestTracker>) -> Self {
Self {
tracker: Arc::clone(tracker),
id: None,
}
}
}
impl Drop for PendingConfirmedRequest {
fn drop(&mut self) {
if let Some(id) = self.id {
self.tracker
.state
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.entries
.retain(|entry| entry.id != id);
}
}
}
#[cfg(test)]
#[path = "confirmed_tracker_tests.rs"]
mod tests;