Skip to main content

bacnet_server/server/
comm_state.rs

1//! The server's DeviceCommunicationControl state (Clause 16.1).
2//!
3//! This module sits under `dcc_timer`, which owns changes from accepted DCC
4//! requests, timer expiry and accepted WARMSTART/COLDSTART. Everything else
5//! holding a [`CommState`] reads it.
6
7use std::sync::atomic::{AtomicBool, Ordering};
8
9use bacnet_types::enums::EnableDisable;
10
11#[cfg(test)]
12#[path = "comm_state_tests.rs"]
13mod tests;
14
15/// The communication state DeviceCommunicationControl has put the server in
16/// (Clause 16.1).
17///
18/// A request for the deprecated DISABLE value is always refused with
19/// `SERVICES` / `SERVICE_REQUEST_DENIED` (Clause 16.1.2), whatever the
20/// [`DccPolicy`](crate::server::DccPolicy), so the server is only ever in one of
21/// these two states.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23pub enum DccState {
24    /// Communication is enabled: ENABLE. The state at every start.
25    #[default]
26    Enable,
27    /// DISABLE_INITIATION restricts independent message initiation. The server
28    /// continues executing requests; I-Am replies to Who-Is, matching I-Have
29    /// replies to Who-Has, and audit notifications remain permitted.
30    DisableInitiation,
31}
32
33impl DccState {
34    /// Whether independent initiation is restricted, including COV and event
35    /// notifications, unsolicited I-Am, remote writes and their Who-Is.
36    /// Solicited discovery replies and audit notifications remain eligible.
37    pub const fn initiation_restricted(self) -> bool {
38        matches!(self, Self::DisableInitiation)
39    }
40}
41
42impl From<DccState> for EnableDisable {
43    fn from(state: DccState) -> Self {
44        match state {
45            DccState::Enable => Self::ENABLE,
46            DccState::DisableInitiation => Self::DISABLE_INITIATION,
47        }
48    }
49}
50
51/// The server's live [`DccState`], read lock-free by every path that starts
52/// a message.
53///
54/// The setter is private to `dcc_timer`; tests reach it through
55/// `set_for_test`.
56#[derive(Debug, Default)]
57pub(crate) struct CommState {
58    initiation_restricted: AtomicBool,
59}
60
61impl CommState {
62    /// The current state.
63    pub(crate) fn get(&self) -> DccState {
64        if self.initiation_restricted() {
65            DccState::DisableInitiation
66        } else {
67            DccState::Enable
68        }
69    }
70
71    /// Whether DCC restricts initiation now; see
72    /// [`DccState::initiation_restricted`].
73    pub(crate) fn initiation_restricted(&self) -> bool {
74        self.initiation_restricted.load(Ordering::Acquire)
75    }
76
77    /// Commit a new state. Only `dcc_timer` calls this: when a DCC request is
78    /// accepted, when its timer runs out, or when a restart is accepted.
79    pub(super) fn set(&self, state: DccState) {
80        self.initiation_restricted
81            .store(state.initiation_restricted(), Ordering::Release);
82    }
83
84    /// Put the state in place without a DCC request, for tests that need DCC
85    /// in force at a given step.
86    #[cfg(test)]
87    pub(crate) fn set_for_test(&self, state: DccState) {
88        self.set(state);
89    }
90}