dns_sd_native/
register.rs1use std::num::NonZeroU32;
2use thiserror::Error;
3
4#[cfg(all(unix, not(target_os = "macos")))]
5pub use crate::linux::ServiceRegistration;
6#[cfg(target_os = "macos")]
7pub use crate::macos::ServiceRegistration;
8#[cfg(target_os = "windows")]
9pub use crate::windows::ServiceRegistration;
10
11#[derive(Debug, Clone)]
15pub struct ServiceRegistrationBuilder {
16 pub(crate) service_type: String,
17 pub(crate) port: u16,
18 pub(crate) name: Option<String>,
19 pub(crate) host: Option<String>,
20 pub(crate) domain: Option<String>,
21 pub(crate) interface_index: Option<NonZeroU32>,
22 pub(crate) txt_record: Vec<(String, TxtRecordValue)>,
23}
24
25impl ServiceRegistrationBuilder {
26 pub fn new(service_type: impl AsRef<str>, port: u16) -> Self {
41 Self {
42 service_type: service_type.as_ref().to_string(),
43 port,
44 name: None,
45 host: None,
46 domain: None,
47 interface_index: None,
48 txt_record: Vec::new(),
49 }
50 }
51
52 pub fn name(&mut self, name: impl AsRef<str>) -> &mut Self {
57 self.name = Some(name.as_ref().to_string());
58 self
59 }
60
61 fn add_txt_record_key_value(&mut self, key: String, value: TxtRecordValue) {
62 self.txt_record.retain_mut(|(k, _v)| *k != key);
63 self.txt_record.push((key, value));
64 }
65
66 pub fn add_txt_record_key_empty(&mut self, key: impl AsRef<str>) -> &mut Self {
68 let key = key.as_ref().to_string();
69 self.add_txt_record_key_value(key, TxtRecordValue::KeyOnly);
70 self
71 }
72
73 pub fn add_txt_record_key_string(
77 &mut self,
78 key: impl AsRef<str>,
79 value: impl AsRef<str>,
80 ) -> &mut Self {
81 let key = key.as_ref().to_string();
82 let value = value.as_ref().to_string();
83 self.add_txt_record_key_value(key, TxtRecordValue::String(value));
84 self
85 }
86
87 #[cfg(not(target_os = "windows"))] pub fn add_txt_record_key_binary(
90 &mut self,
91 key: impl AsRef<str>,
92 value: impl AsRef<[u8]>,
93 ) -> &mut Self {
94 let key = key.as_ref().to_string();
95 let value = value.as_ref().to_vec();
96 self.add_txt_record_key_value(key, TxtRecordValue::Binary(value));
97 self
98 }
99
100 pub fn interface_index(&mut self, index: NonZeroU32) -> &mut Self {
107 self.interface_index = Some(index);
108 self
109 }
110
111 pub fn host(&mut self, host: impl AsRef<str>) -> &mut Self {
118 self.host = Some(host.as_ref().to_string());
119 self
120 }
121
122 pub fn domain(&mut self, domain: impl AsRef<str>) -> &mut Self {
127 self.domain = Some(domain.as_ref().to_string());
128 self
129 }
130
131 pub async fn register(&self) -> Result<ServiceRegistration, ServiceRegistrationError> {
133 validate_txt_records(&self.txt_record)?;
134 ServiceRegistration::new(
135 &self.service_type,
136 self.port,
137 &self.name,
138 &self.host,
139 &self.domain,
140 self.interface_index,
141 &self.txt_record,
142 )
143 .await
144 }
145}
146
147#[derive(Error, Debug)]
149pub enum ServiceRegistrationError {
150 #[error("invalid TXT record key {0:?}: {1}")]
152 InvalidTxtRecordKey(String, String),
153
154 #[error("invalid TXT record value for key {0:?}: {1}")]
156 InvalidTxtRecordValue(String, String),
157
158 #[error("parameter {0:?} contains interior nul byte at position {1}")]
160 ParameterContainsInteriorNulByte(String, usize),
161
162 #[error("interface index {0} is invalid")]
164 InvalidInterfaceIndex(u32),
165
166 #[error("hostname not set and could not be determined automatically: {0}")]
168 HostnameUnavailable(String),
169
170 #[error("DNS-SD not available on system: {0}")]
172 DnsSdUnavailable(String),
173
174 #[error("DNS-SD registration returned an error: {0}")]
176 RegistrationError(String),
177
178 #[error("registration failed: {0}")]
180 RegistrationFailed(String),
181
182 #[error("service name conflict")]
184 NameConflict,
185}
186
187#[derive(Debug, Clone)]
188pub(crate) enum TxtRecordValue {
189 KeyOnly,
190 String(String),
191 #[cfg(not(target_os = "windows"))] Binary(Vec<u8>),
193}
194
195pub(crate) fn validate_txt_records(
197 records: &[(String, TxtRecordValue)],
198) -> Result<(), ServiceRegistrationError> {
199 for (key, value) in records {
200 if key.is_empty() {
201 return Err(ServiceRegistrationError::InvalidTxtRecordKey(
202 key.clone(),
203 "key must not be empty".into(),
204 ));
205 }
206 if key.contains('=') {
207 return Err(ServiceRegistrationError::InvalidTxtRecordKey(
208 key.clone(),
209 "key must not contain '='".into(),
210 ));
211 }
212 if key.chars().any(|c| !(' '..='~').contains(&c)) {
215 return Err(ServiceRegistrationError::InvalidTxtRecordKey(
216 key.clone(),
217 "key must contain only printable US-ASCII characters (0x20-0x7E)".into(),
218 ));
219 }
220 let value_len = match value {
221 TxtRecordValue::KeyOnly => 0,
222 TxtRecordValue::String(s) => 1 + s.len(),
223 #[cfg(not(target_os = "windows"))]
224 TxtRecordValue::Binary(b) => 1 + b.len(),
225 };
226 if key.len() + value_len > 255 {
227 return Err(ServiceRegistrationError::InvalidTxtRecordValue(
228 key.clone(),
229 "key + value must not exceed 255 bytes".into(),
230 ));
231 }
232 }
233 Ok(())
234}