async_dnssd/service/
browse.rs

1use std::{
2	io,
3	os::raw::{
4		c_char,
5		c_void,
6	},
7	pin::Pin,
8	task::{
9		Context,
10		Poll,
11	},
12};
13
14use crate::{
15	cstr,
16	ffi,
17	inner,
18	interface::Interface,
19};
20
21type CallbackStream = crate::stream::ServiceStream<inner::OwnedService, BrowseResult>;
22
23bitflags::bitflags! {
24	/// Flags for [`BrowseResult`](struct.BrowseResult.html)
25	#[derive(Default)]
26	pub struct BrowsedFlags: ffi::DNSServiceFlags {
27		/// Indicates at least one more result is pending in the queue.  If
28		/// not set there still might be more results coming in the future.
29		///
30		/// See [`kDNSServiceFlagsMoreComing`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsmorecoming).
31		const MORE_COMING = ffi::FLAGS_MORE_COMING;
32
33		/// Indicates the result is new.  If not set indicates the result
34		/// was removed.
35		///
36		/// See [`kDNSServiceFlagsAdd`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsadd).
37		const ADD = ffi::FLAGS_ADD;
38	}
39}
40
41/// Pending browse request
42///
43/// Results are delivered through `Stream`.
44#[must_use = "streams do nothing unless polled"]
45pub struct Browse {
46	stream: crate::fused_err_stream::FusedErrorStream<CallbackStream>,
47}
48
49impl Browse {
50	pin_utils::unsafe_pinned!(stream: crate::fused_err_stream::FusedErrorStream<CallbackStream>);
51}
52
53impl futures_core::Stream for Browse {
54	type Item = io::Result<BrowseResult>;
55
56	fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
57		self.stream().poll_next(cx)
58	}
59}
60
61/// Browse result
62///
63/// See [DNSServiceBrowseReply](https://developer.apple.com/documentation/dnssd/dnsservicebrowsereply).
64#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
65pub struct BrowseResult {
66	/// Flags indicating whether the service was added or removed and
67	/// whether there are more pending results.
68	pub flags: BrowsedFlags,
69	/// Interface the service was found on.
70	pub interface: Interface,
71	/// Name of the service.
72	pub service_name: String,
73	/// Type of the service
74	pub reg_type: String,
75	/// Domain the service was found in
76	pub domain: String,
77}
78
79impl BrowseResult {
80	/// Resolve browse result.
81	///
82	/// Should check before whether result has the `Add` flag, as
83	/// otherwise it probably won't find anything.
84	pub fn resolve(&self) -> crate::Resolve {
85		crate::resolve(
86			self.interface,
87			&self.service_name,
88			&self.reg_type,
89			&self.domain,
90		)
91	}
92}
93
94unsafe extern "C" fn browse_callback(
95	_sd_ref: ffi::DNSServiceRef,
96	flags: ffi::DNSServiceFlags,
97	interface_index: u32,
98	error_code: ffi::DNSServiceErrorType,
99	service_name: *const c_char,
100	reg_type: *const c_char,
101	reply_domain: *const c_char,
102	context: *mut c_void,
103) {
104	CallbackStream::run_callback(context, error_code, || {
105		let service_name = cstr::from_cstr(service_name)?;
106		let reg_type = cstr::from_cstr(reg_type)?;
107		let reply_domain = cstr::from_cstr(reply_domain)?;
108
109		Ok(BrowseResult {
110			flags: BrowsedFlags::from_bits_truncate(flags),
111			interface: Interface::from_raw(interface_index),
112			service_name: service_name.to_string(),
113			reg_type: reg_type.to_string(),
114			domain: reply_domain.to_string(),
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///
193/// [`browse_extended`]: fn.browse_extended.html
194/// [`BrowseData`]: struct.BrowseData.html
195#[doc(alias = "DNSServiceBrowse")]
196pub fn browse(reg_type: &str) -> Browse {
197	browse_extended(reg_type, BrowseData::default())
198}