rustbac-core 0.4.0

Core BACnet protocol types, encoders, and service codecs for rustbac.
Documentation
#[cfg(feature = "alloc")]
use crate::encoding::{primitives::decode_unsigned, reader::Reader, tag::Tag};
#[cfg(feature = "alloc")]
use crate::services::value_codec::decode_application_data_value_from_tag;
#[cfg(feature = "alloc")]
use crate::services::{decode_required_ctx_object_id, decode_required_ctx_unsigned};
#[cfg(feature = "alloc")]
use crate::types::{DataValue, ObjectId, PropertyId};
#[cfg(feature = "alloc")]
use crate::DecodeError;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;

pub const SERVICE_CONFIRMED_COV_NOTIFICATION: u8 = 0x01;
pub const SERVICE_UNCONFIRMED_COV_NOTIFICATION: u8 = 0x02;

#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
pub struct CovPropertyValue<'a> {
    pub property_id: PropertyId,
    pub array_index: Option<u32>,
    pub value: DataValue<'a>,
    pub priority: Option<u8>,
}

#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
pub struct CovNotificationRequest<'a> {
    pub subscriber_process_id: u32,
    pub initiating_device_id: ObjectId,
    pub monitored_object_id: ObjectId,
    pub time_remaining_seconds: u32,
    pub values: Vec<CovPropertyValue<'a>>,
}

#[cfg(feature = "alloc")]
impl<'a> CovNotificationRequest<'a> {
    pub fn decode_after_header(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
        let subscriber_process_id = decode_required_ctx_unsigned(r, 0)?;
        let initiating_device_id = decode_required_ctx_object_id(r, 1)?;
        let monitored_object_id = decode_required_ctx_object_id(r, 2)?;
        let time_remaining_seconds = decode_required_ctx_unsigned(r, 3)?;

        match Tag::decode(r)? {
            Tag::Opening { tag_num: 4 } => {}
            _ => return Err(DecodeError::InvalidTag),
        }

        let mut values = Vec::new();
        loop {
            let property_start = Tag::decode(r)?;
            if property_start == (Tag::Closing { tag_num: 4 }) {
                break;
            }

            let property_id = match property_start {
                Tag::Context { tag_num: 0, len } => {
                    PropertyId::from_u32(decode_unsigned(r, len as usize)?)
                }
                _ => return Err(DecodeError::InvalidTag),
            };

            let next = Tag::decode(r)?;
            let (array_index, value_open_tag) = match next {
                Tag::Context { tag_num: 1, len } => {
                    let idx = decode_unsigned(r, len as usize)?;
                    (Some(idx), Tag::decode(r)?)
                }
                other => (None, other),
            };
            if value_open_tag != (Tag::Opening { tag_num: 2 }) {
                return Err(DecodeError::InvalidTag);
            }

            let value_tag = Tag::decode(r)?;
            let value = decode_application_data_value_from_tag(r, value_tag)?;
            match Tag::decode(r)? {
                Tag::Closing { tag_num: 2 } => {}
                _ => return Err(DecodeError::InvalidTag),
            }

            let checkpoint = *r;
            let priority = match Tag::decode(r)? {
                Tag::Context { tag_num: 3, len } => {
                    let p = decode_unsigned(r, len as usize)?;
                    if p > u8::MAX as u32 {
                        return Err(DecodeError::InvalidValue);
                    }
                    Some(p as u8)
                }
                _ => {
                    *r = checkpoint;
                    None
                }
            };

            values.push(CovPropertyValue {
                property_id,
                array_index,
                value,
                priority,
            });
        }

        Ok(Self {
            subscriber_process_id,
            initiating_device_id,
            monitored_object_id,
            time_remaining_seconds,
            values,
        })
    }
}

#[cfg(test)]
mod tests {
    #[cfg(feature = "alloc")]
    use super::{CovNotificationRequest, SERVICE_UNCONFIRMED_COV_NOTIFICATION};
    #[cfg(feature = "alloc")]
    use crate::apdu::UnconfirmedRequestHeader;
    #[cfg(feature = "alloc")]
    use crate::encoding::{
        primitives::{encode_app_real, encode_ctx_unsigned},
        tag::Tag,
        writer::Writer,
    };
    #[cfg(feature = "alloc")]
    use crate::types::{ObjectId, ObjectType, PropertyId};

    #[cfg(feature = "alloc")]
    #[test]
    fn decode_cov_notification_after_header() {
        let mut buf = [0u8; 256];
        let mut w = Writer::new(&mut buf);
        UnconfirmedRequestHeader {
            service_choice: SERVICE_UNCONFIRMED_COV_NOTIFICATION,
        }
        .encode(&mut w)
        .unwrap();
        encode_ctx_unsigned(&mut w, 0, 77).unwrap();
        encode_ctx_unsigned(&mut w, 1, ObjectId::new(ObjectType::Device, 1).raw()).unwrap();
        encode_ctx_unsigned(&mut w, 2, ObjectId::new(ObjectType::AnalogInput, 2).raw()).unwrap();
        encode_ctx_unsigned(&mut w, 3, 120).unwrap();
        Tag::Opening { tag_num: 4 }.encode(&mut w).unwrap();
        encode_ctx_unsigned(&mut w, 0, PropertyId::PresentValue.to_u32()).unwrap();
        Tag::Opening { tag_num: 2 }.encode(&mut w).unwrap();
        encode_app_real(&mut w, 42.25).unwrap();
        Tag::Closing { tag_num: 2 }.encode(&mut w).unwrap();
        encode_ctx_unsigned(&mut w, 3, 8).unwrap();
        Tag::Closing { tag_num: 4 }.encode(&mut w).unwrap();

        let encoded = w.as_written();
        let mut r = crate::encoding::reader::Reader::new(encoded);
        let _header = UnconfirmedRequestHeader::decode(&mut r).unwrap();
        let cov = CovNotificationRequest::decode_after_header(&mut r).unwrap();
        assert_eq!(cov.subscriber_process_id, 77);
        assert_eq!(
            cov.initiating_device_id,
            ObjectId::new(ObjectType::Device, 1)
        );
        assert_eq!(
            cov.monitored_object_id,
            ObjectId::new(ObjectType::AnalogInput, 2)
        );
        assert_eq!(cov.values.len(), 1);
        assert_eq!(cov.values[0].property_id, PropertyId::PresentValue);
        assert_eq!(cov.values[0].priority, Some(8));
    }
}