Skip to main content

bacnet_objects/audit/
reporter_delay.rs

1//! Paired optional delay capability and object-owned flush command state.
2use super::*;
3use crate::device::AuditWriteSource;
4
5/// Audit Reporter Maximum_Send_Delay, in whole seconds (0 through 3600).
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct AuditSendDelay(u16);
8impl AuditSendDelay {
9    /// Validate the Standard's inclusive range; zero keeps the pair present.
10    pub fn new(seconds: u32) -> Result<Self, Error> {
11        if seconds > 3600 {
12            return Err(crate::common::value_out_of_range_error());
13        }
14        Ok(Self(seconds as u16))
15    }
16    /// Configured whole seconds, with zero meaning no additional delay.
17    pub fn seconds(self) -> u16 {
18        self.0
19    }
20}
21
22impl AuditReporterObject {
23    /// Author the optional delay/Send_Now pair, or change a live delay value.
24    /// Presence is fixed throughout an active target ownership lifetime.
25    pub fn set_maximum_send_delay(&mut self, delay: Option<AuditSendDelay>) -> Result<(), Error> {
26        let mut next = self.configuration_internal();
27        next.maximum_send_delay = delay;
28        self.change_configuration(next, None)
29    }
30
31    /// Flush the currently delayed prefix. FALSE clears readback and continues
32    /// already-owned work; a new TRUE command establishes a new completion fence.
33    pub fn set_send_now(&mut self, value: bool) -> Result<(), Error> {
34        self.command_send_now(value, None)
35    }
36
37    fn command_send_now(
38        &mut self,
39        value: bool,
40        source: Option<&AuditWriteSource>,
41    ) -> Result<(), Error> {
42        if self.configuration_internal().maximum_send_delay.is_none() {
43            return Err(crate::common::unknown_property_error());
44        }
45        if let Some(sink) = self
46            .change_sink
47            .as_ref()
48            .and_then(alloc::sync::Weak::upgrade)
49        {
50            if !sink.is_active() {
51                return Err(crate::common::write_access_denied_error());
52            }
53            return sink.send_now(
54                self.oid,
55                &self.status,
56                value,
57                source,
58                self.clock.as_ref().and_then(|clock| clock.read_clock()),
59            );
60        }
61        if self.change_owner.upgrade().is_some() {
62            return Err(crate::common::write_access_denied_error());
63        }
64        // An unowned object has no delayed notifications.
65        self.status.commit_send_now(value, 0, false)?;
66        Ok(())
67    }
68}
69
70impl AuditReporterAuthority<'_> {
71    /// Write one of the concrete writable Reporter fields with original provenance.
72    pub fn write_property(
73        &mut self,
74        property: PropertyIdentifier,
75        value: PropertyValue,
76        index: Option<u32>,
77        source: Option<&AuditWriteSource>,
78    ) -> Result<(), Error> {
79        if property == PropertyIdentifier::DESCRIPTION {
80            return self.write_description(value, index, source);
81        }
82        if !matches!(
83            property,
84            PropertyIdentifier::MAXIMUM_SEND_DELAY | PropertyIdentifier::SEND_NOW
85        ) {
86            return Err(crate::common::write_access_denied_error());
87        }
88        let mut next = self.0.configuration_internal();
89        if next.maximum_send_delay.is_none() {
90            return Err(crate::common::unknown_property_error());
91        }
92        if index.is_some() {
93            return Err(crate::common::property_is_not_an_array_error());
94        }
95        if matches!(value, PropertyValue::Null) {
96            // Equal configuration still checks sealed ownership before no-op success.
97            return self.0.change_configuration(next, source);
98        }
99        match (property, value) {
100            (PropertyIdentifier::MAXIMUM_SEND_DELAY, PropertyValue::Unsigned(seconds)) => {
101                let seconds = u32::try_from(seconds)
102                    .map_err(|_| crate::common::value_out_of_range_error())?;
103                next.maximum_send_delay = Some(AuditSendDelay::new(seconds)?);
104                self.0.change_configuration(next, source)
105            }
106            (PropertyIdentifier::SEND_NOW, PropertyValue::Boolean(value)) => {
107                self.0.command_send_now(value, source)
108            }
109            _ => Err(crate::common::invalid_data_type_error()),
110        }
111    }
112}
113
114impl AuditReporterStatus {
115    /// Current external capture evidence; internal command reset never changes it.
116    #[doc(hidden)]
117    pub fn capture_revision(&self) -> u64 {
118        self.0.lock().capture_revision
119    }
120    /// Monotonic configuration identity, independently of summary filtering.
121    #[doc(hidden)]
122    pub fn configuration_epoch(&self) -> u64 {
123        self.0.lock().configuration_epoch
124    }
125    /// Object-owned command readback; FALSE also represents terminal failure quiescence.
126    #[doc(hidden)]
127    pub fn send_now(&self) -> bool {
128        self.0.lock().send_now
129    }
130    /// Check command identity before preparing any external consequence.
131    #[doc(hidden)]
132    pub fn validate_send_now(&self, captured: bool) -> Result<(), Error> {
133        let state = self.0.lock();
134        if state.command_revision == u64::MAX || (captured && state.capture_revision == u64::MAX) {
135            return Err(Error::Encoding("audit command revision exhausted".into()));
136        }
137        Ok(())
138    }
139    /// Commit one validated command after resource preparation, before queue publication.
140    #[doc(hidden)]
141    pub fn commit_send_now(&self, value: bool, fence: u64, captured: bool) -> Result<(), Error> {
142        let mut state = self.0.lock();
143        let revision = state
144            .command_revision
145            .checked_add(1)
146            .ok_or_else(|| Error::Encoding("audit command revision exhausted".into()))?;
147        let capture = if captured {
148            state
149                .capture_revision
150                .checked_add(1)
151                .ok_or_else(|| Error::Encoding("audit capture revision exhausted".into()))?
152        } else {
153            state.capture_revision
154        };
155        state.command_revision = revision;
156        state.capture_revision = capture;
157        state.send_now = value && fence != 0;
158        if value {
159            state.command_fence = fence;
160        }
161        Ok(())
162    }
163    /// Queue owns ordering: a later TRUE fence cannot be cleared by older work.
164    #[doc(hidden)]
165    pub fn settle_send_now(&self, first_unsent: Option<u64>) {
166        let mut state = self.0.lock();
167        if first_unsent.is_none_or(|first| first > state.command_fence) {
168            state.send_now = false;
169        }
170    }
171}