mint_cli/commands/
mod.rs

1pub mod stats;
2mod writer;
3
4use crate::args::Args;
5use crate::data::DataSource;
6use crate::error::NvmError;
7use crate::layout;
8use crate::layout::args::BlockNames;
9use crate::layout::block::Config;
10use crate::layout::errors::LayoutError;
11use crate::layout::settings::Endianness;
12use crate::output;
13use crate::output::DataRange;
14use crate::output::errors::OutputError;
15use rayon::prelude::*;
16use stats::{BlockStat, BuildStats};
17use std::collections::{HashMap, HashSet};
18use std::time::Instant;
19use writer::write_output;
20
21#[derive(Debug, Clone)]
22struct ResolvedBlock {
23    name: String,
24    file: String,
25}
26
27struct BlockBuildResult {
28    block_names: BlockNames,
29    data_range: DataRange,
30    stat: BlockStat,
31}
32
33fn resolve_blocks(
34    block_args: &[BlockNames],
35) -> Result<(Vec<ResolvedBlock>, HashMap<String, Config>), LayoutError> {
36    let unique_files: HashSet<String> = block_args.iter().map(|b| b.file.clone()).collect();
37
38    let layouts: Result<HashMap<String, Config>, LayoutError> = unique_files
39        .par_iter()
40        .map(|file| layout::load_layout(file).map(|cfg| (file.clone(), cfg)))
41        .collect();
42
43    let layouts = layouts?;
44
45    let mut resolved = Vec::new();
46    for arg in block_args {
47        if arg.name.is_empty() {
48            let layout = &layouts[&arg.file];
49            for block_name in layout.blocks.keys() {
50                resolved.push(ResolvedBlock {
51                    name: block_name.clone(),
52                    file: arg.file.clone(),
53                });
54            }
55        } else {
56            resolved.push(ResolvedBlock {
57                name: arg.name.clone(),
58                file: arg.file.clone(),
59            });
60        }
61    }
62
63    let mut seen = HashSet::new();
64    let deduplicated: Vec<ResolvedBlock> = resolved
65        .into_iter()
66        .filter(|b| seen.insert((b.file.clone(), b.name.clone())))
67        .collect();
68
69    Ok((deduplicated, layouts))
70}
71
72fn build_bytestreams(
73    blocks: &[ResolvedBlock],
74    layouts: &HashMap<String, Config>,
75    data_source: Option<&dyn DataSource>,
76    strict: bool,
77) -> Result<Vec<BlockBuildResult>, NvmError> {
78    blocks
79        .par_iter()
80        .map(|resolved| build_single_bytestream(resolved, layouts, data_source, strict))
81        .collect()
82}
83
84fn build_single_bytestream(
85    resolved: &ResolvedBlock,
86    layouts: &HashMap<String, Config>,
87    data_source: Option<&dyn DataSource>,
88    strict: bool,
89) -> Result<BlockBuildResult, NvmError> {
90    let result = (|| {
91        let layout = &layouts[&resolved.file];
92        let block = &layout.blocks[&resolved.name];
93
94        let (bytestream, padding_bytes) =
95            block.build_bytestream(data_source, &layout.settings, strict)?;
96
97        let data_range = output::bytestream_to_datarange(
98            bytestream,
99            &block.header,
100            &layout.settings,
101            layout.settings.byte_swap,
102            layout.settings.pad_to_end,
103            padding_bytes,
104        )?;
105
106        let crc_value = extract_crc_value(&data_range.crc_bytestream, &layout.settings.endianness);
107
108        let stat = BlockStat {
109            name: resolved.name.clone(),
110            start_address: data_range.start_address,
111            allocated_size: data_range.allocated_size,
112            used_size: data_range.used_size,
113            crc_value,
114        };
115
116        Ok(BlockBuildResult {
117            block_names: BlockNames {
118                name: resolved.name.clone(),
119                file: resolved.file.clone(),
120            },
121            data_range,
122            stat,
123        })
124    })();
125
126    result.map_err(|e| NvmError::InBlock {
127        block_name: resolved.name.clone(),
128        layout_file: resolved.file.clone(),
129        source: Box::new(e),
130    })
131}
132
133fn extract_crc_value(crc_bytestream: &[u8], endianness: &Endianness) -> Option<u32> {
134    if crc_bytestream.len() < 4 {
135        return None;
136    }
137    let bytes: [u8; 4] = crc_bytestream[..4].try_into().ok()?;
138    Some(match endianness {
139        Endianness::Big => u32::from_be_bytes(bytes),
140        Endianness::Little => u32::from_le_bytes(bytes),
141    })
142}
143
144fn output_separate_blocks(
145    results: Vec<BlockBuildResult>,
146    args: &Args,
147) -> Result<BuildStats, NvmError> {
148    let block_stats: Result<Vec<BlockStat>, NvmError> = results
149        .par_iter()
150        .map(|result| {
151            let hex_string = output::emit_hex(
152                std::slice::from_ref(&result.data_range),
153                args.output.record_width as usize,
154                args.output.format,
155            )?;
156
157            write_output(&args.output, &result.block_names.name, &hex_string)?;
158            Ok(result.stat.clone())
159        })
160        .collect();
161
162    let block_stats = block_stats?;
163
164    let mut stats = BuildStats::new();
165    for stat in block_stats {
166        stats.add_block(stat);
167    }
168
169    Ok(stats)
170}
171
172fn output_combined_file(
173    results: Vec<BlockBuildResult>,
174    layouts: &HashMap<String, Config>,
175    args: &Args,
176) -> Result<BuildStats, NvmError> {
177    let mut stats = BuildStats::new();
178    let mut ranges = Vec::new();
179    let mut block_ranges = Vec::new();
180
181    for result in results {
182        let layout = &layouts[&result.block_names.file];
183        let block = &layout.blocks[&result.block_names.name];
184
185        let start = block
186            .header
187            .start_address
188            .checked_add(layout.settings.virtual_offset)
189            .ok_or(LayoutError::InvalidBlockArgument(
190                "start_address + virtual_offset overflow".into(),
191            ))?;
192
193        let end =
194            start
195                .checked_add(block.header.length)
196                .ok_or(LayoutError::InvalidBlockArgument(
197                    "start + length overflow".into(),
198                ))?;
199
200        stats.add_block(result.stat);
201        ranges.push(result.data_range);
202        block_ranges.push((result.block_names.name.clone(), start, end));
203    }
204
205    check_overlaps(&block_ranges)?;
206
207    let hex_string = output::emit_hex(
208        &ranges,
209        args.output.record_width as usize,
210        args.output.format,
211    )?;
212
213    write_output(&args.output, "combined", &hex_string)?;
214
215    Ok(stats)
216}
217
218fn check_overlaps(block_ranges: &[(String, u32, u32)]) -> Result<(), NvmError> {
219    for i in 0..block_ranges.len() {
220        for j in (i + 1)..block_ranges.len() {
221            let (ref name_a, a_start, a_end) = block_ranges[i];
222            let (ref name_b, b_start, b_end) = block_ranges[j];
223
224            let overlap_start = a_start.max(b_start);
225            let overlap_end = a_end.min(b_end);
226
227            if overlap_start < overlap_end {
228                let overlap_size = overlap_end - overlap_start;
229                let msg = format!(
230                    "Block '{}' (0x{:08X}-0x{:08X}) overlaps with block '{}' (0x{:08X}-0x{:08X}). Overlap: 0x{:08X}-0x{:08X} ({} bytes)",
231                    name_a,
232                    a_start,
233                    a_end - 1,
234                    name_b,
235                    b_start,
236                    b_end - 1,
237                    overlap_start,
238                    overlap_end - 1,
239                    overlap_size
240                );
241                return Err(OutputError::BlockOverlapError(msg).into());
242            }
243        }
244    }
245    Ok(())
246}
247
248pub fn build(args: &Args, data_source: Option<&dyn DataSource>) -> Result<BuildStats, NvmError> {
249    let start_time = Instant::now();
250
251    let (resolved_blocks, layouts) = resolve_blocks(&args.layout.blocks)?;
252    let results = build_bytestreams(&resolved_blocks, &layouts, data_source, args.layout.strict)?;
253
254    let mut stats = if args.output.combined {
255        output_combined_file(results, &layouts, args)?
256    } else {
257        output_separate_blocks(results, args)?
258    };
259
260    stats.total_duration = start_time.elapsed();
261    Ok(stats)
262}