1/*
2 * SPDX-FileCopyrightText: 2026 Copyright (c) Contributors to the Eclipse Foundation
3 *
4 * See the NOTICE file(s) distributed with this work for additional
5 * information regarding copyright ownership.
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Apache License Version 2.0 which is available at
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 */
13
14//! The narrow, diagnostic-use communication contract.
15//!
[docs] 16//! [[ dimpl~communication-control-access, Capability-limited diagnostic communication access, dimpl ]]
17//!
18//! Defined here rather than in the default communication plugin's crate, so that
19//! transport crates and replacement plugins can depend on the contract without
20//! coupling to that implementation.
21use std::time::Duration;
22
23use super::{
24 DEFAULT_DEFERRED_RETRY_AFTER, VariantDetectionMode,
25 operation::{ActivationCause, CommunicationOperation, CommunicationOperationFailure},
26};
27
28/// Authoritative communication lifecycle state.
29///
30/// [`CommunicationState::Enabling`] identifies the lifecycle operation in
31/// progress, while [`CommunicationState::Error`] retains the structured failure
32/// from the latest lifecycle operation. Use this type when callers need the
33/// complete current state or failure details.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum CommunicationState {
36 /// Communication has not been enabled, the state is not owned and can be enabled by anyone.
37 Disabled,
38 /// An enabling lifecycle operation is in progress.
39 Enabling(CommunicationOperation),
40 /// Transport is enabled and every stage the claiming operation called for
41 /// has completed.
42 ///
43 /// Which stages those were depends on the operation. An
44 /// [`Enable`](CommunicationOperation::Enable) reaches `Enabled` with no
45 /// variant detected. `Enabled` does not mean detection concluded. Detection
46 /// settles per ECU and asynchronously, so per-ECU `VariantState` is the
47 /// readiness signal.
48 Enabled,
49 /// Transport disablement is in progress.
50 Disabling,
51 /// Transport is physically disabled and exclusively owned by a `DisableLease`.
52 DisabledExclusive,
53 /// Reconfiguration compensation is incomplete. Transport remains disabled
54 /// and activation is blocked until an exclusive recovery lease succeeds.
55 RecoveryRequired(CommunicationOperationFailure),
56 /// The latest lifecycle operation failed.
57 Error(CommunicationOperationFailure),
58}
59
60/// Error returned by communication guard acquisition.
61///
62/// This is a minimal enum containing only the states that can prevent
63/// guard acquisition. Lifecycle operation failures should be reported via
64/// the `Result` returned by a communication plugin's `enable()` and
65/// `DisableLease::release()`.
66#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
67pub enum CommunicationError {
68 /// Communication is currently disabled. No new guards can be acquired.
69 #[error("Communication is disabled")]
70 Disabled,
71 /// Communication is currently being enabled.
72 #[error("Communication is being enabled")]
73 Enabling,
74 /// Communication is currently being disabled.
75 #[error("Communication is being disabled")]
76 Disabling,
77 /// Communication is disabled and exclusively owned by a disable lease.
78 #[error("Communication is exclusively disabled")]
79 DisabledExclusive,
80 /// Communication has failed and cannot be used.
81 #[error("Communication has failed: {0}")]
82 Failed(CommunicationOperationFailure),
83}
84
85/// Opaque RAII guard marking communication as in use, so exclusive disable
86/// cannot begin while diagnostic activity is ongoing.
87///
88/// The implementation is owned by whichever [`CommunicationAccess`] minted it
89/// via [`CommunicationGuard::new`]. This type only stores it type-erased and
90/// drops it. Communication is free again once the last guard is dropped.
91#[must_use = "The guard must be held for the complete diagnostic-activity lifetime. Communication \
92 is marked as 'free' once the last guard is dropped. As long as any guard lives, \
93 communication cannot be disabled."]
94pub struct CommunicationGuard {
95 _inner: Box<dyn Send + Sync>,
96}
97
98impl CommunicationGuard {
99 /// Wraps an implementation-defined guard so that its concrete type stays
100 /// private to the [`CommunicationAccess`] implementation that minted it.
101 pub fn new(inner: impl Send + Sync + 'static) -> Self {
102 Self {
103 _inner: Box::new(inner),
104 }
105 }
106}
107
108/// Narrow capability that does not expose full lifecycle control.
109///
110/// Consumers can inspect state, hold communication active for an operation and
111/// request activation, but cannot disable it, run explicit whole-vehicle
112/// (re-)detection, or otherwise administer the underlying plugin.
113pub trait CommunicationAccess: Send + Sync + 'static {
114 /// Returns the authoritative lifecycle state.
115 fn state(&self) -> CommunicationState;
116
117 /// Acquires diagnostic communication if it is enabled.
118 /// The returned guard prevents the communication from being shut down while it is alive.
119 ///
120 /// # Errors
121 ///
122 /// Returns an error when communication is not enabled.
123 fn acquire(&self) -> Result<CommunicationGuard, CommunicationError>;
124
125 /// Requests activation without blocking.
126 ///
127 /// Callers must check [`state`](Self::state) before using communication.
128 fn request_activate(&self, cause: ActivationCause) -> CommunicationState;
129
130 /// Returns the retry hint for requests deferred while communication starts.
131 ///
132 /// The default preserves compatibility for custom access implementations
133 /// that do not expose configuration-owned retry timing.
134 fn retry_after(&self) -> Duration {
135 DEFAULT_DEFERRED_RETRY_AFTER
136 }
137
138 /// Returns the effective detection policy for the currently enabled (or
139 /// currently being enabled) communication runtime.
140 ///
141 /// Lets a consumer waiting on its own readiness signal distinguish
142 /// "detection is still settling" from "nothing will ever settle this".
143 fn variant_detection(&self) -> VariantDetectionMode;
144}