use heapless::{String, Vec};
use crate::aid::Aid;
use crate::backend::{Scp02Backend, Scp03Backend};
use crate::command::get_status::{get_status_p2, P2_FIRST_TLV, P2_NEXT_TLV};
use crate::command::set_status::set_card_status as set_card_status_cmd;
use crate::error::{ScllError, Warning, WarningKind};
use crate::lifecycle::{check_transition, TransitionPlan};
use crate::limits::{
MAX_APPLETS, MAX_ELFS, MAX_MODULES_PER_ELF, MAX_SDS, MAX_STATUS_PAGES, MAX_STATUS_SCOPE_BYTES,
MAX_WARNINGS,
};
use crate::model::{ApplicationEntry, CardInventory, ExecutableLoadFileEntry, SecurityDomainEntry};
use crate::report::{
CardLifeCycle, GetCardInventoryParams, GetCardInventoryReport, GetCardStatusParams,
GetCardStatusReport, SetCardStatusParams, SetCardStatusReport,
};
use crate::response::{parse_status_e3, parse_status_registry, RegistryEntry};
use crate::scp::ScpSession;
use crate::tlv;
use crate::transport::Transport;
use crate::workflow::session::{self, SW_CONDITIONS, SW_MORE_DATA, SW_OK, SW_REF_NOT_FOUND};
const P1_ISD_SCOPE: u8 = 0x80;
const P1_APP_SD_SCOPE: u8 = 0x40;
const P1_ELF_MODULE_SCOPE: u8 = 0x10;
const PRIV_SECURITY_DOMAIN: u8 = 0x80;
pub fn get_card_status<B>(
t: &mut dyn Transport,
backend: &B,
session: &mut ScpSession,
isd_aid: &[u8],
) -> Result<GetCardStatusReport, ScllError>
where
B: Scp02Backend + Scp03Backend,
{
let (data, scope_truncated) = read_status_scope(t, backend, session, P1_ISD_SCOPE)?;
let mut warnings: Vec<Warning, MAX_WARNINGS> = Vec::new();
let decoded = if let Some(s) = parse_status_e3(&data)? {
s
} else {
let _ = warnings.push(Warning {
kind: WarningKind::GetStatusParseFailed,
detail: String::new(),
});
CardLifeCycle::Unknown(0)
};
if scope_truncated {
let _ = warnings.push(Warning {
kind: WarningKind::GetStatusParseFailed,
detail: String::new(),
});
}
let isd = Aid::new(isd_aid)?;
Ok(GetCardStatusReport {
state: decoded,
effective: GetCardStatusParams {
raw_state_byte: raw_byte(decoded),
decoded_state: decoded,
isd_aid: isd,
},
warnings,
})
}
pub fn get_card_inventory<B>(
t: &mut dyn Transport,
backend: &B,
session: &mut ScpSession,
isd_aid: &[u8],
) -> Result<GetCardInventoryReport, ScllError>
where
B: Scp02Backend + Scp03Backend,
{
let isd = Aid::new(isd_aid)?;
let mut security_domains: Vec<SecurityDomainEntry, MAX_SDS> = Vec::new();
let mut applets: Vec<ApplicationEntry, MAX_APPLETS> = Vec::new();
let mut elfs: Vec<ExecutableLoadFileEntry, MAX_ELFS> = Vec::new();
let mut truncated = false;
truncated |= collect_scope(t, backend, session, P1_ISD_SCOPE, |e| {
let aid = Aid::new(e.aid).unwrap_or_else(|_| isd.clone());
security_domains
.push(SecurityDomainEntry {
aid,
life_cycle_state: e.life_cycle,
privileges: e.privileges,
associated_sd_aid: None,
})
.is_ok()
})?;
truncated |= collect_scope(t, backend, session, P1_APP_SD_SCOPE, |e| {
if e.aid == isd.as_bytes() {
return true; }
let Ok(aid) = Aid::new(e.aid) else {
return true; };
if e.privileges[0] & PRIV_SECURITY_DOMAIN != 0 {
let parent = e.associated_sd_aid.and_then(|s| Aid::new(s).ok());
security_domains
.push(SecurityDomainEntry {
aid,
life_cycle_state: e.life_cycle,
privileges: e.privileges,
associated_sd_aid: parent,
})
.is_ok()
} else {
let parent = e
.associated_sd_aid
.and_then(|s| Aid::new(s).ok())
.unwrap_or_else(|| isd.clone());
let elf = e.elf_aid.and_then(|s| Aid::new(s).ok());
applets
.push(ApplicationEntry {
aid,
life_cycle_state: e.life_cycle,
privileges: e.privileges,
associated_sd_aid: parent,
associated_elf_aid: elf,
})
.is_ok()
}
})?;
truncated |= collect_scope(t, backend, session, P1_ELF_MODULE_SCOPE, |e| {
let Ok(aid) = Aid::new(e.aid) else {
return true;
};
let parent = e
.associated_sd_aid
.and_then(|s| Aid::new(s).ok())
.unwrap_or_else(|| isd.clone());
let mut modules: Vec<Aid, MAX_MODULES_PER_ELF> = Vec::new();
for m in &e.modules {
if let Ok(a) = Aid::new(m) {
let _ = modules.push(a);
}
}
elfs.push(ExecutableLoadFileEntry {
aid,
life_cycle_state: e.life_cycle,
associated_sd_aid: parent,
modules,
})
.is_ok()
})?;
let mut warnings: Vec<Warning, MAX_WARNINGS> = Vec::new();
if truncated {
let _ = warnings.push(Warning {
kind: WarningKind::InventoryTruncated,
detail: String::new(),
});
}
let (sd_count, app_count, elf_count) = (security_domains.len(), applets.len(), elfs.len());
Ok(GetCardInventoryReport {
inventory: CardInventory {
security_domains,
applets,
elfs,
},
effective: GetCardInventoryParams {
isd_aid: isd,
security_domain_count: sd_count,
application_count: app_count,
elf_count,
truncated,
},
warnings,
})
}
fn collect_scope<B, F>(
t: &mut dyn Transport,
backend: &B,
session: &mut ScpSession,
p1: u8,
mut store: F,
) -> Result<bool, ScllError>
where
B: Scp02Backend + Scp03Backend,
F: FnMut(&RegistryEntry) -> bool,
{
let (data, mut truncated) = read_status_scope(t, backend, session, p1)?;
if data.is_empty() && !truncated {
return Ok(false); }
for entry in &parse_status_registry(&data)? {
if !store(entry) {
truncated = true;
}
}
Ok(truncated)
}
fn read_status_scope<B>(
t: &mut dyn Transport,
backend: &B,
session: &mut ScpSession,
p1: u8,
) -> Result<(Vec<u8, MAX_STATUS_SCOPE_BYTES>, bool), ScllError>
where
B: Scp02Backend + Scp03Backend,
{
let mut buf: Vec<u8, MAX_STATUS_SCOPE_BYTES> = Vec::new();
let mut good_len = 0usize;
let mut p2 = P2_FIRST_TLV;
let mut truncated = false;
let mut done = false;
for _ in 0..MAX_STATUS_PAGES {
let capdu = get_status_p2(p1, p2, &[])?;
let (data, sw) = session::transmit_in_session(t, backend, session, &capdu)?;
match sw {
SW_OK | SW_MORE_DATA => {
if buf.extend_from_slice(&data).is_err() {
truncated = true;
break;
}
if tlv::parse(&buf).is_ok() {
good_len = buf.len();
} else if sw == SW_OK {
truncated = true;
}
if sw == SW_OK {
done = true;
break;
}
p2 = P2_NEXT_TLV;
}
SW_REF_NOT_FOUND => return Ok((Vec::new(), false)), other => return Err(ScllError::from_general_sw(other)),
}
}
if !done {
truncated = true; }
buf.truncate(good_len);
Ok((buf, truncated))
}
pub struct SetCardStatusArgs<'a> {
pub target_state: CardLifeCycle,
pub current_state: Option<CardLifeCycle>,
pub force: bool,
pub isd_aid: &'a [u8],
}
pub fn set_card_status<B>(
t: &mut dyn Transport,
backend: &B,
session: &mut ScpSession,
args: &SetCardStatusArgs<'_>,
) -> Result<SetCardStatusReport, ScllError>
where
B: Scp02Backend + Scp03Backend,
{
let current = match args.current_state {
Some(s) => s,
None => get_card_status(t, backend, session, args.isd_aid)?.state,
};
let plan = check_transition(current, args.target_state, args.force)?;
let mut warnings: Vec<Warning, MAX_WARNINGS> = Vec::new();
let (was_no_op, p2_state_byte) = match plan {
TransitionPlan::NoOp => {
let _ = warnings.push(Warning {
kind: WarningKind::LifecycleNoOp,
detail: String::new(),
});
(true, raw_byte(args.target_state))
}
TransitionPlan::Apply { p2 } => {
let capdu = set_card_status_cmd(p2, args.isd_aid)?;
let (_d, sw) = session::transmit_in_session(t, backend, session, &capdu)?;
match sw {
SW_OK => {}
SW_CONDITIONS => return Err(ScllError::IllegalLifecycleTransition),
other => return Err(ScllError::from_general_sw(other)),
}
(false, p2)
}
};
let irreversible = matches!(
args.target_state,
CardLifeCycle::Initialized | CardLifeCycle::Secured
) && current != CardLifeCycle::CardLocked;
Ok(SetCardStatusReport {
effective: SetCardStatusParams {
state_before: current,
target_state: args.target_state,
p1_status_type: P1_ISD_SCOPE,
p2_state_byte,
was_no_op,
force_used: args.force,
irreversible,
},
warnings,
})
}
fn raw_byte(s: CardLifeCycle) -> u8 {
match s {
CardLifeCycle::OpReady => 0x01,
CardLifeCycle::Initialized => 0x07,
CardLifeCycle::Secured => 0x0F,
CardLifeCycle::CardLocked => 0x7F,
CardLifeCycle::Terminated => 0xFF,
CardLifeCycle::Unknown(b) => b,
}
}