Skip to main content

mint_core/layout/
block.rs

1use super::entry::{EntrySource, LeafEntry};
2use super::error::LayoutError;
3use super::header::Header;
4use super::scalar_type::ScalarType;
5use super::settings::{Endianness, MintConfig};
6use super::used_values::ValueSink;
7use super::value::{DataValue, ValueSource};
8use crate::data::DataSource;
9use crate::output::checksum;
10
11use indexmap::IndexMap;
12use serde::Deserialize;
13use std::collections::HashMap;
14
15/// A ref that needs to be resolved after the main traversal pass.
16struct PendingRef {
17    /// Position in the output buffer where placeholder bytes were written.
18    buffer_position: usize,
19    /// The dotted field path of the target being referenced.
20    target_path: String,
21    /// Scalar type for encoding the resolved address.
22    scalar_type: ScalarType,
23    /// Field path of the ref entry itself (for value_sink and error messages).
24    field_path: Vec<String>,
25}
26
27/// A checksum that needs to be resolved after the main traversal pass.
28struct PendingChecksum {
29    /// Position in the output buffer where placeholder bytes were written.
30    buffer_position: usize,
31    /// Scalar type for encoding the checksum value.
32    scalar_type: ScalarType,
33    /// Name of the checksum config in `[mint.checksum]`.
34    config_name: String,
35    /// Field path of the checksum entry (for value_sink and error messages).
36    field_path: Vec<String>,
37}
38
39/// Mutable state tracked during recursive bytestream building
40struct BuildState {
41    buffer: Vec<u8>,
42    offset: usize,
43    padding_count: u32,
44    /// Maps dotted field paths to their byte offsets within the block data.
45    known_offsets: HashMap<String, usize>,
46    /// Refs whose targets may not yet be known; resolved after traversal.
47    pending_refs: Vec<PendingRef>,
48    /// Checksums to compute after the bytestream is fully built.
49    pending_checksums: Vec<PendingChecksum>,
50    /// Resolved checksum values in field-order for stats/visualization.
51    resolved_checksum_values: Vec<u32>,
52}
53
54/// Immutable configuration for bytestream building
55pub struct BuildConfig<'a> {
56    pub endianness: &'a Endianness,
57    pub padding: u8,
58    pub strict: bool,
59    pub consts: &'a HashMap<String, ValueSource>,
60}
61
62pub struct BuildOutput {
63    pub bytestream: Vec<u8>,
64    pub padding_count: u32,
65    pub checksum_values: Vec<u32>,
66}
67
68#[derive(Debug, Deserialize)]
69pub struct Config {
70    pub mint: MintConfig,
71    #[serde(flatten)]
72    pub blocks: IndexMap<String, Block>,
73}
74
75/// Flash block.
76#[derive(Debug, Deserialize)]
77pub struct Block {
78    pub header: Header,
79    pub data: Entry,
80}
81
82/// TODO: Replace the untagged derive with a custom deserializer that treats
83/// maps with `type` as leaves, preserving scalar type parse errors without a
84/// raw-layout validation pass.
85#[derive(Debug, Deserialize)]
86#[serde(untagged)]
87pub enum Entry {
88    Leaf(LeafEntry),
89    Branch(IndexMap<String, Entry>),
90}
91
92impl Block {
93    pub fn build_bytestream(
94        &self,
95        data_source: Option<&dyn DataSource>,
96        settings: &MintConfig,
97        strict: bool,
98        value_sink: &mut dyn ValueSink,
99    ) -> Result<BuildOutput, LayoutError> {
100        let mut state = BuildState {
101            buffer: Vec::with_capacity((self.header.length as usize).min(64 * 1024)),
102            offset: 0,
103            padding_count: 0,
104            known_offsets: HashMap::new(),
105            pending_refs: Vec::new(),
106            pending_checksums: Vec::new(),
107            resolved_checksum_values: Vec::new(),
108        };
109        let config = BuildConfig {
110            endianness: &settings.endianness,
111            padding: self.header.padding,
112            strict,
113            consts: &settings.consts,
114        };
115
116        let mut field_path = Vec::new();
117        let _ = Self::build_bytestream_inner(
118            &self.data,
119            data_source,
120            settings,
121            &mut state,
122            &config,
123            value_sink,
124            &mut field_path,
125        )?;
126
127        // Resolve pending refs now that all offsets are known.
128        if !state.pending_refs.is_empty() {
129            Self::resolve_pending_refs(&mut state, &config, &self.header, value_sink)?;
130        }
131
132        // Resolve pending checksums now that the bytestream is complete.
133        if !state.pending_checksums.is_empty() {
134            Self::resolve_pending_checksums(&mut state, settings, &config, value_sink)?;
135        }
136
137        Ok(BuildOutput {
138            bytestream: state.buffer,
139            padding_count: state.padding_count,
140            checksum_values: state.resolved_checksum_values,
141        })
142    }
143
144    /// Recursively builds the bytestream. Returns the byte offset of the
145    /// first data byte emitted (post-alignment). The branch caller records
146    /// each child's path in `known_offsets` on exit from recursion.
147    fn build_bytestream_inner(
148        table: &Entry,
149        data_source: Option<&dyn DataSource>,
150        settings: &MintConfig,
151        state: &mut BuildState,
152        config: &BuildConfig,
153        value_sink: &mut dyn ValueSink,
154        field_path: &mut Vec<String>,
155    ) -> Result<usize, LayoutError> {
156        match table {
157            Entry::Leaf(leaf) => {
158                let alignment = leaf.get_alignment();
159                while !state.offset.is_multiple_of(alignment) {
160                    state.buffer.push(config.padding);
161                    state.offset += 1;
162                    state.padding_count += 1;
163                }
164
165                let leaf_offset = state.offset;
166
167                if let EntrySource::Ref(target) = &leaf.source {
168                    leaf.validate_ref(target)?;
169                    let size = leaf.scalar_type.size_bytes();
170                    state.pending_refs.push(PendingRef {
171                        buffer_position: state.buffer.len(),
172                        target_path: target.clone(),
173                        scalar_type: leaf.scalar_type,
174                        field_path: field_path.clone(),
175                    });
176                    state.buffer.extend(std::iter::repeat_n(0u8, size));
177                    state.offset += size;
178                    return Ok(leaf_offset);
179                }
180
181                if let EntrySource::Checksum(config_name) = &leaf.source {
182                    leaf.validate_checksum(config_name, settings)?;
183                    let size = leaf.scalar_type.size_bytes();
184                    state.pending_checksums.push(PendingChecksum {
185                        buffer_position: state.buffer.len(),
186                        scalar_type: leaf.scalar_type,
187                        config_name: config_name.clone(),
188                        field_path: field_path.clone(),
189                    });
190                    state.buffer.extend(std::iter::repeat_n(0u8, size));
191                    state.offset += size;
192                    return Ok(leaf_offset);
193                }
194
195                let bytes = leaf.emit_bytes(data_source, config, value_sink, field_path)?;
196                state.offset += bytes.len();
197                state.buffer.extend(bytes);
198                Ok(leaf_offset)
199            }
200            Entry::Branch(branch) => {
201                if branch.is_empty() {
202                    let branch_path = if field_path.is_empty() {
203                        "<root>".to_owned()
204                    } else {
205                        field_path.join(".")
206                    };
207                    return Err(LayoutError::DataValueExportFailed(format!(
208                        "Empty branch '{}' is invalid.",
209                        branch_path
210                    )));
211                }
212
213                let mut branch_offset = None;
214                for (field_name, v) in branch.iter() {
215                    let path_len = field_path.len();
216                    field_path.extend(split_field_path(field_name)?);
217
218                    let offset = Self::build_bytestream_inner(
219                        v,
220                        data_source,
221                        settings,
222                        state,
223                        config,
224                        value_sink,
225                        field_path,
226                    );
227
228                    if let Ok(o) = offset {
229                        state.known_offsets.insert(field_path.join("."), o);
230                        branch_offset.get_or_insert(o);
231                    }
232
233                    field_path.truncate(path_len);
234                    offset.map_err(|e| LayoutError::InField {
235                        field: field_name.clone(),
236                        source: Box::new(e),
237                    })?;
238                }
239                Ok(branch_offset.unwrap_or(state.offset))
240            }
241        }
242    }
243
244    /// Resolves all pending refs by looking up target offsets and patching the buffer.
245    fn resolve_pending_refs(
246        state: &mut BuildState,
247        config: &BuildConfig,
248        header: &Header,
249        value_sink: &mut dyn ValueSink,
250    ) -> Result<(), LayoutError> {
251        for pending in &state.pending_refs {
252            let target_offset = state
253                .known_offsets
254                .get(&pending.target_path)
255                .ok_or_else(|| {
256                    LayoutError::DataValueExportFailed(format!(
257                        "Ref target '{}' not found in block. Available fields: [{}]",
258                        pending.target_path,
259                        state
260                            .known_offsets
261                            .keys()
262                            .cloned()
263                            .collect::<Vec<_>>()
264                            .join(", ")
265                    ))
266                })?;
267
268            let address = header
269                .start_address
270                .checked_add(*target_offset as u32)
271                .ok_or_else(|| {
272                    LayoutError::DataValueExportFailed(format!(
273                        "Address overflow resolving ref to '{}'.",
274                        pending.target_path
275                    ))
276                })?;
277
278            let address_value = DataValue::U64(address as u64);
279            let bytes = address_value.to_bytes(pending.scalar_type, config.endianness, true)?;
280
281            // Patch the placeholder bytes in the buffer.
282            let pos = pending.buffer_position;
283            state.buffer[pos..pos + bytes.len()].copy_from_slice(&bytes);
284
285            // Record the resolved address in value_sink.
286            value_sink.record_value(
287                &pending.field_path,
288                serde_json::Value::Number(serde_json::Number::from(address as u64)),
289            )?;
290        }
291        Ok(())
292    }
293
294    /// Resolves all pending checksums by computing CRC over the buffer and patching in the result.
295    fn resolve_pending_checksums(
296        state: &mut BuildState,
297        settings: &MintConfig,
298        config: &BuildConfig,
299        value_sink: &mut dyn ValueSink,
300    ) -> Result<(), LayoutError> {
301        for pending in &state.pending_checksums {
302            let crc_config = settings.checksum.get(&pending.config_name).ok_or_else(|| {
303                LayoutError::DataValueExportFailed(format!(
304                    "Checksum config '{}' not found in [mint.checksum].",
305                    pending.config_name
306                ))
307            })?;
308
309            let crc_val =
310                checksum::calculate_crc(&state.buffer[..pending.buffer_position], crc_config);
311
312            // Convert CRC to bytes with proper endianness.
313            let crc_bytes = match config.endianness {
314                Endianness::Big => crc_val.to_be_bytes(),
315                Endianness::Little => crc_val.to_le_bytes(),
316            };
317
318            // Patch the placeholder bytes in the buffer.
319            let size = pending.scalar_type.size_bytes();
320            state.buffer[pending.buffer_position..pending.buffer_position + size]
321                .copy_from_slice(&crc_bytes[..size]);
322
323            // Record the resolved value in value_sink.
324            value_sink.record_value(
325                &pending.field_path,
326                serde_json::Value::Number(serde_json::Number::from(crc_val as u64)),
327            )?;
328            state.resolved_checksum_values.push(crc_val);
329        }
330        Ok(())
331    }
332}
333
334fn split_field_path(field_name: &str) -> Result<Vec<String>, LayoutError> {
335    let segments: Vec<&str> = field_name.split('.').collect();
336    if segments.iter().any(|s| s.is_empty()) {
337        return Err(LayoutError::DataValueExportFailed(format!(
338            "Invalid field path '{}'.",
339            field_name
340        )));
341    }
342    Ok(segments.into_iter().map(|s| s.to_owned()).collect())
343}