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;