Skip to main content

psdk_fruit_emapi/
tlv.rs

1use std::collections::BTreeMap;
2
3use crate::{
4  endian::{read_uint16, read_uint32, uint16, uint32},
5  error::{EmapiError, Result},
6};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct TlvEntry {
10  pub tag: u8,
11  pub value: Vec<u8>,
12}
13
14impl TlvEntry {
15  pub fn new(tag: u8, value: impl Into<Vec<u8>>) -> Self {
16    Self {
17      tag,
18      value: value.into(),
19    }
20  }
21
22  pub fn try_new(tag: u16, value: impl Into<Vec<u8>>) -> Result<Self> {
23    if tag > 0xFF {
24      return Err(EmapiError::Range {
25        name: "tag",
26        min: 0,
27        max: 0xFF,
28        value: u64::from(tag),
29      });
30    }
31    Ok(Self::new(tag as u8, value))
32  }
33
34  pub fn uint8(tag: u8, value: u8) -> Result<Self> {
35    Ok(Self::new(tag, [value]))
36  }
37
38  pub fn uint16(tag: u8, value: u16) -> Result<Self> {
39    Ok(Self::new(tag, uint16(value)))
40  }
41
42  pub fn uint32(tag: u8, value: u32) -> Result<Self> {
43    Ok(Self::new(tag, uint32(value)))
44  }
45
46  pub fn string(tag: u8, value: &str) -> Result<Self> {
47    let mut bytes = value.as_bytes().to_vec();
48    bytes.push(0x00);
49    Ok(Self::new(tag, bytes))
50  }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct TlvData {
55  entries: BTreeMap<u8, TlvEntry>,
56}
57
58impl TlvData {
59  pub fn get(&self, tag: u8) -> Option<&TlvEntry> {
60    self.entries.get(&tag)
61  }
62
63  pub fn contains_tag(&self, tag: u8) -> bool {
64    self.entries.contains_key(&tag)
65  }
66
67  pub fn uint8(&self, tag: u8) -> Result<Option<u8>> {
68    let Some(value) = self.value(tag) else {
69      return Ok(None);
70    };
71    if value.len() != 1 {
72      return Err(EmapiError::InvalidTlvType {
73        tag,
74        expected: "uint8",
75        actual_len: value.len(),
76      });
77    }
78    Ok(Some(value[0]))
79  }
80
81  pub fn uint16(&self, tag: u8) -> Result<Option<u16>> {
82    let Some(value) = self.value(tag) else {
83      return Ok(None);
84    };
85    if value.len() != 2 {
86      return Err(EmapiError::InvalidTlvType {
87        tag,
88        expected: "uint16",
89        actual_len: value.len(),
90      });
91    }
92    Ok(Some(read_uint16(value, 0)?))
93  }
94
95  pub fn uint32(&self, tag: u8) -> Result<Option<u32>> {
96    let Some(value) = self.value(tag) else {
97      return Ok(None);
98    };
99    if value.len() != 4 {
100      return Err(EmapiError::InvalidTlvType {
101        tag,
102        expected: "uint32",
103        actual_len: value.len(),
104      });
105    }
106    Ok(Some(read_uint32(value, 0)?))
107  }
108
109  pub fn string(&self, tag: u8) -> Result<Option<String>> {
110    let Some(value) = self.value(tag) else {
111      return Ok(None);
112    };
113    let end = if value.last() == Some(&0x00) {
114      value.len() - 1
115    } else {
116      value.len()
117    };
118    String::from_utf8(value[..end].to_vec())
119      .map(Some)
120      .map_err(|_| EmapiError::InvalidUtf8)
121  }
122
123  fn value(&self, tag: u8) -> Option<&[u8]> {
124    self.entries.get(&tag).map(|entry| entry.value.as_slice())
125  }
126}
127
128pub struct Tlv;
129
130impl Tlv {
131  pub fn encode(entries: &[TlvEntry]) -> Result<Vec<u8>> {
132    let mut result = Vec::new();
133    for entry in entries {
134      if entry.value.len() > usize::from(u16::MAX) {
135        return Err(EmapiError::Range {
136          name: "length",
137          min: 0,
138          max: u64::from(u16::MAX),
139          value: entry.value.len() as u64,
140        });
141      }
142      result.push(entry.tag);
143      result.extend_from_slice(&uint16(entry.value.len() as u16));
144      result.extend_from_slice(&entry.value);
145    }
146    Ok(result)
147  }
148
149  pub fn decode(data: &[u8]) -> Result<TlvData> {
150    let mut entries = BTreeMap::new();
151    let mut offset = 0;
152    while offset < data.len() {
153      if offset + 3 > data.len() {
154        return Err(EmapiError::TruncatedTlvHeader);
155      }
156      let tag = data[offset];
157      let length = usize::from(read_uint16(data, offset + 1)?);
158      let value_start = offset + 3;
159      let value_end = value_start + length;
160      if value_end > data.len() {
161        return Err(EmapiError::TruncatedTlvValue);
162      }
163      entries.insert(
164        tag,
165        TlvEntry::new(tag, data[value_start..value_end].to_vec()),
166      );
167      offset = value_end;
168    }
169    Ok(TlvData { entries })
170  }
171}