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(®_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 ®_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}