rtc_mdns/proto/mod.rs
1//! The Sans-I/O mDNS connection.
2//!
3//! This module is private; [`Mdns`] is re-exported as [`crate::Mdns`], where its
4//! documentation and examples live so that rustdoc renders them and their doctests run.
5
6use std::collections::{HashMap, VecDeque};
7use std::net::{IpAddr, Ipv4Addr, SocketAddr};
8use std::time::{Duration, Instant};
9
10use bytes::BytesMut;
11use log::{trace, warn};
12use shared::{TaggedBytesMut, TransportContext, TransportMessage, TransportProtocol};
13
14use crate::config::{DEFAULT_QUERY_INTERVAL, MAX_MESSAGE_RECORDS, MdnsConfig, RESPONSE_TTL};
15use crate::message::header::Header;
16use crate::message::name::Name;
17use crate::message::parser::Parser;
18use crate::message::question::Question;
19use crate::message::resource::a::AResource;
20use crate::message::resource::{Resource, ResourceHeader};
21use crate::message::{DNSCLASS_INET, DnsType, Message};
22use shared::error::{Error, Result};
23
24/// The mDNS multicast group address (224.0.0.251).
25pub const MDNS_MULTICAST_IPV4: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 251);
26
27/// The standard mDNS port (5353).
28pub const MDNS_PORT: u16 = 5353;
29
30/// mDNS multicast destination address (224.0.0.251:5353).
31///
32/// All mDNS queries and responses should be sent to this address.
33///
34/// # Example
35///
36/// ```rust
37/// use rtc_mdns::MDNS_DEST_ADDR;
38///
39/// assert_eq!(MDNS_DEST_ADDR.to_string(), "224.0.0.251:5353");
40/// ```
41pub const MDNS_DEST_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(MDNS_MULTICAST_IPV4), MDNS_PORT);
42
43/// Unique identifier for tracking mDNS queries.
44///
45/// Each call to [`Mdns::query()`] returns a unique ID that can be used to:
46/// - Track which query was answered in [`MdnsEvent::QueryAnswered`]
47/// - Cancel a pending query with [`Mdns::cancel_query()`]
48/// - Check if a query is still pending with [`Mdns::is_query_pending()`]
49pub type QueryId = u64;
50
51/// A pending mDNS query.
52///
53/// This struct tracks the state of an active query, including when it was
54/// started and when the next retry should occur.
55#[derive(Debug, Clone)]
56pub struct Query {
57 /// Unique identifier for this query.
58 pub id: QueryId,
59 /// The name being queried, with trailing dot (e.g., `"myhost.local."`).
60 pub name_with_suffix: String,
61 /// When the query was started.
62 pub start_time: Instant,
63 /// When the next retry should be sent.
64 pub next_retry: Instant,
65}
66
67/// Events emitted by the mDNS connection.
68///
69/// Poll for events using [`poll_event()`](sansio::Protocol::poll_event) after
70/// calling [`handle_read()`](sansio::Protocol::handle_read) or
71/// [`handle_timeout()`](sansio::Protocol::handle_timeout).
72///
73/// # Example
74///
75/// ```rust,ignore
76/// while let Some(event) = mdns.poll_event() {
77/// match event {
78/// MdnsEvent::QueryAnswered(query_id, addr) => {
79/// println!("Query {} resolved to {}", query_id, addr);
80/// }
81/// MdnsEvent::QueryTimeout(query_id) => {
82/// println!("Query {} timed out", query_id);
83/// }
84/// }
85/// }
86/// ```
87#[derive(Debug)]
88pub enum MdnsEvent {
89 /// A query was successfully answered.
90 ///
91 /// Contains the query ID and the resolved IP address.
92 /// The query is automatically removed from the pending list.
93 QueryAnswered(QueryId, IpAddr),
94
95 /// A query timed out without receiving an answer.
96 ///
97 /// This event is emitted when [`MdnsConfig::query_timeout`](crate::MdnsConfig::query_timeout)
98 /// is set and a query exceeds its timeout duration. The query is automatically
99 /// removed from the pending list when this event is emitted.
100 ///
101 /// To enable query timeouts, configure the connection with
102 /// [`MdnsConfig::with_query_timeout`](crate::MdnsConfig::with_query_timeout):
103 ///
104 /// ```rust
105 /// use rtc_mdns::MdnsConfig;
106 /// use std::time::Duration;
107 ///
108 /// let config = MdnsConfig::default()
109 /// .with_query_timeout(Duration::from_secs(5));
110 /// ```
111 QueryTimeout(QueryId),
112}
113
114/// Sans-I/O mDNS Connection.
115///
116/// This implements a sans-I/O mDNS client/server that can:
117/// - Send mDNS queries and receive answers
118/// - Respond to mDNS questions for configured local names
119///
120/// # Sans-I/O Pattern
121///
122/// This struct implements [`sansio::Protocol`], which means it doesn't perform
123/// any I/O itself. Instead, the caller is responsible for:
124///
125/// 1. Calling [`handle_read()`](sansio::Protocol::handle_read) when packets arrive
126/// 2. Sending packets from [`poll_write()`](sansio::Protocol::poll_write)
127/// 3. Calling [`handle_timeout()`](sansio::Protocol::handle_timeout) on schedule
128/// 4. Processing events from [`poll_event()`](sansio::Protocol::poll_event)
129///
130/// # Example: Complete Event Loop
131///
132/// ```rust
133/// use rtc_mdns::{MdnsConfig, Mdns, MdnsEvent};
134/// use sansio::Protocol;
135/// use std::time::{Duration, Instant};
136///
137/// let mut mdns = Mdns::new(MdnsConfig::default());
138///
139/// // Start a query
140/// let query_id = mdns.query("device.local");
141///
142/// // Simulate an event loop iteration
143/// let now = Instant::now();
144///
145/// // 1. Send queued packets (would go to network in real code)
146/// while let Some(packet) = mdns.poll_write() {
147/// println!("Would send {} bytes to {}", packet.message.len(), packet.transport.peer_addr);
148/// }
149///
150/// // 2. Handle timeout if due
151/// if let Some(deadline) = mdns.poll_timeout() {
152/// if deadline <= now {
153/// mdns.handle_timeout(now).ok();
154/// }
155/// }
156///
157/// // 3. Process any events
158/// while let Some(event) = mdns.poll_event() {
159/// match event {
160/// MdnsEvent::QueryAnswered(query_id, addr) => {
161/// println!("Query {} answered: {}", query_id, addr);
162/// }
163/// MdnsEvent::QueryTimeout(id) => {
164/// println!("Query {} timed out", id);
165/// }
166/// }
167/// }
168/// ```
169///
170/// # Example: Multiple Concurrent Queries
171///
172/// ```rust
173/// use rtc_mdns::{MdnsConfig, Mdns};
174/// use sansio::Protocol;
175///
176/// let mut mdns = Mdns::new(MdnsConfig::default());
177///
178/// // Start multiple queries - each gets a unique ID
179/// let id1 = mdns.query("printer.local");
180/// let id2 = mdns.query("server.local");
181/// let id3 = mdns.query("nas.local");
182///
183/// assert_eq!(mdns.pending_query_count(), 3);
184/// assert!(mdns.is_query_pending(id1));
185/// assert!(mdns.is_query_pending(id2));
186/// assert!(mdns.is_query_pending(id3));
187///
188/// // Cancel one query
189/// mdns.cancel_query(id2);
190/// assert_eq!(mdns.pending_query_count(), 2);
191/// assert!(!mdns.is_query_pending(id2));
192/// ```
193///
194/// # Overview
195///
196/// The [`Mdns`] struct handles the mDNS protocol logic without performing any I/O.
197/// The caller is responsible for:
198///
199/// 1. **Network I/O**: Reading/writing UDP packets to/from 224.0.0.251:5353
200/// 2. **Timing**: Calling `handle_timeout()` when `poll_timeout()` expires
201/// 3. **Event Processing**: Handling events from `poll_event()`
202///
203/// # Query Lifecycle
204///
205/// 1. Call [`Mdns::query()`] with the hostname to resolve
206/// 2. Retrieve the query packet from [`poll_write()`](sansio::Protocol::poll_write)
207/// 3. Send the packet to the mDNS multicast address
208/// 4. When responses arrive, pass them to [`handle_read()`](sansio::Protocol::handle_read)
209/// 5. Check [`poll_event()`](sansio::Protocol::poll_event) for [`MdnsEvent::QueryAnswered`]
210/// 6. If no answer, call [`handle_timeout()`](sansio::Protocol::handle_timeout) to trigger retries
211pub struct Mdns {
212 /// MdnsConfiguration
213 config: MdnsConfig,
214
215 /// Local names with trailing dots (for matching questions)
216 local_names: Vec<String>,
217
218 /// Pending queries
219 queries: Vec<Query>,
220
221 /// Next query ID to assign
222 next_query_id: QueryId,
223
224 /// Query retry interval
225 query_interval: Duration,
226
227 /// Query timeout (None = no automatic timeout)
228 query_timeout: Option<Duration>,
229
230 /// Outgoing packet queue
231 write_outs: VecDeque<TaggedBytesMut>,
232
233 /// Event queue
234 event_outs: VecDeque<MdnsEvent>,
235
236 /// Next timeout for query retries
237 next_timeout: Option<Instant>,
238
239 /// Whether the connection is closed
240 closed: bool,
241}
242
243impl Mdns {
244 /// Create a new mDNS connection with the given configuration.
245 ///
246 /// # Arguments
247 ///
248 /// * `config` - MdnsConfiguration for the connection
249 ///
250 /// # Example
251 ///
252 /// ```rust
253 /// use rtc_mdns::{MdnsConfig, Mdns};
254 /// use std::time::Duration;
255 ///
256 /// // Client-only configuration
257 /// let client = Mdns::new(MdnsConfig::default());
258 ///
259 /// // Server configuration
260 /// use std::net::{IpAddr, Ipv4Addr};
261 /// let server = Mdns::new(
262 /// MdnsConfig::default()
263 /// .with_local_names(vec!["myhost.local".to_string()])
264 /// .with_local_ip(
265 /// IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)),
266 /// )
267 /// );
268 /// ```
269 pub fn new(config: MdnsConfig) -> Self {
270 let local_names = config
271 .local_names
272 .iter()
273 .map(|name| {
274 if name.ends_with('.') {
275 name.clone()
276 } else {
277 format!("{name}.")
278 }
279 })
280 .collect();
281
282 let query_interval = if config.query_interval == Duration::ZERO {
283 DEFAULT_QUERY_INTERVAL
284 } else {
285 config.query_interval
286 };
287
288 let query_timeout = config.query_timeout;
289
290 Self {
291 config,
292 local_names,
293 queries: Vec::new(),
294 next_query_id: 1,
295 query_interval,
296 query_timeout,
297 write_outs: VecDeque::new(),
298 event_outs: VecDeque::new(),
299 next_timeout: None,
300 closed: false,
301 }
302 }
303
304 /// Start a new mDNS query for the given name.
305 ///
306 /// This method queues an mDNS query packet to be sent. The query will be
307 /// automatically retried at the configured interval until either:
308 /// - An answer is received (emits [`MdnsEvent::QueryAnswered`])
309 /// - The query times out (emits [`MdnsEvent::QueryTimeout`] if `query_timeout` is set)
310 /// - The query is cancelled with [`cancel_query()`](Self::cancel_query)
311 /// - The connection is closed
312 ///
313 /// # Arguments
314 ///
315 /// * `name` - The hostname to query (e.g., `"mydevice.local"`)
316 ///
317 /// # Returns
318 ///
319 /// A unique [`QueryId`] that can be used to track this query.
320 ///
321 /// # Example
322 ///
323 /// ```rust
324 /// use rtc_mdns::{MdnsConfig, Mdns, MdnsEvent};
325 /// use sansio::Protocol;
326 ///
327 /// let mut mdns = Mdns::new(MdnsConfig::default());
328 ///
329 /// // Start a query
330 /// let query_id = mdns.query("printer.local");
331 ///
332 /// // The query packet is now queued
333 /// let packet = mdns.poll_write().expect("query packet should be queued");
334 /// assert_eq!(packet.transport.peer_addr.to_string(), "224.0.0.251:5353");
335 ///
336 /// // Track the query
337 /// assert!(mdns.is_query_pending(query_id));
338 /// ```
339 pub fn query(&mut self, name: &str) -> QueryId {
340 let name_with_suffix = if name.ends_with('.') {
341 name.to_string()
342 } else {
343 format!("{name}.")
344 };
345
346 let id = self.next_query_id;
347 self.next_query_id += 1;
348
349 let now = Instant::now();
350 let query = Query {
351 id,
352 name_with_suffix: name_with_suffix.clone(),
353 start_time: now,
354 next_retry: now + self.query_interval, // Schedule first retry after interval
355 };
356 self.queries.push(query);
357
358 // Send the initial query immediately
359 self.send_question(&name_with_suffix, now);
360
361 // Update timeout
362 self.update_next_timeout();
363
364 id
365 }
366
367 /// Cancel a pending query.
368 ///
369 /// Removes the query from the pending list. No more retry packets will
370 /// be sent and no events will be emitted for this query.
371 ///
372 /// # Arguments
373 ///
374 /// * `query_id` - The ID returned by [`query()`](Self::query)
375 ///
376 /// # Example
377 ///
378 /// ```rust
379 /// use rtc_mdns::{MdnsConfig, Mdns};
380 ///
381 /// let mut mdns = Mdns::new(MdnsConfig::default());
382 /// let query_id = mdns.query("device.local");
383 ///
384 /// assert!(mdns.is_query_pending(query_id));
385 /// mdns.cancel_query(query_id);
386 /// assert!(!mdns.is_query_pending(query_id));
387 /// ```
388 pub fn cancel_query(&mut self, query_id: QueryId) {
389 self.queries.retain(|q| q.id != query_id);
390 self.update_next_timeout();
391 }
392
393 /// Check if a query is still pending.
394 ///
395 /// A query is pending until it is either answered or cancelled.
396 ///
397 /// # Arguments
398 ///
399 /// * `query_id` - The ID returned by [`query()`](Self::query)
400 ///
401 /// # Returns
402 ///
403 /// `true` if the query is still waiting for an answer, `false` otherwise.
404 ///
405 /// # Example
406 ///
407 /// ```rust
408 /// use rtc_mdns::{MdnsConfig, Mdns};
409 ///
410 /// let mut mdns = Mdns::new(MdnsConfig::default());
411 /// let query_id = mdns.query("device.local");
412 ///
413 /// // Query is pending until answered or cancelled
414 /// assert!(mdns.is_query_pending(query_id));
415 /// ```
416 pub fn is_query_pending(&self, query_id: QueryId) -> bool {
417 self.queries.iter().any(|q| q.id == query_id)
418 }
419
420 /// Get the number of pending queries.
421 ///
422 /// # Returns
423 ///
424 /// The count of queries that are still waiting for answers.
425 ///
426 /// # Example
427 ///
428 /// ```rust
429 /// use rtc_mdns::{MdnsConfig, Mdns};
430 ///
431 /// let mut mdns = Mdns::new(MdnsConfig::default());
432 /// assert_eq!(mdns.pending_query_count(), 0);
433 ///
434 /// mdns.query("device1.local");
435 /// mdns.query("device2.local");
436 /// assert_eq!(mdns.pending_query_count(), 2);
437 /// ```
438 pub fn pending_query_count(&self) -> usize {
439 self.queries.len()
440 }
441
442 fn send_question(&mut self, name: &str, now: Instant) {
443 let packed_name = match Name::new(name) {
444 Ok(pn) => pn,
445 Err(err) => {
446 log::warn!("Failed to construct mDNS packet: {err}");
447 return;
448 }
449 };
450
451 let raw_query = {
452 let mut msg = Message {
453 header: Header::default(),
454 questions: vec![Question {
455 typ: DnsType::A,
456 class: DNSCLASS_INET,
457 name: packed_name,
458 }],
459 ..Default::default()
460 };
461
462 match msg.pack() {
463 Ok(v) => v,
464 Err(err) => {
465 log::error!("Failed to construct mDNS packet {err}");
466 return;
467 }
468 }
469 };
470
471 log::trace!("Queuing mDNS query for {name}");
472 self.write_outs.push_back(TransportMessage {
473 now,
474 transport: TransportContext {
475 local_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), MDNS_PORT),
476 peer_addr: MDNS_DEST_ADDR,
477 transport_protocol: TransportProtocol::UDP,
478 ecn: None,
479 },
480 message: BytesMut::from(&raw_query[..]),
481 });
482 }
483
484 fn send_answer(&mut self, local_ip: IpAddr, name: &str, now: Instant) {
485 let packed_name = match Name::new(name) {
486 Ok(n) => n,
487 Err(err) => {
488 log::warn!("Failed to pack name for answer: {err}");
489 return;
490 }
491 };
492
493 let raw_answer = {
494 let mut msg = Message {
495 header: Header {
496 response: true,
497 authoritative: true,
498 ..Default::default()
499 },
500 answers: vec![Resource {
501 header: ResourceHeader {
502 typ: DnsType::A,
503 class: DNSCLASS_INET,
504 name: packed_name,
505 ttl: RESPONSE_TTL,
506 ..Default::default()
507 },
508 body: Some(Box::new(AResource {
509 a: match local_ip {
510 IpAddr::V4(ip) => ip.octets(),
511 IpAddr::V6(_) => {
512 log::warn!("Cannot send IPv6 address in A record");
513 //TODO: How to support IPv6 mDNS?
514 return;
515 }
516 },
517 })),
518 }],
519 ..Default::default()
520 };
521
522 match msg.pack() {
523 Ok(v) => v,
524 Err(err) => {
525 log::error!("Failed to pack answer: {err}");
526 return;
527 }
528 }
529 };
530
531 log::trace!("mDNS Queuing answer for {name} -> {local_ip}");
532 self.write_outs.push_back(TransportMessage {
533 now,
534 transport: TransportContext {
535 local_addr: SocketAddr::new(local_ip, MDNS_PORT),
536 peer_addr: MDNS_DEST_ADDR,
537 transport_protocol: TransportProtocol::UDP,
538 ecn: None,
539 },
540 message: BytesMut::from(&raw_answer[..]),
541 });
542 }
543
544 fn process_message(&mut self, msg: &TaggedBytesMut) {
545 let mut parser = Parser::default();
546 if let Err(err) = parser.start(&msg.message) {
547 log::error!("Failed to parse mDNS packet: {err}");
548 return;
549 }
550
551 let src = msg.transport.peer_addr;
552
553 // Process questions (respond if we have local names)
554 self.process_questions(&mut parser, src, msg.now);
555
556 // Process answers (check if they match pending queries)
557 self.process_answers(&mut parser, src);
558 }
559
560 fn process_questions(&mut self, parser: &mut Parser<'_>, _src: SocketAddr, now: Instant) {
561 // Collect names that need answers first to avoid borrow issues
562 let mut names_to_answer: Vec<String> = Vec::new();
563
564 for _ in 0..=MAX_MESSAGE_RECORDS {
565 let q = match parser.question() {
566 Ok(q) => q,
567 Err(err) => {
568 if err == Error::ErrSectionDone {
569 break;
570 }
571 log::error!("Failed to parse question: {err}");
572 return;
573 }
574 };
575
576 // Check if we should answer this question
577 for local_name in &self.local_names {
578 if *local_name == q.name.data {
579 names_to_answer.push(q.name.data.clone());
580 break;
581 }
582 }
583 }
584
585 // Skip remaining questions
586 let _ = parser.skip_all_questions();
587
588 // Now send answers
589 if let Some(local_ip) = self.config.local_ip {
590 for name in names_to_answer {
591 log::trace!(
592 "mDNS Found question for local name: {}, responding with {}",
593 name,
594 local_ip
595 );
596 self.send_answer(local_ip, &name, now);
597 }
598 } else if !names_to_answer.is_empty() {
599 log::warn!("Received questions for local names but no local_addr configured");
600 }
601 }
602
603 fn process_answers(&mut self, parser: &mut Parser<'_>, src: SocketAddr) {
604 for _ in 0..=MAX_MESSAGE_RECORDS {
605 let answer_header = match parser.answer_header() {
606 Ok(a) => a,
607 Err(err) => {
608 if err != Error::ErrSectionDone {
609 log::warn!("Failed to parse answer header: {err}");
610 }
611 return;
612 }
613 };
614
615 // Only process A and AAAA records
616 if answer_header.typ != DnsType::A && answer_header.typ != DnsType::Aaaa {
617 continue;
618 }
619
620 let answer_resource = match parser.answer() {
621 Ok(a) => a,
622 Err(err) => {
623 if err != Error::ErrSectionDone {
624 log::warn!("Failed to parse answer: {err}");
625 }
626 return;
627 }
628 };
629
630 let local_ip = if let Some(body) = answer_resource.body
631 && let Some(a) = body.as_any().downcast_ref::<AResource>()
632 {
633 let local_ip = Ipv4Addr::from_octets(a.a).into();
634 if local_ip != src.ip() {
635 warn!(
636 "mDNS answers with different local ip on AResource {} vs src ip {} on Socket for query {}",
637 local_ip,
638 src.ip(),
639 answer_header.name.data
640 );
641 } else {
642 trace!(
643 "mDNS answers with the local ip {} on AResource and Socket for query {}",
644 local_ip, answer_header.name.data
645 );
646 }
647
648 local_ip
649 } else {
650 warn!(
651 "mDNS answers without AResource, fallback to use src ip {} on Socket for local ip for query {}",
652 src.ip(),
653 answer_header.name.data
654 );
655 src.ip()
656 };
657
658 // Check if this answer matches any pending queries
659 let mut matched_query_ids = HashMap::new();
660 for query in &self.queries {
661 if query.name_with_suffix == answer_header.name.data {
662 matched_query_ids.insert(query.id, local_ip);
663 }
664 }
665
666 // Emit events and remove matched queries
667 for (query_id, local_ip) in matched_query_ids {
668 self.event_outs
669 .push_back(MdnsEvent::QueryAnswered(query_id, local_ip));
670 self.queries.retain(|q| q.id != query_id);
671 }
672 }
673 }
674
675 fn update_next_timeout(&mut self) {
676 self.next_timeout = self.queries.iter().map(|q| q.next_retry).min();
677 }
678}
679
680impl sansio::Protocol<TaggedBytesMut, (), ()> for Mdns {
681 type Rout = ();
682 type Wout = TaggedBytesMut;
683 type Eout = MdnsEvent;
684 type Error = Error;
685 type Time = Instant;
686
687 /// Process an incoming mDNS packet.
688 ///
689 /// Call this method when a UDP packet is received on the mDNS multicast
690 /// address (224.0.0.251:5353).
691 ///
692 /// The connection will:
693 /// - Parse the packet as an mDNS message
694 /// - If it contains questions for our `local_names`, queue response packets
695 /// - If it contains answers matching pending queries, emit events
696 ///
697 /// # Arguments
698 ///
699 /// * `msg` - The received packet with transport context
700 ///
701 /// # Errors
702 ///
703 /// Returns [`Error::ErrConnectionClosed`] if the connection has been closed.
704 ///
705 /// # Example
706 ///
707 /// ```rust,ignore
708 /// use bytes::BytesMut;
709 /// use shared::{TaggedBytesMut, TransportContext, TransportProtocol};
710 /// use std::time::Instant;
711 ///
712 /// // When a packet arrives from the network:
713 /// let msg = TaggedBytesMut {
714 /// now: Instant::now(),
715 /// transport: TransportContext {
716 /// local_addr: "0.0.0.0:5353".parse().unwrap(),
717 /// peer_addr: src_addr,
718 /// transport_protocol: TransportProtocol::UDP,
719 /// ecn: None,
720 /// },
721 /// message: BytesMut::from(&packet_data[..]),
722 /// };
723 /// mdns.handle_read(msg)?;
724 ///
725 /// // Check for events
726 /// while let Some(event) = mdns.poll_event() {
727 /// // handle event
728 /// }
729 /// ```
730 fn handle_read(&mut self, msg: TaggedBytesMut) -> Result<()> {
731 if self.closed {
732 return Err(Error::ErrConnectionClosed);
733 }
734 self.process_message(&msg);
735 self.update_next_timeout();
736 Ok(())
737 }
738
739 /// mDNS doesn't produce read outputs.
740 ///
741 /// Answers to queries are delivered via `poll_event()`
742 /// as [`MdnsEvent::QueryAnswered`] events instead.
743 ///
744 /// # Returns
745 ///
746 /// Always returns `None`.
747 fn poll_read(&mut self) -> Option<Self::Rout> {
748 None
749 }
750
751 /// Handle write requests (not used).
752 ///
753 /// Queries are initiated via the [`query()`](Mdns::query) method instead
754 /// of through this interface.
755 fn handle_write(&mut self, _msg: ()) -> Result<()> {
756 Ok(())
757 }
758
759 /// Get the next packet to send.
760 ///
761 /// Call this method repeatedly until it returns `None` to retrieve all
762 /// queued packets. Packets should be sent via UDP to the address specified
763 /// in `packet.transport.peer_addr` (typically 224.0.0.251:5353).
764 ///
765 /// Packets are queued when:
766 /// - A query is started with [`query()`](Mdns::query)
767 /// - A query retry is triggered by `handle_timeout()`
768 /// - A response is generated for a matching question
769 ///
770 /// # Returns
771 ///
772 /// The next packet to send, or `None` if the queue is empty.
773 ///
774 /// # Example
775 ///
776 /// ```rust
777 /// use rtc_mdns::{MdnsConfig, Mdns};
778 /// use sansio::Protocol;
779 ///
780 /// let mut mdns = Mdns::new(MdnsConfig::default());
781 /// mdns.query("device.local");
782 ///
783 /// // Send all queued packets
784 /// while let Some(packet) = mdns.poll_write() {
785 /// // socket.send_to(&packet.message, packet.transport.peer_addr).await?;
786 /// println!("Send to {}", packet.transport.peer_addr);
787 /// }
788 /// ```
789 fn poll_write(&mut self) -> Option<Self::Wout> {
790 self.write_outs.pop_front()
791 }
792
793 /// Handle external events (not used).
794 ///
795 /// mDNS does not use external events. This method does nothing.
796 fn handle_event(&mut self, _evt: ()) -> Result<()> {
797 Ok(())
798 }
799
800 /// Get the next event.
801 ///
802 /// Call this method repeatedly until it returns `None` to process all
803 /// queued events. Events are generated when:
804 /// - An mDNS answer matches a pending query ([`MdnsEvent::QueryAnswered`])
805 ///
806 /// # Returns
807 ///
808 /// The next event, or `None` if the queue is empty.
809 ///
810 /// # Example
811 ///
812 /// ```rust,ignore
813 /// while let Some(event) = mdns.poll_event() {
814 /// match event {
815 /// MdnsEvent::QueryAnswered(query_id, addr) => {
816 /// println!("Query {} resolved to {}", query_id, addr);
817 /// }
818 /// MdnsEvent::QueryTimeout(id) => {
819 /// println!("Query {} timed out", id);
820 /// }
821 /// }
822 /// }
823 /// ```
824 fn poll_event(&mut self) -> Option<Self::Eout> {
825 self.event_outs.pop_front()
826 }
827
828 /// Handle timeout - retry pending queries.
829 ///
830 /// Call this method when the deadline from `poll_timeout()`
831 /// is reached. This triggers retry logic for pending queries.
832 ///
833 /// For each query whose retry time has passed, a new query packet will
834 /// be queued and can be retrieved with `poll_write()`.
835 ///
836 /// # Arguments
837 ///
838 /// * `now` - The current time
839 ///
840 /// # Errors
841 ///
842 /// Returns [`Error::ErrConnectionClosed`] if the connection has been closed.
843 ///
844 /// # Example
845 ///
846 /// ```rust
847 /// use rtc_mdns::{MdnsConfig, Mdns};
848 /// use sansio::Protocol;
849 /// use std::time::{Duration, Instant};
850 ///
851 /// let mut mdns = Mdns::new(
852 /// MdnsConfig::default().with_query_interval(Duration::from_millis(100))
853 /// );
854 /// mdns.query("device.local");
855 ///
856 /// // Consume initial packet
857 /// mdns.poll_write();
858 ///
859 /// // Simulate time passing
860 /// let future = Instant::now() + Duration::from_millis(150);
861 /// mdns.handle_timeout(future).unwrap();
862 ///
863 /// // A retry packet should be queued
864 /// assert!(mdns.poll_write().is_some());
865 /// ```
866 fn handle_timeout(&mut self, now: Self::Time) -> Result<()> {
867 if self.closed {
868 return Err(Error::ErrConnectionClosed);
869 }
870
871 if let Some(next_timeout) = self.next_timeout.as_ref()
872 && next_timeout <= &now
873 {
874 // Check for timed out queries first
875 if let Some(timeout_duration) = self.query_timeout {
876 let mut timed_out_ids = Vec::new();
877 for query in &self.queries {
878 if now.duration_since(query.start_time) >= timeout_duration {
879 timed_out_ids.push(query.id);
880 }
881 }
882
883 // Emit timeout events and remove timed out queries
884 for query_id in timed_out_ids {
885 log::debug!(
886 "mDNS Query {} timed out after {:?}",
887 query_id,
888 timeout_duration
889 );
890 self.event_outs.push_back(MdnsEvent::QueryTimeout(query_id));
891 self.queries.retain(|q| q.id != query_id);
892 }
893 }
894
895 // Retry queries that are due
896 let mut names_to_query = Vec::new();
897 for query in &mut self.queries {
898 if query.next_retry <= now {
899 names_to_query.push(query.name_with_suffix.clone());
900 query.next_retry = now + self.query_interval;
901 }
902 }
903
904 for name in names_to_query {
905 self.send_question(&name, now);
906 }
907
908 self.update_next_timeout();
909 }
910 Ok(())
911 }
912
913 /// Get the next timeout deadline.
914 ///
915 /// Returns the time at which `handle_timeout()` should
916 /// be called next. Use this to schedule your event loop's sleep/wait.
917 ///
918 /// # Returns
919 ///
920 /// - `Some(instant)` if there are pending queries that need retries
921 /// - `None` if there are no pending queries
922 ///
923 /// # Example
924 ///
925 /// ```rust
926 /// use rtc_mdns::{MdnsConfig, Mdns};
927 /// use sansio::Protocol;
928 ///
929 /// let mut mdns = Mdns::new(MdnsConfig::default());
930 ///
931 /// // No queries, no timeout
932 /// assert!(mdns.poll_timeout().is_none());
933 ///
934 /// // Start a query
935 /// mdns.query("device.local");
936 ///
937 /// // Now there's a timeout scheduled
938 /// assert!(mdns.poll_timeout().is_some());
939 /// ```
940 fn poll_timeout(&mut self) -> Option<Self::Time> {
941 self.next_timeout
942 }
943
944 /// Close the connection.
945 ///
946 /// This clears all pending queries and queued packets/events.
947 /// After closing, `handle_read()` and
948 /// `handle_timeout()` will return
949 /// [`Error::ErrConnectionClosed`].
950 ///
951 /// # Example
952 ///
953 /// ```rust
954 /// use rtc_mdns::{MdnsConfig, Mdns};
955 /// use sansio::Protocol;
956 ///
957 /// let mut mdns = Mdns::new(MdnsConfig::default());
958 /// mdns.query("device.local");
959 ///
960 /// assert_eq!(mdns.pending_query_count(), 1);
961 ///
962 /// mdns.close().unwrap();
963 ///
964 /// // All state is cleared
965 /// assert_eq!(mdns.pending_query_count(), 0);
966 /// assert!(mdns.poll_write().is_none());
967 /// assert!(mdns.poll_timeout().is_none());
968 /// ```
969 fn close(&mut self) -> Result<()> {
970 self.closed = true;
971 self.queries.clear();
972 self.write_outs.clear();
973 self.event_outs.clear();
974 self.next_timeout = None;
975 Ok(())
976 }
977}
978
979#[cfg(test)]
980mod mdns_test;