1#[cfg(feature = "std")]
2use prettytable::{format, row, Table};
3use wireless_mbus_link_layer::WirelessFrame;
4
5use crate::user_data;
6use crate::MbusError;
7use wired_mbus_link_layer as frames;
8use wireless_mbus_link_layer;
9
10#[cfg_attr(
11 feature = "serde",
12 derive(serde::Serialize),
13 serde(bound(deserialize = "'de: 'a"))
14)]
15#[derive(Debug)]
16pub struct MbusData<'a, F> {
17 pub frame: F,
18 pub user_data: Option<user_data::UserDataBlock<'a>>,
19 pub data_records: Option<user_data::DataRecords<'a>>,
20}
21
22impl<'a> TryFrom<&'a [u8]> for MbusData<'a, frames::WiredFrame<'a>> {
23 type Error = MbusError;
24
25 fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
26 let frame = frames::WiredFrame::try_from(data)?;
27 let mut user_data = None;
28 let mut data_records = None;
29 match &frame {
30 frames::WiredFrame::LongFrame { data, .. } => {
31 if let Ok(x) = user_data::UserDataBlock::try_from(*data) {
32 user_data = Some(x);
33 if let Ok(user_data::UserDataBlock::VariableDataStructureWithLongTplHeader {
34 long_tpl_header: _,
35 variable_data_block,
36 ..
37 }) = user_data::UserDataBlock::try_from(*data)
38 {
39 data_records = Some(variable_data_block.into());
40 }
41 }
42 }
43 frames::WiredFrame::SingleCharacter { .. } => (),
44 frames::WiredFrame::ShortFrame { .. } => (),
45 frames::WiredFrame::ControlFrame { .. } => (),
46 _ => (),
47 };
48
49 Ok(MbusData {
50 frame,
51 user_data,
52 data_records,
53 })
54 }
55}
56
57impl<'a> TryFrom<&'a [u8]> for MbusData<'a, WirelessFrame<'a>> {
58 type Error = MbusError;
59
60 fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
61 let frame = wireless_mbus_link_layer::WirelessFrame::try_from(data)?;
62 let mut user_data = None;
63 let mut data_records = None;
64 let wireless_mbus_link_layer::WirelessFrame { data, .. } = &frame;
66
67 if let Ok(user_data_block) = user_data::UserDataBlock::try_from(*data) {
68 match &user_data_block {
69 user_data::UserDataBlock::VariableDataStructureWithLongTplHeader {
70 variable_data_block,
71 ..
72 } => {
73 data_records = Some((*variable_data_block).into());
74 }
75 user_data::UserDataBlock::VariableDataStructureWithShortTplHeader {
76 variable_data_block,
77 ..
78 } => {
79 data_records = Some((*variable_data_block).into());
80 }
81 _ => {}
82 }
83 user_data = Some(user_data_block);
84 }
85
86 Ok(MbusData {
87 frame,
88 user_data,
89 data_records,
90 })
91 }
92}
93
94#[cfg(feature = "std")]
95fn clean_and_convert(input: &str) -> Vec<u8> {
96 use core::str;
97 let input = input.trim();
98 let cleaned_data: String = input.replace("0x", "").replace([' ', ',', 'x'], "");
99
100 cleaned_data
101 .as_bytes()
102 .chunks(2)
103 .map(|chunk| {
104 let byte_str = str::from_utf8(chunk).unwrap_or_default();
105 u8::from_str_radix(byte_str, 16).unwrap_or_default()
106 })
107 .collect()
108}
109
110#[cfg(feature = "std")]
111#[must_use]
112pub fn serialize_mbus_data(data: &str, format: &str, key: Option<&[u8; 16]>) -> String {
113 match format {
114 "json" => parse_to_json(data, key),
115 "yaml" => parse_to_yaml(data, key),
116 "csv" => parse_to_csv(data, key).to_string(),
117 "mermaid" => parse_to_mermaid(data, key),
118 _ => parse_to_table(data, key).to_string(),
119 }
120}
121
122#[cfg(feature = "std")]
123#[must_use]
124pub fn parse_to_json(input: &str, key: Option<&[u8; 16]>) -> String {
125 use user_data::UserDataBlock;
126
127 let data = clean_and_convert(input);
128 let mut decrypted_buffer = [0u8; 256];
130 let mut decrypted_len = 0usize;
131
132 if let Ok(mut parsed_data) = MbusData::<frames::WiredFrame>::try_from(data.as_slice()) {
134 #[cfg(feature = "decryption")]
135 if let Some(key_bytes) = key {
136 if let Some(user_data) = &parsed_data.user_data {
137 if let UserDataBlock::VariableDataStructureWithLongTplHeader {
138 long_tpl_header,
139 ..
140 } = user_data
141 {
142 if long_tpl_header.is_encrypted() {
143 if let Ok(mfr) = &long_tpl_header.manufacturer {
144 let mut provider = crate::decryption::StaticKeyProvider::<1>::new();
145 let mfr_id = mfr.to_id();
146 let id_num = long_tpl_header.identification_number.number;
147 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
148 if let Ok(len) =
149 user_data.decrypt_variable_data(&provider, &mut decrypted_buffer)
150 {
151 decrypted_len = len;
152 }
153 }
154 }
155 }
156 }
157 }
158 #[cfg(not(feature = "decryption"))]
159 let _ = key;
160
161 #[cfg(feature = "decryption")]
163 if decrypted_len > 0 {
164 if let Some(UserDataBlock::VariableDataStructureWithLongTplHeader {
165 long_tpl_header,
166 ..
167 }) = &parsed_data.user_data
168 {
169 if let Some(decrypted_data) = decrypted_buffer.get(..decrypted_len) {
170 parsed_data.data_records = Some(user_data::DataRecords::new(
171 decrypted_data,
172 Some(long_tpl_header),
173 ));
174 }
175 }
176 }
177
178 let mfr_code_str = parsed_data.user_data.as_ref().and_then(|ud| {
179 if let UserDataBlock::VariableDataStructureWithLongTplHeader {
180 long_tpl_header, ..
181 } = ud
182 {
183 long_tpl_header
184 .manufacturer
185 .as_ref()
186 .ok()
187 .map(|m| format!("{}", m))
188 } else {
189 None
190 }
191 });
192 let mut json_val = serde_json::to_value(&parsed_data).unwrap_or_default();
193 if let (Some(code), serde_json::Value::Object(ref mut map)) = (mfr_code_str, &mut json_val)
194 {
195 if let Some(info) = crate::manufacturers::lookup_manufacturer(&code) {
196 map.insert(
197 "manufacturer_info".to_string(),
198 serde_json::json!({
199 "name": info.name,
200 "website": info.website,
201 "description": info.description,
202 }),
203 );
204 }
205 }
206 return serde_json::to_string_pretty(&json_val)
207 .unwrap_or_default()
208 .to_string();
209 }
210
211 let mut crc_buf = [0u8; 512];
213 let wireless_data =
214 wireless_mbus_link_layer::strip_format_a_crcs(&data, &mut crc_buf).unwrap_or(&data);
215 if let Ok(mut parsed_data) =
216 MbusData::<wireless_mbus_link_layer::WirelessFrame>::try_from(wireless_data)
217 {
218 #[cfg(feature = "decryption")]
219 {
220 let mut long_header_for_records: Option<&user_data::LongTplHeader> = None;
221 if let Some(key_bytes) = key {
222 let manufacturer_id = &parsed_data.frame.manufacturer_id;
223 if let Some(user_data) = &parsed_data.user_data {
224 let (is_encrypted, long_header) = match user_data {
225 UserDataBlock::VariableDataStructureWithLongTplHeader {
226 long_tpl_header,
227 ..
228 } => (long_tpl_header.is_encrypted(), Some(long_tpl_header)),
229 UserDataBlock::VariableDataStructureWithShortTplHeader {
230 short_tpl_header,
231 ..
232 } => (short_tpl_header.is_encrypted(), None),
233 _ => (false, None),
234 };
235 long_header_for_records = long_header;
236
237 if is_encrypted {
238 let mut provider = crate::decryption::StaticKeyProvider::<1>::new();
239
240 let decrypt_result = match user_data {
241 UserDataBlock::VariableDataStructureWithLongTplHeader {
242 long_tpl_header,
243 ..
244 } => {
245 if let Ok(mfr) = &long_tpl_header.manufacturer {
246 let mfr_id = mfr.to_id();
247 let id_num = long_tpl_header.identification_number.number;
248 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
249 user_data
250 .decrypt_variable_data(&provider, &mut decrypted_buffer)
251 } else {
252 Err(crate::decryption::DecryptionError::DecryptionFailed)
253 }
254 }
255 UserDataBlock::VariableDataStructureWithShortTplHeader { .. } => {
256 let mfr_id = manufacturer_id.manufacturer_code.to_id();
257 let id_num = manufacturer_id.identification_number.number;
258 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
259 user_data.decrypt_variable_data_with_context(
260 &provider,
261 manufacturer_id.manufacturer_code,
262 id_num,
263 manufacturer_id.version,
264 manufacturer_id.device_type,
265 &mut decrypted_buffer,
266 )
267 }
268 _ => Err(crate::decryption::DecryptionError::UnknownEncryptionState),
269 };
270
271 if let Ok(len) = decrypt_result {
272 decrypted_len = len;
273 }
274 }
275 }
276 }
277
278 if let Some(decrypted_data) = decrypted_buffer.get(..decrypted_len) {
280 if !decrypted_data.is_empty() {
281 parsed_data.data_records = Some(user_data::DataRecords::new(
282 decrypted_data,
283 long_header_for_records,
284 ));
285 }
286 }
287 }
288 #[cfg(not(feature = "decryption"))]
289 let _ = key;
290
291 let mfr_code_str = format!("{}", parsed_data.frame.manufacturer_id.manufacturer_code);
292 let mut json_val = serde_json::to_value(&parsed_data).unwrap_or_default();
293 if let serde_json::Value::Object(ref mut map) = json_val {
294 if let Some(info) = crate::manufacturers::lookup_manufacturer(&mfr_code_str) {
295 map.insert(
296 "manufacturer_info".to_string(),
297 serde_json::json!({
298 "name": info.name,
299 "website": info.website,
300 "description": info.description,
301 }),
302 );
303 }
304 }
305 return serde_json::to_string_pretty(&json_val)
306 .unwrap_or_default()
307 .to_string();
308 }
309
310 "{}".to_string()
312}
313
314#[cfg(feature = "std")]
315#[must_use]
316fn parse_to_yaml(input: &str, key: Option<&[u8; 16]>) -> String {
317 use user_data::UserDataBlock;
318
319 let data = clean_and_convert(input);
320 let mut decrypted_buffer = [0u8; 256];
322 let mut decrypted_len = 0usize;
323
324 if let Ok(mut parsed_data) = MbusData::<frames::WiredFrame>::try_from(data.as_slice()) {
326 #[cfg(feature = "decryption")]
327 if let Some(key_bytes) = key {
328 if let Some(user_data) = &parsed_data.user_data {
329 if let UserDataBlock::VariableDataStructureWithLongTplHeader {
330 long_tpl_header,
331 ..
332 } = user_data
333 {
334 if long_tpl_header.is_encrypted() {
335 if let Ok(mfr) = &long_tpl_header.manufacturer {
336 let mut provider = crate::decryption::StaticKeyProvider::<1>::new();
337 let mfr_id = mfr.to_id();
338 let id_num = long_tpl_header.identification_number.number;
339 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
340 if let Ok(len) =
341 user_data.decrypt_variable_data(&provider, &mut decrypted_buffer)
342 {
343 decrypted_len = len;
344 }
345 }
346 }
347 }
348 }
349 }
350 #[cfg(not(feature = "decryption"))]
351 let _ = key;
352
353 #[cfg(feature = "decryption")]
355 if decrypted_len > 0 {
356 if let Some(UserDataBlock::VariableDataStructureWithLongTplHeader {
357 long_tpl_header,
358 ..
359 }) = &parsed_data.user_data
360 {
361 let decrypted_data = decrypted_buffer.get(..decrypted_len).unwrap_or(&[]);
362 parsed_data.data_records = Some(user_data::DataRecords::new(
363 decrypted_data,
364 Some(long_tpl_header),
365 ));
366 }
367 }
368
369 let mfr_code_str = parsed_data.user_data.as_ref().and_then(|ud| {
370 if let UserDataBlock::VariableDataStructureWithLongTplHeader {
371 long_tpl_header, ..
372 } = ud
373 {
374 long_tpl_header
375 .manufacturer
376 .as_ref()
377 .ok()
378 .map(|m| format!("{}", m))
379 } else {
380 None
381 }
382 });
383 let base = serde_yaml::to_string(&parsed_data).unwrap_or_default();
384 return if let Some(code) = mfr_code_str {
385 if let Some(info) = crate::manufacturers::lookup_manufacturer(&code) {
386 format!(
387 "{}manufacturer_info:\n name: {}\n website: {}\n description: {}\n",
388 base, info.name, info.website, info.description
389 )
390 } else {
391 base
392 }
393 } else {
394 base
395 };
396 }
397
398 let mut crc_buf = [0u8; 512];
400 let wireless_data =
401 wireless_mbus_link_layer::strip_format_a_crcs(&data, &mut crc_buf).unwrap_or(&data);
402 if let Ok(mut parsed_data) =
403 MbusData::<wireless_mbus_link_layer::WirelessFrame>::try_from(wireless_data)
404 {
405 #[cfg(feature = "decryption")]
406 {
407 let mut long_header_for_records: Option<&user_data::LongTplHeader> = None;
408 if let Some(key_bytes) = key {
409 let manufacturer_id = &parsed_data.frame.manufacturer_id;
410 if let Some(user_data) = &parsed_data.user_data {
411 let (is_encrypted, long_header) = match user_data {
412 UserDataBlock::VariableDataStructureWithLongTplHeader {
413 long_tpl_header,
414 ..
415 } => (long_tpl_header.is_encrypted(), Some(long_tpl_header)),
416 UserDataBlock::VariableDataStructureWithShortTplHeader {
417 short_tpl_header,
418 ..
419 } => (short_tpl_header.is_encrypted(), None),
420 _ => (false, None),
421 };
422 long_header_for_records = long_header;
423
424 if is_encrypted {
425 let mut provider = crate::decryption::StaticKeyProvider::<1>::new();
426
427 let decrypt_result = match user_data {
428 UserDataBlock::VariableDataStructureWithLongTplHeader {
429 long_tpl_header,
430 ..
431 } => {
432 if let Ok(mfr) = &long_tpl_header.manufacturer {
433 let mfr_id = mfr.to_id();
434 let id_num = long_tpl_header.identification_number.number;
435 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
436 user_data
437 .decrypt_variable_data(&provider, &mut decrypted_buffer)
438 } else {
439 Err(crate::decryption::DecryptionError::DecryptionFailed)
440 }
441 }
442 UserDataBlock::VariableDataStructureWithShortTplHeader { .. } => {
443 let mfr_id = manufacturer_id.manufacturer_code.to_id();
444 let id_num = manufacturer_id.identification_number.number;
445 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
446 user_data.decrypt_variable_data_with_context(
447 &provider,
448 manufacturer_id.manufacturer_code,
449 id_num,
450 manufacturer_id.version,
451 manufacturer_id.device_type,
452 &mut decrypted_buffer,
453 )
454 }
455 _ => Err(crate::decryption::DecryptionError::UnknownEncryptionState),
456 };
457
458 if let Ok(len) = decrypt_result {
459 decrypted_len = len;
460 }
461 }
462 }
463 }
464
465 if decrypted_len > 0 {
467 let decrypted_data = decrypted_buffer.get(..decrypted_len).unwrap_or(&[]);
468 parsed_data.data_records = Some(user_data::DataRecords::new(
469 decrypted_data,
470 long_header_for_records,
471 ));
472 }
473 }
474 #[cfg(not(feature = "decryption"))]
475 let _ = key;
476
477 let mfr_code_str = format!("{}", parsed_data.frame.manufacturer_id.manufacturer_code);
478 let base = serde_yaml::to_string(&parsed_data).unwrap_or_default();
479 return if let Some(info) = crate::manufacturers::lookup_manufacturer(&mfr_code_str) {
480 format!(
481 "{}manufacturer_info:\n name: {}\n website: {}\n description: {}\n",
482 base, info.name, info.website, info.description
483 )
484 } else {
485 base
486 };
487 }
488
489 "---\nerror: Could not parse data\n".to_string()
491}
492
493#[cfg(feature = "std")]
494#[must_use]
495fn parse_to_table(input: &str, key: Option<&[u8; 16]>) -> String {
496 use user_data::UserDataBlock;
497
498 let data = clean_and_convert(input);
499
500 let mut table_output = String::new();
501
502 if let Ok(parsed_data) = MbusData::<frames::WiredFrame>::try_from(data.as_slice()) {
504 let mut table = Table::new();
505 table.set_format(*format::consts::FORMAT_BOX_CHARS);
506
507 match parsed_data.frame {
508 frames::WiredFrame::LongFrame {
509 function,
510 address,
511 data: _,
512 } => {
513 table_output.push_str("Long Frame \n");
514
515 table.set_titles(row!["Function", "Address"]);
516 table.add_row(row![function, address]);
517
518 table_output.push_str(&table.to_string());
519 let mut _is_encyrpted = false;
520 if let Some(UserDataBlock::VariableDataStructureWithLongTplHeader {
521 long_tpl_header,
522 variable_data_block: _,
523 ..
524 }) = &parsed_data.user_data
525 {
526 let mut info_table = Table::new();
527 info_table.set_format(*format::consts::FORMAT_BOX_CHARS);
528 info_table.set_titles(row!["Field", "Value"]);
529 info_table.add_row(row![
530 "Identification Number",
531 long_tpl_header.identification_number
532 ]);
533 {
534 let mfr_str = long_tpl_header
535 .manufacturer
536 .as_ref()
537 .map_or_else(|e| format!("Error: {}", e), |m| format!("{}", m));
538 info_table.add_row(row!["Manufacturer", &mfr_str]);
539 if let Some(info) = crate::manufacturers::lookup_manufacturer(&mfr_str) {
540 info_table.add_row(row!["Manufacturer Name", info.name]);
541 info_table.add_row(row!["Website", info.website]);
542 info_table.add_row(row!["Description", info.description]);
543 }
544 }
545 info_table.add_row(row![
546 "Access Number",
547 long_tpl_header.short_tpl_header.access_number
548 ]);
549 info_table.add_row(row!["Status", long_tpl_header.short_tpl_header.status]);
550 info_table.add_row(row![
551 "Security Mode",
552 long_tpl_header
553 .short_tpl_header
554 .configuration_field
555 .security_mode()
556 ]);
557 info_table.add_row(row!["Version", long_tpl_header.version]);
558 info_table.add_row(row!["DeviceType", long_tpl_header.device_type]);
559 table_output.push_str(&info_table.to_string());
560 _is_encyrpted = long_tpl_header.is_encrypted();
561 }
562 let mut value_table = Table::new();
563 value_table.set_format(*format::consts::FORMAT_BOX_CHARS);
564 value_table.set_titles(row!["Value", "Data Information", "Header Hex", "Data Hex"]);
565 if let Some(data_records) = parsed_data.data_records {
566 for record in data_records.flatten() {
567 let value_information = match record
568 .data_record_header
569 .processed_data_record_header
570 .value_information
571 {
572 Some(ref x) => format!("{}", x),
573 None => ")".to_string(),
574 };
575 let data_information = match record
576 .data_record_header
577 .processed_data_record_header
578 .data_information
579 {
580 Some(ref x) => format!("{}", x),
581 None => "None".to_string(),
582 };
583 value_table.add_row(row![
584 format!("({}{}", record.data, value_information),
585 data_information,
586 record.data_record_header_hex(),
587 record.data_hex()
588 ]);
589 }
590 }
591 table_output.push_str(&value_table.to_string());
592 }
593 frames::WiredFrame::ShortFrame { .. } => {
594 table_output.push_str("Short Frame\n");
595 }
596 frames::WiredFrame::SingleCharacter { .. } => {
597 table_output.push_str("Single Character Frame\n");
598 }
599 frames::WiredFrame::ControlFrame { .. } => {
600 table_output.push_str("Control Frame\n");
601 }
602 _ => {
603 table_output.push_str("Unknown Frame\n");
604 }
605 }
606 return table_output;
607 }
608
609 let mut crc_buf = [0u8; 512];
611 let wireless_data =
612 wireless_mbus_link_layer::strip_format_a_crcs(&data, &mut crc_buf).unwrap_or(&data);
613 if let Ok(parsed_data) =
614 MbusData::<wireless_mbus_link_layer::WirelessFrame>::try_from(wireless_data)
615 {
616 let wireless_mbus_link_layer::WirelessFrame {
617 function,
618 manufacturer_id,
619 data,
620 } = &parsed_data.frame;
621 {
622 let mut table = Table::new();
623 table.set_format(*format::consts::FORMAT_BOX_CHARS);
624 table.set_titles(row!["Field", "Value"]);
625 table.add_row(row!["Function", format!("{:?}", function)]);
626 {
627 let mfr_str = format!("{}", manufacturer_id.manufacturer_code);
628 table.add_row(row!["Manufacturer Code", &mfr_str]);
629 if let Some(info) = crate::manufacturers::lookup_manufacturer(&mfr_str) {
630 table.add_row(row!["Manufacturer Name", info.name]);
631 table.add_row(row!["Website", info.website]);
632 table.add_row(row!["Description", info.description]);
633 }
634 }
635 table.add_row(row![
636 "Identification Number",
637 format!("{:?}", manufacturer_id.identification_number)
638 ]);
639 table.add_row(row![
640 "Device Type",
641 format!("{:?}", manufacturer_id.device_type)
642 ]);
643 table.add_row(row!["Version", format!("{:?}", manufacturer_id.version)]);
644 table.add_row(row![
645 "Is globally Unique Id",
646 format!("{:?}", manufacturer_id.is_unique_globally)
647 ]);
648 table_output.push_str(&table.to_string());
649 let mut is_encrypted = false;
650 match &parsed_data.user_data {
651 Some(UserDataBlock::VariableDataStructureWithLongTplHeader {
652 long_tpl_header,
653 variable_data_block: _,
654 extended_link_layer: _,
655 }) => {
656 let mut info_table = Table::new();
657 info_table.set_format(*format::consts::FORMAT_BOX_CHARS);
658 info_table.set_titles(row!["Field", "Value"]);
659 info_table.add_row(row![
660 "Identification Number",
661 long_tpl_header.identification_number
662 ]);
663 {
664 let mfr_str = long_tpl_header
665 .manufacturer
666 .as_ref()
667 .map_or_else(|e| format!("Error: {}", e), |m| format!("{}", m));
668 info_table.add_row(row!["Manufacturer", &mfr_str]);
669 if let Some(info) = crate::manufacturers::lookup_manufacturer(&mfr_str) {
670 info_table.add_row(row!["Manufacturer Name", info.name]);
671 info_table.add_row(row!["Website", info.website]);
672 info_table.add_row(row!["Description", info.description]);
673 }
674 }
675 info_table.add_row(row![
676 "Access Number",
677 long_tpl_header.short_tpl_header.access_number
678 ]);
679 info_table.add_row(row!["Status", long_tpl_header.short_tpl_header.status]);
680 info_table.add_row(row![
681 "Security Mode",
682 long_tpl_header
683 .short_tpl_header
684 .configuration_field
685 .security_mode()
686 ]);
687 info_table.add_row(row!["Version", long_tpl_header.version]);
688 info_table.add_row(row!["Device Type", long_tpl_header.device_type]);
689 table_output.push_str(&info_table.to_string());
690 is_encrypted = long_tpl_header.is_encrypted();
691 }
692 Some(UserDataBlock::VariableDataStructureWithShortTplHeader {
693 short_tpl_header,
694 variable_data_block: _,
695 extended_link_layer: _,
696 }) => {
697 let mut info_table = Table::new();
698 info_table.set_format(*format::consts::FORMAT_BOX_CHARS);
699 info_table.set_titles(row!["Field", "Value"]);
700 info_table.add_row(row!["Access Number", short_tpl_header.access_number]);
701 info_table.add_row(row!["Status", short_tpl_header.status]);
702 info_table.add_row(row![
703 "Security Mode",
704 short_tpl_header.configuration_field.security_mode()
705 ]);
706 table_output.push_str(&info_table.to_string());
707 is_encrypted = short_tpl_header.is_encrypted();
708 }
709 _ => (),
710 }
711
712 let mut value_table = Table::new();
713 value_table.set_format(*format::consts::FORMAT_BOX_CHARS);
714 value_table.set_titles(row!["Value", "Data Information", "Header Hex", "Data Hex"]);
715
716 if is_encrypted {
717 #[cfg(feature = "decryption")]
718 if let Some(key_bytes) = key {
719 if let Some(user_data) = &parsed_data.user_data {
721 let mut decrypted = [0u8; 256];
722 let mut provider = crate::decryption::StaticKeyProvider::<1>::new();
723
724 let decrypt_result = match user_data {
726 UserDataBlock::VariableDataStructureWithLongTplHeader {
727 long_tpl_header,
728 ..
729 } => {
730 if let Ok(mfr) = &long_tpl_header.manufacturer {
731 let mfr_id = mfr.to_id();
732 let id_num = long_tpl_header.identification_number.number;
733 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
734 user_data.decrypt_variable_data(&provider, &mut decrypted)
735 } else {
736 Err(crate::decryption::DecryptionError::DecryptionFailed)
737 }
738 }
739 UserDataBlock::VariableDataStructureWithShortTplHeader { .. } => {
740 let mfr_id = manufacturer_id.manufacturer_code.to_id();
742 let id_num = manufacturer_id.identification_number.number;
743 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
744 user_data.decrypt_variable_data_with_context(
745 &provider,
746 manufacturer_id.manufacturer_code,
747 id_num,
748 manufacturer_id.version,
749 manufacturer_id.device_type,
750 &mut decrypted,
751 )
752 }
753 _ => Err(crate::decryption::DecryptionError::UnknownEncryptionState),
754 };
755
756 match decrypt_result {
757 Ok(len) => {
758 table_output.push_str("Decrypted successfully\n");
759 let decrypted_data = decrypted.get(..len).unwrap_or(&[]);
761 let long_header = match user_data {
763 UserDataBlock::VariableDataStructureWithLongTplHeader {
764 long_tpl_header,
765 ..
766 } => Some(long_tpl_header),
767 _ => None,
768 };
769 let data_records =
770 user_data::DataRecords::new(decrypted_data, long_header);
771 for record in data_records.flatten() {
772 let value_information = match record
773 .data_record_header
774 .processed_data_record_header
775 .value_information
776 {
777 Some(ref x) => format!("{}", x),
778 None => ")".to_string(),
779 };
780 let data_information = match record
781 .data_record_header
782 .processed_data_record_header
783 .data_information
784 {
785 Some(ref x) => format!("{}", x),
786 None => "None".to_string(),
787 };
788 value_table.add_row(row![
789 format!("({}{}", record.data, value_information),
790 data_information,
791 record.data_record_header_hex(),
792 record.data_hex()
793 ]);
794 }
795 table_output.push_str(&value_table.to_string());
796 }
797 Err(e) => {
798 table_output.push_str(&format!("Decryption failed: {:?}\n", e));
799 table_output.push_str("Encrypted Payload : ");
800 table_output.push_str(
801 &data
802 .iter()
803 .map(|b| format!("{:02X}", b))
804 .collect::<String>(),
805 );
806 table_output.push('\n');
807 }
808 }
809 }
810 } else {
811 table_output.push_str("Encrypted Payload : ");
812 table_output.push_str(
813 &data
814 .iter()
815 .map(|b| format!("{:02X}", b))
816 .collect::<String>(),
817 );
818 table_output.push('\n');
819 }
820
821 #[cfg(not(feature = "decryption"))]
822 {
823 let _ = key; table_output.push_str("Encrypted Payload : ");
825 table_output.push_str(
826 &data
827 .iter()
828 .map(|b| format!("{:02X}", b))
829 .collect::<String>(),
830 );
831 table_output.push('\n');
832 }
833 } else {
834 if let Some(data_records) = &parsed_data.data_records {
835 for record in data_records.clone().flatten() {
836 let value_information = match record
837 .data_record_header
838 .processed_data_record_header
839 .value_information
840 {
841 Some(ref x) => format!("{}", x),
842 None => ")".to_string(),
843 };
844 let data_information = match record
845 .data_record_header
846 .processed_data_record_header
847 .data_information
848 {
849 Some(ref x) => format!("{}", x),
850 None => "None".to_string(),
851 };
852 value_table.add_row(row![
853 format!("({}{}", record.data, value_information),
854 data_information,
855 record.data_record_header_hex(),
856 record.data_hex()
857 ]);
858 }
859 }
860 table_output.push_str(&value_table.to_string());
861 }
862 }
863 return table_output;
864 }
865
866 "Error: Could not parse data as wired or wireless M-Bus".to_string()
868}
869
870#[cfg(feature = "std")]
871#[must_use]
872pub fn parse_to_csv(input: &str, key: Option<&[u8; 16]>) -> String {
873 use crate::user_data::UserDataBlock;
874 use prettytable::csv;
875
876 let data = clean_and_convert(input);
877 let mut decrypted_buffer = [0u8; 256];
879 let mut decrypted_len = 0usize;
880
881 let mut writer = csv::Writer::from_writer(vec![]);
882
883 if let Ok(mut parsed_data) = MbusData::<frames::WiredFrame>::try_from(data.as_slice()) {
885 #[cfg(feature = "decryption")]
886 if let Some(key_bytes) = key {
887 if let Some(user_data) = &parsed_data.user_data {
888 if let UserDataBlock::VariableDataStructureWithLongTplHeader {
889 long_tpl_header,
890 ..
891 } = user_data
892 {
893 if long_tpl_header.is_encrypted() {
894 if let Ok(mfr) = &long_tpl_header.manufacturer {
895 let mut provider = crate::decryption::StaticKeyProvider::<1>::new();
896 let mfr_id = mfr.to_id();
897 let id_num = long_tpl_header.identification_number.number;
898 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
899 if let Ok(len) =
900 user_data.decrypt_variable_data(&provider, &mut decrypted_buffer)
901 {
902 decrypted_len = len;
903 }
904 }
905 }
906 }
907 }
908 }
909 #[cfg(not(feature = "decryption"))]
910 let _ = key;
911
912 #[cfg(feature = "decryption")]
914 if decrypted_len > 0 {
915 if let Some(UserDataBlock::VariableDataStructureWithLongTplHeader {
916 long_tpl_header,
917 ..
918 }) = &parsed_data.user_data
919 {
920 let decrypted_data = decrypted_buffer.get(..decrypted_len).unwrap_or(&[]);
921 parsed_data.data_records = Some(user_data::DataRecords::new(
922 decrypted_data,
923 Some(long_tpl_header),
924 ));
925 }
926 }
927
928 match parsed_data.frame {
929 frames::WiredFrame::LongFrame {
930 function, address, ..
931 } => {
932 let data_point_count = parsed_data
933 .data_records
934 .as_ref()
935 .map(|records| records.clone().flatten().count())
936 .unwrap_or(0);
937
938 let mut headers = vec![
939 "FrameType".to_string(),
940 "Function".to_string(),
941 "Address".to_string(),
942 "Identification Number".to_string(),
943 "Manufacturer".to_string(),
944 "Access Number".to_string(),
945 "Status".to_string(),
946 "Security Mode".to_string(),
947 "Version".to_string(),
948 "Device Type".to_string(),
949 ];
950
951 for i in 1..=data_point_count {
952 headers.push(format!("DataPoint{}_Value", i));
953 headers.push(format!("DataPoint{}_Info", i));
954 }
955
956 let header_refs: Vec<&str> = headers.iter().map(|s| s.as_str()).collect();
957 writer
958 .write_record(header_refs)
959 .map_err(|_| ())
960 .unwrap_or_default();
961
962 let mut row = vec![
963 "LongFrame".to_string(),
964 function.to_string(),
965 address.to_string(),
966 ];
967
968 match &parsed_data.user_data {
969 Some(UserDataBlock::VariableDataStructureWithLongTplHeader {
970 long_tpl_header,
971 ..
972 }) => {
973 row.extend_from_slice(&[
974 long_tpl_header.identification_number.to_string(),
975 long_tpl_header
976 .manufacturer
977 .as_ref()
978 .map_or_else(|e| format!("Error: {}", e), |m| format!("{}", m)),
979 long_tpl_header.short_tpl_header.access_number.to_string(),
980 long_tpl_header.short_tpl_header.status.to_string(),
981 long_tpl_header
982 .short_tpl_header
983 .configuration_field
984 .security_mode()
985 .to_string(),
986 long_tpl_header.version.to_string(),
987 long_tpl_header.device_type.to_string(),
988 ]);
989 }
990 Some(UserDataBlock::FixedDataStructure {
991 identification_number,
992 access_number,
993 status,
994 ..
995 }) => {
996 row.extend_from_slice(&[
997 identification_number.to_string(),
998 "".to_string(), access_number.to_string(),
1000 status.to_string(),
1001 "".to_string(), "".to_string(), "".to_string(), ]);
1005 }
1006 _ => {
1007 for _ in 0..7 {
1009 row.push("".to_string());
1010 }
1011 }
1012 }
1013
1014 if let Some(data_records) = parsed_data.data_records {
1015 for record in data_records.flatten() {
1016 let parsed_value = format!("{}", record.data);
1018
1019 let value_information = match record
1021 .data_record_header
1022 .processed_data_record_header
1023 .value_information
1024 {
1025 Some(x) => format!("{}", x),
1026 None => ")".to_string(),
1027 };
1028
1029 let formatted_value = format!("({}{}", parsed_value, value_information);
1031
1032 let data_information = match record
1033 .data_record_header
1034 .processed_data_record_header
1035 .data_information
1036 {
1037 Some(x) => format!("{}", x),
1038 None => "None".to_string(),
1039 };
1040
1041 row.push(formatted_value);
1042 row.push(data_information);
1043 }
1044 }
1045
1046 let row_refs: Vec<&str> = row.iter().map(|s| s.as_str()).collect();
1047 writer
1048 .write_record(row_refs)
1049 .map_err(|_| ())
1050 .unwrap_or_default();
1051 }
1052 _ => {
1053 writer
1054 .write_record(["FrameType"])
1055 .map_err(|_| ())
1056 .unwrap_or_default();
1057 writer
1058 .write_record([format!("{:?}", parsed_data.frame).as_str()])
1059 .map_err(|_| ())
1060 .unwrap_or_default();
1061 }
1062 }
1063
1064 let csv_data = writer.into_inner().unwrap_or_default();
1065 return String::from_utf8(csv_data)
1066 .unwrap_or_else(|_| "Error converting CSV data to string".to_string());
1067 }
1068
1069 let mut crc_buf = [0u8; 512];
1071 let wireless_data =
1072 wireless_mbus_link_layer::strip_format_a_crcs(&data, &mut crc_buf).unwrap_or(&data);
1073 if let Ok(mut parsed_data) =
1074 MbusData::<wireless_mbus_link_layer::WirelessFrame>::try_from(wireless_data)
1075 {
1076 decrypted_len = 0;
1078
1079 #[cfg(feature = "decryption")]
1080 {
1081 let mut long_header_for_records: Option<&user_data::LongTplHeader> = None;
1082 if let Some(key_bytes) = key {
1083 let manufacturer_id = &parsed_data.frame.manufacturer_id;
1084 if let Some(user_data) = &parsed_data.user_data {
1085 let (is_encrypted, long_header) = match user_data {
1086 UserDataBlock::VariableDataStructureWithLongTplHeader {
1087 long_tpl_header,
1088 ..
1089 } => (long_tpl_header.is_encrypted(), Some(long_tpl_header)),
1090 UserDataBlock::VariableDataStructureWithShortTplHeader {
1091 short_tpl_header,
1092 ..
1093 } => (short_tpl_header.is_encrypted(), None),
1094 _ => (false, None),
1095 };
1096 long_header_for_records = long_header;
1097
1098 if is_encrypted {
1099 let mut provider = crate::decryption::StaticKeyProvider::<1>::new();
1100
1101 let decrypt_result = match user_data {
1102 UserDataBlock::VariableDataStructureWithLongTplHeader {
1103 long_tpl_header,
1104 ..
1105 } => {
1106 if let Ok(mfr) = &long_tpl_header.manufacturer {
1107 let mfr_id = mfr.to_id();
1108 let id_num = long_tpl_header.identification_number.number;
1109 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
1110 user_data
1111 .decrypt_variable_data(&provider, &mut decrypted_buffer)
1112 } else {
1113 Err(crate::decryption::DecryptionError::DecryptionFailed)
1114 }
1115 }
1116 UserDataBlock::VariableDataStructureWithShortTplHeader { .. } => {
1117 let mfr_id = manufacturer_id.manufacturer_code.to_id();
1118 let id_num = manufacturer_id.identification_number.number;
1119 let _ = provider.add_key(mfr_id, id_num, *key_bytes);
1120 user_data.decrypt_variable_data_with_context(
1121 &provider,
1122 manufacturer_id.manufacturer_code,
1123 id_num,
1124 manufacturer_id.version,
1125 manufacturer_id.device_type,
1126 &mut decrypted_buffer,
1127 )
1128 }
1129 _ => Err(crate::decryption::DecryptionError::UnknownEncryptionState),
1130 };
1131
1132 if let Ok(len) = decrypt_result {
1133 decrypted_len = len;
1134 }
1135 }
1136 }
1137 }
1138
1139 if decrypted_len > 0 {
1141 let decrypted_data = decrypted_buffer.get(..decrypted_len).unwrap_or(&[]);
1142 parsed_data.data_records = Some(user_data::DataRecords::new(
1143 decrypted_data,
1144 long_header_for_records,
1145 ));
1146 }
1147 }
1148
1149 let frame_type = "Wireless";
1150
1151 let data_point_count = parsed_data
1152 .data_records
1153 .as_ref()
1154 .map(|records| records.clone().flatten().count())
1155 .unwrap_or(0);
1156
1157 let mut headers = vec![
1158 "FrameType".to_string(),
1159 "Identification Number".to_string(),
1160 "Manufacturer".to_string(),
1161 "Access Number".to_string(),
1162 "Status".to_string(),
1163 "Security Mode".to_string(),
1164 "Version".to_string(),
1165 "Device Type".to_string(),
1166 ];
1167
1168 for i in 1..=data_point_count {
1169 headers.push(format!("DataPoint{}_Value", i));
1170 headers.push(format!("DataPoint{}_Info", i));
1171 }
1172
1173 let header_refs: Vec<&str> = headers.iter().map(|s| s.as_str()).collect();
1174 writer
1175 .write_record(header_refs)
1176 .map_err(|_| ())
1177 .unwrap_or_default();
1178
1179 let mut row = vec![frame_type.to_string()];
1180
1181 match &parsed_data.user_data {
1182 Some(UserDataBlock::VariableDataStructureWithLongTplHeader {
1183 long_tpl_header,
1184 variable_data_block: _,
1185 extended_link_layer: _,
1186 }) => {
1187 row.extend_from_slice(&[
1188 long_tpl_header.identification_number.to_string(),
1189 long_tpl_header
1190 .manufacturer
1191 .as_ref()
1192 .map_or_else(|e| format!("Err({:?})", e), |m| format!("{:?}", m)),
1193 long_tpl_header.short_tpl_header.access_number.to_string(),
1194 long_tpl_header.short_tpl_header.status.to_string(),
1195 long_tpl_header
1196 .short_tpl_header
1197 .configuration_field
1198 .security_mode()
1199 .to_string(),
1200 long_tpl_header.version.to_string(),
1201 long_tpl_header.device_type.to_string(),
1202 ]);
1203 }
1204 _ => {
1205 for _ in 0..7 {
1207 row.push("".to_string());
1208 }
1209 }
1210 }
1211
1212 if let Some(data_records) = &parsed_data.data_records {
1213 for record in data_records.clone().flatten() {
1214 let parsed_value = format!("{}", record.data);
1215 let value_information = match record
1216 .data_record_header
1217 .processed_data_record_header
1218 .value_information
1219 {
1220 Some(x) => format!("{}", x),
1221 None => ")".to_string(),
1222 };
1223 let formatted_value = format!("({}{}", parsed_value, value_information);
1224 let data_information = match record
1225 .data_record_header
1226 .processed_data_record_header
1227 .data_information
1228 {
1229 Some(x) => format!("{}", x),
1230 None => "None".to_string(),
1231 };
1232 row.push(formatted_value);
1233 row.push(data_information);
1234 }
1235 }
1236
1237 let row_refs: Vec<&str> = row.iter().map(|s| s.as_str()).collect();
1238 writer
1239 .write_record(row_refs)
1240 .map_err(|_| ())
1241 .unwrap_or_default();
1242
1243 let csv_data = writer.into_inner().unwrap_or_default();
1244 return String::from_utf8(csv_data)
1245 .unwrap_or_else(|_| "Error converting CSV data to string".to_string());
1246 }
1247
1248 writer
1250 .write_record(["Error"])
1251 .map_err(|_| ())
1252 .unwrap_or_default();
1253 writer
1254 .write_record(["Error parsing data as wired or wireless M-Bus"])
1255 .map_err(|_| ())
1256 .unwrap_or_default();
1257
1258 let csv_data = writer.into_inner().unwrap_or_default();
1259 String::from_utf8(csv_data)
1260 .unwrap_or_else(|_| "Error converting CSV data to string".to_string())
1261}
1262
1263#[cfg(feature = "std")]
1264#[must_use]
1265pub fn parse_to_mermaid(input: &str, _key: Option<&[u8; 16]>) -> String {
1266 use user_data::UserDataBlock;
1267
1268 const MAX_PER_ROW: usize = 4;
1269 const RECORD_COLORS: &[(&str, &str)] = &[
1271 ("#1565c0", "#fff"),
1272 ("#2e7d32", "#fff"),
1273 ("#e65100", "#fff"),
1274 ("#6a1b9a", "#fff"),
1275 ("#c62828", "#fff"),
1276 ("#00695c", "#fff"),
1277 ("#f9a825", "#000"),
1278 ("#4527a0", "#fff"),
1279 ];
1280
1281 let data = clean_and_convert(input);
1282
1283 if let Ok(parsed_data) = MbusData::<frames::WiredFrame>::try_from(data.as_slice()) {
1285 let mut out = String::from("flowchart TD\n");
1286 let mut styles = String::new();
1287
1288 match parsed_data.frame {
1289 frames::WiredFrame::LongFrame {
1290 function,
1291 address,
1292 data: _,
1293 } => {
1294 out.push_str(" subgraph FRAME_SG[\"Frame Header\"]\n");
1296 out.push_str("");
1297 out.push_str(&format!(
1298 " FTYPE[\"Long Frame\"]\n FUNC[\"Function: {}\"]\n ADDR[\"Address: {}\"]\n",
1299 mermaid_escape(&format!("{}", function)),
1300 mermaid_escape(&format!("{}", address))
1301 ));
1302 let (chains, pads) =
1303 mermaid_centered_chains(&["FTYPE", "FUNC", "ADDR"], MAX_PER_ROW, "FP");
1304 out.push_str(&chains);
1305 out.push_str(" end\n");
1306 styles.push_str(&pads);
1307 styles.push_str(" style FRAME_SG fill:#2e86c1,color:#fff,stroke:#1a5276\n");
1308 styles.push_str(" style FTYPE fill:#2980b9,color:#fff,stroke:#1a5276\n");
1309 styles.push_str(" style FUNC fill:#2980b9,color:#fff,stroke:#1a5276\n");
1310 styles.push_str(" style ADDR fill:#2980b9,color:#fff,stroke:#1a5276\n");
1311
1312 if let Some(UserDataBlock::VariableDataStructureWithLongTplHeader {
1313 long_tpl_header,
1314 ..
1315 }) = &parsed_data.user_data
1316 {
1317 let mfr = long_tpl_header
1318 .manufacturer
1319 .as_ref()
1320 .map_or_else(|e| format!("Error: {}", e), |m| format!("{}", m));
1321
1322 let mfr_info = crate::manufacturers::lookup_manufacturer(&mfr);
1323 out.push_str(" subgraph DEV_SG[\"Device Info\"]\n");
1324 out.push_str("");
1325 out.push_str(&format!(
1326 " DEV1[\"ID: {}\"]\n",
1327 mermaid_escape(&format!("{}", long_tpl_header.identification_number))
1328 ));
1329 out.push_str(&format!(
1330 " DEV2[\"Manufacturer: {}\"]\n",
1331 mermaid_escape(&mfr)
1332 ));
1333 out.push_str(&format!(
1334 " DEV3[\"Version: {}\"]\n",
1335 long_tpl_header.version
1336 ));
1337 out.push_str(&format!(
1338 " DEV4[\"Device Type: {}\"]\n",
1339 mermaid_escape(&format!("{:?}", long_tpl_header.device_type))
1340 ));
1341 out.push_str(&format!(
1342 " DEV5[\"Access Number: {}\"]\n",
1343 long_tpl_header.short_tpl_header.access_number
1344 ));
1345 out.push_str(&format!(
1346 " DEV6[\"Status: {}\"]\n",
1347 mermaid_escape(&format!("{}", long_tpl_header.short_tpl_header.status))
1348 ));
1349 let mut dev_node_count = 6usize;
1350 if let Some(ref info) = mfr_info {
1351 dev_node_count += 1;
1352 out.push_str(&format!(
1353 " DEV{}[\"Name: {}\"]\n",
1354 dev_node_count,
1355 mermaid_escape(info.name)
1356 ));
1357 dev_node_count += 1;
1358 out.push_str(&format!(
1359 " DEV{}[\"Website: {}\"]\n",
1360 dev_node_count,
1361 mermaid_escape(info.website)
1362 ));
1363 dev_node_count += 1;
1364 out.push_str(&format!(
1365 " DEV{}[\"{}\"]\n",
1366 dev_node_count,
1367 mermaid_escape(info.description)
1368 ));
1369 }
1370 let dev_ids: Vec<String> =
1371 (1..=dev_node_count).map(|i| format!("DEV{}", i)).collect();
1372 let dev_id_refs: Vec<&str> = dev_ids.iter().map(|s| s.as_str()).collect();
1373 let (chains, pads) = mermaid_centered_chains(&dev_id_refs, MAX_PER_ROW, "DP");
1374 out.push_str(&chains);
1375 out.push_str(" end\n");
1376 styles.push_str(&pads);
1377 styles.push_str(" style DEV_SG fill:#1e8449,color:#fff,stroke:#145a32\n");
1378 for i in 1..=dev_node_count {
1379 styles.push_str(&format!(
1380 " style DEV{} fill:#27ae60,color:#fff,stroke:#145a32\n",
1381 i
1382 ));
1383 }
1384
1385 out.push_str(" FRAME_SG --> DEV_SG\n");
1386 }
1387
1388 if let Some(data_records) = parsed_data.data_records {
1389 out.push_str(" subgraph REC_SG[\"Data Records\"]\n");
1390 out.push_str("");
1391 let records: Vec<_> = data_records.flatten().collect();
1392 for (i, record) in records.iter().enumerate() {
1393 let value_information = match record
1394 .data_record_header
1395 .processed_data_record_header
1396 .value_information
1397 {
1398 Some(ref x) => format!("{}", x),
1399 None => String::new(),
1400 };
1401 let label = format!("({}{}", record.data, value_information);
1402 out.push_str(&format!(" R{}[\"{}\"]\n", i, mermaid_escape(&label)));
1403 let (fill, text) = RECORD_COLORS
1404 .get(i % RECORD_COLORS.len())
1405 .copied()
1406 .unwrap_or(("#888", "#fff"));
1407 styles.push_str(&format!(
1408 " style R{} fill:{},color:{},stroke:#333\n",
1409 i, fill, text
1410 ));
1411 }
1412 let ids: Vec<String> = (0..records.len()).map(|i| format!("R{}", i)).collect();
1413 let id_refs: Vec<&str> = ids.iter().map(|s| s.as_str()).collect();
1414 let (chains, pads) = mermaid_centered_chains(&id_refs, MAX_PER_ROW, "RP");
1415 out.push_str(&chains);
1416 out.push_str(" end\n");
1417 styles.push_str(" style REC_SG fill:#6c3483,color:#fff,stroke:#4a235a\n");
1418 styles.push_str(&pads);
1419 out.push_str(" DEV_SG --> REC_SG\n");
1420 }
1421 }
1422 frames::WiredFrame::ShortFrame { function, address } => {
1423 out.push_str(" subgraph FRAME_SG[\"Short Frame\"]\n");
1424 out.push_str(&format!(
1425 " FUNC[\"Function: {}\"]\n ADDR[\"Address: {}\"]\n",
1426 mermaid_escape(&format!("{}", function)),
1427 mermaid_escape(&format!("{}", address))
1428 ));
1429 out.push_str(" end\n");
1430 styles.push_str(" style FRAME_SG fill:#2e86c1,color:#fff,stroke:#1a5276\n");
1431 }
1432 frames::WiredFrame::SingleCharacter { character } => {
1433 out.push_str(&format!(
1434 " FRAME[\"Single Character: 0x{:02X}\"]\n",
1435 character
1436 ));
1437 styles.push_str(" style FRAME fill:#2e86c1,color:#fff,stroke:#1a5276\n");
1438 }
1439 frames::WiredFrame::ControlFrame {
1440 function, address, ..
1441 } => {
1442 out.push_str(" subgraph FRAME_SG[\"Control Frame\"]\n");
1443 out.push_str(&format!(
1444 " FUNC[\"Function: {}\"]\n ADDR[\"Address: {}\"]\n",
1445 mermaid_escape(&format!("{}", function)),
1446 mermaid_escape(&format!("{}", address))
1447 ));
1448 out.push_str(" end\n");
1449 styles.push_str(" style FRAME_SG fill:#2e86c1,color:#fff,stroke:#1a5276\n");
1450 }
1451 _ => {
1452 out.push_str(" FRAME[\"Unknown Frame\"]\n");
1453 }
1454 }
1455 out.push_str(&styles);
1456 return out;
1457 }
1458
1459 let mut crc_buf = [0u8; 512];
1461 let wireless_data =
1462 wireless_mbus_link_layer::strip_format_a_crcs(&data, &mut crc_buf).unwrap_or(&data);
1463 if let Ok(parsed_data) =
1464 MbusData::<wireless_mbus_link_layer::WirelessFrame>::try_from(wireless_data)
1465 {
1466 let wireless_mbus_link_layer::WirelessFrame {
1467 function,
1468 manufacturer_id,
1469 data: _,
1470 } = &parsed_data.frame;
1471
1472 let mut out = String::from("flowchart TD\n");
1473 let mut styles = String::new();
1474
1475 let wmfr_str = format!("{}", manufacturer_id.manufacturer_code);
1476 let wmfr_info = crate::manufacturers::lookup_manufacturer(&wmfr_str);
1477 out.push_str(" subgraph FRAME_SG[\"Wireless Frame\"]\n");
1478 out.push_str(&format!(" FUNC[\"Function: {:?}\"]\n", function));
1479 out.push_str(&format!(
1480 " MFR[\"Manufacturer: {}\"]\n",
1481 mermaid_escape(&wmfr_str)
1482 ));
1483 out.push_str(&format!(
1484 " ID[\"ID: {:?}\"]\n",
1485 manufacturer_id.identification_number
1486 ));
1487 out.push_str(&format!(
1488 " DEVT[\"Device Type: {:?}\"]\n",
1489 manufacturer_id.device_type
1490 ));
1491 out.push_str(&format!(
1492 " VER[\"Version: {:?}\"]\n",
1493 manufacturer_id.version
1494 ));
1495 let mut wframe_nodes: Vec<&str> = vec!["FUNC", "MFR", "ID", "DEVT", "VER"];
1496 if let Some(ref info) = wmfr_info {
1497 out.push_str(&format!(
1498 " MFRNAME[\"Name: {}\"]\n",
1499 mermaid_escape(info.name)
1500 ));
1501 out.push_str(&format!(
1502 " MFRWEB[\"Website: {}\"]\n",
1503 mermaid_escape(info.website)
1504 ));
1505 out.push_str(&format!(
1506 " MFRDESC[\"{}\"]\n",
1507 mermaid_escape(info.description)
1508 ));
1509 wframe_nodes.extend_from_slice(&["MFRNAME", "MFRWEB", "MFRDESC"]);
1510 }
1511 out.push_str(" end\n");
1512 styles.push_str(" style FRAME_SG fill:#2e86c1,color:#fff,stroke:#1a5276\n");
1513 for node in &wframe_nodes {
1514 styles.push_str(&format!(
1515 " style {} fill:#2980b9,color:#fff,stroke:#1a5276\n",
1516 node
1517 ));
1518 }
1519
1520 if let Some(data_records) = parsed_data.data_records {
1521 out.push_str(" subgraph REC_SG[\"Data Records\"]\n");
1522 let records: Vec<_> = data_records.flatten().collect();
1523 for (i, record) in records.iter().enumerate() {
1524 let value_information = match record
1525 .data_record_header
1526 .processed_data_record_header
1527 .value_information
1528 {
1529 Some(ref x) => format!("{}", x),
1530 None => String::new(),
1531 };
1532 let label = format!("({}{}", record.data, value_information);
1533 out.push_str(&format!(" R{}[\"{}\"]\n", i, mermaid_escape(&label)));
1534 let (fill, text) = RECORD_COLORS
1535 .get(i % RECORD_COLORS.len())
1536 .copied()
1537 .unwrap_or(("#888", "#fff"));
1538 styles.push_str(&format!(
1539 " style R{} fill:{},color:{},stroke:#333\n",
1540 i, fill, text
1541 ));
1542 }
1543 out.push_str(" end\n");
1544 styles.push_str(" style REC_SG fill:#6c3483,color:#fff,stroke:#4a235a\n");
1545 out.push_str(" FRAME_SG --> REC_SG\n");
1546 }
1547
1548 out.push_str(&styles);
1549 return out;
1550 }
1551
1552 "flowchart TD\n ERR[\"Error: Could not parse data\"]\n".to_string()
1553}
1554
1555#[cfg(feature = "std")]
1559fn mermaid_centered_chains(ids: &[&str], max_per_row: usize, pad_prefix: &str) -> (String, String) {
1560 let mut body = String::new();
1561 let mut styles = String::new();
1562 let mut pad_idx = 0usize;
1563 for chunk in ids.chunks(max_per_row) {
1564 let row: Vec<String> = if chunk.len() == max_per_row {
1565 chunk.iter().map(|s| s.to_string()).collect()
1566 } else {
1567 let padding = max_per_row - chunk.len();
1568 let left = padding / 2;
1569 let right = padding - left;
1570 let mut row: Vec<String> = Vec::new();
1571 for _ in 0..left {
1572 let id = format!("{}P{}", pad_prefix, pad_idx);
1573 body.push_str(&format!(" {}[\" \"]\n", id));
1574 styles.push_str(&format!(
1575 " style {} fill:none,stroke:none,color:none\n",
1576 id
1577 ));
1578 row.push(id);
1579 pad_idx += 1;
1580 }
1581 row.extend(chunk.iter().map(|s| s.to_string()));
1582 for _ in 0..right {
1583 let id = format!("{}P{}", pad_prefix, pad_idx);
1584 body.push_str(&format!(" {}[\" \"]\n", id));
1585 styles.push_str(&format!(
1586 " style {} fill:none,stroke:none,color:none\n",
1587 id
1588 ));
1589 row.push(id);
1590 pad_idx += 1;
1591 }
1592 row
1593 };
1594 for pair in row.windows(2) {
1596 if let (Some(a), Some(b)) = (pair.first(), pair.get(1)) {
1597 body.push_str(&format!(" {}~~~{}\n", a, b));
1598 }
1599 }
1600 }
1601 (body, styles)
1602}
1603
1604#[cfg(feature = "std")]
1605fn mermaid_escape(s: &str) -> String {
1606 s.replace('"', "#quot;")
1607 .replace('[', "#91;")
1608 .replace(']', "#93;")
1609}
1610
1611#[cfg(test)]
1612mod tests {
1613
1614 #[cfg(feature = "std")]
1615 #[test]
1616 fn test_csv_converter() {
1617 use super::parse_to_csv;
1618 let input = "68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16";
1619 let csv_output: String = parse_to_csv(input, None);
1620 println!("{}", csv_output);
1621 let yaml_output: String = super::parse_to_yaml(input, None);
1622 println!("{}", yaml_output);
1623 let json_output: String = super::parse_to_json(input, None);
1624 println!("{}", json_output);
1625 let table_output: String = super::parse_to_table(input, None);
1626 println!("{}", table_output);
1627 }
1628
1629 #[cfg(feature = "std")]
1630 #[test]
1631 fn test_csv_expected_output() {
1632 use super::parse_to_csv;
1633 let input = "68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16";
1634 let csv_output = parse_to_csv(input, None);
1635
1636 let expected = "FrameType,Function,Address,Identification Number,Manufacturer,Access Number,Status,Security Mode,Version,Device Type,DataPoint1_Value,DataPoint1_Info,DataPoint2_Value,DataPoint2_Info,DataPoint3_Value,DataPoint3_Info,DataPoint4_Value,DataPoint4_Info,DataPoint5_Value,DataPoint5_Info,DataPoint6_Value,DataPoint6_Info,DataPoint7_Value,DataPoint7_Info,DataPoint8_Value,DataPoint8_Info,DataPoint9_Value,DataPoint9_Info,DataPoint10_Value,DataPoint10_Info\nLongFrame,\"RspUd (ACD: false, DFC: false)\",Primary (1),02205100,SLB,0,\"Permanent error, Manufacturer specific 3\",No encryption used,2,Heat Meter (Return),(0)e4[Wh],\"0,Inst,32-bit Integer\",(3)e-1[m³](Volume),\"0,Inst,BCD 8-digit\",(0)e3[W],\"0,Inst,BCD 6-digit\",(0)e-3[m³h⁻¹],\"0,Inst,BCD 6-digit\",(1288)e-1[°C],\"0,Inst,BCD 4-digit\",(516)e-1[°C],\"0,Inst,BCD 4-digit\",(7723)e-2[°K],\"0,Inst,BCD 6-digit\",(12/Jan/12)(Date),\"0,Inst,Date Type G\",(3383)[day],\"0,Inst,16-bit Integer\",\"(Manufacturer Specific: [15, 96, 0])\",None\n";
1637
1638 assert_eq!(csv_output, expected);
1639 }
1640
1641 #[cfg(feature = "std")]
1642 #[test]
1643 fn test_yaml_expected_output() {
1644 use super::parse_to_yaml;
1645 let input = "68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16";
1646 let yaml_output = parse_to_yaml(input, None);
1647
1648 let expected_start = "frame: !LongFrame\n function: !RspUd\n acd: false\n dfc: false\n address: !Primary 1\nuser_data: !VariableDataStructureWithLongTplHeader\n";
1650
1651 assert!(yaml_output.starts_with(expected_start));
1652 assert!(yaml_output.contains("device_type: HeatMeterReturn"));
1654 assert!(yaml_output.contains("identification_number:"));
1655 assert!(yaml_output.contains("status: PERMANENT_ERROR | MANUFACTURER_SPECIFIC_3"));
1656 }
1657
1658 #[cfg(feature = "std")]
1659 #[test]
1660 fn test_json_expected_output() {
1661 use super::parse_to_json;
1662 let input = "68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16";
1663 let json_output = parse_to_json(input, None);
1664
1665 assert!(json_output.contains("\"Ok\""));
1667 assert!(json_output.contains("\"LongFrame\""));
1668 assert!(json_output.contains("\"RspUd\""));
1669 assert!(json_output.contains("\"number\": 2205100"));
1670 assert!(json_output.contains("\"device_type\": \"HeatMeterReturn\""));
1671 assert!(json_output.contains("\"status\": \"PERMANENT_ERROR | MANUFACTURER_SPECIFIC_3\""));
1672
1673 let json_parsed = serde_json::from_str::<serde_json::Value>(&json_output);
1675 assert!(json_parsed.is_ok());
1676 }
1677
1678 #[cfg(feature = "std")]
1679 #[test]
1680 fn test_mermaid_expected_output() {
1681 use super::parse_to_mermaid;
1682 let input = "68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16";
1683 let mermaid_output = parse_to_mermaid(input, None);
1684
1685 assert!(mermaid_output.starts_with("flowchart TD\n"));
1686 assert!(mermaid_output.contains("Long Frame"));
1687 assert!(mermaid_output.contains("Device Info"));
1688 assert!(mermaid_output.contains("Data Records"));
1689 assert!(mermaid_output.contains("02205100"));
1690 assert!(mermaid_output.contains("SLB"));
1691 }
1692
1693 #[cfg(feature = "std")]
1694 #[test]
1695 fn test_table_expected_output() {
1696 use super::parse_to_table;
1697 let input = "68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16";
1698 let table_output = parse_to_table(input, None);
1699
1700 assert!(table_output.starts_with("Long Frame"));
1702
1703 assert!(table_output.contains("RspUd (ACD: false, DFC: false)"));
1705 assert!(table_output.contains("Primary (1)"));
1706 assert!(table_output.contains("Identification Number"));
1707 assert!(table_output.contains("02205100"));
1708 assert!(table_output.contains("SLB"));
1709
1710 assert!(table_output.contains("(0)e4[Wh]"));
1712 assert!(table_output.contains("(3)e-1[m³](Volume)"));
1713 assert!(table_output.contains("(1288)e-1[°C]"));
1714 assert!(table_output.contains("(12/Jan/12)(Date)"));
1715 assert!(table_output.contains("(3383)[day]"));
1716 }
1717}