bacnet-server 0.15.0

BACnet server: APDU dispatch and service handlers
Documentation
use super::*;

use bacnet_services::cov::SubscribeCOVPropertyRequest;
use bacnet_services::cov_multiple::{
    COVReference, COVSubscriptionSpecification, SubscribeCOVPropertyMultipleRequest,
};
use bacnet_types::constructed::PropertyReference;

#[tokio::test]
async fn subscribe_initial_and_resub_ack_precede_notification_and_cancel_is_quiet() {
    let fixture = DispatchFixture::new(life_safety_db(), []).await;
    let encode_request = |confirmed: Option<bool>, lifetime: Option<u32>| {
        let request = SubscribeCOVPropertyRequest {
            subscriber_process_identifier: 12,
            monitored_object_identifier: point_oid(),
            issue_confirmed_notifications: confirmed,
            lifetime,
            monitored_property_identifier: PropertyIdentifier::SILENCED,
            monitored_property_array_index: None,
            cov_increment: None,
        };
        let mut encoded = BytesMut::new();
        request.encode(&mut encoded).unwrap();
        encoded.freeze()
    };

    for (invoke_id, confirmed, lifetime, expected_frames) in [
        (1, Some(false), Some(300), 2),
        (2, Some(false), Some(600), 2),
        (3, None, None, 1),
    ] {
        fixture
            .dispatch(
                invoke_id,
                ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY,
                encode_request(confirmed, lifetime),
            )
            .await;
        let apdus = fixture.take_apdus();
        assert_eq!(apdus.len(), expected_frames);
        assert!(matches!(apdus[0], Apdu::SimpleAck(_)));
        if expected_frames == 2 {
            assert_eq!(
                single_properties(&apdus[1]),
                vec![
                    PropertyIdentifier::SILENCED,
                    PropertyIdentifier::STATUS_FLAGS,
                ]
            );
        }
    }
}

fn multiple_request(lifetime: Option<u32>, max_notification_delay: Option<u32>) -> Bytes {
    let request = SubscribeCOVPropertyMultipleRequest {
        subscriber_process_identifier: 13,
        issue_confirmed_notifications: false,
        lifetime,
        max_notification_delay,
        list_of_cov_subscription_specifications: vec![COVSubscriptionSpecification {
            monitored_object_identifier: point_oid(),
            list_of_cov_references: vec![COVReference {
                monitored_property: PropertyReference {
                    property_identifier: PropertyIdentifier::SILENCED,
                    property_array_index: None,
                },
                cov_increment: None,
                timestamped: false,
            }],
        }],
    };
    let mut encoded = BytesMut::new();
    request.encode(&mut encoded).unwrap();
    encoded.freeze()
}

#[tokio::test]
async fn multiple_initial_and_resub_ack_precede_payload_and_cancel_is_quiet() {
    let fixture = DispatchFixture::new(life_safety_db(), []).await;

    for (invoke_id, lifetime, delay, expected_frames) in [
        (4, Some(300), Some(10), 2),
        (5, Some(600), Some(10), 2),
        (6, None, None, 1),
    ] {
        fixture
            .dispatch(
                invoke_id,
                ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY_MULTIPLE,
                multiple_request(lifetime, delay),
            )
            .await;
        let apdus = fixture.take_apdus();
        assert_eq!(apdus.len(), expected_frames);
        assert!(matches!(apdus[0], Apdu::SimpleAck(_)));
        if expected_frames == 2 {
            let Apdu::UnconfirmedRequest(notification) = &apdus[1] else {
                panic!("expected unconfirmed COVNotificationMultiple");
            };
            let notification =
                COVNotificationMultipleRequest::decode(&notification.service_request).unwrap();
            let properties: Vec<_> = notification.list_of_cov_notifications[0]
                .list_of_values
                .iter()
                .map(|value| value.property_identifier)
                .collect();
            assert_eq!(
                properties,
                vec![
                    PropertyIdentifier::SILENCED,
                    PropertyIdentifier::STATUS_FLAGS,
                ]
            );
        }
    }
    assert!(fixture.cov_table.read().await.is_empty());
}

#[path = "property_parameters.rs"]
mod property_parameters;

#[path = "ordinary_parameters.rs"]
mod ordinary_parameters;

#[tokio::test]
async fn initial_single_and_multiple_life_safety_payloads_include_one_status_flags() {
    let single = subscription(
        Some(PropertyIdentifier::OPERATION_EXPECTED),
        CovNotificationKind::Single,
        1,
    );
    let mut multiple = subscription(
        Some(PropertyIdentifier::SILENCED),
        CovNotificationKind::Multiple,
        2,
    );
    multiple.subscriber_mac = single.subscriber_mac.clone();
    let fixture = ExactFixture::new([single.clone(), multiple.clone()]).await;
    let (single, multiple) = {
        let table = fixture.cov_table.read().await;
        (
            table
                .get_subscription(&single.key().unwrap())
                .unwrap()
                .clone(),
            table
                .get_subscription(&multiple.key().unwrap())
                .unwrap()
                .clone(),
        )
    };

    BACnetServer::<TestTransport>::fire_initial_cov_notification(
        &crate::server::cov_notify_context::CovNotifyContext {
            db: &fixture.db,
            network: &fixture.network,
            cov_table: &fixture.cov_table,
            cov_in_flight: &fixture.cov_in_flight,
            notification_transactions: &fixture.transactions,
            comm_state: &fixture.comm_state,
            config: &Arc::default(),
        },
        &single,
    )
    .await;
    BACnetServer::<TestTransport>::fire_initial_cov_notification_multiple(
        &crate::server::cov_notify_context::CovNotifyContext {
            db: &fixture.db,
            network: &fixture.network,
            cov_table: &fixture.cov_table,
            cov_in_flight: &fixture.cov_in_flight,
            notification_transactions: &fixture.transactions,
            comm_state: &fixture.comm_state,
            config: &Arc::default(),
        },
        &[multiple],
    )
    .await;

    let apdus = fixture.take_apdus();
    assert_eq!(apdus.len(), 2);
    assert_eq!(
        single_properties(&apdus[0]),
        vec![
            PropertyIdentifier::OPERATION_EXPECTED,
            PropertyIdentifier::STATUS_FLAGS,
        ]
    );
    let Apdu::UnconfirmedRequest(request) = &apdus[1] else {
        panic!("expected unconfirmed multiple notification");
    };
    let notification = COVNotificationMultipleRequest::decode(&request.service_request).unwrap();
    let properties: Vec<_> = notification.list_of_cov_notifications[0]
        .list_of_values
        .iter()
        .map(|value| value.property_identifier)
        .collect();
    assert_eq!(
        properties,
        vec![
            PropertyIdentifier::SILENCED,
            PropertyIdentifier::STATUS_FLAGS,
        ]
    );
}