bacnet-network 0.10.1

BACnet network layer: routing, router tables, priority channels
Documentation
use super::*;
use bacnet_encoding::npdu::NpduAddress;
use bacnet_transport::sc::{LoopbackWebSocket, ScTransport, WebSocketPort};
use bacnet_transport::sc_frame::{
    decode_sc_message, encode_sc_message, ScFunction, ScMessage, ScOption, Vmac,
};
use tokio::time::{timeout, Duration};

async fn sc_hub_accept(ws_hub: &LoopbackWebSocket, hub_vmac: Vmac) {
    let data = ws_hub.recv().await.unwrap();
    let req = decode_sc_message(&data).unwrap();
    assert_eq!(req.function, ScFunction::ConnectRequest);

    let mut accept_payload = Vec::with_capacity(26);
    accept_payload.extend_from_slice(&hub_vmac);
    accept_payload.extend_from_slice(&[0u8; 16]);
    accept_payload.extend_from_slice(&1476u16.to_be_bytes());
    accept_payload.extend_from_slice(&1476u16.to_be_bytes());

    let accept = ScMessage {
        function: ScFunction::ConnectAccept,
        message_id: req.message_id,
        originating_vmac: None,
        destination_vmac: None,
        dest_options: Vec::new(),
        data_options: Vec::new(),
        payload: Bytes::from(accept_payload),
    };
    let mut buf = BytesMut::new();
    encode_sc_message(&mut buf, &accept);
    ws_hub.send(&buf).await.unwrap();
}

#[test]
fn forward_unicast_preserves_data_attributes() {
    let (tx_a, _rx_a) = mpsc::channel::<SendRequest>(256);
    let (tx_b, mut rx_b) = mpsc::channel::<SendRequest>(256);
    let send_txs = vec![tx_a, tx_b];

    let route = crate::router_table::RouteEntry {
        port_index: 1,
        directly_connected: true,
        next_hop_mac: MacAddr::new(),
        last_seen: None,
        reachability: crate::router_table::ReachabilityStatus::Reachable,
        busy_until: None,
        flap_count: 0,
        last_port_change: None,
    };

    let npdu = Npdu {
        is_network_message: false,
        expecting_reply: false,
        priority: bacnet_types::enums::NetworkPriority::NORMAL,
        destination: Some(NpduAddress {
            network: 2000,
            mac_address: MacAddr::from_slice(&[0x01, 0x02]),
        }),
        source: None,
        hop_count: 10,
        payload: Bytes::from_static(&[0x10, 0x08]),
        ..Npdu::default()
    };
    let data_attributes = vec![
        DataAttribute {
            option_type: 1,
            must_understand: true,
            data: Vec::new(),
        },
        DataAttribute {
            option_type: 31,
            must_understand: false,
            data: vec![0x12, 0x34, 0x56],
        },
    ];

    forward_unicast(&send_txs, &route, 1000, &[0x0A], npdu, 0, &data_attributes);

    match rx_b.try_recv().unwrap() {
        SendRequest::Unicast {
            npdu: data,
            data_attributes: sent_attributes,
            ..
        } => {
            let decoded = decode_npdu(data).unwrap();
            assert!(decoded.destination.is_none());
            assert!(decoded.source.is_some());
            assert_eq!(sent_attributes, data_attributes);
        }
        SendRequest::Broadcast { .. } => panic!("expected forwarded unicast"),
    }
}

#[test]
fn forward_broadcast_preserves_data_attributes_on_each_output_port() {
    let (tx_a, mut rx_a) = mpsc::channel::<SendRequest>(256);
    let (tx_b, mut rx_b) = mpsc::channel::<SendRequest>(256);
    let (tx_c, mut rx_c) = mpsc::channel::<SendRequest>(256);
    let send_txs = vec![tx_a, tx_b, tx_c];

    let npdu = Npdu {
        is_network_message: false,
        expecting_reply: false,
        priority: bacnet_types::enums::NetworkPriority::NORMAL,
        destination: Some(NpduAddress {
            network: 0xFFFF,
            mac_address: MacAddr::new(),
        }),
        source: None,
        hop_count: 10,
        payload: Bytes::from_static(&[0x10, 0x08]),
        ..Npdu::default()
    };
    let data_attributes = vec![DataAttribute {
        option_type: 2,
        must_understand: true,
        data: vec![0xAA],
    }];

    forward_broadcast(&send_txs, 1, 1000, &[0x0A], &npdu, &data_attributes);

    for sent in [rx_a.try_recv().unwrap(), rx_c.try_recv().unwrap()] {
        match sent {
            SendRequest::Broadcast {
                npdu: data,
                data_attributes: sent_attributes,
            } => {
                let decoded = decode_npdu(data).unwrap();
                assert_eq!(decoded.destination.as_ref().unwrap().network, 0xFFFF);
                assert!(decoded.source.is_some());
                assert_eq!(sent_attributes, data_attributes);
            }
            SendRequest::Unicast { .. } => panic!("expected forwarded broadcast"),
        }
    }
    assert!(rx_b.try_recv().is_err());
}

