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}