sftool 0.2.2

SiFli SoC serial utility command-line tool
use anyhow::{Context, Result, anyhow, bail};
use std::io::Write;

use sftool_lib::ChipType;

use crate::config::SfToolConfig;
use crate::stub_config_spec::StubConfigSpec;

pub fn load_stub_config_spec(path: &str) -> Result<StubConfigSpec> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("Failed to read stub config file '{}'", path))?;
    let spec: StubConfigSpec =
        serde_json::from_str(&content).with_context(|| "Failed to parse stub config JSON")?;
    Ok(spec)
}

pub fn execute_stub_write(files: &[String], spec: &StubConfigSpec) -> Result<()> {
    let config = spec.to_stub_config().context("Invalid stub config")?;
    for file in files {
        sftool_lib::stub_config::write_stub_config_to_file(file, &config)
            .with_context(|| format!("Failed to write stub config to '{}'", file))?;
    }
    Ok(())
}

pub fn execute_stub_clear(files: &[String]) -> Result<()> {
    for file in files {
        sftool_lib::stub_config::clear_stub_config_in_file(file)
            .with_context(|| format!("Failed to clear stub config in '{}'", file))?;
    }
    Ok(())
}

pub fn execute_stub_read(files: &[String], output: Option<&str>) -> Result<()> {
    if let Some(output_path) = output {
        if files.len() != 1 {
            bail!("--output requires exactly one input file");
        }
        let config = sftool_lib::stub_config::read_stub_config_from_file(&files[0])
            .with_context(|| format!("Failed to read stub config from '{}'", files[0]))?;
        let spec = StubConfigSpec::from_stub_config(&config);
        let json = serde_json::to_string_pretty(&spec)?;
        std::fs::write(output_path, json)
            .with_context(|| format!("Failed to write stub config to '{}'", output_path))?;
        return Ok(());
    }

    if files.len() == 1 {
        let config = sftool_lib::stub_config::read_stub_config_from_file(&files[0])
            .with_context(|| format!("Failed to read stub config from '{}'", files[0]))?;
        let spec = StubConfigSpec::from_stub_config(&config);
        println!("{}", serde_json::to_string_pretty(&spec)?);
        return Ok(());
    }

    #[derive(serde::Serialize)]
    struct StubReadOutput<'a> {
        file: &'a str,
        config: StubConfigSpec,
    }

    let mut output_items = Vec::new();
    for file in files {
        let config = sftool_lib::stub_config::read_stub_config_from_file(file)
            .with_context(|| format!("Failed to read stub config from '{}'", file))?;
        let spec = StubConfigSpec::from_stub_config(&config);
        output_items.push(StubReadOutput { file, config: spec });
    }

    println!("{}", serde_json::to_string_pretty(&output_items)?);
    Ok(())
}

pub fn execute_stub_config_command(config: &SfToolConfig) -> Result<()> {
    let stub = config
        .stub
        .as_ref()
        .ok_or_else(|| anyhow!("No stub command found in config file"))?;

    if let Some(ref stub_write) = stub.write {
        execute_stub_write(&stub_write.files, &stub_write.config)
    } else if let Some(ref stub_clear) = stub.clear {
        execute_stub_clear(&stub_clear.files)
    } else if let Some(ref stub_read) = stub.read {
        execute_stub_read(&stub_read.files, stub_read.output.as_deref())
    } else {
        bail!("Stub command must contain exactly one of write, clear, or read")
    }
}

pub fn chip_key(chip_type: &ChipType) -> &'static str {
    match chip_type {
        ChipType::SF32LB52 => "sf32lb52",
        ChipType::SF32LB55 => "sf32lb55",
        ChipType::SF32LB56 => "sf32lb56",
        ChipType::SF32LB58 => "sf32lb58",
    }
}

pub fn prepare_stub_path(
    stub_config_json: Option<&str>,
    chip_type: &ChipType,
    memory_type: &str,
    stub_path: Option<String>,
) -> Result<(Option<String>, Option<tempfile::NamedTempFile>)> {
    let config_path = match stub_config_json {
        Some(path) => path,
        None => return Ok((stub_path, None)),
    };

    let spec = load_stub_config_spec(config_path)?;
    let config = spec.to_stub_config().context("Invalid stub config")?;

    let mut data =
        sftool_lib::load_stub_bytes(stub_path.as_deref(), chip_type.clone(), memory_type)
            .context("Failed to load base stub image")?;

    sftool_lib::stub_config::write_stub_config_to_bytes(&mut data, &config)
        .context("Failed to apply stub config")?;

    let mut temp_file = tempfile::Builder::new()
        .prefix(&format!(
            "sftool_stub_{}_{}_",
            chip_key(chip_type),
            memory_type
        ))
        .suffix(".bin")
        .tempfile()
        .context("Failed to create temp stub file")?;
    temp_file
        .write_all(&data)
        .context("Failed to write stub into temp file")?;

    let path = temp_file.path().to_string_lossy().to_string();
    Ok((Some(path), Some(temp_file)))
}