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