bacnet-server 0.15.0

BACnet server: APDU dispatch and service handlers
Documentation
use super::*;
use bacnet_objects::elevator::EscalatorObject;
use bacnet_services::common::BACnetPropertyValue;
use bacnet_services::wpm::{WriteAccessSpecification, WritePropertyMultipleRequest};
use bacnet_types::error::RemoteError;

fn encode_value(value: PropertyValue) -> Vec<u8> {
    let mut buf = BytesMut::new();
    encode_property_value(&mut buf, &value).unwrap();
    buf.to_vec()
}

fn escalator_db() -> (ObjectDatabase, ObjectIdentifier) {
    let mut db = ObjectDatabase::new();
    let escalator = EscalatorObject::new(1, "ESC-1").unwrap();
    let oid = escalator.object_identifier();
    db.add(Box::new(escalator)).unwrap();
    (db, oid)
}

fn read(db: &ObjectDatabase, oid: ObjectIdentifier, property: PropertyIdentifier) -> PropertyValue {
    db.get(&oid).unwrap().read_property(property, None).unwrap()
}

fn write_property(
    db: &mut ObjectDatabase,
    oid: ObjectIdentifier,
    property: PropertyIdentifier,
    array_index: Option<u32>,
    value: Vec<u8>,
) -> Result<(), Error> {
    let request = WritePropertyRequest {
        object_identifier: oid,
        property_identifier: property,
        property_array_index: array_index,
        property_value: value,
        priority: None,
    };
    let mut request_bytes = BytesMut::new();
    request.encode(&mut request_bytes).unwrap();
    handle_write_property(db, &request_bytes).map(|_| ())
}

fn write_multiple(
    db: &mut ObjectDatabase,
    oid: ObjectIdentifier,
    properties: Vec<BACnetPropertyValue>,
) -> Result<(), Error> {
    let request = WritePropertyMultipleRequest {
        list_of_write_access_specs: vec![WriteAccessSpecification {
            object_identifier: oid,
            list_of_properties: properties,
        }],
    };
    let mut request_bytes = BytesMut::new();
    request.encode(&mut request_bytes).unwrap();
    handle_write_property_multiple(db, &request_bytes).map(|_| ())
}

fn assert_property_error(error: Error, expected: ErrorCode) {
    match error {
        Error::Remote(RemoteError::Error { class, code }) => {
            assert_eq!(class, ErrorClass::PROPERTY);
            assert_eq!(code, expected);
        }
        other => panic!("expected PROPERTY/{expected:?}, got {other:?}"),
    }
}

fn status_values() -> Vec<(PropertyIdentifier, PropertyValue)> {
    vec![
        (PropertyIdentifier::POWER_MODE, PropertyValue::Boolean(true)),
        (
            PropertyIdentifier::OPERATION_DIRECTION,
            PropertyValue::Enumerated(2),
        ),
        (
            PropertyIdentifier::ESCALATOR_MODE,
            PropertyValue::Enumerated(3),
        ),
        (PropertyIdentifier::ENERGY_METER, PropertyValue::Real(18.75)),
        (
            PropertyIdentifier::FAULT_SIGNALS,
            PropertyValue::List(vec![
                PropertyValue::Enumerated(0),
                PropertyValue::Enumerated(1024),
            ]),
        ),
        (
            PropertyIdentifier::PASSENGER_ALARM,
            PropertyValue::Boolean(true),
        ),
    ]
}

#[test]
fn escalator_status_family_writes_over_wp_in_service_and_oos() {
    for out_of_service in [false, true] {
        let (mut db, oid) = escalator_db();
        if out_of_service {
            write_property(
                &mut db,
                oid,
                PropertyIdentifier::OUT_OF_SERVICE,
                None,
                encode_value(PropertyValue::Boolean(true)),
            )
            .unwrap();
        }

        for (property, value) in status_values() {
            write_property(&mut db, oid, property, None, encode_value(value.clone()))
                .unwrap_or_else(|error| {
                    panic!("{property:?} must write with OOS={out_of_service}: {error:?}")
                });
            assert_eq!(read(&db, oid, property), value);
        }
    }
}

