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}