async_dnssd/
txt_record.rs

1use std::ops::Range;
2
3/// Key-Value container that uses DNS `TXT` RDATA as representation
4///
5/// The binary representation can be used as RDATA for `DNS-SD TXT
6/// Records` (see [RFC 6763, section 6]).
7///
8/// Each entry results in one string in the `TXT` represenation; `TXT`
9/// RDATA contains many (but at least one) possibly empty strings, each
10/// up to 255 bytes.
11///
12/// Key and value are separated by the first `=` in an entry, and the
13/// key must consist of printable ASCII characters (0x20...0x7E) apart
14/// from `=`.  Keys should be 9 characters or fewer.
15///
16/// Values can be any binary string (but the total length of an entry
17/// cannot exceed 255 bytes).
18///
19/// An entry also can have no value at all (which is different from
20/// having an empty value) if there is no `=` separator in the entry.
21///
22/// [RFC 6763, section 6]: https://tools.ietf.org/html/rfc6763#section-6
23///     "RFC 6763, 6. Data Syntax for DNS-SD TXT Records"
24#[derive(Clone)]
25pub struct TxtRecord(Vec<u8>);
26
27impl TxtRecord {
28	/// Constructs a new, empty `TxtRecord`.
29	pub fn new() -> Self {
30		Self(Vec::new())
31	}
32
33	/// Parse binary blob as TXT RDATA
34	///
35	/// Same as [`parse`] but takes ownership of buffer.
36	///
37	/// [`parse`]: #method.parse
38	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	/// Parse some binary blob as TXT RDATA
57	///
58	/// A single empty string (encoded as `0x00`) gets decoded as "empty" `TxtRecord` (i.e. the
59	/// reverse th `rdata()`); an empty slice is treated the same, although it wouldn't be valid
60	/// RDATA.
61	///
62	/// This only fails when the length of a chunk exceeds the remaining data.
63	pub fn parse(data: &[u8]) -> Option<Self> {
64		Self::parse_vec(data.into())
65	}
66
67	/// Constructs a new, empty `TxtRecord` with the specified capacity.
68	///
69	/// The inserting operations will still reallocate if necessary.
70	pub fn with_capacity(capacity: usize) -> Self {
71		Self(Vec::with_capacity(capacity))
72	}
73
74	/// Reserves capacity for at least `additional` more bytes to be
75	/// used by inserting operations.
76	///
77	/// Each entry requires 1 byte for the total length, the length
78	/// of the key for the key; if there is a value 1 byte for the
79	/// separator `=` and the length of the value for the value.
80	pub fn reserve(&mut self, additional: usize) {
81		self.0.reserve(additional);
82	}
83
84	/// Returns `true` if the `TxtRecord` contains no elements (both in
85	/// bytes and key-value entries).
86	pub fn is_empty(&self) -> bool {
87		self.0.is_empty()
88	}
89
90	/// Clears the `TxtRecord`, removing all entries.
91	pub fn clear(&mut self) {
92		self.0.clear();
93	}
94
95	/// if not empty this returns valid TXT RDATA, otherwise just an
96	/// empty slice.
97	pub fn data(&self) -> &[u8] {
98		&self.0
99	}
100
101	/// always returns valid TXT RDATA; when the container is empty it
102	/// will return a TXT record with a single empty string (i.e.
103	/// `&[0x00]`).
104	pub fn rdata(&self) -> &[u8] {
105		if self.0.is_empty() {
106			&[0x00] // empty RDATA not allowed, use single empty chunk instead
107		} 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	/// Iterate over all `(key, value)` pairs.
120	pub fn iter(&self) -> TxtRecordIter<'_> {
121		TxtRecordIter {
122			pos: 0,
123			data: &self.0,
124		}
125	}
126
127	/// Get value for entry with given key
128	///
129	/// Returns `None` if there is no such entry, `Some(None)` if the
130	/// entry exists but has no value, and `Some(Some(value))` if the
131	/// entry exists and has a value.
132	#[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	/// Remove entry with given key (if it exists)
138	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	/// Insert or update the entry with `key` to have the given value or on value
145	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	/// Insert or update the entry with `key` to have no value
168	pub fn set_no_value(&mut self, key: &[u8]) -> Result<(), TxtRecordError> {
169		self.set(key, None)
170	}
171
172	/// Insert or update the entry with `key` to have the given value
173	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/// Error returned when inserting new entries failed
194#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
195pub enum TxtRecordError {
196	/// Key contained invalid characters
197	InvalidKey,
198	/// Total entry would be longer than 255 bytes
199	EntryTooLong,
200}
201
202struct PositionKeyIter<'a> {
203	pos: usize,
204	data: &'a [u8],
205}
206
207impl<'a> Iterator for PositionKeyIter<'a> {
208	// (start..end, key)
209	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
228/// Iterator for entries in `TxtRecord`
229///
230/// Items are `(key, value)` pairs.
231pub struct TxtRecordIter<'a> {
232	pos: usize,
233	data: &'a [u8],
234}
235
236impl<'a> Iterator for TxtRecordIter<'a> {
237	// key, value
238	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}