use crate::error::ScllError;
use crate::report::CardLifeCycle;
const P2_INITIALIZED: u8 = 0x07;
const P2_SECURED: u8 = 0x0F;
const P2_CARD_LOCKED: u8 = 0x7F;
pub fn check_transition(
current: CardLifeCycle,
target: CardLifeCycle,
force: bool,
) -> Result<TransitionPlan, ScllError> {
use CardLifeCycle::{CardLocked, Initialized, OpReady, Secured, Terminated, Unknown};
if matches!(target, Terminated) {
return Err(ScllError::TerminateOutOfScope);
}
if matches!(target, Unknown(_)) {
return Err(ScllError::IllegalLifecycleTransition);
}
if current == target {
return Ok(TransitionPlan::NoOp);
}
if matches!(current, Terminated | Unknown(_)) {
return Err(ScllError::IllegalLifecycleTransition);
}
let p2 = match (current, target) {
(OpReady, Initialized) => P2_INITIALIZED, (Initialized | CardLocked, Secured) => P2_SECURED, (OpReady, Secured) if force => P2_SECURED, (Secured, CardLocked) => P2_CARD_LOCKED, _ => return Err(ScllError::IllegalLifecycleTransition), };
Ok(TransitionPlan::Apply { p2 })
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransitionPlan {
NoOp, Apply { p2: u8 }, }
#[cfg(test)]
mod tests {
use super::*;
use crate::report::CardLifeCycle::{
CardLocked, Initialized, OpReady, Secured, Terminated, Unknown,
};
const VALID: [CardLifeCycle; 4] = [OpReady, Initialized, Secured, CardLocked];
#[test]
fn terminated_is_never_a_target_under_any_force() {
for ¤t in &[
OpReady,
Initialized,
Secured,
CardLocked,
Terminated,
Unknown(0x42),
] {
for force in [false, true] {
assert!(matches!(
check_transition(current, Terminated, force),
Err(ScllError::TerminateOutOfScope)
));
}
}
}
#[test]
fn forward_provisioning_is_one_way() {
assert_eq!(
check_transition(OpReady, Initialized, false).unwrap(),
TransitionPlan::Apply { p2: 0x07 }
);
assert_eq!(
check_transition(Initialized, Secured, false).unwrap(),
TransitionPlan::Apply { p2: 0x0F }
);
for force in [false, true] {
for &(from, to) in &[
(Initialized, OpReady),
(Secured, Initialized),
(Secured, OpReady),
(CardLocked, Initialized),
(CardLocked, OpReady),
] {
assert!(matches!(
check_transition(from, to, force),
Err(ScllError::IllegalLifecycleTransition)
));
}
}
}
#[test]
fn skip_ahead_to_secured_requires_force() {
assert!(matches!(
check_transition(OpReady, Secured, false),
Err(ScllError::IllegalLifecycleTransition)
));
assert_eq!(
check_transition(OpReady, Secured, true).unwrap(),
TransitionPlan::Apply { p2: 0x0F }
);
}
#[test]
fn lock_and_unlock_are_reversible() {
assert_eq!(
check_transition(Secured, CardLocked, false).unwrap(),
TransitionPlan::Apply { p2: 0x7F }
);
assert_eq!(
check_transition(CardLocked, Secured, false).unwrap(),
TransitionPlan::Apply { p2: 0x0F }
);
}
#[test]
fn same_state_is_a_no_op() {
for &s in &VALID {
assert_eq!(check_transition(s, s, false).unwrap(), TransitionPlan::NoOp);
}
}
#[test]
fn unknown_states_are_refused() {
assert!(matches!(
check_transition(Secured, Unknown(0x99), false),
Err(ScllError::IllegalLifecycleTransition)
));
assert!(matches!(
check_transition(Unknown(0x99), Secured, true),
Err(ScllError::IllegalLifecycleTransition)
));
}
#[test]
fn nothing_leaves_terminated() {
for &to in &VALID {
assert!(matches!(
check_transition(Terminated, to, true),
Err(ScllError::IllegalLifecycleTransition)
));
}
}
}