bacnet-server 0.15.0

BACnet server: APDU dispatch and service handlers
Documentation
use super::*;
use crate::server::GetEventInformationBudget;
use bacnet_types::bitstring::EventTransitionBits;
use bacnet_types::error::RemoteError;
use std::sync::{
    Arc,
    atomic::{AtomicUsize, Ordering},
};

struct Counted {
    inner: ProjectionFixture,
    calls: Arc<AtomicUsize>,
    projections: Arc<AtomicUsize>,
}
impl BACnetObject for Counted {
    fn object_identifier(&self) -> ObjectIdentifier {
        self.calls.fetch_add(1, Ordering::SeqCst);
        self.inner.object_identifier()
    }
    fn object_name(&self) -> &str {
        self.calls.fetch_add(1, Ordering::SeqCst);
        self.inner.object_name()
    }
    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
        self.calls.fetch_add(1, Ordering::SeqCst);
        self.projections.fetch_add(1, Ordering::SeqCst);
        self.inner.property_list()
    }
    fn read_property(
        &self,
        property: PropertyIdentifier,
        index: Option<u32>,
    ) -> Result<PropertyValue, Error> {
        self.calls.fetch_add(1, Ordering::SeqCst);
        self.inner.read_property(property, index)
    }
    fn write_property(
        &mut self,
        property: PropertyIdentifier,
        index: Option<u32>,
        value: PropertyValue,
        priority: Option<u8>,
    ) -> Result<(), Error> {
        self.inner.write_property(property, index, value, priority)
    }
}

#[test]
fn get_event_information_preflight_no_callbacks_and_full_scan_after_capacity() {
    let calls = Arc::new(AtomicUsize::new(0));
    let projections = Arc::new(AtomicUsize::new(0));
    let mut db = ObjectDatabase::new();
    for inner in [
        ProjectionFixture::summary(1),
        ProjectionFixture::summary(2),
        ProjectionFixture::notification_class(
            99,
            Some(PropertyValue::Unsigned(42)),
            Some(PropertyValue::List(vec![PropertyValue::Unsigned(1); 3])),
        ),
    ] {
        db.add(Box::new(Counted {
            inner,
            calls: calls.clone(),
            projections: projections.clone(),
        }))
        .unwrap();
    }
    calls.store(0, Ordering::SeqCst);
    let budget = GetEventInformationBudget {
        max_objects: 2,
        ..Default::default()
    };
    for cursor in [
        None,
        Some(ObjectIdentifier::new(ObjectType::NOTIFICATION_CLASS, 999).unwrap()),
    ] {
        assert!(matches!(
            configured(&db, cursor, budget),
            Err(EventInformationFailure::Objects)
        ));
        assert_eq!(calls.load(Ordering::SeqCst), 0);
    }
    for bytes in [1, 16384] {
        projections.store(0, Ordering::SeqCst);
        let result = configured(
            &db,
            None,
            GetEventInformationBudget {
                max_objects: 3,
                max_returned_summaries: 1,
                max_service_ack_bytes: bytes,
            },
        );
        assert_eq!(projections.load(Ordering::SeqCst), 3);
        if bytes == 1 {
            assert!(matches!(result, Err(EventInformationFailure::Bytes)));
        } else {
            assert!(
                GetEventInformationAck::decode(&result.unwrap())
                    .unwrap()
                    .more_events
            );
        }
    }
}

fn configured(
    db: &ObjectDatabase,
    cursor: Option<ObjectIdentifier>,
    budget: GetEventInformationBudget,
) -> Result<BytesMut, EventInformationFailure> {
    let mut encoded = BytesMut::new();
    handle_get_event_information_configured(db, &request(cursor), &mut encoded, budget)?;
    Ok(encoded)
}

fn database(count: u32) -> ObjectDatabase {
    let mut db = ObjectDatabase::new();
    add_class(&mut db, 99, [1, 100, 255]);
    for instance in (1..=count).rev() {
        db.add(Box::new(ProjectionFixture::summary(instance)))
            .unwrap();
    }
    db
}

