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