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::{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;