#[test]
fn escalator_fault_signals_empty_singleton_and_multi_reach_the_object_over_wp() {
    let (mut db, oid) = escalator_db();

    write_property(
        &mut db,
        oid,
        PropertyIdentifier::FAULT_SIGNALS,
        None,
        Vec::new(),
    )
    .unwrap();
    assert_eq!(
        read(&db, oid, PropertyIdentifier::FAULT_SIGNALS),
        PropertyValue::List(vec![])
    );

    write_property(
        &mut db,
        oid,
        PropertyIdentifier::FAULT_SIGNALS,
        None,
        encode_value(PropertyValue::Enumerated(8)),
    )
    .unwrap();
    assert_eq!(
        read(&db, oid, PropertyIdentifier::FAULT_SIGNALS),
        PropertyValue::List(vec![PropertyValue::Enumerated(8)])
    );

    let multi = PropertyValue::List(vec![
        PropertyValue::Enumerated(0),
        PropertyValue::Enumerated(65535),
    ]);
    write_property(
        &mut db,
        oid,
        PropertyIdentifier::FAULT_SIGNALS,
        None,
        encode_value(multi.clone()),
    )
    .unwrap();
    assert_eq!(read(&db, oid, PropertyIdentifier::FAULT_SIGNALS), multi);
}

#[test]
fn escalator_fault_signals_reject_array_index_before_empty_list_decode() {
    let (mut db, oid) = escalator_db();
    let prior = PropertyValue::List(vec![PropertyValue::Enumerated(8)]);
    write_property(
        &mut db,
        oid,
        PropertyIdentifier::FAULT_SIGNALS,
        None,
        encode_value(prior.clone()),
    )
    .unwrap();

    let error = write_property(
        &mut db,
        oid,
        PropertyIdentifier::FAULT_SIGNALS,
        Some(0),
        Vec::new(),
    )
    .unwrap_err();
    assert_property_error(error, ErrorCode::PROPERTY_IS_NOT_AN_ARRAY);
    assert_eq!(read(&db, oid, PropertyIdentifier::FAULT_SIGNALS), prior);
}

#[test]
fn escalator_empty_fault_signals_wpm_prefix_stays_committed() {
    let (mut db, oid) = escalator_db();
    let prior = PropertyValue::List(vec![
        PropertyValue::Enumerated(0),
        PropertyValue::Enumerated(1024),
    ]);
    write_property(
        &mut db,
        oid,
        PropertyIdentifier::FAULT_SIGNALS,
        None,
        encode_value(prior.clone()),
    )
    .unwrap();

    let error = write_multiple(
        &mut db,
        oid,
        vec![
            BACnetPropertyValue {
                property_identifier: PropertyIdentifier::FAULT_SIGNALS,
                property_array_index: None,
                value: Vec::new(),
                priority: None,
            },
            BACnetPropertyValue {
                property_identifier: PropertyIdentifier::PASSENGER_ALARM,
                property_array_index: None,
                value: encode_value(PropertyValue::Real(1.0)),
                priority: None,
            },
        ],
    )
    .unwrap_err();
    assert_property_error(error, ErrorCode::INVALID_DATA_TYPE);
    assert_eq!(
        read(&db, oid, PropertyIdentifier::FAULT_SIGNALS),
        PropertyValue::List(vec![])
    );
    assert_eq!(
        read(&db, oid, PropertyIdentifier::PASSENGER_ALARM),
        PropertyValue::Boolean(false)
    );

    write_multiple(
        &mut db,
        oid,
        vec![BACnetPropertyValue {
            property_identifier: PropertyIdentifier::FAULT_SIGNALS,
            property_array_index: None,
            value: Vec::new(),
            priority: None,
        }],
    )
    .unwrap();
    assert_eq!(
        read(&db, oid, PropertyIdentifier::FAULT_SIGNALS),
        PropertyValue::List(vec![])
    );
}