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.change_sink.as_ref().and_then(std::sync::Weak::upgrade) {
46            if !sink.is_active() {
47                return Err(crate::common::write_access_denied_error());
48            }
49            return sink.send_now(
50                self.oid,
51                &self.status,
52                value,
53                source,
54                self.clock.as_ref().and_then(|clock| clock.read_clock()),
55            );
56        }
57        if self.change_owner.upgrade().is_some() {
58            return Err(crate::common::write_access_denied_error());
59        }
60        // An unowned object has no delayed notifications.
61        self.status.commit_send_now(value, 0, false)?;
62        Ok(())
63    }
64}
65
66impl AuditReporterAuthority<'_> {
67    /// Write one of the concrete writable Reporter fields with original provenance.
68    pub fn write_property(
69        &mut self,
70        property: PropertyIdentifier,
71        value: PropertyValue,
72        index: Option<u32>,
73        source: Option<&AuditWriteSource>,
74    ) -> Result<(), Error> {
75        if property == PropertyIdentifier::DESCRIPTION {
76            return self.write_description(value, index, source);
77        }
78        if !matches!(
79            property,
80            PropertyIdentifier::MAXIMUM_SEND_DELAY | PropertyIdentifier::SEND_NOW
81        ) {
82            return Err(crate::common::write_access_denied_error());
83        }
84        let mut next = self.0.configuration_internal();
85        if next.maximum_send_delay.is_none() {
86            return Err(crate::common::unknown_property_error());
87        }
88        if index.is_some() {
89            return Err(crate::common::property_is_not_an_array_error());
90        }
91        if matches!(value, PropertyValue::Null) {
92            // Equal configuration still checks sealed ownership before no-op success.
93            return self.0.change_configuration(next, source);
94        }
95        match (property, value) {
96            (PropertyIdentifier::MAXIMUM_SEND_DELAY, PropertyValue::Unsigned(seconds)) => {
97                let seconds = u32::try_from(seconds)
98                    .map_err(|_| crate::common::value_out_of_range_error())?;
99                next.maximum_send_delay = Some(AuditSendDelay::new(seconds)?);
100                self.0.change_configuration(next, source)
101            }
102            (PropertyIdentifier::SEND_NOW, PropertyValue::Boolean(value)) => {
103                self.0.command_send_now(value, source)
104            }
105            _ => Err(crate::common::invalid_data_type_error()),
106        }
107    }
108}
109
110impl AuditReporterStatus {
111    /// Current external capture evidence; internal command reset never changes it.
112    #[doc(hidden)]
113    pub fn capture_revision(&self) -> u64 {
114        self.0.lock().unwrap().capture_revision
115    }
116    /// Monotonic configuration identity, independently of summary filtering.
117    #[doc(hidden)]
118    pub fn configuration_epoch(&self) -> u64 {
119        self.0.lock().unwrap().configuration_epoch
120    }
121    /// Object-owned command readback; FALSE also represents terminal failure quiescence.
122    #[doc(hidden)]
123    pub fn send_now(&self) -> bool {
124        self.0.lock().unwrap().send_now
125    }
126    /// Check command identity before preparing any external consequence.
127    #[doc(hidden)]
128    pub fn validate_send_now(&self, captured: bool) -> Result<(), Error> {
129        let state = self.0.lock().unwrap();
130        if state.command_revision == u64::MAX || (captured && state.capture_revision == u64::MAX) {
131            return Err(Error::Encoding("audit command revision exhausted".into()));
132        }
133        Ok(())
134    }
135    /// Commit one validated command after resource preparation, before queue publication.
136    #[doc(hidden)]
137    pub fn commit_send_now(&self, value: bool, fence: u64, captured: bool) -> Result<(), Error> {
138        let mut state = self.0.lock().unwrap();
139        let revision = state
140            .command_revision
141            .checked_add(1)
142            .ok_or_else(|| Error::Encoding("audit command revision exhausted".into()))?;
143        let capture = if captured {
144            state
145                .capture_revision
146                .checked_add(1)
147                .ok_or_else(|| Error::Encoding("audit capture revision exhausted".into()))?
148        } else {
149            state.capture_revision
150        };
151        state.command_revision = revision;
152        state.capture_revision = capture;
153        state.send_now = value && fence != 0;
154        if value {
155            state.command_fence = fence;
156        }
157        Ok(())
158    }
159    /// Queue owns ordering: a later TRUE fence cannot be cleared by older work.
160    #[doc(hidden)]
161    pub fn settle_send_now(&self, first_unsent: Option<u64>) {
162        let mut state = self.0.lock().unwrap();
163        if first_unsent.is_none_or(|first| first > state.command_fence) {
164            state.send_now = false;
165        }
166    }
167}