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::errors::OutputError;
14use crate::output::DataRange;
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) -> u32 {
134    match endianness {
135        Endianness::Big => u32::from_be_bytes([
136            crc_bytestream[0],
137            crc_bytestream[1],
138            crc_bytestream[2],
139            crc_bytestream[3],
140        ]),
141        Endianness::Little => u32::from_le_bytes([
142            crc_bytestream[0],
143            crc_bytestream[1],
144            crc_bytestream[2],
145            crc_bytestream[3],
146        ]),
147    }
148}
149
150fn output_separate_blocks(
151    results: Vec<BlockBuildResult>,
152    args: &Args,
153) -> Result<BuildStats, NvmError> {
154    let block_stats: Result<Vec<BlockStat>, NvmError> = results
155        .par_iter()
156        .map(|result| {
157            let hex_string = output::emit_hex(
158                std::slice::from_ref(&result.data_range),
159                args.output.record_width as usize,
160                args.output.format,
161            )?;
162
163            write_output(&args.output, &result.block_names.name, &hex_string)?;
164            Ok(result.stat.clone())
165        })
166        .collect();
167
168    let block_stats = block_stats?;
169
170    let mut stats = BuildStats::new();
171    for stat in block_stats {
172        stats.add_block(stat);
173    }
174
175    Ok(stats)
176}
177
178fn output_combined_file(
179    results: Vec<BlockBuildResult>,
180    layouts: &HashMap<String, Config>,
181    args: &Args,
182) -> Result<BuildStats, NvmError> {
183    let mut stats = BuildStats::new();
184    let mut ranges = Vec::new();
185    let mut block_ranges = Vec::new();
186
187    for result in results {
188        let layout = &layouts[&result.block_names.file];
189        let block = &layout.blocks[&result.block_names.name];
190
191        let start = block
192            .header
193            .start_address
194            .checked_add(layout.settings.virtual_offset)
195            .ok_or(LayoutError::InvalidBlockArgument(
196                "start_address + virtual_offset overflow".into(),
197            ))?;
198
199        let end =
200            start
201                .checked_add(block.header.length)
202                .ok_or(LayoutError::InvalidBlockArgument(
203                    "start + length overflow".into(),
204                ))?;
205
206        stats.add_block(result.stat);
207        ranges.push(result.data_range);
208        block_ranges.push((result.block_names.name.clone(), start, end));
209    }
210
211    check_overlaps(&block_ranges)?;
212
213    let hex_string = output::emit_hex(
214        &ranges,
215        args.output.record_width as usize,
216        args.output.format,
217    )?;
218
219    write_output(&args.output, "combined", &hex_string)?;
220
221    Ok(stats)
222}
223
224fn check_overlaps(block_ranges: &[(String, u32, u32)]) -> Result<(), NvmError> {
225    for i in 0..block_ranges.len() {
226        for j in (i + 1)..block_ranges.len() {
227            let (ref name_a, a_start, a_end) = block_ranges[i];
228            let (ref name_b, b_start, b_end) = block_ranges[j];
229
230            let overlap_start = a_start.max(b_start);
231            let overlap_end = a_end.min(b_end);
232
233            if overlap_start < overlap_end {
234                let overlap_size = overlap_end - overlap_start;
235                let msg = format!(
236                    "Block '{}' (0x{:08X}-0x{:08X}) overlaps with block '{}' (0x{:08X}-0x{:08X}). Overlap: 0x{:08X}-0x{:08X} ({} bytes)",
237                    name_a,
238                    a_start,
239                    a_end - 1,
240                    name_b,
241                    b_start,
242                    b_end - 1,
243                    overlap_start,
244                    overlap_end - 1,
245                    overlap_size
246                );
247                return Err(OutputError::BlockOverlapError(msg).into());
248            }
249        }
250    }
251    Ok(())
252}
253
254pub fn build(args: &Args, data_source: Option<&dyn DataSource>) -> Result<BuildStats, NvmError> {
255    let start_time = Instant::now();
256
257    let (resolved_blocks, layouts) = resolve_blocks(&args.layout.blocks)?;
258    let results = build_bytestreams(&resolved_blocks, &layouts, data_source, args.layout.strict)?;
259
260    let mut stats = if args.output.combined {
261        output_combined_file(results, &layouts, args)?
262    } else {
263        output_separate_blocks(results, args)?
264    };
265
266    stats.total_duration = start_time.elapsed();
267    Ok(stats)
268}