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 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
62pub trait Callback {
67 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
78enum 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
132pub 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 #[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 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 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 pub fn reset_refresh_timer(&mut self, now: T::Instant) {
235 self.refresh_timer.reset(now);
236 }
237
238 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 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 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(¬ification_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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 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 #[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()); }
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()); }
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 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 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()); 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 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()); 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 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()); 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 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()); 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 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()); 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 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 #[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 #[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 #[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 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}