bacnet-network 0.12.0

BACnet network layer: routing, router tables, priority channels
Documentation
use super::*;
use bacnet_encoding::npdu::decode_npdu;
use bacnet_transport::port::ReceivedNpdu;
use std::future::{poll_fn, Future};
use std::sync::{atomic::AtomicUsize, Mutex};
use std::task::Poll;
use tokio::sync::mpsc;

struct HeldPort {
    issued: Arc<AtomicUsize>,
    frames: Mutex<Vec<(Npdu, Vec<u8>)>>,
    fail: bool,
}
impl TransportPort for HeldPort {
    async fn start(&mut self) -> Result<mpsc::Receiver<ReceivedNpdu>, Error> {
        Ok(mpsc::channel(1).1)
    }
    async fn stop(&mut self) -> Result<(), Error> {
        Ok(())
    }
    async fn send_unicast(&self, wire: &[u8], mac: &[u8]) -> Result<(), Error> {
        assert_eq!(
            self.issued.load(Ordering::SeqCst),
            1,
            "issuance must precede transport polling"
        );
        self.frames.lock().unwrap().push((
            decode_npdu(Bytes::copy_from_slice(wire)).unwrap(),
            mac.to_vec(),
        ));
        if self.fail {
            Err(Error::Encoding("injected transport failure".into()))
        } else {
            std::future::pending().await
        }
    }
    async fn send_broadcast(&self, _: &[u8]) -> Result<(), Error> {
        unreachable!()
    }
    fn local_receive_apdu_capacity(&self) -> u16 {
        1476
    }

    fn local_mac(&self) -> &[u8] {
        &[2]
    }
}
fn layer(fail: bool) -> (NetworkLayer<HeldPort>, Arc<AtomicUsize>) {
    let issued = Arc::new(AtomicUsize::new(0));
    (
        NetworkLayer::new(HeldPort {
            issued: issued.clone(),
            frames: Mutex::new(Vec::new()),
            fail,
        }),
        issued,
    )
}

#[tokio::test]
async fn issuance_waits_for_poll_and_complete_encoding_but_not_transport_result() {
    for destination in [
        None,
        Some(NpduAddress {
            network: 123,
            mac_address: MacAddr::from_slice(&[3]),
        }),
    ] {
        let (network, issued) = layer(false);
        let operation = network.send_apdu_on_issuance(
            &[0x20, 1, 15],
            &[1],
            destination.as_ref(),
            false,
            NetworkPriority::URGENT,
            || {
                issued.fetch_add(1, Ordering::SeqCst);
            },
        );
        assert_eq!(issued.load(Ordering::SeqCst), 0);
        assert!(network.transport.frames.lock().unwrap().is_empty());
        tokio::pin!(operation);
        poll_fn(|cx| {
            assert!(operation.as_mut().poll(cx).is_pending());
            Poll::Ready(())
        })
        .await;
        assert_eq!(issued.load(Ordering::SeqCst), 1);
        let frames = network.transport.frames.lock().unwrap();
        assert_eq!(frames.len(), 1);
        assert_eq!(frames[0].0.destination, destination);
        assert_eq!(frames[0].0.payload.as_ref(), &[0x20, 1, 15]);
        assert_eq!(frames[0].0.priority, NetworkPriority::URGENT);
        assert!(!frames[0].0.expecting_reply);
        assert_eq!(frames[0].1, vec![1]);
    }
}

#[tokio::test]
async fn encoding_failure_never_issues_and_transport_failure_does_not_undo_issuance() {
    let (network, issued) = layer(true);
    let invalid = NpduAddress {
        network: 0,
        mac_address: MacAddr::from_slice(&[3]),
    };
    let error = network
        .send_apdu_on_issuance(
            &[0x20, 1, 15],
            &[1],
            Some(&invalid),
            false,
            NetworkPriority::NORMAL,
            || {
                issued.fetch_add(1, Ordering::SeqCst);
            },
        )
        .await
        .unwrap_err();
    assert!(matches!(error, Error::Encoding(s) if s.contains("DNET")));
    assert_eq!(issued.load(Ordering::SeqCst), 0);
    assert!(network.transport.frames.lock().unwrap().is_empty());
    assert!(network
        .send_apdu_on_issuance(
            &[0x20, 1, 15],
            &[1],
            None,
            false,
            NetworkPriority::NORMAL,
            || {
                issued.fetch_add(1, Ordering::SeqCst);
            }
        )
        .await
        .is_err());
    assert_eq!(issued.load(Ordering::SeqCst), 1);
    assert_eq!(network.transport.frames.lock().unwrap().len(), 1);
}