Skip to main content

bacnet_objects/device/
audit_recipient.rs

1//! Device-owned recipient state and the synchronous runtime mutation capability.
2use super::*;
3use bacnet_types::constructed::{BACnetAddress, BACnetRecipient};
4use std::sync::Weak;
5
6/// Actual remote requester context, supplied only after service authorization.
7#[doc(hidden)]
8#[derive(Clone)]
9pub struct AuditWriteSource {
10    pub device: BACnetRecipient,
11    pub invoke_id: u8,
12}
13
14/// Installed target runtime. Implementations must prepare both deliveries before
15/// changing `current`, then commit and register their owned worker atomically.
16#[doc(hidden)]
17pub trait AuditRecipientChangeSink: Send + Sync {
18    fn change(
19        &self,
20        current: &mut BACnetRecipient,
21        new: BACnetRecipient,
22        source: Option<&AuditWriteSource>,
23        clock: Option<ClockFrame>,
24    ) -> Result<(), Error>;
25    fn is_active(&self) -> bool;
26}
27
28#[derive(Default)]
29pub(super) struct RecipientState {
30    value: Option<BACnetRecipient>,
31    sink: Option<Weak<dyn AuditRecipientChangeSink>>,
32}
33
34fn property_error(code: ErrorCode) -> Error {
35    Error::Protocol {
36        class: ErrorClass::PROPERTY.to_raw() as u32,
37        code: code.to_raw() as u32,
38    }
39}
40
41/// A scoped operation capability for the built-in Device. It deliberately does
42/// not expose a mutable Device reference or permit replacing its installed state.
43#[doc(hidden)]
44pub struct DeviceAuthority<'a>(pub(super) &'a mut DeviceObject);
45
46impl DeviceAuthority<'_> {
47    pub fn object_identifier(&self) -> ObjectIdentifier {
48        self.0.oid
49    }
50    pub fn set_services_supported(&mut self, services: &[ServiceSupported]) {
51        self.0.set_services_supported(services);
52    }
53    pub fn write_property(
54        &mut self,
55        property: PropertyIdentifier,
56        index: Option<u32>,
57        value: PropertyValue,
58        priority: Option<u8>,
59    ) -> Result<(), Error> {
60        self.0.write_property(property, index, value, priority)
61    }
62    pub fn provision_audit_recipient(&mut self, recipient: BACnetRecipient) -> Result<(), Error> {
63        self.0.provision_audit_recipient(recipient)
64    }
65    pub fn provisioned_audit_recipient(&self) -> Option<&BACnetRecipient> {
66        self.0.recipient.value.as_ref()
67    }
68    pub fn validate_audit_recipient_installation(&self) -> Result<(), Error> {
69        if self.0.recipient.value.is_none()
70            || self
71                .0
72                .recipient
73                .sink
74                .as_ref()
75                .and_then(Weak::upgrade)
76                .is_some()
77        {
78            return Err(property_error(ErrorCode::WRITE_ACCESS_DENIED));
79        }
80        Ok(())
81    }
82    pub fn install_audit_recipient(
83        &mut self,
84        sink: &Arc<dyn AuditRecipientChangeSink>,
85    ) -> Result<(), Error> {
86        self.validate_audit_recipient_installation()?;
87        self.0.recipient.sink = Some(Arc::downgrade(sink));
88        Ok(())
89    }
90    pub fn uninstall_audit_recipient(&mut self, sink: &Arc<dyn AuditRecipientChangeSink>) {
91        if !sink.is_active()
92            && self
93                .0
94                .recipient
95                .sink
96                .as_ref()
97                .and_then(Weak::upgrade)
98                .is_some_and(|current| Arc::ptr_eq(&current, sink))
99        {
100            self.0.recipient.sink = None;
101        }
102    }
103    pub fn write_audit_recipient(
104        &mut self,
105        index: Option<u32>,
106        value: PropertyValue,
107        priority: Option<u8>,
108        source: Option<&AuditWriteSource>,
109    ) -> Result<(), Error> {
110        self.0.write_audit_recipient(index, value, priority, source)
111    }
112}
113
114impl DeviceObject {
115    /// Provision the initial target Audit recipient before installing a server
116    /// runtime. This alone does not expose the network property. Active changes
117    /// must use WriteProperty so both old and new recipients are notified.
118    pub fn provision_audit_recipient(&mut self, recipient: BACnetRecipient) -> Result<(), Error> {
119        validate_recipient(&recipient)?;
120        if self
121            .recipient
122            .sink
123            .as_ref()
124            .and_then(Weak::upgrade)
125            .is_some()
126        {
127            return Err(property_error(ErrorCode::WRITE_ACCESS_DENIED));
128        }
129        self.recipient.value = Some(recipient);
130        Ok(())
131    }
132
133    pub(super) fn audit_recipient_present(&self) -> bool {
134        self.recipient.value.is_some()
135            && self
136                .recipient
137                .sink
138                .as_ref()
139                .and_then(Weak::upgrade)
140                .is_some()
141    }
142
143    pub(super) fn read_audit_recipient(&self, index: Option<u32>) -> Result<PropertyValue, Error> {
144        if !self.audit_recipient_present() {
145            return Err(property_error(ErrorCode::UNKNOWN_PROPERTY));
146        }
147        if index.is_some() {
148            return Err(property_error(ErrorCode::PROPERTY_IS_NOT_AN_ARRAY));
149        }
150        let mut bytes = bytes::BytesMut::new();
151        bacnet_encoding::constructed::encode_recipient(
152            &mut bytes,
153            self.recipient.value.as_ref().expect("present"),
154        )?;
155        Ok(PropertyValue::ApplicationData(bytes.to_vec()))
156    }
157
158    pub(super) fn write_audit_recipient(
159        &mut self,
160        index: Option<u32>,
161        value: PropertyValue,
162        priority: Option<u8>,
163        source: Option<&AuditWriteSource>,
164    ) -> Result<(), Error> {
165        let sink = self
166            .recipient
167            .sink
168            .as_ref()
169            .and_then(Weak::upgrade)
170            .ok_or_else(|| property_error(ErrorCode::UNKNOWN_PROPERTY))?;
171        if !sink.is_active() {
172            return Err(property_error(ErrorCode::WRITE_ACCESS_DENIED));
173        }
174        if index.is_some() {
175            return Err(property_error(ErrorCode::PROPERTY_IS_NOT_AN_ARRAY));
176        }
177        if priority.is_some_and(|p| !(1..=16).contains(&p)) {
178            return Err(Error::Protocol {
179                class: ErrorClass::SERVICES.to_raw() as u32,
180                code: ErrorCode::PARAMETER_OUT_OF_RANGE.to_raw() as u32,
181            });
182        }
183        if value == PropertyValue::Null {
184            return Ok(());
185        }
186        let PropertyValue::ApplicationData(bytes) = value else {
187            return Err(property_error(ErrorCode::INVALID_DATA_TYPE));
188        };
189        let (new, end) = bacnet_encoding::constructed::decode_recipient(&bytes, 0)
190            .map_err(|_| property_error(ErrorCode::INVALID_DATA_ENCODING))?;
191        if end != bytes.len() {
192            return Err(property_error(ErrorCode::INVALID_DATA_ENCODING));
193        }
194        validate_recipient(&new)?;
195        let clock = self.clock_frame();
196        let current = self.recipient.value.as_mut().expect("installed recipient");
197        if *current == new {
198            return Ok(());
199        }
200        sink.change(current, new, source, clock)
201    }
202}
203
204/// A recipient the property can hold: a concrete Device, or an address whose
205/// MAC is within the configured-recipient bound. Provisioning checks the MAC
206/// here because nothing decodes it on the way in (#1124); a write already has.
207fn validate_recipient(recipient: &BACnetRecipient) -> Result<(), Error> {
208    match recipient {
209        BACnetRecipient::Device(oid)
210            if oid.object_type() != ObjectType::DEVICE
211                || oid.instance_number() == ObjectIdentifier::MAX_INSTANCE =>
212        {
213            Err(property_error(ErrorCode::INVALID_DATA_ENCODING))
214        }
215        BACnetRecipient::Address(address)
216            if address.mac_address.len() > BACnetAddress::MAX_MAC_LEN =>
217        {
218            Err(property_error(ErrorCode::INVALID_DATA_ENCODING))
219        }
220        _ => Ok(()),
221    }
222}