promptparse/
tlv.rs

1use crate::error::PromptParseError;
2use crate::utils::checksum::crc16_xmodem;
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct TlvTag {
6    /// Tag ID
7    pub id: String,
8    /// Tag Value
9    pub value: String,
10    /// Sub Tags
11    pub sub_tags: Option<Vec<TlvTag>>,
12    /// Tag Length
13    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
36/// Decode TLV string into array of TLV Tags
37pub 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
65/// Encode TLV Tags array into TLV string
66pub 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
83/// Generate CRC Checksum for provided string
84pub 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
92/// Get TLV string combined with CRC Tag
93pub 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
101/// Get Tag or Sub-tag by Tag ID in array of TLV Tags
102pub 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
119/// Create new TLV Tag
120pub 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}