#[tokio::test]
async fn sc_to_sc_forwarding_preserves_data_attributes_as_data_options() {
    let (ws_client_a, ws_hub_a) = LoopbackWebSocket::pair();
    let (ws_client_b, ws_hub_b) = LoopbackWebSocket::pair();
    let hub_a_vmac: Vmac = [0xA0; 6];
    let hub_b_vmac: Vmac = [0xB0; 6];
    let dest_vmac: Vmac = [0x22; 6];

    let accept_a = tokio::spawn(async move {
        sc_hub_accept(&ws_hub_a, hub_a_vmac).await;
        ws_hub_a
    });
    let accept_b = tokio::spawn(async move {
        sc_hub_accept(&ws_hub_b, hub_b_vmac).await;
        ws_hub_b
    });

    let port_a = RouterPort {
        transport: ScTransport::new(ws_client_a, [0x01; 6]),
        network_number: 1000,
    };
    let port_b = RouterPort {
        transport: ScTransport::new(ws_client_b, [0x02; 6]),
        network_number: 2000,
    };
    let (mut router, _local_rx) = BACnetRouter::start(vec![port_a, port_b]).await.unwrap();
    let ws_hub_a = accept_a.await.unwrap();
    let ws_hub_b = accept_b.await.unwrap();

    let apdu = Bytes::from_static(&[0x10, 0x08]);
    let npdu = Npdu {
        is_network_message: false,
        expecting_reply: false,
        priority: bacnet_types::enums::NetworkPriority::NORMAL,
        destination: Some(NpduAddress {
            network: 2000,
            mac_address: MacAddr::from_slice(&dest_vmac),
        }),
        source: None,
        hop_count: 255,
        payload: apdu.clone(),
        ..Npdu::default()
    };
    let mut npdu_buf = BytesMut::new();
    encode_npdu(&mut npdu_buf, &npdu).unwrap();

    let inbound = ScMessage {
        function: ScFunction::EncapsulatedNpdu,
        message_id: 0x2233,
        originating_vmac: Some(hub_a_vmac),
        destination_vmac: None,
        dest_options: Vec::new(),
        data_options: vec![
            ScOption {
                option_type: 1,
                must_understand: true,
                data: Vec::new(),
            },
            ScOption {
                option_type: 31,
                must_understand: false,
                data: vec![0x12, 0x34, 0x56],
            },
        ],
        payload: npdu_buf.freeze(),
    };
    let mut sc_buf = BytesMut::new();
    encode_sc_message(&mut sc_buf, &inbound);
    ws_hub_a.send(&sc_buf).await.unwrap();

    let forwarded = async {
        for _ in 0..5 {
            let data = ws_hub_b.recv().await.unwrap();
            let msg = decode_sc_message(&data).unwrap();
            if msg.function != ScFunction::EncapsulatedNpdu {
                continue;
            }
            let decoded = decode_npdu(msg.payload.clone()).unwrap();
            if !decoded.is_network_message && decoded.payload == apdu {
                return msg;
            }
        }
        panic!("did not receive forwarded APDU");
    };
    let msg = timeout(Duration::from_secs(1), forwarded)
        .await
        .expect("timed out waiting for forwarded SC NPDU");

    assert_eq!(msg.destination_vmac, Some(dest_vmac));
    assert_eq!(msg.data_options.len(), 2);
    assert_eq!(msg.data_options[0].option_type, 1);
    assert!(msg.data_options[0].must_understand);
    assert!(msg.data_options[0].data.is_empty());
    assert_eq!(msg.data_options[1].option_type, 31);
    assert!(!msg.data_options[1].must_understand);
    assert_eq!(msg.data_options[1].data, vec![0x12, 0x34, 0x56]);

    router.stop().await;
}