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;