#[test]
fn get_event_information_configured_256_257_exact_cursor_pages() {
    for count in [0, 1, 256, 257] {
        let db = database(count);
        let encoded = configured(&db, None, GetEventInformationBudget::default()).unwrap();
        let ack = GetEventInformationAck::decode(&encoded).unwrap();
        assert_eq!(ack.list_of_event_summaries.len(), count.min(256) as usize);
        assert_eq!(ack.more_events, count > 256);
        for (i, summary) in ack.list_of_event_summaries.iter().enumerate() {
            assert_eq!(summary.object_identifier.instance_number(), i as u32 + 1);
        }
        if count == 257 {
            let cursor = ack
                .list_of_event_summaries
                .last()
                .unwrap()
                .object_identifier;
            let final_page =
                configured(&db, Some(cursor), GetEventInformationBudget::default()).unwrap();
            let last = GetEventInformationAck::decode(&final_page).unwrap();
            assert!(!last.more_events);
            assert_eq!(last.list_of_event_summaries.len(), 1);
            assert_eq!(
                last.list_of_event_summaries[0]
                    .object_identifier
                    .instance_number(),
                257
            );
        } else {
            let mut legacy = BytesMut::new();
            handle_get_event_information(&db, &[], &mut legacy).unwrap();
            assert_eq!(encoded, legacy);
        }
    }
}

#[test]
fn get_event_information_configured_exact_bytes_tiny_empty_and_prefix() {
    let one = configured(&database(1), None, GetEventInformationBudget::default()).unwrap();
    let two = configured(&database(2), None, GetEventInformationBudget::default()).unwrap();
    for (bytes, expected, more) in [
        (one.len(), 1, true),
        (two.len() - 1, 1, true),
        (two.len(), 2, false),
    ] {
        let encoded = configured(
            &database(2),
            None,
            GetEventInformationBudget {
                max_service_ack_bytes: bytes,
                ..Default::default()
            },
        )
        .unwrap();
        assert!(encoded.len() <= bytes);
        let ack = GetEventInformationAck::decode(&encoded).unwrap();
        assert_eq!(ack.list_of_event_summaries.len(), expected);
        assert_eq!(ack.more_events, more);
    }
    for bytes in [0, 1, 3, one.len() - 1] {
        assert!(matches!(
            configured(
                &database(1),
                None,
                GetEventInformationBudget {
                    max_service_ack_bytes: bytes,
                    ..Default::default()
                }
            ),
            Err(EventInformationFailure::Bytes)
        ));
    }
    let empty = configured(
        &database(0),
        None,
        GetEventInformationBudget {
            max_service_ack_bytes: 4,
            ..Default::default()
        },
    )
    .unwrap();
    assert_eq!(&empty[..], &[0x0e, 0x0f, 0x19, 0]);
    assert!(matches!(
        configured(
            &database(0),
            None,
            GetEventInformationBudget {
                max_service_ack_bytes: 3,
                ..Default::default()
            }
        ),
        Err(EventInformationFailure::Bytes)
    ));
}

