fxa_client/state_machine/
checker.rsuse crate::{FxaEvent, FxaState};
use error_support::{breadcrumb, report_error};
use parking_lot::Mutex;
pub use super::internal_machines::Event as FxaStateCheckerEvent;
use super::internal_machines::State as InternalState;
use super::internal_machines::*;
pub enum FxaStateCheckerState {
GetAuthState,
BeginOAuthFlow {
scopes: Vec<String>,
entrypoint: String,
},
BeginPairingFlow {
pairing_url: String,
scopes: Vec<String>,
entrypoint: String,
},
CompleteOAuthFlow {
code: String,
state: String,
},
InitializeDevice,
EnsureDeviceCapabilities,
CheckAuthorizationStatus,
GetProfile,
Disconnect,
Complete {
new_state: FxaState,
},
Cancel,
}
pub struct FxaStateMachineChecker {
inner: Mutex<FxaStateMachineCheckerInner>,
}
struct FxaStateMachineCheckerInner {
public_state: FxaState,
internal_state: InternalState,
state_machine: Box<dyn InternalStateMachine + Send>,
reported_error: bool,
}
impl Default for FxaStateMachineChecker {
fn default() -> Self {
Self {
inner: Mutex::new(FxaStateMachineCheckerInner {
public_state: FxaState::Uninitialized,
internal_state: InternalState::Cancel,
state_machine: Box::new(UninitializedStateMachine),
reported_error: false,
}),
}
}
}
impl FxaStateMachineChecker {
pub fn new() -> Self {
Self::default()
}
pub fn handle_public_event(&self, event: FxaEvent) {
let mut inner = self.inner.lock();
if inner.reported_error {
return;
}
match &inner.internal_state {
InternalState::Complete(_) | InternalState::Cancel => (),
internal_state => {
report_error!(
"fxa-state-machine-checker",
"handle_public_event called with non-terminal internal state (event: {event}, internal state: {internal_state})",
);
inner.reported_error = true;
return;
}
}
inner.state_machine = make_state_machine(&inner.public_state);
match inner.state_machine.initial_state(event.clone()) {
Ok(state) => {
breadcrumb!(
"fxa-state-machine-checker: public transition start ({event} -> {state})"
);
inner.internal_state = state;
if let InternalState::Complete(new_state) = &inner.internal_state {
inner.public_state = new_state.clone();
breadcrumb!("fxa-state-machine-checker: public transition end");
}
}
Err(e) => {
report_error!(
"fxa-state-machine-checker",
"Error in handle_public_event: {e}"
);
inner.reported_error = true;
}
}
}
pub fn handle_internal_event(&self, event: FxaStateCheckerEvent) {
let mut inner = self.inner.lock();
if inner.reported_error {
return;
}
match inner
.state_machine
.next_state(inner.internal_state.clone(), event.clone())
{
Ok(state) => {
breadcrumb!("fxa-state-machine-checker: internal transition ({event} -> {state})");
match &state {
InternalState::Complete(new_state) => {
inner.public_state = new_state.clone();
breadcrumb!("fxa-state-machine-checker: public transition end");
}
InternalState::Cancel => {
breadcrumb!("fxa-state-machine-checker: public transition end (cancelled)");
}
_ => (),
};
inner.internal_state = state;
}
Err(e) => {
report_error!(
"fxa-state-machine-checker",
"Error in handle_internal_event: {e}"
);
inner.reported_error = true;
}
}
}
pub fn check_public_state(&self, state: FxaState) {
let mut inner = self.inner.lock();
if inner.reported_error {
return;
}
match &inner.internal_state {
InternalState::Complete(_) | InternalState::Cancel => (),
internal_state => {
report_error!(
"fxa-state-machine-checker",
"check_public_state called with non-terminal internal state (expected: {state} actual internal state: {internal_state})"
);
inner.reported_error = true;
return;
}
}
if inner.public_state != state {
report_error!(
"fxa-state-machine-checker",
"Public state mismatch: expected: {state}, actual: {} ({})",
inner.public_state,
inner.internal_state
);
inner.reported_error = true;
} else {
breadcrumb!("fxa-state-machine-checker: check_public_state successful {state}");
}
}
pub fn check_internal_state(&self, state: FxaStateCheckerState) {
let mut inner = self.inner.lock();
if inner.reported_error {
return;
}
let state: InternalState = state.into();
if inner.internal_state != state {
report_error!(
"fxa-state-machine-checker",
"Internal state mismatch (expected: {state}, actual: {})",
inner.internal_state
);
inner.reported_error = true;
} else {
breadcrumb!("fxa-state-machine-checker: check_internal_state successful {state}");
}
}
}
fn make_state_machine(public_state: &FxaState) -> Box<dyn InternalStateMachine + Send> {
match public_state {
FxaState::Uninitialized => Box::new(UninitializedStateMachine),
FxaState::Disconnected => Box::new(DisconnectedStateMachine),
FxaState::Authenticating { .. } => Box::new(AuthenticatingStateMachine),
FxaState::Connected => Box::new(ConnectedStateMachine),
FxaState::AuthIssues => Box::new(AuthIssuesStateMachine),
}
}
impl From<InternalState> for FxaStateCheckerState {
fn from(state: InternalState) -> Self {
match state {
InternalState::GetAuthState => Self::GetAuthState,
InternalState::BeginOAuthFlow { scopes, entrypoint } => {
Self::BeginOAuthFlow { scopes, entrypoint }
}
InternalState::BeginPairingFlow {
pairing_url,
scopes,
entrypoint,
} => Self::BeginPairingFlow {
pairing_url,
scopes,
entrypoint,
},
InternalState::CompleteOAuthFlow { code, state } => {
Self::CompleteOAuthFlow { code, state }
}
InternalState::InitializeDevice => Self::InitializeDevice,
InternalState::EnsureDeviceCapabilities => Self::EnsureDeviceCapabilities,
InternalState::CheckAuthorizationStatus => Self::CheckAuthorizationStatus,
InternalState::Disconnect => Self::Disconnect,
InternalState::GetProfile => Self::GetProfile,
InternalState::Complete(new_state) => Self::Complete { new_state },
InternalState::Cancel => Self::Cancel,
}
}
}
impl From<FxaStateCheckerState> for InternalState {
fn from(state: FxaStateCheckerState) -> Self {
match state {
FxaStateCheckerState::GetAuthState => Self::GetAuthState,
FxaStateCheckerState::BeginOAuthFlow { scopes, entrypoint } => {
Self::BeginOAuthFlow { scopes, entrypoint }
}
FxaStateCheckerState::BeginPairingFlow {
pairing_url,
scopes,
entrypoint,
} => Self::BeginPairingFlow {
pairing_url,
scopes,
entrypoint,
},
FxaStateCheckerState::CompleteOAuthFlow { code, state } => {
Self::CompleteOAuthFlow { code, state }
}
FxaStateCheckerState::InitializeDevice => Self::InitializeDevice,
FxaStateCheckerState::EnsureDeviceCapabilities => Self::EnsureDeviceCapabilities,
FxaStateCheckerState::CheckAuthorizationStatus => Self::CheckAuthorizationStatus,
FxaStateCheckerState::Disconnect => Self::Disconnect,
FxaStateCheckerState::GetProfile => Self::GetProfile,
FxaStateCheckerState::Complete { new_state } => Self::Complete(new_state),
FxaStateCheckerState::Cancel => Self::Cancel,
}
}
}