bacnet-encoding 0.13.0

BACnet ASN.1/BER encoding, APDU and NPDU codecs per ASHRAE 135-2020
Documentation
use super::*;

fn raw_context_value(buf: &mut BytesMut, tag_number: u8, value: &[u8]) {
    tags::encode_tag(buf, tag_number, tags::TagClass::Context, value.len() as u32);
    buf.extend_from_slice(value);
}

fn raw_change_of_life_safety(
    values: [&[u8]; 3],
    field_tags: [u8; 4],
    status_flags: &[u8],
    closing_tag: Option<u8>,
) -> BytesMut {
    let mut buf = BytesMut::new();
    tags::encode_opening_tag(&mut buf, 8);
    raw_context_value(&mut buf, field_tags[0], values[0]);
    raw_context_value(&mut buf, field_tags[1], values[1]);
    raw_context_value(&mut buf, field_tags[2], status_flags);
    raw_context_value(&mut buf, field_tags[3], values[2]);
    if let Some(tag) = closing_tag {
        tags::encode_closing_tag(&mut buf, tag);
    }
    buf
}

fn decode_params(data: &[u8]) -> Result<NotificationParameters, Error> {
    decode_notification_parameters(data, 0)
}

fn encoded_event_notification() -> (BytesMut, usize) {
    let request = EventNotificationRequest {
        process_identifier: 1,
        initiating_device_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap(),
        event_object_identifier: ObjectIdentifier::new(ObjectType::LIFE_SAFETY_POINT, 1).unwrap(),
        timestamp: BACnetTimeStamp::SequenceNumber(1),
        notification_class: 1,
        priority: 1,
        event_type: EventType::CHANGE_OF_LIFE_SAFETY,
        message_text: None,
        notify_type: NotifyType::ALARM,
        ack_required: true,
        from_state: EventState::NORMAL,
        to_state: EventState::FAULT,
        event_values: None,
    };
    let mut buf = BytesMut::new();
    encode_event_notification(&request, &mut buf).unwrap();
    let event_values_offset = buf.len();
    tags::encode_opening_tag(&mut buf, 12);
    encode_notification_parameters(
        &NotificationParameters::ChangeOfLifeSafety {
            new_state: LifeSafetyState::PRE_ALARM,
            new_mode: LifeSafetyMode::ON,
            status_flags: StatusFlags::IN_ALARM,
            operation_expected: LifeSafetyOperation::SILENCE,
        },
        &mut buf,
    )
    .unwrap();
    tags::encode_closing_tag(&mut buf, 12);
    (buf, event_values_offset)
}

#[test]
fn change_of_life_safety_accepts_u32_max_with_leading_zero() {
    let max = [0, 0xff, 0xff, 0xff, 0xff];
    let decoded = decode_params(&raw_change_of_life_safety(
        [&max, &max, &max],
        [0, 1, 2, 3],
        &[4, 0xf0],
        Some(8),
    ))
    .unwrap();

    assert_eq!(
        decoded,
        NotificationParameters::ChangeOfLifeSafety {
            new_state: LifeSafetyState::from_raw(u32::MAX),
            new_mode: LifeSafetyMode::from_raw(u32::MAX),
            status_flags: StatusFlags::all(),
            operation_expected: LifeSafetyOperation::from_raw(u32::MAX),
        }
    );
}

#[test]
fn change_of_life_safety_rejects_values_above_u32() {
    let one = [1];
    let overflow_alias = [1, 0, 0, 0, 1];
    let u64_max = [0xff; 8];

    for field in 0..3 {
        for invalid in [&overflow_alias[..], &u64_max] {
            let mut values: [&[u8]; 3] = [&one, &one, &one];
            values[field] = invalid;
            assert!(decode_params(&raw_change_of_life_safety(
                values,
                [0, 1, 2, 3],
                &[4, 0x80],
                Some(8),
            ))
            .is_err());
        }
    }
}

#[test]
fn change_of_life_safety_requires_exact_field_tags() {
    for field in 0..4 {
        let mut field_tags = [0, 1, 2, 3];
        field_tags[field] = 7;
        assert!(decode_params(&raw_change_of_life_safety(
            [&[1], &[1], &[1]],
            field_tags,
            &[4, 0x80],
            Some(8),
        ))
        .is_err());
    }

    let mut application_tagged =
        raw_change_of_life_safety([&[1], &[1], &[1]], [0, 1, 2, 3], &[4, 0x80], Some(8));
    application_tagged[1] &= !0x08;
    assert!(decode_params(&application_tagged).is_err());
}

#[test]
fn change_of_life_safety_requires_four_status_bits_with_zero_padding() {
    for invalid in [
        &[][..],
        &[4][..],
        &[3, 0xf0][..],
        &[4, 0xf1][..],
        &[4, 0xf0, 0][..],
    ] {
        assert!(decode_params(&raw_change_of_life_safety(
            [&[1], &[1], &[1]],
            [0, 1, 2, 3],
            invalid,
            Some(8),
        ))
        .is_err());
    }
}

#[test]
fn change_of_life_safety_requires_its_immediate_closing_tag() {
    assert!(decode_params(&raw_change_of_life_safety(
        [&[1], &[1], &[1]],
        [0, 1, 2, 3],
        &[4, 0x80],
        None,
    ))
    .is_err());
    assert!(decode_params(&raw_change_of_life_safety(
        [&[1], &[1], &[1]],
        [0, 1, 2, 3],
        &[4, 0x80],
        Some(9),
    ))
    .is_err());

    let mut extra_field =
        raw_change_of_life_safety([&[1], &[1], &[1]], [0, 1, 2, 3], &[4, 0x80], None);
    raw_context_value(&mut extra_field, 4, &[1]);
    tags::encode_closing_tag(&mut extra_field, 8);
    assert!(decode_params(&extra_field).is_err());
}

#[test]
fn change_of_life_safety_rejects_every_truncated_prefix() {
    let encoded = raw_change_of_life_safety([&[1], &[1], &[1]], [0, 1, 2, 3], &[4, 0x80], Some(8));

    for end in 0..encoded.len() {
        assert!(decode_params(&encoded[..end]).is_err(), "prefix {end}");
    }
}

#[test]
fn event_notification_requires_event_values_outer_framing() {
    let (encoded, event_values_offset) = encoded_event_notification();
    assert!(decode_event_notification(&encoded).is_ok());

    let mut missing_opening = encoded.to_vec();
    missing_opening.remove(event_values_offset);
    assert!(decode_event_notification(&missing_opening).is_err());

    let mut wrong_opening = encoded.clone();
    let mut tag = BytesMut::new();
    tags::encode_opening_tag(&mut tag, 11);
    wrong_opening[event_values_offset] = tag[0];
    assert!(decode_event_notification(&wrong_opening).is_err());

    let mut missing_closing = encoded.clone();
    missing_closing.truncate(missing_closing.len() - 1);
    assert!(decode_event_notification(&missing_closing).is_err());

    let mut wrong_closing = missing_closing.clone();
    tags::encode_closing_tag(&mut wrong_closing, 9);
    assert!(decode_event_notification(&wrong_closing).is_err());

    let mut extra_sibling = missing_closing;
    raw_context_value(&mut extra_sibling, 4, &[1]);
    tags::encode_closing_tag(&mut extra_sibling, 12);
    assert!(decode_event_notification(&extra_sibling).is_err());

    let mut trailing_data = encoded;
    raw_context_value(&mut trailing_data, 13, &[1]);
    assert!(decode_event_notification(&trailing_data).is_err());
}