1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
use std::collections::HashMap;
use std::fmt;
use std::str::Utf8Error;
use crate::ZeroconfError;
/// Struct containing the entries for TXT records associated with a service
///
/// # Examples
/// ```
/// # tokio_test::block_on(async {
/// let mut txt = async_zeroconf::TxtRecord::new();
/// txt.add("key1".to_string(), "value1".to_string());
/// txt.add("key2".to_string(), "value2".to_string());
/// let service_ref = async_zeroconf::Service::new_with_txt("Server", "_http._tcp", 80, txt)
/// .publish().await?;
/// # Ok::<(), async_zeroconf::ZeroconfError>(())
/// # });
/// ```
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TxtRecord {
records: HashMap<String, Vec<u8>>,
}
impl TxtRecord {
/// Create a new TXT record collection
pub fn new() -> Self {
TxtRecord {
records: HashMap::new(),
}
}
/// Add an entry from a string
pub fn add(&mut self, k: String, v: String) {
self.records.insert(k, v.as_bytes().to_vec());
}
/// Add an entry from a slice of u8's
pub fn add_vec(&mut self, k: String, v: Vec<u8>) {
self.records.insert(k, v);
}
/// Get Iterator
///
/// # Examples
/// ```
/// let mut txt = async_zeroconf::TxtRecord::new();
/// txt.add("key".to_string(), "value".to_string());
/// // Iterator
/// let iter = txt.iter();
/// for (k, v) in iter {
/// println!("{}, {:?}", k, v);
/// }
/// ```
pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<u8>)> {
self.records.iter()
}
/// Get Iterator including conversion to string. As the conversion to a
/// UTF-8 string could fail the value is returned as a `Result`.
///
/// # Examples
/// ```
/// let mut txt = async_zeroconf::TxtRecord::new();
/// txt.add("key".to_string(), "value".to_string());
/// // String iterator
/// let iter = txt.iter_string();
/// for (k, v) in iter {
/// match v {
/// Ok(v) => println!("{}, {}", k, v),
/// Err(_) => println!("{} not valid UTF-8", k)
/// }
/// }
/// ```
pub fn iter_string(&self) -> impl Iterator<Item = (&String, Result<&str, Utf8Error>)> {
self.records
.iter()
.map(|(k, v)| (k, std::str::from_utf8(v)))
}
/// Get Iterator including conversion to string. If the conversion to UTF-8
/// fails, '�' will be returned instead.
///
/// # Examples
/// ```
/// let mut txt = async_zeroconf::TxtRecord::new();
/// txt.add("key".to_string(), "value".to_string());
/// // String iterator
/// let iter = txt.iter_string_lossy();
/// for (k, v) in iter {
/// println!("{}, {}", k, v);
/// }
/// ```
pub fn iter_string_lossy(&self) -> impl Iterator<Item = (&String, &str)> {
self.records
.iter()
.map(|(k, v)| (k, std::str::from_utf8(v).unwrap_or("�")))
}
/// Validate if this TXT record collection contains all valid values.
/// This checks that the key is 9 characters or less, the value is 255
/// characters or less and that the key only has printable ASCII characters
/// excluding '='.
///
/// # Examples
/// ```
/// let mut valid_txt = async_zeroconf::TxtRecord::new();
/// valid_txt.add("key".to_string(), "value".to_string());
/// assert!(valid_txt.validate().is_ok());
///
/// let mut invalid_txt = async_zeroconf::TxtRecord::new();
/// invalid_txt.add("k\0".to_string(), "value".to_string());
/// assert!(invalid_txt.validate().is_err());
/// ```
pub fn validate(&self) -> Result<(), ZeroconfError> {
for (k, v) in self.iter() {
let all_printable_ascii = k.chars().all(|c| (0x20..=0x7E).contains(&(c as u32)));
if k.len() > 9 || v.len() > 255 || k.contains('=') || !all_printable_ascii {
return Err(ZeroconfError::InvalidTxtRecord(format!(
"{}={}",
k,
String::from_utf8_lossy(v)
)));
}
}
Ok(())
}
/// Empty if no records are associated
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
}
impl Default for TxtRecord {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for TxtRecord {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = "{".to_string();
let mut first = true;
for (k, v) in self.iter_string_lossy() {
if !first {
string.push_str(", ");
}
string.push_str(format!("\"{}\": \"{}\"", k, v).as_str());
first = false;
}
string.push('}');
write!(f, "{}", string)
}
}