1use std::collections::{HashMap, HashSet};
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum ConnectionStatus {
5 Online,
6 Offline,
7 Connecting,
8}
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct ConnectionStats {
12 pub peer_id: String,
13 pub status: ConnectionStatus,
14 pub bootstrap_connections: u32,
15 pub direct_peers: u32,
16 pub relay_connections: u32,
17 pub active_transports: Vec<String>,
18 pub protocols: Vec<String>,
19 pub connected_peers: Vec<String>,
20 pub room_peers: Vec<String>,
21 pub peer_transports: HashMap<String, Vec<String>>,
22 pub peer_addresses: HashMap<String, Vec<String>>,
23 pub circuit_address: Option<String>,
24 pub web_rtc_address: Option<String>,
25}
26
27#[derive(Debug, Clone)]
29pub struct RawStats {
30 pub peer_id: String,
31 pub num_peers: u32,
32 pub relay_count: u32,
33 pub listeners: Vec<String>,
34 pub external_addresses: Vec<String>,
35 pub connected_peers: Vec<String>,
36 pub peer_conn_addrs: HashMap<String, Vec<String>>,
37 pub peer_advertised_addrs: HashMap<String, Vec<String>>,
38 pub circuit_address: Option<String>,
39 pub web_rtc_address: Option<String>,
40}
41
42pub fn build_connection_stats(raw: RawStats, room_peers: Vec<String>) -> ConnectionStats {
44 let direct = raw.num_peers.saturating_sub(raw.relay_count);
45
46 let mut transports = HashSet::new();
47 for addr in raw.listeners.iter().chain(raw.external_addresses.iter()) {
48 let name = transport_name(addr);
49 if name != "unknown" {
50 transports.insert(name.to_string());
51 }
52 }
53
54 let mut peer_transports = HashMap::new();
55 let mut peer_addresses = HashMap::new();
56 for peer_id in &raw.connected_peers {
57 let conn_addrs = raw.peer_conn_addrs.get(peer_id).cloned().unwrap_or_default();
58 let mut seen = HashSet::new();
59 let mut names = Vec::new();
60 for addr in &conn_addrs {
61 let name = transport_name(addr);
62 if name != "unknown" && seen.insert(name) {
63 names.push(name.to_string());
64 }
65 }
66 if (names.is_empty() || (names.len() == 1 && names[0] == "unknown"))
69 && let Some(advertised) = raw.peer_advertised_addrs.get(peer_id)
70 {
71 for addr in advertised {
72 let name = transport_name(addr);
73 if name != "unknown" && seen.insert(name) {
74 names.push(name.to_string());
75 }
76 }
77 }
78 names.retain(|n| n != "unknown");
80 peer_transports.insert(peer_id.clone(), names);
81 let addrs = raw.peer_advertised_addrs.get(peer_id).cloned().unwrap_or_default();
82 let useful_addrs: Vec<String> = addrs.into_iter().filter(|a| addr_has_transport(a)).collect();
83 peer_addresses.insert(peer_id.clone(), useful_addrs);
84 }
85
86 ConnectionStats {
87 peer_id: raw.peer_id,
88 status: if raw.num_peers > 0 {
89 ConnectionStatus::Online
90 } else {
91 ConnectionStatus::Offline
92 },
93 bootstrap_connections: 0,
94 direct_peers: direct,
95 relay_connections: raw.relay_count,
96 active_transports: transports.into_iter().collect(),
97 protocols: vec![],
98 connected_peers: raw.connected_peers,
99 room_peers,
100 peer_transports,
101 peer_addresses,
102 circuit_address: raw.circuit_address,
103 web_rtc_address: raw.web_rtc_address,
104 }
105}
106
107pub fn addr_has_transport(addr: &str) -> bool {
116 addr.contains("webrtc")
117 || addr.contains("webtransport")
118 || addr.contains("p2p-circuit")
119 || addr.contains("wss")
120 || addr.contains("ws")
121 || addr.contains("tcp")
122 || addr.contains("udp")
123 || addr.contains("quic")
124 || addr.contains("ip4")
125 || addr.contains("ip6")
126 || addr.contains("dns4")
127 || addr.contains("dns6")
128 || addr.contains("dns")
129}
130
131pub fn transport_name(addr: &str) -> &'static str {
137 if let Some(circuit_pos) = addr.find("p2p-circuit") {
141 if let Some(webrtc_pos) = addr.find("webrtc")
142 && webrtc_pos > circuit_pos
143 {
144 return "webrtc";
145 }
146 if let Some(wt_pos) = addr.find("webtransport")
147 && wt_pos > circuit_pos
148 {
149 return "webtransport";
150 }
151 return "p2p-circuit";
152 }
153
154 if addr.contains("webrtc-direct") {
155 "webrtc-direct"
156 } else if addr.contains("webrtc") {
157 "webrtc"
158 } else if addr.contains("webtransport") {
159 "webtransport"
160 } else if addr.contains("wss") || addr.contains("ws") {
161 "websocket"
162 } else if addr.contains("tcp") {
163 "tcp"
164 } else if addr.contains("quic") {
165 "quic"
166 } else {
167 "unknown"
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn transport_name_for_webrtc() {
177 let addr = "/dns4/example.com/tcp/443/wss/p2p-circuit/webrtc/p2p/12D3KooWGz9xtYbQ8UYj4oRmx4pD3vZvq6T5zN4pMjz6p6V7X8Y9";
178 assert_eq!(transport_name(addr), "webrtc");
179 }
180
181 #[test]
182 fn transport_name_for_wss() {
183 let addr = "/dns4/example.com/tcp/443/wss";
184 assert_eq!(transport_name(addr), "websocket");
185 }
186
187 #[test]
188 fn transport_name_for_tcp() {
189 let addr = "/ip4/127.0.0.1/tcp/4001";
190 assert_eq!(transport_name(addr), "tcp");
191 }
192
193 #[test]
194 fn transport_name_for_quic() {
195 let addr = "/ip4/127.0.0.1/udp/4001/quic-v1";
196 assert_eq!(transport_name(addr), "quic");
197 }
198
199 #[test]
200 fn transport_name_for_webrtc_direct() {
201 let addr = "/ip4/127.0.0.1/udp/4001/webrtc-direct/certhash/uEiA...";
202 assert_eq!(transport_name(addr), "webrtc-direct");
203 }
204
205 #[test]
206 fn transport_name_for_relay() {
207 let addr = "/dns4/example.com/tcp/443/wss/p2p-circuit/p2p/12D3KooWGz9xtYbQ8UYj4oRmx4pD3vZvq6T5zN4pMjz6p6V7X8Y9";
208 assert_eq!(transport_name(addr), "p2p-circuit");
209 }
210
211 #[test]
212 fn transport_name_for_circuit_over_webtransport() {
213 let addr = "/dns4/relay.example.com/udp/443/quic/webtransport/p2p/12D3KooWRelay/p2p-circuit/p2p/12D3KooWTarget";
214 assert_eq!(transport_name(addr), "p2p-circuit");
215 }
216
217 #[test]
218 fn transport_name_for_circuit_over_webrtc_signaling() {
219 let addr = "/dns4/relay.example.com/tcp/443/wss/p2p-circuit/webrtc/p2p/12D3KooWTarget";
220 assert_eq!(transport_name(addr), "webrtc");
221 }
222
223 #[test]
224 fn bare_peer_id_has_no_transport() {
225 let addr = "/p2p/12D3KooWKLKynHuhH8v2QNgfoj728nZLP83GUZpzeWX8um4VrVGa";
226 assert!(!addr_has_transport(addr));
227 }
228
229 #[test]
230 fn full_multiaddr_has_transport() {
231 let addr = "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWKLKynHuhH8v2QNgfoj728nZLP83GUZpzeWX8um4VrVGa";
232 assert!(addr_has_transport(addr));
233 }
234}