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;