1use super::types::ConversionType;
2use crate::blocks::common::{BlockHeader, BlockParse, read_u8, read_u16, validate_buffer_size};
3use crate::{Error, Result};
4
5use alloc::boxed::Box;
6use alloc::format;
7use alloc::string::String;
8use alloc::vec::Vec;
9
10#[cfg(feature = "std")]
11use alloc::collections::BTreeMap;
12#[cfg(feature = "std")]
13use alloc::collections::BTreeSet;
14
15#[derive(Debug, Clone)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct ConversionBlock {
18 pub header: BlockHeader,
19
20 pub name_addr: Option<u64>,
22 pub unit_addr: Option<u64>,
23 pub comment_addr: Option<u64>,
24 pub inverse_addr: Option<u64>,
25 pub refs: Vec<u64>,
26
27 pub conversion_type: ConversionType,
29 pub precision: u8,
30 pub flags: u16,
31 pub ref_count: u16,
32 pub value_count: u16,
33 pub phys_range_min: Option<f64>,
34 pub phys_range_max: Option<f64>,
35 pub values: Vec<f64>,
36
37 pub formula: Option<String>,
38
39 #[cfg(feature = "std")]
43 pub resolved_texts: Option<BTreeMap<usize, String>>,
44 #[cfg(not(feature = "std"))]
45 pub resolved_texts: Option<()>,
46
47 #[cfg(feature = "std")]
50 pub resolved_conversions: Option<BTreeMap<usize, Box<ConversionBlock>>>,
51 #[cfg(not(feature = "std"))]
52 pub resolved_conversions: Option<()>,
53
54 pub default_conversion: Option<Box<ConversionBlock>>,
57}
58
59impl BlockParse<'_> for ConversionBlock {
60 const ID: &'static str = "##CC";
61 fn from_bytes(bytes: &[u8]) -> Result<Self> {
62 let header = Self::parse_header(bytes)?;
63
64 let mut offset = 24;
65
66 let name_addr = read_link(bytes, &mut offset);
68 let unit_addr = read_link(bytes, &mut offset);
69 let comment_addr = read_link(bytes, &mut offset);
70 let inverse_addr = read_link(bytes, &mut offset);
71
72 let fixed_links = 4;
73 let additional_links = header.link_count.saturating_sub(fixed_links);
74 let mut refs = Vec::with_capacity(additional_links as usize);
75 for _ in 0..additional_links {
76 refs.push(read_u64_checked(bytes, &mut offset)?);
77 }
78
79 let conversion_type = ConversionType::from_u8(read_u8(bytes, offset));
81 offset += 1;
82 let precision = read_u8(bytes, offset);
83 offset += 1;
84 let flags = read_u16(bytes, offset);
85 offset += 2;
86 let ref_count = read_u16(bytes, offset);
87 offset += 2;
88 let value_count = read_u16(bytes, offset);
89 offset += 2;
90
91 let size_without_range =
96 24 + (header.link_count as usize * 8) + 8 + (value_count as usize * 8);
97 let size_with_range = size_without_range + 16;
98 let has_range_data = header.length as usize >= size_with_range;
99
100 let phys_range_min = if has_range_data {
101 let val = f64::from_bits(read_u64_checked(bytes, &mut offset)?);
102 Some(val)
103 } else {
104 None
105 };
106
107 let phys_range_max = if has_range_data {
108 let val = f64::from_bits(read_u64_checked(bytes, &mut offset)?);
109 Some(val)
110 } else {
111 None
112 };
113
114 let mut values = Vec::with_capacity(value_count as usize);
115 for _ in 0..value_count {
116 let val = f64::from_bits(read_u64_checked(bytes, &mut offset)?);
117 values.push(val);
118 }
119
120 Ok(Self {
121 header,
122 name_addr,
123 unit_addr,
124 comment_addr,
125 inverse_addr,
126 refs,
127 conversion_type,
128 precision,
129 flags,
130 ref_count,
131 value_count,
132 phys_range_min,
133 phys_range_max,
134 values,
135 formula: None,
136 resolved_texts: None,
137 resolved_conversions: None,
138 default_conversion: None,
139 })
140 }
141}
142
143fn read_link(bytes: &[u8], offset: &mut usize) -> Option<u64> {
145 let link = u64::from_le_bytes([
146 bytes[*offset],
147 bytes[*offset + 1],
148 bytes[*offset + 2],
149 bytes[*offset + 3],
150 bytes[*offset + 4],
151 bytes[*offset + 5],
152 bytes[*offset + 6],
153 bytes[*offset + 7],
154 ]);
155 *offset += 8;
156 if link == 0 { None } else { Some(link) }
157}
158
159fn read_u64_checked(bytes: &[u8], offset: &mut usize) -> Result<u64> {
161 validate_buffer_size(bytes, *offset + 8)?;
162 let val = u64::from_le_bytes([
163 bytes[*offset],
164 bytes[*offset + 1],
165 bytes[*offset + 2],
166 bytes[*offset + 3],
167 bytes[*offset + 4],
168 bytes[*offset + 5],
169 bytes[*offset + 6],
170 bytes[*offset + 7],
171 ]);
172 *offset += 8;
173 Ok(val)
174}
175
176impl ConversionBlock {
177 #[cfg(feature = "std")]
189 pub fn resolve_all_dependencies(&mut self, file_data: &[u8]) -> Result<()> {
190 self.resolve_all_dependencies_with_address(file_data, 0)
191 }
192
193 #[cfg(feature = "std")]
195 pub fn resolve_all_dependencies_with_address(
196 &mut self,
197 file_data: &[u8],
198 current_address: u64,
199 ) -> Result<()> {
200 let mut visited = BTreeSet::new();
202 self.resolve_all_dependencies_recursive(file_data, 0, &mut visited, current_address)
203 }
204
205 #[cfg(feature = "std")]
216 fn resolve_all_dependencies_recursive(
217 &mut self,
218 file_data: &[u8],
219 depth: usize,
220 visited: &mut BTreeSet<u64>,
221 current_address: u64,
222 ) -> Result<()> {
223 use crate::blocks::common::{BlockHeader, read_string_block};
224
225 const MAX_DEPTH: usize = 20; if depth > MAX_DEPTH {
229 return Err(Error::ConversionChainTooDeep {
230 max_depth: MAX_DEPTH,
231 });
232 }
233
234 visited.insert(current_address);
236
237 self.resolve_formula(file_data)?;
239
240 let mut resolved_texts = BTreeMap::new();
242 let mut resolved_conversions = BTreeMap::new();
243 let mut default_conversion = None;
244
245 let has_default_conversion = matches!(
247 self.conversion_type,
248 crate::blocks::conversion::types::ConversionType::RangeToText );
250
251 let default_ref_index = if has_default_conversion && self.refs.len() > 2 {
253 Some(self.refs.len() - 1)
256 } else {
257 None
258 };
259
260 for (i, &link_addr) in self.refs.iter().enumerate() {
262 if link_addr == 0 {
264 continue; }
266
267 if visited.contains(&link_addr) {
269 return Err(Error::ConversionChainCycle { address: link_addr });
270 }
271
272 let offset = link_addr as usize;
273 if offset + 24 > file_data.len() {
274 continue; }
276
277 let header = BlockHeader::from_bytes(&file_data[offset..offset + 24])?;
279
280 match header.id.as_str() {
281 "##TX" => {
282 if let Some(text) = read_string_block(file_data, link_addr)? {
284 resolved_texts.insert(i, text);
285 }
286 }
287 "##CC" => {
288 let mut nested_conversion = ConversionBlock::from_bytes(&file_data[offset..])?;
290 nested_conversion.resolve_all_dependencies_recursive(
291 file_data,
292 depth + 1,
293 visited,
294 link_addr,
295 )?;
296
297 if Some(i) == default_ref_index {
299 default_conversion = Some(Box::new(nested_conversion));
300 } else {
301 resolved_conversions.insert(i, Box::new(nested_conversion));
302 }
303 }
304 _ => {
305 }
308 }
309 }
310
311 if !resolved_texts.is_empty() {
313 self.resolved_texts = Some(resolved_texts);
314 }
315 if !resolved_conversions.is_empty() {
316 self.resolved_conversions = Some(resolved_conversions);
317 }
318 if default_conversion.is_some() {
319 self.default_conversion = default_conversion;
320 }
321
322 visited.remove(¤t_address);
324
325 Ok(())
326 }
327
328 #[cfg(feature = "std")]
331 pub fn get_resolved_text(&self, ref_index: usize) -> Option<&String> {
332 self.resolved_texts.as_ref()?.get(&ref_index)
333 }
334
335 #[cfg(feature = "std")]
338 pub fn get_resolved_conversion(&self, ref_index: usize) -> Option<&ConversionBlock> {
339 self.resolved_conversions
340 .as_ref()?
341 .get(&ref_index)
342 .map(|boxed| boxed.as_ref())
343 }
344
345 pub fn get_default_conversion(&self) -> Option<&ConversionBlock> {
348 self.default_conversion.as_ref().map(|boxed| boxed.as_ref())
349 }
350
351 pub fn to_bytes(&self) -> Result<Vec<u8>> {
357 let links = 4 + self.refs.len();
358
359 let mut header = self.header.clone();
360 header.link_count = links as u64;
361
362 let mut size = 24 + links * 8 + 1 + 1 + 2 + 2 + 2;
363 if self.phys_range_min.is_some() || self.phys_range_max.is_some() {
365 size += 16;
366 }
367 size += self.values.len() * 8;
368 header.length = size as u64;
369
370 let mut buf = Vec::with_capacity(size);
371 buf.extend_from_slice(&header.to_bytes()?);
372 for link in [
373 self.name_addr,
374 self.unit_addr,
375 self.comment_addr,
376 self.inverse_addr,
377 ] {
378 buf.extend_from_slice(&link.unwrap_or(0).to_le_bytes());
379 }
380 for l in &self.refs {
381 buf.extend_from_slice(&l.to_le_bytes());
382 }
383 buf.push(self.conversion_type.to_u8());
384 buf.push(self.precision);
385 buf.extend_from_slice(&self.flags.to_le_bytes());
386 buf.extend_from_slice(&(self.ref_count).to_le_bytes());
387 buf.extend_from_slice(&(self.value_count).to_le_bytes());
388 if self.phys_range_min.is_some() || self.phys_range_max.is_some() {
390 buf.extend_from_slice(&self.phys_range_min.unwrap_or(0.0).to_le_bytes());
391 buf.extend_from_slice(&self.phys_range_max.unwrap_or(0.0).to_le_bytes());
392 }
393 for v in &self.values {
394 buf.extend_from_slice(&v.to_le_bytes());
395 }
396 if buf.len() != size {
397 return Err(Error::BlockSerializationError(format!(
398 "ConversionBlock expected size {size} but wrote {}",
399 buf.len()
400 )));
401 }
402 Ok(buf)
403 }
404
405 pub fn identity() -> Self {
417 Self {
418 header: BlockHeader {
419 id: String::from("##CC"),
420 reserved: 0,
421 length: 0, link_count: 4,
423 },
424 name_addr: None,
425 unit_addr: None,
426 comment_addr: None,
427 inverse_addr: None,
428 refs: Vec::new(),
429 conversion_type: ConversionType::Identity,
430 precision: 0,
431 flags: 0,
432 ref_count: 0,
433 value_count: 0,
434 phys_range_min: None,
435 phys_range_max: None,
436 values: Vec::new(),
437 formula: None,
438 resolved_texts: None,
439 resolved_conversions: None,
440 default_conversion: None,
441 }
442 }
443
444 pub fn linear(offset: f64, factor: f64) -> Self {
465 Self {
466 header: BlockHeader {
467 id: String::from("##CC"),
468 reserved: 0,
469 length: 0, link_count: 4,
471 },
472 name_addr: None,
473 unit_addr: None,
474 comment_addr: None,
475 inverse_addr: None,
476 refs: Vec::new(),
477 conversion_type: ConversionType::Linear,
478 precision: 0,
479 flags: 0,
480 ref_count: 0,
481 value_count: 2,
482 phys_range_min: None,
483 phys_range_max: None,
484 values: alloc::vec![offset, factor],
485 formula: None,
486 resolved_texts: None,
487 resolved_conversions: None,
488 default_conversion: None,
489 }
490 }
491
492 pub fn rational(p1: f64, p2: f64, p3: f64, p4: f64, p5: f64, p6: f64) -> Self {
512 Self {
513 header: BlockHeader {
514 id: String::from("##CC"),
515 reserved: 0,
516 length: 0,
517 link_count: 4,
518 },
519 name_addr: None,
520 unit_addr: None,
521 comment_addr: None,
522 inverse_addr: None,
523 refs: Vec::new(),
524 conversion_type: ConversionType::Rational,
525 precision: 0,
526 flags: 0,
527 ref_count: 0,
528 value_count: 6,
529 phys_range_min: None,
530 phys_range_max: None,
531 values: alloc::vec![p1, p2, p3, p4, p5, p6],
532 formula: None,
533 resolved_texts: None,
534 resolved_conversions: None,
535 default_conversion: None,
536 }
537 }
538
539 pub fn is_identity(&self) -> bool {
545 match self.conversion_type {
546 ConversionType::Identity => true,
547 ConversionType::Linear => {
548 self.values.len() >= 2 && self.values[0] == 0.0 && self.values[1] == 1.0
549 }
550 _ => false,
551 }
552 }
553
554 pub fn with_physical_range(mut self, min: f64, max: f64) -> Self {
560 self.phys_range_min = Some(min);
561 self.phys_range_max = Some(max);
562 self.flags |= 0b10; self
564 }
565}