#[test]
fn get_event_information_configured_late_errors_win_and_normals_do_not_imply_more() {
    for bytes in [1, 16384] {
        for normal in [false, true] {
            let mut db = database(1);
            let mut late = ProjectionFixture::summary(2);
            if normal {
                late.set(
                    PropertyIdentifier::EVENT_STATE,
                    PropertyValue::Enumerated(0),
                );
            }
            late.remove(PropertyIdentifier::NOTIFY_TYPE);
            db.add(Box::new(late)).unwrap();
            let budget = GetEventInformationBudget {
                max_returned_summaries: 1,
                max_service_ack_bytes: bytes,
                ..Default::default()
            };
            assert!(
                matches!(configured(&db, None, budget), Err(EventInformationFailure::Service(Error::Remote(RemoteError::Error { class, code })))
                if class == ErrorClass::DEVICE && code == ErrorCode::OPERATIONAL_PROBLEM)
            );
            assert!(handle_get_event_information(&db, &[], &mut BytesMut::new()).is_err());
        }
    }
    let mut db = database(1);
    let mut normal = ProjectionFixture::summary(2);
    normal.set(
        PropertyIdentifier::EVENT_STATE,
        PropertyValue::Enumerated(0),
    );
    db.add(Box::new(normal)).unwrap();
    let mut disabled = ProjectionFixture::summary(3);
    disabled.advertise(PropertyIdentifier::EVENT_DETECTION_ENABLE);
    disabled.set(
        PropertyIdentifier::EVENT_DETECTION_ENABLE,
        PropertyValue::Boolean(false),
    );
    disabled.remove(PropertyIdentifier::NOTIFY_TYPE);
    db.add(Box::new(disabled)).unwrap();
    let ack = configured(
        &db,
        None,
        GetEventInformationBudget {
            max_returned_summaries: 1,
            ..Default::default()
        },
    )
    .unwrap();
    assert!(!GetEventInformationAck::decode(&ack).unwrap().more_events);
    db.add(Box::new(ProjectionFixture::summary(4))).unwrap();
    let ack = configured(
        &db,
        None,
        GetEventInformationBudget {
            max_returned_summaries: 1,
            ..Default::default()
        },
    )
    .unwrap();
    assert!(GetEventInformationAck::decode(&ack).unwrap().more_events);
}

#[test]
fn get_event_information_configured_cardinality_decode_and_cursor_precedence() {
    let mut db = database(1);
    // Malformed projection must never be touched on rejected cardinality.
    let mut malformed = ProjectionFixture::summary(2);
    malformed.remove(PropertyIdentifier::NOTIFY_TYPE);
    db.add(Box::new(malformed)).unwrap();
    let budget = GetEventInformationBudget {
        max_objects: 2,
        ..Default::default()
    };
    for cursor in [
        None,
        Some(ObjectIdentifier::new(ObjectType::NOTIFICATION_CLASS, 999).unwrap()),
    ] {
        assert!(matches!(
            configured(&db, cursor, budget),
            Err(EventInformationFailure::Objects)
        ));
    }
    let mut out = BytesMut::from(&b"sentinel"[..]);
    assert!(matches!(
        handle_get_event_information_configured(&db, &[0xff], &mut out, budget),
        Err(EventInformationFailure::Service(_))
    ));
    assert_eq!(&out[..], b"sentinel");
}

#[test]
fn get_event_information_configured_mixed_types_missing_cursor_and_class_errors() {
    let mut db = database(1);
    let mut unacked = ProjectionFixture::summary(3);
    unacked.oid = ObjectIdentifier::new(ObjectType::BINARY_INPUT, 3).unwrap();
    unacked.set(
        PropertyIdentifier::EVENT_STATE,
        PropertyValue::Enumerated(0),
    );
    unacked.set(
        PropertyIdentifier::ACKED_TRANSITIONS,
        transition_bits(EventTransitionBits::TO_FAULT | EventTransitionBits::TO_NORMAL),
    );
    db.add(Box::new(unacked)).unwrap();
    for cursor in [
        None,
        Some(ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 2).unwrap()),
        Some(ObjectIdentifier::new(ObjectType::BINARY_INPUT, 3).unwrap()),
    ] {
        let expected = response(&db, cursor, None).unwrap();
        let encoded = configured(&db, cursor, GetEventInformationBudget::default()).unwrap();
        let ack = GetEventInformationAck::decode(&encoded).unwrap();
        assert_eq!(encoded.len(), expected.1);
        assert_eq!(format!("{ack:?}"), format!("{:?}", expected.0));
    }
    add_class(&mut db, 98, [2, 3, 4]);
    assert!(matches!(
        configured(&db, None, GetEventInformationBudget::default()),
        Err(EventInformationFailure::Service(_))
    ));
    let mut missing = ObjectDatabase::new();
    missing
        .add(Box::new(ProjectionFixture::summary(1)))
        .unwrap();
    assert!(matches!(
        configured(&missing, None, GetEventInformationBudget::default()),
        Err(EventInformationFailure::Service(_))
    ));
}