1use std::collections::HashMap;
8use std::net::Ipv4Addr;
9use std::sync::atomic::{AtomicU8, Ordering};
10use std::sync::Arc;
11use std::time::Instant;
12
13use bytes::{Bytes, BytesMut};
14use tokio::sync::{mpsc, oneshot, Mutex, RwLock, Semaphore};
15use tokio::task::JoinHandle;
16use tokio::time::Duration;
17use tracing::{debug, warn};
18
19use bacnet_encoding::apdu::{
20 self, encode_apdu, validate_max_apdu_length, AbortPdu, Apdu, ComplexAck,
21 ConfirmedRequest as ConfirmedRequestPdu, ErrorPdu, RejectPdu, SegmentAck as SegmentAckPdu,
22 SimpleAck, UnconfirmedRequest as UnconfirmedRequestPdu,
23};
24use bacnet_encoding::primitives::encode_property_value;
25use bacnet_encoding::segmentation::{
26 max_segment_payload, split_payload, SegmentReceiver, SegmentedPduType,
27};
28use bacnet_network::layer::NetworkLayer;
29use bacnet_objects::database::ObjectDatabase;
30use bacnet_objects::notification_class::get_notification_recipients;
31use bacnet_services::alarm_event::EventNotificationRequest;
32use bacnet_services::common::BACnetPropertyValue;
33use bacnet_services::cov::COVNotificationRequest;
34use bacnet_services::who_is::{IAmRequest, WhoIsRequest};
35use bacnet_transport::bip::BipTransport;
36use bacnet_transport::port::TransportPort;
37use bacnet_types::enums::{
38 AbortReason, ConfirmedServiceChoice, ErrorClass, ErrorCode, NetworkPriority, NotifyType,
39 ObjectType, PropertyIdentifier, RejectReason, Segmentation, UnconfirmedServiceChoice,
40};
41use bacnet_types::error::Error;
42use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue, Time};
43use bacnet_types::MacAddr;
44
45use crate::cov::CovSubscriptionTable;
46use crate::handlers;
47
48const MAX_SEG_RECEIVERS: usize = 128;
50
51const SEG_RECEIVER_TIMEOUT: Duration = Duration::from_secs(4);
53
54const MAX_NEG_SEGMENT_ACK_RETRIES: u8 = 3;
56
57const DEFAULT_APDU_RETRIES: u8 = 3;
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum CovAckResult {
67 Ack,
69 Error,
71}
72
73pub struct ServerTsm {
80 next_invoke_id: u8,
81 pending: HashMap<(MacAddr, u8), oneshot::Sender<CovAckResult>>,
84}
85
86impl ServerTsm {
87 fn new() -> Self {
88 Self {
89 next_invoke_id: 0,
90 pending: HashMap::new(),
91 }
92 }
93
94 fn allocate(&mut self, peer: MacAddr) -> (u8, oneshot::Receiver<CovAckResult>) {
97 let id = self.next_invoke_id;
98 self.next_invoke_id = self.next_invoke_id.wrapping_add(1);
99 let rx = self.register(peer, id);
100 (id, rx)
101 }
102
103 fn register(&mut self, peer: MacAddr, invoke_id: u8) -> oneshot::Receiver<CovAckResult> {
105 let (tx, rx) = oneshot::channel();
106 self.pending.insert((peer, invoke_id), tx);
107 rx
108 }
109
110 fn record_result(&mut self, peer: &MacAddr, invoke_id: u8, result: CovAckResult) -> bool {
113 if let Some(tx) = self.pending.remove(&(peer.clone(), invoke_id)) {
114 let _ = tx.send(result);
115 true
116 } else {
117 false
118 }
119 }
120
121 fn remove(&mut self, peer: &MacAddr, invoke_id: u8) {
123 self.pending.remove(&(peer.clone(), invoke_id));
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct TimeSyncData {
130 pub raw_service_data: Bytes,
132 pub is_utc: bool,
134}
135
136#[derive(Clone)]
138pub struct ServerConfig {
139 pub interface: Ipv4Addr,
141 pub port: u16,
143 pub broadcast_address: Ipv4Addr,
145 pub max_apdu_length: u32,
147 pub segmentation_supported: Segmentation,
149 pub vendor_id: u16,
151 pub cov_retry_timeout_ms: u64,
153 pub on_time_sync: Option<Arc<dyn Fn(TimeSyncData) + Send + Sync>>,
155 pub dcc_password: Option<String>,
157 pub reinit_password: Option<String>,
159 pub enable_fault_detection: bool,
163}
164
165impl std::fmt::Debug for ServerConfig {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 f.debug_struct("ServerConfig")
168 .field("interface", &self.interface)
169 .field("port", &self.port)
170 .field("broadcast_address", &self.broadcast_address)
171 .field("max_apdu_length", &self.max_apdu_length)
172 .field("segmentation_supported", &self.segmentation_supported)
173 .field("vendor_id", &self.vendor_id)
174 .field("cov_retry_timeout_ms", &self.cov_retry_timeout_ms)
175 .field(
176 "on_time_sync",
177 &self.on_time_sync.as_ref().map(|_| "<callback>"),
178 )
179 .field("dcc_password", &self.dcc_password.as_ref().map(|_| "***"))
180 .field(
181 "reinit_password",
182 &self.reinit_password.as_ref().map(|_| "***"),
183 )
184 .field("enable_fault_detection", &self.enable_fault_detection)
185 .finish()
186 }
187}
188
189impl Default for ServerConfig {
190 fn default() -> Self {
191 Self {
192 interface: Ipv4Addr::UNSPECIFIED,
193 port: 0xBAC0,
194 broadcast_address: Ipv4Addr::BROADCAST,
195 max_apdu_length: 1476,
196 segmentation_supported: Segmentation::NONE,
197 vendor_id: 0,
198 cov_retry_timeout_ms: 3000,
199 on_time_sync: None,
200 dcc_password: None,
201 reinit_password: None,
202 enable_fault_detection: false,
203 }
204 }
205}
206
207pub struct ServerBuilder<T: TransportPort> {
209 config: ServerConfig,
210 db: ObjectDatabase,
211 transport: Option<T>,
212}
213
214impl<T: TransportPort + 'static> ServerBuilder<T> {
215 pub fn database(mut self, db: ObjectDatabase) -> Self {
217 self.db = db;
218 self
219 }
220
221 pub fn transport(mut self, transport: T) -> Self {
223 self.transport = Some(transport);
224 self
225 }
226
227 pub fn dcc_password(mut self, password: impl Into<String>) -> Self {
229 self.config.dcc_password = Some(password.into());
230 self
231 }
232
233 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
235 self.config.reinit_password = Some(password.into());
236 self
237 }
238
239 pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
241 self.config.enable_fault_detection = enabled;
242 self
243 }
244
245 pub fn vendor_id(mut self, id: u16) -> Self {
247 self.config.vendor_id = id;
248 self
249 }
250
251 pub async fn build(self) -> Result<BACnetServer<T>, Error> {
253 let transport = self
254 .transport
255 .ok_or_else(|| Error::Encoding("transport not set on ServerBuilder".into()))?;
256 BACnetServer::start(self.config, self.db, transport).await
257 }
258}
259
260pub struct BipServerBuilder {
262 config: ServerConfig,
263 db: ObjectDatabase,
264}
265
266impl BipServerBuilder {
267 pub fn interface(mut self, ip: Ipv4Addr) -> Self {
269 self.config.interface = ip;
270 self
271 }
272
273 pub fn port(mut self, port: u16) -> Self {
275 self.config.port = port;
276 self
277 }
278
279 pub fn broadcast_address(mut self, addr: Ipv4Addr) -> Self {
281 self.config.broadcast_address = addr;
282 self
283 }
284
285 pub fn database(mut self, db: ObjectDatabase) -> Self {
287 self.db = db;
288 self
289 }
290
291 pub fn dcc_password(mut self, password: impl Into<String>) -> Self {
293 self.config.dcc_password = Some(password.into());
294 self
295 }
296
297 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
299 self.config.reinit_password = Some(password.into());
300 self
301 }
302
303 pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
305 self.config.enable_fault_detection = enabled;
306 self
307 }
308
309 pub async fn build(self) -> Result<BACnetServer<BipTransport>, Error> {
311 let transport = BipTransport::new(
312 self.config.interface,
313 self.config.port,
314 self.config.broadcast_address,
315 );
316 BACnetServer::start(self.config, self.db, transport).await
317 }
318}
319
320type SegKey = (MacAddr, u8);
322
323struct SegmentedRequestState {
324 receiver: SegmentReceiver,
325 first_req: bacnet_encoding::apdu::ConfirmedRequest,
326 last_activity: Instant,
327 expected_seq: u8,
328 last_acked_seq: u8,
329 window_pos: u8,
330 actual_window_size: u8,
331}
332
333pub struct BACnetServer<T: TransportPort> {
335 #[allow(dead_code)]
336 config: ServerConfig,
337 #[allow(dead_code)]
339 network: Arc<NetworkLayer<T>>,
340 db: Arc<RwLock<ObjectDatabase>>,
342 #[allow(dead_code)]
344 cov_table: Arc<RwLock<CovSubscriptionTable>>,
345 #[allow(dead_code)]
347 seg_ack_senders: Arc<Mutex<HashMap<SegKey, mpsc::Sender<SegmentAckPdu>>>>,
348 #[allow(dead_code)]
351 cov_in_flight: Arc<Semaphore>,
352 #[allow(dead_code)]
354 server_tsm: Arc<Mutex<ServerTsm>>,
355 comm_state: Arc<AtomicU8>,
357 #[allow(dead_code)]
360 dcc_timer: Arc<Mutex<Option<JoinHandle<()>>>>,
361 dispatch_task: Option<JoinHandle<()>>,
362 cov_purge_task: Option<JoinHandle<()>>,
363 fault_detection_task: Option<JoinHandle<()>>,
364 event_enrollment_task: Option<JoinHandle<()>>,
365 trend_log_task: Option<JoinHandle<()>>,
366 schedule_tick_task: Option<JoinHandle<()>>,
367 local_mac: MacAddr,
368}
369
370impl BACnetServer<BipTransport> {
371 pub fn bip_builder() -> BipServerBuilder {
373 BipServerBuilder {
374 config: ServerConfig::default(),
375 db: ObjectDatabase::new(),
376 }
377 }
378
379 pub fn builder() -> BipServerBuilder {
381 Self::bip_builder()
382 }
383}
384
385#[cfg(feature = "sc-tls")]
386impl BACnetServer<bacnet_transport::sc::ScTransport<bacnet_transport::sc_tls::TlsWebSocket>> {
387 pub fn sc_builder() -> ScServerBuilder {
389 ScServerBuilder {
390 config: ServerConfig::default(),
391 db: ObjectDatabase::new(),
392 hub_url: String::new(),
393 tls_config: None,
394 vmac: [0; 6],
395 heartbeat_interval_ms: 30_000,
396 heartbeat_timeout_ms: 60_000,
397 reconnect: None,
398 }
399 }
400}
401
402#[cfg(feature = "sc-tls")]
406pub struct ScServerBuilder {
407 config: ServerConfig,
408 db: ObjectDatabase,
409 hub_url: String,
410 tls_config: Option<std::sync::Arc<tokio_rustls::rustls::ClientConfig>>,
411 vmac: bacnet_transport::sc_frame::Vmac,
412 heartbeat_interval_ms: u64,
413 heartbeat_timeout_ms: u64,
414 reconnect: Option<bacnet_transport::sc::ScReconnectConfig>,
415}
416
417#[cfg(feature = "sc-tls")]
418impl ScServerBuilder {
419 pub fn hub_url(mut self, url: &str) -> Self {
421 self.hub_url = url.to_string();
422 self
423 }
424
425 pub fn tls_config(
427 mut self,
428 config: std::sync::Arc<tokio_rustls::rustls::ClientConfig>,
429 ) -> Self {
430 self.tls_config = Some(config);
431 self
432 }
433
434 pub fn vmac(mut self, vmac: [u8; 6]) -> Self {
436 self.vmac = vmac;
437 self
438 }
439
440 pub fn database(mut self, db: ObjectDatabase) -> Self {
442 self.db = db;
443 self
444 }
445
446 pub fn heartbeat_interval_ms(mut self, ms: u64) -> Self {
448 self.heartbeat_interval_ms = ms;
449 self
450 }
451
452 pub fn heartbeat_timeout_ms(mut self, ms: u64) -> Self {
454 self.heartbeat_timeout_ms = ms;
455 self
456 }
457
458 pub fn reconnect(mut self, config: bacnet_transport::sc::ScReconnectConfig) -> Self {
460 self.reconnect = Some(config);
461 self
462 }
463
464 pub fn dcc_password(mut self, password: impl Into<String>) -> Self {
466 self.config.dcc_password = Some(password.into());
467 self
468 }
469
470 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
472 self.config.reinit_password = Some(password.into());
473 self
474 }
475
476 pub fn enable_fault_detection(mut self, enabled: bool) -> Self {
478 self.config.enable_fault_detection = enabled;
479 self
480 }
481
482 pub async fn build(
484 self,
485 ) -> Result<
486 BACnetServer<bacnet_transport::sc::ScTransport<bacnet_transport::sc_tls::TlsWebSocket>>,
487 Error,
488 > {
489 let tls_config = self
490 .tls_config
491 .ok_or_else(|| Error::Encoding("SC server builder: tls_config is required".into()))?;
492
493 let ws = bacnet_transport::sc_tls::TlsWebSocket::connect(&self.hub_url, tls_config).await?;
494
495 let mut transport = bacnet_transport::sc::ScTransport::new(ws, self.vmac)
496 .with_heartbeat_interval_ms(self.heartbeat_interval_ms)
497 .with_heartbeat_timeout_ms(self.heartbeat_timeout_ms);
498 if let Some(rc) = self.reconnect {
499 #[allow(deprecated)]
500 {
501 transport = transport.with_reconnect(rc);
502 }
503 }
504
505 BACnetServer::start(self.config, self.db, transport).await
506 }
507}
508
509mod cov_notifications;
510mod dispatch;
511mod event_notifications;
512mod lifecycle;
513mod requests;
514mod segmentation;
515
516#[cfg(test)]
517mod tests;
518
519impl<T: TransportPort + 'static> BACnetServer<T> {
520 pub fn generic_builder() -> ServerBuilder<T> {
521 ServerBuilder {
522 config: ServerConfig::default(),
523 db: ObjectDatabase::new(),
524 transport: None,
525 }
526 }
527}