1use super::error::LayoutError;
2use super::scalar_type::{FixedPointType, ScalarType};
3use super::settings::{EndianBytes, Endianness};
4use super::value::DataValue;
5use std::fmt;
6
7macro_rules! impl_saturating_unsigned_try_from_data_value {
8 ($($t:ty),* $(,)?) => {$(
9 impl TryFrom<&DataValue> for $t {
10 type Error = LayoutError;
11 fn try_from(value: &DataValue) -> Result<Self, LayoutError> {
12 match value {
13 DataValue::Bool(val) => {
14 let n: u8 = if *val { 1 } else { 0 };
15 Ok(n as $t)
16 }
17 DataValue::U64(val) => Ok((*val).min(<$t>::MAX as u64) as $t),
18 DataValue::I64(val) => {
19 if *val < 0 {
20 Ok(<$t>::MIN)
21 } else {
22 Ok((*val as u64).min(<$t>::MAX as u64) as $t)
23 }
24 }
25 DataValue::F64(val) => Ok(*val as $t),
26 DataValue::Str(_) => {
27 return Err(LayoutError::DataValueExportFailed(
28 "Cannot convert string to scalar type.".to_owned(),
29 ));
30 }
31 }
32 }
33 }
34 )* }; }
35
36macro_rules! impl_saturating_signed_try_from_data_value {
37 ($($t:ty),* $(,)?) => {$(
38 impl TryFrom<&DataValue> for $t {
39 type Error = LayoutError;
40 fn try_from(value: &DataValue) -> Result<Self, LayoutError> {
41 match value {
42 DataValue::Bool(val) => {
43 let n: u8 = if *val { 1 } else { 0 };
44 Ok(n as $t)
45 }
46 DataValue::U64(val) => Ok((*val).min(<$t>::MAX as u64) as $t),
47 DataValue::I64(val) => {
48 let clamped = i128::from(*val).clamp(<$t>::MIN as i128, <$t>::MAX as i128);
49 Ok(clamped as $t)
50 }
51 DataValue::F64(val) => Ok(*val as $t),
52 DataValue::Str(_) => {
53 return Err(LayoutError::DataValueExportFailed(
54 "Cannot convert string to scalar type.".to_owned(),
55 ));
56 }
57 }
58 }
59 }
60 )* }; }
61
62macro_rules! impl_float_try_from_data_value {
63 ($($t:ty),* $(,)?) => {$(
64 impl TryFrom<&DataValue> for $t {
65 type Error = LayoutError;
66 fn try_from(value: &DataValue) -> Result<Self, LayoutError> {
67 match value {
68 DataValue::Bool(val) => {
69 let n: u8 = if *val { 1 } else { 0 };
70 Ok(n as $t)
71 }
72 DataValue::U64(val) => Ok(*val as $t),
73 DataValue::I64(val) => Ok(*val as $t),
74 DataValue::F64(val) => Ok(*val as $t),
75 DataValue::Str(_) => {
76 return Err(LayoutError::DataValueExportFailed(
77 "Cannot convert string to scalar type.".to_owned(),
78 ));
79 }
80 }
81 }
82 }
83 )* }; }
84
85impl_saturating_unsigned_try_from_data_value!(u8, u16, u32, u64);
86impl_saturating_signed_try_from_data_value!(i8, i16, i32, i64, i128);
87impl_float_try_from_data_value!(f32, f64);
88
89pub trait TryFromStrict<T>: Sized {
90 fn try_from_strict(value: T) -> Result<Self, LayoutError>;
91}
92
93macro_rules! err {
94 ($msg:expr) => {
95 LayoutError::DataValueExportFailed($msg.to_owned())
96 };
97}
98
99macro_rules! impl_try_from_strict_unsigned {
100 ($($t:ty),* $(,)?) => {$(
101 impl TryFromStrict<&DataValue> for $t {
102 fn try_from_strict(value: &DataValue) -> Result<Self, LayoutError> {
103 match value {
104 DataValue::U64(v) => <Self as TryFrom<u64>>::try_from(*v)
105 .map_err(|_| err!(format!("u64 value {} out of range for {}", v, stringify!($t)))),
106 DataValue::I64(v) => {
107 if *v < 0 { return Err(err!("negative integer cannot convert to unsigned in strict mode")); }
108 <Self as TryFrom<u64>>::try_from(*v as u64)
109 .map_err(|_| err!(format!("i64 value {} out of range for {}", v, stringify!($t))))
110 }
111 DataValue::F64(v) => {
112 if !v.is_finite() { return Err(err!("non-finite float cannot convert to integer in strict mode")); }
113 if v.fract() != 0.0 { return Err(err!("float to integer conversion not allowed unless value is an exact integer")); }
114 if *v < 0.0 || *v > (<$t>::MAX as f64) { return Err(err!(format!("float value {} out of range for {}", v, stringify!($t)))); }
115 Ok(*v as $t)
116 }
117 DataValue::Bool(b) => {
118 let n: u8 = if *b { 1 } else { 0 };
119 Ok(n as $t)
120 }
121 DataValue::Str(_) => Err(err!("Cannot convert string to scalar type.")),
122 }
123 }
124 }
125 )*};
126}
127
128macro_rules! impl_try_from_strict_signed {
129 ($($t:ty),* $(,)?) => {$(
130 impl TryFromStrict<&DataValue> for $t {
131 fn try_from_strict(value: &DataValue) -> Result<Self, LayoutError> {
132 match value {
133 DataValue::U64(v) => {
134 <Self as TryFrom<i128>>::try_from(*v as i128)
135 .map_err(|_| err!(format!("u64 value {} out of range for {}", v, stringify!($t))))
136 }
137 DataValue::I64(v) => <Self as TryFrom<i64>>::try_from(*v)
138 .map_err(|_| err!(format!("i64 value {} out of range for {}", v, stringify!($t)))),
139 DataValue::F64(v) => {
140 if !v.is_finite() { return Err(err!("non-finite float cannot convert to integer in strict mode")); }
141 if v.fract() != 0.0 { return Err(err!("float to integer conversion not allowed unless value is an exact integer")); }
142 if *v < (<$t>::MIN as f64) || *v > (<$t>::MAX as f64) { return Err(err!(format!("float value {} out of range for {}", v, stringify!($t)))); }
143 Ok(*v as $t)
144 }
145 DataValue::Bool(b) => {
146 let n: u8 = if *b { 1 } else { 0 };
147 Ok(n as $t)
148 }
149 DataValue::Str(_) => Err(err!("Cannot convert string to scalar type.")),
150 }
151 }
152 }
153 )*};
154}
155
156macro_rules! impl_try_from_strict_float_targets {
157 ($t:ty) => {
158 impl TryFromStrict<&DataValue> for $t {
159 fn try_from_strict(value: &DataValue) -> Result<Self, LayoutError> {
160 match value {
161 DataValue::F64(v) => {
162 if !v.is_finite() {
163 return Err(err!("non-finite float not allowed in strict mode"));
164 }
165 let out = *v as $t;
166 if out.is_finite() {
167 Ok(out)
168 } else {
169 Err(err!(format!(
170 "float value {} out of range for {}",
171 v,
172 stringify!($t)
173 )))
174 }
175 }
176 DataValue::U64(v) => {
177 let out = (*v as $t);
178 if !out.is_finite() {
179 return Err(err!("integer to float produced non-finite value"));
180 }
181 if (out as u64) == *v {
183 Ok(out)
184 } else {
185 Err(err!(
186 "lossy integer to float conversion not allowed in strict mode"
187 ))
188 }
189 }
190 DataValue::I64(v) => {
191 let out = (*v as $t);
192 if !out.is_finite() {
193 return Err(err!("integer to float produced non-finite value"));
194 }
195 if (out as i64) == *v {
196 Ok(out)
197 } else {
198 Err(err!(
199 "lossy integer to float conversion not allowed in strict mode"
200 ))
201 }
202 }
203 DataValue::Bool(b) => {
204 let out: $t = if *b { 1.0 } else { 0.0 };
205 Ok(out)
206 }
207 DataValue::Str(_) => Err(err!("Cannot convert string to scalar type.")),
208 }
209 }
210 }
211 };
212}
213
214impl_try_from_strict_unsigned!(u8, u16, u32, u64);
215impl_try_from_strict_signed!(i8, i16, i32, i64, i128);
216impl_try_from_strict_float_targets!(f32);
217impl TryFromStrict<&DataValue> for f64 {
218 fn try_from_strict(value: &DataValue) -> Result<Self, LayoutError> {
219 match value {
220 DataValue::F64(v) => Ok(*v),
221 DataValue::U64(v) => {
222 let out = *v as f64;
223 if (out as u64) == *v {
224 Ok(out)
225 } else {
226 Err(err!(
227 "lossy integer to float conversion not allowed in strict mode"
228 ))
229 }
230 }
231 DataValue::I64(v) => {
232 let out = *v as f64;
233 if (out as i64) == *v {
234 Ok(out)
235 } else {
236 Err(err!(
237 "lossy integer to float conversion not allowed in strict mode"
238 ))
239 }
240 }
241 DataValue::Bool(b) => {
242 let out = if *b { 1.0 } else { 0.0 };
243 Ok(out)
244 }
245 DataValue::Str(_) => Err(err!("Cannot convert string to scalar type.")),
246 }
247 }
248}
249
250pub fn clamp_bitfield_value(
256 value: &DataValue,
257 bits: usize,
258 signed: bool,
259 strict: bool,
260) -> Result<i128, LayoutError> {
261 let raw: i128 = if strict {
262 i128::try_from_strict(value)?
263 } else {
264 i128::try_from(value)?
265 };
266
267 let (min, max) = if signed {
268 let half = 1i128 << (bits - 1);
269 (-half, half - 1)
270 } else {
271 (0, (1i128 << bits) - 1)
272 };
273
274 if strict && (raw < min || raw > max) {
275 return Err(LayoutError::BitfieldOutOfRange {
276 value: raw,
277 bits,
278 signedness: if signed { "signed" } else { "unsigned" },
279 min,
280 max,
281 });
282 }
283 Ok(raw.clamp(min, max))
284}
285
286fn data_value_display(value: &DataValue) -> String {
287 match value {
288 DataValue::Bool(value) => value.to_string(),
289 DataValue::U64(value) => value.to_string(),
290 DataValue::I64(value) => value.to_string(),
291 DataValue::F64(value) => value.to_string(),
292 DataValue::Str(value) => format!("{value:?}"),
293 }
294}
295
296fn fixed_point_overflow_error(
297 fixed: FixedPointType,
298 value: &DataValue,
299 scaled: impl fmt::Display,
300) -> LayoutError {
301 LayoutError::DataValueExportFailed(format!(
302 "fixed-point type '{}' overflows {} for value {} (scaled to {})",
303 fixed,
304 fixed.storage_label(),
305 data_value_display(value),
306 scaled
307 ))
308}
309
310fn fixed_point_non_finite_error(fixed: FixedPointType, value: &DataValue) -> LayoutError {
311 LayoutError::DataValueExportFailed(format!(
312 "fixed-point type '{}' cannot encode non-finite value {}",
313 fixed,
314 data_value_display(value)
315 ))
316}
317
318fn encode_fixed_point_bytes(
319 value: &DataValue,
320 fixed: FixedPointType,
321 endianness: &Endianness,
322 strict: bool,
323) -> Result<Vec<u8>, LayoutError> {
324 let encoded = encode_fixed_point_value(value, fixed, strict)?;
325 encode_integer_bytes(encoded, fixed, endianness)
326}
327
328fn encode_fixed_point_value(
329 value: &DataValue,
330 fixed: FixedPointType,
331 strict: bool,
332) -> Result<i128, LayoutError> {
333 let (min, max) = fixed.encoded_bounds();
334 let scale = 1i128 << fixed.fractional_bits;
335
336 let encoded = match value {
337 DataValue::Bool(raw) => clamp_fixed_point_integer(
338 if *raw { 1 } else { 0 },
339 scale,
340 min,
341 max,
342 fixed,
343 value,
344 strict,
345 )?,
346 DataValue::U64(raw) => {
347 clamp_fixed_point_integer(i128::from(*raw), scale, min, max, fixed, value, strict)?
348 }
349 DataValue::I64(raw) => {
350 clamp_fixed_point_integer(i128::from(*raw), scale, min, max, fixed, value, strict)?
351 }
352 DataValue::F64(raw) => clamp_fixed_point_float(*raw, min, max, fixed, strict, value)?,
353 DataValue::Str(_) => {
354 return Err(LayoutError::DataValueExportFailed(
355 "Cannot convert string to scalar type.".to_owned(),
356 ));
357 }
358 };
359
360 Ok(encoded)
361}
362
363fn clamp_fixed_point_integer(
364 raw: i128,
365 scale: i128,
366 min: i128,
367 max: i128,
368 fixed: FixedPointType,
369 original: &DataValue,
370 strict: bool,
371) -> Result<i128, LayoutError> {
372 let Some(scaled) = raw.checked_mul(scale) else {
373 if strict {
374 return Err(fixed_point_overflow_error(
375 fixed,
376 original,
377 "integer scaling overflow",
378 ));
379 }
380 return Ok(if raw.is_negative() { min } else { max });
381 };
382
383 if strict && (scaled < min || scaled > max) {
384 return Err(fixed_point_overflow_error(fixed, original, scaled));
385 }
386
387 Ok(scaled.clamp(min, max))
388}
389
390fn clamp_fixed_point_float(
391 raw: f64,
392 min: i128,
393 max: i128,
394 fixed: FixedPointType,
395 strict: bool,
396 original: &DataValue,
397) -> Result<i128, LayoutError> {
398 if !raw.is_finite() {
399 return Err(fixed_point_non_finite_error(fixed, original));
400 }
401
402 let scaled = raw * (2f64).powi(i32::from(fixed.fractional_bits));
403 if !scaled.is_finite() {
404 if strict {
405 return Err(fixed_point_overflow_error(fixed, original, scaled));
406 }
407 return Ok(if scaled.is_sign_negative() { min } else { max });
408 }
409
410 let rounded = scaled.round_ties_even();
411 let rounded_int = rounded as i128;
412 if rounded_int < min {
413 if strict {
414 return Err(fixed_point_overflow_error(fixed, original, rounded));
415 }
416 return Ok(min);
417 }
418 if rounded_int > max {
419 if strict {
420 return Err(fixed_point_overflow_error(fixed, original, rounded));
421 }
422 return Ok(max);
423 }
424
425 Ok(rounded_int)
426}
427
428fn encode_integer_bytes(
429 encoded: i128,
430 fixed: FixedPointType,
431 endianness: &Endianness,
432) -> Result<Vec<u8>, LayoutError> {
433 match (fixed.signed, fixed.total_bits) {
434 (false, 8) => Ok((encoded as u8).to_endian_bytes(endianness)),
435 (false, 16) => Ok((encoded as u16).to_endian_bytes(endianness)),
436 (false, 32) => Ok((encoded as u32).to_endian_bytes(endianness)),
437 (false, 64) => Ok(u64::try_from(encoded)
438 .map_err(|_| {
439 LayoutError::DataValueExportFailed(format!(
440 "fixed-point type '{}' encoded value {} could not be written as u64",
441 fixed, encoded
442 ))
443 })?
444 .to_endian_bytes(endianness)),
445 (true, 8) => Ok((encoded as i8).to_endian_bytes(endianness)),
446 (true, 16) => Ok((encoded as i16).to_endian_bytes(endianness)),
447 (true, 32) => Ok((encoded as i32).to_endian_bytes(endianness)),
448 (true, 64) => Ok(i64::try_from(encoded)
449 .map_err(|_| {
450 LayoutError::DataValueExportFailed(format!(
451 "fixed-point type '{}' encoded value {} could not be written as i64",
452 fixed, encoded
453 ))
454 })?
455 .to_endian_bytes(endianness)),
456 _ => Err(LayoutError::DataValueExportFailed(format!(
457 "unsupported fixed-point width '{}'",
458 fixed
459 ))),
460 }
461}
462
463pub fn convert_value_to_bytes(
464 value: &DataValue,
465 scalar_type: ScalarType,
466 endianness: &Endianness,
467 strict: bool,
468) -> Result<Vec<u8>, LayoutError> {
469 macro_rules! to_bytes {
470 ($t:ty) => {{
471 let val: $t = if strict {
472 <$t as TryFromStrict<&DataValue>>::try_from_strict(value)?
473 } else {
474 <$t as TryFrom<&DataValue>>::try_from(value)?
475 };
476 Ok(val.to_endian_bytes(endianness))
477 }};
478 }
479
480 match scalar_type {
481 ScalarType::U8 => to_bytes!(u8),
482 ScalarType::I8 => to_bytes!(i8),
483 ScalarType::U16 => to_bytes!(u16),
484 ScalarType::I16 => to_bytes!(i16),
485 ScalarType::U32 => to_bytes!(u32),
486 ScalarType::I32 => to_bytes!(i32),
487 ScalarType::U64 => to_bytes!(u64),
488 ScalarType::I64 => to_bytes!(i64),
489 ScalarType::F32 => to_bytes!(f32),
490 ScalarType::F64 => to_bytes!(f64),
491 ScalarType::Fixed(fixed) => encode_fixed_point_bytes(value, fixed, endianness, strict),
492 }
493}