async_dnssd/service/
browse.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	cstr,
17	ffi,
18	inner,
19	interface::Interface,
20};
21
22type CallbackStream = crate::stream::ServiceStream<inner::OwnedService, BrowseResult>;
23
24bitflags::bitflags! {
25	/// Flags for [`BrowseResult`]
26	#[derive(Default)]
27	pub struct BrowsedFlags: ffi::DNSServiceFlags {
28		/// Indicates at least one more result is pending in the queue.  If
29		/// not set there still might be more results coming in the future.
30		///
31		/// See [`kDNSServiceFlagsMoreComing`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsmorecoming).
32		const MORE_COMING = ffi::FLAGS_MORE_COMING;
33
34		/// Indicates the result is new.  If not set indicates the result
35		/// was removed.
36		///
37		/// See [`kDNSServiceFlagsAdd`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsadd).
38		const ADD = ffi::FLAGS_ADD;
39	}
40}
41
42/// Pending browse request
43///
44/// Results are delivered through `Stream`.
45#[must_use = "streams do nothing unless polled"]
46pub struct Browse {
47	stream: crate::fused_err_stream::FusedErrorStream<CallbackStream>,
48}
49
50impl futures_core::Stream for Browse {
51	type Item = io::Result<BrowseResult>;
52
53	fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
54		let this = self.get_mut();
55		this.stream.poll_next_unpin(cx)
56	}
57}
58
59/// Browse result
60///
61/// See [DNSServiceBrowseReply](https://developer.apple.com/documentation/dnssd/dnsservicebrowsereply).
62#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
63pub struct BrowseResult {
64	/// Flags indicating whether the service was added or removed and
65	/// whether there are more pending results.
66	pub flags: BrowsedFlags,
67	/// Interface the service was found on.
68	pub interface: Interface,
69	/// Name of the service.
70	pub service_name: String,
71	/// Type of the service
72	pub reg_type: String,
73	/// Domain the service was found in
74	pub domain: String,
75}
76
77impl BrowseResult {
78	/// Resolve browse result.
79	///
80	/// Should check before whether result has the `Add` flag, as
81	/// otherwise it probably won't find anything.
82	pub fn resolve(&self) -> crate::Resolve {
83		crate::resolve(
84			self.interface,
85			&self.service_name,
86			&self.reg_type,
87			&self.domain,
88		)
89	}
90}
91
92unsafe extern "C" fn browse_callback(
93	_sd_ref: ffi::DNSServiceRef,
94	flags: ffi::DNSServiceFlags,
95	interface_index: u32,
96	error_code: ffi::DNSServiceErrorType,
97	service_name: *const c_char,
98	reg_type: *const c_char,
99	reply_domain: *const c_char,
100	context: *mut c_void,
101) {
102	unsafe {
103		CallbackStream::run_callback(context, error_code, || {
104			let service_name = cstr::from_cstr(service_name)?;
105			let reg_type = cstr::from_cstr(reg_type)?;
106			let reply_domain = cstr::from_cstr(reply_domain)?;
107
108			Ok(BrowseResult {
109				flags: BrowsedFlags::from_bits_truncate(flags),
110				interface: Interface::from_raw(interface_index),
111				service_name: service_name.to_string(),
112				reg_type: reg_type.to_string(),
113				domain: reply_domain.to_string(),
114			})
115		});
116	}
117}
118
119/// Optional data when browsing for a service; either use its default
120/// value or customize it like:
121///
122/// ```
123/// # use async_dnssd::BrowseData;
124/// BrowseData {
125///     domain: Some("example.com"),
126///     ..Default::default()
127/// };
128/// ```
129#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
130pub struct BrowseData<'a> {
131	/// interface to query records on
132	pub interface: Interface,
133	/// domain on which to search for the service
134	pub domain: Option<&'a str>,
135	#[doc(hidden)]
136	pub _non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
137}
138
139impl<'a> Default for BrowseData<'a> {
140	fn default() -> Self {
141		Self {
142			interface: Interface::default(),
143			domain: None,
144			_non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
145		}
146	}
147}
148
149fn _browse_extended(reg_type: &str, data: BrowseData<'_>) -> io::Result<Browse> {
150	crate::init();
151
152	let reg_type = cstr::CStr::from(&reg_type)?;
153	let domain = cstr::NullableCStr::from(&data.domain)?;
154
155	let stream = CallbackStream::new(move |sender| {
156		inner::OwnedService::browse(
157			0, // no flags
158			data.interface.into_raw(),
159			&reg_type,
160			&domain,
161			Some(browse_callback),
162			sender,
163		)
164	})
165	.into();
166
167	Ok(Browse { stream })
168}
169
170/// Browse for available services
171///
172/// `reg_type` specifies the service type to search, e.g. `"_ssh._tcp"`.
173///
174/// See [`DNSServiceBrowse`](https://developer.apple.com/documentation/dnssd/1804742-dnsservicebrowse).
175#[doc(alias = "DNSServiceBrowse")]
176pub fn browse_extended(reg_type: &str, data: BrowseData<'_>) -> Browse {
177	match _browse_extended(reg_type, data) {
178		Ok(r) => r,
179		Err(e) => Browse {
180			stream: Err(e).into(),
181		},
182	}
183}
184
185/// Browse for available services
186///
187/// `reg_type` specifies the service type to search, e.g. `"_ssh._tcp"`.
188///
189/// Uses [`browse_extended`] with default [`BrowseData`].
190///
191/// See [`DNSServiceBrowse`](https://developer.apple.com/documentation/dnssd/1804742-dnsservicebrowse).
192#[doc(alias = "DNSServiceBrowse")]
193pub fn browse(reg_type: &str) -> Browse {
194	browse_extended(reg_type, BrowseData::default())
195}