use consortium_cfg_common::config::TEE_UUID_ENV;
use std::path::PathBuf;
use thiserror::Error;
use tokio::process::Command;
use uuid::Uuid;
use super::Compiler;
use crate::compile::env::CstiWorkspace;
use crate::compile::utils::{
binary_name, elf_path, inject_csti, resolve_cargo, run_command, verify_elf,
};
use crate::progress::BuildReporter;
pub const OPTEE_TARGET: &str = "aarch64-unknown-linux-gnu";
#[derive(Error, Debug)]
pub enum OpteeCompileError {
#[error("TA_DEV_KIT_DIR does not exist: {0:?}")]
TaDevKitNotFound(PathBuf),
#[error("OP-TEE signer not found at: {0:?}")]
SignerNotFound(PathBuf),
#[error("TA signing key not found at: {0:?}")]
SigningKeyNotFound(PathBuf),
#[error("Python interpreter not found on PATH (tried: python, python3)")]
PythonNotFound,
#[error("Cargo failed: {0}")]
BuildFailed(String),
#[error("ELF not found at expected path: {0:?}")]
ElfNotFound(PathBuf),
#[error("OP-TEE signing failed: {0}")]
SigningFailed(String),
#[error("Signed TA not found after signing: {0:?}")]
SignedTaNotFound(PathBuf),
#[error("{0}")]
UtilityError(String),
}
pub struct OpteeConfiguration {
pub path: PathBuf,
pub release: bool,
pub offline: bool,
pub linker: Option<String>,
pub ta_dev_kit_dir: PathBuf,
pub optee_signer: PathBuf,
pub ta_uuid: Uuid,
pub sign: bool,
pub workspace: Option<CstiWorkspace>,
}
pub struct OpteeCompiler;
impl OpteeCompiler {
pub async fn build_with_reporter(
config: OpteeConfiguration,
reporter: Option<&dyn BuildReporter>,
) -> Result<PathBuf, OpteeCompileError> {
Self::cargo(&config, "build", reporter).await?;
let elf = Self::output_path(&config).await?;
if !elf.exists() {
return Err(OpteeCompileError::ElfNotFound(elf));
}
verify_elf(&elf)
.await
.map_err(OpteeCompileError::UtilityError)?;
if !config.sign {
return Ok(elf);
}
if !config.optee_signer.exists() {
return Err(OpteeCompileError::SignerNotFound(config.optee_signer));
}
let ta_key = config.ta_dev_kit_dir.join("keys").join("default_ta.pem");
if !ta_key.exists() {
return Err(OpteeCompileError::SigningKeyNotFound(ta_key));
}
let python = resolve_python().await?;
let ta = elf
.parent()
.ok_or_else(|| {
OpteeCompileError::UtilityError(format!(
"Could not determine output directory for ELF: {elf:?}"
))
})?
.join(format!("{}.ta", config.ta_uuid));
let mut sign_cmd = Command::new(&python);
sign_cmd
.arg(&config.optee_signer)
.arg("--uuid")
.arg(config.ta_uuid.to_string())
.arg("--key")
.arg(&ta_key)
.arg("--in")
.arg(&elf)
.arg("--out")
.arg(&ta)
.env("TA_DEV_KIT_DIR", &config.ta_dev_kit_dir);
let status = run_command(&mut sign_cmd, reporter).await.map_err(|e| {
OpteeCompileError::SigningFailed(format!(
"Failed to execute OP-TEE signer via {python}: {e}"
))
})?;
if !status.success() {
return Err(OpteeCompileError::SigningFailed(format!(
"OP-TEE signing failed with status: {status}"
)));
}
if !ta.exists() {
return Err(OpteeCompileError::SignedTaNotFound(ta));
}
Ok(ta)
}
pub async fn check_with_reporter(
config: &OpteeConfiguration,
reporter: Option<&dyn BuildReporter>,
) -> Result<(), OpteeCompileError> {
Self::cargo(config, "check", reporter).await
}
async fn cargo(
config: &OpteeConfiguration,
subcommand: &str,
reporter: Option<&dyn BuildReporter>,
) -> Result<(), OpteeCompileError> {
if !config.ta_dev_kit_dir.exists() {
return Err(OpteeCompileError::TaDevKitNotFound(
config.ta_dev_kit_dir.clone(),
));
}
let cargo = resolve_cargo()
.await
.map_err(OpteeCompileError::UtilityError)?;
let bin = binary_name(&config.path)
.await
.map_err(OpteeCompileError::UtilityError)?;
let mut cmd = Command::new(&cargo);
cmd.arg(subcommand)
.arg("--bin")
.arg(&bin)
.arg("--features")
.arg("ta")
.arg("--target")
.arg(OPTEE_TARGET)
.current_dir(&config.path)
.env("TA_DEV_KIT_DIR", &config.ta_dev_kit_dir)
.env(TEE_UUID_ENV, config.ta_uuid.to_string())
.env("RUSTFLAGS", "-C panic=abort");
inject_csti(&mut cmd, config.workspace.as_ref());
if config.release {
cmd.arg("--release");
}
if config.offline {
cmd.arg("--offline");
}
if let Some(ref linker) = config.linker {
cmd.arg("--config")
.arg(format!("{OPTEE_TARGET}.linker={linker}"));
}
let status = run_command(&mut cmd, reporter)
.await
.map_err(|e| OpteeCompileError::BuildFailed(e.to_string()))?;
if !status.success() {
return Err(OpteeCompileError::BuildFailed(format!(
"cargo {subcommand} failed with status: {status}"
)));
}
Ok(())
}
}
impl Compiler for OpteeCompiler {
type Configuration = OpteeConfiguration;
type Error = OpteeCompileError;
type Output = PathBuf;
async fn build(config: Self::Configuration) -> Result<Self::Output, Self::Error> {
Self::build_with_reporter(config, None).await
}
async fn output_path(config: &Self::Configuration) -> Result<PathBuf, Self::Error> {
elf_path(
&config.path,
config.workspace.as_ref(),
OPTEE_TARGET,
config.release,
)
.await
.map_err(OpteeCompileError::UtilityError)
}
}
pub struct OpteeBuilder {
pub path: PathBuf,
pub release: bool,
pub linker: Option<String>,
}
pub struct OpteeEnvironment {
pub ta_dev_kit_dir: PathBuf,
pub optee_signer: PathBuf,
pub ta_uuid: Uuid,
}
impl OpteeBuilder {
pub async fn build(&self, env: &OpteeEnvironment) -> Result<PathBuf, String> {
OpteeCompiler::build(OpteeConfiguration {
path: self.path.clone(),
release: self.release,
offline: false,
linker: self.linker.clone(),
ta_dev_kit_dir: env.ta_dev_kit_dir.clone(),
optee_signer: env.optee_signer.clone(),
ta_uuid: env.ta_uuid,
sign: true,
workspace: None,
})
.await
.map_err(|e| e.to_string())
}
}
async fn resolve_python() -> Result<String, OpteeCompileError> {
for candidate in ["python", "python3"] {
if let Ok(status) = Command::new(candidate).arg("--version").status().await
&& status.success()
{
return Ok(candidate.to_string());
}
}
Err(OpteeCompileError::PythonNotFound)
}