diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/pc/jsep_transport_unittest.cc | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/pc/jsep_transport_unittest.cc')
-rw-r--r-- | third_party/libwebrtc/pc/jsep_transport_unittest.cc | 1386 |
1 files changed, 1386 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/jsep_transport_unittest.cc b/third_party/libwebrtc/pc/jsep_transport_unittest.cc new file mode 100644 index 0000000000..f057d37a0d --- /dev/null +++ b/third_party/libwebrtc/pc/jsep_transport_unittest.cc @@ -0,0 +1,1386 @@ +/* + * Copyright 2018 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "pc/jsep_transport.h" + +#include <stdint.h> +#include <string.h> + +#include <ostream> +#include <string> +#include <tuple> +#include <utility> + +#include "api/candidate.h" +#include "media/base/fake_rtp.h" +#include "p2p/base/fake_dtls_transport.h" +#include "p2p/base/fake_ice_transport.h" +#include "p2p/base/p2p_constants.h" +#include "p2p/base/packet_transport_internal.h" +#include "rtc_base/async_packet_socket.h" +#include "rtc_base/buffer.h" +#include "rtc_base/byte_order.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/helpers.h" +#include "rtc_base/logging.h" +#include "rtc_base/net_helper.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/ssl_certificate.h" +#include "rtc_base/ssl_identity.h" +#include "rtc_base/third_party/sigslot/sigslot.h" +#include "test/gtest.h" +#include "test/scoped_key_value_config.h" + +namespace cricket { +namespace { +using webrtc::SdpType; + +static const char kIceUfrag1[] = "U001"; +static const char kIcePwd1[] = "TESTICEPWD00000000000001"; +static const char kIceUfrag2[] = "U002"; +static const char kIcePwd2[] = "TESTIEPWD00000000000002"; +static const char kTransportName[] = "Test Transport"; + +enum class SrtpMode { + kSdes, + kDtlsSrtp, +}; + +struct NegotiateRoleParams { + ConnectionRole local_role; + ConnectionRole remote_role; + SdpType local_type; + SdpType remote_type; +}; + +std::ostream& operator<<(std::ostream& os, const ConnectionRole& role) { + std::string str = "invalid"; + ConnectionRoleToString(role, &str); + os << str; + return os; +} + +std::ostream& operator<<(std::ostream& os, const NegotiateRoleParams& param) { + os << "[Local role " << param.local_role << " Remote role " + << param.remote_role << " LocalType " << SdpTypeToString(param.local_type) + << " RemoteType " << SdpTypeToString(param.remote_type) << "]"; + return os; +} + +rtc::scoped_refptr<webrtc::IceTransportInterface> CreateIceTransport( + std::unique_ptr<FakeIceTransport> internal) { + if (!internal) { + return nullptr; + } + + return rtc::make_ref_counted<FakeIceTransportWrapper>(std::move(internal)); +} + +class JsepTransport2Test : public ::testing::Test, public sigslot::has_slots<> { + protected: + std::unique_ptr<webrtc::SrtpTransport> CreateSdesTransport( + rtc::PacketTransportInternal* rtp_packet_transport, + rtc::PacketTransportInternal* rtcp_packet_transport) { + auto srtp_transport = std::make_unique<webrtc::SrtpTransport>( + rtcp_packet_transport == nullptr, field_trials_); + + srtp_transport->SetRtpPacketTransport(rtp_packet_transport); + if (rtcp_packet_transport) { + srtp_transport->SetRtcpPacketTransport(rtp_packet_transport); + } + return srtp_transport; + } + + std::unique_ptr<webrtc::DtlsSrtpTransport> CreateDtlsSrtpTransport( + cricket::DtlsTransportInternal* rtp_dtls_transport, + cricket::DtlsTransportInternal* rtcp_dtls_transport) { + auto dtls_srtp_transport = std::make_unique<webrtc::DtlsSrtpTransport>( + rtcp_dtls_transport == nullptr, field_trials_); + dtls_srtp_transport->SetDtlsTransports(rtp_dtls_transport, + rtcp_dtls_transport); + return dtls_srtp_transport; + } + + // Create a new JsepTransport with a FakeDtlsTransport and a + // FakeIceTransport. + std::unique_ptr<JsepTransport> CreateJsepTransport2(bool rtcp_mux_enabled, + SrtpMode srtp_mode) { + auto ice_internal = std::make_unique<FakeIceTransport>( + kTransportName, ICE_CANDIDATE_COMPONENT_RTP); + auto rtp_dtls_transport = + std::make_unique<FakeDtlsTransport>(ice_internal.get()); + auto ice = CreateIceTransport(std::move(ice_internal)); + + std::unique_ptr<FakeIceTransport> rtcp_ice_internal; + std::unique_ptr<FakeDtlsTransport> rtcp_dtls_transport; + if (!rtcp_mux_enabled) { + rtcp_ice_internal = std::make_unique<FakeIceTransport>( + kTransportName, ICE_CANDIDATE_COMPONENT_RTCP); + rtcp_dtls_transport = + std::make_unique<FakeDtlsTransport>(rtcp_ice_internal.get()); + } + auto rtcp_ice = CreateIceTransport(std::move(rtcp_ice_internal)); + + std::unique_ptr<webrtc::RtpTransport> unencrypted_rtp_transport; + std::unique_ptr<webrtc::SrtpTransport> sdes_transport; + std::unique_ptr<webrtc::DtlsSrtpTransport> dtls_srtp_transport; + switch (srtp_mode) { + case SrtpMode::kSdes: + sdes_transport = CreateSdesTransport(rtp_dtls_transport.get(), + rtcp_dtls_transport.get()); + sdes_transport_ = sdes_transport.get(); + break; + case SrtpMode::kDtlsSrtp: + dtls_srtp_transport = CreateDtlsSrtpTransport( + rtp_dtls_transport.get(), rtcp_dtls_transport.get()); + break; + default: + RTC_DCHECK_NOTREACHED(); + } + + auto jsep_transport = std::make_unique<JsepTransport>( + kTransportName, /*local_certificate=*/nullptr, std::move(ice), + std::move(rtcp_ice), std::move(unencrypted_rtp_transport), + std::move(sdes_transport), std::move(dtls_srtp_transport), + std::move(rtp_dtls_transport), std::move(rtcp_dtls_transport), + /*sctp_transport=*/nullptr, + /*rtcp_mux_active_callback=*/[&]() { OnRtcpMuxActive(); }); + + signal_rtcp_mux_active_received_ = false; + return jsep_transport; + } + + JsepTransportDescription MakeJsepTransportDescription( + bool rtcp_mux_enabled, + const char* ufrag, + const char* pwd, + const rtc::scoped_refptr<rtc::RTCCertificate>& cert, + ConnectionRole role = CONNECTIONROLE_NONE) { + JsepTransportDescription jsep_description; + jsep_description.rtcp_mux_enabled = rtcp_mux_enabled; + + std::unique_ptr<rtc::SSLFingerprint> fingerprint; + if (cert) { + fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*cert); + } + jsep_description.transport_desc = + TransportDescription(std::vector<std::string>(), ufrag, pwd, + ICEMODE_FULL, role, fingerprint.get()); + return jsep_description; + } + + Candidate CreateCandidate(int component) { + Candidate c; + c.set_address(rtc::SocketAddress("192.168.1.1", 8000)); + c.set_component(component); + c.set_protocol(UDP_PROTOCOL_NAME); + c.set_priority(1); + return c; + } + + void OnRtcpMuxActive() { signal_rtcp_mux_active_received_ = true; } + + rtc::AutoThread main_thread_; + std::unique_ptr<JsepTransport> jsep_transport_; + bool signal_rtcp_mux_active_received_ = false; + // The SrtpTransport is owned by `jsep_transport_`. Keep a raw pointer here + // for testing. + webrtc::SrtpTransport* sdes_transport_ = nullptr; + + webrtc::test::ScopedKeyValueConfig field_trials_; +}; + +// The parameterized tests cover both cases when RTCP mux is enable and +// disabled. +class JsepTransport2WithRtcpMux : public JsepTransport2Test, + public ::testing::WithParamInterface<bool> {}; + +// This test verifies the ICE parameters are properly applied to the transports. +TEST_P(JsepTransport2WithRtcpMux, SetIceParameters) { + bool rtcp_mux_enabled = GetParam(); + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + + JsepTransportDescription jsep_description; + jsep_description.transport_desc = TransportDescription(kIceUfrag1, kIcePwd1); + jsep_description.rtcp_mux_enabled = rtcp_mux_enabled; + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(jsep_description, SdpType::kOffer) + .ok()); + auto fake_ice_transport = static_cast<FakeIceTransport*>( + jsep_transport_->rtp_dtls_transport()->ice_transport()); + EXPECT_EQ(ICEMODE_FULL, fake_ice_transport->remote_ice_mode()); + EXPECT_EQ(kIceUfrag1, fake_ice_transport->ice_ufrag()); + EXPECT_EQ(kIcePwd1, fake_ice_transport->ice_pwd()); + if (!rtcp_mux_enabled) { + fake_ice_transport = static_cast<FakeIceTransport*>( + jsep_transport_->rtcp_dtls_transport()->ice_transport()); + ASSERT_TRUE(fake_ice_transport); + EXPECT_EQ(ICEMODE_FULL, fake_ice_transport->remote_ice_mode()); + EXPECT_EQ(kIceUfrag1, fake_ice_transport->ice_ufrag()); + EXPECT_EQ(kIcePwd1, fake_ice_transport->ice_pwd()); + } + + jsep_description.transport_desc = TransportDescription(kIceUfrag2, kIcePwd2); + ASSERT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(jsep_description, + SdpType::kAnswer) + .ok()); + fake_ice_transport = static_cast<FakeIceTransport*>( + jsep_transport_->rtp_dtls_transport()->ice_transport()); + EXPECT_EQ(ICEMODE_FULL, fake_ice_transport->remote_ice_mode()); + EXPECT_EQ(kIceUfrag2, fake_ice_transport->remote_ice_ufrag()); + EXPECT_EQ(kIcePwd2, fake_ice_transport->remote_ice_pwd()); + if (!rtcp_mux_enabled) { + fake_ice_transport = static_cast<FakeIceTransport*>( + jsep_transport_->rtcp_dtls_transport()->ice_transport()); + ASSERT_TRUE(fake_ice_transport); + EXPECT_EQ(ICEMODE_FULL, fake_ice_transport->remote_ice_mode()); + EXPECT_EQ(kIceUfrag2, fake_ice_transport->remote_ice_ufrag()); + EXPECT_EQ(kIcePwd2, fake_ice_transport->remote_ice_pwd()); + } +} + +// Similarly, test DTLS parameters are properly applied to the transports. +TEST_P(JsepTransport2WithRtcpMux, SetDtlsParameters) { + bool rtcp_mux_enabled = GetParam(); + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + + // Create certificates. + rtc::scoped_refptr<rtc::RTCCertificate> local_cert = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("local", rtc::KT_DEFAULT)); + rtc::scoped_refptr<rtc::RTCCertificate> remote_cert = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("remote", rtc::KT_DEFAULT)); + jsep_transport_->SetLocalCertificate(local_cert); + + // Apply offer. + JsepTransportDescription local_description = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + local_cert, CONNECTIONROLE_ACTPASS); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, SdpType::kOffer) + .ok()); + // Apply Answer. + JsepTransportDescription remote_description = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + remote_cert, CONNECTIONROLE_ACTIVE); + ASSERT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + SdpType::kAnswer) + .ok()); + + // Verify that SSL role and remote fingerprint were set correctly based on + // transport descriptions. + auto role = jsep_transport_->GetDtlsRole(); + ASSERT_TRUE(role); + EXPECT_EQ(rtc::SSL_SERVER, role); // Because remote description was "active". + auto fake_dtls = + static_cast<FakeDtlsTransport*>(jsep_transport_->rtp_dtls_transport()); + EXPECT_EQ(remote_description.transport_desc.identity_fingerprint->ToString(), + fake_dtls->dtls_fingerprint().ToString()); + + if (!rtcp_mux_enabled) { + auto fake_rtcp_dtls = + static_cast<FakeDtlsTransport*>(jsep_transport_->rtcp_dtls_transport()); + EXPECT_EQ( + remote_description.transport_desc.identity_fingerprint->ToString(), + fake_rtcp_dtls->dtls_fingerprint().ToString()); + } +} + +// Same as above test, but with remote transport description using +// CONNECTIONROLE_PASSIVE, expecting SSL_CLIENT role. +TEST_P(JsepTransport2WithRtcpMux, SetDtlsParametersWithPassiveAnswer) { + bool rtcp_mux_enabled = GetParam(); + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + + // Create certificates. + rtc::scoped_refptr<rtc::RTCCertificate> local_cert = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("local", rtc::KT_DEFAULT)); + rtc::scoped_refptr<rtc::RTCCertificate> remote_cert = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("remote", rtc::KT_DEFAULT)); + jsep_transport_->SetLocalCertificate(local_cert); + + // Apply offer. + JsepTransportDescription local_description = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + local_cert, CONNECTIONROLE_ACTPASS); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, SdpType::kOffer) + .ok()); + // Apply Answer. + JsepTransportDescription remote_description = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + remote_cert, CONNECTIONROLE_PASSIVE); + ASSERT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + SdpType::kAnswer) + .ok()); + + // Verify that SSL role and remote fingerprint were set correctly based on + // transport descriptions. + auto role = jsep_transport_->GetDtlsRole(); + ASSERT_TRUE(role); + EXPECT_EQ(rtc::SSL_CLIENT, + role); // Because remote description was "passive". + auto fake_dtls = + static_cast<FakeDtlsTransport*>(jsep_transport_->rtp_dtls_transport()); + EXPECT_EQ(remote_description.transport_desc.identity_fingerprint->ToString(), + fake_dtls->dtls_fingerprint().ToString()); + + if (!rtcp_mux_enabled) { + auto fake_rtcp_dtls = + static_cast<FakeDtlsTransport*>(jsep_transport_->rtcp_dtls_transport()); + EXPECT_EQ( + remote_description.transport_desc.identity_fingerprint->ToString(), + fake_rtcp_dtls->dtls_fingerprint().ToString()); + } +} + +// Tests SetNeedsIceRestartFlag and need_ice_restart, ensuring needs_ice_restart +// only starts returning "false" once an ICE restart has been initiated. +TEST_P(JsepTransport2WithRtcpMux, NeedsIceRestart) { + bool rtcp_mux_enabled = GetParam(); + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + + // Use the same JsepTransportDescription for both offer and answer. + JsepTransportDescription description; + description.transport_desc = TransportDescription(kIceUfrag1, kIcePwd1); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(description, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(description, SdpType::kAnswer) + .ok()); + // Flag initially should be false. + EXPECT_FALSE(jsep_transport_->needs_ice_restart()); + + // After setting flag, it should be true. + jsep_transport_->SetNeedsIceRestartFlag(); + EXPECT_TRUE(jsep_transport_->needs_ice_restart()); + + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(description, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(description, SdpType::kAnswer) + .ok()); + EXPECT_TRUE(jsep_transport_->needs_ice_restart()); + + // Doing an offer/answer that restarts ICE should clear the flag. + description.transport_desc = TransportDescription(kIceUfrag2, kIcePwd2); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(description, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(description, SdpType::kAnswer) + .ok()); + EXPECT_FALSE(jsep_transport_->needs_ice_restart()); +} + +TEST_P(JsepTransport2WithRtcpMux, GetStats) { + bool rtcp_mux_enabled = GetParam(); + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + + size_t expected_stats_size = rtcp_mux_enabled ? 1u : 2u; + TransportStats stats; + EXPECT_TRUE(jsep_transport_->GetStats(&stats)); + EXPECT_EQ(expected_stats_size, stats.channel_stats.size()); + EXPECT_EQ(ICE_CANDIDATE_COMPONENT_RTP, stats.channel_stats[0].component); + if (!rtcp_mux_enabled) { + EXPECT_EQ(ICE_CANDIDATE_COMPONENT_RTCP, stats.channel_stats[1].component); + } +} + +// Tests that VerifyCertificateFingerprint only returns true when the +// certificate matches the fingerprint. +TEST_P(JsepTransport2WithRtcpMux, VerifyCertificateFingerprint) { + bool rtcp_mux_enabled = GetParam(); + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + + EXPECT_FALSE( + jsep_transport_->VerifyCertificateFingerprint(nullptr, nullptr).ok()); + rtc::KeyType key_types[] = {rtc::KT_RSA, rtc::KT_ECDSA}; + + for (auto& key_type : key_types) { + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing", key_type)); + ASSERT_NE(nullptr, certificate); + + std::string digest_algorithm; + ASSERT_TRUE(certificate->GetSSLCertificate().GetSignatureDigestAlgorithm( + &digest_algorithm)); + ASSERT_FALSE(digest_algorithm.empty()); + std::unique_ptr<rtc::SSLFingerprint> good_fingerprint = + rtc::SSLFingerprint::CreateUnique(digest_algorithm, + *certificate->identity()); + ASSERT_NE(nullptr, good_fingerprint); + + EXPECT_TRUE(jsep_transport_ + ->VerifyCertificateFingerprint(certificate.get(), + good_fingerprint.get()) + .ok()); + EXPECT_FALSE(jsep_transport_ + ->VerifyCertificateFingerprint(certificate.get(), nullptr) + .ok()); + EXPECT_FALSE( + jsep_transport_ + ->VerifyCertificateFingerprint(nullptr, good_fingerprint.get()) + .ok()); + + rtc::SSLFingerprint bad_fingerprint = *good_fingerprint; + bad_fingerprint.digest.AppendData("0", 1); + EXPECT_FALSE( + jsep_transport_ + ->VerifyCertificateFingerprint(certificate.get(), &bad_fingerprint) + .ok()); + } +} + +// Tests the logic of DTLS role negotiation for an initial offer/answer. +TEST_P(JsepTransport2WithRtcpMux, ValidDtlsRoleNegotiation) { + bool rtcp_mux_enabled = GetParam(); + // Just use the same certificate for both sides; doesn't really matter in a + // non end-to-end test. + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA)); + + JsepTransportDescription local_description = MakeJsepTransportDescription( + rtcp_mux_enabled, kIceUfrag1, kIcePwd1, certificate); + JsepTransportDescription remote_description = MakeJsepTransportDescription( + rtcp_mux_enabled, kIceUfrag2, kIcePwd2, certificate); + + // Parameters which set the SSL role to SSL_CLIENT. + NegotiateRoleParams valid_client_params[] = { + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_ACTPASS, SdpType::kAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_ACTPASS, SdpType::kPrAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_PASSIVE, SdpType::kOffer, + SdpType::kAnswer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_PASSIVE, SdpType::kOffer, + SdpType::kPrAnswer}, + // Combinations permitted by RFC 8842 section 5.3 + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_PASSIVE, SdpType::kAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_PASSIVE, SdpType::kPrAnswer, + SdpType::kOffer}, + }; + + for (auto& param : valid_client_params) { + jsep_transport_ = + CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + local_description.transport_desc.connection_role = param.local_role; + remote_description.transport_desc.connection_role = param.remote_role; + + // Set the offer first. + if (param.local_type == SdpType::kOffer) { + EXPECT_TRUE(jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, + param.local_type) + .ok()); + EXPECT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + param.remote_type) + .ok()); + } else { + EXPECT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + param.remote_type) + .ok()); + EXPECT_TRUE(jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, + param.local_type) + .ok()); + } + EXPECT_EQ(rtc::SSL_CLIENT, *jsep_transport_->GetDtlsRole()); + } + + // Parameters which set the SSL role to SSL_SERVER. + NegotiateRoleParams valid_server_params[] = { + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_ACTPASS, SdpType::kAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_ACTPASS, SdpType::kPrAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_ACTIVE, SdpType::kOffer, + SdpType::kAnswer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_ACTIVE, SdpType::kOffer, + SdpType::kPrAnswer}, + // Combinations permitted by RFC 8842 section 5.3 + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_ACTIVE, SdpType::kPrAnswer, + SdpType::kOffer}, + }; + + for (auto& param : valid_server_params) { + jsep_transport_ = + CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + local_description.transport_desc.connection_role = param.local_role; + remote_description.transport_desc.connection_role = param.remote_role; + + // Set the offer first. + if (param.local_type == SdpType::kOffer) { + EXPECT_TRUE(jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, + param.local_type) + .ok()); + EXPECT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + param.remote_type) + .ok()); + } else { + EXPECT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + param.remote_type) + .ok()); + EXPECT_TRUE(jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, + param.local_type) + .ok()); + } + EXPECT_EQ(rtc::SSL_SERVER, *jsep_transport_->GetDtlsRole()); + } +} + +// Tests the logic of DTLS role negotiation for an initial offer/answer. +TEST_P(JsepTransport2WithRtcpMux, InvalidDtlsRoleNegotiation) { + bool rtcp_mux_enabled = GetParam(); + // Just use the same certificate for both sides; doesn't really matter in a + // non end-to-end test. + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA)); + + JsepTransportDescription local_description = MakeJsepTransportDescription( + rtcp_mux_enabled, kIceUfrag1, kIcePwd1, certificate); + JsepTransportDescription remote_description = MakeJsepTransportDescription( + rtcp_mux_enabled, kIceUfrag2, kIcePwd2, certificate); + + NegotiateRoleParams duplicate_params[] = { + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_ACTIVE, SdpType::kAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_ACTPASS, SdpType::kAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_PASSIVE, SdpType::kAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_ACTIVE, SdpType::kPrAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_ACTPASS, SdpType::kPrAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_PASSIVE, SdpType::kPrAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_ACTIVE, SdpType::kOffer, + SdpType::kAnswer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_ACTPASS, SdpType::kOffer, + SdpType::kAnswer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_PASSIVE, SdpType::kOffer, + SdpType::kAnswer}, + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_ACTIVE, SdpType::kOffer, + SdpType::kPrAnswer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_ACTPASS, SdpType::kOffer, + SdpType::kPrAnswer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_PASSIVE, SdpType::kOffer, + SdpType::kPrAnswer}}; + + for (auto& param : duplicate_params) { + jsep_transport_ = + CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + local_description.transport_desc.connection_role = param.local_role; + remote_description.transport_desc.connection_role = param.remote_role; + + if (param.local_type == SdpType::kOffer) { + EXPECT_TRUE(jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, + param.local_type) + .ok()); + EXPECT_FALSE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + param.remote_type) + .ok()); + } else { + EXPECT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + param.remote_type) + .ok()); + EXPECT_FALSE(jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, + param.local_type) + .ok()); + } + } + + // Invalid parameters due to the offerer not using a role consistent with the + // state + NegotiateRoleParams offerer_without_actpass_params[] = { + // Cannot use ACTPASS in an answer + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_PASSIVE, SdpType::kAnswer, + SdpType::kOffer}, + {CONNECTIONROLE_ACTPASS, CONNECTIONROLE_PASSIVE, SdpType::kPrAnswer, + SdpType::kOffer}, + // Cannot send ACTIVE or PASSIVE in an offer (must handle, must not send) + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_PASSIVE, SdpType::kOffer, + SdpType::kAnswer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_ACTIVE, SdpType::kOffer, + SdpType::kAnswer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_ACTPASS, SdpType::kOffer, + SdpType::kAnswer}, + {CONNECTIONROLE_ACTIVE, CONNECTIONROLE_PASSIVE, SdpType::kOffer, + SdpType::kPrAnswer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_ACTIVE, SdpType::kOffer, + SdpType::kPrAnswer}, + {CONNECTIONROLE_PASSIVE, CONNECTIONROLE_ACTPASS, SdpType::kOffer, + SdpType::kPrAnswer}}; + + for (auto& param : offerer_without_actpass_params) { + jsep_transport_ = + CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + local_description.transport_desc.connection_role = param.local_role; + remote_description.transport_desc.connection_role = param.remote_role; + + if (param.local_type == SdpType::kOffer) { + EXPECT_TRUE(jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, + param.local_type) + .ok()) + << param; + EXPECT_FALSE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + param.remote_type) + .ok()) + << param; + } else { + EXPECT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_description, + param.remote_type) + .ok()) + << param; + EXPECT_FALSE(jsep_transport_ + ->SetLocalJsepTransportDescription(local_description, + param.local_type) + .ok()) + << param; + } + } +} + +INSTANTIATE_TEST_SUITE_P(JsepTransport2Test, + JsepTransport2WithRtcpMux, + ::testing::Bool()); + +// Test that a reoffer in the opposite direction is successful as long as the +// role isn't changing. Doesn't test every possible combination like the test +// above. +TEST_F(JsepTransport2Test, ValidDtlsReofferFromAnswerer) { + // Just use the same certificate for both sides; doesn't really matter in a + // non end-to-end test. + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA)); + bool rtcp_mux_enabled = true; + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + JsepTransportDescription local_offer = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate, CONNECTIONROLE_ACTPASS); + JsepTransportDescription remote_answer = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + certificate, CONNECTIONROLE_ACTIVE); + + EXPECT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_offer, SdpType::kOffer) + .ok()); + EXPECT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_answer, SdpType::kAnswer) + .ok()); + + // We were actpass->active previously, now in the other direction it's + // actpass->passive. + JsepTransportDescription remote_offer = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + certificate, CONNECTIONROLE_ACTPASS); + JsepTransportDescription local_answer = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate, CONNECTIONROLE_PASSIVE); + + EXPECT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_offer, SdpType::kOffer) + .ok()); + EXPECT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_answer, SdpType::kAnswer) + .ok()); +} + +// Test that a reoffer in the opposite direction fails if the role changes. +// Inverse of test above. +TEST_F(JsepTransport2Test, InvalidDtlsReofferFromAnswerer) { + // Just use the same certificate for both sides; doesn't really matter in a + // non end-to-end test. + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA)); + bool rtcp_mux_enabled = true; + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + JsepTransportDescription local_offer = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate, CONNECTIONROLE_ACTPASS); + JsepTransportDescription remote_answer = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + certificate, CONNECTIONROLE_ACTIVE); + + EXPECT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_offer, SdpType::kOffer) + .ok()); + EXPECT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_answer, SdpType::kAnswer) + .ok()); + + // Changing role to passive here isn't allowed. Though for some reason this + // only fails in SetLocalTransportDescription. + JsepTransportDescription remote_offer = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + certificate, CONNECTIONROLE_PASSIVE); + JsepTransportDescription local_answer = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate, CONNECTIONROLE_ACTIVE); + + EXPECT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_offer, SdpType::kOffer) + .ok()); + EXPECT_FALSE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_answer, SdpType::kAnswer) + .ok()); +} + +// Test that a remote offer with the current negotiated role can be accepted. +// This is allowed by dtls-sdp, though we'll never generate such an offer, +// since JSEP requires generating "actpass". +TEST_F(JsepTransport2Test, RemoteOfferWithCurrentNegotiatedDtlsRole) { + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA)); + bool rtcp_mux_enabled = true; + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + JsepTransportDescription remote_desc = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate, CONNECTIONROLE_ACTPASS); + JsepTransportDescription local_desc = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + certificate, CONNECTIONROLE_ACTIVE); + + // Normal initial offer/answer with "actpass" in the offer and "active" in + // the answer. + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kAnswer) + .ok()); + + // Sanity check that role was actually negotiated. + absl::optional<rtc::SSLRole> role = jsep_transport_->GetDtlsRole(); + ASSERT_TRUE(role); + EXPECT_EQ(rtc::SSL_CLIENT, *role); + + // Subsequent offer with current negotiated role of "passive". + remote_desc.transport_desc.connection_role = CONNECTIONROLE_PASSIVE; + EXPECT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc, SdpType::kOffer) + .ok()); + EXPECT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kAnswer) + .ok()); +} + +// Test that a remote offer with the inverse of the current negotiated DTLS +// role is rejected. +TEST_F(JsepTransport2Test, RemoteOfferThatChangesNegotiatedDtlsRole) { + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA)); + bool rtcp_mux_enabled = true; + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + JsepTransportDescription remote_desc = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate, CONNECTIONROLE_ACTPASS); + JsepTransportDescription local_desc = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + certificate, CONNECTIONROLE_ACTIVE); + + // Normal initial offer/answer with "actpass" in the offer and "active" in + // the answer. + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kAnswer) + .ok()); + + // Sanity check that role was actually negotiated. + absl::optional<rtc::SSLRole> role = jsep_transport_->GetDtlsRole(); + ASSERT_TRUE(role); + EXPECT_EQ(rtc::SSL_CLIENT, *role); + + // Subsequent offer with current negotiated role of "passive". + remote_desc.transport_desc.connection_role = CONNECTIONROLE_ACTIVE; + EXPECT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc, SdpType::kOffer) + .ok()); + EXPECT_FALSE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kAnswer) + .ok()); +} + +// Test that a remote offer which changes both fingerprint and role is accepted. +TEST_F(JsepTransport2Test, RemoteOfferThatChangesFingerprintAndDtlsRole) { + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing1", rtc::KT_ECDSA)); + rtc::scoped_refptr<rtc::RTCCertificate> certificate2 = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing2", rtc::KT_ECDSA)); + bool rtcp_mux_enabled = true; + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + JsepTransportDescription remote_desc = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate, CONNECTIONROLE_ACTPASS); + JsepTransportDescription remote_desc2 = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate2, CONNECTIONROLE_ACTPASS); + + JsepTransportDescription local_desc = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + certificate, CONNECTIONROLE_ACTIVE); + + // Normal initial offer/answer with "actpass" in the offer and "active" in + // the answer. + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kAnswer) + .ok()); + + // Sanity check that role was actually negotiated. + absl::optional<rtc::SSLRole> role = jsep_transport_->GetDtlsRole(); + ASSERT_TRUE(role); + EXPECT_EQ(rtc::SSL_CLIENT, *role); + + // Subsequent exchange with new remote fingerprint and different role. + local_desc.transport_desc.connection_role = CONNECTIONROLE_PASSIVE; + EXPECT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc2, SdpType::kOffer) + .ok()); + EXPECT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kAnswer) + .ok()); + + role = jsep_transport_->GetDtlsRole(); + ASSERT_TRUE(role); + EXPECT_EQ(rtc::SSL_SERVER, *role); +} + +// Testing that a legacy client that doesn't use the setup attribute will be +// interpreted as having an active role. +TEST_F(JsepTransport2Test, DtlsSetupWithLegacyAsAnswerer) { + rtc::scoped_refptr<rtc::RTCCertificate> certificate = + rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("testing", rtc::KT_ECDSA)); + bool rtcp_mux_enabled = true; + jsep_transport_ = CreateJsepTransport2(rtcp_mux_enabled, SrtpMode::kDtlsSrtp); + jsep_transport_->SetLocalCertificate(certificate); + + JsepTransportDescription remote_desc = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag1, kIcePwd1, + certificate, CONNECTIONROLE_ACTPASS); + JsepTransportDescription local_desc = + MakeJsepTransportDescription(rtcp_mux_enabled, kIceUfrag2, kIcePwd2, + certificate, CONNECTIONROLE_ACTIVE); + + local_desc.transport_desc.connection_role = CONNECTIONROLE_ACTPASS; + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kOffer) + .ok()); + // Use CONNECTIONROLE_NONE to simulate legacy endpoint. + remote_desc.transport_desc.connection_role = CONNECTIONROLE_NONE; + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc, SdpType::kAnswer) + .ok()); + + absl::optional<rtc::SSLRole> role = jsep_transport_->GetDtlsRole(); + ASSERT_TRUE(role); + // Since legacy answer omitted setup atribute, and we offered actpass, we + // should act as passive (server). + EXPECT_EQ(rtc::SSL_SERVER, *role); +} + +// Tests that when the RTCP mux is successfully negotiated, the RTCP transport +// will be destroyed and the SignalRtpMuxActive will be fired. +TEST_F(JsepTransport2Test, RtcpMuxNegotiation) { + jsep_transport_ = + CreateJsepTransport2(/*rtcp_mux_enabled=*/false, SrtpMode::kDtlsSrtp); + JsepTransportDescription local_desc; + local_desc.rtcp_mux_enabled = true; + ASSERT_NE(nullptr, jsep_transport_->rtcp_dtls_transport()); + EXPECT_FALSE(signal_rtcp_mux_active_received_); + + // The remote side supports RTCP-mux. + JsepTransportDescription remote_desc; + remote_desc.rtcp_mux_enabled = true; + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc, SdpType::kAnswer) + .ok()); + + EXPECT_EQ(nullptr, jsep_transport_->rtcp_dtls_transport()); + EXPECT_TRUE(signal_rtcp_mux_active_received_); + + // The remote side doesn't support RTCP-mux. + jsep_transport_ = + CreateJsepTransport2(/*rtcp_mux_enabled=*/false, SrtpMode::kDtlsSrtp); + signal_rtcp_mux_active_received_ = false; + remote_desc.rtcp_mux_enabled = false; + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(local_desc, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(remote_desc, SdpType::kAnswer) + .ok()); + + EXPECT_NE(nullptr, jsep_transport_->rtcp_dtls_transport()); + EXPECT_FALSE(signal_rtcp_mux_active_received_); +} + +TEST_F(JsepTransport2Test, SdesNegotiation) { + jsep_transport_ = + CreateJsepTransport2(/*rtcp_mux_enabled=*/true, SrtpMode::kSdes); + ASSERT_TRUE(sdes_transport_); + EXPECT_FALSE(sdes_transport_->IsSrtpActive()); + + JsepTransportDescription offer_desc; + offer_desc.cryptos.push_back(cricket::CryptoParams( + 1, rtc::kCsAesCm128HmacSha1_32, "inline:" + rtc::CreateRandomString(40), + std::string())); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer) + .ok()); + + JsepTransportDescription answer_desc; + answer_desc.cryptos.push_back(cricket::CryptoParams( + 1, rtc::kCsAesCm128HmacSha1_32, "inline:" + rtc::CreateRandomString(40), + std::string())); + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer) + .ok()); + EXPECT_TRUE(sdes_transport_->IsSrtpActive()); +} + +TEST_F(JsepTransport2Test, SdesNegotiationWithEmptyCryptosInAnswer) { + jsep_transport_ = + CreateJsepTransport2(/*rtcp_mux_enabled=*/true, SrtpMode::kSdes); + ASSERT_TRUE(sdes_transport_); + EXPECT_FALSE(sdes_transport_->IsSrtpActive()); + + JsepTransportDescription offer_desc; + offer_desc.cryptos.push_back(cricket::CryptoParams( + 1, rtc::kCsAesCm128HmacSha1_32, "inline:" + rtc::CreateRandomString(40), + std::string())); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer) + .ok()); + + JsepTransportDescription answer_desc; + ASSERT_TRUE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer) + .ok()); + // SRTP is not active because the crypto parameter is answer is empty. + EXPECT_FALSE(sdes_transport_->IsSrtpActive()); +} + +TEST_F(JsepTransport2Test, SdesNegotiationWithMismatchedCryptos) { + jsep_transport_ = + CreateJsepTransport2(/*rtcp_mux_enabled=*/true, SrtpMode::kSdes); + ASSERT_TRUE(sdes_transport_); + EXPECT_FALSE(sdes_transport_->IsSrtpActive()); + + JsepTransportDescription offer_desc; + offer_desc.cryptos.push_back(cricket::CryptoParams( + 1, rtc::kCsAesCm128HmacSha1_32, "inline:" + rtc::CreateRandomString(40), + std::string())); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer) + .ok()); + + JsepTransportDescription answer_desc; + answer_desc.cryptos.push_back(cricket::CryptoParams( + 1, rtc::kCsAesCm128HmacSha1_80, "inline:" + rtc::CreateRandomString(40), + std::string())); + // Expected to fail because the crypto parameters don't match. + ASSERT_FALSE( + jsep_transport_ + ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer) + .ok()); +} + +// Tests that the remote candidates can be added to the transports after both +// local and remote descriptions are set. +TEST_F(JsepTransport2Test, AddRemoteCandidates) { + jsep_transport_ = + CreateJsepTransport2(/*rtcp_mux_enabled=*/true, SrtpMode::kDtlsSrtp); + auto fake_ice_transport = static_cast<FakeIceTransport*>( + jsep_transport_->rtp_dtls_transport()->ice_transport()); + + Candidates candidates; + candidates.push_back(CreateCandidate(/*COMPONENT_RTP*/ 1)); + candidates.push_back(CreateCandidate(/*COMPONENT_RTP*/ 1)); + + JsepTransportDescription desc; + ASSERT_TRUE( + jsep_transport_->SetLocalJsepTransportDescription(desc, SdpType::kOffer) + .ok()); + // Expected to fail because the remote description is unset. + EXPECT_FALSE(jsep_transport_->AddRemoteCandidates(candidates).ok()); + + ASSERT_TRUE( + jsep_transport_->SetRemoteJsepTransportDescription(desc, SdpType::kAnswer) + .ok()); + EXPECT_EQ(0u, fake_ice_transport->remote_candidates().size()); + EXPECT_TRUE(jsep_transport_->AddRemoteCandidates(candidates).ok()); + EXPECT_EQ(candidates.size(), fake_ice_transport->remote_candidates().size()); +} + +enum class Scenario { + kSdes, + kDtlsBeforeCallerSendOffer, + kDtlsBeforeCallerSetAnswer, + kDtlsAfterCallerSetAnswer, +}; + +class JsepTransport2HeaderExtensionTest + : public JsepTransport2Test, + public ::testing::WithParamInterface<std::tuple<Scenario, bool>> { + protected: + JsepTransport2HeaderExtensionTest() {} + + void CreateJsepTransportPair(SrtpMode mode) { + jsep_transport1_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/true, mode); + jsep_transport2_ = CreateJsepTransport2(/*rtcp_mux_enabled=*/true, mode); + + auto fake_dtls1 = + static_cast<FakeDtlsTransport*>(jsep_transport1_->rtp_dtls_transport()); + auto fake_dtls2 = + static_cast<FakeDtlsTransport*>(jsep_transport2_->rtp_dtls_transport()); + + fake_dtls1->fake_ice_transport()->SignalReadPacket.connect( + this, &JsepTransport2HeaderExtensionTest::OnReadPacket1); + fake_dtls2->fake_ice_transport()->SignalReadPacket.connect( + this, &JsepTransport2HeaderExtensionTest::OnReadPacket2); + + if (mode == SrtpMode::kDtlsSrtp) { + auto cert1 = rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT)); + jsep_transport1_->rtp_dtls_transport()->SetLocalCertificate(cert1); + auto cert2 = rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT)); + jsep_transport2_->rtp_dtls_transport()->SetLocalCertificate(cert2); + } + } + + void OnReadPacket1(rtc::PacketTransportInternal* transport, + const char* data, + size_t size, + const int64_t& /* packet_time_us */, + int flags) { + RTC_LOG(LS_INFO) << "JsepTransport 1 Received a packet."; + CompareHeaderExtensions( + reinterpret_cast<const char*>(kPcmuFrameWithExtensions), + sizeof(kPcmuFrameWithExtensions), data, size, recv_encrypted_headers1_, + false); + received_packet_count_++; + } + + void OnReadPacket2(rtc::PacketTransportInternal* transport, + const char* data, + size_t size, + const int64_t& /* packet_time_us */, + int flags) { + RTC_LOG(LS_INFO) << "JsepTransport 2 Received a packet."; + CompareHeaderExtensions( + reinterpret_cast<const char*>(kPcmuFrameWithExtensions), + sizeof(kPcmuFrameWithExtensions), data, size, recv_encrypted_headers2_, + false); + received_packet_count_++; + } + + void ConnectTransport() { + auto rtp_dtls_transport1 = + static_cast<FakeDtlsTransport*>(jsep_transport1_->rtp_dtls_transport()); + auto rtp_dtls_transport2 = + static_cast<FakeDtlsTransport*>(jsep_transport2_->rtp_dtls_transport()); + rtp_dtls_transport1->SetDestination(rtp_dtls_transport2); + } + + int GetRtpAuthLen() { + bool use_gcm = std::get<1>(GetParam()); + if (use_gcm) { + return 16; + } + return 10; + } + + void TestSendRecvPacketWithEncryptedHeaderExtension() { + TestOneWaySendRecvPacketWithEncryptedHeaderExtension( + jsep_transport1_.get()); + TestOneWaySendRecvPacketWithEncryptedHeaderExtension( + jsep_transport2_.get()); + } + + void TestOneWaySendRecvPacketWithEncryptedHeaderExtension( + JsepTransport* sender_transport) { + size_t rtp_len = sizeof(kPcmuFrameWithExtensions); + size_t packet_size = rtp_len + GetRtpAuthLen(); + rtc::Buffer rtp_packet_buffer(packet_size); + char* rtp_packet_data = rtp_packet_buffer.data<char>(); + memcpy(rtp_packet_data, kPcmuFrameWithExtensions, rtp_len); + // In order to be able to run this test function multiple times we can not + // use the same sequence number twice. Increase the sequence number by one. + rtc::SetBE16(reinterpret_cast<uint8_t*>(rtp_packet_data) + 2, + ++sequence_number_); + rtc::CopyOnWriteBuffer rtp_packet(rtp_packet_data, rtp_len, packet_size); + + int packet_count_before = received_packet_count_; + rtc::PacketOptions options; + // Send a packet and verify that the packet can be successfully received and + // decrypted. + ASSERT_TRUE(sender_transport->rtp_transport()->SendRtpPacket( + &rtp_packet, options, cricket::PF_SRTP_BYPASS)); + EXPECT_EQ(packet_count_before + 1, received_packet_count_); + } + + int sequence_number_ = 0; + int received_packet_count_ = 0; + std::unique_ptr<JsepTransport> jsep_transport1_; + std::unique_ptr<JsepTransport> jsep_transport2_; + std::vector<int> recv_encrypted_headers1_; + std::vector<int> recv_encrypted_headers2_; +}; + +// Test that the encrypted header extension works and can be changed in +// different scenarios. +TEST_P(JsepTransport2HeaderExtensionTest, EncryptedHeaderExtensionNegotiation) { + Scenario scenario = std::get<0>(GetParam()); + bool use_gcm = std::get<1>(GetParam()); + SrtpMode mode = SrtpMode ::kDtlsSrtp; + if (scenario == Scenario::kSdes) { + mode = SrtpMode::kSdes; + } + CreateJsepTransportPair(mode); + recv_encrypted_headers1_.push_back(kHeaderExtensionIDs[0]); + recv_encrypted_headers2_.push_back(kHeaderExtensionIDs[1]); + + cricket::CryptoParams sdes_param(1, rtc::kCsAesCm128HmacSha1_80, + "inline:" + rtc::CreateRandomString(40), + std::string()); + if (use_gcm) { + auto fake_dtls1 = + static_cast<FakeDtlsTransport*>(jsep_transport1_->rtp_dtls_transport()); + auto fake_dtls2 = + static_cast<FakeDtlsTransport*>(jsep_transport2_->rtp_dtls_transport()); + + fake_dtls1->SetSrtpCryptoSuite(rtc::kSrtpAeadAes256Gcm); + fake_dtls2->SetSrtpCryptoSuite(rtc::kSrtpAeadAes256Gcm); + } + + if (scenario == Scenario::kDtlsBeforeCallerSendOffer) { + ConnectTransport(); + } + + JsepTransportDescription offer_desc; + offer_desc.encrypted_header_extension_ids = recv_encrypted_headers1_; + if (scenario == Scenario::kSdes) { + offer_desc.cryptos.push_back(sdes_param); + } + ASSERT_TRUE( + jsep_transport1_ + ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport2_ + ->SetRemoteJsepTransportDescription(offer_desc, SdpType::kOffer) + .ok()); + + JsepTransportDescription answer_desc; + answer_desc.encrypted_header_extension_ids = recv_encrypted_headers2_; + if (scenario == Scenario::kSdes) { + answer_desc.cryptos.push_back(sdes_param); + } + ASSERT_TRUE( + jsep_transport2_ + ->SetLocalJsepTransportDescription(answer_desc, SdpType::kAnswer) + .ok()); + + if (scenario == Scenario::kDtlsBeforeCallerSetAnswer) { + ConnectTransport(); + // Sending packet from transport2 to transport1 should work when they are + // partially configured. + TestOneWaySendRecvPacketWithEncryptedHeaderExtension( + /*sender_transport=*/jsep_transport2_.get()); + } + + ASSERT_TRUE( + jsep_transport1_ + ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer) + .ok()); + + if (scenario == Scenario::kDtlsAfterCallerSetAnswer || + scenario == Scenario::kSdes) { + ConnectTransport(); + } + EXPECT_TRUE(jsep_transport1_->rtp_transport()->IsSrtpActive()); + EXPECT_TRUE(jsep_transport2_->rtp_transport()->IsSrtpActive()); + TestSendRecvPacketWithEncryptedHeaderExtension(); + + // Change the encrypted header extension in a new offer/answer exchange. + recv_encrypted_headers1_.clear(); + recv_encrypted_headers2_.clear(); + recv_encrypted_headers1_.push_back(kHeaderExtensionIDs[1]); + recv_encrypted_headers2_.push_back(kHeaderExtensionIDs[0]); + offer_desc.encrypted_header_extension_ids = recv_encrypted_headers1_; + answer_desc.encrypted_header_extension_ids = recv_encrypted_headers2_; + ASSERT_TRUE( + jsep_transport1_ + ->SetLocalJsepTransportDescription(offer_desc, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport2_ + ->SetRemoteJsepTransportDescription(offer_desc, SdpType::kOffer) + .ok()); + ASSERT_TRUE( + jsep_transport2_ + ->SetLocalJsepTransportDescription(answer_desc, SdpType::kAnswer) + .ok()); + ASSERT_TRUE( + jsep_transport1_ + ->SetRemoteJsepTransportDescription(answer_desc, SdpType::kAnswer) + .ok()); + EXPECT_TRUE(jsep_transport1_->rtp_transport()->IsSrtpActive()); + EXPECT_TRUE(jsep_transport2_->rtp_transport()->IsSrtpActive()); + TestSendRecvPacketWithEncryptedHeaderExtension(); +} + +INSTANTIATE_TEST_SUITE_P( + JsepTransport2Test, + JsepTransport2HeaderExtensionTest, + ::testing::Values( + std::make_tuple(Scenario::kSdes, false), + std::make_tuple(Scenario::kDtlsBeforeCallerSendOffer, true), + std::make_tuple(Scenario::kDtlsBeforeCallerSetAnswer, true), + std::make_tuple(Scenario::kDtlsAfterCallerSetAnswer, true), + std::make_tuple(Scenario::kDtlsBeforeCallerSendOffer, false), + std::make_tuple(Scenario::kDtlsBeforeCallerSetAnswer, false), + std::make_tuple(Scenario::kDtlsAfterCallerSetAnswer, false))); + +// This test verifies the ICE parameters are properly applied to the transports. +TEST_F(JsepTransport2Test, SetIceParametersWithRenomination) { + jsep_transport_ = + CreateJsepTransport2(/* rtcp_mux_enabled= */ true, SrtpMode::kDtlsSrtp); + + JsepTransportDescription jsep_description; + jsep_description.transport_desc = TransportDescription(kIceUfrag1, kIcePwd1); + jsep_description.transport_desc.AddOption(ICE_OPTION_RENOMINATION); + ASSERT_TRUE( + jsep_transport_ + ->SetLocalJsepTransportDescription(jsep_description, SdpType::kOffer) + .ok()); + auto fake_ice_transport = static_cast<FakeIceTransport*>( + jsep_transport_->rtp_dtls_transport()->ice_transport()); + EXPECT_EQ(ICEMODE_FULL, fake_ice_transport->remote_ice_mode()); + EXPECT_EQ(kIceUfrag1, fake_ice_transport->ice_ufrag()); + EXPECT_EQ(kIcePwd1, fake_ice_transport->ice_pwd()); + EXPECT_TRUE(fake_ice_transport->ice_parameters().renomination); + + jsep_description.transport_desc = TransportDescription(kIceUfrag2, kIcePwd2); + jsep_description.transport_desc.AddOption(ICE_OPTION_RENOMINATION); + ASSERT_TRUE(jsep_transport_ + ->SetRemoteJsepTransportDescription(jsep_description, + SdpType::kAnswer) + .ok()); + fake_ice_transport = static_cast<FakeIceTransport*>( + jsep_transport_->rtp_dtls_transport()->ice_transport()); + EXPECT_EQ(ICEMODE_FULL, fake_ice_transport->remote_ice_mode()); + EXPECT_EQ(kIceUfrag2, fake_ice_transport->remote_ice_ufrag()); + EXPECT_EQ(kIcePwd2, fake_ice_transport->remote_ice_pwd()); + EXPECT_TRUE(fake_ice_transport->remote_ice_parameters().renomination); +} + +} // namespace +} // namespace cricket |