mint_cli/commands/
mod.rs

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