Skip to main content

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::{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 `Protocol` 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    /// The immediate direct-SC hop's verified leaf and connection incarnation:
265    /// for a routed request, the router that forwarded it. Absent off a
266    /// verified direct SC connection. Separate from claimed addresses.
267    pub fn direct_sc_identity(&self) -> Option<DirectScIdentity> {
268        self.provenance.direct_sc_identity()
269    }
270
271    /// The requester as an endpoint's Audit record names it: its address,
272    /// the originating one when routed, with the request's invoke ID.
273    pub(in crate::server) fn audit_source(&self) -> bacnet_objects::device::AuditWriteSource {
274        use bacnet_types::constructed::{BACnetAddress, BACnetRecipient};
275        let (network_number, mac_address) = match &self.source_network {
276            Some(source) => (source.network, source.mac_address.clone()),
277            None => (0, self.source_mac.clone()),
278        };
279        bacnet_objects::device::AuditWriteSource {
280            device: BACnetRecipient::Address(BACnetAddress {
281                network_number,
282                mac_address,
283            }),
284            invoke_id: self.invoke_id,
285        }
286    }
287}
288
289impl std::fmt::Debug for ReinitializeContext {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        f.debug_struct("ReinitializeContext")
292            .field("state", &self.state)
293            .field("source_mac_len", &self.source_mac.len())
294            .field(
295                "source_network",
296                &self
297                    .source_network
298                    .as_ref()
299                    .map(|s| (s.network, s.mac_address.len())),
300            )
301            .field("provenance", &self.provenance)
302            .field("invoke_id", &self.invoke_id)
303            .finish_non_exhaustive()
304    }
305}
306
307/// Where a ReinitializeDevice request came from: its [`ReinitializeContext`]
308/// short of the state, which decoding supplies.
309pub(in crate::server) struct Requester {
310    source_mac: MacAddr,
311    source_network: Option<NpduAddress>,
312    provenance: TransportProvenance,
313}
314
315impl Requester {
316    pub(in crate::server) fn new(
317        source_mac: &[u8],
318        source_network: Option<&NpduAddress>,
319        provenance: TransportProvenance,
320    ) -> Self {
321        Self {
322            source_mac: MacAddr::from_slice(source_mac),
323            source_network: source_network.cloned(),
324            provenance,
325        }
326    }
327}
328
329/// Answer a ReinitializeDevice request: decode it, check its password and its
330/// state, then run `handler` with the database write-locked. A valid request
331/// with no handler is refused with SERVICES / SERVICE_REQUEST_DENIED.
332///
333/// `still_open` runs once the write lock is held, so an owner that closed
334/// while the request waited refuses it before the handler runs.
335/// `on_accepted` runs only once the request is accepted, meaning the handler
336/// accepted it and any Network Port activation succeeded, under the same
337/// guard and with no suspension before the reply is built: the place for the
338/// DCC restart transition and the Audit record (#1564).
339///
340/// A SimpleACK comes back with the hooks the handler registered, for the
341/// caller to run once it has handed the reply off. An Error comes back with
342/// none: they were dropped here.
343pub(in crate::server) async fn response(
344    db: &RwLock<ObjectDatabase>,
345    request: &ConfirmedRequestPdu,
346    password: &Option<String>,
347    handler: Option<&ReinitializeHandler>,
348    requester: Requester,
349    still_open: impl FnOnce() -> Result<(), Error>,
350    on_accepted: impl FnOnce(&ReinitializeContext, &mut ObjectDatabase),
351) -> (Apdu, AfterReply) {
352    let outcome = match handlers::handle_reinitialize_device(&request.service_request, password) {
353        Ok(state) => match handler {
354            Some(handler) => {
355                let context = ReinitializeContext::new(
356                    state,
357                    requester.source_mac,
358                    requester.source_network,
359                    requester.provenance,
360                    request.invoke_id,
361                );
362                let mut db = db.write().await;
363                let outcome = still_open()
364                    .and_then(|()| check_network_ports(state, &mut db))
365                    .and_then(|()| invoke(handler, &context, &mut db))
366                    .and_then(|()| activate_network_ports(state, &mut db));
367                // Taken under the guard, so a clone the handler kept adds
368                // nothing later. A refusal drops them here, unrun.
369                let hooks = context.after_reply.take();
370                outcome.map(|()| {
371                    on_accepted(&context, &mut db);
372                    hooks
373                })
374            }
375            None => Err(services_error(ErrorCode::SERVICE_REQUEST_DENIED)),
376        },
377        Err(error) => Err(error),
378    };
379    match outcome {
380        Ok(hooks) => {
381            let ack = Apdu::SimpleAck(SimpleAck {
382                invoke_id: request.invoke_id,
383                service_choice: request.service_choice,
384            });
385            (ack, hooks)
386        }
387        Err(error) => (
388            error_apdu_from_error(request.invoke_id, request.service_choice, &error),
389            AfterReply::default(),
390        ),
391    }
392}
393
394/// Full-server acceptance also owns the DCC restart transition and the
395/// request's Audit record (#1564), through `audit`, the requester's. Acquire
396/// the timer slot before the handler's database lock, as DCC
397/// replacement/expiry do.
398///
399/// An accepted restart that ends DISABLE_INITIATION records only its
400/// DEVICE_RESET: the implicit re-enable is part of the reset, not a
401/// DeviceCommunicationControl operation of its own.
402pub(in crate::server) async fn server_response<T: TransportPort + 'static>(
403    services: &super::request_services::RequestServices<T>,
404    request: &ConfirmedRequestPdu,
405    requester: Requester,
406    audit: &mut super::audit_reporter::WriteAudit<'_, T>,
407) -> (Apdu, AfterReply) {
408    let cov_resume = services.cov_table.read().await.timed().clone();
409    let mut slot = services.dcc_timer.lock().await;
410    let mut restart_accepted = false;
411    let reply = response(
412        &services.db,
413        request,
414        &services.config.reinit_password,
415        services.config.on_reinitialize.as_ref(),
416        requester,
417        || Ok(()),
418        |context, db| {
419            if matches!(
420                context.state,
421                ReinitializedState::WARMSTART | ReinitializedState::COLDSTART
422            ) {
423                super::dcc_timer::enable_for_restart(&slot, &services.comm_state, &cov_resume);
424                restart_accepted = true;
425            }
426            if let Some(operation) = audit_operation(context.state) {
427                audit.device_operation(db, operation);
428            }
429        },
430    )
431    .await;
432    if restart_accepted {
433        // The commit already happened. Cancellation during this borrowed join
434        // retains the aborted handle for later replacement/shutdown cleanup.
435        super::dcc_timer::cancel(&mut slot).await;
436    }
437    reply
438}
439
440/// The Audit operation an accepted request in `state` records (Clause 19.6,
441/// Table 19-5, #1564): DEVICE_RESET for the two restarts, DEVICE_BACKUP for
442/// the backup steps and DEVICE_RESTORE for the restore steps.
443/// ACTIVATE_CHANGES has no row in the table, so it records nothing.
444pub(in crate::server) fn audit_operation(state: ReinitializedState) -> Option<AuditOperation> {
445    match state {
446        ReinitializedState::COLDSTART | ReinitializedState::WARMSTART => {
447            Some(AuditOperation::DEVICE_RESET)
448        }
449        ReinitializedState::START_BACKUP | ReinitializedState::END_BACKUP => {
450            Some(AuditOperation::DEVICE_BACKUP)
451        }
452        ReinitializedState::START_RESTORE
453        | ReinitializedState::END_RESTORE
454        | ReinitializedState::ABORT_RESTORE => Some(AuditOperation::DEVICE_RESTORE),
455        _ => None,
456    }
457}
458
459/// Run `handler` under a panic guard. Execution has begun once it is called,
460/// so the request may no longer draw a Reject (Clause 20.1.8): a panic, a
461/// Reject, or an error with no class and code is answered SERVICES / OTHER.
462fn invoke(
463    handler: &ReinitializeHandler,
464    context: &ReinitializeContext,
465    db: &mut ObjectDatabase,
466) -> Result<(), Error> {
467    match catch_unwind(AssertUnwindSafe(|| handler(context, db))) {
468        Ok(Ok(())) => Ok(()),
469        Ok(Err(error @ (Error::Protocol { .. } | Error::Structured { .. }))) => Err(error),
470        Ok(Err(error)) => {
471            debug!(%error, "ReinitializeDevice handler error answered SERVICES / OTHER");
472            Err(services_error(ErrorCode::OTHER))
473        }
474        Err(_) => {
475            warn!(
476                state = context.state.to_raw(),
477                invoke_id = context.invoke_id,
478                "ReinitializeDevice handler panicked; answered SERVICES / OTHER"
479            );
480            Err(services_error(ErrorCode::OTHER))
481        }
482    }
483}
484
485/// Whether `state` activates the values written to Network Port objects
486/// (Clause 12.56.13).
487fn activates(state: ReinitializedState) -> bool {
488    matches!(
489        state,
490        ReinitializedState::ACTIVATE_CHANGES | ReinitializedState::WARMSTART
491    )
492}
493
494/// The reply when Network Port values fail validation on activation:
495/// PROPERTY / INVALID_CONFIGURATION_DATA (Clause 16.4.1.3.1).
496fn invalid_configuration(error: &Error) -> Error {
497    debug!(%error, "Network Port activation refused");
498    Error::Protocol {
499        class: ErrorClass::PROPERTY,
500        code: ErrorCode::INVALID_CONFIGURATION_DATA,
501    }
502}
503
504/// Before the handler runs, check that the pending Network Port values
505/// would still be taken, so a handler never schedules a restart for a
506/// request that then fails.
507fn check_network_ports(state: ReinitializedState, db: &mut ObjectDatabase) -> Result<(), Error> {
508    if !activates(state) {
509        return Ok(());
510    }
511    db.check_network_port_changes()
512        .map_err(|error| invalid_configuration(&error))
513}
514
515/// Once the handler has accepted ACTIVATE_CHANGES or WARMSTART, put the
516/// values written to Network Port objects into effect (Clause 12.56.13):
517/// the transport's BDT, for one. The check before the handler makes a
518/// refusal here a race with a change to the transport; it is answered the
519/// same way, as an Error, since the request may no longer be rejected
520/// (Clause 20.1.8).
521fn activate_network_ports(state: ReinitializedState, db: &mut ObjectDatabase) -> Result<(), Error> {
522    if !activates(state) {
523        return Ok(());
524    }
525    db.activate_network_port_changes()
526        .map(drop)
527        .map_err(|error| invalid_configuration(&error))
528}
529
530fn services_error(code: ErrorCode) -> Error {
531    Error::Protocol {
532        class: ErrorClass::SERVICES,
533        code,
534    }
535}
536
537impl<T: TransportPort + 'static> BACnetServer<T> {
538    /// Report a reset the application carries out for its own reasons, not
539    /// at a peer's ReinitializeDevice request: a DEVICE_RESET Audit record
540    /// from this device, with no invoke ID (Clause 19.6, Table 19-5 covers
541    /// resets for any reason, #1564).
542    ///
543    /// An accepted WARMSTART or COLDSTART is recorded already; don't report
544    /// it again. The record goes through the Reporter that monitors the
545    /// local Device, under that Device's audit policy, as a peer's reset's
546    /// does, and does nothing when no Reporter selects it. It returns once
547    /// the record is admitted, not delivered, so call it before the restart
548    /// begins and give delivery time before the process exits.
549    ///
550    /// # Errors
551    ///
552    /// The stopped-server error once [`stop`](Self::stop) has run.
553    pub async fn report_device_reset_local(&self) -> Result<(), Error> {
554        let network = self.active_network()?;
555        let mut db = self.db.write().await;
556        if let Some(mut audit) = super::audit_reporter::WriteAudit::local(
557            &self.config,
558            network,
559            &self.notification_transactions,
560            &db,
561        ) {
562            audit.device_operation(&mut db, AuditOperation::DEVICE_RESET);
563        }
564        Ok(())
565    }
566}
567
568impl<T: TransportPort + 'static> ServerBuilder<T> {
569    /// Set the password required for ReinitializeDevice requests.
570    pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
571        self.config.reinit_password = Some(password.into());
572        self
573    }
574
575    /// Set the ReinitializeDevice handler. It must reply before restarting and
576    /// stay quick; see [`ReinitializeHandler`].
577    pub fn on_reinitialize<F>(mut self, handler: F) -> Self
578    where
579        F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
580            + Send
581            + Sync
582            + 'static,
583    {
584        self.config.on_reinitialize = Some(Arc::new(handler));
585        self
586    }
587}
588
589impl BipServerBuilder {
590    /// Set the password required for ReinitializeDevice requests.
591    pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
592        self.config.reinit_password = Some(password.into());
593        self
594    }
595
596    /// Set the ReinitializeDevice handler. It must reply before restarting and
597    /// stay quick; see [`ReinitializeHandler`].
598    pub fn on_reinitialize<F>(mut self, handler: F) -> Self
599    where
600        F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
601            + Send
602            + Sync
603            + 'static,
604    {
605        self.config.on_reinitialize = Some(Arc::new(handler));
606        self
607    }
608}
609
610#[cfg(feature = "sc-tls")]
611impl super::ScServerBuilder {
612    /// Set the password required for ReinitializeDevice requests.
613    pub fn reinit_password(mut self, password: impl Into<String>) -> Self {
614        self.config.reinit_password = Some(password.into());
615        self
616    }
617
618    /// Set the ReinitializeDevice handler. It must reply before restarting and
619    /// stay quick; see [`ReinitializeHandler`].
620    pub fn on_reinitialize<F>(mut self, handler: F) -> Self
621    where
622        F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error>
623            + Send
624            + Sync
625            + 'static,
626    {
627        self.config.on_reinitialize = Some(Arc::new(handler));
628        self
629    }
630}
631
632#[cfg(test)]
633#[path = "reinitialize_hook_tests.rs"]
634mod hook_tests;