async_dnssd/service/
connection.rs

1use futures_util::FutureExt;
2use std::{
3	future::Future,
4	io,
5	os::raw::c_void,
6	pin::Pin,
7	task::{
8		Context,
9		Poll,
10	},
11};
12
13use crate::{
14	Record,
15	cstr,
16	dns_consts::{
17		Class,
18		Type,
19	},
20	ffi,
21	inner,
22	interface::Interface,
23};
24
25type CallbackFuture = crate::future::ServiceFuture<inner::SharedService, RegisterRecordResult>;
26
27/// Connection to register records with
28pub struct Connection(inner::SharedService);
29
30/// Create [`Connection`] to register records
31/// with
32///
33/// See [`DNSServiceCreateConnection`](https://developer.apple.com/documentation/dnssd/1804724-dnsservicecreateconnection).
34#[doc(alias = "DNSServiceCreateConnection")]
35pub fn connect() -> io::Result<Connection> {
36	crate::init();
37
38	Ok(Connection(inner::SharedService::create_connection()?))
39}
40
41bitflags::bitflags! {
42	/// Flags used to register a record
43	#[derive(Default)]
44	pub struct RegisterRecordFlags: ffi::DNSServiceFlags {
45		/// Indicates there might me multiple records with the given name, type and class.
46		///
47		/// See [`kDNSServiceFlagsShared`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsshared).
48		const SHARED = ffi::FLAGS_SHARED;
49
50		/// Indicates the records with the given name, type and class is unique.
51		///
52		/// See [`kDNSServiceFlagsUnique`](https://developer.apple.com/documentation/dnssd/1823436-anonymous/kdnsserviceflagsunique).
53		const UNIQUE = ffi::FLAGS_UNIQUE;
54	}
55}
56
57/// Pending record registration
58///
59/// Becomes invalid when the future completes; use the returned
60/// [`Record`] instead.
61// the future gets canceled by dropping the record; must
62// not drop the future without dropping the record.
63#[must_use = "futures do nothing unless polled"]
64pub struct RegisterRecord {
65	future: CallbackFuture,
66	record: Option<Record>,
67}
68
69impl Future for RegisterRecord {
70	type Output = io::Result<Record>;
71
72	fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
73		let this = self.get_mut();
74		futures_core::ready!(this.future.poll_unpin(cx))?;
75		Poll::Ready(Ok(this.record.take().unwrap()))
76	}
77}
78
79#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
80struct RegisterRecordResult;
81
82unsafe extern "C" fn register_record_callback(
83	_sd_ref: ffi::DNSServiceRef,
84	_record_ref: ffi::DNSRecordRef,
85	_flags: ffi::DNSServiceFlags,
86	error_code: ffi::DNSServiceErrorType,
87	context: *mut c_void,
88) {
89	unsafe {
90		CallbackFuture::run_callback(context, error_code, || Ok(RegisterRecordResult));
91	}
92}
93
94/// Optional data when registering a record; either use its default
95/// value or customize it like:
96///
97/// ```
98/// # use async_dnssd::RegisterRecordData;
99/// RegisterRecordData {
100///     ttl: 60,
101///     ..Default::default()
102/// };
103/// ```
104#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
105pub struct RegisterRecordData {
106	/// flags for registration
107	pub flags: RegisterRecordFlags,
108	/// interface to register record on
109	pub interface: Interface,
110	/// class of the resource record (default: `IN`)
111	pub rr_class: Class,
112	/// time to live of the resource record in seconds (passing 0 will
113	/// select a sensible default)
114	pub ttl: u32,
115	#[doc(hidden)]
116	pub _non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
117}
118
119impl Default for RegisterRecordData {
120	fn default() -> Self {
121		Self {
122			flags: RegisterRecordFlags::default(),
123			interface: Interface::default(),
124			rr_class: Class::IN,
125			ttl: 0,
126			_non_exhaustive: crate::non_exhaustive_struct::NonExhaustiveMarker,
127		}
128	}
129}
130
131impl Connection {
132	/// Register record on interface with given name, type, class, rdata
133	/// and ttl
134	///
135	/// See [`DNSServiceRegisterRecord`](https://developer.apple.com/documentation/dnssd/1804727-dnsserviceregisterrecord).
136	#[doc(alias = "DNSServiceRegisterRecord")]
137	pub fn register_record_extended(
138		&self,
139		fullname: &str,
140		rr_type: Type,
141		rdata: &[u8],
142		data: RegisterRecordData,
143	) -> io::Result<RegisterRecord> {
144		let fullname = cstr::CStr::from(&fullname)?;
145
146		let (future, record) = CallbackFuture::new_with(self.0.clone(), move |sender| {
147			self.0.clone().register_record(
148				data.flags.bits(),
149				data.interface.into_raw(),
150				&fullname,
151				rr_type,
152				data.rr_class,
153				rdata,
154				data.ttl,
155				Some(register_record_callback),
156				sender,
157			)
158		})?;
159
160		Ok(RegisterRecord {
161			future,
162			record: Some(record.into()),
163		})
164	}
165
166	/// Register record on interface with given name, type, class, rdata
167	/// and ttl
168	///
169	/// Uses [`register_record_extended`][Self::register_record_extended] with default [`RegisterRecordData`].
170	///
171	/// See [`DNSServiceRegisterRecord`](https://developer.apple.com/documentation/dnssd/1804727-dnsserviceregisterrecord).
172	#[doc(alias = "DNSServiceRegisterRecord")]
173	pub fn register_record(
174		&self,
175		fullname: &str,
176		rr_type: Type,
177		rdata: &[u8],
178	) -> io::Result<RegisterRecord> {
179		self.register_record_extended(fullname, rr_type, rdata, RegisterRecordData::default())
180	}
181}
182
183impl RegisterRecord {
184	fn inner_record(&self) -> &Record {
185		self.record.as_ref().expect("RegisterRecord future is done")
186	}
187
188	/// Type of the record
189	///
190	/// # Panics
191	///
192	/// Panics after the future completed.  Use the returned
193	/// [`Record`] instead.
194	pub fn rr_type(&self) -> Type {
195		self.inner_record().rr_type()
196	}
197
198	/// Update record
199	///
200	/// Cannot change type or class of record.
201	///
202	/// # Panics
203	///
204	/// Panics after the future completed.  Use the returned
205	/// [`Record`] instead.
206	///
207	/// See [`DNSServiceUpdateRecord`](https://developer.apple.com/documentation/dnssd/1804739-dnsserviceupdaterecord).
208	#[doc(alias = "DNSServiceUpdateRecord")]
209	pub fn update_record(&self, rdata: &[u8], ttl: u32) -> io::Result<()> {
210		self.inner_record().update_record(rdata, ttl)
211	}
212
213	/// Keep record for as long as the underlying connection lives.
214	///
215	/// Keep the a handle to the underlying connection (either the
216	/// [`Connection`] or some other record from
217	/// the same `Connection`) alive.
218	///
219	/// Due to some implementation detail the underlying connection
220	/// might live until this future successfully completes.
221	///
222	/// # Panics
223	///
224	/// Panics after the future completed.  Use the returned
225	/// [`Record`] instead.
226	// - implementation detail: this drives the future to continuation,
227	//   it is not possible to drop the (shared) underlying service
228	//   before. instead we could store the callback context with the
229	//   underyling service, and drop it either when dropping the
230	//   service or the callback was called.
231	pub fn keep(self) {
232		let (fut, rec) = (
233			self.future,
234			self.record.expect("RegisterRecord future is done"),
235		);
236		// drive future to continuation, ignore errors
237		tokio::spawn(fut.map(|_| ()));
238		rec.keep();
239	}
240}