use bacnet_rs::{
network::Npdu,
object::Device,
service::{IAmRequest, UnconfirmedServiceChoice, WhoIsRequest},
};
use std::{
net::{SocketAddr, UdpSocket},
sync::atomic::{AtomicBool, Ordering},
sync::Arc,
time::Duration,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("BACnet Responder Device Example");
println!("==============================\n");
let args: Vec<String> = std::env::args().collect();
let device_id: u32 = if args.len() > 1 {
args[1].parse().unwrap_or(12345)
} else {
12345
};
let mut device = Device::new(device_id, format!("Test Device {}", device_id));
device.set_vendor_by_id(260)?; device.model_name = "Rust BACnet Test Device".to_string();
device.firmware_revision = "1.0.0".to_string();
println!("Creating BACnet device:");
println!(" Device ID: {}", device.identifier.instance);
println!(" Name: {}", device.object_name);
println!(" Vendor: {}", device.format_vendor_display());
println!(" Model: {}", device.model_name);
println!();
let bind_addr = "0.0.0.0:47808";
let socket = UdpSocket::bind(bind_addr)?;
socket.set_broadcast(true)?;
socket.set_read_timeout(Some(Duration::from_millis(100)))?;
println!("Listening on {}...", bind_addr);
println!("Device is ready to respond to Who-Is requests!");
println!("Press Ctrl+C to stop.\n");
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
let mut recv_buffer = [0u8; 1500];
let mut response_count = 0;
while running.load(Ordering::SeqCst) {
match socket.recv_from(&mut recv_buffer) {
Ok((len, source)) => {
if let Some(whois) = process_whois(&recv_buffer[..len]) {
if whois.matches(device_id) {
println!("Received Who-Is from {} (matches our device)", source);
if let Ok(response) = create_iam_response(&device, source) {
match socket.send_to(&response, source) {
Ok(_) => {
response_count += 1;
println!(
"Sent I-Am response #{} to {}",
response_count, source
);
}
Err(e) => {
eprintln!("Failed to send I-Am: {}", e);
}
}
}
} else {
println!("Received Who-Is from {} (not for us)", source);
}
}
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
}
Err(e) => {
eprintln!("Error receiving: {}", e);
}
}
}
println!("\nShutting down...");
println!("Total I-Am responses sent: {}", response_count);
Ok(())
}
fn process_whois(data: &[u8]) -> Option<WhoIsRequest> {
if data.len() < 4 || data[0] != 0x81 {
return None;
}
let bvlc_function = data[1];
let bvlc_length = ((data[2] as u16) << 8) | (data[3] as u16);
if data.len() != bvlc_length as usize {
return None;
}
let npdu_start = match bvlc_function {
0x0A | 0x0B => 4, 0x04 => 10, _ => return None,
};
if data.len() <= npdu_start {
return None;
}
let (_npdu, npdu_len) = match Npdu::decode(&data[npdu_start..]) {
Ok(result) => result,
Err(_) => return None,
};
let apdu_start = npdu_start + npdu_len;
if data.len() <= apdu_start {
return None;
}
let apdu = &data[apdu_start..];
if apdu.len() < 2 || apdu[0] != 0x10 {
return None;
}
let service_choice = apdu[1];
if service_choice != UnconfirmedServiceChoice::WhoIs as u8 {
return None;
}
if apdu.len() > 2 {
WhoIsRequest::decode(&apdu[2..]).ok()
} else {
Some(WhoIsRequest::new())
}
}
fn create_iam_response(
device: &Device,
_destination: SocketAddr,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let iam = IAmRequest::new(
device.identifier,
1476, 0, device.vendor_identifier as u32,
);
let mut iam_buffer = Vec::new();
iam.encode(&mut iam_buffer)?;
let mut npdu = Npdu::new();
npdu.control.priority = 0;
let npdu_buffer = npdu.encode();
let mut apdu = vec![0x10]; apdu.push(UnconfirmedServiceChoice::IAm as u8);
apdu.extend_from_slice(&iam_buffer);
let mut message = npdu_buffer;
message.extend_from_slice(&apdu);
let mut bvlc_message = vec![
0x81, 0x0A, 0x00, 0x00, ];
bvlc_message.extend_from_slice(&message);
let total_len = bvlc_message.len() as u16;
bvlc_message[2] = (total_len >> 8) as u8;
bvlc_message[3] = (total_len & 0xFF) as u8;
Ok(bvlc_message)
}