rustbac-core 0.4.0

Core BACnet protocol types, encoders, and service codecs for rustbac.
Documentation
use crate::apdu::ConfirmedRequestHeader;
use crate::encoding::{
    primitives::{decode_unsigned, encode_ctx_object_id, encode_ctx_unsigned},
    reader::Reader,
    tag::Tag,
    writer::Writer,
};
use crate::types::{ObjectId, ObjectType};
use crate::{DecodeError, EncodeError};

pub const SERVICE_CREATE_OBJECT: u8 = 0x0A;
pub const SERVICE_DELETE_OBJECT: u8 = 0x0B;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CreateObjectSpecifier {
    ObjectType(ObjectType),
    ObjectId(ObjectId),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CreateObjectRequest {
    pub specifier: CreateObjectSpecifier,
    pub invoke_id: u8,
}

impl CreateObjectRequest {
    pub fn by_type(object_type: ObjectType, invoke_id: u8) -> Self {
        Self {
            specifier: CreateObjectSpecifier::ObjectType(object_type),
            invoke_id,
        }
    }

    pub fn by_id(object_id: ObjectId, invoke_id: u8) -> Self {
        Self {
            specifier: CreateObjectSpecifier::ObjectId(object_id),
            invoke_id,
        }
    }

    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
        ConfirmedRequestHeader {
            segmented: false,
            more_follows: false,
            segmented_response_accepted: true,
            max_segments: 0,
            max_apdu: 5,
            invoke_id: self.invoke_id,
            sequence_number: None,
            proposed_window_size: None,
            service_choice: SERVICE_CREATE_OBJECT,
        }
        .encode(w)?;

        match self.specifier {
            CreateObjectSpecifier::ObjectType(object_type) => {
                encode_ctx_unsigned(w, 0, object_type.to_u16() as u32)?
            }
            CreateObjectSpecifier::ObjectId(object_id) => {
                encode_ctx_object_id(w, 1, object_id.raw())?
            }
        }
        Ok(())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CreateObjectAck {
    pub object_id: ObjectId,
}

impl CreateObjectAck {
    pub fn decode_after_header(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
        let object_id = match Tag::decode(r)? {
            Tag::Context { tag_num: 0, len } => {
                if len != 4 {
                    return Err(DecodeError::InvalidLength);
                }
                ObjectId::from_raw(decode_unsigned(r, len as usize)?)
            }
            _ => return Err(DecodeError::InvalidTag),
        };
        Ok(Self { object_id })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeleteObjectRequest {
    pub object_id: ObjectId,
    pub invoke_id: u8,
}

impl DeleteObjectRequest {
    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
        ConfirmedRequestHeader {
            segmented: false,
            more_follows: false,
            segmented_response_accepted: false,
            max_segments: 0,
            max_apdu: 5,
            invoke_id: self.invoke_id,
            sequence_number: None,
            proposed_window_size: None,
            service_choice: SERVICE_DELETE_OBJECT,
        }
        .encode(w)?;
        encode_ctx_object_id(w, 0, self.object_id.raw())
    }
}

#[cfg(test)]
mod tests {
    use super::{
        CreateObjectAck, CreateObjectRequest, DeleteObjectRequest, SERVICE_CREATE_OBJECT,
        SERVICE_DELETE_OBJECT,
    };
    use crate::apdu::{ComplexAckHeader, ConfirmedRequestHeader};
    use crate::encoding::primitives::encode_ctx_object_id;
    use crate::encoding::{reader::Reader, writer::Writer};
    use crate::types::{ObjectId, ObjectType};

    #[test]
    fn encode_create_object_request() {
        let req = CreateObjectRequest::by_type(ObjectType::AnalogValue, 3);
        let mut buf = [0u8; 64];
        let mut w = Writer::new(&mut buf);
        req.encode(&mut w).unwrap();
        let mut r = Reader::new(w.as_written());
        let hdr = ConfirmedRequestHeader::decode(&mut r).unwrap();
        assert_eq!(hdr.service_choice, SERVICE_CREATE_OBJECT);
    }

    #[test]
    fn encode_delete_object_request() {
        let req = DeleteObjectRequest {
            object_id: ObjectId::new(ObjectType::AnalogValue, 9),
            invoke_id: 5,
        };
        let mut buf = [0u8; 64];
        let mut w = Writer::new(&mut buf);
        req.encode(&mut w).unwrap();
        let mut r = Reader::new(w.as_written());
        let hdr = ConfirmedRequestHeader::decode(&mut r).unwrap();
        assert_eq!(hdr.service_choice, SERVICE_DELETE_OBJECT);
    }

    #[test]
    fn decode_create_object_ack() {
        let mut buf = [0u8; 64];
        let mut w = Writer::new(&mut buf);
        ComplexAckHeader {
            segmented: false,
            more_follows: false,
            invoke_id: 3,
            sequence_number: None,
            proposed_window_size: None,
            service_choice: SERVICE_CREATE_OBJECT,
        }
        .encode(&mut w)
        .unwrap();
        encode_ctx_object_id(&mut w, 0, ObjectId::new(ObjectType::AnalogValue, 9).raw()).unwrap();
        let mut r = Reader::new(w.as_written());
        let _ack_hdr = ComplexAckHeader::decode(&mut r).unwrap();
        let ack = CreateObjectAck::decode_after_header(&mut r).unwrap();
        assert_eq!(ack.object_id, ObjectId::new(ObjectType::AnalogValue, 9));
    }
}