bacnet_parse 0.2.0

no_std BACnet parser
Documentation
use super::Error;
use arrayref::array_ref;

pub fn parse_npdu(bytes: &[u8]) -> Result<NPDU, Error> {
    if bytes.len() < 3 {
        return Err(Error::Length("insufficient size for npdu"));
    }
    if bytes[0] != 0x01 {
        return Err(Error::InvalidValue("unhandled npdu version"));
    }

    let mut npdu = NPDU::default();
    npdu.ncpi_control = bytes[1];

    npdu.payload = if npdu.is_dst_spec_present() {
        let bytes_after_dst = if let Ok((s, dst)) = NetAddr::parse(&bytes[2..]) {
            npdu.dst = Some(DstHopCount { dst, hopcount: 0 });
            s
        } else {
            bytes
        };

        let hopcount_bytes = if npdu.is_src_spec_present() {
            let (bytes_after_src, src) = NetAddr::parse(bytes_after_dst)?;
            npdu.src = Some(src);
            bytes_after_src
        } else {
            bytes_after_dst
        };

        if hopcount_bytes.len() < 2 {
            return Err(Error::Length("insufficient size for hopcount and payload"));
        }
        if let Some(dst_hopcount) = &mut npdu.dst {
            dst_hopcount.hopcount = hopcount_bytes[0];
            &hopcount_bytes[1..]
        } else {
            hopcount_bytes
        }
    } else if npdu.is_src_spec_present() {
        let (bytes_after_src, src_addr) = NetAddr::parse(&bytes[2..])?;
        npdu.src = Some(src_addr);
        bytes_after_src
    } else {
        &bytes[2..]
    };
    Ok(npdu)
}

#[derive(Default)]
pub struct NPDU<'a> {
    ncpi_control: u8,
    dst: Option<DstHopCount<'a>>,
    src: Option<NetAddr<'a>>,
    payload: &'a [u8],
}

impl<'a> NPDU<'a> {
    /// `true` if the payload is APDU, `false` if payload is NLM/RPDU
    pub fn is_apdu(&self) -> bool {
        self.ncpi_control & 0x80 == 0
    }

    pub fn is_dst_spec_present(&self) -> bool {
        self.ncpi_control & 0x20 != 0
    }

    pub fn is_src_spec_present(&self) -> bool {
        self.ncpi_control & 0x08 != 0
    }

    pub fn is_expecting_reply(&self) -> bool {
        self.ncpi_control & 0x04 != 0
    }

    pub fn prio(&self) -> NCPIPriority {
        match self.ncpi_control & 3 {
            0 => NCPIPriority::Normal,
            1 => NCPIPriority::Urgent,
            2 => NCPIPriority::CriticalEquip,
            3 => NCPIPriority::LifeSafety,
            _ => unsafe { core::hint::unreachable_unchecked() },
        }
    }

    pub fn src(&self) -> &Option<NetAddr<'a>> {
        &self.src
    }

    pub fn dst_hopcount(&self) -> &Option<DstHopCount<'a>> {
        &self.dst
    }

    pub fn ncpi_control(&self) -> u8 {
        self.ncpi_control
    }

    pub fn payload(&self) -> &'a [u8] {
        self.payload
    }
}

#[derive(Debug, PartialEq)]
pub enum NCPIPriority {
    LifeSafety,
    CriticalEquip,
    Urgent,
    Normal,
}

#[derive(Default)]
pub struct DstHopCount<'a> {
    dst: NetAddr<'a>,
    hopcount: u8,
}

impl<'a> DstHopCount<'a> {
    pub fn dst(&self) -> &NetAddr<'a> {
        &self.dst
    }

    pub fn hopcount(&self) -> u8 {
        self.hopcount
    }
}

#[derive(Default, Debug)]
pub struct NetAddr<'a> {
    net: u16,
    addr: &'a [u8],
}

impl<'a> NetAddr<'a> {
    fn parse(b: &'a [u8]) -> Result<(&'a [u8], Self), Error> {
        if b.len() < 4 {
            return Err(Error::Length("insufficient size for netaddr"));
        }
        let net = u16::from_be_bytes(*array_ref!(b, 0, 2));
        let addrlen = b[2];
        let rest_of_bytes_offset = 3 + addrlen as usize;
        if b.len() < rest_of_bytes_offset {
            return Err(Error::Length("insufficient size for netaddr address"));
        }
        let addr = &b[3..3 + addrlen as usize];
        Ok((&b[rest_of_bytes_offset..], Self { net, addr }))
    }

    pub fn net(&self) -> u16 {
        self.net
    }

    pub fn addr(&self) -> &'a [u8] {
        self.addr
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn netaddr_test() {
        const BYTES: &[u8] = &[0x12, 0x34, 0x4, 192, 168, 1, 10];
        let (rest, netaddr) = NetAddr::parse(BYTES).unwrap();
        assert_eq!(rest.len(), 0);
        assert_eq!(netaddr.net(), 0x1234);
        assert_eq!(netaddr.addr(), &[192, 168, 1, 10]);
    }
}