#![forbid(unsafe_code)]
use crate::attestation::AttestationError;
use crate::noc::NocError;
use crate::state_machine::action::Expectation;
use crate::state_machine::stage::Stage;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CommissioningError {
#[error("invalid commissioner config: {0}")]
InvalidConfig(&'static str),
#[error("unexpected response kind: expected {expected:?}, got {got:?}")]
UnexpectedResponseKind {
expected: Expectation,
got: Expectation,
},
#[error("response delivered out of order at stage {0:?}")]
OutOfOrderResponse(Stage),
#[error("device rejected stage {stage:?}: IM status {im_status:#x}")]
DeviceImStatus {
stage: Stage,
im_status: u16,
},
#[error("malformed response at stage {0:?}")]
MalformedResponse(Stage),
#[error("attestation verification failed: {0}")]
Attestation(#[from] AttestationError),
#[error("NOC issuance failed: {0}")]
Noc(#[from] NocError),
#[error("CASE session establishment failed")]
CaseEstablishmentFailed,
#[error("device does not support {needed:?} network type (credential/device mismatch)")]
NetworkFeatureUnsupported {
needed: NetworkKind,
},
#[error(
"network commissioning rejected at stage {stage:?}: \
networking_status {networking_status:#x}, \
debug_text={debug_text:?}, hint={remediation_hint:?}"
)]
NetworkRejected {
stage: Stage,
networking_status: u8,
debug_text: Option<String>,
remediation_hint: RemediationHint,
},
#[error("device is Wi-Fi but no wifi credentials supplied")]
WifiCredentialsRequired,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum NetworkKind {
WiFi,
Thread,
Ethernet,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum RemediationHint {
CheckPassphrase,
CheckSsid,
CheckRegulatoryRegion,
UpgradeSecurityMode,
DeviceNetworkSlotsFull,
DeviceIpStackFailure,
None,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state_machine::action::Expectation;
use crate::state_machine::stage::Stage;
#[test]
fn invalid_config_carries_message() {
let e = CommissioningError::InvalidConfig("missing IPK epoch key");
let msg = e.to_string();
assert!(msg.contains("missing IPK"), "{msg}");
}
#[test]
fn unexpected_response_kind_shows_both_sides() {
let e = CommissioningError::UnexpectedResponseKind {
expected: Expectation::ArmFailsafeResponse,
got: Expectation::AttestationResponse,
};
let msg = e.to_string();
assert!(msg.contains("ArmFailsafeResponse"), "{msg}");
assert!(msg.contains("AttestationResponse"), "{msg}");
}
#[test]
fn out_of_order_response_names_the_stage() {
let e = CommissioningError::OutOfOrderResponse(Stage::ArmFailsafe);
let msg = e.to_string();
assert!(msg.contains("ArmFailsafe"), "{msg}");
}
#[test]
fn device_im_status_includes_stage_and_status_code() {
let e = CommissioningError::DeviceImStatus {
stage: Stage::ArmFailsafe,
im_status: 0x0098,
};
let msg = e.to_string();
assert!(msg.contains("ArmFailsafe"), "{msg}");
assert!(msg.contains("0x98"), "{msg}");
}
#[test]
fn remediation_hint_is_copy_eq_hash() {
fn assert_copy<T: Copy + Eq + std::hash::Hash>() {}
assert_copy::<RemediationHint>();
assert_eq!(RemediationHint::None, RemediationHint::None);
assert_ne!(RemediationHint::None, RemediationHint::CheckPassphrase);
}
}