bacnet-encoding 0.13.0

BACnet ASN.1/BER encoding, APDU and NPDU codecs per ASHRAE 135-2020
Documentation
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

use super::decode_helpers::{decode_context_status_flags, finish_variant};
use super::*;

pub(super) fn validate_authentication_factor(data: &[u8]) -> Result<(), Error> {
    let (_, pos) = decode_context_u32(data, 0, 0, "AuthenticationFactor format-type")?;
    let (format_class, pos) = decode_context(data, pos, 1, "AuthenticationFactor format-class")?;
    primitives::decode_unsigned(format_class)?;
    let (_, pos) = decode_context(data, pos, 2, "AuthenticationFactor value")?;
    if pos != data.len() {
        return Err(Error::trailing(
            pos,
            "AuthenticationFactor has unexpected fields",
        ));
    }
    Ok(())
}

pub(super) fn decode_complex_event_type(
    data: &[u8],
    inner_start: usize,
    variant_body_end: usize,
) -> Result<NotificationParameters, Error> {
    let mut property_values = Vec::new();
    let mut pos = inner_start;
    while pos < variant_body_end {
        if property_values.len() >= MAX_FRAMED_ITEMS {
            return Err(Error::overflow(
                pos,
                format!("ComplexEventType exceeds {MAX_FRAMED_ITEMS} property values"),
            ));
        }
        let (property_value, next) = decode_bacnet_property_value_in_list(data, pos, 6)?;
        if next <= pos || next > variant_body_end {
            return Err(Error::decoding(
                pos,
                "ComplexEventType property value made invalid progress",
            ));
        }
        validate_tlv_sequence(&property_value.value, "ComplexEventType property value")?;
        property_values.push(property_value);
        pos = next;
    }
    finish_variant(
        NotificationParameters::ComplexEventType { property_values },
        pos,
        variant_body_end,
        6,
    )
}

fn decode_object_identifier(
    data: &[u8],
    offset: usize,
    context_tag: u8,
    field: &str,
) -> Result<(ObjectIdentifier, usize), Error> {
    let (content, next) = decode_context(data, offset, context_tag, field)?;
    Ok((ObjectIdentifier::decode(content)?, next))
}

fn decode_access_credential(
    data: &[u8],
    offset: usize,
) -> Result<(BACnetDeviceObjectReference, usize), Error> {
    let (opening, mut pos) = tags::decode_tag(data, offset)?;
    if !opening.is_opening_tag(4) {
        return Err(misplaced_tag(
            data,
            &opening,
            Some(4),
            offset,
            "AccessEvent expected opening [4] for access-credential",
        ));
    }

    let device_identifier = if tags::decode_tag(data, pos)?.0.is_context(0) {
        let (value, next) =
            decode_object_identifier(data, pos, 0, "AccessEvent credential device-identifier")?;
        pos = next;
        Some(value)
    } else {
        None
    };
    let (object_identifier, next) =
        decode_object_identifier(data, pos, 1, "AccessEvent credential object-identifier")?;
    pos = next;

    let (closing, next) = tags::decode_tag(data, pos)?;
    if !closing.is_closing_tag(4) {
        return Err(Error::decoding_kind(
            unclosed_kind(&closing),
            pos,
            "AccessEvent credential has duplicate, out-of-order, or trailing fields",
        ));
    }
    Ok((
        BACnetDeviceObjectReference {
            device_identifier,
            object_identifier,
        },
        next,
    ))
}

pub(super) fn decode_access_event(
    data: &[u8],
    inner_start: usize,
    variant_body_end: usize,
) -> Result<NotificationParameters, Error> {
    let (access_event, pos) = decode_context_enum(
        data,
        inner_start,
        0,
        "AccessEvent access-event",
        AccessEvent::from_raw,
    )?;
    let (status_flags, pos) =
        decode_context_status_flags(data, pos, 1, "AccessEvent status-flags")?;
    let (access_event_tag, pos) = decode_context_u32(data, pos, 2, "AccessEvent access-event-tag")?;
    let (timestamp, pos) = primitives::decode_timestamp(data, pos, 3)?;
    let access_event_time = match timestamp {
        BACnetTimeStamp::DateTime { date, time } => (date, time),
        // Another alternative of the timestamp CHOICE doesn't fit here.
        _ => {
            return Err(Error::invalid_tag(
                pos,
                "AccessEvent expected a DateTime timestamp",
            ));
        }
    };
    let (access_credential, pos) = decode_access_credential(data, pos)?;

    let (authentication_factor, pos) = if pos < variant_body_end {
        let (opening, content_start) = tags::decode_tag(data, pos)?;
        if !opening.is_opening_tag(5) {
            return Err(misplaced_tag(
                data,
                &opening,
                Some(5),
                pos,
                "AccessEvent expected opening [5] for authentication-factor",
            ));
        }
        let (value, next) = extract_raw_context(data, content_start, 5)?;
        validate_authentication_factor(&value)?;
        (Some(value), next)
    } else {
        (None, pos)
    };

    finish_variant(
        NotificationParameters::AccessEvent {
            access_event,
            status_flags,
            access_event_tag,
            access_event_time,
            access_credential,
            authentication_factor,
        },
        pos,
        variant_body_end,
        13,
    )
}