loadstone 1.0.0

Portable secure bootloader for Cortex-M MCUs
#![feature(bool_to_option)]

use anyhow::Result;
use loadstone_config::{codegen::generate_modules, security::SecurityMode, Configuration};
use std::fs;

fn configure_runner(target: &str) {
    println!("cargo:rerun-if-changed={}", RUNNER_TARGET_FILE);

    const RUNNER_TARGET_FILE: &str = ".cargo/.runner-target";
    fs::write(RUNNER_TARGET_FILE, target).unwrap();
}

fn main() -> Result<()> { process_configuration_file() }

fn process_configuration_file() -> Result<()> {
    println!("cargo:rerun-if-env-changed=LOADSTONE_CONFIG");

    let configuration: Configuration = if let Ok(config) = std::env::var("LOADSTONE_CONFIG") {
        if config.is_empty() {
            return Ok(()); // Assuming tests
        } else {
            ron::from_str(&config)?
        }
    } else {
        panic!(
            "\r\n\r\nBuilding Loadstone requires you supply a configuration file, \
                embedded in the `LOADSTONE_CONFIG` environment variable. \r\nTry again with \
                'LOADSTONE_CONFIG=`cat my_config.ron` cargo... \r\nIf you're just looking \
                to run unit tests, or to build a port that does not require any code \
                generation (manual port), supply an empty string:
                'LOADSTONE_CONFIG=\"\" cargo...`\r\n\r\n"
        )
    };

    validate_feature_flags_against_configuration(&configuration);
    generate_modules(env!("CARGO_MANIFEST_DIR"), &configuration)?;
    configure_runner(&configuration.port.to_string());

    Ok(())
}

fn validate_feature_flags_against_configuration(configuration: &Configuration) {
    let supplied_flags: Vec<_> = std::env::vars()
        .filter_map(|(k, _)| {
            k.starts_with("CARGO_FEATURE_")
                .then_some(k.strip_prefix("CARGO_FEATURE_")?.to_owned().to_lowercase())
        })
        .collect();

    let missing_flags: Vec<_> = configuration
        .required_feature_flags()
        .map(|s| s.replace("-", "_"))
        .filter(|f| !&supplied_flags.contains(&(*f).to_owned()))
        .collect();

    if configuration.security_configuration.security_mode != SecurityMode::P256ECDSA
        && supplied_flags.contains(&"ecdsa_verify".to_owned())
    {
        panic!("Configuration mismatch. Configuration file does not specify ECDSA security mode, \
                but the `ecdsa-verify` flag was supplied. Try again without `ecdsa-verify` for CRC mode.");
    }

    if !missing_flags.is_empty() {
        panic!(
            "\r\n\r\nThe configuration file requires flags that haven't been supplied. \
            Please build again with `--features={}`\r\n\r\n",
            missing_flags.join(","),
        );
    }
}