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