use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
pub const BVLC_TYPE: u8 = 0x81;
pub const BVLC_HEADER_SIZE: usize = 4;
pub const BACNET_IP_PORT: u16 = 47808;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum BvlcFunction {
Result = 0x00,
WriteBroadcastDistributionTable = 0x01,
ReadBroadcastDistributionTable = 0x02,
ReadBroadcastDistributionTableAck = 0x03,
ForwardedNpdu = 0x04,
RegisterForeignDevice = 0x05,
ReadForeignDeviceTable = 0x06,
ReadForeignDeviceTableAck = 0x07,
DeleteForeignDeviceTableEntry = 0x08,
DistributeBroadcastToNetwork = 0x09,
OriginalUnicastNpdu = 0x0A,
OriginalBroadcastNpdu = 0x0B,
SecureBvll = 0x0C,
}
impl BvlcFunction {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0x00 => Some(Self::Result),
0x01 => Some(Self::WriteBroadcastDistributionTable),
0x02 => Some(Self::ReadBroadcastDistributionTable),
0x03 => Some(Self::ReadBroadcastDistributionTableAck),
0x04 => Some(Self::ForwardedNpdu),
0x05 => Some(Self::RegisterForeignDevice),
0x06 => Some(Self::ReadForeignDeviceTable),
0x07 => Some(Self::ReadForeignDeviceTableAck),
0x08 => Some(Self::DeleteForeignDeviceTableEntry),
0x09 => Some(Self::DistributeBroadcastToNetwork),
0x0A => Some(Self::OriginalUnicastNpdu),
0x0B => Some(Self::OriginalBroadcastNpdu),
0x0C => Some(Self::SecureBvll),
_ => None,
}
}
pub fn has_npdu(&self) -> bool {
matches!(
self,
Self::ForwardedNpdu | Self::OriginalUnicastNpdu | Self::OriginalBroadcastNpdu
)
}
pub fn is_broadcast(&self) -> bool {
matches!(
self,
Self::OriginalBroadcastNpdu | Self::DistributeBroadcastToNetwork
)
}
}
impl TryFrom<u8> for BvlcFunction {
type Error = BvlcError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::from_u8(value).ok_or(BvlcError::UnknownFunction(value))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum BvlcResultCode {
Success = 0x0000,
WriteBdtNak = 0x0010,
ReadBdtNak = 0x0020,
RegisterForeignDeviceNak = 0x0030,
ReadFdtNak = 0x0040,
DeleteFdtEntryNak = 0x0050,
DistributeBroadcastNak = 0x0060,
}
impl BvlcResultCode {
pub fn from_u16(value: u16) -> Option<Self> {
match value {
0x0000 => Some(Self::Success),
0x0010 => Some(Self::WriteBdtNak),
0x0020 => Some(Self::ReadBdtNak),
0x0030 => Some(Self::RegisterForeignDeviceNak),
0x0040 => Some(Self::ReadFdtNak),
0x0050 => Some(Self::DeleteFdtEntryNak),
0x0060 => Some(Self::DistributeBroadcastNak),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BvlcHeader {
pub function: BvlcFunction,
pub length: u16,
}
impl BvlcHeader {
pub fn new(function: BvlcFunction, length: u16) -> Self {
Self { function, length }
}
pub fn encode(&self, buf: &mut BytesMut) {
buf.put_u8(BVLC_TYPE);
buf.put_u8(self.function as u8);
buf.put_u16(self.length);
}
pub fn decode(data: &[u8]) -> Result<Self, BvlcError> {
if data.len() < BVLC_HEADER_SIZE {
return Err(BvlcError::TooShort {
expected: BVLC_HEADER_SIZE,
actual: data.len(),
});
}
let bvlc_type = data[0];
if bvlc_type != BVLC_TYPE {
return Err(BvlcError::InvalidType(bvlc_type));
}
let function = BvlcFunction::try_from(data[1])?;
let length = u16::from_be_bytes([data[2], data[3]]);
Ok(Self { function, length })
}
}
#[derive(Debug, Clone)]
pub struct BvlcMessage {
pub header: BvlcHeader,
pub npdu: Vec<u8>,
pub original_source: Option<SocketAddrV4>,
pub result_code: Option<BvlcResultCode>,
}
impl BvlcMessage {
pub fn original_unicast(npdu: Vec<u8>) -> Self {
let length = (BVLC_HEADER_SIZE + npdu.len()) as u16;
Self {
header: BvlcHeader::new(BvlcFunction::OriginalUnicastNpdu, length),
npdu,
original_source: None,
result_code: None,
}
}
pub fn original_broadcast(npdu: Vec<u8>) -> Self {
let length = (BVLC_HEADER_SIZE + npdu.len()) as u16;
Self {
header: BvlcHeader::new(BvlcFunction::OriginalBroadcastNpdu, length),
npdu,
original_source: None,
result_code: None,
}
}
pub fn forwarded_npdu(npdu: Vec<u8>, original_source: SocketAddrV4) -> Self {
let length = (BVLC_HEADER_SIZE + 6 + npdu.len()) as u16;
Self {
header: BvlcHeader::new(BvlcFunction::ForwardedNpdu, length),
npdu,
original_source: Some(original_source),
result_code: None,
}
}
pub fn result(code: BvlcResultCode) -> Self {
Self {
header: BvlcHeader::new(BvlcFunction::Result, 6), npdu: Vec::new(),
original_source: None,
result_code: Some(code),
}
}
pub fn encode(&self) -> Bytes {
let mut buf = BytesMut::with_capacity(self.header.length as usize);
self.header.encode(&mut buf);
match self.header.function {
BvlcFunction::Result => {
if let Some(code) = self.result_code {
buf.put_u16(code as u16);
}
}
BvlcFunction::ForwardedNpdu => {
if let Some(addr) = &self.original_source {
buf.put_slice(&addr.ip().octets());
buf.put_u16(addr.port());
}
buf.put_slice(&self.npdu);
}
_ => {
buf.put_slice(&self.npdu);
}
}
buf.freeze()
}
pub fn decode(data: &[u8]) -> Result<Self, BvlcError> {
let header = BvlcHeader::decode(data)?;
if data.len() < header.length as usize {
return Err(BvlcError::TooShort {
expected: header.length as usize,
actual: data.len(),
});
}
let mut cursor = &data[BVLC_HEADER_SIZE..];
match header.function {
BvlcFunction::Result => {
if cursor.len() < 2 {
return Err(BvlcError::TooShort {
expected: 2,
actual: cursor.len(),
});
}
let code_value = cursor.get_u16();
let result_code = BvlcResultCode::from_u16(code_value);
Ok(Self {
header,
npdu: Vec::new(),
original_source: None,
result_code,
})
}
BvlcFunction::ForwardedNpdu => {
if cursor.len() < 6 {
return Err(BvlcError::TooShort {
expected: 6,
actual: cursor.len(),
});
}
let ip = Ipv4Addr::new(
cursor.get_u8(),
cursor.get_u8(),
cursor.get_u8(),
cursor.get_u8(),
);
let port = cursor.get_u16();
let original_source = SocketAddrV4::new(ip, port);
Ok(Self {
header,
npdu: cursor.to_vec(),
original_source: Some(original_source),
result_code: None,
})
}
BvlcFunction::OriginalUnicastNpdu | BvlcFunction::OriginalBroadcastNpdu => Ok(Self {
header,
npdu: cursor.to_vec(),
original_source: None,
result_code: None,
}),
_ => Ok(Self {
header,
npdu: cursor.to_vec(),
original_source: None,
result_code: None,
}),
}
}
pub fn npdu(&self) -> Option<&[u8]> {
if self.header.function.has_npdu() && !self.npdu.is_empty() {
Some(&self.npdu)
} else {
None
}
}
pub fn is_broadcast(&self) -> bool {
self.header.function.is_broadcast()
}
pub fn effective_source(&self) -> Option<SocketAddr> {
self.original_source.map(SocketAddr::V4)
}
}
#[derive(Debug, thiserror::Error)]
pub enum BvlcError {
#[error("Message too short: expected {expected}, got {actual}")]
TooShort { expected: usize, actual: usize },
#[error("Invalid BVLC type: 0x{0:02X} (expected 0x81)")]
InvalidType(u8),
#[error("Unknown BVLC function: 0x{0:02X}")]
UnknownFunction(u8),
#[error("Invalid message format")]
InvalidFormat,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bvlc_function_from_u8() {
assert_eq!(
BvlcFunction::from_u8(0x0A),
Some(BvlcFunction::OriginalUnicastNpdu)
);
assert_eq!(
BvlcFunction::from_u8(0x0B),
Some(BvlcFunction::OriginalBroadcastNpdu)
);
assert_eq!(BvlcFunction::from_u8(0xFF), None);
}
#[test]
fn test_bvlc_header_encode_decode() {
let header = BvlcHeader::new(BvlcFunction::OriginalUnicastNpdu, 10);
let mut buf = BytesMut::new();
header.encode(&mut buf);
let decoded = BvlcHeader::decode(&buf).unwrap();
assert_eq!(decoded.function, BvlcFunction::OriginalUnicastNpdu);
assert_eq!(decoded.length, 10);
}
#[test]
fn test_original_unicast_encode_decode() {
let npdu = vec![0x01, 0x04, 0x00, 0x05]; let msg = BvlcMessage::original_unicast(npdu.clone());
let encoded = msg.encode();
let decoded = BvlcMessage::decode(&encoded).unwrap();
assert_eq!(decoded.header.function, BvlcFunction::OriginalUnicastNpdu);
assert_eq!(decoded.npdu, npdu);
}
#[test]
fn test_original_broadcast_encode_decode() {
let npdu = vec![0x01, 0x04, 0x00, 0x05];
let msg = BvlcMessage::original_broadcast(npdu.clone());
let encoded = msg.encode();
let decoded = BvlcMessage::decode(&encoded).unwrap();
assert_eq!(decoded.header.function, BvlcFunction::OriginalBroadcastNpdu);
assert!(decoded.is_broadcast());
}
#[test]
fn test_forwarded_npdu_encode_decode() {
let npdu = vec![0x01, 0x04, 0x00, 0x05];
let source = SocketAddrV4::new(Ipv4Addr::new(192, 168, 1, 100), 47808);
let msg = BvlcMessage::forwarded_npdu(npdu.clone(), source);
let encoded = msg.encode();
let decoded = BvlcMessage::decode(&encoded).unwrap();
assert_eq!(decoded.header.function, BvlcFunction::ForwardedNpdu);
assert_eq!(decoded.original_source, Some(source));
assert_eq!(decoded.npdu, npdu);
}
#[test]
fn test_bvlc_result_encode_decode() {
let msg = BvlcMessage::result(BvlcResultCode::Success);
let encoded = msg.encode();
let decoded = BvlcMessage::decode(&encoded).unwrap();
assert_eq!(decoded.header.function, BvlcFunction::Result);
assert_eq!(decoded.result_code, Some(BvlcResultCode::Success));
}
}