pub(crate) mod session;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use bacnet_client::client::{DeviceCollisionEvent, DeviceEvent, DeviceEventKind};
use bacnet_client::discovery::DiscoveredDevice;
use tokio::sync::broadcast::error::RecvError;
use tokio::sync::mpsc;
use super::message::{hex, AddressStyle, DeviceRow, WorkerEvent};
pub(crate) const EVENT_CHANNEL_CAPACITY: usize = 1_024;
pub(crate) const COMMAND_CHANNEL_CAPACITY: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Offer {
Sent,
Dropped,
Closed,
}
#[derive(Clone)]
pub(crate) struct EventSink {
tx: mpsc::Sender<WorkerEvent>,
dropped: Arc<AtomicU64>,
}
impl EventSink {
pub(crate) fn channel(capacity: usize) -> (Self, mpsc::Receiver<WorkerEvent>) {
let (tx, rx) = mpsc::channel(capacity);
let sink = Self {
tx,
dropped: Arc::new(AtomicU64::new(0)),
};
(sink, rx)
}
pub(crate) fn dropped_counter(&self) -> Arc<AtomicU64> {
Arc::clone(&self.dropped)
}
pub(crate) fn add_dropped(&self, n: u64) {
self.dropped.fetch_add(n, Ordering::Relaxed);
}
pub(crate) fn offer(&self, event: WorkerEvent) -> Offer {
match self.tx.try_send(event) {
Ok(()) => Offer::Sent,
Err(mpsc::error::TrySendError::Full(_)) => {
self.add_dropped(1);
Offer::Dropped
}
Err(mpsc::error::TrySendError::Closed(_)) => Offer::Closed,
}
}
pub(crate) async fn deliver(&self, event: WorkerEvent) -> bool {
self.tx.send(event).await.is_ok()
}
}
pub(crate) struct DeviceFeed {
style: AddressStyle,
pub(crate) needs_resync: bool,
pub(crate) devices_open: bool,
pub(crate) collisions_open: bool,
}
impl DeviceFeed {
pub(crate) fn new(style: AddressStyle) -> Self {
Self {
style,
needs_resync: false,
devices_open: true,
collisions_open: true,
}
}
pub(crate) fn row(&self, device: &DiscoveredDevice) -> DeviceRow {
let address = match (&device.source_network, &device.source_address) {
(Some(_), Some(remote)) => format!(
"{} via {}",
hex(remote.as_slice()),
self.style.format(device.mac_address.as_slice())
),
_ => self.style.format(device.mac_address.as_slice()),
};
DeviceRow {
instance: device.object_identifier.instance_number(),
address,
network: device.source_network,
vendor_id: device.vendor_id,
max_apdu: device.max_apdu_length,
segmentation: device.segmentation_supported,
last_seen: device.last_seen,
}
}
pub(crate) fn on_device(&mut self, sink: &EventSink, received: Result<DeviceEvent, RecvError>) {
match received {
Ok(event) => {
let row = self.row(&event.device);
let event = match event.kind {
DeviceEventKind::Discovered => WorkerEvent::Discovered(row),
DeviceEventKind::Updated => WorkerEvent::Updated(row),
DeviceEventKind::Lost => {
tracing::info!("device {} lost ({})", row.instance, row.address);
WorkerEvent::Lost(row)
}
};
if sink.offer(event) == Offer::Dropped {
self.needs_resync = true;
}
}
Err(RecvError::Lagged(missed)) => {
sink.add_dropped(missed);
self.needs_resync = true;
}
Err(RecvError::Closed) => self.devices_open = false,
}
}
pub(crate) fn on_collision(
&mut self,
sink: &EventSink,
received: Result<DeviceCollisionEvent, RecvError>,
) -> Option<WorkerEvent> {
match received {
Ok(event) => Some(WorkerEvent::Collision {
retained: self.row(&event.retained),
incoming: self.row(&event.incoming),
}),
Err(RecvError::Lagged(missed)) => {
sink.add_dropped(missed);
None
}
Err(RecvError::Closed) => {
self.collisions_open = false;
None
}
}
}
}