async_dnssd/service/
register.rs

1use futures_util::StreamExt;
2use std::{
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	Record,
17	cstr,
18	dns_consts::Type,
19	ffi,
20	inner,
21	interface::Interface,
22};
23
24type CallbackStream = crate::stream::ServiceStream<inner::SharedService, RegisterResult>;
25
26bitflags::bitflags! {
27	/// Flags used to register service
28	#[derive(Default)]
29	pub struct RegisterFlags: ffi::DNSServiceFlags {
30		/// Indicates a name conflict should not get handled automatically.
31		///
32		/// See [`kDNSServiceFlagsNoAutoRename`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsnoautorename).
33		const NO_AUTO_RENAME = ffi::FLAGS_NO_AUTO_RENAME;
34
35		/// Indicates there might me multiple records with the given name, type and class.
36		///
37		/// See [`kDNSServiceFlagsShared`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsshared).
38		const SHARED = ffi::FLAGS_SHARED;
39
40		/// Indicates the records with the given name, type and class is unique.
41		///
42		/// See [`kDNSServiceFlagsUnique`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsunique).
43		const UNIQUE = ffi::FLAGS_UNIQUE;
44	}
45}
46
47bitflags::bitflags! {
48	/// Flags for [`QueryRecordResult`]
49	#[derive(Default)]
50	pub struct RegisteredFlags: ffi::DNSServiceFlags {
51		/// WARNING: not supported by avahi! Indiciates whether registration was added or removed.
52		///
53		/// Indicates the registration was succesful.  If not set indicates the
54		/// registration was removed.
55		///
56		/// See [`kDNSServiceFlagsAdd`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsadd).
57		const ADD = ffi::FLAGS_ADD;
58	}
59}
60
61/// Registration handle
62///
63/// Can be used to add additional records to the registration.
64///
65/// Also keeps the registration alive (same as `Registration`).
66#[derive(Clone)]
67pub struct RegistrationHandle(inner::SharedService);
68
69impl RegistrationHandle {
70	/// Add a record to a registered service
71	///
72	/// See [`DNSServiceAddRecord`](https://developer.apple.com/documentation/dnssd/1804730-dnsserviceaddrecord)
73	#[doc(alias = "DNSServiceAddRecord")]
74	pub fn add_record(&self, rr_type: Type, rdata: &[u8], ttl: u32) -> io::Result<Record> {
75		Ok(self
76			.0
77			.clone()
78			.add_record(0 /* no flags */, rr_type, rdata, ttl)?
79			.into())
80	}
81
82	/// Get [`Record`] handle for default TXT record
83	/// associated with the service registration (e.g. to update it).
84	///
85	/// [`Record::keep`] doesn't do anything useful on that handle.
86	pub fn get_default_txt_record(&self) -> Record {
87		self.0.clone().get_default_txt_record().into()
88	}
89}
90
91/// Registration
92///
93/// A registration can become active and get deleted again; you need to
94/// poll the stream for updates.
95///
96/// Keeps the registration alive (same as `RegistrationHandle`).
97#[must_use = "streams do nothing unless polled"]
98pub struct Registration {
99	stream: CallbackStream,
100	handle: RegistrationHandle,
101}
102
103impl std::ops::Deref for Registration {
104	type Target = RegistrationHandle;
105
106	fn deref(&self) -> &Self::Target {
107		&self.handle
108	}
109}
110
111impl futures_core::Stream for Registration {
112	type Item = io::Result<RegisterResult>;
113
114	fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
115		let this = self.get_mut();
116		this.stream.poll_next_unpin(cx)
117	}
118}
119
120/// Service registration result
121///
122/// See [`DNSServiceRegisterReply`](https://developer.apple.com/documentation/dnssd/dnsserviceregisterreply).
123#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
124pub struct RegisterResult {
125	/// flags
126	pub flags: RegisteredFlags,
127	/// if [`NoAutoRename`](enum.RegisterFlag.html#variant.NoAutoRename)
128	/// was set this is the original name, otherwise it might be
129	/// different.
130	pub name: String,
131	/// the registered service type
132	pub reg_type: String,
133	/// domain the service was registered on
134	pub domain: String,
135}
136
137unsafe extern "C" fn register_callback(
138	_sd_ref: ffi::DNSServiceRef,
139	flags: ffi::DNSServiceFlags,
140	error_code: ffi::DNSServiceErrorType,
141	name: *const c_char,
142	reg_type: *const c_char,
143	domain: *const c_char,
144	context: *mut c_void,
145) {
146	unsafe {
147		CallbackStream::run_callback(context, error_code, || {
148			let name = cstr::from_cstr(name)?;
149			let reg_type = cstr::from_cstr(reg_type)?;
150			let domain = cstr::from_cstr(domain)?;
151
152			Ok(RegisterResult {
153				flags: RegisteredFlags::from_bits_truncate(flags),
154				name: name.to_string(),
155				reg_type: reg_type.to_string(),
156				domain: domain.to_string(),
157			})
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`][crate::TxtRecord] to create the value for this field
189	/// (both [`TxtRecord::data`][crate::TxtRecord::data] and
190	/// [`TxtRecord::rdata`][crate::TxtRecord::rdata] produce appropriate values).
191	pub txt: &'a [u8],
192	#[doc(hidden)]
193	pub _non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
194}
195
196impl<'a> Default for RegisterData<'a> {
197	fn default() -> Self {
198		Self {
199			flags: RegisterFlags::default(),
200			interface: Interface::default(),
201			name: None,
202			domain: None,
203			host: None,
204			txt: b"",
205			_non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
206		}
207	}
208}
209
210/// Register a service
211///
212/// * `reg_type`: the service type followed by the protocol, separated
213///   by a dot (for example, "_ssh._tcp").  For details see
214///   [`DNSServiceRegister`]
215/// * `port`: The port (in native byte order) on which the service
216///   accepts connections.  Pass 0 for a "placeholder" service.
217/// * `data`: additional service data
218///
219/// See [`DNSServiceRegister`].
220///
221/// [`DNSServiceRegister`]: https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister
222#[doc(alias = "DNSServiceRegister")]
223#[allow(clippy::too_many_arguments)]
224pub fn register_extended(
225	reg_type: &str,
226	port: u16,
227	data: RegisterData<'_>,
228) -> io::Result<Registration> {
229	crate::init();
230
231	let name = cstr::NullableCStr::from(&data.name)?;
232	let reg_type = cstr::CStr::from(&reg_type)?;
233	let domain = cstr::NullableCStr::from(&data.domain)?;
234	let host = cstr::NullableCStr::from(&data.host)?;
235
236	let stream = CallbackStream::new(move |sender| {
237		inner::OwnedService::register(
238			data.flags.bits(),
239			data.interface.into_raw(),
240			&name,
241			&reg_type,
242			&domain,
243			&host,
244			port.to_be(),
245			data.txt,
246			Some(register_callback),
247			sender,
248		)
249		.map(|s| s.share())
250	})?;
251
252	let handle = RegistrationHandle(stream.service().clone());
253	Ok(Registration { stream, handle })
254}
255
256/// Register a service
257///
258/// * `reg_type`: the service type followed by the protocol, separated
259///   by a dot (for example, "_ssh._tcp").  For details see
260///   [`DNSServiceRegister`]
261/// * `port`: The port (in native byte order) on which the service
262///   accepts connections.  Pass 0 for a "placeholder" service.
263/// * `handle`: the tokio event loop handle
264///
265/// Uses [`register_extended`] with default [`RegisterData`].
266///
267/// See [`DNSServiceRegister`].
268///
269/// [`DNSServiceRegister`]: https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister
270///
271/// # Example
272///
273/// ```no_run
274/// # use async_dnssd::register;
275/// # use futures::StreamExt;
276/// # #[deny(unused_must_use)]
277/// # #[tokio::main(flavor = "current_thread")]
278/// # async fn main() -> std::io::Result<()> {
279/// let mut registration = async_dnssd::register("_ssh._tcp", 22)?;
280/// let result = registration.next().await.expect("no stream end")?;
281/// # registration.for_each(|_| async {}).await;
282/// # Ok(())
283/// # }
284/// ```
285#[doc(alias = "DNSServiceRegister")]
286#[allow(clippy::too_many_arguments)]
287pub fn register(reg_type: &str, port: u16) -> io::Result<Registration> {
288	register_extended(reg_type, port, RegisterData::default())
289}