use heapless::Vec;
use crate::aid::Aid;
use crate::backend::{Scp02Backend, Scp03Backend};
use crate::cap::{parse as cap_parse, InflateCtx};
use crate::command::delete::delete_object;
use crate::command::install::{install_for_install_make_selectable, install_for_load, PrivLen};
use crate::command::load::load_block;
use crate::error::ScllError;
use crate::limits::{HASH_MAX, INSTALL_PARAMS_MAX, LOAD_BLOCK_DATA};
use crate::report::{
CreateSsdParams, CreateSsdReport, DeleteCascade, DeleteObjectParams, DeleteObjectReport,
DeleteTargetKind, LoadPackageParams, LoadPackageReport,
};
use crate::scp::ScpSession;
use crate::transport::Transport;
use crate::workflow::session::{self, SW_CONDITIONS, SW_OK, SW_REF_NOT_FOUND, SW_WRONG_DATA};
const PRIV_REFUSED_MASK: u8 = 0x40 | 0x20 | 0x01;
const MAX_LOAD_BYTES: usize = 256 * LOAD_BLOCK_DATA;
pub struct CreateSsdArgs<'a> {
pub parent_sd_aid: &'a [u8],
pub ssd_aid: &'a [u8],
pub elf_aid: &'a [u8],
pub module_aid: &'a [u8],
pub privileges: [u8; 3],
pub install_params: &'a [u8],
}
pub fn create_ssd<B>(
t: &mut dyn Transport,
backend: &B,
parent_session: &mut ScpSession,
args: &CreateSsdArgs<'_>,
priv_len: PrivLen,
) -> Result<CreateSsdReport, ScllError>
where
B: Scp02Backend + Scp03Backend,
{
if args.privileges[0] & PRIV_REFUSED_MASK != 0 {
return Err(ScllError::UnsupportedPrivilege);
}
let capdu = install_for_install_make_selectable(
args.elf_aid,
args.module_aid,
args.ssd_aid,
args.privileges,
priv_len,
args.install_params,
)?;
let (_d, sw) = session::transmit_in_session(t, backend, parent_session, &capdu)?;
match sw {
SW_OK => {}
SW_REF_NOT_FOUND => return Err(ScllError::ResidentSdNotFound),
SW_WRONG_DATA => return Err(ScllError::AidAlreadyExists),
other => return Err(ScllError::from_general_sw(other)),
}
Ok(CreateSsdReport {
effective: CreateSsdParams {
ssd_aid_effective: Aid::new(args.ssd_aid)?,
aid_was_generated: false,
parent_sd_aid: Aid::new(args.parent_sd_aid)?,
privileges_used: args.privileges,
elf_aid_used: Aid::new(args.elf_aid)?,
module_aid_used: Aid::new(args.module_aid)?,
install_params_used: copy_bounded::<INSTALL_PARAMS_MAX>(args.install_params)?,
},
warnings: Vec::new(),
})
}
pub struct LoadPackageArgs<'a> {
pub target_sd_aid: &'a [u8],
pub cap_zip: &'a [u8],
pub lfdb_hash: &'a [u8],
}
pub fn load_package<B>(
t: &mut dyn Transport,
backend: &B,
parent_session: &mut ScpSession,
args: &LoadPackageArgs<'_>,
infl: &mut InflateCtx,
) -> Result<LoadPackageReport, ScllError>
where
B: Scp02Backend + Scp03Backend,
{
let cap = cap_parse(args.cap_zip, infl)?;
let package_aid = cap.package_aid.clone();
let total = cap.lfdb().len();
if total > MAX_LOAD_BYTES {
return Err(ScllError::LoadTooLarge);
}
let install = install_for_load(package_aid.as_bytes(), args.target_sd_aid, args.lfdb_hash)?;
let (_d, sw) = session::transmit_in_session(t, backend, parent_session, &install)?;
match sw {
SW_OK => {}
SW_REF_NOT_FOUND => return Err(ScllError::ResidentSdNotFound),
SW_WRONG_DATA => return Err(ScllError::PackageAidExists),
other => return Err(ScllError::from_general_sw(other)),
}
infl.reset();
let mut lfdb = cap.lfdb();
let mut out = [0u8; LOAD_BLOCK_DATA];
let mut sent = 0usize;
let mut block_no = 0u8;
let mut blocks_sent = 0u16;
loop {
let n = lfdb.next_block(infl, &mut out)?;
if n == 0 {
break;
}
sent += n;
let last = sent >= total;
let capdu = load_block(block_no, last, &out[..n])?;
let (_ld, lsw) = session::transmit_in_session(t, backend, parent_session, &capdu)?;
if lsw != SW_OK {
return Err(ScllError::from_general_sw(lsw));
}
block_no = block_no.wrapping_add(1);
blocks_sent += 1;
if last {
break;
}
}
Ok(LoadPackageReport {
effective: LoadPackageParams {
package_aid,
load_file_size: u32::try_from(total).unwrap_or(u32::MAX),
hash_value: copy_bounded::<HASH_MAX>(args.lfdb_hash)?,
block_count: blocks_sent,
target_sd_aid: Aid::new(args.target_sd_aid)?,
},
warnings: Vec::new(),
})
}
pub fn delete_ssd<B>(
t: &mut dyn Transport,
backend: &B,
parent_session: &mut ScpSession,
ssd_aid: &[u8],
cascade: DeleteCascade,
) -> Result<DeleteObjectReport, ScllError>
where
B: Scp02Backend + Scp03Backend,
{
let cascade_flag = matches!(cascade, DeleteCascade::Cascade | DeleteCascade::Always);
let capdu = delete_object(ssd_aid, cascade_flag)?;
let (_d, sw) = session::transmit_in_session(t, backend, parent_session, &capdu)?;
match sw {
SW_OK => {}
SW_REF_NOT_FOUND => return Err(ScllError::TargetNoLongerExists),
SW_CONDITIONS if !cascade_flag => return Err(ScllError::SsdHasApplets),
other => return Err(ScllError::from_general_sw(other)),
}
Ok(DeleteObjectReport {
effective: DeleteObjectParams {
target_aid: Aid::new(ssd_aid)?,
target_kind: DeleteTargetKind::Ssd,
cascade_requested: cascade,
cascade_used: cascade_flag,
instances_removed: Vec::new(),
elfs_removed: Vec::new(),
},
warnings: Vec::new(),
})
}
fn copy_bounded<const N: usize>(src: &[u8]) -> Result<Vec<u8, N>, ScllError> {
let mut v = Vec::new();
v.extend_from_slice(src)
.map_err(|()| ScllError::Build(crate::command::BuildError::Overflow))?;
Ok(v)
}