Skip to main content

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;