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    /// The transport the server's network layer runs on, for its
11    /// transport-specific state: a B/IPv6 server's `local_address()`, which
12    /// a peer turns into this server's VMAC with `resolve_virtual_address`,
13    /// or a transport's counters. `None` once the server no longer holds its
14    /// network layer.
15    pub fn transport(&self) -> Option<&T> {
16        self.network.as_deref().map(NetworkLayer::transport)
17    }
18
19    /// Get a reference to the shared object database.
20    ///
21    /// Objects read through this handle return raw standalone data. A built-in
22    /// Device follows its declared service profile, with absent or empty COV lists.
23    /// Served Device definitions and live lists belong to the executor: use
24    /// [`read_local`](Self::read_local) for that view. Normal Device mutation and
25    /// replacement cannot change the served execution profile.
26    /// Install Device objects before startup: changing their membership through
27    /// this handle does not rebind the discovery limiter's startup identity.
28    ///
29    /// A clone the application keeps can outlive the server's own handle, and
30    /// whichever goes last drops every object, waiting for the saves durable
31    /// objects have queued. In async code, let go of such a clone with
32    /// [`drop_database_off_runtime`], so that wait runs on Tokio's blocking
33    /// pool instead of a runtime worker (#1513).
34    pub fn database(&self) -> &Arc<RwLock<ObjectDatabase>> {
35        &self.db
36    }
37
38    /// Read one local property through the server's ReadProperty evaluator.
39    ///
40    /// Device wildcard reads target the configured identity; removal returns
41    /// UNKNOWN_OBJECT even if another Device is present. The database's lowest
42    /// Device instance still owns the live COV lists, independent of insertion
43    /// order; other Device objects receive empty lists. Every served Device uses
44    /// the executor's services, COV presence and property definitions, even after
45    /// public profile mutation, replacement or custom object installation.
46    ///
47    /// This is the live local read boundary: it applies the same Device
48    /// wildcard resolution, `UNKNOWN_OBJECT` and `PROPERTY_IS_NOT_AN_ARRAY`
49    /// checks, and value source as network ReadProperty. The selected Device's
50    /// `Active_COV_Subscriptions` (ordinary and single-property subscriptions)
51    /// and `Active_COV_Multiple_Subscriptions` (SubscribeCOVPropertyMultiple
52    /// contexts) are projected from the COV subscription table at one sampled
53    /// instant, returned as their encoded `BACnetLIST of BACnetCOVSubscription`
54    /// and `BACnetLIST of BACnetCOVMultipleSubscription`
55    /// (`PropertyValue::ApplicationData`). After [`stop`](Self::stop) the
56    /// server services no subscriptions, so both properties read as empty lists.
57    ///
58    /// The selected Device's `Device_Address_Binding` lists the server's device
59    /// bindings at the time of the read (#1369): each configured
60    /// [`DeviceBinding`] and each device whose I-Am was heard in the last ten
61    /// minutes, in Device instance order, as a `PropertyValue::List` whose
62    /// items are encoded BACnetAddressBindings
63    /// (`PropertyValue::ApplicationData`). A device on this network has
64    /// network number 0. The bindings outlast `stop`, so they still read then.
65    ///
66    /// A Group's `Present_Value` is rebuilt from its members, whose rows count
67    /// against the ReadPropertyMultiple work limit
68    /// ([`ReadPropertyMultipleBudget::max_result_elements`](crate::server::ReadPropertyMultipleBudget))
69    /// as for network ReadProperty; past it the read fails with
70    /// [`Error::abort`] carrying OUT_OF_RESOURCES.
71    pub async fn read_local(
72        &self,
73        oid: &ObjectIdentifier,
74        property: PropertyIdentifier,
75        array_index: Option<u32>,
76    ) -> Result<PropertyValue, Error> {
77        let db = self.db.read().await;
78        let view = crate::device_view::DeviceReadContext::new(
79            &db,
80            crate::device_view::DeviceExecution::FullServer,
81        )
82        .with_identity(Some(&self.config.device))
83        .with_work_limit(
84            self.config
85                .read_property_multiple_budget
86                .max_result_elements,
87        );
88        let lookup_oid = handlers::resolve_read_target(
89            &db,
90            oid,
91            self.config.registered_network_port,
92            view.device_oid(),
93        );
94        let plan =
95            handlers::plan_read_property(&db, Some(&view), lookup_oid, property, array_index)
96                .map_err(|failure| match failure {
97                    handlers::ReadFailure::Service(error) => error,
98                    handlers::ReadFailure::Work | handlers::ReadFailure::Bytes => {
99                        Error::abort(AbortReason::OUT_OF_RESOURCES)
100                    }
101                })?;
102        use super::requests::confirmed_response::{active_cov_snapshot, address_bindings};
103        let live = match plan.live_cov(&db) {
104            Some(selection) if self.dispatch_task.is_none() => {
105                let bindings = address_bindings(&self.device_bindings, selection).await;
106                Some(
107                    crate::cov::active::LiveDeviceCov::stopped(selection)
108                        .with_address_bindings(selection, bindings),
109                )
110            }
111            Some(selection) => {
112                let tables = super::requests::confirmed_response::LiveTables {
113                    cov: &self.cov_table,
114                    bindings: &self.device_bindings,
115                };
116                Some(active_cov_snapshot(&db, tables, selection).await)
117            }
118            None => None,
119        };
120        let view = view.with_live(live.as_ref());
121        handlers::read_property_value(&db, Some(&view), plan)
122    }
123
124    /// Write one local property from its encoded value: the octets a network
125    /// WriteProperty would carry for it.
126    ///
127    /// The octets take the steps the WriteProperty handler takes before an
128    /// object sees a value. An array index is refused as it is over the
129    /// network (UNKNOWN_PROPERTY for a property the object doesn't hold,
130    /// PROPERTY_IS_NOT_AN_ARRAY for one that isn't an array), and the octets
131    /// are decoded with that handler's per-property framing, which hands a
132    /// Schedule's Effective_Period or a Staging's Stages to the object in the
133    /// form it decodes. The value then takes the [`write_local`](Self::write_local)
134    /// path, with its COV, event and audit work. A value read with
135    /// [`read_local`](Self::read_local) and encoded therefore writes back, and
136    /// any value a network client could write is accepted here.
137    ///
138    /// Like `write_local`, it must be awaited inside a Tokio runtime, failing
139    /// before anything is written outside one, and a caller dropped once the
140    /// write has committed skips none of the work the write owes (#1367).
141    pub async fn write_local_encoded(
142        &self,
143        oid: &ObjectIdentifier,
144        property: PropertyIdentifier,
145        array_index: Option<u32>,
146        value: &[u8],
147        priority: Option<u8>,
148        source: crate::LocalCommandSource,
149    ) -> Result<(), Error> {
150        self.active_network()?;
151        let value = {
152            let db = self.db.read().await;
153            let object = db.get(oid).ok_or(Error::protocol(
154                ErrorClass::OBJECT,
155                ErrorCode::UNKNOWN_OBJECT,
156            ))?;
157            handlers::gate_and_decode_write(object, property, array_index, value)?
158        };
159        self.write_local(oid, property, array_index, value, priority, source)
160            .await
161    }
162
163    /// Create a cloneable handle for unsolicited I-Am announcements.
164    pub fn i_am_broadcaster(&self) -> IAmBroadcaster<T> {
165        IAmBroadcaster {
166            state: Arc::downgrade(&self.broadcaster),
167        }
168    }
169
170    /// Get the communication state per DeviceCommunicationControl.
171    ///
172    /// The server refuses the deprecated DISABLE, so this is
173    /// [`DccState::Enable`] or [`DccState::DisableInitiation`].
174    pub fn comm_state(&self) -> DccState {
175        self.comm_state.get()
176    }
177
178    /// Generate PICS from the database and the server's effective Device execution view.
179    /// Standalone [`PicsGenerator`](crate::pics::PicsGenerator) keeps raw-object semantics.
180    ///
181    /// The caller must supply a [`PicsConfig`](crate::pics::PicsConfig) for fields not available from the server
182    /// (device profile, data links, network roles, etc.). The vendor and model names, the firmware and
183    /// application versions, and the protocol version and revision come from the served Device object,
184    /// so the PICS can't contradict what the device reports.
185    pub async fn generate_pics(&self, pics_config: &crate::pics::PicsConfig) -> crate::pics::Pics {
186        let db = self.db.read().await;
187        crate::pics::PicsGenerator::new(&db, &self.config, pics_config)
188            .for_server()
189            .generate()
190    }
191}
192
193impl<T: TransportPort + bacnet_transport::bip::AsBip + 'static> BACnetServer<T> {
194    /// How this server's foreign-device registration is going (#938):
195    /// whether it holds a live lease, the time left on it, and the last
196    /// attempt's result and outcome, a BBMD's refusal included. It reads
197    /// [`BipTransport::bvlc_client_snapshot`] and sends nothing. Fails on a
198    /// transport that isn't B/IP, and once the server has stopped.
199    pub fn foreign_registration(
200        &self,
201    ) -> Result<bacnet_transport::bip::ForeignRegistrationSnapshot, Error> {
202        let transport = self.transport().ok_or_else(broadcaster::stopped)?;
203        Ok(transport
204            .as_bip()?
205            .bvlc_client_snapshot()
206            .foreign_registration)
207    }
208}
209
210#[cfg(test)]
211#[path = "local_encoded_writes_tests.rs"]
212mod local_encoded_writes_tests;