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}