use crate::peer_connection::transport::ice::candidate::{
RTCIceServerTransportProtocol, RTCIceTcpCandidateType,
};
use crate::peer_connection::transport::ice::candidate_type::RTCIceCandidateType;
use crate::statistics::stats::ice_candidate::RTCIceCandidateStats;
use crate::statistics::stats::{RTCStats, RTCStatsType};
use std::time::Instant;
#[derive(Debug, Default, Clone)]
pub struct IceCandidateAccumulator {
pub transport_id: String,
pub address: Option<String>,
pub port: u16,
pub protocol: String,
pub candidate_type: RTCIceCandidateType,
pub priority: u16,
pub url: String,
pub relay_protocol: RTCIceServerTransportProtocol,
pub foundation: String,
pub related_address: String,
pub related_port: u16,
pub username_fragment: String,
pub tcp_type: RTCIceTcpCandidateType,
}
impl IceCandidateAccumulator {
pub fn snapshot(&self, now: Instant, id: &str, is_local: bool) -> RTCIceCandidateStats {
RTCIceCandidateStats {
stats: RTCStats {
timestamp: now,
typ: if is_local {
RTCStatsType::LocalCandidate
} else {
RTCStatsType::RemoteCandidate
},
id: id.to_string(),
},
transport_id: self.transport_id.clone(),
address: self.address.clone(),
port: self.port,
protocol: self.protocol.clone(),
candidate_type: self.candidate_type,
priority: self.priority,
url: self.url.clone(),
relay_protocol: self.relay_protocol,
foundation: self.foundation.clone(),
related_address: self.related_address.clone(),
related_port: self.related_port,
username_fragment: self.username_fragment.clone(),
tcp_type: self.tcp_type,
}
}
pub fn snapshot_local(&self, now: Instant, id: &str) -> RTCIceCandidateStats {
self.snapshot(now, id, true)
}
pub fn snapshot_remote(&self, now: Instant, id: &str) -> RTCIceCandidateStats {
self.snapshot(now, id, false)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
let acc = IceCandidateAccumulator::default();
assert_eq!(acc.transport_id, "");
assert_eq!(acc.address, None);
assert_eq!(acc.port, 0);
assert_eq!(acc.protocol, "");
assert_eq!(acc.candidate_type, RTCIceCandidateType::default());
assert_eq!(acc.priority, 0);
assert_eq!(acc.url, "");
assert_eq!(acc.foundation, "");
assert_eq!(acc.related_address, "");
assert_eq!(acc.related_port, 0);
assert_eq!(acc.username_fragment, "");
}
#[test]
fn test_snapshot_local_host_candidate() {
let now = Instant::now();
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: Some("192.168.1.100".to_string()),
port: 50000,
protocol: "udp".to_string(),
candidate_type: RTCIceCandidateType::Host,
priority: 65535, foundation: "1234567890".to_string(),
username_fragment: "abcd1234".to_string(),
..Default::default()
};
let stats = acc.snapshot_local(now, "RTCIceCandidate_host_udp_192.168.1.100_50000");
assert_eq!(
stats.stats.id,
"RTCIceCandidate_host_udp_192.168.1.100_50000"
);
assert_eq!(stats.stats.typ, RTCStatsType::LocalCandidate);
assert_eq!(stats.stats.timestamp, now);
assert_eq!(stats.transport_id, "RTCTransport_0");
assert_eq!(stats.address, Some("192.168.1.100".to_string()));
assert_eq!(stats.port, 50000);
assert_eq!(stats.protocol, "udp");
assert_eq!(stats.candidate_type, RTCIceCandidateType::Host);
assert_eq!(stats.priority, 65535);
assert_eq!(stats.foundation, "1234567890");
assert_eq!(stats.username_fragment, "abcd1234");
}
#[test]
fn test_snapshot_remote_srflx_candidate() {
let now = Instant::now();
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: Some("203.0.113.50".to_string()),
port: 60000,
protocol: "udp".to_string(),
candidate_type: RTCIceCandidateType::Srflx,
priority: 50000,
foundation: "9876543210".to_string(),
related_address: "192.168.1.100".to_string(),
related_port: 50000,
url: "stun:stun.example.com:3478".to_string(),
username_fragment: "efgh5678".to_string(),
..Default::default()
};
let stats = acc.snapshot_remote(now, "RTCIceCandidate_srflx_udp_203.0.113.50_60000");
assert_eq!(stats.stats.typ, RTCStatsType::RemoteCandidate);
assert_eq!(stats.address, Some("203.0.113.50".to_string()));
assert_eq!(stats.port, 60000);
assert_eq!(stats.candidate_type, RTCIceCandidateType::Srflx);
assert_eq!(stats.related_address, "192.168.1.100");
assert_eq!(stats.related_port, 50000);
assert_eq!(stats.url, "stun:stun.example.com:3478");
}
#[test]
fn test_snapshot_relay_candidate() {
let now = Instant::now();
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: Some("10.0.0.50".to_string()),
port: 49152,
protocol: "udp".to_string(),
candidate_type: RTCIceCandidateType::Relay,
priority: 10000,
foundation: "relay123".to_string(),
related_address: "192.168.1.100".to_string(),
related_port: 50000,
url: "turn:turn.example.com:3478".to_string(),
relay_protocol: RTCIceServerTransportProtocol::Udp,
username_fragment: "ijkl9012".to_string(),
..Default::default()
};
let stats = acc.snapshot_local(now, "RTCIceCandidate_relay");
assert_eq!(stats.stats.typ, RTCStatsType::LocalCandidate);
assert_eq!(stats.candidate_type, RTCIceCandidateType::Relay);
assert_eq!(stats.relay_protocol, RTCIceServerTransportProtocol::Udp);
assert_eq!(stats.url, "turn:turn.example.com:3478");
}
#[test]
fn test_snapshot_tcp_candidate() {
let now = Instant::now();
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: Some("192.168.1.100".to_string()),
port: 9,
protocol: "tcp".to_string(),
candidate_type: RTCIceCandidateType::Host,
priority: 60000,
foundation: "tcp123".to_string(),
tcp_type: RTCIceTcpCandidateType::Active,
..Default::default()
};
let stats = acc.snapshot_local(now, "RTCIceCandidate_host_tcp");
assert_eq!(stats.protocol, "tcp");
assert_eq!(stats.tcp_type, RTCIceTcpCandidateType::Active);
}
#[test]
fn test_snapshot_local_vs_remote_type() {
let now = Instant::now();
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: Some("192.168.1.100".to_string()),
port: 50000,
protocol: "udp".to_string(),
candidate_type: RTCIceCandidateType::Host,
..Default::default()
};
let local_stats = acc.snapshot_local(now, "local_id");
let remote_stats = acc.snapshot_remote(now, "remote_id");
assert_eq!(local_stats.stats.typ, RTCStatsType::LocalCandidate);
assert_eq!(remote_stats.stats.typ, RTCStatsType::RemoteCandidate);
}
#[test]
fn test_clone() {
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: Some("192.168.1.100".to_string()),
port: 50000,
protocol: "udp".to_string(),
candidate_type: RTCIceCandidateType::Host,
priority: 65535,
foundation: "1234567890".to_string(),
..Default::default()
};
let cloned = acc.clone();
assert_eq!(cloned.transport_id, acc.transport_id);
assert_eq!(cloned.address, acc.address);
assert_eq!(cloned.port, acc.port);
assert_eq!(cloned.candidate_type, acc.candidate_type);
}
#[test]
fn test_snapshot_json_serialization_local() {
let now = Instant::now();
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: Some("192.168.1.100".to_string()),
port: 50000,
protocol: "udp".to_string(),
candidate_type: RTCIceCandidateType::Host,
priority: 65535,
foundation: "abcd1234".to_string(),
username_fragment: "user123".to_string(),
..Default::default()
};
let stats = acc.snapshot_local(now, "RTCIceCandidate_1");
let json = serde_json::to_string(&stats).expect("should serialize");
assert!(json.contains("\"address\":\"192.168.1.100\""));
assert!(json.contains("\"port\":50000"));
assert!(json.contains("\"protocol\":\"udp\""));
assert!(json.contains("\"candidateType\":\"host\""));
assert!(json.contains("\"type\":\"local-candidate\""));
}
#[test]
fn test_snapshot_json_serialization_remote() {
let now = Instant::now();
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: Some("203.0.113.50".to_string()),
port: 60000,
protocol: "udp".to_string(),
candidate_type: RTCIceCandidateType::Srflx,
..Default::default()
};
let stats = acc.snapshot_remote(now, "RTCIceCandidate_2");
let json = serde_json::to_string(&stats).expect("should serialize");
assert!(json.contains("\"candidateType\":\"srflx\""));
assert!(json.contains("\"type\":\"remote-candidate\""));
}
#[test]
fn test_address_none() {
let now = Instant::now();
let acc = IceCandidateAccumulator {
transport_id: "RTCTransport_0".to_string(),
address: None, port: 50000,
protocol: "udp".to_string(),
candidate_type: RTCIceCandidateType::Host,
..Default::default()
};
let stats = acc.snapshot_local(now, "RTCIceCandidate_mdns");
assert_eq!(stats.address, None);
let json = serde_json::to_string(&stats).expect("should serialize");
assert!(json.contains("\"address\":null"));
}
}