1use crate::error::PromptParseError;
2use crate::utils::checksum::crc16_xmodem;
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct TlvTag {
6 pub id: String,
8 pub value: String,
10 pub sub_tags: Option<Vec<TlvTag>>,
12 pub length: usize,
14}
15
16impl TlvTag {
17 pub fn new(id: String, value: String) -> Self {
18 Self {
19 length: value.len(),
20 id,
21 value,
22 sub_tags: None,
23 }
24 }
25
26 pub fn with_sub_tags(id: String, value: String, sub_tags: Vec<TlvTag>) -> Self {
27 Self {
28 length: value.len(),
29 id,
30 value,
31 sub_tags: Some(sub_tags),
32 }
33 }
34}
35
36pub fn decode(payload: &str) -> Result<Vec<TlvTag>, PromptParseError> {
38 let mut tags = Vec::new();
39 let mut idx = 0;
40
41 while idx < payload.len() {
42 if idx + 4 > payload.len() {
43 break;
44 }
45
46 let id = payload[idx..idx + 2].to_string();
47 let length_str = &payload[idx + 2..idx + 4];
48 let length = length_str
49 .parse::<usize>()
50 .map_err(|_| PromptParseError::InvalidTlv)?;
51
52 if idx + 4 + length > payload.len() {
53 return Err(PromptParseError::InvalidTlv);
54 }
55
56 let value = payload[idx + 4..idx + 4 + length].to_string();
57
58 tags.push(TlvTag::new(id, value));
59 idx += 4 + length;
60 }
61
62 Ok(tags)
63}
64
65pub fn encode(tags: &[TlvTag]) -> String {
67 let mut payload = String::new();
68
69 for tag in tags {
70 payload.push_str(&tag.id);
71 payload.push_str(&format!("{:02}", tag.length));
72
73 if let Some(sub_tags) = &tag.sub_tags {
74 payload.push_str(&encode(sub_tags));
75 } else {
76 payload.push_str(&tag.value);
77 }
78 }
79
80 payload
81}
82
83pub fn checksum(payload: &str, upper_case: bool) -> String {
85 let mut sum = format!("{:x}", crc16_xmodem(payload, 0xffff));
86 if upper_case {
87 sum = sum.to_uppercase();
88 }
89 format!("{sum:0>4}")
90}
91
92pub fn with_crc_tag(payload: &str, crc_tag_id: &str, upper_case: bool) -> String {
94 let mut result = payload.to_string();
95 result.push_str(&format!("{crc_tag_id:0>2}"));
96 result.push_str("04");
97 result.push_str(&checksum(&result, upper_case));
98 result
99}
100
101pub fn get_tag<'a>(
103 tlv_tags: &'a [TlvTag],
104 tag_id: &str,
105 sub_tag_id: Option<&str>,
106) -> Option<&'a TlvTag> {
107 let tag = tlv_tags.iter().find(|t| t.id == tag_id)?;
108
109 if let Some(sub_id) = sub_tag_id {
110 if let Some(sub_tags) = &tag.sub_tags {
111 return sub_tags.iter().find(|s| s.id == sub_id);
112 }
113 return None;
114 }
115
116 Some(tag)
117}
118
119pub fn tag(tag_id: &str, value: &str) -> TlvTag {
121 TlvTag::new(tag_id.to_string(), value.to_string())
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_tlv_encode_decode() {
130 let tags = vec![tag("00", "01"), tag("01", "11"), tag("02", "test")];
131
132 let encoded = encode(&tags);
133 let decoded = decode(&encoded).unwrap();
134
135 assert_eq!(decoded.len(), 3);
136 assert_eq!(decoded[0].id, "00");
137 assert_eq!(decoded[0].value, "01");
138 assert_eq!(decoded[1].id, "01");
139 assert_eq!(decoded[1].value, "11");
140 assert_eq!(decoded[2].id, "02");
141 assert_eq!(decoded[2].value, "test");
142 }
143
144 #[test]
145 fn test_checksum() {
146 let payload =
147 "00020101021229370016A0000006770101110113006680111111153037645802TH540520.156304";
148 let result = checksum(payload, true);
149 assert_eq!(result, "42BE");
150 }
151
152 #[test]
153 fn test_with_crc_tag() {
154 let payload = "00020101021129370016A0000006770101110113006681222333353037645802TH";
155 let result = with_crc_tag(payload, "63", true);
156 assert_eq!(
157 result,
158 "00020101021129370016A0000006770101110113006681222333353037645802TH63041DCF"
159 );
160 }
161
162 #[test]
163 fn test_get_tag() {
164 let tags = vec![tag("00", "01"), tag("01", "11"), tag("02", "test")];
165
166 let found = get_tag(&tags, "01", None).unwrap();
167 assert_eq!(found.value, "11");
168
169 let not_found = get_tag(&tags, "99", None);
170 assert!(not_found.is_none());
171 }
172}