use crate::{
AlignOptions, ChecksumAlgorithm, FillOptions, MergeMode, MergeOptions, RemapOptions, Segment,
SwapMode, parse_log_commands,
};
use hexy_core::ops::{LogCommand, LogCommandKind};
use super::error::{CliError, ExecuteOutput};
use super::io::{
FsProvider, ReadProvider, load_binary_input, load_hex_ascii_input, load_input,
load_intel_hex_16bit_input, validate_porsche_output_args, write_output_for_args,
};
use super::signature::{
apply_data_processing, apply_signature_verification, is_supported_data_processing_method,
is_supported_signature_verify_method,
};
use super::types::{Args, ChecksumParams, ChecksumTarget, OutputFormat, ParseArgError};
use std::time::{SystemTime, UNIX_EPOCH};
impl Args {
fn wrap_error<T, E: std::fmt::Display>(
&self,
opt: &str,
res: Result<T, E>,
) -> Result<T, CliError> {
res.map_err(|e| CliError::Other(format!("{opt}: {e}")))
}
fn validate_supported_features(&self) -> Result<(), CliError> {
if !self.merge_transparent.is_empty() && !self.merge_opaque.is_empty() {
return Err(CliError::Unsupported(
"cannot combine /MT and /MO in one command".into(),
));
}
if self.s12_map && self.s12x_map {
return Err(CliError::Unsupported(
"cannot combine /S12MAP and /S12XMAP".into(),
));
}
if self.s08_map && (self.s12_map || self.s12x_map) {
return Err(CliError::Unsupported(
"cannot combine /S08MAP with /S12MAP or /S12XMAP".into(),
));
}
if self.remap.is_some() && (self.s12_map || self.s12x_map || self.s08_map) {
return Err(CliError::Unsupported(
"cannot combine /REMAP with /S08MAP, /S12MAP, or /S12XMAP".into(),
));
}
if self.postbuild.is_some() {
return Err(CliError::Unsupported(
"postbuild (/PB) is not supported yet".into(),
));
}
if let Some(ref params) = self.data_processing
&& !is_supported_data_processing_method(params.method)
{
return Err(CliError::Unsupported(format!(
"data processing (/DP{}) is not supported yet",
params.method
)));
}
if let Some(ref params) = self.signature_verify
&& !is_supported_signature_verify_method(params.method)
{
return Err(CliError::Unsupported(format!(
"signature verification (/SV{}) is not supported yet",
params.method
)));
}
if let (Some(dp), Some(sv)) = (&self.data_processing, &self.signature_verify)
&& dp.placement.is_some()
{
return Err(CliError::Unsupported(format!(
"placed data processing (/DP{}) cannot be combined with signature verification (/SV{}) in one command",
dp.method, sv.method
)));
}
if self.import_binary.is_some() && self.import_hex_ascii.is_some() {
return Err(CliError::Unsupported(
"binary import (/IN) cannot be combined with HEX ASCII import (/IA)".into(),
));
}
if self.import_i16.is_some()
&& (self.import_binary.is_some() || self.import_hex_ascii.is_some())
{
return Err(CliError::Unsupported(
"16-bit Intel HEX import (/II2) cannot be combined with /IN or /IA".into(),
));
}
if (self.import_binary.is_some() || self.import_i16.is_some()) && self.input_file.is_some()
{
return Err(CliError::Unsupported(
"explicit import (/IN, /II2) cannot be combined with input file".into(),
));
}
if self.checksum.is_some() && !self.checksum_multi.is_empty() {
return Err(CliError::Unsupported(
"cannot combine /CS* with /CSM* in one command".into(),
));
}
Ok(())
}
pub fn execute(&self) -> Result<ExecuteOutput, CliError> {
let provider = FsProvider;
self.execute_with_provider(&provider)
}
pub(super) fn execute_with_provider<P: ReadProvider>(
&self,
provider: &P,
) -> Result<ExecuteOutput, CliError> {
self.validate_supported_features()?;
validate_porsche_output_args(self)?;
let mut hexfile = self.load_hexfile(provider)?;
self.execute_operations(&mut hexfile, provider)?;
let is_porsche_output = matches!(self.output_format, Some(OutputFormat::Porsche));
let checksum_bytes = if is_porsche_output {
None
} else {
self.apply_checksums(&mut hexfile)?
};
let _signature_bytes = self.apply_data_processing(&mut hexfile)?;
self.apply_signature_verification(&hexfile)?;
self.write_outputs(&hexfile, provider)?;
Ok(ExecuteOutput { checksum_bytes })
}
fn execute_operations<P: ReadProvider>(
&self,
hexfile: &mut crate::HexFile,
provider: &P,
) -> Result<(), CliError> {
if self.s12_map {
self.wrap_error("/S12MAP", hexfile.map_star12())?;
}
if self.s12x_map {
self.wrap_error("/S12XMAP", hexfile.map_star12x())?;
}
if self.s08_map {
self.wrap_error("/S08MAP", hexfile.map_star08())?;
}
if let Some(ref remap) = self.remap {
let options = RemapOptions {
start: remap.start,
end: remap.end,
linear: remap.linear,
size: remap.size,
inc: remap.inc,
};
self.wrap_error("/REMAP", hexfile.remap(&options))?;
}
for op in &self.dspic_expand {
self.wrap_error("/CDSPX", hexfile.dspic_expand(op.range, op.target))?;
}
for op in &self.dspic_shrink {
self.wrap_error("/CDSPS", hexfile.dspic_shrink(op.range, op.target))?;
}
for range in &self.dspic_clear_ghost {
self.wrap_error("/CDSPG", hexfile.dspic_clear_ghost(*range))?;
}
if self.fill_pattern_set {
let options = FillOptions {
pattern: self.fill_pattern.clone(),
overwrite: false,
};
self.wrap_error("/FR", hexfile.fill_ranges(&self.fill_ranges, &options))?;
} else {
for range in &self.fill_ranges {
let data = self.wrap_error("/FR", random_fill_bytes(*range))?;
hexfile.prepend_segment(Segment::new(range.start(), data));
}
}
hexfile.cut_ranges(&self.cut_ranges);
for merge in &self.merge_transparent {
let other = load_input(provider, &merge.file)?;
let options = MergeOptions {
mode: MergeMode::Preserve,
offset: merge.offset.unwrap_or(0),
range: merge.range,
};
self.wrap_error("/MT", hexfile.merge(&other, &options))?;
}
for merge in &self.merge_opaque {
let other = load_input(provider, &merge.file)?;
let options = MergeOptions {
mode: MergeMode::Overwrite,
offset: merge.offset.unwrap_or(0),
range: merge.range,
};
self.wrap_error("/MO", hexfile.merge(&other, &options))?;
}
if self.address_range_empty_output {
hexfile.filter_ranges(&[]);
} else if !self.address_range.is_empty() {
hexfile.filter_ranges(&self.address_range);
}
if let Some(ref path) = self.log_file {
let content = provider
.read_string(path)
.map_err(|e| CliError::Other(format!("/L: {e}")))?;
let commands =
parse_log_commands(&content).map_err(|e| CliError::Other(format!("/L: {e}")))?;
execute_compat_log_commands(hexfile, &commands)?;
}
if self.fill_all {
self.wrap_error(
"/FA",
hexfile.fill_gaps_bounded(self.align_fill, crate::DEFAULT_DENSE_SPAN_LIMIT),
)?;
}
if let Some(alignment) = self.align_address {
let options = AlignOptions {
alignment,
fill_byte: self.align_fill,
align_length: false,
};
self.wrap_error("/AD/AL", hexfile.align(&options))?;
if self.align_length {
let options = AlignOptions {
alignment,
fill_byte: self.align_fill,
align_length: true,
};
self.wrap_error("/AD/AL", hexfile.align(&options))?;
}
}
if let Some(size) = self.split_block_size {
hexfile.split(size);
}
if self.swap_word {
self.wrap_error("/SWAPWORD", hexfile.swap_bytes(SwapMode::Word))?;
}
if self.swap_long {
self.wrap_error("/SWAPLONG", hexfile.swap_bytes(SwapMode::DWord))?;
}
Ok(())
}
fn load_hexfile<P: ReadProvider>(&self, provider: &P) -> Result<crate::HexFile, CliError> {
if let Some(ref import) = self.import_binary {
return load_binary_input(provider, &import.file, import.offset);
}
if let Some(ref import) = self.import_hex_ascii {
let ascii = load_hex_ascii_input(provider, &import.file, import.offset)?;
if let Some(ref path) = self.input_file {
let mut base = load_input(provider, path)?;
if super::io::hexfiles_overlap(&base, &ascii) {
if !self.silent {
eprintln!("Warning: /IA overlaps input file; ignoring input file");
}
return Ok(ascii);
}
for segment in ascii.segments() {
base.append_segment(segment.clone());
}
return Ok(base);
}
return Ok(ascii);
}
if let Some(ref import) = self.import_i16 {
return load_intel_hex_16bit_input(provider, import);
}
if let Some(ref path) = self.input_file {
return load_input(provider, path);
}
if self.log_file.is_some()
|| !self.merge_transparent.is_empty()
|| !self.merge_opaque.is_empty()
{
return Ok(crate::HexFile::new());
}
Err(ParseArgError::MissingInputFile.into())
}
fn apply_checksums(&self, hexfile: &mut crate::HexFile) -> Result<Option<Vec<u8>>, CliError> {
let mut legacy_bytes = None;
if let Some(cs_params) = self.checksum.as_ref() {
legacy_bytes = Some(self.run_checksum(hexfile, cs_params, false)?);
}
for cs_params in &self.checksum_multi {
self.run_checksum(hexfile, cs_params, true)?;
}
Ok(legacy_bytes)
}
fn run_checksum(
&self,
hexfile: &mut crate::HexFile,
cs_params: &ChecksumParams,
is_multi: bool,
) -> Result<Vec<u8>, CliError> {
let opt_base = if is_multi {
if cs_params.little_endian {
"/CSMR"
} else {
"/CSM"
}
} else if cs_params.little_endian {
"/CSR"
} else {
"/CS"
};
let opt = format!("{opt_base}{}", cs_params.algorithm);
let algorithm =
self.wrap_error(&opt, ChecksumAlgorithm::from_index(cs_params.algorithm))?;
let forced_range = cs_params
.forced_range
.as_ref()
.map(|forced| crate::ForcedRange {
range: forced.range,
pattern: forced.pattern.clone(),
});
let options = crate::ChecksumOptions {
algorithm,
range: cs_params.range,
little_endian_output: cs_params.little_endian,
forced_range,
exclude_ranges: cs_params.exclude_ranges.clone(),
target_exclude: None,
};
let target = self.resolve_checksum_target(hexfile, &cs_params.target);
let result = self.wrap_error(&opt, hexfile.checksum(&options, &target))?;
if let ChecksumTarget::File(path) = &cs_params.target {
let formatted = result
.iter()
.map(|b| format!("0x{:02X}", b))
.collect::<Vec<_>>()
.join(", ");
self.wrap_error(&opt, std::fs::write(path, formatted))?;
}
Ok(result)
}
fn resolve_checksum_target(
&self,
hexfile: &crate::HexFile,
target: &ChecksumTarget,
) -> crate::ChecksumTarget {
match target {
ChecksumTarget::None => crate::ChecksumTarget::None,
ChecksumTarget::Address(addr) => crate::ChecksumTarget::Address(*addr),
ChecksumTarget::Append => crate::ChecksumTarget::Append,
ChecksumTarget::Begin => {
if let Some(start) = hexfile.min_address() {
crate::ChecksumTarget::Address(start)
} else {
crate::ChecksumTarget::Append
}
}
ChecksumTarget::Prepend => crate::ChecksumTarget::Prepend,
ChecksumTarget::OverwriteEnd => crate::ChecksumTarget::OverwriteEnd,
ChecksumTarget::File(path) => crate::ChecksumTarget::File(path.clone()),
}
}
fn apply_data_processing(
&self,
hexfile: &mut crate::HexFile,
) -> Result<Option<Vec<u8>>, CliError> {
let Some(ref params) = self.data_processing else {
return Ok(None);
};
apply_data_processing(hexfile, params)
}
fn apply_signature_verification(&self, hexfile: &crate::HexFile) -> Result<(), CliError> {
let Some(ref params) = self.signature_verify else {
return Ok(());
};
apply_signature_verification(hexfile, params)
}
fn write_outputs<P: ReadProvider>(
&self,
hexfile: &crate::HexFile,
provider: &P,
) -> Result<(), CliError> {
write_output_for_args(self, hexfile, provider)
}
}
fn execute_compat_log_commands(
hexfile: &mut crate::HexFile,
commands: &[LogCommand],
) -> Result<(), CliError> {
for command in commands {
match &command.kind {
LogCommandKind::FileOpen(path) => {
return Err(CliError::Unsupported(format!(
"/L: FileOpen is not supported for command-line export (line {}): {}",
command.line,
path.display()
)));
}
LogCommandKind::FileClose | LogCommandKind::FileNew => {
*hexfile = crate::HexFile::new();
}
}
}
Ok(())
}
fn random_fill_bytes(range: crate::AddressRange) -> Result<Vec<u8>, crate::OpsError> {
let len = materialized_range_len(range, "random fill range")?;
if len == 0 {
return Ok(Vec::new());
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let mut state = {
let seed = now ^ ((range.start() as u64) << 32) ^ range.length();
if seed == 0 { 0x9E3779B97F4A7C15 } else { seed }
};
let mut out = Vec::with_capacity(len);
for _ in 0..len {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
out.push((state >> 32) as u8);
}
Ok(out)
}
fn materialized_range_len(
range: crate::AddressRange,
context: &str,
) -> Result<usize, crate::OpsError> {
if range.extends_past_address_space() {
return Err(crate::OpsError::AddressOverflow(format!(
"{context} {:#X}-{:#X} requests end {:#X} beyond u32 address space",
range.start(),
range.end(),
range.requested_end()
)));
}
if range.length() > u32::MAX as u64 {
return Err(crate::OpsError::AddressOverflow(format!(
"{context} length {} cannot be materialized",
range.length()
)));
}
usize::try_from(range.length()).map_err(|_| {
crate::OpsError::AddressOverflow(format!(
"{context} length {} exceeds usize",
range.length()
))
})
}