Skip to main content

bacnet_server/server/
handles.rs

1use super::*;
2
3impl<T: TransportPort + 'static> BACnetServer<T> {
4    /// Return the last bound local MAC snapshot, including after stop.
5    /// This retained address does not assert that the transport is still active.
6    pub fn local_mac(&self) -> &[u8] {
7        &self.local_mac
8    }
9
10    /// Get a reference to the shared object database.
11    ///
12    /// Objects read through this handle return raw standalone data. A built-in
13    /// Device follows its declared service profile, with absent or empty COV lists.
14    /// Served Device definitions and live lists belong to the executor: use
15    /// [`read_local`](Self::read_local) for that view. Normal Device mutation and
16    /// replacement cannot change the served execution profile.
17    /// Install Device objects before startup: changing their membership through
18    /// this handle does not rebind the discovery limiter's startup identity.
19    pub fn database(&self) -> &Arc<RwLock<ObjectDatabase>> {
20        &self.db
21    }
22
23    /// Read one local property through the server's ReadProperty evaluator.
24    ///
25    /// With multiple Device objects, the lowest instance is selected for Device
26    /// wildcard reads and the live COV lists, independent of insertion order.
27    /// Other Device objects receive empty COV lists. Every served Device uses
28    /// the executor's services, COV presence and property definitions, even after
29    /// public profile mutation, replacement or custom object installation.
30    ///
31    /// This is the live local read boundary: it applies the same Device
32    /// wildcard resolution, `UNKNOWN_OBJECT` and `PROPERTY_IS_NOT_AN_ARRAY`
33    /// checks, and value source as network ReadProperty. The selected Device's
34    /// `Active_COV_Subscriptions` (ordinary and single-property subscriptions)
35    /// and `Active_COV_Multiple_Subscriptions` (SubscribeCOVPropertyMultiple
36    /// contexts) are projected from the COV subscription table at one sampled
37    /// instant, returned as their encoded `BACnetLIST of BACnetCOVSubscription`
38    /// and `BACnetLIST of BACnetCOVMultipleSubscription`
39    /// (`PropertyValue::ApplicationData`). After [`stop`](Self::stop) the
40    /// server services no subscriptions, so both properties read as empty lists.
41    ///
42    /// The selected Device's `Device_Address_Binding` lists the server's device
43    /// bindings at the time of the read (#1369): each configured
44    /// [`DeviceBinding`] and each device whose I-Am was heard in the last ten
45    /// minutes, in Device instance order, as a `PropertyValue::List` whose
46    /// items are encoded BACnetAddressBindings
47    /// (`PropertyValue::ApplicationData`). A device on this network has
48    /// network number 0. The bindings outlast `stop`, so they still read then.
49    ///
50    /// A Group's `Present_Value` is rebuilt from its members, whose rows count
51    /// against the ReadPropertyMultiple work limit
52    /// ([`ReadPropertyMultipleBudget::max_result_elements`](crate::server::ReadPropertyMultipleBudget))
53    /// as for network ReadProperty; past it the read fails with
54    /// [`Error::Abort`] carrying OUT_OF_RESOURCES.
55    pub async fn read_local(
56        &self,
57        oid: &ObjectIdentifier,
58        property: PropertyIdentifier,
59        array_index: Option<u32>,
60    ) -> Result<PropertyValue, Error> {
61        let db = self.db.read().await;
62        let lookup_oid =
63            handlers::resolve_read_target(&db, oid, self.config.registered_network_port);
64        let view = crate::device_view::DeviceReadContext::new(
65            &db,
66            crate::device_view::DeviceExecution::FullServer,
67        )
68        .with_work_limit(
69            self.config
70                .read_property_multiple_budget
71                .max_result_elements,
72        );
73        let plan =
74            handlers::plan_read_property(&db, Some(&view), lookup_oid, property, array_index)
75                .map_err(|failure| match failure {
76                    handlers::ReadFailure::Service(error) => error,
77                    handlers::ReadFailure::Work | handlers::ReadFailure::Bytes => Error::Abort {
78                        reason: AbortReason::OUT_OF_RESOURCES.to_raw(),
79                    },
80                })?;
81        use super::requests::confirmed_response::{active_cov_snapshot, address_bindings};
82        let live = match plan.live_cov(&db) {
83            Some(selection) if self.dispatch_task.is_none() => {
84                let bindings = address_bindings(&self.device_bindings, selection).await;
85                Some(
86                    crate::cov::active::LiveDeviceCov::stopped(selection)
87                        .with_address_bindings(selection, bindings),
88                )
89            }
90            Some(selection) => {
91                let tables = super::requests::confirmed_response::LiveTables {
92                    cov: &self.cov_table,
93                    bindings: &self.device_bindings,
94                };
95                Some(active_cov_snapshot(&db, tables, selection).await)
96            }
97            None => None,
98        };
99        let view = view.with_live(live.as_ref());
100        handlers::read_property_value(&db, Some(&view), plan)
101    }
102
103    /// Write one local property from its encoded value: the octets a network
104    /// WriteProperty would carry for it.
105    ///
106    /// The octets take the steps the WriteProperty handler takes before an
107    /// object sees a value. An array index is refused as it is over the
108    /// network (UNKNOWN_PROPERTY for a property the object doesn't hold,
109    /// PROPERTY_IS_NOT_AN_ARRAY for one that isn't an array), and the octets
110    /// are decoded with that handler's per-property framing, which hands a
111    /// Schedule's Effective_Period or a Staging's Stages to the object in the
112    /// form it decodes. The value then takes the [`write_local`](Self::write_local)
113    /// path, with its COV, event and audit work. A value read with
114    /// [`read_local`](Self::read_local) and encoded therefore writes back, and
115    /// any value a network client could write is accepted here.
116    ///
117    /// Like `write_local`, it must be awaited inside a Tokio runtime, failing
118    /// before anything is written outside one, and a caller dropped once the
119    /// write has committed skips none of the work the write owes (#1367).
120    pub async fn write_local_encoded(
121        &self,
122        oid: &ObjectIdentifier,
123        property: PropertyIdentifier,
124        array_index: Option<u32>,
125        value: &[u8],
126        priority: Option<u8>,
127        source: crate::LocalCommandSource,
128    ) -> Result<(), Error> {
129        self.active_network()?;
130        let value = {
131            let db = self.db.read().await;
132            let object = db.get(oid).ok_or_else(|| Error::Protocol {
133                class: ErrorClass::OBJECT.to_raw() as u32,
134                code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
135            })?;
136            handlers::gate_and_decode_write(object, property, array_index, value)?
137        };
138        self.write_local(oid, property, array_index, value, priority, source)
139            .await
140    }
141
142    /// Create a cloneable handle for unsolicited I-Am announcements.
143    pub fn i_am_broadcaster(&self) -> IAmBroadcaster<T> {
144        IAmBroadcaster {
145            state: Arc::downgrade(&self.broadcaster),
146        }
147    }
148
149    /// Get the communication state per DeviceCommunicationControl.
150    ///
151    /// The server refuses the deprecated DISABLE, so this is
152    /// [`DccState::Enable`] or [`DccState::DisableInitiation`].
153    pub fn comm_state(&self) -> DccState {
154        self.comm_state.get()
155    }
156
157    /// Generate PICS from the database and the server's effective Device execution view.
158    /// Standalone [`PicsGenerator`](crate::pics::PicsGenerator) keeps raw-object semantics.
159    ///
160    /// The caller must supply a [`PicsConfig`](crate::pics::PicsConfig) for fields not available from the server
161    /// (vendor name, model, firmware revision, etc.).
162    pub async fn generate_pics(&self, pics_config: &crate::pics::PicsConfig) -> crate::pics::Pics {
163        let db = self.db.read().await;
164        crate::pics::PicsGenerator::new(&db, &self.config, pics_config)
165            .for_server()
166            .generate()
167    }
168}
169
170#[cfg(test)]
171#[path = "local_encoded_writes_tests.rs"]
172mod local_encoded_writes_tests;