1use alloc::{collections::BTreeSet, string::String};
8use core::net::SocketAddr;
9
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
13pub struct StunFacts {
14 pub working_udp: bool,
16 pub mapping_varies: bool,
19}
20
21impl StunFacts {
22 pub fn from_reflexive(reflexive: &[SocketAddr]) -> Self {
24 let distinct_v4: BTreeSet<&SocketAddr> = reflexive.iter().filter(|a| a.is_ipv4()).collect();
25 Self {
26 working_udp: !reflexive.is_empty(),
27 mapping_varies: distinct_v4.len() >= 2,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
34pub struct PortMapFacets {
35 pub upnp: Option<bool>,
37 pub pmp: Option<bool>,
39 pub pcp: Option<bool>,
41 pub have_port_map: bool,
44}
45
46impl PortMapFacets {
47 pub fn summary(&self) -> String {
53 if !self.have_port_map && self.upnp.is_none() && self.pmp.is_none() && self.pcp.is_none() {
54 return "?".into();
55 }
56 let mut s = String::new();
57 if self.have_port_map {
58 s.push_str("active-");
59 }
60 for (v, c) in [(self.upnp, 'U'), (self.pmp, 'M'), (self.pcp, 'C')] {
61 match v {
62 None => s.push('_'),
63 Some(true) => s.push(c),
64 Some(false) => {}
65 }
66 }
67 s
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use alloc::vec::Vec;
74
75 use super::*;
76
77 fn addrs(list: &[&str]) -> Vec<SocketAddr> {
78 list.iter().map(|a| a.parse().unwrap()).collect()
79 }
80
81 #[test]
82 fn stun_facts() {
83 assert_eq!(StunFacts::from_reflexive(&[]), StunFacts::default());
84 let one = StunFacts::from_reflexive(&addrs(&["203.0.113.7:41641"]));
85 assert!(one.working_udp && !one.mapping_varies);
86 let two = StunFacts::from_reflexive(&addrs(&["203.0.113.7:41641", "198.51.100.9:51000"]));
87 assert!(two.working_udp && two.mapping_varies);
88 let dup = StunFacts::from_reflexive(&addrs(&["203.0.113.7:41641", "203.0.113.7:41641"]));
89 assert!(!dup.mapping_varies);
90 let v6 = StunFacts::from_reflexive(&addrs(&["[2001:db8::1]:1", "[2001:db8::2]:1"]));
91 assert!(v6.working_udp && !v6.mapping_varies);
92 }
93
94 #[test]
97 fn summary_matches_go() {
98 let f = |upnp, pmp, pcp, have| PortMapFacets {
99 upnp,
100 pmp,
101 pcp,
102 have_port_map: have,
103 };
104 assert_eq!(f(None, None, None, false).summary(), "?");
105 assert_eq!(
106 f(Some(true), Some(true), Some(true), false).summary(),
107 "UMC"
108 );
109 assert_eq!(
110 f(Some(true), Some(false), Some(true), false).summary(),
111 "UC"
112 );
113 assert_eq!(
114 f(None, Some(true), Some(true), true).summary(),
115 "active-_MC"
116 );
117 assert_eq!(f(None, Some(false), Some(false), false).summary(), "_");
118 assert_eq!(f(None, None, None, true).summary(), "active-___");
120 }
121}