use crate::peer_connection::transport::{
RTCDtlsRole, RTCDtlsTransportState, RTCIceRole, RTCIceTransportState,
};
use crate::statistics::stats::transport::RTCTransportStats;
use crate::statistics::stats::{RTCStats, RTCStatsType};
use std::time::Instant;
#[derive(Debug)]
pub struct TransportStatsAccumulator {
pub transport_id: String,
pub packets_sent: u64,
pub packets_received: u64,
pub bytes_sent: u64,
pub bytes_received: u64,
pub ice_role: RTCIceRole,
pub ice_local_username_fragment: String,
pub ice_state: RTCIceTransportState,
pub dtls_state: RTCDtlsTransportState,
pub dtls_role: RTCDtlsRole,
pub tls_version: String,
pub dtls_cipher: String,
pub srtp_cipher: String,
pub selected_candidate_pair_id: String,
pub selected_candidate_pair_changes: u32,
pub local_certificate_id: String,
pub remote_certificate_id: String,
pub ccfb_messages_sent: u32,
pub ccfb_messages_received: u32,
}
impl TransportStatsAccumulator {
pub fn on_packet_sent(&mut self, bytes: usize) {
self.packets_sent += 1;
self.bytes_sent += bytes as u64;
}
pub fn on_packet_received(&mut self, bytes: usize) {
self.packets_received += 1;
self.bytes_received += bytes as u64;
}
pub fn on_selected_candidate_pair_changed(&mut self, pair_id: String) {
self.selected_candidate_pair_id = pair_id;
self.selected_candidate_pair_changes += 1;
}
pub fn on_ice_state_changed(&mut self, state: RTCIceTransportState) {
self.ice_state = state;
}
pub fn on_dtls_state_changed(&mut self, state: RTCDtlsTransportState) {
self.dtls_state = state;
}
pub fn on_dtls_handshake_complete(
&mut self,
tls_version: String,
dtls_cipher: String,
srtp_cipher: String,
dtls_role: RTCDtlsRole,
) {
self.tls_version = tls_version;
self.dtls_cipher = dtls_cipher;
self.srtp_cipher = srtp_cipher;
self.dtls_role = dtls_role;
}
pub fn on_ccfb_sent(&mut self) {
self.ccfb_messages_sent += 1;
}
pub fn on_ccfb_received(&mut self) {
self.ccfb_messages_received += 1;
}
pub fn snapshot(&self, now: Instant) -> RTCTransportStats {
RTCTransportStats {
stats: RTCStats {
timestamp: now,
typ: RTCStatsType::Transport,
id: self.transport_id.clone(),
},
packets_sent: self.packets_sent,
packets_received: self.packets_received,
bytes_sent: self.bytes_sent,
bytes_received: self.bytes_received,
ice_role: self.ice_role,
ice_local_username_fragment: self.ice_local_username_fragment.clone(),
dtls_state: self.dtls_state,
ice_state: self.ice_state,
selected_candidate_pair_id: self.selected_candidate_pair_id.clone(),
local_certificate_id: self.local_certificate_id.clone(),
remote_certificate_id: self.remote_certificate_id.clone(),
tls_version: self.tls_version.clone(),
dtls_cipher: self.dtls_cipher.clone(),
dtls_role: self.dtls_role,
srtp_cipher: self.srtp_cipher.clone(),
selected_candidate_pair_changes: self.selected_candidate_pair_changes,
ccfb_messages_sent: self.ccfb_messages_sent,
ccfb_messages_received: self.ccfb_messages_received,
}
}
}
impl Default for TransportStatsAccumulator {
fn default() -> Self {
Self {
transport_id: "RTCTransport".to_string(),
packets_sent: 0,
packets_received: 0,
bytes_sent: 0,
bytes_received: 0,
ice_role: RTCIceRole::default(),
ice_local_username_fragment: String::new(),
ice_state: RTCIceTransportState::default(),
dtls_state: RTCDtlsTransportState::default(),
dtls_role: RTCDtlsRole::default(),
tls_version: String::new(),
dtls_cipher: String::new(),
srtp_cipher: String::new(),
selected_candidate_pair_id: String::new(),
selected_candidate_pair_changes: 0,
local_certificate_id: String::new(),
remote_certificate_id: String::new(),
ccfb_messages_sent: 0,
ccfb_messages_received: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
let acc = TransportStatsAccumulator::default();
assert_eq!(acc.transport_id, "RTCTransport");
assert_eq!(acc.packets_sent, 0);
assert_eq!(acc.packets_received, 0);
assert_eq!(acc.bytes_sent, 0);
assert_eq!(acc.bytes_received, 0);
assert_eq!(acc.selected_candidate_pair_changes, 0);
assert_eq!(acc.ccfb_messages_sent, 0);
assert_eq!(acc.ccfb_messages_received, 0);
}
#[test]
fn test_on_packet_sent() {
let mut acc = TransportStatsAccumulator::default();
acc.on_packet_sent(100);
assert_eq!(acc.packets_sent, 1);
assert_eq!(acc.bytes_sent, 100);
acc.on_packet_sent(200);
assert_eq!(acc.packets_sent, 2);
assert_eq!(acc.bytes_sent, 300);
}
#[test]
fn test_on_packet_received() {
let mut acc = TransportStatsAccumulator::default();
acc.on_packet_received(150);
assert_eq!(acc.packets_received, 1);
assert_eq!(acc.bytes_received, 150);
acc.on_packet_received(250);
assert_eq!(acc.packets_received, 2);
assert_eq!(acc.bytes_received, 400);
}
#[test]
fn test_on_selected_candidate_pair_changed() {
let mut acc = TransportStatsAccumulator::default();
acc.on_selected_candidate_pair_changed("pair_1".to_string());
assert_eq!(acc.selected_candidate_pair_id, "pair_1");
assert_eq!(acc.selected_candidate_pair_changes, 1);
acc.on_selected_candidate_pair_changed("pair_2".to_string());
assert_eq!(acc.selected_candidate_pair_id, "pair_2");
assert_eq!(acc.selected_candidate_pair_changes, 2);
}
#[test]
fn test_on_ice_state_changed() {
let mut acc = TransportStatsAccumulator::default();
acc.on_ice_state_changed(RTCIceTransportState::Checking);
assert_eq!(acc.ice_state, RTCIceTransportState::Checking);
acc.on_ice_state_changed(RTCIceTransportState::Connected);
assert_eq!(acc.ice_state, RTCIceTransportState::Connected);
acc.on_ice_state_changed(RTCIceTransportState::Completed);
assert_eq!(acc.ice_state, RTCIceTransportState::Completed);
}
#[test]
fn test_on_dtls_state_changed() {
let mut acc = TransportStatsAccumulator::default();
acc.on_dtls_state_changed(RTCDtlsTransportState::Connecting);
assert_eq!(acc.dtls_state, RTCDtlsTransportState::Connecting);
acc.on_dtls_state_changed(RTCDtlsTransportState::Connected);
assert_eq!(acc.dtls_state, RTCDtlsTransportState::Connected);
}
#[test]
fn test_on_dtls_handshake_complete() {
let mut acc = TransportStatsAccumulator::default();
acc.on_dtls_handshake_complete(
"DTLS 1.2".to_string(),
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".to_string(),
"SRTP_AES128_CM_HMAC_SHA1_80".to_string(),
RTCDtlsRole::Client,
);
assert_eq!(acc.tls_version, "DTLS 1.2");
assert_eq!(acc.dtls_cipher, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
assert_eq!(acc.srtp_cipher, "SRTP_AES128_CM_HMAC_SHA1_80");
assert_eq!(acc.dtls_role, RTCDtlsRole::Client);
}
#[test]
fn test_on_ccfb_sent_received() {
let mut acc = TransportStatsAccumulator::default();
acc.on_ccfb_sent();
acc.on_ccfb_sent();
assert_eq!(acc.ccfb_messages_sent, 2);
acc.on_ccfb_received();
assert_eq!(acc.ccfb_messages_received, 1);
}
#[test]
fn test_bidirectional_traffic() {
let mut acc = TransportStatsAccumulator::default();
acc.on_packet_sent(100);
acc.on_packet_received(80);
acc.on_packet_sent(200);
acc.on_packet_received(150);
acc.on_packet_sent(50);
acc.on_packet_received(120);
assert_eq!(acc.packets_sent, 3);
assert_eq!(acc.bytes_sent, 350);
assert_eq!(acc.packets_received, 3);
assert_eq!(acc.bytes_received, 350);
}
#[test]
fn test_snapshot() {
let mut acc = TransportStatsAccumulator::default();
acc.transport_id = "RTCTransport_0".to_string();
acc.on_packet_sent(100);
acc.on_packet_received(80);
acc.on_ice_state_changed(RTCIceTransportState::Connected);
acc.on_dtls_state_changed(RTCDtlsTransportState::Connected);
acc.on_dtls_handshake_complete(
"DTLS 1.2".to_string(),
"AES_GCM".to_string(),
"SRTP_AES".to_string(),
RTCDtlsRole::Server,
);
acc.on_selected_candidate_pair_changed("pair_1".to_string());
acc.on_ccfb_sent();
acc.on_ccfb_received();
let now = Instant::now();
let stats = acc.snapshot(now);
assert_eq!(stats.stats.id, "RTCTransport_0");
assert_eq!(stats.stats.typ, RTCStatsType::Transport);
assert_eq!(stats.stats.timestamp, now);
assert_eq!(stats.packets_sent, 1);
assert_eq!(stats.bytes_sent, 100);
assert_eq!(stats.packets_received, 1);
assert_eq!(stats.bytes_received, 80);
assert_eq!(stats.ice_state, RTCIceTransportState::Connected);
assert_eq!(stats.dtls_state, RTCDtlsTransportState::Connected);
assert_eq!(stats.tls_version, "DTLS 1.2");
assert_eq!(stats.dtls_cipher, "AES_GCM");
assert_eq!(stats.srtp_cipher, "SRTP_AES");
assert_eq!(stats.dtls_role, RTCDtlsRole::Server);
assert_eq!(stats.selected_candidate_pair_id, "pair_1");
assert_eq!(stats.selected_candidate_pair_changes, 1);
assert_eq!(stats.ccfb_messages_sent, 1);
assert_eq!(stats.ccfb_messages_received, 1);
}
#[test]
fn test_snapshot_json_serialization() {
let mut acc = TransportStatsAccumulator::default();
acc.on_packet_sent(500);
acc.on_packet_received(400);
let now = Instant::now();
let stats = acc.snapshot(now);
let json = serde_json::to_string(&stats).expect("should serialize");
assert!(json.contains("\"packetsSent\":1"));
assert!(json.contains("\"bytesSent\":500"));
assert!(json.contains("\"packetsReceived\":1"));
assert!(json.contains("\"bytesReceived\":400"));
assert!(json.contains("\"type\":\"transport\""));
}
}