use serde::{Deserialize, Serialize};
use sftool_lib::{AfterOperation, BeforeOperation, ChipType};
use crate::stub_config_spec::StubConfigSpec;
pub struct Defaults;
impl Defaults {
pub const CHIP: &'static str = "SF32LB52";
pub const MEMORY: &'static str = "nor";
pub const BAUD: u32 = 1000000;
pub const BEFORE: &'static str = "default_reset";
pub const AFTER: &'static str = "soft_reset";
pub const CONNECT_ATTEMPTS: i8 = 3;
pub const COMPAT: bool = false;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HexString(pub String);
impl HexString {
pub fn to_u32(&self) -> Result<u32, String> {
if !self.0.starts_with("0x") {
return Err(format!("Invalid hex string format: {}", self.0));
}
let hex_part = &self.0[2..];
u32::from_str_radix(hex_part, 16)
.map_err(|e| format!("Failed to parse hex string '{}': {}", self.0, e))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WriteFlashFileConfig {
pub path: String,
pub address: Option<HexString>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadFlashFileConfig {
pub path: String,
pub address: HexString,
pub size: HexString,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegionItemConfig {
pub address: HexString,
pub size: HexString,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WriteFlashCommandConfig {
#[serde(default)]
pub verify: bool,
#[serde(default)]
pub erase_all: bool,
#[serde(default)]
pub no_compress: bool,
pub files: Vec<WriteFlashFileConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadFlashCommandConfig {
pub files: Vec<ReadFlashFileConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EraseFlashCommandConfig {
pub address: HexString,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EraseRegionCommandConfig {
pub regions: Vec<RegionItemConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StubWriteCommandConfig {
pub files: Vec<String>,
pub config: StubConfigSpec,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StubClearCommandConfig {
pub files: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StubReadCommandConfig {
pub files: Vec<String>,
#[serde(default)]
pub output: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StubCommandConfig {
#[serde(default)]
pub write: Option<StubWriteCommandConfig>,
#[serde(default)]
pub clear: Option<StubClearCommandConfig>,
#[serde(default)]
pub read: Option<StubReadCommandConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SfToolConfig {
#[serde(default = "default_chip")]
pub chip: String,
#[serde(default = "default_memory")]
pub memory: String,
#[serde(default)]
pub port: String,
#[serde(default = "default_baud")]
pub baud: u32,
#[serde(default = "default_before")]
pub before: String,
#[serde(default = "default_after")]
pub after: String,
#[serde(default = "default_connect_attempts")]
pub connect_attempts: i8,
#[serde(default)]
pub compat: bool,
#[serde(default)]
pub quiet: bool,
#[serde(default)]
pub stub_path: Option<String>,
pub write_flash: Option<WriteFlashCommandConfig>,
pub read_flash: Option<ReadFlashCommandConfig>,
pub erase_flash: Option<EraseFlashCommandConfig>,
pub erase_region: Option<EraseRegionCommandConfig>,
pub stub: Option<StubCommandConfig>,
}
fn default_chip() -> String {
Defaults::CHIP.to_string()
}
fn default_memory() -> String {
Defaults::MEMORY.to_string()
}
fn default_baud() -> u32 {
Defaults::BAUD
}
fn default_before() -> String {
Defaults::BEFORE.to_string()
}
fn default_after() -> String {
Defaults::AFTER.to_string()
}
fn default_connect_attempts() -> i8 {
Defaults::CONNECT_ATTEMPTS
}
impl SfToolConfig {
pub fn from_file(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let config: SfToolConfig = serde_json::from_str(&content)?;
Ok(config)
}
pub fn with_defaults() -> Self {
Self {
chip: Defaults::CHIP.to_string(), memory: Defaults::MEMORY.to_string(),
port: String::new(), baud: Defaults::BAUD,
before: Defaults::BEFORE.to_string(),
after: Defaults::AFTER.to_string(),
connect_attempts: Defaults::CONNECT_ATTEMPTS,
compat: Defaults::COMPAT,
quiet: false,
stub_path: None,
write_flash: None,
read_flash: None,
erase_flash: None,
erase_region: None,
stub: None,
}
}
pub fn parse_chip_type(&self) -> Result<ChipType, String> {
match self.chip.as_str() {
"SF32LB52" => Ok(ChipType::SF32LB52),
"SF32LB55" => Ok(ChipType::SF32LB55),
"SF32LB56" => Ok(ChipType::SF32LB56),
"SF32LB58" => Ok(ChipType::SF32LB58),
_ => Err(format!("Invalid chip type: {}", self.chip)),
}
}
pub fn parse_before(&self) -> Result<BeforeOperation, String> {
match self.before.as_str() {
"default_reset" => Ok(BeforeOperation::DefaultReset),
"no_reset" => Ok(BeforeOperation::NoReset),
"no_reset_no_sync" => Ok(BeforeOperation::NoResetNoSync),
_ => Err(format!("Invalid before operation: {}", self.before)),
}
}
pub fn parse_after(&self) -> Result<AfterOperation, String> {
match self.after.as_str() {
"no_reset" => Ok(AfterOperation::NoReset),
"soft_reset" => Ok(AfterOperation::SoftReset),
_ => Err(format!("Invalid after operation: {}", self.after)),
}
}
pub fn validate(&self) -> Result<(), String> {
let stub_sub_count = self
.stub
.as_ref()
.map(|stub| {
[
stub.write.is_some(),
stub.clear.is_some(),
stub.read.is_some(),
]
.iter()
.filter(|&&x| x)
.count()
})
.unwrap_or(0);
let stub_command = if stub_sub_count > 0 { 1 } else { 0 };
let command_count = [
self.write_flash.is_some(),
self.read_flash.is_some(),
self.erase_flash.is_some(),
self.erase_region.is_some(),
]
.iter()
.filter(|&&x| x)
.count()
+ stub_command;
if command_count != 1 {
return Err("Configuration must contain exactly one command (write_flash, read_flash, erase_flash, erase_region, or stub)".to_string());
}
if let Some(ref stub) = self.stub {
if stub_sub_count != 1 {
return Err("stub must contain exactly one of write, clear, or read".to_string());
}
if let Some(ref stub_write) = stub.write
&& stub_write.files.is_empty()
{
return Err("stub.write.files must not be empty".to_string());
}
if let Some(ref stub_clear) = stub.clear
&& stub_clear.files.is_empty()
{
return Err("stub.clear.files must not be empty".to_string());
}
if let Some(ref stub_read) = stub.read {
if stub_read.files.is_empty() {
return Err("stub.read.files must not be empty".to_string());
}
if stub_read.output.is_some() && stub_read.files.len() != 1 {
return Err("stub.read.output requires exactly one input file".to_string());
}
}
return Ok(());
}
if let Some(ref write_flash) = self.write_flash {
for file in &write_flash.files {
if let Some(ref addr) = file.address {
addr.to_u32().map_err(|e| {
format!("Invalid address in write_flash file '{}': {}", file.path, e)
})?;
}
}
}
if let Some(ref read_flash) = self.read_flash {
for file in &read_flash.files {
file.address.to_u32().map_err(|e| {
format!("Invalid address in read_flash file '{}': {}", file.path, e)
})?;
file.size.to_u32().map_err(|e| {
format!("Invalid size in read_flash file '{}': {}", file.path, e)
})?;
}
}
if let Some(ref erase_flash) = self.erase_flash {
erase_flash
.address
.to_u32()
.map_err(|e| format!("Invalid erase_flash address: {}", e))?;
}
if let Some(ref erase_region) = self.erase_region {
for region in &erase_region.regions {
region
.address
.to_u32()
.map_err(|e| format!("Invalid erase_region address: {}", e))?;
region
.size
.to_u32()
.map_err(|e| format!("Invalid erase_region size: {}", e))?;
}
}
Ok(())
}
}