async_dnssd/service/
register.rs

1use std::{
2	future::Future,
3	io,
4	os::raw::{
5		c_char,
6		c_void,
7	},
8	pin::Pin,
9	task::{
10		Context,
11		Poll,
12	},
13};
14
15use crate::{
16	cstr,
17	dns_consts::Type,
18	ffi,
19	inner,
20	interface::Interface,
21};
22
23type CallbackFuture = crate::future::ServiceFuture<inner::SharedService, RegisterResult>;
24
25bitflags::bitflags! {
26	/// Flags used to register service
27	#[derive(Default)]
28	pub struct RegisterFlags: ffi::DNSServiceFlags {
29		/// Indicates a name conflict should not get handled automatically.
30		///
31		/// See [`kDNSServiceFlagsNoAutoRename`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsnoautorename).
32		const NO_AUTO_RENAME = ffi::FLAGS_NO_AUTO_RENAME;
33
34		/// Indicates there might me multiple records with the given name, type and class.
35		///
36		/// See [`kDNSServiceFlagsShared`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsshared).
37		const SHARED = ffi::FLAGS_SHARED;
38
39		/// Indicates the records with the given name, type and class is unique.
40		///
41		/// See [`kDNSServiceFlagsUnique`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsunique).
42		const UNIQUE = ffi::FLAGS_UNIQUE;
43	}
44}
45
46/// Successful registration
47///
48/// On dropping the registration the service will be unregistered.
49/// Registered [`Record`](struct.Record.html)s from this `Registration`
50/// or the originating [`Register`](struct.Register.html) future will
51/// keep the `Registration` alive.
52pub struct Registration(inner::SharedService);
53
54impl Registration {
55	/// Add a record to a registered service
56	///
57	/// See [`DNSServiceAddRecord`](https://developer.apple.com/documentation/dnssd/1804730-dnsserviceaddrecord)
58	#[doc(alias = "DNSServiceAddRecord")]
59	pub fn add_record(&self, rr_type: Type, rdata: &[u8], ttl: u32) -> io::Result<crate::Record> {
60		Ok(self
61			.0
62			.clone()
63			.add_record(0 /* no flags */, rr_type, rdata, ttl)?
64			.into())
65	}
66
67	/// Get [`Record`](struct.Record.html) handle for default TXT record
68	/// associated with the service registration (e.g. to update it).
69	///
70	/// [`Record::keep`](struct.Record.html#method.keep) doesn't do
71	/// anything useful on that handle.
72	pub fn get_default_txt_record(&self) -> crate::Record {
73		self.0.clone().get_default_txt_record().into()
74	}
75}
76
77/// Pending registration
78///
79/// Becomes invalid when the future completes; use the returned
80/// [`Registration`](struct.Registration.html) instead.
81#[must_use = "futures do nothing unless polled"]
82pub struct Register {
83	future: CallbackFuture,
84}
85
86impl Register {
87	pin_utils::unsafe_pinned!(future: CallbackFuture);
88
89	/// Add a record to a registered service
90	///
91	/// See [`DNSServiceAddRecord`](https://developer.apple.com/documentation/dnssd/1804730-dnsserviceaddrecord)
92	#[doc(alias = "DNSServiceAddRecord")]
93	pub fn add_record(&self, rr_type: Type, rdata: &[u8], ttl: u32) -> io::Result<crate::Record> {
94		Ok(self
95			.future
96			.service()
97			.clone()
98			.add_record(0 /* no flags */, rr_type, rdata, ttl)?
99			.into())
100	}
101
102	/// Get [`Record`](struct.Record.html) handle for default TXT record
103	/// associated with the service registration (e.g. to update it).
104	///
105	/// [`Record::keep`](struct.Record.html#method.keep) doesn't do
106	/// anything useful on that handle.
107	pub fn get_default_txt_record(&self) -> crate::Record {
108		self.future
109			.service()
110			.clone()
111			.get_default_txt_record()
112			.into()
113	}
114}
115
116impl Future for Register {
117	type Output = io::Result<(Registration, RegisterResult)>;
118
119	fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
120		let (service, item) = futures_core::ready!(self.future().poll(cx))?;
121		Poll::Ready(Ok((Registration(service), item)))
122	}
123}
124
125/// Service registration result
126///
127/// See [`DNSServiceRegisterReply`](https://developer.apple.com/documentation/dnssd/dnsserviceregisterreply).
128#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
129pub struct RegisterResult {
130	/// if [`NoAutoRename`](enum.RegisterFlag.html#variant.NoAutoRename)
131	/// was set this is the original name, otherwise it might be
132	/// different.
133	pub name: String,
134	/// the registered service type
135	pub reg_type: String,
136	/// domain the service was registered on
137	pub domain: String,
138}
139
140unsafe extern "C" fn register_callback(
141	_sd_ref: ffi::DNSServiceRef,
142	_flags: ffi::DNSServiceFlags,
143	error_code: ffi::DNSServiceErrorType,
144	name: *const c_char,
145	reg_type: *const c_char,
146	domain: *const c_char,
147	context: *mut c_void,
148) {
149	CallbackFuture::run_callback(context, error_code, || {
150		let name = cstr::from_cstr(name)?;
151		let reg_type = cstr::from_cstr(reg_type)?;
152		let domain = cstr::from_cstr(domain)?;
153
154		Ok(RegisterResult {
155			name: name.to_string(),
156			reg_type: reg_type.to_string(),
157			domain: domain.to_string(),
158		})
159	});
160}
161
162/// Optional data when registering a service; either use its default
163/// value or customize it like:
164///
165/// ```
166/// # use async_dnssd::RegisterData;
167/// RegisterData {
168///     txt: b"some text data",
169///     ..Default::default()
170/// };
171/// ```
172#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
173pub struct RegisterData<'a> {
174	/// flags for registration
175	pub flags: RegisterFlags,
176	/// interface to register service on
177	pub interface: Interface,
178	/// service name, defaults to hostname
179	pub name: Option<&'a str>,
180	/// domain on which to advertise the service
181	pub domain: Option<&'a str>,
182	/// the SRV target host name, defaults to local hostname(s).
183	/// Address records are NOT automatically generated for other names.
184	pub host: Option<&'a str>,
185	/// The TXT record rdata. Empty RDATA is treated like `b"\0"`, i.e.
186	/// a TXT record with a single empty string.
187	///
188	/// You can use [`TxtRecord`] to create the value for this field
189	/// (both [`TxtRecord::data`] and [`TxtRecord::rdata`] produce
190	/// appropriate values).
191	///
192	/// [`TxtRecord`]: struct.TxtRecord.html
193	/// [`TxtRecord::data`]: struct.TxtRecord.html#method.data
194	/// [`TxtRecord::rdata`]: struct.TxtRecord.html#method.rdata
195	pub txt: &'a [u8],
196	#[doc(hidden)]
197	pub _non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
198}
199
200impl<'a> Default for RegisterData<'a> {
201	fn default() -> Self {
202		Self {
203			flags: RegisterFlags::default(),
204			interface: Interface::default(),
205			name: None,
206			domain: None,
207			host: None,
208			txt: b"",
209			_non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
210		}
211	}
212}
213
214/// Register a service
215///
216/// * `reg_type`: the service type followed by the protocol, separated
217///   by a dot (for example, "_ssh._tcp").  For details see
218///   [`DNSServiceRegister`]
219/// * `port`: The port (in native byte order) on which the service
220///   accepts connections.  Pass 0 for a "placeholder" service.
221/// * `data`: additional service data
222///
223/// See [`DNSServiceRegister`].
224///
225/// [`DNSServiceRegister`]: https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister
226#[doc(alias = "DNSServiceRegister")]
227#[allow(clippy::too_many_arguments)]
228pub fn register_extended(
229	reg_type: &str,
230	port: u16,
231	data: RegisterData<'_>,
232) -> io::Result<Register> {
233	crate::init();
234
235	let name = cstr::NullableCStr::from(&data.name)?;
236	let reg_type = cstr::CStr::from(&reg_type)?;
237	let domain = cstr::NullableCStr::from(&data.domain)?;
238	let host = cstr::NullableCStr::from(&data.host)?;
239
240	let future = CallbackFuture::new(move |sender| {
241		inner::OwnedService::register(
242			data.flags.bits(),
243			data.interface.into_raw(),
244			&name,
245			&reg_type,
246			&domain,
247			&host,
248			port.to_be(),
249			data.txt,
250			Some(register_callback),
251			sender,
252		)
253		.map(|s| s.share())
254	})?;
255
256	Ok(Register { future })
257}
258
259/// Register a service
260///
261/// * `reg_type`: the service type followed by the protocol, separated
262///   by a dot (for example, "_ssh._tcp").  For details see
263///   [`DNSServiceRegister`]
264/// * `port`: The port (in native byte order) on which the service
265///   accepts connections.  Pass 0 for a "placeholder" service.
266/// * `handle`: the tokio event loop handle
267///
268/// Uses [`register_extended`] with default [`RegisterData`].
269///
270/// [`register_extended`]: fn.register_extended.html
271/// [`RegisterData`]: struct.RegisterData.html
272///
273/// See [`DNSServiceRegister`].
274///
275/// [`DNSServiceRegister`]: https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister
276///
277/// # Example
278///
279/// ```no_run
280/// # use async_dnssd::register;
281/// # #[deny(unused_must_use)]
282/// # #[tokio::main(flavor = "current_thread")]
283/// # async fn main() -> std::io::Result<()> {
284/// let registration = register("_ssh._tcp", 22)?.await?;
285/// # Ok(())
286/// # }
287/// ```
288#[doc(alias = "DNSServiceRegister")]
289#[allow(clippy::too_many_arguments)]
290pub fn register(reg_type: &str, port: u16) -> io::Result<Register> {
291	register_extended(reg_type, port, RegisterData::default())
292}