fxa_client/state_machine/
checker.rs
1use crate::{FxaEvent, FxaState};
12use error_support::{breadcrumb, report_error};
13use parking_lot::Mutex;
14
15pub use super::internal_machines::Event as FxaStateCheckerEvent;
16use super::internal_machines::State as InternalState;
17use super::internal_machines::*;
18
19pub enum FxaStateCheckerState {
22 GetAuthState,
23 BeginOAuthFlow {
24 scopes: Vec<String>,
25 entrypoint: String,
26 },
27 BeginPairingFlow {
28 pairing_url: String,
29 scopes: Vec<String>,
30 entrypoint: String,
31 },
32 CompleteOAuthFlow {
33 code: String,
34 state: String,
35 },
36 InitializeDevice,
37 EnsureDeviceCapabilities,
38 CheckAuthorizationStatus,
39 GetProfile,
40 Disconnect,
41 Complete {
42 new_state: FxaState,
43 },
44 Cancel,
45}
46
47pub struct FxaStateMachineChecker {
48 inner: Mutex<FxaStateMachineCheckerInner>,
49}
50
51struct FxaStateMachineCheckerInner {
52 public_state: FxaState,
53 internal_state: InternalState,
54 state_machine: Box<dyn InternalStateMachine + Send>,
55 reported_error: bool,
58}
59
60impl Default for FxaStateMachineChecker {
61 fn default() -> Self {
62 Self {
63 inner: Mutex::new(FxaStateMachineCheckerInner {
64 public_state: FxaState::Uninitialized,
65 internal_state: InternalState::Cancel,
66 state_machine: Box::new(UninitializedStateMachine),
67 reported_error: false,
68 }),
69 }
70 }
71}
72
73impl FxaStateMachineChecker {
74 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn handle_public_event(&self, event: FxaEvent) {
80 let mut inner = self.inner.lock();
81 if inner.reported_error {
82 return;
83 }
84 match &inner.internal_state {
85 InternalState::Complete(_) | InternalState::Cancel => (),
86 internal_state => {
87 report_error!(
88 "fxa-state-machine-checker",
89 "handle_public_event called with non-terminal internal state (event: {event}, internal state: {internal_state})",
90 );
91 inner.reported_error = true;
92 return;
93 }
94 }
95
96 inner.state_machine = make_state_machine(&inner.public_state);
97 match inner.state_machine.initial_state(event.clone()) {
98 Ok(state) => {
99 breadcrumb!(
100 "fxa-state-machine-checker: public transition start ({event} -> {state})"
101 );
102 inner.internal_state = state;
103 if let InternalState::Complete(new_state) = &inner.internal_state {
104 inner.public_state = new_state.clone();
105 breadcrumb!("fxa-state-machine-checker: public transition end");
106 }
107 }
108 Err(e) => {
109 report_error!(
110 "fxa-state-machine-checker",
111 "Error in handle_public_event: {e}"
112 );
113 inner.reported_error = true;
114 }
115 }
116 }
117
118 pub fn handle_internal_event(&self, event: FxaStateCheckerEvent) {
120 let mut inner = self.inner.lock();
121 if inner.reported_error {
122 return;
123 }
124 match inner
125 .state_machine
126 .next_state(inner.internal_state.clone(), event.clone())
127 {
128 Ok(state) => {
129 breadcrumb!("fxa-state-machine-checker: internal transition ({event} -> {state})");
130 match &state {
131 InternalState::Complete(new_state) => {
132 inner.public_state = new_state.clone();
133 breadcrumb!("fxa-state-machine-checker: public transition end");
134 }
135 InternalState::Cancel => {
136 breadcrumb!("fxa-state-machine-checker: public transition end (cancelled)");
137 }
138 _ => (),
139 };
140 inner.internal_state = state;
141 }
142 Err(e) => {
143 report_error!(
144 "fxa-state-machine-checker",
145 "Error in handle_internal_event: {e}"
146 );
147 inner.reported_error = true;
148 }
149 }
150 }
151
152 pub fn check_public_state(&self, state: FxaState) {
156 let mut inner = self.inner.lock();
157 if inner.reported_error {
158 return;
159 }
160 match &inner.internal_state {
161 InternalState::Complete(_) | InternalState::Cancel => (),
162 internal_state => {
163 report_error!(
164 "fxa-state-machine-checker",
165 "check_public_state called with non-terminal internal state (expected: {state} actual internal state: {internal_state})"
166 );
167 inner.reported_error = true;
168 return;
169 }
170 }
171 if inner.public_state != state {
172 report_error!(
173 "fxa-state-machine-checker",
174 "Public state mismatch: expected: {state}, actual: {} ({})",
175 inner.public_state,
176 inner.internal_state
177 );
178 inner.reported_error = true;
179 } else {
180 breadcrumb!("fxa-state-machine-checker: check_public_state successful {state}");
181 }
182 }
183
184 pub fn check_internal_state(&self, state: FxaStateCheckerState) {
188 let mut inner = self.inner.lock();
189 if inner.reported_error {
190 return;
191 }
192 let state: InternalState = state.into();
193 if inner.internal_state != state {
194 report_error!(
195 "fxa-state-machine-checker",
196 "Internal state mismatch (expected: {state}, actual: {})",
197 inner.internal_state
198 );
199 inner.reported_error = true;
200 } else {
201 breadcrumb!("fxa-state-machine-checker: check_internal_state successful {state}");
202 }
203 }
204}
205
206fn make_state_machine(public_state: &FxaState) -> Box<dyn InternalStateMachine + Send> {
207 match public_state {
208 FxaState::Uninitialized => Box::new(UninitializedStateMachine),
209 FxaState::Disconnected => Box::new(DisconnectedStateMachine),
210 FxaState::Authenticating { .. } => Box::new(AuthenticatingStateMachine),
211 FxaState::Connected => Box::new(ConnectedStateMachine),
212 FxaState::AuthIssues => Box::new(AuthIssuesStateMachine),
213 }
214}
215
216impl From<InternalState> for FxaStateCheckerState {
217 fn from(state: InternalState) -> Self {
218 match state {
219 InternalState::GetAuthState => Self::GetAuthState,
220 InternalState::BeginOAuthFlow { scopes, entrypoint } => {
221 Self::BeginOAuthFlow { scopes, entrypoint }
222 }
223 InternalState::BeginPairingFlow {
224 pairing_url,
225 scopes,
226 entrypoint,
227 } => Self::BeginPairingFlow {
228 pairing_url,
229 scopes,
230 entrypoint,
231 },
232 InternalState::CompleteOAuthFlow { code, state } => {
233 Self::CompleteOAuthFlow { code, state }
234 }
235 InternalState::InitializeDevice => Self::InitializeDevice,
236 InternalState::EnsureDeviceCapabilities => Self::EnsureDeviceCapabilities,
237 InternalState::CheckAuthorizationStatus => Self::CheckAuthorizationStatus,
238 InternalState::Disconnect => Self::Disconnect,
239 InternalState::GetProfile => Self::GetProfile,
240 InternalState::Complete(new_state) => Self::Complete { new_state },
241 InternalState::Cancel => Self::Cancel,
242 }
243 }
244}
245
246impl From<FxaStateCheckerState> for InternalState {
247 fn from(state: FxaStateCheckerState) -> Self {
248 match state {
249 FxaStateCheckerState::GetAuthState => Self::GetAuthState,
250 FxaStateCheckerState::BeginOAuthFlow { scopes, entrypoint } => {
251 Self::BeginOAuthFlow { scopes, entrypoint }
252 }
253 FxaStateCheckerState::BeginPairingFlow {
254 pairing_url,
255 scopes,
256 entrypoint,
257 } => Self::BeginPairingFlow {
258 pairing_url,
259 scopes,
260 entrypoint,
261 },
262 FxaStateCheckerState::CompleteOAuthFlow { code, state } => {
263 Self::CompleteOAuthFlow { code, state }
264 }
265 FxaStateCheckerState::InitializeDevice => Self::InitializeDevice,
266 FxaStateCheckerState::EnsureDeviceCapabilities => Self::EnsureDeviceCapabilities,
267 FxaStateCheckerState::CheckAuthorizationStatus => Self::CheckAuthorizationStatus,
268 FxaStateCheckerState::Disconnect => Self::Disconnect,
269 FxaStateCheckerState::GetProfile => Self::GetProfile,
270 FxaStateCheckerState::Complete { new_state } => Self::Complete(new_state),
271 FxaStateCheckerState::Cancel => Self::Cancel,
272 }
273 }
274}