async_dnssd/
txt_record.rs1use std::ops::Range;
2
3#[derive(Clone)]
25pub struct TxtRecord(Vec<u8>);
26
27impl TxtRecord {
28 pub fn new() -> Self {
30 Self(Vec::new())
31 }
32
33 pub fn parse_vec(data: Vec<u8>) -> Option<Self> {
39 if data.len() == 1 && data[0] == 0 {
40 let mut data = data;
41 data.clear();
42 return Some(Self(data));
43 }
44 let mut pos = 0;
45 while pos < data.len() {
46 let len = data[pos] as usize;
47 let new_pos = pos + 1 + len;
48 if new_pos > data.len() {
49 return None;
50 }
51 pos = new_pos;
52 }
53 Some(Self(data))
54 }
55
56 pub fn parse(data: &[u8]) -> Option<Self> {
64 Self::parse_vec(data.into())
65 }
66
67 pub fn with_capacity(capacity: usize) -> Self {
71 Self(Vec::with_capacity(capacity))
72 }
73
74 pub fn reserve(&mut self, additional: usize) {
81 self.0.reserve(additional);
82 }
83
84 pub fn is_empty(&self) -> bool {
87 self.0.is_empty()
88 }
89
90 pub fn clear(&mut self) {
92 self.0.clear();
93 }
94
95 pub fn data(&self) -> &[u8] {
98 &self.0
99 }
100
101 pub fn rdata(&self) -> &[u8] {
105 if self.0.is_empty() {
106 &[0x00] } else {
108 &self.0
109 }
110 }
111
112 fn _position_keys(&self) -> PositionKeyIter<'_> {
113 PositionKeyIter {
114 pos: 0,
115 data: &self.0,
116 }
117 }
118
119 pub fn iter(&self) -> TxtRecordIter<'_> {
121 TxtRecordIter {
122 pos: 0,
123 data: &self.0,
124 }
125 }
126
127 #[allow(clippy::option_option)]
133 pub fn get(&self, key: &[u8]) -> Option<Option<&[u8]>> {
134 self.iter().find(|&(k, _)| key == k).map(|(_, value)| value)
135 }
136
137 pub fn remove(&mut self, key: &[u8]) {
139 if let Some((loc, _)) = self._position_keys().find(|&(_, k)| key == k) {
140 self.0.drain(loc);
141 }
142 }
143
144 pub fn set(&mut self, key: &[u8], value: Option<&[u8]>) -> Result<(), TxtRecordError> {
146 for &k in key {
147 if k == b'=' || !(0x20..=0x7e).contains(&k) {
148 return Err(TxtRecordError::InvalidKey);
149 }
150 }
151 let entry_len = key.len() + value.map(|v| v.len() + 1).unwrap_or(0);
152 if entry_len > 255 {
153 return Err(TxtRecordError::EntryTooLong);
154 }
155 self.remove(key);
156
157 self.0.push(entry_len as u8);
158 self.0.extend_from_slice(key);
159 if let Some(value) = value {
160 self.0.push(b'=');
161 self.0.extend_from_slice(value);
162 }
163
164 Ok(())
165 }
166
167 pub fn set_no_value(&mut self, key: &[u8]) -> Result<(), TxtRecordError> {
169 self.set(key, None)
170 }
171
172 pub fn set_value(&mut self, key: &[u8], value: &[u8]) -> Result<(), TxtRecordError> {
174 self.set(key, Some(value))
175 }
176}
177
178impl Default for TxtRecord {
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184impl<'a> IntoIterator for &'a TxtRecord {
185 type IntoIter = TxtRecordIter<'a>;
186 type Item = (&'a [u8], Option<&'a [u8]>);
187
188 fn into_iter(self) -> Self::IntoIter {
189 self.iter()
190 }
191}
192
193#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
195pub enum TxtRecordError {
196 InvalidKey,
198 EntryTooLong,
200}
201
202struct PositionKeyIter<'a> {
203 pos: usize,
204 data: &'a [u8],
205}
206
207impl<'a> Iterator for PositionKeyIter<'a> {
208 type Item = (Range<usize>, &'a [u8]);
210
211 fn next(&mut self) -> Option<Self::Item> {
212 if self.data.is_empty() {
213 return None;
214 }
215 let len = self.data[0] as usize;
216 let entry_pos = self.pos;
217 let entry = &self.data[1..][..len];
218 self.data = &self.data[len + 1..];
219 self.pos += len + 1;
220
221 Some(match entry.iter().position(|&b| b == b'=') {
222 Some(pos) => (entry_pos..self.pos, &entry[..pos]),
223 None => (entry_pos..self.pos, entry),
224 })
225 }
226}
227
228pub struct TxtRecordIter<'a> {
232 pos: usize,
233 data: &'a [u8],
234}
235
236impl<'a> Iterator for TxtRecordIter<'a> {
237 type Item = (&'a [u8], Option<&'a [u8]>);
239
240 fn next(&mut self) -> Option<Self::Item> {
241 if self.data.is_empty() {
242 return None;
243 }
244 let len = self.data[0] as usize;
245 let entry = &self.data[1..][..len];
246 self.data = &self.data[len + 1..];
247 self.pos += len + 1;
248
249 Some(match entry.iter().position(|&b| b == b'=') {
250 Some(pos) => (&entry[..pos], Some(&entry[pos + 1..])),
251 None => (entry, None),
252 })
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::TxtRecord;
259
260 #[test]
261 fn modifications() {
262 let mut r = TxtRecord::new();
263 assert!(r.is_empty());
264 assert_eq!(r.data(), b"");
265 assert_eq!(r.rdata(), b"\x00");
266
267 r.set(b"foo", Some(b"bar")).unwrap();
268 assert!(!r.is_empty());
269 assert_eq!(r.data(), b"\x07foo=bar");
270 assert_eq!(r.rdata(), b"\x07foo=bar");
271
272 r.set(b"u", Some(b"vw")).unwrap();
273 assert!(!r.is_empty());
274 assert_eq!(r.data(), b"\x07foo=bar\x04u=vw");
275 assert_eq!(r.rdata(), b"\x07foo=bar\x04u=vw");
276 assert_eq!(
277 r.iter().collect::<Vec<_>>(),
278 vec![
279 (b"foo" as &[u8], Some(b"bar" as &[u8])),
280 (b"u", Some(b"vw")),
281 ]
282 );
283
284 r.set(b"foo", None).unwrap();
285 assert!(!r.is_empty());
286 assert_eq!(r.data(), b"\x04u=vw\x03foo");
287 assert_eq!(r.rdata(), b"\x04u=vw\x03foo");
288 assert_eq!(
289 r.iter().collect::<Vec<_>>(),
290 vec![(b"u" as &[u8], Some(b"vw" as &[u8])), (b"foo", None),]
291 );
292
293 r.set(b"foo", Some(b"bar")).unwrap();
294 assert!(!r.is_empty());
295 assert_eq!(r.data(), b"\x04u=vw\x07foo=bar");
296 assert_eq!(r.rdata(), b"\x04u=vw\x07foo=bar");
297
298 r.remove(b"foo");
299 assert!(!r.is_empty());
300 assert_eq!(r.data(), b"\x04u=vw");
301 assert_eq!(r.rdata(), b"\x04u=vw");
302 }
303}