use super::block::BuildConfig;
use super::conversions::clamp_bitfield_value;
use super::error::LayoutError;
use super::scalar_type::{ScalarType, fixed_point_unsupported_error};
use super::settings::{Endianness, MintConfig};
use super::used_values::{
ValueSink, array_2d_to_json, array_to_json, data_value_to_json, i128_to_json,
};
use super::value::{DataValue, ValueSource};
use crate::data::DataSource;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LeafEntry {
#[serde(rename = "type")]
pub scalar_type: ScalarType,
#[serde(flatten, default)]
size_keys: SizeKeys,
#[serde(flatten)]
pub source: EntrySource,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum SizeSource {
OneD(usize),
TwoD([usize; 2]),
}
#[derive(Debug, Default, Deserialize)]
struct SizeKeys {
#[serde(rename = "size")]
size: Option<SizeSource>,
#[serde(rename = "SIZE")]
strict_size: Option<SizeSource>,
}
impl SizeKeys {
fn resolve(&self) -> Result<(Option<SizeSource>, bool), LayoutError> {
match (&self.size, &self.strict_size) {
(Some(_), Some(_)) => Err(LayoutError::DataValueExportFailed(
"Use either 'size' or 'SIZE', not both.".into(),
)),
(Some(s), None) => Ok((Some(s.clone()), false)),
(None, Some(s)) => Ok((Some(s.clone()), true)),
(None, None) => Ok((None, false)),
}
}
}
#[derive(Debug, Deserialize)]
pub enum EntrySource {
#[serde(rename = "name")]
Name(String),
#[serde(rename = "value")]
Value(ValueSource),
#[serde(rename = "bitmap")]
Bitmap(Vec<BitmapField>),
#[serde(rename = "ref")]
Ref(String),
#[serde(rename = "checksum")]
Checksum(String),
#[serde(rename = "const")]
Const(String),
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BitmapField {
pub bits: usize,
#[serde(flatten)]
pub source: BitmapFieldSource,
}
#[derive(Debug, Deserialize)]
pub enum BitmapFieldSource {
#[serde(rename = "name")]
Name(String),
#[serde(rename = "value")]
Value(DataValue),
}
impl BitmapField {
fn resolve_value(
&self,
data_source: Option<&dyn DataSource>,
) -> Result<DataValue, LayoutError> {
match &self.source {
BitmapFieldSource::Name(name) => {
let Some(ds) = data_source else {
return Err(LayoutError::MissingDataSheet(format!(
"Bitmap field '{}' requires a value from a data source, but none was provided.",
name
)));
};
Ok(ds.retrieve_single_value(name)?)
}
BitmapFieldSource::Value(v) => Ok(v.clone()),
}
}
}
impl LeafEntry {
pub fn get_alignment(&self) -> usize {
self.scalar_type.size_bytes()
}
pub fn emit_bytes(
&self,
data_source: Option<&dyn DataSource>,
config: &BuildConfig,
value_sink: &mut dyn ValueSink,
field_path: &[String],
) -> Result<Vec<u8>, LayoutError> {
if let EntrySource::Ref(_) = &self.source {
return Err(LayoutError::DataValueExportFailed(
"Ref entries are resolved in a fixup pass, not via emit_bytes.".into(),
));
}
if let EntrySource::Checksum(_) = &self.source {
return Err(LayoutError::DataValueExportFailed(
"Checksum entries are resolved in a fixup pass, not via emit_bytes.".into(),
));
}
if let EntrySource::Bitmap(fields) = &self.source {
self.validate_bitmap(fields)?;
return self.emit_bitmap(fields, data_source, config, value_sink, field_path);
}
let (size, strict_len) = self.size_keys.resolve()?;
match size {
None => self.emit_bytes_single(data_source, config, value_sink, field_path),
Some(SizeSource::OneD(size)) => self.emit_bytes_1d(
data_source,
size,
config,
strict_len,
value_sink,
field_path,
),
Some(SizeSource::TwoD(size)) => self.emit_bytes_2d(
data_source,
size,
config,
strict_len,
value_sink,
field_path,
),
}
}
pub fn validate_const<'a>(
&self,
name: &str,
config: &'a BuildConfig<'a>,
size: Option<&SizeSource>,
) -> Result<&'a ValueSource, LayoutError> {
if name.is_empty() {
return Err(LayoutError::DataValueExportFailed(
"Const name must not be empty.".into(),
));
}
let value = config.consts.get(name).ok_or_else(|| {
let available = config.consts.keys().cloned().collect::<Vec<_>>().join(", ");
LayoutError::DataValueExportFailed(format!(
"Const '{}' not found in [mint.const]. Available: [{}]",
name, available
))
})?;
match (size, value) {
(Some(SizeSource::TwoD(_)), _) => {
return Err(LayoutError::DataValueExportFailed(
"2D arrays within the layout file are not supported.".to_owned(),
));
}
(Some(SizeSource::OneD(_)), ValueSource::Single(DataValue::Str(_))) => {}
(Some(SizeSource::OneD(_)), ValueSource::Array(_)) => {}
(Some(SizeSource::OneD(_)), ValueSource::Single(_)) => {
return Err(LayoutError::DataValueExportFailed(
"size/SIZE keys are forbidden with scalar const.".into(),
));
}
(None, _) => {}
}
Ok(value)
}
pub fn validate_ref(&self, target: &str) -> Result<(), LayoutError> {
if self.scalar_type.fixed_point().is_some() {
return Err(fixed_point_unsupported_error("Ref", self.scalar_type));
}
if self.size_keys.size.is_some() || self.size_keys.strict_size.is_some() {
return Err(LayoutError::DataValueExportFailed(
"size/SIZE keys are forbidden with ref.".into(),
));
}
if !matches!(
self.scalar_type,
ScalarType::U16 | ScalarType::U32 | ScalarType::U64
) {
return Err(LayoutError::DataValueExportFailed(
"Ref requires unsigned integer storage type (u16, u32, u64).".into(),
));
}
if target.is_empty() {
return Err(LayoutError::DataValueExportFailed(
"Ref target path must not be empty.".into(),
));
}
Ok(())
}
pub fn validate_checksum(
&self,
config_name: &str,
settings: &MintConfig,
) -> Result<(), LayoutError> {
if self.scalar_type.fixed_point().is_some() {
return Err(fixed_point_unsupported_error("Checksum", self.scalar_type));
}
if self.size_keys.size.is_some() || self.size_keys.strict_size.is_some() {
return Err(LayoutError::DataValueExportFailed(
"size/SIZE keys are forbidden with checksum.".into(),
));
}
if config_name.is_empty() {
return Err(LayoutError::DataValueExportFailed(
"Checksum config name must not be empty.".into(),
));
}
if !settings.checksum.contains_key(config_name) {
let available = settings
.checksum
.keys()
.cloned()
.collect::<Vec<_>>()
.join(", ");
return Err(LayoutError::DataValueExportFailed(format!(
"Checksum config '{}' not found in [mint.checksum]. Available: [{}]",
config_name, available
)));
}
if !matches!(self.scalar_type, ScalarType::U32) {
return Err(LayoutError::DataValueExportFailed(format!(
"Checksum type must be u32 (4 bytes), got {} ({} bytes).",
self.scalar_type.name(),
self.scalar_type.size_bytes()
)));
}
Ok(())
}
fn validate_bitmap(&self, fields: &[BitmapField]) -> Result<(), LayoutError> {
if self.scalar_type.fixed_point().is_some() {
return Err(fixed_point_unsupported_error("Bitmap", self.scalar_type));
}
if self.size_keys.size.is_some() || self.size_keys.strict_size.is_some() {
return Err(LayoutError::DataValueExportFailed(
"size/SIZE keys are forbidden with bitmap.".into(),
));
}
if !self.scalar_type.is_integer() {
return Err(LayoutError::DataValueExportFailed(
"Bitmap requires integer storage type.".into(),
));
}
let mut total_bits = 0usize;
for field in fields {
if field.bits == 0 {
return Err(LayoutError::DataValueExportFailed(
"Bitmap field bits must be > 0.".into(),
));
}
total_bits += field.bits;
}
let expected_bits = self.scalar_type.size_bytes() * 8;
if total_bits != expected_bits {
return Err(LayoutError::DataValueExportFailed(format!(
"Bitmap total bits ({}) must equal storage width ({}).",
total_bits, expected_bits
)));
}
Ok(())
}
fn emit_bitmap(
&self,
fields: &[BitmapField],
data_source: Option<&dyn DataSource>,
config: &BuildConfig,
value_sink: &mut dyn ValueSink,
field_path: &[String],
) -> Result<Vec<u8>, LayoutError> {
let signed = self.scalar_type.is_signed();
let mut accumulator: u128 = 0;
let mut offset: usize = 0;
for field in fields {
let value = field.resolve_value(data_source)?;
let clamped = clamp_bitfield_value(&value, field.bits, signed, config.strict)?;
let mask = (1u128 << field.bits) - 1;
let pattern = (clamped as u128) & mask;
accumulator |= pattern << offset;
let mut bitmap_path = field_path.to_vec();
bitmap_path.push(bitmap_field_key(field, offset));
value_sink.record_value(&bitmap_path, i128_to_json(clamped)?)?;
offset += field.bits;
}
encode_bitmap_storage(accumulator, self.scalar_type, config.endianness)
}
fn emit_bytes_single(
&self,
data_source: Option<&dyn DataSource>,
config: &BuildConfig,
value_sink: &mut dyn ValueSink,
field_path: &[String],
) -> Result<Vec<u8>, LayoutError> {
match &self.source {
EntrySource::Name(name) => {
let Some(ds) = data_source else {
return Err(LayoutError::MissingDataSheet(format!(
"Field '{}' requires a value from a data source, but none was provided.",
name
)));
};
let value = ds.retrieve_single_value(name)?;
let bytes = value.to_bytes(self.scalar_type, config.endianness, config.strict)?;
value_sink.record_value(field_path, data_value_to_json(&value)?)?;
Ok(bytes)
}
EntrySource::Value(ValueSource::Single(v)) => {
let bytes = v.to_bytes(self.scalar_type, config.endianness, config.strict)?;
value_sink.record_value(field_path, data_value_to_json(v)?)?;
Ok(bytes)
}
EntrySource::Value(_) => Err(LayoutError::DataValueExportFailed(
"Single value expected for scalar type.".to_owned(),
)),
EntrySource::Const(name) => match self.validate_const(name, config, None)? {
ValueSource::Single(v) => {
let bytes = v.to_bytes(self.scalar_type, config.endianness, config.strict)?;
value_sink.record_value(field_path, data_value_to_json(v)?)?;
Ok(bytes)
}
ValueSource::Array(_) => Err(LayoutError::DataValueExportFailed(
"Single value expected for scalar type.".to_owned(),
)),
},
EntrySource::Bitmap(_) => unreachable!("bitmap handled in emit_bytes"),
EntrySource::Ref(_) => unreachable!("ref handled in build_bytestream"),
EntrySource::Checksum(_) => unreachable!("checksum handled in build_bytestream"),
}
}
fn emit_bytes_1d(
&self,
data_source: Option<&dyn DataSource>,
size: usize,
config: &BuildConfig,
strict_len: bool,
value_sink: &mut dyn ValueSink,
field_path: &[String],
) -> Result<Vec<u8>, LayoutError> {
let elem = self.scalar_type.size_bytes();
let total_bytes = size
.checked_mul(elem)
.ok_or(LayoutError::DataValueExportFailed(
"Array size overflow".into(),
))?;
let mut out = Vec::with_capacity(total_bytes);
match &self.source {
EntrySource::Name(name) => {
let Some(ds) = data_source else {
return Err(LayoutError::MissingDataSheet(format!(
"Field '{}' requires a value from a data source, but none was provided.",
name
)));
};
match ds.retrieve_1d_array_or_string(name)? {
ValueSource::Single(v) => {
if !matches!(self.scalar_type, ScalarType::U8) {
return Err(LayoutError::DataValueExportFailed(
"Strings should have type u8.".to_owned(),
));
}
out.extend(v.string_to_bytes()?);
value_sink.record_value(field_path, data_value_to_json(&v)?)?;
}
ValueSource::Array(v) => {
for value in &v {
out.extend(value.to_bytes(
self.scalar_type,
config.endianness,
config.strict,
)?);
}
value_sink.record_value(field_path, array_to_json(&v)?)?;
}
}
}
EntrySource::Value(ValueSource::Array(v)) => {
for value in v {
out.extend(value.to_bytes(
self.scalar_type,
config.endianness,
config.strict,
)?);
}
value_sink.record_value(field_path, array_to_json(v)?)?;
}
EntrySource::Value(ValueSource::Single(v)) => {
if !matches!(self.scalar_type, ScalarType::U8) {
return Err(LayoutError::DataValueExportFailed(
"Strings should have type u8.".to_owned(),
));
}
out.extend(v.string_to_bytes()?);
value_sink.record_value(field_path, data_value_to_json(v)?)?;
}
EntrySource::Const(name) => {
match self.validate_const(name, config, Some(&SizeSource::OneD(size)))? {
ValueSource::Array(v) => {
for value in v {
out.extend(value.to_bytes(
self.scalar_type,
config.endianness,
config.strict,
)?);
}
value_sink.record_value(field_path, array_to_json(v)?)?;
}
ValueSource::Single(v) => {
if !matches!(self.scalar_type, ScalarType::U8) {
return Err(LayoutError::DataValueExportFailed(
"Strings should have type u8.".to_owned(),
));
}
out.extend(v.string_to_bytes()?);
value_sink.record_value(field_path, data_value_to_json(v)?)?;
}
}
}
EntrySource::Bitmap(_) => unreachable!("bitmap handled in emit_bytes"),
EntrySource::Ref(_) => unreachable!("ref handled in build_bytestream"),
EntrySource::Checksum(_) => unreachable!("checksum handled in build_bytestream"),
}
if out.len() > total_bytes {
return Err(LayoutError::DataValueExportFailed(
"Array/string is larger than defined size.".to_owned(),
));
}
if strict_len && out.len() < total_bytes {
return Err(LayoutError::DataValueExportFailed(
"Array/string is smaller than defined size (strict SIZE).".to_owned(),
));
}
while out.len() < total_bytes {
out.push(config.padding);
}
Ok(out)
}
fn emit_bytes_2d(
&self,
data_source: Option<&dyn DataSource>,
size: [usize; 2],
config: &BuildConfig,
strict_len: bool,
value_sink: &mut dyn ValueSink,
field_path: &[String],
) -> Result<Vec<u8>, LayoutError> {
match &self.source {
EntrySource::Name(name) => {
let Some(ds) = data_source else {
return Err(LayoutError::MissingDataSheet(format!(
"Field '{}' requires a value from a data source, but none was provided.",
name
)));
};
let data = ds.retrieve_2d_array(name)?;
let rows = size[0];
let cols = size[1];
let elem = self.scalar_type.size_bytes();
let total_elems =
rows.checked_mul(cols)
.ok_or(LayoutError::DataValueExportFailed(
"2D size overflow".into(),
))?;
let total_bytes =
total_elems
.checked_mul(elem)
.ok_or(LayoutError::DataValueExportFailed(
"2D byte count overflow".into(),
))?;
if data.iter().any(|row| row.len() != cols) {
return Err(LayoutError::DataValueExportFailed(
"2D array column count mismatch.".to_owned(),
));
}
if data.len() > rows {
return Err(LayoutError::DataValueExportFailed(
"2D array row count greater than defined size.".to_owned(),
));
}
if strict_len && data.len() < rows {
return Err(LayoutError::DataValueExportFailed(
"2D array row count smaller than defined size (strict SIZE).".to_owned(),
));
}
let mut out = Vec::with_capacity(total_bytes);
for row in &data {
for v in row {
out.extend(v.to_bytes(
self.scalar_type,
config.endianness,
config.strict,
)?);
}
}
value_sink.record_value(field_path, array_2d_to_json(&data)?)?;
while out.len() < total_bytes {
out.push(config.padding);
}
Ok(out)
}
EntrySource::Value(_) => Err(LayoutError::DataValueExportFailed(
"2D arrays within the layout file are not supported.".to_owned(),
)),
EntrySource::Const(name) => {
self.validate_const(name, config, Some(&SizeSource::TwoD(size)))?;
Err(LayoutError::DataValueExportFailed(
"2D arrays within the layout file are not supported.".to_owned(),
))
}
EntrySource::Bitmap(_) => unreachable!("bitmap handled in emit_bytes"),
EntrySource::Ref(_) => unreachable!("ref handled in build_bytestream"),
EntrySource::Checksum(_) => unreachable!("checksum handled in build_bytestream"),
}
}
}
fn encode_bitmap_storage(
accumulator: u128,
scalar_type: ScalarType,
endianness: &Endianness,
) -> Result<Vec<u8>, LayoutError> {
if !scalar_type.is_integer() {
return Err(LayoutError::DataValueExportFailed(
"Bitmap requires integer storage type.".into(),
));
}
let width = scalar_type.size_bytes();
let bytes = match endianness {
Endianness::Little => (accumulator as u64).to_le_bytes()[..width].to_vec(),
Endianness::Big => (accumulator as u64).to_be_bytes()[8 - width..].to_vec(),
};
Ok(bytes)
}
fn bitmap_field_key(field: &BitmapField, offset: usize) -> String {
match &field.source {
BitmapFieldSource::Name(name) => name.clone(),
BitmapFieldSource::Value(_) => format!("reserved_{}_{}", offset, field.bits),
}
}