mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00
Implement crypto stats on DTLS transport
Bug: chromium:1018077 Change-Id: I585d4064f39e5f9d268b408ebf6ae13a056c778a Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/158403 Reviewed-by: Henrik Boström <hbos@webrtc.org> Reviewed-by: Steve Anton <steveanton@webrtc.org> Commit-Queue: Harald Alvestrand <hta@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29628}
This commit is contained in:
parent
a81e2b4510
commit
5cb7807a36
16 changed files with 143 additions and 7 deletions
|
@ -600,6 +600,9 @@ class RTC_EXPORT RTCTransportStats final : public RTCStats {
|
||||||
RTCStatsMember<std::string> selected_candidate_pair_id;
|
RTCStatsMember<std::string> selected_candidate_pair_id;
|
||||||
RTCStatsMember<std::string> local_certificate_id;
|
RTCStatsMember<std::string> local_certificate_id;
|
||||||
RTCStatsMember<std::string> remote_certificate_id;
|
RTCStatsMember<std::string> remote_certificate_id;
|
||||||
|
RTCStatsMember<std::string> tls_version;
|
||||||
|
RTCStatsMember<std::string> dtls_cipher;
|
||||||
|
RTCStatsMember<std::string> srtp_cipher;
|
||||||
RTCStatsMember<uint32_t> selected_candidate_pair_changes;
|
RTCStatsMember<uint32_t> selected_candidate_pair_changes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -380,6 +380,14 @@ bool DtlsTransport::GetSrtpCryptoSuite(int* cipher) {
|
||||||
return dtls_->GetDtlsSrtpCryptoSuite(cipher);
|
return dtls_->GetDtlsSrtpCryptoSuite(cipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DtlsTransport::GetSslVersionBytes(int* version) const {
|
||||||
|
if (dtls_state() != DTLS_TRANSPORT_CONNECTED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dtls_->GetSslVersionBytes(version);
|
||||||
|
}
|
||||||
|
|
||||||
// Called from upper layers to send a media packet.
|
// Called from upper layers to send a media packet.
|
||||||
int DtlsTransport::SendPacket(const char* data,
|
int DtlsTransport::SendPacket(const char* data,
|
||||||
size_t size,
|
size_t size,
|
||||||
|
|
|
@ -142,6 +142,8 @@ class DtlsTransport : public DtlsTransportInternal {
|
||||||
|
|
||||||
bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) override;
|
bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) override;
|
||||||
|
|
||||||
|
// Find out which TLS version was negotiated
|
||||||
|
bool GetSslVersionBytes(int* version) const override;
|
||||||
// Find out which DTLS-SRTP cipher was negotiated
|
// Find out which DTLS-SRTP cipher was negotiated
|
||||||
bool GetSrtpCryptoSuite(int* cipher) override;
|
bool GetSrtpCryptoSuite(int* cipher) override;
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,8 @@ class DtlsTransportInternal : public rtc::PacketTransportInternal {
|
||||||
|
|
||||||
virtual bool SetDtlsRole(rtc::SSLRole role) = 0;
|
virtual bool SetDtlsRole(rtc::SSLRole role) = 0;
|
||||||
|
|
||||||
|
// Finds out which TLS/DTLS version is running.
|
||||||
|
virtual bool GetSslVersionBytes(int* version) const = 0;
|
||||||
// Finds out which DTLS-SRTP cipher was negotiated.
|
// Finds out which DTLS-SRTP cipher was negotiated.
|
||||||
// TODO(zhihuang): Remove this once all dependencies implement this.
|
// TODO(zhihuang): Remove this once all dependencies implement this.
|
||||||
virtual bool GetSrtpCryptoSuite(int* cipher) = 0;
|
virtual bool GetSrtpCryptoSuite(int* cipher) = 0;
|
||||||
|
|
|
@ -168,6 +168,13 @@ class FakeDtlsTransport : public DtlsTransportInternal {
|
||||||
remote_cert_ = cert;
|
remote_cert_ = cert;
|
||||||
}
|
}
|
||||||
bool IsDtlsActive() const override { return do_dtls_; }
|
bool IsDtlsActive() const override { return do_dtls_; }
|
||||||
|
bool GetSslVersionBytes(int* version) const override {
|
||||||
|
if (!do_dtls_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*version = 0x0102;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool GetSrtpCryptoSuite(int* crypto_suite) override {
|
bool GetSrtpCryptoSuite(int* crypto_suite) override {
|
||||||
if (!do_dtls_) {
|
if (!do_dtls_) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -66,6 +66,9 @@ bool NoOpDtlsTransport::GetDtlsRole(rtc::SSLRole* role) const {
|
||||||
bool NoOpDtlsTransport::SetDtlsRole(rtc::SSLRole role) {
|
bool NoOpDtlsTransport::SetDtlsRole(rtc::SSLRole role) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
bool NoOpDtlsTransport::GetSslVersionBytes(int* version) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bool NoOpDtlsTransport::GetSrtpCryptoSuite(int* cipher) {
|
bool NoOpDtlsTransport::GetSrtpCryptoSuite(int* cipher) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ class NoOpDtlsTransport : public DtlsTransportInternal {
|
||||||
bool IsDtlsActive() const override;
|
bool IsDtlsActive() const override;
|
||||||
bool GetDtlsRole(rtc::SSLRole* role) const override;
|
bool GetDtlsRole(rtc::SSLRole* role) const override;
|
||||||
bool SetDtlsRole(rtc::SSLRole role) override;
|
bool SetDtlsRole(rtc::SSLRole role) override;
|
||||||
|
bool GetSslVersionBytes(int* version) const override;
|
||||||
bool GetSrtpCryptoSuite(int* cipher) override;
|
bool GetSrtpCryptoSuite(int* cipher) override;
|
||||||
bool GetSslCipherSuite(int* cipher) override;
|
bool GetSslCipherSuite(int* cipher) override;
|
||||||
rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate() const override;
|
rtc::scoped_refptr<rtc::RTCCertificate> GetLocalCertificate() const override;
|
||||||
|
|
|
@ -772,6 +772,7 @@ bool JsepTransport::GetTransportStats(DtlsTransportInternal* dtls_transport,
|
||||||
} else {
|
} else {
|
||||||
substats.component = ICE_CANDIDATE_COMPONENT_RTP;
|
substats.component = ICE_CANDIDATE_COMPONENT_RTP;
|
||||||
}
|
}
|
||||||
|
dtls_transport->GetSslVersionBytes(&substats.ssl_version_bytes);
|
||||||
dtls_transport->GetSrtpCryptoSuite(&substats.srtp_crypto_suite);
|
dtls_transport->GetSrtpCryptoSuite(&substats.srtp_crypto_suite);
|
||||||
dtls_transport->GetSslCipherSuite(&substats.ssl_cipher_suite);
|
dtls_transport->GetSslCipherSuite(&substats.ssl_cipher_suite);
|
||||||
substats.dtls_state = dtls_transport->dtls_state();
|
substats.dtls_state = dtls_transport->dtls_state();
|
||||||
|
|
|
@ -1721,6 +1721,26 @@ void RTCStatsCollector::ProduceTransportStats_n(
|
||||||
transport_stats->local_certificate_id = local_certificate_id;
|
transport_stats->local_certificate_id = local_certificate_id;
|
||||||
if (!remote_certificate_id.empty())
|
if (!remote_certificate_id.empty())
|
||||||
transport_stats->remote_certificate_id = remote_certificate_id;
|
transport_stats->remote_certificate_id = remote_certificate_id;
|
||||||
|
// Crypto information
|
||||||
|
if (channel_stats.ssl_version_bytes) {
|
||||||
|
char bytes[5];
|
||||||
|
snprintf(bytes, sizeof(bytes), "%04X", channel_stats.ssl_version_bytes);
|
||||||
|
transport_stats->tls_version = bytes;
|
||||||
|
}
|
||||||
|
if (channel_stats.ssl_cipher_suite != rtc::TLS_NULL_WITH_NULL_NULL &&
|
||||||
|
rtc::SSLStreamAdapter::SslCipherSuiteToName(
|
||||||
|
channel_stats.ssl_cipher_suite)
|
||||||
|
.length()) {
|
||||||
|
transport_stats->dtls_cipher =
|
||||||
|
rtc::SSLStreamAdapter::SslCipherSuiteToName(
|
||||||
|
channel_stats.ssl_cipher_suite);
|
||||||
|
}
|
||||||
|
if (channel_stats.srtp_crypto_suite != rtc::SRTP_INVALID_CRYPTO_SUITE &&
|
||||||
|
rtc::SrtpCryptoSuiteToName(channel_stats.srtp_crypto_suite)
|
||||||
|
.length()) {
|
||||||
|
transport_stats->srtp_cipher =
|
||||||
|
rtc::SrtpCryptoSuiteToName(channel_stats.srtp_crypto_suite);
|
||||||
|
}
|
||||||
report->AddStats(std::move(transport_stats));
|
report->AddStats(std::move(transport_stats));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2202,6 +2202,68 @@ TEST_F(RTCStatsCollectorTest, CollectRTCTransportStats) {
|
||||||
report->Get(expected_rtcp_transport.id())->cast_to<RTCTransportStats>());
|
report->Get(expected_rtcp_transport.id())->cast_to<RTCTransportStats>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(RTCStatsCollectorTest, CollectRTCTransportStatsWithCrypto) {
|
||||||
|
const char kTransportName[] = "transport";
|
||||||
|
|
||||||
|
pc_->AddVoiceChannel("audio", kTransportName);
|
||||||
|
|
||||||
|
std::unique_ptr<cricket::Candidate> rtp_local_candidate =
|
||||||
|
CreateFakeCandidate("42.42.42.42", 42, "protocol", rtc::ADAPTER_TYPE_WIFI,
|
||||||
|
cricket::LOCAL_PORT_TYPE, 42);
|
||||||
|
std::unique_ptr<cricket::Candidate> rtp_remote_candidate =
|
||||||
|
CreateFakeCandidate("42.42.42.42", 42, "protocol",
|
||||||
|
rtc::ADAPTER_TYPE_UNKNOWN, cricket::LOCAL_PORT_TYPE,
|
||||||
|
42);
|
||||||
|
std::unique_ptr<cricket::Candidate> rtcp_local_candidate =
|
||||||
|
CreateFakeCandidate("42.42.42.42", 42, "protocol", rtc::ADAPTER_TYPE_WIFI,
|
||||||
|
cricket::LOCAL_PORT_TYPE, 42);
|
||||||
|
std::unique_ptr<cricket::Candidate> rtcp_remote_candidate =
|
||||||
|
CreateFakeCandidate("42.42.42.42", 42, "protocol",
|
||||||
|
rtc::ADAPTER_TYPE_UNKNOWN, cricket::LOCAL_PORT_TYPE,
|
||||||
|
42);
|
||||||
|
|
||||||
|
cricket::ConnectionInfo rtp_connection_info;
|
||||||
|
rtp_connection_info.best_connection = false;
|
||||||
|
rtp_connection_info.local_candidate = *rtp_local_candidate.get();
|
||||||
|
rtp_connection_info.remote_candidate = *rtp_remote_candidate.get();
|
||||||
|
rtp_connection_info.sent_total_bytes = 42;
|
||||||
|
rtp_connection_info.recv_total_bytes = 1337;
|
||||||
|
cricket::TransportChannelStats rtp_transport_channel_stats;
|
||||||
|
rtp_transport_channel_stats.component = cricket::ICE_CANDIDATE_COMPONENT_RTP;
|
||||||
|
rtp_transport_channel_stats.ice_transport_stats.connection_infos.push_back(
|
||||||
|
rtp_connection_info);
|
||||||
|
// The state must be connected in order for crypto parameters to show up.
|
||||||
|
rtp_transport_channel_stats.dtls_state = cricket::DTLS_TRANSPORT_CONNECTED;
|
||||||
|
rtp_transport_channel_stats.ice_transport_stats
|
||||||
|
.selected_candidate_pair_changes = 1;
|
||||||
|
rtp_transport_channel_stats.ssl_version_bytes = 0x0203;
|
||||||
|
// 0x2F is TLS_RSA_WITH_AES_128_CBC_SHA according to IANA
|
||||||
|
rtp_transport_channel_stats.ssl_cipher_suite = 0x2F;
|
||||||
|
rtp_transport_channel_stats.srtp_crypto_suite = rtc::SRTP_AES128_CM_SHA1_80;
|
||||||
|
pc_->SetTransportStats(kTransportName, {rtp_transport_channel_stats});
|
||||||
|
|
||||||
|
// Get stats
|
||||||
|
rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
|
||||||
|
|
||||||
|
RTCTransportStats expected_rtp_transport(
|
||||||
|
"RTCTransport_transport_" +
|
||||||
|
rtc::ToString(cricket::ICE_CANDIDATE_COMPONENT_RTP),
|
||||||
|
report->timestamp_us());
|
||||||
|
expected_rtp_transport.bytes_sent = 42;
|
||||||
|
expected_rtp_transport.bytes_received = 1337;
|
||||||
|
expected_rtp_transport.dtls_state = RTCDtlsTransportState::kConnected;
|
||||||
|
expected_rtp_transport.selected_candidate_pair_changes = 1;
|
||||||
|
// Crypto parameters
|
||||||
|
expected_rtp_transport.tls_version = "0203";
|
||||||
|
expected_rtp_transport.dtls_cipher = "TLS_RSA_WITH_AES_128_CBC_SHA";
|
||||||
|
expected_rtp_transport.srtp_cipher = "AES_CM_128_HMAC_SHA1_80";
|
||||||
|
|
||||||
|
ASSERT_TRUE(report->Get(expected_rtp_transport.id()));
|
||||||
|
EXPECT_EQ(
|
||||||
|
expected_rtp_transport,
|
||||||
|
report->Get(expected_rtp_transport.id())->cast_to<RTCTransportStats>());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(RTCStatsCollectorTest, CollectNoStreamRTCOutboundRTPStreamStats_Audio) {
|
TEST_F(RTCStatsCollectorTest, CollectNoStreamRTCOutboundRTPStreamStats_Audio) {
|
||||||
cricket::VoiceMediaInfo voice_media_info;
|
cricket::VoiceMediaInfo voice_media_info;
|
||||||
|
|
||||||
|
|
|
@ -973,6 +973,9 @@ class RTCStatsReportVerifier {
|
||||||
RTCCertificateStats::kType);
|
RTCCertificateStats::kType);
|
||||||
verifier.TestMemberIsIDReference(transport.remote_certificate_id,
|
verifier.TestMemberIsIDReference(transport.remote_certificate_id,
|
||||||
RTCCertificateStats::kType);
|
RTCCertificateStats::kType);
|
||||||
|
verifier.TestMemberIsDefined(transport.tls_version);
|
||||||
|
verifier.TestMemberIsDefined(transport.dtls_cipher);
|
||||||
|
verifier.TestMemberIsDefined(transport.srtp_cipher);
|
||||||
verifier.TestMemberIsPositive<uint32_t>(
|
verifier.TestMemberIsPositive<uint32_t>(
|
||||||
transport.selected_candidate_pair_changes);
|
transport.selected_candidate_pair_changes);
|
||||||
return verifier.ExpectAllMembersSuccessfullyTested();
|
return verifier.ExpectAllMembersSuccessfullyTested();
|
||||||
|
|
|
@ -27,6 +27,7 @@ struct TransportChannelStats {
|
||||||
~TransportChannelStats();
|
~TransportChannelStats();
|
||||||
|
|
||||||
int component = 0;
|
int component = 0;
|
||||||
|
int ssl_version_bytes = 0;
|
||||||
int srtp_crypto_suite = rtc::SRTP_INVALID_CRYPTO_SUITE;
|
int srtp_crypto_suite = rtc::SRTP_INVALID_CRYPTO_SUITE;
|
||||||
int ssl_cipher_suite = rtc::TLS_NULL_WITH_NULL_NULL;
|
int ssl_cipher_suite = rtc::TLS_NULL_WITH_NULL_NULL;
|
||||||
DtlsTransportState dtls_state = DTLS_TRANSPORT_NEW;
|
DtlsTransportState dtls_state = DTLS_TRANSPORT_NEW;
|
||||||
|
|
|
@ -373,9 +373,9 @@ bool OpenSSLStreamAdapter::GetSslCipherSuite(int* cipher_suite) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int OpenSSLStreamAdapter::GetSslVersion() const {
|
SSLProtocolVersion OpenSSLStreamAdapter::GetSslVersion() const {
|
||||||
if (state_ != SSL_CONNECTED) {
|
if (state_ != SSL_CONNECTED) {
|
||||||
return -1;
|
return SSL_PROTOCOL_NOT_GIVEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ssl_version = SSL_version(ssl_);
|
int ssl_version = SSL_version(ssl_);
|
||||||
|
@ -395,7 +395,15 @@ int OpenSSLStreamAdapter::GetSslVersion() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return SSL_PROTOCOL_NOT_GIVEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenSSLStreamAdapter::GetSslVersionBytes(int* version) const {
|
||||||
|
if (state_ != SSL_CONNECTED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*version = SSL_version(ssl_);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key Extractor interface
|
// Key Extractor interface
|
||||||
|
|
|
@ -96,8 +96,8 @@ class OpenSSLStreamAdapter final : public SSLStreamAdapter {
|
||||||
|
|
||||||
bool GetSslCipherSuite(int* cipher) override;
|
bool GetSslCipherSuite(int* cipher) override;
|
||||||
|
|
||||||
int GetSslVersion() const override;
|
SSLProtocolVersion GetSslVersion() const override;
|
||||||
|
bool GetSslVersionBytes(int* version) const override;
|
||||||
// Key Extractor interface
|
// Key Extractor interface
|
||||||
bool ExportKeyingMaterial(const std::string& label,
|
bool ExportKeyingMaterial(const std::string& label,
|
||||||
const uint8_t* context,
|
const uint8_t* context,
|
||||||
|
|
|
@ -91,7 +91,8 @@ bool IsGcmCryptoSuiteName(const std::string& crypto_suite);
|
||||||
enum SSLRole { SSL_CLIENT, SSL_SERVER };
|
enum SSLRole { SSL_CLIENT, SSL_SERVER };
|
||||||
enum SSLMode { SSL_MODE_TLS, SSL_MODE_DTLS };
|
enum SSLMode { SSL_MODE_TLS, SSL_MODE_DTLS };
|
||||||
enum SSLProtocolVersion {
|
enum SSLProtocolVersion {
|
||||||
SSL_PROTOCOL_TLS_10,
|
SSL_PROTOCOL_NOT_GIVEN = -1,
|
||||||
|
SSL_PROTOCOL_TLS_10 = 0,
|
||||||
SSL_PROTOCOL_TLS_11,
|
SSL_PROTOCOL_TLS_11,
|
||||||
SSL_PROTOCOL_TLS_12,
|
SSL_PROTOCOL_TLS_12,
|
||||||
SSL_PROTOCOL_DTLS_10 = SSL_PROTOCOL_TLS_11,
|
SSL_PROTOCOL_DTLS_10 = SSL_PROTOCOL_TLS_11,
|
||||||
|
@ -187,7 +188,12 @@ class SSLStreamAdapter : public StreamAdapterInterface {
|
||||||
// connection (e.g. 0x2F for "TLS_RSA_WITH_AES_128_CBC_SHA").
|
// connection (e.g. 0x2F for "TLS_RSA_WITH_AES_128_CBC_SHA").
|
||||||
virtual bool GetSslCipherSuite(int* cipher_suite);
|
virtual bool GetSslCipherSuite(int* cipher_suite);
|
||||||
|
|
||||||
virtual int GetSslVersion() const = 0;
|
// Retrieves the enum value for SSL version.
|
||||||
|
// Will return -1 until the version has been negotiated.
|
||||||
|
virtual SSLProtocolVersion GetSslVersion() const = 0;
|
||||||
|
// Retrieves the 2-byte version from the TLS protocol.
|
||||||
|
// Will return false until the version has been negotiated.
|
||||||
|
virtual bool GetSslVersionBytes(int* version) const = 0;
|
||||||
|
|
||||||
// Key Exporter interface from RFC 5705
|
// Key Exporter interface from RFC 5705
|
||||||
// Arguments are:
|
// Arguments are:
|
||||||
|
|
|
@ -883,6 +883,9 @@ WEBRTC_RTCSTATS_IMPL(RTCTransportStats, RTCStats, "transport",
|
||||||
&selected_candidate_pair_id,
|
&selected_candidate_pair_id,
|
||||||
&local_certificate_id,
|
&local_certificate_id,
|
||||||
&remote_certificate_id,
|
&remote_certificate_id,
|
||||||
|
&tls_version,
|
||||||
|
&dtls_cipher,
|
||||||
|
&srtp_cipher,
|
||||||
&selected_candidate_pair_changes)
|
&selected_candidate_pair_changes)
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@ -899,6 +902,9 @@ RTCTransportStats::RTCTransportStats(std::string&& id, int64_t timestamp_us)
|
||||||
selected_candidate_pair_id("selectedCandidatePairId"),
|
selected_candidate_pair_id("selectedCandidatePairId"),
|
||||||
local_certificate_id("localCertificateId"),
|
local_certificate_id("localCertificateId"),
|
||||||
remote_certificate_id("remoteCertificateId"),
|
remote_certificate_id("remoteCertificateId"),
|
||||||
|
tls_version("tlsVersion"),
|
||||||
|
dtls_cipher("dtlsCipher"),
|
||||||
|
srtp_cipher("srtpCipher"),
|
||||||
selected_candidate_pair_changes("selectedCandidatePairChanges") {}
|
selected_candidate_pair_changes("selectedCandidatePairChanges") {}
|
||||||
|
|
||||||
RTCTransportStats::RTCTransportStats(const RTCTransportStats& other)
|
RTCTransportStats::RTCTransportStats(const RTCTransportStats& other)
|
||||||
|
@ -910,6 +916,9 @@ RTCTransportStats::RTCTransportStats(const RTCTransportStats& other)
|
||||||
selected_candidate_pair_id(other.selected_candidate_pair_id),
|
selected_candidate_pair_id(other.selected_candidate_pair_id),
|
||||||
local_certificate_id(other.local_certificate_id),
|
local_certificate_id(other.local_certificate_id),
|
||||||
remote_certificate_id(other.remote_certificate_id),
|
remote_certificate_id(other.remote_certificate_id),
|
||||||
|
tls_version(other.tls_version),
|
||||||
|
dtls_cipher(other.dtls_cipher),
|
||||||
|
srtp_cipher(other.srtp_cipher),
|
||||||
selected_candidate_pair_changes(other.selected_candidate_pair_changes) {}
|
selected_candidate_pair_changes(other.selected_candidate_pair_changes) {}
|
||||||
|
|
||||||
RTCTransportStats::~RTCTransportStats() {}
|
RTCTransportStats::~RTCTransportStats() {}
|
||||||
|
|
Loading…
Reference in a new issue