cotton_ssdp/
engine.rs

1use crate::message;
2use crate::message::Message;
3use crate::refresh_timer::{RefreshTimer, Timebase};
4use crate::udp;
5use crate::{Advertisement, Notification};
6use alloc::collections::BTreeMap;
7#[cfg(not(feature = "std"))]
8use alloc::{string::String, string::ToString, vec::Vec};
9use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
10use cotton_netif::{InterfaceIndex, NetworkEvent};
11use slotmap::SlotMap;
12
13const MAX_PACKET_SIZE: usize = 512;
14
15struct Interface {
16    ips: Vec<IpAddr>,
17    up: bool,
18}
19
20fn target_match(search: &str, candidate: &str) -> bool {
21    if search == "ssdp:all" {
22        return true;
23    }
24    if search == candidate {
25        return true;
26    }
27    // UPnP DA 1.0 s1.2.3
28    if let Some((sbase, sversion)) = search.rsplit_once(':') {
29        if let Some((cbase, cversion)) = candidate.rsplit_once(':') {
30            if sbase == cbase {
31                if let Ok(sversion) = sversion.parse::<usize>() {
32                    if let Ok(cversion) = cversion.parse::<usize>() {
33                        return cversion >= sversion;
34                    }
35                }
36            }
37        }
38    }
39    false
40}
41
42fn rewrite_host(url: &str, ip: &IpAddr) -> String {
43    let Some(prefix) = url.find("://") else {
44        return url.to_string();
45    };
46
47    if let Some(slash) = url[prefix + 3..].find('/') {
48        if let Some(colon) = url[prefix + 3..].find(':') {
49            if colon < slash {
50                return url[..prefix + 3].to_string()
51                    + &ip.to_string()
52                    + &url[colon + prefix + 3..];
53            }
54        }
55        return url[..prefix + 3].to_string()
56            + &ip.to_string()
57            + &url[slash + prefix + 3..];
58    }
59    url[..prefix + 3].to_string() + &ip.to_string()
60}
61
62/// A callback made by [`Engine`] when notification messages arrive
63///
64/// See implementations in [`crate::Service`] and [`crate::AsyncService`].
65///
66pub trait Callback {
67    /// An SSDP notification has been received
68    fn on_notification(&self, notification: &Notification);
69}
70
71struct ActiveSearch<CB: Callback> {
72    notification_type: String,
73    callback: CB,
74}
75
76slotmap::new_key_type! { struct ActiveSearchKey; }
77
78/// Is there an active search that we're going to respond to?`
79enum ResponseNeeded<Instant> {
80    None,
81    Multicast(Instant),
82    Unicast(Instant, SocketAddr, IpAddr, String),
83}
84
85struct ActiveAdvertisement<Instant> {
86    advertisement: Advertisement,
87    response_needed: ResponseNeeded<Instant>,
88}
89
90impl<Instant> ActiveAdvertisement<Instant> {
91    fn notify_on<SCK: udp::TargetedSend>(
92        &self,
93        unique_service_name: &str,
94        source: &IpAddr,
95        socket: &SCK,
96    ) {
97        let url = rewrite_host(&self.advertisement.location, source);
98        let _ = socket.send_with(
99            MAX_PACKET_SIZE,
100            &SocketAddr::V4(SocketAddrV4::new(
101                Ipv4Addr::new(239, 255, 255, 250),
102                1900,
103            )),
104            source,
105            |b| {
106                message::build_notify(
107                    b,
108                    &self.advertisement.notification_type,
109                    unique_service_name,
110                    &url,
111                )
112            },
113        );
114    }
115
116    fn notify_on_all<SCK: udp::TargetedSend>(
117        &self,
118        unique_service_name: &str,
119        interfaces: &BTreeMap<InterfaceIndex, Interface>,
120        socket: &SCK,
121    ) {
122        for interface in interfaces.values() {
123            if interface.up {
124                for ip in &interface.ips {
125                    self.notify_on(unique_service_name, ip, socket);
126                }
127            }
128        }
129    }
130}
131
132/// The core of an SSDP implementation
133///
134/// This low-level facility is usually wrapped-up in
135/// [`crate::Service`] or [`crate::AsyncService`] for use in larger
136/// programs, but can also be used directly when needed (e.g. on
137/// embedded systems).
138///
139/// This struct handles parsing and emitting SSDP messages; it does
140/// not own or define the UDP sockets themselves, which are left to
141/// its owner.  The owner should pass incoming UDP packets to
142/// [`Engine::on_data`], and changes to available network interfaces
143/// (if required) to [`Engine::on_network_event`].
144///
145/// The notifications will be retransmitted on a timer; the owner
146/// of the `Engine` should, each time incoming packets have been dealt
147/// with, call [`Engine::poll_timeout`] to determine the `Instant` when
148/// `Engine` next has work to do, and then once that Instant occurs, call
149/// [`Engine::handle_timeout`] so that the work can be done. See, for
150/// instance, the `tokio::select!` loop in `AsyncService::new_inner`.
151///
152pub struct Engine<CB: Callback, T: Timebase> {
153    interfaces: BTreeMap<InterfaceIndex, Interface>,
154    active_searches: SlotMap<ActiveSearchKey, ActiveSearch<CB>>,
155    advertisements: BTreeMap<String, ActiveAdvertisement<T::Instant>>,
156    refresh_timer: RefreshTimer<T>,
157    random_seed: u32,
158}
159
160impl<CB: Callback, T: Timebase> Engine<CB, T> {
161    /// Create a new Engine, parameterised by callback type
162    ///
163    #[must_use]
164    pub fn new(random_seed: u32, now: T::Instant) -> Self {
165        Self {
166            interfaces: BTreeMap::default(),
167            active_searches: SlotMap::with_key(),
168            advertisements: BTreeMap::default(),
169            refresh_timer: RefreshTimer::new(random_seed, now),
170            random_seed,
171        }
172    }
173
174    /// Deal with any expired timeouts
175    pub fn handle_timeout<SCK: udp::TargetedSend>(
176        &mut self,
177        socket: &SCK,
178        now: T::Instant,
179    ) {
180        if now >= self.refresh_timer.next_refresh() {
181            self.refresh(socket);
182            self.refresh_timer.update_refresh(now);
183        }
184
185        for (key, value) in &mut self.advertisements {
186            match &value.response_needed {
187                ResponseNeeded::Multicast(instant) => {
188                    if now >= *instant {
189                        value.notify_on_all(key, &self.interfaces, socket);
190                        value.response_needed = ResponseNeeded::None;
191                    }
192                }
193                ResponseNeeded::Unicast(
194                    instant,
195                    wasfrom,
196                    wasto,
197                    response_type,
198                ) => {
199                    if now >= *instant {
200                        Self::send_response(
201                            socket,
202                            *wasto,
203                            *wasfrom,
204                            key,
205                            response_type,
206                            &value.advertisement.location,
207                        );
208                        value.response_needed = ResponseNeeded::None;
209                    }
210                }
211                _ => (),
212            }
213        }
214    }
215
216    /// Obtain the desired delay before the next call to `handle_timeout`
217    pub fn poll_timeout(&self) -> T::Instant {
218        let mut next_wake = self.refresh_timer.next_refresh();
219        for value in self.advertisements.values() {
220            match value.response_needed {
221                ResponseNeeded::Multicast(instant) => {
222                    next_wake = next_wake.min(instant)
223                }
224                ResponseNeeded::Unicast(instant, _, _, _) => {
225                    next_wake = next_wake.min(instant)
226                }
227                _ => (),
228            }
229        }
230        next_wake
231    }
232
233    /// Reset the refresh timer (e.g. if network has gone away and come back)
234    pub fn reset_refresh_timer(&mut self, now: T::Instant) {
235        self.refresh_timer.reset(now);
236    }
237
238    /// Re-send all announcements
239    pub fn refresh<SCK: udp::TargetedSend>(&mut self, socket: &SCK) {
240        for (key, value) in &self.advertisements {
241            value.notify_on_all(key, &self.interfaces, socket);
242        }
243
244        // If anybody is doing an ssdp:all search, then we don't need to
245        // do any of the other searches.
246        if self
247            .active_searches
248            .values()
249            .any(|x| x.notification_type == "ssdp:all")
250        {
251            self.search_on_all("ssdp:all", socket);
252        } else {
253            for s in self.active_searches.values() {
254                self.search_on_all(&s.notification_type, socket);
255            }
256        }
257    }
258
259    fn search_on<SCK: udp::TargetedSend>(
260        search_type: &str,
261        source: &IpAddr,
262        socket: &SCK,
263    ) {
264        let _ = socket.send_with(
265            MAX_PACKET_SIZE,
266            &"239.255.255.250:1900".parse().unwrap(),
267            source,
268            |b| message::build_search(b, search_type),
269        );
270    }
271
272    fn search_on_all<SCK: udp::TargetedSend>(
273        &self,
274        search_type: &str,
275        socket: &SCK,
276    ) {
277        for interface in self.interfaces.values() {
278            if interface.up {
279                for ip in &interface.ips {
280                    Self::search_on(search_type, ip, socket);
281                }
282            }
283        }
284    }
285
286    /// Subscribe to notifications of a particular service type
287    ///
288    /// And send searches.
289    pub fn subscribe<SCK: udp::TargetedSend>(
290        &mut self,
291        notification_type: String,
292        callback: CB,
293        socket: &SCK,
294    ) {
295        self.search_on_all(&notification_type, socket);
296        let s = ActiveSearch {
297            notification_type,
298            callback,
299        };
300        self.active_searches.insert(s);
301    }
302
303    fn call_subscribers(&self, notification: &Notification) {
304        for s in self.active_searches.values() {
305            match notification {
306                Notification::ByeBye {
307                    notification_type, ..
308                }
309                | Notification::Alive {
310                    notification_type, ..
311                } => {
312                    if target_match(&s.notification_type, notification_type) {
313                        s.callback.on_notification(notification);
314                    }
315                }
316            }
317        }
318    }
319
320    fn send_response<SCK: udp::TargetedSend>(
321        socket: &SCK,
322        wasto: IpAddr,
323        wasfrom: SocketAddr,
324        service_name: &str,
325        response_type: &str,
326        location: &str,
327    ) {
328        let url = rewrite_host(location, &wasto);
329        let _ = socket.send_with(MAX_PACKET_SIZE, &wasfrom, &wasto, |b| {
330            message::build_response(b, response_type, service_name, &url)
331        });
332    }
333
334    /// Notify the `Engine` that data is ready on one of its sockets
335    pub fn on_data(
336        &mut self,
337        buf: &[u8],
338        wasto: IpAddr,
339        wasfrom: SocketAddr,
340        now: T::Instant,
341    ) {
342        if let Ok(m) = message::parse(buf) {
343            match m {
344                Message::NotifyAlive {
345                    notification_type,
346                    unique_service_name,
347                    location,
348                } => {
349                    self.call_subscribers(&Notification::Alive {
350                        notification_type,
351                        unique_service_name,
352                        location,
353                    });
354                }
355                Message::NotifyByeBye {
356                    notification_type,
357                    unique_service_name,
358                } => {
359                    self.call_subscribers(&Notification::ByeBye {
360                        notification_type,
361                        unique_service_name,
362                    });
363                }
364                Message::Search {
365                    search_target,
366                    maximum_wait_sec,
367                } => {
368                    let max_delay_ms =
369                        ((maximum_wait_sec as u32) * 1000).clamp(0, 5000);
370                    let delay_ms = (self.random_seed % max_delay_ms) + 10;
371                    let mut reply_at = now;
372                    reply_at +=
373                        core::time::Duration::from_millis(delay_ms.into())
374                            .into();
375                    for value in self.advertisements.values_mut() {
376                        if target_match(
377                            &search_target,
378                            &value.advertisement.notification_type,
379                        ) {
380                            match value.response_needed {
381                                ResponseNeeded::None => {
382                                    // Schedule a response
383                                    let response_type = if search_target
384                                        == "ssdp:all"
385                                    {
386                                        &value.advertisement.notification_type
387                                    } else {
388                                        &search_target
389                                    };
390                                    value.response_needed =
391                                        ResponseNeeded::Unicast(
392                                            reply_at,
393                                            wasfrom,
394                                            wasto,
395                                            response_type.to_string(),
396                                        );
397                                }
398                                ResponseNeeded::Unicast(
399                                    instant,
400                                    previous_from,
401                                    _,
402                                    _,
403                                ) => {
404                                    if wasfrom != previous_from {
405                                        // Two different searchers are now
406                                        // asking for this: send a
407                                        // multicast reply.
408                                        value.response_needed =
409                                            ResponseNeeded::Multicast(instant);
410                                    }
411                                }
412                                _ => (),
413                            }
414                        }
415                    }
416                }
417                Message::Response {
418                    search_target,
419                    unique_service_name,
420                    location,
421                } => {
422                    self.call_subscribers(&Notification::Alive {
423                        notification_type: search_target,
424                        unique_service_name,
425                        location,
426                    });
427                }
428            };
429        }
430    }
431
432    fn join_multicast<MCAST: udp::Multicast>(
433        interface: InterfaceIndex,
434        multicast: &MCAST,
435    ) -> Result<(), udp::Error> {
436        multicast.join_multicast_group(
437            &IpAddr::V4(Ipv4Addr::new(239, 255, 255, 250)),
438            interface,
439        )
440    }
441
442    fn leave_multicast<MCAST: udp::Multicast>(
443        interface: InterfaceIndex,
444        multicast: &MCAST,
445    ) -> Result<(), udp::Error> {
446        multicast.leave_multicast_group(
447            &IpAddr::V4(Ipv4Addr::new(239, 255, 255, 250)),
448            interface,
449        )
450    }
451
452    fn send_all<SCK: udp::TargetedSend>(&self, ips: &[IpAddr], search: &SCK) {
453        for ip in ips {
454            if self
455                .active_searches
456                .values()
457                .any(|x| x.notification_type == "ssdp:all")
458            {
459                Self::search_on("ssdp:all", ip, search);
460            } else {
461                for s in self.active_searches.values() {
462                    Self::search_on(&s.notification_type, ip, search);
463                }
464            }
465
466            for (key, value) in &self.advertisements {
467                value.notify_on(key, ip, search);
468            }
469        }
470    }
471
472    /// Notify the `Engine` of a new network interface
473    ///
474    /// NB. If your network-interface notifications are coming from `cotton-netif`,
475    /// you should call the general `on_network_event` instead of this specific
476    /// method.
477    ///
478    /// # Errors
479    ///
480    /// Passes on errors from the underlying system-calls for joining
481    /// multicast groups.
482    pub fn on_new_link_event<SCK: udp::TargetedSend, MCAST: udp::Multicast>(
483        &mut self,
484        ix: &InterfaceIndex,
485        flags: &cotton_netif::Flags,
486        multicast: &MCAST,
487        search: &SCK,
488    ) -> Result<(), udp::Error> {
489        if flags.contains(cotton_netif::Flags::MULTICAST) {
490            let up = flags.contains(
491                cotton_netif::Flags::RUNNING | cotton_netif::Flags::UP,
492            );
493            let mut do_send = false;
494            if let Some(v) = self.interfaces.get_mut(ix) {
495                if up && !v.up {
496                    do_send = true;
497                }
498                v.up = up;
499            } else {
500                Self::join_multicast(*ix, multicast)?;
501                self.interfaces.insert(
502                    *ix,
503                    Interface {
504                        ips: Vec::new(),
505                        up,
506                    },
507                );
508            }
509            if do_send {
510                self.send_all(&self.interfaces[ix].ips, search);
511            }
512        }
513        Ok(())
514    }
515
516    /// Notify the `Engine` of a deleted network interface
517    ///
518    /// NB. If your network-interface notifications are coming from `cotton-netif`,
519    /// you should call the general `on_network_event` instead of this specific
520    /// method.
521    ///
522    /// # Errors
523    ///
524    /// Passes on errors from the underlying system-calls for leaving
525    /// multicast groups.
526    pub fn on_del_link_event<MCAST: udp::Multicast>(
527        &mut self,
528        ix: &InterfaceIndex,
529        multicast: &MCAST,
530    ) -> Result<(), udp::Error> {
531        if self.interfaces.remove(ix).is_some() {
532            Self::leave_multicast(*ix, multicast)?;
533        }
534        Ok(())
535    }
536
537    /// Notify the `Engine` of a new IP address
538    ///
539    /// NB. If your IP address notifications are coming from `cotton-netif`,
540    /// you should call the general `on_network_event` instead of this specific
541    /// method.
542    pub fn on_new_addr_event<SCK: udp::TargetedSend>(
543        &mut self,
544        ix: &InterfaceIndex,
545        addr: &IpAddr,
546        search: &SCK,
547    ) {
548        if addr.is_ipv4() {
549            if let Some(ref mut v) = self.interfaces.get_mut(ix) {
550                if !v.ips.contains(addr) {
551                    v.ips.push(*addr);
552                    if v.up {
553                        self.send_all(&[*addr], search);
554                    }
555                }
556            }
557        }
558    }
559
560    /// Notify the `Engine` of a deleted IP address
561    ///
562    /// NB. If your IP address notifications are coming from `cotton-netif`,
563    /// you should call the general `on_network_event` instead of this specific
564    /// method.
565    pub fn on_del_addr_event(&mut self, ix: &InterfaceIndex, addr: &IpAddr) {
566        if let Some(ref mut v) = self.interfaces.get_mut(ix) {
567            if let Some(n) = v.ips.iter().position(|a| a == addr) {
568                v.ips.swap_remove(n);
569            }
570        }
571    }
572
573    /// Notify the `Engine` of a network interface change
574    ///
575    /// # Errors
576    ///
577    /// Passes on errors from the underlying system-calls for joining
578    /// (and leaving) multicast groups.
579    pub fn on_network_event<SCK: udp::TargetedSend, MCAST: udp::Multicast>(
580        &mut self,
581        e: &NetworkEvent,
582        multicast: &MCAST,
583        search: &SCK,
584    ) -> Result<(), udp::Error> {
585        match e {
586            NetworkEvent::NewLink(ix, _name, flags) => {
587                self.on_new_link_event(ix, flags, multicast, search)?;
588            }
589            NetworkEvent::DelLink(ix) => {
590                self.on_del_link_event(ix, multicast)?;
591            }
592            NetworkEvent::NewAddr(ix, addr, _prefix) => {
593                self.on_new_addr_event(ix, addr, search);
594            }
595            NetworkEvent::DelAddr(ix, addr, _prefix) => {
596                self.on_del_addr_event(ix, addr);
597            }
598        }
599        Ok(())
600    }
601
602    fn byebye_on<SCK: udp::TargetedSend>(
603        unique_service_name: &str,
604        notification_type: &str,
605        source: &IpAddr,
606        socket: &SCK,
607    ) {
608        let _ = socket.send_with(
609            MAX_PACKET_SIZE,
610            &SocketAddr::V4(SocketAddrV4::new(
611                Ipv4Addr::new(239, 255, 255, 250),
612                1900,
613            )),
614            source,
615            |b| {
616                message::build_byebye(
617                    b,
618                    unique_service_name,
619                    notification_type,
620                )
621            },
622        );
623    }
624
625    fn byebye_on_all<SCK: udp::TargetedSend>(
626        &self,
627        notification_type: &str,
628        unique_service_name: &str,
629        socket: &SCK,
630    ) {
631        for interface in self.interfaces.values() {
632            if interface.up {
633                for ip in &interface.ips {
634                    Self::byebye_on(
635                        notification_type,
636                        unique_service_name,
637                        ip,
638                        socket,
639                    );
640                }
641            }
642        }
643    }
644
645    /// Advertise a local resource to SSDP peers
646    pub fn advertise<SCK: udp::TargetedSend>(
647        &mut self,
648        unique_service_name: String,
649        advertisement: Advertisement,
650        socket: &SCK,
651    ) {
652        let active_advertisement = ActiveAdvertisement {
653            advertisement,
654            response_needed: ResponseNeeded::None,
655        };
656
657        active_advertisement.notify_on_all(
658            &unique_service_name,
659            &self.interfaces,
660            socket,
661        );
662        self.advertisements
663            .insert(unique_service_name, active_advertisement);
664    }
665
666    /// Withdraw an advertisement for a local resource
667    ///
668    /// For instance, it is "polite" to call this if shutting down
669    /// cleanly.
670    ///
671    pub fn deadvertise<SCK: udp::TargetedSend>(
672        &mut self,
673        unique_service_name: &str,
674        socket: &SCK,
675    ) {
676        if let Some(advertisement) =
677            self.advertisements.remove(unique_service_name)
678        {
679            self.byebye_on_all(
680                &advertisement.advertisement.notification_type,
681                unique_service_name,
682                socket,
683            );
684        }
685    }
686}
687
688#[cfg(all(test, feature = "std"))]
689mod tests {
690    use super::*;
691    use crate::message::parse;
692    use crate::refresh_timer::StdTimebase;
693    use core::net::{Ipv6Addr, SocketAddrV4};
694    use std::sync::{Arc, Mutex};
695    use std::time::Instant;
696
697    // Bit of a palaver to make make_index() const even though it can panic,
698    // see https://ktkaufman03.github.io/blog/2023/04/20/rust-compile-time-checks/
699    trait IsValidIndex {
700        const RESULT: ();
701    }
702
703    struct CustomIndex<const I: u32>;
704
705    impl<const I: u32> IsValidIndex for CustomIndex<I> {
706        const RESULT: () = assert!(I != 0, "Zero is not a valid index");
707    }
708
709    #[allow(clippy::let_unit_value)]
710    const fn make_index<const I: u32>() -> InterfaceIndex {
711        let _ = <CustomIndex<I> as IsValidIndex>::RESULT;
712        unsafe { InterfaceIndex(core::num::NonZeroU32::new_unchecked(I)) }
713    }
714
715    /* ==== Tests for target_match() ==== */
716
717    #[test]
718    fn target_match_ssdp_all() {
719        assert!(target_match("ssdp:all", "upnp::rootdevice"));
720        assert_eq!(false, target_match("upnp::rootdevice", "ssdp:all"));
721    }
722
723    #[test]
724    fn target_match_equality() {
725        assert!(target_match("upnp::rootdevice", "upnp::rootdevice"));
726    }
727
728    #[test]
729    fn target_match_downlevel() {
730        // If we search for CD:1 we should pick up CD:2's, but not vice versa
731        assert!(target_match(
732            "upnp::ContentDirectory:1",
733            "upnp::ContentDirectory:2"
734        ));
735        assert_eq!(
736            false,
737            target_match(
738                "upnp::ContentDirectory:2",
739                "upnp::ContentDirectory:1"
740            )
741        );
742
743        // Various noncanonical forms
744        assert_eq!(
745            false,
746            target_match("upnp::ContentDirectory", "upnp::ContentDirectory:1")
747        );
748        assert_eq!(
749            false,
750            target_match("upnp::ContentDirectory:1", "upnp::ContentDirectory")
751        );
752        assert_eq!(false, target_match("fnord", "upnp::ContentDirectory:1"));
753        assert_eq!(false, target_match("upnp::ContentDirectory:1", "fnord"));
754        assert_eq!(
755            false,
756            target_match(
757                "upnp::ContentDirectory:1",
758                "upnp::ContentDirectory:X"
759            )
760        );
761        assert_eq!(
762            false,
763            target_match(
764                "upnp::ContentDirectory:X",
765                "upnp::ContentDirectory:1"
766            )
767        );
768    }
769
770    #[derive(Default)]
771    struct FakeSocket {
772        sends: Mutex<Vec<(SocketAddr, IpAddr, Message)>>,
773        mcasts: Mutex<Vec<(IpAddr, InterfaceIndex, bool)>>,
774        injecting_multicast_error: bool,
775    }
776
777    impl FakeSocket {
778        fn contains_send<F>(
779            &self,
780            wasto: SocketAddr,
781            wasfrom: IpAddr,
782            mut f: F,
783        ) -> bool
784        where
785            F: FnMut(&Message) -> bool,
786        {
787            self.sends.lock().unwrap().iter().any(|(to, from, msg)| {
788                *to == wasto && *from == wasfrom && f(msg)
789            })
790        }
791
792        fn contains_search(&self, search: &str) -> bool {
793            self.contains_send(multicast_dest(), LOCAL_SRC, |m| {
794                matches!(m,
795                             Message::Search { search_target, .. }
796                             if search_target == search)
797            })
798        }
799
800        fn no_sends(&self) -> bool {
801            self.sends.lock().unwrap().is_empty()
802        }
803
804        fn send_count(&self) -> usize {
805            self.sends.lock().unwrap().len()
806        }
807
808        fn contains_mcast(
809            &self,
810            group: IpAddr,
811            interface: InterfaceIndex,
812            join: bool,
813        ) -> bool {
814            self.mcasts.lock().unwrap().iter().any(|(gp, ix, jn)| {
815                *gp == group && *ix == interface && *jn == join
816            })
817        }
818
819        fn no_mcasts(&self) -> bool {
820            self.mcasts.lock().unwrap().is_empty()
821        }
822
823        fn mcast_count(&self) -> usize {
824            self.mcasts.lock().unwrap().len()
825        }
826
827        fn clear(&self) {
828            self.sends.lock().unwrap().clear();
829            self.mcasts.lock().unwrap().clear();
830        }
831
832        fn build_notify(notification_type: &str) -> Vec<u8> {
833            let mut buf = [0u8; 512];
834
835            let n = message::build_notify(
836                &mut buf,
837                notification_type,
838                "uuid:37",
839                "http://me",
840            );
841            buf[0..n].to_vec()
842        }
843
844        fn build_byebye(notification_type: &str) -> Vec<u8> {
845            let mut buf = [0u8; 512];
846
847            let n =
848                message::build_byebye(&mut buf, notification_type, "uuid:37");
849            buf[0..n].to_vec()
850        }
851
852        fn build_response(notification_type: &str) -> Vec<u8> {
853            let mut buf = [0u8; 512];
854
855            let n = message::build_response(
856                &mut buf,
857                notification_type,
858                "uuid:37",
859                "http://me",
860            );
861            buf[0..n].to_vec()
862        }
863
864        fn build_search(notification_type: &str) -> Vec<u8> {
865            let mut buf = [0u8; 512];
866            let n = message::build_search(&mut buf, notification_type);
867            buf[0..n].to_vec()
868        }
869
870        fn inject_multicast_error(&mut self, errors: bool) {
871            self.injecting_multicast_error = errors;
872        }
873    }
874
875    impl udp::TargetedSend for FakeSocket {
876        fn send_with<F>(
877            &self,
878            size: usize,
879            to: &SocketAddr,
880            from: &IpAddr,
881            f: F,
882        ) -> Result<(), udp::Error>
883        where
884            F: FnOnce(&mut [u8]) -> usize,
885        {
886            let mut buffer = vec![0u8; size];
887            let actual_size = f(&mut buffer);
888            self.sends.lock().unwrap().push((
889                *to,
890                *from,
891                parse(&buffer[0..actual_size]).unwrap(),
892            ));
893            Ok(())
894        }
895    }
896
897    impl udp::Multicast for FakeSocket {
898        fn join_multicast_group(
899            &self,
900            multicast_address: &IpAddr,
901            interface: InterfaceIndex,
902        ) -> Result<(), udp::Error> {
903            if self.injecting_multicast_error {
904                Err(udp::Error::Syscall(
905                    udp::Syscall::JoinMulticast,
906                    std::io::Error::new(std::io::ErrorKind::Other, "injected"),
907                ))
908            } else {
909                self.mcasts.lock().unwrap().push((
910                    *multicast_address,
911                    interface,
912                    true,
913                ));
914                Ok(())
915            }
916        }
917
918        fn leave_multicast_group(
919            &self,
920            multicast_address: &IpAddr,
921            interface: InterfaceIndex,
922        ) -> Result<(), udp::Error> {
923            if self.injecting_multicast_error {
924                Err(udp::Error::Syscall(
925                    udp::Syscall::LeaveMulticast,
926                    std::io::Error::new(std::io::ErrorKind::Other, "injected"),
927                ))
928            } else {
929                self.mcasts.lock().unwrap().push((
930                    *multicast_address,
931                    interface,
932                    false,
933                ));
934                Ok(())
935            }
936        }
937    }
938
939    #[derive(Default, Clone)]
940    struct FakeCallback {
941        calls: Arc<Mutex<Vec<Notification>>>,
942    }
943
944    impl FakeCallback {
945        fn contains_notify(&self, desired_type: &str) -> bool {
946            self.calls.lock().unwrap().iter().any(|n| {
947                matches!(
948                n,
949                Notification::Alive { notification_type, .. }
950                if notification_type == desired_type
951                    )
952            })
953        }
954
955        fn contains_byebye(&self, desired_type: &str) -> bool {
956            self.calls.lock().unwrap().iter().any(|n| {
957                matches!(n,
958                Notification::ByeBye { notification_type, .. }
959                if notification_type == desired_type
960                )
961            })
962        }
963
964        fn no_notifies(&self) -> bool {
965            self.calls.lock().unwrap().is_empty()
966        }
967
968        fn clear(&mut self) {
969            self.calls.lock().unwrap().clear();
970        }
971    }
972
973    impl Callback for FakeCallback {
974        fn on_notification(&self, notification: &Notification) {
975            self.calls.lock().unwrap().push(notification.clone());
976        }
977    }
978
979    fn multicast_dest() -> SocketAddr {
980        SocketAddr::V4(SocketAddrV4::new(
981            Ipv4Addr::new(239, 255, 255, 250),
982            1900,
983        ))
984    }
985
986    const LOCAL_IX: InterfaceIndex = make_index::<4>();
987    const LOCAL_SRC: IpAddr = IpAddr::V4(Ipv4Addr::new(192, 168, 100, 1));
988    const LOCAL_SRC_2: IpAddr = IpAddr::V4(Ipv4Addr::new(169, 254, 33, 203));
989    const MULTICAST_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(239, 255, 255, 250));
990
991    fn remote_src() -> SocketAddr {
992        SocketAddr::V4(SocketAddrV4::new(
993            Ipv4Addr::new(192, 168, 100, 60),
994            12345,
995        ))
996    }
997
998    fn remote_src_2() -> SocketAddr {
999        SocketAddr::V4(SocketAddrV4::new(
1000            Ipv4Addr::new(192, 168, 100, 160),
1001            12345,
1002        ))
1003    }
1004
1005    fn remote_src_3() -> SocketAddr {
1006        SocketAddr::V4(SocketAddrV4::new(
1007            Ipv4Addr::new(192, 168, 100, 160),
1008            54321,
1009        ))
1010    }
1011
1012    fn new_eth0_if() -> NetworkEvent {
1013        NetworkEvent::NewLink(
1014            make_index::<4>(),
1015            "jeth0".to_string(),
1016            cotton_netif::Flags::UP
1017                | cotton_netif::Flags::RUNNING
1018                | cotton_netif::Flags::MULTICAST,
1019        )
1020    }
1021
1022    fn new_eth0_if_down() -> NetworkEvent {
1023        NetworkEvent::NewLink(
1024            LOCAL_IX,
1025            "jeth0".to_string(),
1026            cotton_netif::Flags::MULTICAST,
1027        )
1028    }
1029
1030    fn new_eth0_if_nomulti() -> NetworkEvent {
1031        NetworkEvent::NewLink(
1032            LOCAL_IX,
1033            "jeth0".to_string(),
1034            cotton_netif::Flags::UP | cotton_netif::Flags::RUNNING,
1035        )
1036    }
1037
1038    fn del_eth0() -> NetworkEvent {
1039        NetworkEvent::DelLink(LOCAL_IX)
1040    }
1041
1042    const NEW_ETH0_ADDR: NetworkEvent =
1043        NetworkEvent::NewAddr(LOCAL_IX, LOCAL_SRC, 8);
1044    const NEW_ETH0_ADDR_2: NetworkEvent =
1045        NetworkEvent::NewAddr(LOCAL_IX, LOCAL_SRC_2, 8);
1046    const DEL_ETH0_ADDR: NetworkEvent =
1047        NetworkEvent::DelAddr(LOCAL_IX, LOCAL_SRC, 8);
1048    const DEL_ETH0_ADDR_2: NetworkEvent =
1049        NetworkEvent::DelAddr(LOCAL_IX, LOCAL_SRC_2, 8);
1050
1051    const NEW_IPV6_ADDR: NetworkEvent =
1052        NetworkEvent::NewAddr(LOCAL_IX, IpAddr::V6(Ipv6Addr::LOCALHOST), 64);
1053
1054    fn root_advert() -> Advertisement {
1055        Advertisement {
1056            notification_type: "upnp:rootdevice".to_string(),
1057            location: "http://127.0.0.1/description.xml".to_string(),
1058        }
1059    }
1060
1061    fn root_advert_2() -> Advertisement {
1062        Advertisement {
1063            notification_type: "upnp:rootdevice".to_string(),
1064            location: "http://127.0.0.1/nested/description.xml".to_string(),
1065        }
1066    }
1067
1068    struct Fixture {
1069        e: Engine<FakeCallback, StdTimebase>,
1070        c: FakeCallback,
1071        s: FakeSocket,
1072    }
1073
1074    impl Default for Fixture {
1075        fn default() -> Self {
1076            Self {
1077                e: Engine::<FakeCallback, StdTimebase>::new(
1078                    0u32,
1079                    Instant::now(),
1080                ),
1081                c: FakeCallback::default(),
1082                s: FakeSocket::default(),
1083            }
1084        }
1085    }
1086
1087    impl Fixture {
1088        fn new_with<F: FnMut(&mut Fixture)>(mut f: F) -> Fixture {
1089            let mut fixture = Fixture::default();
1090            f(&mut fixture);
1091            fixture.c.clear();
1092            fixture.s.clear();
1093            fixture
1094        }
1095    }
1096
1097    /* ==== Tests for Engine ==== */
1098
1099    #[test]
1100    fn search_sent_on_network_event_if_already_subscribed() {
1101        let mut f = Fixture::new_with(|f| {
1102            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1103            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1104        });
1105
1106        f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1107
1108        assert!(f.s.send_count() == 1);
1109        assert!(f.s.contains_search("ssdp:all"));
1110    }
1111
1112    #[test]
1113    fn search_sent_on_subscribe_if_network_already_exists() {
1114        let mut f = Fixture::new_with(|f| {
1115            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1116            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1117        });
1118
1119        f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1120
1121        assert!(f.s.send_count() == 1);
1122        assert!(f.s.contains_send(
1123            multicast_dest(),
1124            LOCAL_SRC,
1125            |m| matches!(m,
1126                         Message::Search { search_target, .. }
1127                         if search_target == "ssdp:all")
1128        ));
1129    }
1130
1131    #[test]
1132    fn no_search_sent_on_down_interface() {
1133        let mut f = Fixture::new_with(|f| {
1134            f.e.on_network_event(&new_eth0_if_down(), &f.s, &f.s)
1135                .unwrap();
1136            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1137        });
1138
1139        f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1140
1141        assert!(f.s.no_sends());
1142    }
1143
1144    #[test]
1145    fn no_search_sent_on_non_multicast_interface() {
1146        let mut f = Fixture::new_with(|f| {
1147            f.e.on_network_event(&new_eth0_if_nomulti(), &f.s, &f.s)
1148                .unwrap();
1149            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1150        });
1151
1152        f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1153
1154        assert!(f.s.no_sends());
1155    }
1156
1157    #[test]
1158    fn searches_sent_on_two_ips() {
1159        let mut f = Fixture::new_with(|f| {
1160            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1161            f.e.on_network_event(&new_eth0_if_down(), &f.s, &f.s)
1162                .unwrap();
1163            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1164            f.e.on_network_event(&NEW_ETH0_ADDR_2, &f.s, &f.s).unwrap();
1165        });
1166
1167        f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1168
1169        assert!(f.s.send_count() == 2);
1170        assert!(f.s.contains_send(
1171            multicast_dest(),
1172            LOCAL_SRC,
1173            |m| matches!(m,
1174                         Message::Search { search_target, .. }
1175                         if search_target == "ssdp:all")
1176        ));
1177        assert!(f.s.contains_send(
1178            multicast_dest(),
1179            LOCAL_SRC_2,
1180            |m| matches!(m,
1181                         Message::Search { search_target, .. }
1182                         if search_target == "ssdp:all")
1183        ));
1184    }
1185
1186    #[test]
1187    fn no_search_sent_on_deleted_ips() {
1188        let mut f = Fixture::new_with(|f| {
1189            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1190            f.e.on_network_event(&new_eth0_if_down(), &f.s, &f.s)
1191                .unwrap();
1192            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1193            f.e.on_network_event(&NEW_ETH0_ADDR_2, &f.s, &f.s).unwrap();
1194            f.e.on_network_event(&DEL_ETH0_ADDR_2, &f.s, &f.s).unwrap();
1195        });
1196
1197        f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1198
1199        assert!(f.s.send_count() == 1);
1200        assert!(f.s.contains_send(
1201            multicast_dest(),
1202            LOCAL_SRC,
1203            |m| matches!(m,
1204                         Message::Search { search_target, .. }
1205                         if search_target == "ssdp:all")
1206        ));
1207    }
1208
1209    #[test]
1210    fn search_sent_on_interface_newly_up() {
1211        let mut f = Fixture::new_with(|f| {
1212            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1213            f.e.on_network_event(&new_eth0_if_down(), &f.s, &f.s)
1214                .unwrap();
1215            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1216        });
1217
1218        f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1219
1220        assert!(f.s.send_count() == 1);
1221        assert!(f.s.contains_send(
1222            multicast_dest(),
1223            LOCAL_SRC,
1224            |m| matches!(m,
1225                         Message::Search { search_target, .. }
1226                         if search_target == "ssdp:all")
1227        ));
1228    }
1229
1230    #[test]
1231    fn only_one_ssdpall_search_is_sent() {
1232        let mut f = Fixture::new_with(|f| {
1233            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1234            f.e.subscribe("upnp::Content:2".to_string(), f.c.clone(), &f.s);
1235            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1236        });
1237
1238        f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1239
1240        assert!(f.s.send_count() == 1);
1241        assert!(f.s.contains_send(
1242            multicast_dest(),
1243            LOCAL_SRC,
1244            |m| matches!(m,
1245                         Message::Search { search_target, .. }
1246                         if search_target == "ssdp:all")
1247        ));
1248    }
1249
1250    #[test]
1251    fn two_normal_searches_are_sent() {
1252        let mut f = Fixture::new_with(|f| {
1253            f.e.subscribe("upnp::Renderer:3".to_string(), f.c.clone(), &f.s);
1254            f.e.subscribe("upnp::Content:2".to_string(), f.c.clone(), &f.s);
1255            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1256        });
1257
1258        f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1259
1260        assert!(f.s.send_count() == 2);
1261        assert!(f.s.contains_send(
1262            multicast_dest(),
1263            LOCAL_SRC,
1264            |m| matches!(m,
1265                         Message::Search { search_target, .. }
1266                         if search_target == "upnp::Renderer:3")
1267        ));
1268        assert!(f.s.contains_send(
1269            multicast_dest(),
1270            LOCAL_SRC,
1271            |m| matches!(m,
1272                         Message::Search { search_target, .. }
1273                         if search_target == "upnp::Content:2")
1274        ));
1275    }
1276
1277    #[test]
1278    fn bogus_message_ignored() {
1279        let mut f = Fixture::default();
1280
1281        f.e.on_data(
1282            &[0, 1, 2, 3, 4, 5],
1283            LOCAL_SRC,
1284            remote_src(),
1285            Instant::now(),
1286        );
1287
1288        assert!(f.s.no_sends());
1289    }
1290
1291    #[test]
1292    fn notify_calls_subscriber() {
1293        let mut f = Fixture::new_with(|f| {
1294            f.e.subscribe("upnp::Renderer:3".to_string(), f.c.clone(), &f.s);
1295        });
1296
1297        let n = FakeSocket::build_notify("upnp::Renderer:3");
1298        f.e.on_data(&n, LOCAL_SRC, remote_src(), Instant::now());
1299
1300        assert_eq!(false, f.c.contains_byebye("upnp::Renderer:3"));
1301        assert!(f.c.contains_notify("upnp::Renderer:3"));
1302    }
1303
1304    #[test]
1305    fn notify_doesnt_call_subscriber() {
1306        let mut f = Fixture::new_with(|f| {
1307            f.e.subscribe("upnp::Renderer:3".to_string(), f.c.clone(), &f.s);
1308        });
1309
1310        let n = FakeSocket::build_notify("upnp::ContentDirectory:3");
1311        f.e.on_data(&n, LOCAL_SRC, remote_src(), Instant::now());
1312
1313        assert!(f.c.no_notifies()); // not interested in this NT
1314    }
1315
1316    #[test]
1317    fn response_calls_subscriber() {
1318        let mut f = Fixture::new_with(|f| {
1319            f.e.subscribe("upnp::Renderer:3".to_string(), f.c.clone(), &f.s);
1320        });
1321
1322        let n = FakeSocket::build_response("upnp::Renderer:3");
1323        f.e.on_data(&n, LOCAL_SRC, remote_src(), Instant::now());
1324
1325        assert!(f.c.contains_notify("upnp::Renderer:3"));
1326    }
1327
1328    #[test]
1329    fn response_doesnt_call_subscriber() {
1330        let mut f = Fixture::new_with(|f| {
1331            f.e.subscribe("upnp::Media:3".to_string(), f.c.clone(), &f.s);
1332        });
1333
1334        let n = FakeSocket::build_response("upnp::ContentDirectory:3");
1335        f.e.on_data(&n, LOCAL_SRC, remote_src(), Instant::now());
1336
1337        assert!(f.c.no_notifies()); // not interested in this NT
1338    }
1339
1340    #[test]
1341    fn notify_sent_on_network_event() {
1342        let mut f = Fixture::new_with(|f| {
1343            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1344            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1345        });
1346
1347        f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1348
1349        // Note URL has been rewritten to include the real IP address
1350        assert!(f.s.contains_send(
1351            multicast_dest(), LOCAL_SRC,
1352            |m| matches!(m,
1353                         Message::NotifyAlive { notification_type, unique_service_name, location }
1354                         if notification_type == "upnp:rootdevice"
1355                         && unique_service_name == "uuid:137"
1356                         && location == "http://192.168.100.1/description.xml")));
1357    }
1358
1359    #[test]
1360    fn no_notify_sent_on_down_interface() {
1361        let mut f = Fixture::new_with(|f| {
1362            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1363            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1364            f.e.on_network_event(&new_eth0_if_down(), &f.s, &f.s)
1365                .unwrap();
1366        });
1367
1368        f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1369
1370        assert!(f.s.no_sends());
1371    }
1372
1373    #[test]
1374    fn notify_sent_on_advertise() {
1375        let mut f = Fixture::new_with(|f| {
1376            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1377            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1378        });
1379
1380        f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1381
1382        assert!(f.s.contains_send(
1383            multicast_dest(), LOCAL_SRC,
1384            |m| matches!(m,
1385                         Message::NotifyAlive { notification_type, unique_service_name, location }
1386                         if notification_type == "upnp:rootdevice"
1387                         && unique_service_name == "uuid:137"
1388                         && location == "http://192.168.100.1/description.xml")));
1389    }
1390
1391    #[test]
1392    fn notify_sent_on_deadvertise() {
1393        let mut f = Fixture::new_with(|f| {
1394            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1395            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1396            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1397        });
1398
1399        f.e.deadvertise("uuid:137", &f.s);
1400
1401        assert!(f.s.contains_send(
1402            multicast_dest(),
1403            LOCAL_SRC,
1404            |m| matches!(m,
1405                         Message::NotifyByeBye { notification_type, unique_service_name }
1406                         if notification_type == "upnp:rootdevice"
1407                         && unique_service_name == "uuid:137")
1408        ));
1409    }
1410
1411    #[test]
1412    fn no_notify_sent_on_down_interface_on_deadvertise() {
1413        let mut f = Fixture::new_with(|f| {
1414            f.e.on_network_event(&new_eth0_if_down(), &f.s, &f.s)
1415                .unwrap();
1416            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1417            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1418        });
1419
1420        f.e.deadvertise("uuid:137", &f.s);
1421
1422        assert!(f.s.no_sends());
1423    }
1424
1425    #[test]
1426    fn response_sent_to_specific_search() {
1427        let mut f = Fixture::new_with(|f| {
1428            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1429            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1430            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1431        });
1432
1433        // Get initial announcement salvos out of the way
1434        let now = Instant::now() + core::time::Duration::from_secs(60);
1435        while f.e.poll_timeout() < now {
1436            f.e.handle_timeout(&f.s, now);
1437        }
1438
1439        f.s.clear();
1440
1441        let n = FakeSocket::build_search("upnp:rootdevice");
1442        let now = Instant::now();
1443        f.e.on_data(&n, LOCAL_SRC, remote_src(), now);
1444
1445        f.e.handle_timeout(&f.s, now);
1446        assert!(f.s.no_sends()); // not yet!
1447
1448        let next = f.e.poll_timeout() - now;
1449        assert!(next < std::time::Duration::from_secs(6));
1450
1451        f.e.handle_timeout(&f.s, now + std::time::Duration::from_secs(6));
1452
1453        assert!(f.s.contains_send(
1454            remote_src(), LOCAL_SRC,
1455            |m| matches!(m,
1456                         Message::Response { search_target, unique_service_name,
1457                                             location }
1458                         if search_target == "upnp:rootdevice"
1459                         && unique_service_name == "uuid:137"
1460                         && location == "http://192.168.100.1/description.xml")));
1461    }
1462
1463    #[test]
1464    fn response_multicast_to_multiple_searchers() {
1465        let mut f = Fixture::new_with(|f| {
1466            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1467            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1468            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1469        });
1470
1471        // Get initial announcement salvos out of the way
1472        let now = Instant::now() + core::time::Duration::from_secs(60);
1473        while f.e.poll_timeout() < now {
1474            f.e.handle_timeout(&f.s, now);
1475        }
1476        f.e.handle_timeout(&f.s, now);
1477
1478        f.s.clear();
1479
1480        let n = FakeSocket::build_search("upnp:rootdevice");
1481        f.e.on_data(&n, LOCAL_SRC, remote_src(), now);
1482        f.e.on_data(&n, LOCAL_SRC, remote_src_2(), now);
1483        f.e.on_data(&n, LOCAL_SRC, remote_src_3(), now);
1484
1485        let next = f.e.poll_timeout() - now;
1486        assert!(next < std::time::Duration::from_secs(6));
1487
1488        f.e.handle_timeout(&f.s, now);
1489
1490        assert!(f.s.no_sends()); // not yet!
1491
1492        f.e.handle_timeout(&f.s, now + std::time::Duration::from_secs(6));
1493
1494        assert!(f.s.contains_send(
1495            multicast_dest(),
1496            LOCAL_SRC,
1497            |m| matches!(m,
1498                         Message::NotifyAlive { notification_type, unique_service_name, location }
1499                         if notification_type == "upnp:rootdevice"
1500                         && unique_service_name == "uuid:137"
1501                         && location == "http://192.168.100.1/description.xml")));
1502    }
1503
1504    #[test]
1505    fn response_unicast_to_repeated_searchers() {
1506        let mut f = Fixture::new_with(|f| {
1507            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1508            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1509            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1510        });
1511
1512        // Get initial announcement salvos out of the way
1513        let now = Instant::now() + core::time::Duration::from_secs(60);
1514        while f.e.poll_timeout() < now {
1515            f.e.handle_timeout(&f.s, now);
1516        }
1517        f.e.handle_timeout(&f.s, now);
1518
1519        f.s.clear();
1520
1521        let n = FakeSocket::build_search("upnp:rootdevice");
1522        f.e.on_data(&n, LOCAL_SRC, remote_src(), now);
1523        f.e.on_data(&n, LOCAL_SRC, remote_src(), now);
1524
1525        let next = f.e.poll_timeout() - now;
1526        assert!(next < std::time::Duration::from_secs(6));
1527
1528        f.e.handle_timeout(&f.s, now);
1529
1530        assert!(f.s.no_sends()); // not yet!
1531
1532        f.e.handle_timeout(&f.s, now + std::time::Duration::from_secs(6));
1533
1534        assert!(f.s.contains_send(
1535            remote_src(), LOCAL_SRC,
1536            |m| matches!(m,
1537                         Message::Response { search_target, unique_service_name,
1538                                             location }
1539                         if search_target == "upnp:rootdevice"
1540                         && unique_service_name == "uuid:137"
1541                         && location == "http://192.168.100.1/description.xml")));
1542    }
1543
1544    #[test]
1545    fn response_sent_to_downlevel_search() {
1546        let mut f = Fixture::new_with(|f| {
1547            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1548            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1549            f.e.advertise(
1550                "uuid:137".to_string(),
1551                Advertisement {
1552                    notification_type: "upnp::Directory:3".to_string(),
1553                    location: "http://127.0.0.1/description.xml".to_string(),
1554                },
1555                &f.s,
1556            );
1557        });
1558
1559        // Get initial announcement salvos out of the way
1560        let now = Instant::now() + core::time::Duration::from_secs(60);
1561        while f.e.poll_timeout() < now {
1562            f.e.handle_timeout(&f.s, now);
1563        }
1564        f.e.handle_timeout(&f.s, now);
1565
1566        f.s.clear();
1567
1568        let n = FakeSocket::build_search("upnp::Directory:2");
1569        f.e.on_data(&n, LOCAL_SRC, remote_src(), now);
1570
1571        assert!(f.s.no_sends()); // not yet!
1572
1573        f.e.handle_timeout(&f.s, now + std::time::Duration::from_secs(6));
1574
1575        assert!(f.s.contains_send(
1576            remote_src(), LOCAL_SRC,
1577            |m| matches!(m,
1578                         Message::Response { search_target, unique_service_name,
1579                                             location }
1580                         if search_target == "upnp::Directory:2"
1581                         && unique_service_name == "uuid:137"
1582                         && location == "http://192.168.100.1/description.xml")));
1583    }
1584
1585    #[test]
1586    fn response_sent_to_generic_search() {
1587        let mut f = Fixture::new_with(|f| {
1588            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1589            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1590            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1591        });
1592
1593        // Get initial announcement salvos out of the way
1594        let now = Instant::now() + core::time::Duration::from_secs(60);
1595        while f.e.poll_timeout() < now {
1596            f.e.handle_timeout(&f.s, now);
1597        }
1598
1599        f.s.clear();
1600
1601        let n = FakeSocket::build_search("ssdp:all");
1602        f.e.on_data(&n, LOCAL_SRC, remote_src(), now);
1603
1604        assert!(f.s.no_sends()); // not yet!
1605
1606        f.e.handle_timeout(&f.s, now + std::time::Duration::from_secs(6));
1607
1608        assert!(f.s.contains_send(
1609            remote_src(), LOCAL_SRC,
1610            |m| matches!(m,
1611                         Message::Response { search_target, unique_service_name,
1612                                             location }
1613                         if search_target == "upnp:rootdevice"
1614                         && unique_service_name == "uuid:137"
1615                         && location == "http://192.168.100.1/description.xml")));
1616    }
1617
1618    #[test]
1619    fn response_not_sent_to_other_search() {
1620        let mut f = Fixture::new_with(|f| {
1621            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1622            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1623            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1624        });
1625
1626        // Get initial announcement salvos out of the way
1627        let now = Instant::now() + core::time::Duration::from_secs(60);
1628        while f.e.poll_timeout() < now {
1629            f.e.handle_timeout(&f.s, now);
1630        }
1631
1632        f.s.clear();
1633
1634        let n = FakeSocket::build_search("upnp::ContentDirectory:7");
1635        f.e.on_data(&n, LOCAL_SRC, remote_src(), now);
1636
1637        f.e.handle_timeout(&f.s, now + std::time::Duration::from_secs(6));
1638
1639        assert!(f.s.no_sends());
1640    }
1641
1642    #[test]
1643    fn byebye_calls_subscriber() {
1644        let mut f = Fixture::new_with(|f| {
1645            f.e.subscribe("upnp::Renderer:3".to_string(), f.c.clone(), &f.s);
1646        });
1647
1648        let n = FakeSocket::build_byebye("upnp::Renderer:3");
1649        f.e.on_data(&n, LOCAL_SRC, remote_src(), Instant::now());
1650
1651        assert_eq!(false, f.c.contains_notify("upnp::Renderer:3"));
1652        assert!(f.c.contains_byebye("upnp::Renderer:3"));
1653    }
1654
1655    /* ==== Tests for IPv4 multicast handling ==== */
1656
1657    #[test]
1658    fn join_multicast_on_new_interface() {
1659        let mut f = Fixture::default();
1660
1661        f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1662
1663        assert!(f.s.mcast_count() == 1);
1664        assert!(f.s.contains_mcast(MULTICAST_IP, LOCAL_IX, true));
1665    }
1666
1667    #[test]
1668    fn dont_join_multicast_on_repeat_interface() {
1669        let mut f = Fixture::new_with(|f| {
1670            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1671        });
1672
1673        f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1674
1675        assert!(f.s.no_mcasts());
1676    }
1677
1678    #[test]
1679    fn leave_multicast_on_interface_gone() {
1680        let mut f = Fixture::new_with(|f| {
1681            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1682            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1683        });
1684
1685        f.e.on_network_event(&del_eth0(), &f.s, &f.s).unwrap();
1686
1687        assert!(f.s.mcast_count() == 1);
1688        assert!(f.s.contains_mcast(MULTICAST_IP, LOCAL_IX, false));
1689    }
1690
1691    /* ==== Tests for multicast error handling ==== */
1692
1693    #[test]
1694    fn error_join_multicast_on_new_interface() {
1695        let mut f = Fixture::new_with(|f| {
1696            f.s.inject_multicast_error(true);
1697        });
1698
1699        assert!(f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).is_err());
1700    }
1701
1702    #[test]
1703    fn error_leave_multicast_on_interface_gone() {
1704        let mut f = Fixture::new_with(|f| {
1705            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1706            f.s.inject_multicast_error(true);
1707        });
1708
1709        assert!(f.e.on_network_event(&del_eth0(), &f.s, &f.s).is_err());
1710    }
1711
1712    #[test]
1713    fn refresh_retransmits_adverts() {
1714        let mut f = Fixture::new_with(|f| {
1715            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1716            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1717            f.e.advertise("uuid:137".to_string(), root_advert(), &f.s);
1718            f.e.advertise("uuid:XYZ".to_string(), root_advert_2(), &f.s);
1719        });
1720
1721        f.e.refresh(&f.s);
1722
1723        assert!(f.s.send_count() == 2);
1724        assert!(f.s.contains_send(
1725            multicast_dest(), LOCAL_SRC,
1726            |m| matches!(m,
1727                         Message::NotifyAlive { notification_type, unique_service_name, location }
1728                         if notification_type == "upnp:rootdevice"
1729                         && unique_service_name == "uuid:137"
1730                         && location == "http://192.168.100.1/description.xml")));
1731        assert!(f.s.contains_send(
1732            multicast_dest(), LOCAL_SRC,
1733            |m| matches!(m,
1734                         Message::NotifyAlive { notification_type, unique_service_name, location }
1735                         if notification_type == "upnp:rootdevice"
1736                         && unique_service_name == "uuid:XYZ"
1737                         && location == "http://192.168.100.1/nested/description.xml")));
1738    }
1739
1740    #[test]
1741    fn refresh_retransmits_searches() {
1742        let mut f = Fixture::new_with(|f| {
1743            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1744            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1745            f.e.subscribe("upnp::Renderer:3".to_string(), f.c.clone(), &f.s);
1746            f.e.subscribe("upnp::Content:2".to_string(), f.c.clone(), &f.s);
1747        });
1748
1749        f.e.refresh(&f.s);
1750
1751        assert!(f.s.send_count() == 2);
1752        assert!(f.s.contains_send(
1753            multicast_dest(),
1754            LOCAL_SRC,
1755            |m| matches!(m,
1756                         Message::Search { search_target, .. }
1757                         if search_target == "upnp::Renderer:3")
1758        ));
1759        assert!(f.s.contains_send(
1760            multicast_dest(),
1761            LOCAL_SRC,
1762            |m| matches!(m,
1763                         Message::Search { search_target, .. }
1764                         if search_target == "upnp::Content:2")
1765        ));
1766    }
1767
1768    #[test]
1769    fn refresh_retransmits_generic_search() {
1770        let mut f = Fixture::new_with(|f| {
1771            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1772            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1773            f.e.subscribe("upnp::Renderer:3".to_string(), f.c.clone(), &f.s);
1774            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1775        });
1776
1777        f.e.refresh(&f.s);
1778
1779        assert!(f.s.send_count() == 1);
1780        assert!(f.s.contains_send(
1781            multicast_dest(),
1782            LOCAL_SRC,
1783            |m| matches!(m,
1784                         Message::Search { search_target, .. }
1785                         if search_target == "ssdp:all")
1786        ));
1787    }
1788
1789    /* ==== Tests for out-of-sequence messages ==== */
1790
1791    #[test]
1792    fn bogus_dellink_ignored() {
1793        let mut f = Fixture::default();
1794
1795        f.e.on_network_event(&del_eth0(), &f.s, &f.s).unwrap();
1796    }
1797
1798    #[test]
1799    fn repeat_address_ignored() {
1800        let mut f = Fixture::new_with(|f| {
1801            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1802            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1803            f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1804        });
1805
1806        f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1807
1808        assert!(f.s.no_sends());
1809    }
1810
1811    #[test]
1812    fn address_before_link_ignored() {
1813        let mut f = Fixture::new_with(|f| {
1814            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1815        });
1816
1817        f.e.on_network_event(&NEW_ETH0_ADDR, &f.s, &f.s).unwrap();
1818
1819        assert!(f.s.no_sends());
1820    }
1821
1822    #[test]
1823    fn ipv6_address_ignored() {
1824        let mut f = Fixture::new_with(|f| {
1825            f.e.subscribe("ssdp:all".to_string(), f.c.clone(), &f.s);
1826            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1827        });
1828
1829        f.e.on_network_event(&NEW_IPV6_ADDR, &f.s, &f.s).unwrap();
1830
1831        assert!(f.s.no_sends());
1832    }
1833
1834    #[test]
1835    fn bogus_deladdr_ignored() {
1836        let mut f = Fixture::new_with(|f| {
1837            f.e.on_network_event(&new_eth0_if(), &f.s, &f.s).unwrap();
1838        });
1839
1840        f.e.on_network_event(&DEL_ETH0_ADDR, &f.s, &f.s).unwrap();
1841
1842        assert!(f.s.no_sends());
1843    }
1844
1845    #[test]
1846    fn bogus_deladdr_ignored_2() {
1847        let mut f = Fixture::default();
1848
1849        f.e.on_network_event(&DEL_ETH0_ADDR, &f.s, &f.s).unwrap();
1850
1851        assert!(f.s.no_sends());
1852    }
1853
1854    #[test]
1855    fn bogus_deadvertise_ignored() {
1856        let mut f = Fixture::default();
1857
1858        f.e.deadvertise("uuid:137", &f.s);
1859
1860        assert!(f.s.no_sends());
1861    }
1862
1863    #[test]
1864    fn url_host_rewritten() {
1865        let url = rewrite_host("http://127.0.0.1/description.xml", &LOCAL_SRC);
1866        assert_eq!(url, "http://192.168.100.1/description.xml");
1867    }
1868
1869    #[test]
1870    fn url_host_rewritten2() {
1871        let url = rewrite_host("http://127.0.0.1/", &LOCAL_SRC);
1872        assert_eq!(url, "http://192.168.100.1/");
1873    }
1874
1875    #[test]
1876    fn url_host_rewritten3() {
1877        let url = rewrite_host("http://127.0.0.1", &LOCAL_SRC);
1878        assert_eq!(url, "http://192.168.100.1");
1879    }
1880
1881    #[test]
1882    fn url_host_rewritten4() {
1883        let url = rewrite_host("http://127.0.0.1:3333/foo/bar", &LOCAL_SRC);
1884        assert_eq!(url, "http://192.168.100.1:3333/foo/bar");
1885    }
1886
1887    #[test]
1888    fn url_host_rewritten5() {
1889        // NB not a port number!
1890        let url =
1891            rewrite_host("http://127.0.0.1/foo:3333/foo/bar", &LOCAL_SRC);
1892        assert_eq!(url, "http://192.168.100.1/foo:3333/foo/bar");
1893    }
1894
1895    #[test]
1896    fn bogus_url_passed_through() {
1897        let url = rewrite_host("fnord", &LOCAL_SRC);
1898        assert_eq!(url, "fnord".to_string());
1899    }
1900
1901    #[test]
1902    fn bogus_url_passed_through2() {
1903        let url = rewrite_host("fnord:/", &LOCAL_SRC);
1904        assert_eq!(url, "fnord:/".to_string());
1905    }
1906
1907    #[test]
1908    fn reset() {
1909        let mut f = Fixture::default();
1910        let now = Instant::now();
1911        f.e.handle_timeout(&f.s, now);
1912        assert_ne!(f.e.poll_timeout(), now);
1913        f.e.reset_refresh_timer(now);
1914        assert_eq!(f.e.poll_timeout(), now);
1915    }
1916}