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//! Authoritative communication lifecycle operations and failures.
15//!
[docs] 16//! [[ dimpl~communication-control-operations, Authoritative communication lifecycle operations, dimpl ]]
17//!
18//! Part of the communication-access contract (with [`super::access`]), defined
19//! here so that transport crates and replacement communication plugins can
20//! depend on it without coupling to the default plugin's crate.
21
22/// Why authoritative communication activation was requested.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ActivationCause {
25 /// Startup requires communication to be available immediately (`Always` mode).
26 Startup,
27 /// A diagnostic UDS operation needed communication while it was not ready.
28 DiagnosticRequest,
29 /// An application or custom plugin explicitly requested activation.
30 Explicit,
31 /// An exclusive disable lease was released.
32 DisableRelease,
33}
34
35/// Why whole-vehicle (re-)detection was requested via a communication
36/// plugin's `trigger_detection`.
37///
38/// The framework attaches no authorization meaning to a cause. Which ones are
39/// admitted in a given
40/// [`CommunicationInitMode`](crate::communication_control::CommunicationInitMode)
41/// is the selected plugin's policy.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum DetectionCause {
44 /// An application or custom plugin explicitly requested (re-)detection.
45 Explicit,
46 // TODO(networkreset-endpoint, #490): add TopologyRediscovery variant for
47 // the `networkreset(trigger_detection=true)` request once the endpoint
48 // exists.
49}
50
51/// A lifecycle operation controlled by the communication framework.
52///
53/// An activation prepares every registered lifecycle reconfiguration
54/// participant and hook, publishes their state through one synchronous commit
55/// barrier, enables the transport, initializes the hooks, then runs
56/// [`CommunicationVariantDetection`](super::CommunicationVariantDetection).
57/// Activation-shaped operations differ only in which stages they run:
58///
59/// | Operation | Prepare + commit | Transport | `initialize` | Detector |
60/// |-------------------|------------------|--------------------------|--------------|-----------------------------------------------|
61/// | `Enable` | yes | enable | yes | no |
62/// | `EnableAndDetect` | yes | enable | yes | yes, subject to `VariantDetectionMode` |
63/// | `Detect` | untouched | untouched | untouched | yes |
64/// | `Resume` | yes | restore displaced state | if enabled | whatever the enable it resumes did |
65///
66/// Which of them a caller may request in a given `init_mode` is the selected
67/// communication plugin's policy.
68///
69/// # Stages run only on a transition
70///
71/// Every row above applies only when the operation claims a transition. From
72/// [`Enabled`](super::access::CommunicationState::Enabled), `Enable` and
73/// `EnableAndDetect` succeed without running any stage. Detecting from there
74/// requires a separate [`Detect`](Self::Detect).
75/// `VariantState` remains the readiness signal, because detection settles per
76/// ECU and asynchronously.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum CommunicationOperation {
79 /// Activate physical transport and run lifecycle hooks, but *not* variant
80 /// detection. For plugins that own the detection decision themselves.
81 Enable,
82 /// Activate physical transport, run lifecycle hooks, then run variant
83 /// detection.
84 EnableAndDetect,
85 /// Register a lifecycle hook with the communication worker.
86 RegisterLifecycleHook,
87 /// Register the whole-vehicle variant detector with the communication worker.
88 RegisterVariantDetection,
89 /// Disable physical transport under an exclusive lease.
90 Disable,
91 /// Resume physical transport after a disable lease is released.
92 Resume,
93 /// Finalize pending lifecycle reconfiguration while remaining disabled.
94 FinishDisableLease,
95 /// Run whole-vehicle variant detection against an already-live transport.
96 ///
97 /// Touches neither the transport nor the lifecycle hooks, and is therefore
98 /// valid only from
99 /// [`CommunicationState::Enabled`](super::access::CommunicationState::Enabled).
100 /// From any other state it is refused with
101 /// [`CommunicationOperationFailure::TransitionFailure`].
102 Detect,
103}
104
105impl CommunicationOperation {
106 /// Whether this operation's activation sequence runs variant detection
107 /// after the transport and the lifecycle hooks are up.
108 ///
109 /// Only meaningful for the operations that reach the activation sequence.
110 /// `Resume` never consults this. It repeats the shape of the enable it
111 /// resumes, which only the worker knows.
112 #[must_use]
113 pub const fn runs_detection(self) -> bool {
114 !matches!(self, Self::Enable)
115 }
116}
117
118/// A structured failure of an authoritative lifecycle operation.
119#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
120pub enum CommunicationOperationFailure {
121 #[error(
122 "Permission denied for {operation:?}. Likely the operation was protected by a different \
123 owner."
124 )]
125 PermissionFailure {
126 /// Lifecycle operation that was rejected.
127 operation: CommunicationOperation,
128 },
129
130 /// The physical transport operation failed.
131 #[error("Transport {operation:?} failed: {error}")]
132 TransportFailure {
133 /// Lifecycle operation that invoked the transport.
134 operation: CommunicationOperation,
135 /// Transport error description.
136 error: String,
137 },
138 /// A post-activation initializer failed.
139 #[error("Communication initializer {initializer} failed: {error}")]
140 InitializerFailure {
141 /// Initializer name.
142 initializer: String,
143 /// Initializer error description.
144 error: String,
145 },
146 /// An initializer failed and transport cleanup failed as well.
147 #[error(
148 "Communication initializer {initializer} failed: {initializer_error}; transport cleanup \
149 failed: {cleanup_error}"
150 )]
151 InitializerCleanupFailure {
152 /// Initializer name.
153 initializer: String,
154 /// Initializer error description.
155 initializer_error: String,
156 /// Transport cleanup error description.
157 cleanup_error: String,
158 },
159 /// Variant detection failed.
160 #[error("Variant detection {detector} failed: {error}")]
161 DetectionFailure {
162 /// Detector name.
163 detector: String,
164 /// Detection error description.
165 error: String,
166 },
167 /// Variant detection failed and transport cleanup failed as well.
168 #[error(
169 "Variant detection {detector} failed: {detection_error}; transport cleanup failed: \
170 {cleanup_error}"
171 )]
172 DetectionCleanupFailure {
173 /// Detector name.
174 detector: String,
175 /// Detection error description.
176 detection_error: String,
177 /// Transport cleanup error description.
178 cleanup_error: String,
179 },
180
181 /// The operation was invalid for the current authoritative lifecycle state.
182 ///
183 /// Notably returned for a [`CommunicationOperation::Detect`] requested
184 /// while communication is not `Enabled`. Detection runs against a live
185 /// transport and never brings one up itself.
186 #[error("Cannot {operation:?} in the current lifecycle state")]
187 TransitionFailure {
188 /// Requested operation.
189 operation: CommunicationOperation,
190 },
191
192 /// The operation was rejected because an exclusive disable lease currently owns the state.
193 /// Only the matching lease may re-activate communication.
194 #[error("Cannot {operation:?}: an exclusive disable lease is currently held")]
195 DisableLeaseHeld {
196 /// Requested operation.
197 operation: CommunicationOperation,
198 },
199
200 /// Variant detection was rejected because active communication guards are
201 /// held.
202 ///
203 /// A [`CommunicationGuard`](super::access::CommunicationGuard) is the only
204 /// lock that spans a whole multi-request sequence (e.g. a flash transfer's
205 /// `TransferData` blocks). The per-ECU transport semaphore reopens between
206 /// individual requests, so a detection request could otherwise land between
207 /// two requests of that sequence.
208 ///
209 /// Detection is refused rather than made to wait, so that the decision stays
210 /// synchronous and a minutes-long sequence cannot block it.
211 #[error("Cannot {operation:?}: communication guards are currently held")]
212 GuardsHeld {
213 /// Requested operation.
214 operation: CommunicationOperation,
215 },
216
217 /// The lifecycle worker is shutting down. No new operation may start.
218 #[error("Cannot {operation:?}: the communication framework is shutting down")]
219 ShuttingDown {
220 /// Requested operation.
221 operation: CommunicationOperation,
222 },
223
224 /// The lifecycle worker exited unexpectedly and cannot process the request.
225 #[error("Cannot {operation:?}: the communication worker is unavailable")]
226 WorkerUnavailable {
227 /// Requested operation.
228 operation: CommunicationOperation,
229 },
230
231 /// `init_mode` does not authorize this operation.
232 ///
233 /// Returned by a communication plugin applying its own policy, never by the
234 /// framework's lifecycle handle.
235 #[error(
236 "init_mode does not authorize {operation:?}; communication must be enabled out of band"
237 )]
238 ModeDisabled {
239 /// Requested operation.
240 operation: CommunicationOperation,
241 },
242}