bacnet-client 0.15.0

BACnet async client: TSM, segmentation, discovery, and service APIs
Documentation
use std::sync::Arc;

use bacnet_encoding::apdu::SegmentAck as SegmentAckPdu;
use bacnet_types::MacAddr;
#[cfg(test)]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(test)]
use tokio::sync::Notify;
use tokio::sync::{Mutex, mpsc};

use crate::tsm::{TransactionOwner, Tsm};

pub(super) struct TransactionCleanup {
    pub(super) mac: MacAddr,
    pub(super) invoke_id: u8,
    pub(super) owner: TransactionOwner,
    pub(super) cancel_tsm: bool,
    pub(super) seg_ack_sender: Option<mpsc::Sender<SegmentAckPdu>>,
}

pub(super) struct TransactionGuard {
    tsm: Arc<Mutex<Tsm>>,
    cleanup_tx: mpsc::UnboundedSender<TransactionCleanup>,
    cleanup: Option<TransactionCleanup>,
}

impl TransactionGuard {
    pub(super) fn new(
        tsm: Arc<Mutex<Tsm>>,
        cleanup_tx: mpsc::UnboundedSender<TransactionCleanup>,
        mac: MacAddr,
        invoke_id: u8,
        owner: TransactionOwner,
        seg_ack_sender: Option<mpsc::Sender<SegmentAckPdu>>,
    ) -> Self {
        Self {
            tsm,
            cleanup_tx,
            cleanup: Some(TransactionCleanup {
                mac,
                invoke_id,
                owner,
                cancel_tsm: true,
                seg_ack_sender,
            }),
        }
    }

    pub(super) fn mark_completed(&mut self) {
        self.cleanup = None;
    }
}

impl Drop for TransactionGuard {
    fn drop(&mut self) {
        let Some(cleanup) = self.cleanup.take() else {
            return;
        };
        if let Err(error) = self.cleanup_tx.send(cleanup) {
            let cleanup = error.0;
            if let Ok(mut tsm) = self.tsm.try_lock() {
                tsm.cancel_transaction_for_owner(&cleanup.mac, cleanup.invoke_id, &cleanup.owner);
            }
        }
    }
}

#[cfg(test)]
#[derive(Default)]
pub(super) struct SegmentedPostWaitCleanupHook {
    enabled: AtomicBool,
    reached: Notify,
    release: Notify,
}

#[cfg(test)]
impl SegmentedPostWaitCleanupHook {
    pub(super) fn enable(&self) {
        self.enabled.store(true, Ordering::SeqCst);
    }

    pub(super) async fn wait_until_reached(&self) {
        self.reached.notified().await;
    }

    pub(super) fn release(&self) {
        self.release.notify_one();
    }

    pub(super) async fn pause_if_enabled(&self) {
        if self.enabled.swap(false, Ordering::SeqCst) {
            self.reached.notify_one();
            self.release.notified().await;
        }
    }
}

#[cfg(test)]
#[derive(Default)]
pub(super) struct SegmentedCleanupHook {
    delay_next: AtomicBool,
    reached: Notify,
    release: Notify,
    processed: Notify,
    last_removed: AtomicBool,
}

#[cfg(test)]
impl SegmentedCleanupHook {
    pub(super) fn delay_next(&self) {
        self.delay_next.store(true, Ordering::SeqCst);
    }

    pub(super) async fn wait_until_reached(&self) {
        self.reached.notified().await;
    }

    pub(super) fn release(&self) {
        self.release.notify_one();
    }

    pub(super) async fn pause_if_enabled(&self) {
        if self.delay_next.swap(false, Ordering::SeqCst) {
            self.reached.notify_one();
            self.release.notified().await;
        }
    }

    pub(super) fn record_processed(&self, removed: bool) {
        self.last_removed.store(removed, Ordering::SeqCst);
        self.processed.notify_one();
    }

    pub(super) async fn wait_processed(&self) -> bool {
        self.processed.notified().await;
        self.last_removed.load(Ordering::SeqCst)
    }
}