1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! [![LICENSE](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](LICENSE)
//! [![Crates.io Version](https://img.shields.io/crates/v/bacnet_parse.svg)](https://crates.io/crates/bacnet_parse)
//!
//! # bacnet_parse is a #![no_std] library to parse BACnet bytes into read-only data structures
//!
//! Currently handles:
//! * MS/TP
//! * BVLL (basic - just enough to get NPDU)
//! * NPDU
//!
//! Targeting support for:
//! * NSDU ([NLM/RPDU](http://www.bacnetwiki.com/wiki/index.php?title=Network_Layer_Message_Type), APDU)
//!
//! To assist parsing BACnet IP or BACnet Ethernet, two recommended libraries are:
//! * [pnet](https://crates.io/crates/pnet)
//! * [etherparse](https://crates.io/crates/etherparse)
//!
//! ## How to use this library
//!
//! For BACnet ethernet and BACnet IP, first identify your BACnet application layer bytes then call
//! to `parse_bvlc(bytes)` and go from there.
//!
//! For MSTP, call either `parse_mstp(bytes)` or `parse_mstp_skip_crc_compute(bytes)`.
//!
//! Not yet implemented below:
//!
//! In order to parse the RPDU or APDU, first check which one you have with `npdu.is_apdu()` then
//! call either `parse_apdu(npdu.payload())` or `parse_rpdu(npdu.payload())`.
//!
//! ## Examples
//!
//! BVLC example
//!
//! ```
//! # use bacnet_parse::*;
//! # use bacnet_parse::bvlc::*;
//! # fn main() -> Result <(), Error> {
//! let bytes: &[u8] = &[
//!     0x81, 0x0a, 0x00, 0x1b, // BVLC
//!     0x01, 0x20, 0x00, 0x0d, 0x01, 0x3d, 0xff, // NPDU
//!     0x30, 0xc9, 0x0c, 0x0c, 0x02, 0x00, 0x00, 0x6f, 0x19, 0x4c, 0x29, 0x00, 0x3e, 0x21,
//!     0x21, 0x3f, // APDU
//! ];
//!
//! let bvlc = parse_bvlc(&bytes)?;
//!
//! assert_eq!(bvlc.bvlc_function(), BVLCFunction::UnicastNPDU);
//!
//! let npdu = bvlc.npdu().as_ref().expect("npdu");
//!
//! assert_eq!(npdu.ncpi_control(), 0x20);
//! assert_eq!(npdu.is_apdu(), true);
//! assert_eq!(npdu.is_src_spec_present(), false);
//! assert_eq!(npdu.is_dst_spec_present(), true);
//! assert_eq!(npdu.is_expecting_reply(), false);
//! assert_eq!(npdu.src().is_none(), true);
//!
//! let dst_hopcount = npdu.dst_hopcount().as_ref().expect("dst_hopcount");
//!
//! assert_eq!(dst_hopcount.hopcount(), 255);
//!
//! let dst = dst_hopcount.dst();
//!
//! assert_eq!(dst.net(), 13);
//! assert_eq!(dst.addr().len(), 1);
//! assert_eq!(dst.addr()[0], 61);
//! # Ok(())
//! # }
//! ```
//!
//! MSTP example
//! ```
//! # use bacnet_parse::*;
//! # use bacnet_parse::mstp::*;
//! # fn main() -> Result <(), Error> {
//! let bytes: &[u8] = &[
//!     0x55, 0xff, 0x05, 0x0c, 0x7f, 0x00, 0x1f, 0x35, 0x01, 0x0c, 0x00, 0x01, 0x06, 0xc0,
//!     0xa8, 0x01, 0x12, 0xba, 0xc0, 0x02, 0x01, 0x6a, 0x0f, 0x0c, 0x00, 0x80, 0x00, 0x0a,
//!     0x19, 0x55, 0x3e, 0x44, 0x41, 0xe8, 0x00, 0x01, 0x3f, 0x49, 0x09, 0xc9, 0x6f,
//! ];
//!
//! let frame = parse_mstp(bytes)?;
//!
//! let (actual, expected) = frame.crcs().header();
//! assert_eq!(actual, expected);
//! assert_eq!(actual, 0x35);
//!
//! let (actual, expected) = frame.crcs().data();
//! assert_eq!(actual, expected);
//! assert_eq!(actual, 0x6fc9);
//!
//! assert_eq!(frame.frame_type(), MSTPFrameType::BACnetDataExpectingReply);
//! let npdu = frame.npdu().as_ref().expect("npdu");
//!
//! let src = npdu.src().as_ref().expect("src");
//! assert_eq!(src.net(), 1);
//! assert_eq!(src.addr().len(), 6);
//! let addr_cmp: &[u8] = &[0xc0, 0xa8, 0x01, 0x12, 0xba, 0xc0];
//! assert_eq!(src.addr(), addr_cmp);
//! assert_eq!(npdu.dst_hopcount().is_none(), true);
//!
//! let bytes: &[u8] = &[
//!     0x55, 0xff, 0x05, 0x0c, 0x7f, 0x00, 0x1f, 0x34, 0x01, 0x0c, 0x00, 0x01, 0x06, 0xc0,
//!     0xa8, 0x01, 0x12, 0xba, 0xc0, 0x02, 0x01, 0x6a, 0x0f, 0x0c, 0x00, 0x80, 0x00, 0x0a,
//!     0x19, 0x55, 0x3e, 0x44, 0x41, 0xe8, 0x00, 0x01, 0x3f, 0x49, 0x09, 0xc9, 0x6e,
//! ];
//!
//! let frame = parse_mstp(bytes)?;
//!
//! let (actual, expected) = frame.crcs().header();
//! assert_ne!(actual, expected);
//! assert_eq!(actual, 0x34);
//!
//! let (actual, expected) = frame.crcs().data();
//! assert_ne!(actual, expected);
//! assert_eq!(actual, 0x6ec9);
//! # Ok(())
//! # }
//! ```
//!
//! ## Why not use [nom](https://crates.io/crates/nom)?
//!
//! nom is a great library, but I don't think it's well suited to application layer data with weird
//! formats like BACnet. For example, the weirdness of the NPDU layout where the hop count value's
//! existence is tied to but may or may not be contiguous with the destination port/address.
//!
//! Avoiding the use of nom may also lower the barrier to entry for contribution so that a
//! potential contributor does not also need to learn the nom library.
//!
//! These are opinions, so if you disagree and would like to use nom for parsing, feel free to make
//! a pull request that includes nom.
#![no_std]

pub mod mstp;
pub use mstp::{parse_mstp, parse_mstp_skip_crc_compute};

pub mod bvlc;
pub use bvlc::parse_bvlc;

pub mod npdu;

pub mod nsdu;
pub use nsdu::{parse_apdu, parse_rpdu};

#[derive(Debug)]
pub enum Error {
    Length(&'static str),
    InvalidValue(&'static str),
    Unknown,
}

impl From<()> for Error {
    fn from(_: ()) -> Self {
        Self::Unknown
    }
}

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

    #[test]
    fn simple_test() {
        let bytes: &[u8] = &[
            0x81, 0x0a, 0x00, 0x1b, // BVLC
            0x01, 0x20, 0x00, 0x0d, 0x01, 0x3d, 0xff, // NPDU
            0x30, 0xc9, 0x0c, 0x0c, 0x02, 0x00, 0x00, 0x6f, 0x19, 0x4c, 0x29, 0x00, 0x3e, 0x21,
            0x21, 0x3f, // APDU
        ];

        let bvlc = parse_bvlc(&bytes).unwrap();
        assert_eq!(bvlc.bvlc_function(), BVLCFunction::UnicastNPDU);

        let npdu = bvlc.npdu().as_ref().unwrap();
        assert_eq!(npdu.ncpi_control(), 0x20);
        assert_eq!(npdu.is_apdu(), true);
        assert_eq!(npdu.is_src_spec_present(), false);
        assert_eq!(npdu.is_dst_spec_present(), true);
        assert_eq!(npdu.is_expecting_reply(), false);

        assert_eq!(npdu.src().is_none(), true);

        let dst_hopcount = npdu.dst_hopcount().as_ref().unwrap();
        assert_eq!(dst_hopcount.hopcount(), 255);

        let dst = dst_hopcount.dst();
        assert_eq!(dst.net(), 13);
        assert_eq!(dst.addr().len(), 1);
        assert_eq!(dst.addr()[0], 61);
    }
}