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