bacnet_server/server/reinitialize.rs
1//! ReinitializeDevice (Clause 16.4): the application handler that carries a
2//! request out, the requester context it receives, the builder setters for
3//! the handler and the password, and the guarded reply the full server and
4//! endpoint sessions share.
5
6use std::panic::{catch_unwind, AssertUnwindSafe};
7
8use bacnet_transport::port::{DirectScIdentity, TransportProvenance};
9use bacnet_types::enums::ReinitializedState;
10
11use super::requests::confirmed_response::error_apdu_from_error;
12use super::*;
13
14/// Carries out a ReinitializeDevice request (Clause 16.4) on a full server or
15/// an endpoint session.
16///
17/// It is called only for a request that decoded, passed its password check
18/// and names a state the clause defines. `Ok(())` sends the SimpleACK; an
19/// `Err` is sent in its place. The rules:
20///
21/// - **Reply first, restart later.** The SimpleACK goes out only after the
22/// handler returns, so it must not restart the device inline. Accept or
23/// refuse, prepare, and schedule the restart (or the backup or restore
24/// step) to run after the reply: Clause 16.4.2 has the device acknowledge
25/// the request before it shuts down.
26/// - **No reply signal yet.** Nothing tells the application when the
27/// SimpleACK has left (#1565), so any delay before restarting is best
28/// effort, and on MS/TP it can need longer: a postponed reply waits for
29/// the token. [`BACnetServer::stop`] seals responses and aborts request
30/// tasks before joining them, so a restart path that stops the server
31/// before the reply has left drops the SimpleACK. On a full server, accepted
32/// WARMSTART/COLDSTART ends DISABLE_INITIATION immediately (Clause 16.1.2),
33/// cancels its timer and resumes held COV, even if the reply later fails.
34/// - **Be quick.** It runs synchronously on a runtime worker with the object
35/// database write-locked, so every other request waits for it. Hand slow
36/// work, such as writing backup files, to a task. On an endpoint session a
37/// blocking handler stalls the whole session.
38/// - **Database edits are raw.** On a full server, changes made through the
39/// `&mut ObjectDatabase` skip what [`BACnetServer::write_local`] adds
40/// around a write: the Object_Name uniqueness check, the COV fanout, the
41/// event pass, Schedule and Command follow-ups, and the Audit record. A
42/// change that needs them, as ACTIVATE_CHANGES work on Network Port or
43/// Device properties may, belongs in `write_local`, called after the
44/// reply from a task the handler schedules (for example by sending the
45/// change to the code that holds the server).
46/// - **Gate the requester.** Without `reinit_password` any peer reaches the
47/// handler. [`MutationPolicy`](crate::mutation::MutationPolicy) and the
48/// mutation authorizer don't cover ReinitializeDevice, so check the
49/// [`ReinitializeContext`] to restrict sources.
50/// - **Refuse with [`Error::Protocol`].** Its class and code go back in an
51/// Error PDU, for example DEVICE / CONFIGURATION_IN_PROGRESS (Clause
52/// 16.4.1.3.1). Once the handler has run, the request may no longer be
53/// rejected (Clause 20.1.8), so an [`Error::Reject`], or any error other
54/// than `Protocol` or `Structured`, is sent as SERVICES / OTHER. A panic is
55/// caught (with unwind builds) and also answered SERVICES / OTHER, since
56/// the handler may have partly acted.
57pub type ReinitializeHandler =
58 Arc<dyn Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error> + Send + Sync>;
59
60/// The ReinitializeDevice request a [`ReinitializeHandler`] is asked to carry
61/// out, and who sent it.
62///
63/// The addresses are claims, not an authenticated operator. For routed
64/// traffic `source_mac` is the last router, so restrict routed requesters by
65/// `source_network`; only [`direct_sc_identity`](Self::direct_sc_identity) is
66/// verified. Neither the mutation policy nor the mutation authorizer sees
67/// this service, so a handler that serves only some peers checks them here.
68///
69/// The server builds the context; [`new`](Self::new) lets a test build one
70/// to call a handler directly.
71#[derive(Clone, PartialEq, Eq)]
72#[non_exhaustive]
73pub struct ReinitializeContext {
74 /// The requested state, one of those Clause 16.4 defines.
75 pub state: ReinitializedState,
76 /// Immediate data-link peer (or router) address.
77 pub source_mac: MacAddr,
78 /// Originating NPDU source when the request was routed.
79 pub source_network: Option<NpduAddress>,
80 /// Immutable ingress snapshot, retained after the admitting connection closes.
81 pub provenance: TransportProvenance,
82 /// Confirmed-request invoke identifier.
83 pub invoke_id: u8,
84}
85
86impl ReinitializeContext {
87 /// A context with these fields, for calling a handler outside the server,
88 /// such as in its unit tests. Outside `bacnet-transport` only
89 /// [`TransportProvenance::unverified`] can be built.
90 pub fn new(
91 state: ReinitializedState,
92 source_mac: MacAddr,
93 source_network: Option<NpduAddress>,
94 provenance: TransportProvenance,
95 invoke_id: u8,
96 ) -> Self {
97 Self {
98 state,
99 source_mac,
100 source_network,
101 provenance,
102 invoke_id,
103 }
104 }
105
106 /// Original verified direct-SC leaf and incarnation, separate from claims.
107 pub fn direct_sc_identity(&self) -> Option<DirectScIdentity> {
108 self.provenance.direct_sc_identity()
109 }
110}
111
112impl std::fmt::Debug for ReinitializeContext {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 f.debug_struct("ReinitializeContext")
115 .field("state", &self.state)
116 .field("source_mac_len", &self.source_mac.len())
117 .field(
118 "source_network",
119 &self
120 .source_network
121 .as_ref()
122 .map(|s| (s.network, s.mac_address.len())),
123 )
124 .field("provenance", &self.provenance)
125 .field("invoke_id", &self.invoke_id)
126 .finish_non_exhaustive()
127 }
128}
129
130/// Where a ReinitializeDevice request came from: its [`ReinitializeContext`]
131/// short of the state, which decoding supplies.
132pub(in crate::server) struct Requester {
133 source_mac: MacAddr,
134 source_network: Option<NpduAddress>,
135 provenance: TransportProvenance,
136}
137
138impl Requester {
139 pub(in crate::server) fn new(
140 source_mac: &[u8],
141 source_network: Option<&NpduAddress>,
142 provenance: TransportProvenance,
143 ) -> Self {
144 Self {
145 source_mac: MacAddr::from_slice(source_mac),
146 source_network: source_network.cloned(),
147 provenance,
148 }
149 }
150}
151
152/// Answer a ReinitializeDevice request: decode it, check its password and its
153/// state, then run `handler` with the database write-locked. A valid request
154/// with no handler is refused with SERVICES / SERVICE_REQUEST_DENIED.
155///
156/// `still_open` runs once the write lock is held, so an owner that closed
157/// while the request waited refuses it before the handler runs. The guard is
158/// released before `on_accepted` runs. That callback runs synchronously on
159/// success, before reply construction and with no intervening suspension.
160pub(in crate::server) async fn response(
161 db: &RwLock<ObjectDatabase>,
162 request: &ConfirmedRequestPdu,
163 password: &Option<String>,
164 handler: Option<&ReinitializeHandler>,
165 requester: Requester,
166 still_open: impl FnOnce() -> Result<(), Error>,
167 on_accepted: impl FnOnce(ReinitializedState),
168) -> Apdu {
169 let outcome = match handlers::handle_reinitialize_device(&request.service_request, password) {
170 Ok(state) => match handler {
171 Some(handler) => {
172 let context = ReinitializeContext::new(
173 state,
174 requester.source_mac,
175 requester.source_network,
176 requester.provenance,
177 request.invoke_id,
178 );
179 let mut db = db.write().await;
180 still_open()
181 .and_then(|()| invoke(handler, &context, &mut db))
182 .map(|()| state)
183 }
184 None => Err(services_error(ErrorCode::SERVICE_REQUEST_DENIED)),
185 },
186 Err(error) => Err(error),
187 };
188 match outcome {
189 Ok(state) => {
190 on_accepted(state);
191 Apdu::SimpleAck(SimpleAck {
192 invoke_id: request.invoke_id,
193 service_choice: request.service_choice,
194 })
195 }
196 Err(error) => error_apdu_from_error(request.invoke_id, request.service_choice, &error),
197 }
198}
199
200/// Full-server acceptance also owns the DCC restart transition. Acquire the
201/// timer slot before the handler's database lock, as DCC replacement/expiry do.
202pub(in crate::server) async fn server_response<T: TransportPort + 'static>(
203 services: &super::request_services::RequestServices<T>,
204 request: &ConfirmedRequestPdu,
205 requester: Requester,
206) -> Apdu {
207 let cov_resume = services.cov_table.read().await.timed().clone();
208 let mut slot = services.dcc_timer.lock().await;
209 let mut restart_accepted = false;
210 let reply = response(
211 &services.db,
212 request,
213 &services.config.reinit_password,
214 services.config.on_reinitialize.as_ref(),
215 requester,
216 || Ok(()),
217 |state| {
218 if matches!(
219 state,
220 ReinitializedState::WARMSTART | ReinitializedState::COLDSTART
221 ) {
222 super::dcc_timer::enable_for_restart(&slot, &services.comm_state, &cov_resume);
223 restart_accepted = true;
224 }
225 },
226 )
227 .await;
228 if restart_accepted {
229 // The commit already happened. Cancellation during this borrowed join
230 // retains the aborted handle for later replacement/shutdown cleanup.
231 super::dcc_timer::cancel(&mut slot).await;
232 }
233 reply
234}
235
236/// Run `handler` under a panic guard. Execution has begun once it is called,
237/// so the request may no longer draw a Reject (Clause 20.1.8): a panic, a
238/// Reject, or an error with no class and code is answered SERVICES / OTHER.
239fn invoke(
240 handler: &ReinitializeHandler,
241 context: &ReinitializeContext,
242 db: &mut ObjectDatabase,
243) -> Result<(), Error> {
244 match catch_unwind(AssertUnwindSafe(|| handler(context, db))) {
245 Ok(Ok(())) => Ok(()),
246 Ok(Err(error @ (Error::Protocol { .. } | Error::Structured { .. }))) => Err(error),
247 Ok(Err(error)) => {
248 debug!(%error, "ReinitializeDevice handler error answered SERVICES / OTHER");
249 Err(services_error(ErrorCode::OTHER))
250 }
251 Err(_) => {
252 warn!(
253 state = context.state.to_raw(),
254 invoke_id = context.invoke_id,
255 "ReinitializeDevice handler panicked; answered SERVICES / OTHER"
256 );
257 Err(services_error(ErrorCode::OTHER))
258 }
259 }
260}
261
262fn services_error(code: ErrorCode) -> Error {
263 Error::Protocol {
264 class: ErrorClass::SERVICES.to_raw() as u32,
265 code: code.to_raw() as u32,
266 }
267}
268
269impl<T: TransportPort + 'static> ServerBuilder<T> {
270 /// Set the password required for ReinitializeDevice requests.
271 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
272 self.config.reinit_password = Some(password.into());
273 self
274 }
275
276 /// Set the ReinitializeDevice handler. It must reply before restarting and
277 /// stay quick; see [`ReinitializeHandler`].
278 pub fn on_reinitialize<F>(mut self, handler: F) -> Self
279 where
280 F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
281 + Send
282 + Sync
283 + 'static,
284 {
285 self.config.on_reinitialize = Some(Arc::new(handler));
286 self
287 }
288}
289
290impl BipServerBuilder {
291 /// Set the password required for ReinitializeDevice requests.
292 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
293 self.config.reinit_password = Some(password.into());
294 self
295 }
296
297 /// Set the ReinitializeDevice handler. It must reply before restarting and
298 /// stay quick; see [`ReinitializeHandler`].
299 pub fn on_reinitialize<F>(mut self, handler: F) -> Self
300 where
301 F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
302 + Send
303 + Sync
304 + 'static,
305 {
306 self.config.on_reinitialize = Some(Arc::new(handler));
307 self
308 }
309}
310
311#[cfg(feature = "sc-tls")]
312impl super::ScServerBuilder {
313 /// Set the password required for ReinitializeDevice requests.
314 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
315 self.config.reinit_password = Some(password.into());
316 self
317 }
318
319 /// Set the ReinitializeDevice handler. It must reply before restarting and
320 /// stay quick; see [`ReinitializeHandler`].
321 pub fn on_reinitialize<F>(mut self, handler: F) -> Self
322 where
323 F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
324 + Send
325 + Sync
326 + 'static,
327 {
328 self.config.on_reinitialize = Some(Arc::new(handler));
329 self
330 }
331}