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::{AssertUnwindSafe, catch_unwind};
7
8use bacnet_transport::port::{DirectScIdentity, TransportProvenance};
9use bacnet_types::enums::{AuditOperation, 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 register the restart (or the backup or restore
24/// step) with [`ReinitializeContext::after_reply`]: Clause 16.4.2 has the
25/// device acknowledge the request before it shuts down.
26/// - **The after-reply hook.** It runs once the SimpleACK has been handed
27/// to the transport (#1565). On MS/TP, handed off means queued: on the
28/// reply channel or the transmit queue the frame still waits for the
29/// token, so stopping the server or the port at once can drop the reply
30/// there. Wait a short while (a few token rotations) first. It
31/// never runs for a request answered
32/// with an Error: the handler refused it or panicked, or the Network Port
33/// activation below failed. Nor does it run when the reply could not be
34/// handed over; the requester retries then. [`BACnetServer::stop`] seals
35/// responses and aborts request tasks before joining them, so a restart
36/// path that stops the server starts from the hook, never from the
37/// handler. On a full server, accepted WARMSTART/COLDSTART ends
38/// DISABLE_INITIATION as the handler accepts (Clause 16.1.2), cancels its
39/// timer and resumes held COV, even if the reply later fails.
40/// - **Be quick.** It runs synchronously on a runtime worker with the object
41/// database write-locked, so every other request waits for it. Hand slow
42/// work, such as writing backup files, to a task. On an endpoint session a
43/// blocking handler stalls the whole session. Never wait on another task
44/// or thread from it (a `block_on`, a channel receive, a join): if that
45/// work needs the database, the server deadlocks.
46/// - **Database edits are raw.** On a full server, changes made through the
47/// `&mut ObjectDatabase` skip what [`BACnetServer::write_local`] adds
48/// around a write: the Object_Name uniqueness check, the COV fanout, the
49/// event pass, Schedule and Command follow-ups, and the Audit record. A
50/// change that needs them, as ACTIVATE_CHANGES work on Network Port or
51/// Device properties may, belongs in `write_local`, called after the
52/// reply from a task the handler schedules (for example by sending the
53/// change to the code that holds the server).
54/// - **Network Port changes activate around it.** For ACTIVATE_CHANGES or
55/// WARMSTART the server first checks that the values written to Network
56/// Port objects would still be taken
57/// ([`ObjectDatabase::check_network_port_changes`]), and refuses with
58/// PROPERTY / INVALID_CONFIGURATION_DATA (Clause 16.4.1.3.1) before the
59/// handler runs if not. Once the handler accepts, the server puts them
60/// into effect under the same lock and clears Changes_Pending (Clause
61/// 12.56.13), the transport's BDT for one
62/// ([`ObjectDatabase::activate_network_port_changes`]): all or none per
63/// port, not across ports. A refusal then is answered the same way and
64/// leaves them pending. With no handler set, a registered port's
65/// pending-activation rows refuse writes, since nothing could activate
66/// them.
67/// - **Colour outputs on an in-process restart.** A server start drives each
68/// Color and Color Temperature output to its default (#1526). An
69/// application that carries WARMSTART or COLDSTART out in process, without
70/// building a new server, should do the same: call `start_output` on each
71/// colour object, which is safe to call again (a zero default then keeps
72/// the current value). It is a raw database edit, so it sends no COV
73/// notification of its own.
74/// - **Audited once accepted.** With Audit Reporters configured, an accepted
75/// request (the handler accepted and any Network Port activation took)
76/// records DEVICE_RESET, DEVICE_BACKUP or DEVICE_RESTORE from the
77/// requester (Clause 19.6, Table 19-5, #1564); ACTIVATE_CHANGES, which
78/// the table doesn't list, and every refusal record nothing. Report a
79/// reset made for the application's own reasons with
80/// [`BACnetServer::report_device_reset_local`].
81/// - **Gate the requester.** Without `reinit_password` any peer reaches the
82/// handler. Neither [`MutationPolicy`](crate::mutation::MutationPolicy)
83/// nor the [`ServerPolicy`](crate::ServerPolicy) covers ReinitializeDevice, so check the
84/// [`ReinitializeContext`] to restrict sources.
85/// - **Refuse with [`Error::protocol`].** Its class and code go back in an
86/// Error PDU, for example DEVICE / CONFIGURATION_IN_PROGRESS (Clause
87/// 16.4.1.3.1). Once the handler has run, the request may no longer be
88/// rejected (Clause 20.1.8), so an [`Error::reject`], or any error other
89/// than an Error PDU's `Remote` or `Structured`, is sent as SERVICES / OTHER. A panic is
90/// caught (with unwind builds) and also answered SERVICES / OTHER, since
91/// the handler may have partly acted.
92pub type ReinitializeHandler =
93 Arc<dyn Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error> + Send + Sync>;
94
95/// Records an accepted ReinitializeDevice request on an endpoint session
96/// (#1564): the Table 19-5 operation and the requester, called under the
97/// database guard once the request is accepted. The session supplies it
98/// when its Audit Reporter is configured.
99pub type EndpointReinitializeAudit = Arc<
100 dyn Fn(&mut ObjectDatabase, AuditOperation, &bacnet_objects::device::AuditWriteSource)
101 + Send
102 + Sync,
103>;
104
105/// The ReinitializeDevice request a [`ReinitializeHandler`] is asked to carry
106/// out, and who sent it.
107///
108/// The addresses are claims, not an authenticated operator. For routed
109/// traffic `source_mac` is the last router, so restrict routed requesters by
110/// `source_network`. Only the certificate accessors are verified:
111/// [`principal`](Self::principal) names the direct SC peer that sent the
112/// request itself, and is `None` for a routed one;
113/// [`forwarding_peer_principal`](Self::forwarding_peer_principal) and
114/// [`direct_sc_identity`](Self::direct_sc_identity) name the immediate hop,
115/// a router included. Neither the mutation policy nor the
116/// [`ServerPolicy`](crate::ServerPolicy) sees this service, so a handler that
117/// serves only some peers checks them here.
118///
119/// The server builds the context; [`new`](Self::new) lets a test build one
120/// to call a handler directly.
121#[derive(Clone, PartialEq, Eq)]
122#[non_exhaustive]
123pub struct ReinitializeContext {
124 /// The requested state, one of those Clause 16.4 defines.
125 pub state: ReinitializedState,
126 /// Immediate data-link peer (or router) address.
127 pub source_mac: MacAddr,
128 /// Originating NPDU source when the request was routed.
129 pub source_network: Option<NpduAddress>,
130 /// Immutable ingress snapshot, retained after the admitting connection closes.
131 pub provenance: TransportProvenance,
132 /// Confirmed-request invoke identifier.
133 pub invoke_id: u8,
134 /// Work registered with [`after_reply`](Self::after_reply). Clones
135 /// share it, so a hook registered through a clone still belongs to the
136 /// request.
137 after_reply: AfterReplyHooks,
138}
139
140/// One piece of work registered with [`ReinitializeContext::after_reply`].
141type AfterReplyHook = Box<dyn FnOnce() + Send>;
142
143/// The hooks a request's handler registered, shared by its context's clones.
144#[derive(Clone, Default)]
145struct AfterReplyHooks(Arc<std::sync::Mutex<Vec<AfterReplyHook>>>);
146
147impl AfterReplyHooks {
148 fn hooks(&self) -> std::sync::MutexGuard<'_, Vec<AfterReplyHook>> {
149 self.0
150 .lock()
151 .unwrap_or_else(std::sync::PoisonError::into_inner)
152 }
153
154 fn take(&self) -> AfterReply {
155 AfterReply(std::mem::take(&mut *self.hooks()))
156 }
157}
158
159/// Hooks are work the handler scheduled, not part of the request, so they
160/// never make two contexts unequal.
161impl PartialEq for AfterReplyHooks {
162 fn eq(&self, _: &Self) -> bool {
163 true
164 }
165}
166
167impl Eq for AfterReplyHooks {}
168
169/// The after-reply hooks of an accepted request, taken from its context.
170/// Empty for a request answered with an Error.
171#[derive(Default)]
172#[must_use = "an accepted request's hooks run once its reply is handed off"]
173pub(in crate::server) struct AfterReply(Vec<AfterReplyHook>);
174
175impl AfterReply {
176 /// Whether no hook waits, as for every refusal.
177 #[cfg(test)]
178 pub(in crate::server) fn is_empty(&self) -> bool {
179 self.0.is_empty()
180 }
181
182 /// Run each hook in registration order, once the SimpleACK has been
183 /// handed to the transport, with no server lock held. A panicking hook
184 /// is logged and the rest still run.
185 pub(in crate::server) fn run(self) {
186 for hook in self.0 {
187 if catch_unwind(AssertUnwindSafe(hook)).is_err() {
188 warn!("ReinitializeDevice after-reply hook panicked");
189 }
190 }
191 }
192}
193
194impl ReinitializeContext {
195 /// A context with these fields, for calling a handler outside the server,
196 /// such as in its unit tests. Outside `bacnet-transport` only
197 /// [`TransportProvenance::unverified`] can be built.
198 pub fn new(
199 state: ReinitializedState,
200 source_mac: MacAddr,
201 source_network: Option<NpduAddress>,
202 provenance: TransportProvenance,
203 invoke_id: u8,
204 ) -> Self {
205 Self {
206 state,
207 source_mac,
208 source_network,
209 provenance,
210 invoke_id,
211 after_reply: AfterReplyHooks::default(),
212 }
213 }
214
215 /// Run `hook` once this request's SimpleACK has been handed to the
216 /// transport, with no server lock held: the place to restart, or to
217 /// start the backup or restore step, after replying (Clause 16.4.2).
218 ///
219 /// Hooks run in the order registered, on the task that answered the
220 /// request, so each should be quick and hand longer work to a task or
221 /// thread of its own. On MS/TP, handed off means queued: the frame
222 /// still waits for the token, so a hook that stops the server or the
223 /// port at once can drop the reply; wait a short while first. Hooks
224 /// registered through a clone of the context after the handler returns
225 /// never run. They are dropped unrun when the request is
226 /// answered with an Error instead (the handler refused it or panicked,
227 /// or the Network Port activation after it failed), when the reply
228 /// could not be handed over, and when [`BACnetServer::stop`] aborts the
229 /// request task first. A panic in one is logged and the next still runs.
230 /// A context built with [`new`](Self::new) outside the server never runs
231 /// its hooks unless a test runs them itself.
232 pub fn after_reply(&self, hook: impl FnOnce() + Send + 'static) {
233 self.after_reply.hooks().push(Box::new(hook));
234 }
235
236 /// Test seam: take and run the hooks registered so far, as the server
237 /// does once the reply is handed off, and return how many there were.
238 /// For a binding's tests of a handler built on [`after_reply`](Self::after_reply);
239 /// never call it from a handler.
240 #[doc(hidden)]
241 pub fn run_after_reply_for_test(&self) -> usize {
242 let hooks = self.after_reply.take();
243 let count = hooks.0.len();
244 hooks.run();
245 count
246 }
247
248 /// The principal of the peer that originated the request: the verified
249 /// leaf certificate of a direct BACnet/SC connection, when the request
250 /// carries no routed source. `None` for other ingress, hub-relayed and
251 /// routed requests included (see [`crate::policy`]).
252 pub fn principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
253 crate::policy::originating_principal(&self.provenance, self.source_network.as_ref())
254 }
255
256 /// The principal of the direct BACnet/SC peer that handed this request
257 /// over, a router included when the request was routed; `None` off a
258 /// verified direct SC connection. Trusting it trusts everything that
259 /// peer forwards.
260 pub fn forwarding_peer_principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
261 self.provenance.principal()
262 }
263
264 crate::policy::certificate_accessors!();
265
266 /// The immediate direct-SC hop's verified leaf and connection incarnation:
267 /// for a routed request, the router that forwarded it. Absent off a
268 /// verified direct SC connection. Separate from claimed addresses.
269 pub fn direct_sc_identity(&self) -> Option<DirectScIdentity> {
270 self.provenance.direct_sc_identity()
271 }
272
273 /// The requester as an endpoint's Audit record names it: its address,
274 /// the originating one when routed, with the request's invoke ID.
275 pub(in crate::server) fn audit_source(&self) -> bacnet_objects::device::AuditWriteSource {
276 use bacnet_types::constructed::{BACnetAddress, BACnetRecipient};
277 let (network_number, mac_address) = match &self.source_network {
278 Some(source) => (source.network, source.mac_address.clone()),
279 None => (0, self.source_mac.clone()),
280 };
281 bacnet_objects::device::AuditWriteSource {
282 device: BACnetRecipient::Address(BACnetAddress {
283 network_number,
284 mac_address,
285 }),
286 invoke_id: self.invoke_id,
287 }
288 }
289}
290
291impl std::fmt::Debug for ReinitializeContext {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 f.debug_struct("ReinitializeContext")
294 .field("state", &self.state)
295 .field("source_mac_len", &self.source_mac.len())
296 .field(
297 "source_network",
298 &self
299 .source_network
300 .as_ref()
301 .map(|s| (s.network, s.mac_address.len())),
302 )
303 .field("provenance", &self.provenance)
304 .field("invoke_id", &self.invoke_id)
305 .finish_non_exhaustive()
306 }
307}
308
309/// Where a ReinitializeDevice request came from: its [`ReinitializeContext`]
310/// short of the state, which decoding supplies.
311pub(in crate::server) struct Requester {
312 source_mac: MacAddr,
313 source_network: Option<NpduAddress>,
314 provenance: TransportProvenance,
315}
316
317impl Requester {
318 pub(in crate::server) fn new(
319 source_mac: &[u8],
320 source_network: Option<&NpduAddress>,
321 provenance: TransportProvenance,
322 ) -> Self {
323 Self {
324 source_mac: MacAddr::from_slice(source_mac),
325 source_network: source_network.cloned(),
326 provenance,
327 }
328 }
329}
330
331/// Answer a ReinitializeDevice request: decode it, check its password and its
332/// state, then run `handler` with the database write-locked. A valid request
333/// with no handler is refused with SERVICES / SERVICE_REQUEST_DENIED.
334///
335/// `still_open` runs once the write lock is held, so an owner that closed
336/// while the request waited refuses it before the handler runs.
337/// `on_accepted` runs only once the request is accepted, meaning the handler
338/// accepted it and any Network Port activation succeeded, under the same
339/// guard and with no suspension before the reply is built: the place for the
340/// DCC restart transition and the Audit record (#1564).
341///
342/// A SimpleACK comes back with the hooks the handler registered, for the
343/// caller to run once it has handed the reply off. An Error comes back with
344/// none: they were dropped here.
345pub(in crate::server) async fn response(
346 db: &RwLock<ObjectDatabase>,
347 request: &ConfirmedRequestPdu,
348 password: &Option<String>,
349 handler: Option<&ReinitializeHandler>,
350 requester: Requester,
351 still_open: impl FnOnce() -> Result<(), Error>,
352 on_accepted: impl FnOnce(&ReinitializeContext, &mut ObjectDatabase),
353) -> (Apdu, AfterReply) {
354 let outcome = match handlers::handle_reinitialize_device(&request.service_request, password) {
355 Ok(state) => match handler {
356 Some(handler) => {
357 let context = ReinitializeContext::new(
358 state,
359 requester.source_mac,
360 requester.source_network,
361 requester.provenance,
362 request.invoke_id,
363 );
364 let mut db = db.write().await;
365 let outcome = still_open()
366 .and_then(|()| check_network_ports(state, &mut db))
367 .and_then(|()| invoke(handler, &context, &mut db))
368 .and_then(|()| activate_network_ports(state, &mut db));
369 // Taken under the guard, so a clone the handler kept adds
370 // nothing later. A refusal drops them here, unrun.
371 let hooks = context.after_reply.take();
372 outcome.map(|()| {
373 on_accepted(&context, &mut db);
374 hooks
375 })
376 }
377 None => Err(services_error(ErrorCode::SERVICE_REQUEST_DENIED)),
378 },
379 Err(error) => Err(error),
380 };
381 match outcome {
382 Ok(hooks) => {
383 let ack = Apdu::SimpleAck(SimpleAck {
384 invoke_id: request.invoke_id,
385 service_choice: request.service_choice,
386 });
387 (ack, hooks)
388 }
389 Err(error) => (
390 error_apdu_from_error(request.invoke_id, request.service_choice, &error),
391 AfterReply::default(),
392 ),
393 }
394}
395
396/// Full-server acceptance also owns the DCC restart transition and the
397/// request's Audit record (#1564), through `audit`, the requester's. Acquire
398/// the timer slot before the handler's database lock, as DCC
399/// replacement/expiry do.
400///
401/// An accepted restart that ends DISABLE_INITIATION records only its
402/// DEVICE_RESET: the implicit re-enable is part of the reset, not a
403/// DeviceCommunicationControl operation of its own.
404pub(in crate::server) async fn server_response<T: TransportPort + 'static>(
405 services: &super::request_services::RequestServices<T>,
406 request: &ConfirmedRequestPdu,
407 requester: Requester,
408 audit: &mut super::audit_reporter::WriteAudit<'_, T>,
409) -> (Apdu, AfterReply) {
410 let cov_resume = services.cov_table.read().await.timed().clone();
411 let mut slot = services.dcc_timer.lock().await;
412 let mut restart_accepted = false;
413 let reply = response(
414 &services.db,
415 request,
416 &services.config.reinit_password,
417 services.config.on_reinitialize.as_ref(),
418 requester,
419 || Ok(()),
420 |context, db| {
421 if matches!(
422 context.state,
423 ReinitializedState::WARMSTART | ReinitializedState::COLDSTART
424 ) {
425 super::dcc_timer::enable_for_restart(&slot, &services.comm_state, &cov_resume);
426 restart_accepted = true;
427 }
428 if let Some(operation) = audit_operation(context.state) {
429 audit.device_operation(db, operation);
430 }
431 },
432 )
433 .await;
434 if restart_accepted {
435 // The commit already happened. Cancellation during this borrowed join
436 // retains the aborted handle for later replacement/shutdown cleanup.
437 super::dcc_timer::cancel(&mut slot).await;
438 }
439 reply
440}
441
442/// The Audit operation an accepted request in `state` records (Clause 19.6,
443/// Table 19-5, #1564): DEVICE_RESET for the two restarts, DEVICE_BACKUP for
444/// the backup steps and DEVICE_RESTORE for the restore steps.
445/// ACTIVATE_CHANGES has no row in the table, so it records nothing.
446pub(in crate::server) fn audit_operation(state: ReinitializedState) -> Option<AuditOperation> {
447 match state {
448 ReinitializedState::COLDSTART | ReinitializedState::WARMSTART => {
449 Some(AuditOperation::DEVICE_RESET)
450 }
451 ReinitializedState::START_BACKUP | ReinitializedState::END_BACKUP => {
452 Some(AuditOperation::DEVICE_BACKUP)
453 }
454 ReinitializedState::START_RESTORE
455 | ReinitializedState::END_RESTORE
456 | ReinitializedState::ABORT_RESTORE => Some(AuditOperation::DEVICE_RESTORE),
457 _ => None,
458 }
459}
460
461/// Run `handler` under a panic guard. Execution has begun once it is called,
462/// so the request may no longer draw a Reject (Clause 20.1.8): a panic, a
463/// Reject, or an error with no class and code is answered SERVICES / OTHER.
464fn invoke(
465 handler: &ReinitializeHandler,
466 context: &ReinitializeContext,
467 db: &mut ObjectDatabase,
468) -> Result<(), Error> {
469 match catch_unwind(AssertUnwindSafe(|| handler(context, db))) {
470 Ok(Ok(())) => Ok(()),
471 Ok(Err(error @ (Error::Remote(RemoteError::Error { .. }) | Error::Structured { .. }))) => {
472 Err(error)
473 }
474 Ok(Err(error)) => {
475 debug!(%error, "ReinitializeDevice handler error answered SERVICES / OTHER");
476 Err(services_error(ErrorCode::OTHER))
477 }
478 Err(_) => {
479 warn!(
480 state = context.state.to_raw(),
481 invoke_id = context.invoke_id,
482 "ReinitializeDevice handler panicked; answered SERVICES / OTHER"
483 );
484 Err(services_error(ErrorCode::OTHER))
485 }
486 }
487}
488
489/// Whether `state` activates the values written to Network Port objects
490/// (Clause 12.56.13).
491fn activates(state: ReinitializedState) -> bool {
492 matches!(
493 state,
494 ReinitializedState::ACTIVATE_CHANGES | ReinitializedState::WARMSTART
495 )
496}
497
498/// The reply when Network Port values fail validation on activation:
499/// PROPERTY / INVALID_CONFIGURATION_DATA (Clause 16.4.1.3.1).
500fn invalid_configuration(error: &Error) -> Error {
501 debug!(%error, "Network Port activation refused");
502 Error::protocol(ErrorClass::PROPERTY, ErrorCode::INVALID_CONFIGURATION_DATA)
503}
504
505/// Before the handler runs, check that the pending Network Port values
506/// would still be taken, so a handler never schedules a restart for a
507/// request that then fails.
508fn check_network_ports(state: ReinitializedState, db: &mut ObjectDatabase) -> Result<(), Error> {
509 if !activates(state) {
510 return Ok(());
511 }
512 db.check_network_port_changes()
513 .map_err(|error| invalid_configuration(&error))
514}
515
516/// Once the handler has accepted ACTIVATE_CHANGES or WARMSTART, put the
517/// values written to Network Port objects into effect (Clause 12.56.13):
518/// the transport's BDT, for one. The check before the handler makes a
519/// refusal here a race with a change to the transport; it is answered the
520/// same way, as an Error, since the request may no longer be rejected
521/// (Clause 20.1.8).
522fn activate_network_ports(state: ReinitializedState, db: &mut ObjectDatabase) -> Result<(), Error> {
523 if !activates(state) {
524 return Ok(());
525 }
526 db.activate_network_port_changes()
527 .map(drop)
528 .map_err(|error| invalid_configuration(&error))
529}
530
531fn services_error(code: ErrorCode) -> Error {
532 Error::protocol(ErrorClass::SERVICES, code)
533}
534
535impl<T: TransportPort + 'static> BACnetServer<T> {
536 /// Report a reset the application carries out for its own reasons, not
537 /// at a peer's ReinitializeDevice request: a DEVICE_RESET Audit record
538 /// from this device, with no invoke ID (Clause 19.6, Table 19-5 covers
539 /// resets for any reason, #1564).
540 ///
541 /// An accepted WARMSTART or COLDSTART is recorded already; don't report
542 /// it again. The record goes through the Reporter that monitors the
543 /// local Device, under that Device's audit policy, as a peer's reset's
544 /// does, and does nothing when no Reporter selects it. It returns once
545 /// the record is admitted, not delivered, so call it before the restart
546 /// begins and give delivery time before the process exits.
547 ///
548 /// # Errors
549 ///
550 /// The stopped-server error once [`stop`](Self::stop) has run.
551 pub async fn report_device_reset_local(&self) -> Result<(), Error> {
552 let network = self.active_network()?;
553 let mut db = self.db.write().await;
554 if let Some(mut audit) = super::audit_reporter::WriteAudit::local(
555 &self.config,
556 network,
557 &self.notification_transactions,
558 &db,
559 ) {
560 audit.device_operation(&mut db, AuditOperation::DEVICE_RESET);
561 }
562 Ok(())
563 }
564}
565
566impl<T: TransportPort + 'static> ServerBuilder<T> {
567 /// Set the password required for ReinitializeDevice requests.
568 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
569 self.config.reinit_password = Some(password.into());
570 self
571 }
572
573 /// Set the ReinitializeDevice handler. It must reply before restarting and
574 /// stay quick; see [`ReinitializeHandler`].
575 pub fn on_reinitialize<F>(mut self, handler: F) -> Self
576 where
577 F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
578 + Send
579 + Sync
580 + 'static,
581 {
582 self.config.on_reinitialize = Some(Arc::new(handler));
583 self
584 }
585}
586
587impl BipServerBuilder {
588 /// Set the password required for ReinitializeDevice requests.
589 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
590 self.config.reinit_password = Some(password.into());
591 self
592 }
593
594 /// Set the ReinitializeDevice handler. It must reply before restarting and
595 /// stay quick; see [`ReinitializeHandler`].
596 pub fn on_reinitialize<F>(mut self, handler: F) -> Self
597 where
598 F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
599 + Send
600 + Sync
601 + 'static,
602 {
603 self.config.on_reinitialize = Some(Arc::new(handler));
604 self
605 }
606}
607
608#[cfg(feature = "sc-tls")]
609impl super::ScServerBuilder {
610 /// Set the password required for ReinitializeDevice requests.
611 pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
612 self.config.reinit_password = Some(password.into());
613 self
614 }
615
616 /// Set the ReinitializeDevice handler. It must reply before restarting and
617 /// stay quick; see [`ReinitializeHandler`].
618 pub fn on_reinitialize<F>(mut self, handler: F) -> Self
619 where
620 F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
621 + Send
622 + Sync
623 + 'static,
624 {
625 self.config.on_reinitialize = Some(Arc::new(handler));
626 self
627 }
628}
629
630#[cfg(test)]
631#[path = "reinitialize_hook_tests.rs"]
632mod hook_tests;