commit 71efddbb34e1193157ccbdeb703f1c04e0a68512 parent d85546f0299f70bb6bf54c5544063db6c7d261fb Author: Dan Baker <dbaker@mozilla.com> Date: Mon, 1 Dec 2025 21:27:56 -0700 Bug 2000941 - Vendor libwebrtc from 22fd6f0934 Upstream commit: https://webrtc.googlesource.com/src/+/22fd6f09340cd742b65354db93600a88527cc641 Create DatagramConnection to support RtcTransport web API Bug: chromium:443019066 Change-Id: I806d607b5bf2a7ac82b991cd59b3880b8e4862d7 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/408781 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Auto-Submit: Tony Herre <herre@google.com> Reviewed-by: Guido Urdaneta <guidou@webrtc.org> Commit-Queue: Guido Urdaneta <guidou@webrtc.org> Cr-Commit-Position: refs/heads/main@{#45669} Diffstat:
13 files changed, 850 insertions(+), 5 deletions(-)
diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor @@ -1,4 +1,4 @@ # ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc -libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-02T04:25:26.099303+00:00. +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-02T04:27:40.711574+00:00. # base of lastest vendoring -be5165f548 +22fd6f0934 diff --git a/third_party/libwebrtc/api/BUILD.gn b/third_party/libwebrtc/api/BUILD.gn @@ -1088,6 +1088,38 @@ rtc_source_set("sequence_checker") { ] } +rtc_library("datagram_connection") { + visibility = [ "*" ] + sources = [ "datagram_connection.h" ] + deps = [ + ":array_view", + ":candidate", + ":ref_count", + "../p2p:transport_description", + "../rtc_base:macromagic", + "../rtc_base/system:rtc_export", + "environment", + "//third_party/abseil-cpp/absl/functional:any_invocable", + ] +} + +rtc_library("datagram_connection_factory") { + visibility = [ "*" ] + sources = [ + "datagram_connection_factory.cc", + "datagram_connection_factory.h", + ] + deps = [ + ":datagram_connection", + ":ref_count", + "../p2p:port_allocator", + "../pc:datagram_connection_internal", + "../rtc_base:macromagic", + "../rtc_base/system:rtc_export", + "environment", + ] +} + if (rtc_include_tests) { if (rtc_enable_protobuf && !build_with_chromium) { rtc_library("audioproc_f_api") { @@ -1615,6 +1647,17 @@ if (rtc_include_tests) { ] } + rtc_library("mock_datagram_connection_observer") { + visibility = [ "*" ] + testonly = true + sources = [ "test/mock_datagram_connection_observer.h" ] + deps = [ + "../api:datagram_connection", + "//test:test_support", + "//testing/gmock", + ] + } + rtc_library("create_time_controller") { visibility = [ "*" ] testonly = true diff --git a/third_party/libwebrtc/api/DEPS b/third_party/libwebrtc/api/DEPS @@ -95,6 +95,14 @@ specific_include_rules = { "+rtc_base/copy_on_write_buffer.h", ], + "datagram_connection\.h": [ + "+p2p/base/transport_description.h", + ], + + "datagram_connection_factory\.h": [ + "+p2p/base/port_allocator.h", + ], + "dtls_transport_interface\.h": [ "+rtc_base/ssl_certificate.h", ], diff --git a/third_party/libwebrtc/api/datagram_connection.h b/third_party/libwebrtc/api/datagram_connection.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 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. + */ +#ifndef API_DATAGRAM_CONNECTION_H_ +#define API_DATAGRAM_CONNECTION_H_ + +#include <memory> + +#include "absl/functional/any_invocable.h" +#include "api/array_view.h" +#include "api/candidate.h" +#include "api/ref_count.h" +#include "p2p/base/transport_description.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +// Experimental class to support prototyping of a packet-level web API +// "RtcTransport" being discussed in the w3c working group. +// Subject to dramatic change without notice. +// +// All interactions should be on the same thread which is also used for +// networking internals. +class RTC_EXPORT DatagramConnection : public RefCountInterface { + public: + class Observer { + public: + virtual ~Observer() = default; + virtual void OnCandidateGathered(const Candidate& candidate) = 0; + virtual void OnPacketReceived(ArrayView<const uint8_t> data) = 0; + // Notification of an asynchronous failure to an earlier call to SendPacket. + // TODO(crbug.com/443019066): Associate this with a specific send call. + virtual void OnSendError() = 0; + // Notification of an error unrelated to sending. Observers should + // check the current state of the connection. + virtual void OnConnectionError() = 0; + + virtual void OnWritableChange() = 0; + }; + + virtual ~DatagramConnection() = default; + + virtual void SetRemoteIceParameters(const IceParameters& ice_parameters) = 0; + virtual void AddRemoteCandidate(const Candidate& candidate) = 0; + + // Whether SendPacket calls should be expected to succeed. + // See also Observer::OnWritableChange(). + virtual bool Writable() = 0; + + enum class SSLRole { kClient, kServer }; + virtual void SetRemoteDtlsParameters(absl::string_view digestAlgorithm, + const uint8_t* digest, + size_t digest_len, + SSLRole ssl_role) = 0; + // SendPacket on this connection, returning whether the send succeeded. + virtual bool SendPacket(ArrayView<const uint8_t> data) = 0; + + // Initiate closing connection and releasing resources. Must be called before + // destruction. + virtual void Terminate( + absl::AnyInvocable<void()> terminate_complete_callback) = 0; +}; + +} // namespace webrtc +#endif // API_DATAGRAM_CONNECTION_H_ diff --git a/third_party/libwebrtc/api/datagram_connection_factory.cc b/third_party/libwebrtc/api/datagram_connection_factory.cc @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 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 "api/datagram_connection_factory.h" + +#include <memory> +#include <utility> + +#include "pc/datagram_connection_internal.h" + +namespace webrtc { + +// static +scoped_refptr<DatagramConnection> CreateDatagramConnection( + const Environment& env, + std::unique_ptr<PortAllocator> port_allocator, + absl::string_view transport_name, + bool ice_controlling, + scoped_refptr<RTCCertificate> certificate, + std::unique_ptr<DatagramConnection::Observer> observer) { + return make_ref_counted<DatagramConnectionInternal>( + env, std::move(port_allocator), transport_name, ice_controlling, + certificate, std::move(observer)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/api/datagram_connection_factory.h b/third_party/libwebrtc/api/datagram_connection_factory.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 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. + */ +#ifndef API_DATAGRAM_CONNECTION_FACTORY_H_ +#define API_DATAGRAM_CONNECTION_FACTORY_H_ + +#include <memory> + +#include "api/datagram_connection.h" +#include "api/environment/environment.h" +#include "api/ref_count.h" +#include "p2p/base/port_allocator.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { + +RTC_EXPORT scoped_refptr<DatagramConnection> CreateDatagramConnection( + const Environment& env, + std::unique_ptr<PortAllocator> port_allocator, + absl::string_view transport_name, + bool ice_controlling, + scoped_refptr<RTCCertificate> certificate, + std::unique_ptr<DatagramConnection::Observer> observer); + +} // namespace webrtc +#endif // API_DATAGRAM_CONNECTION_FACTORY_H_ diff --git a/third_party/libwebrtc/api/test/mock_datagram_connection_observer.h b/third_party/libwebrtc/api/test/mock_datagram_connection_observer.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 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. + */ +#ifndef API_TEST_MOCK_DATAGRAM_CONNECTION_OBSERVER_H_ +#define API_TEST_MOCK_DATAGRAM_CONNECTION_OBSERVER_H_ + +#include "api/datagram_connection.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockDatagramConnectionObserver : public DatagramConnection::Observer { + public: + MOCK_METHOD(void, + OnCandidateGathered, + (const Candidate& candidate), + (override)); + MOCK_METHOD(void, + OnPacketReceived, + (ArrayView<const uint8_t> data), + (override)); + MOCK_METHOD(void, OnSendError, (), (override)); + MOCK_METHOD(void, OnConnectionError, (), (override)); + MOCK_METHOD(void, OnWritableChange, (), (override)); +}; + +} // namespace webrtc + +#endif // API_TEST_MOCK_DATAGRAM_CONNECTION_OBSERVER_H_ diff --git a/third_party/libwebrtc/moz-patch-stack/s0027.patch b/third_party/libwebrtc/moz-patch-stack/s0027.patch @@ -203,7 +203,7 @@ index 3efce2dd19..cbfc05f243 100644 } diff --git a/api/BUILD.gn b/api/BUILD.gn -index 1c02e38171..7758d17af0 100644 +index 042dccefd9..250b0bef76 100644 --- a/api/BUILD.gn +++ b/api/BUILD.gn @@ -44,6 +44,9 @@ rtc_library("enable_media") { diff --git a/third_party/libwebrtc/moz-patch-stack/s0102.patch b/third_party/libwebrtc/moz-patch-stack/s0102.patch @@ -126,7 +126,7 @@ index 771e0b196a..7e1e8353ab 100644 "Generated during 'gn gen' by //BUILD.gn.", "", diff --git a/api/BUILD.gn b/api/BUILD.gn -index 7758d17af0..129e246104 100644 +index 250b0bef76..8eacf519ff 100644 --- a/api/BUILD.gn +++ b/api/BUILD.gn @@ -8,8 +8,8 @@ @@ -601,7 +601,7 @@ index b7561e53b6..fe7eb57423 100644 import("../../webrtc.gni") diff --git a/pc/BUILD.gn b/pc/BUILD.gn -index 4c837bb838..96fbcc9539 100644 +index 97b3af690e..5fc4b44f21 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -30,8 +30,8 @@ diff --git a/third_party/libwebrtc/pc/BUILD.gn b/third_party/libwebrtc/pc/BUILD.gn @@ -2066,6 +2066,33 @@ rtc_source_set("libjingle_peerconnection") { ] } +rtc_library("datagram_connection_internal") { + sources = [ + "datagram_connection_internal.cc", + "datagram_connection_internal.h", + ] + deps = [ + "../api:datagram_connection", + "../api:ice_transport_interface", + "../api:ref_count", + "../api/environment", + "../call:rtp_interfaces", + "../modules/rtp_rtcp:rtp_rtcp_format", + "../p2p:dtls_transport", + "../p2p:p2p_constants", + "../p2p:p2p_transport_channel", + "../p2p:port", + "../p2p:port_allocator", + "../pc:dtls_srtp_transport", + "../pc:dtls_transport", + "../rtc_base:crypto_random", + "../rtc_base:logging", + "../rtc_base:macromagic", + "../rtc_base:ssl_adapter", + "../rtc_base/system:rtc_export", + ] +} + if (rtc_include_tests && !build_with_chromium) { rtc_source_set("fake_codec_lookup_helper") { testonly = true @@ -2085,6 +2112,7 @@ if (rtc_include_tests && !build_with_chromium) { "audio_rtp_receiver_unittest.cc", "channel_unittest.cc", "codec_vendor_unittest.cc", + "datagram_connection_unittest.cc", "dtls_srtp_transport_integrationtest.cc", "dtls_srtp_transport_unittest.cc", "dtls_transport_unittest.cc", @@ -2112,6 +2140,7 @@ if (rtc_include_tests && !build_with_chromium) { ":audio_rtp_receiver", ":channel", ":codec_vendor", + ":datagram_connection_internal", ":dtls_srtp_transport", ":dtls_transport", ":ice_transport", @@ -2137,6 +2166,7 @@ if (rtc_include_tests && !build_with_chromium) { "../api:array_view", "../api:audio_options_api", "../api:candidate", + "../api:datagram_connection", "../api:dtls_transport_interface", "../api:field_trials", "../api:field_trials_view", @@ -2145,6 +2175,7 @@ if (rtc_include_tests && !build_with_chromium) { "../api:libjingle_peerconnection_api", "../api:make_ref_counted", "../api:media_stream_interface", + "../api:mock_datagram_connection_observer", "../api:priority", "../api:rtc_error", "../api:rtc_error_matchers", @@ -2191,8 +2222,10 @@ if (rtc_include_tests && !build_with_chromium) { "../p2p:ice_transport_internal", "../p2p:p2p_constants", "../p2p:p2p_test_utils", + "../p2p:p2p_transport_channel", "../p2p:packet_transport_internal", "../p2p:port_allocator", + "../p2p:port_interface", "../p2p:transport_description", "../p2p:transport_description_factory", "../p2p:transport_info", @@ -2201,10 +2234,12 @@ if (rtc_include_tests && !build_with_chromium) { "../rtc_base:byte_order", "../rtc_base:checks", "../rtc_base:copy_on_write_buffer", + "../rtc_base:gunit_helpers", "../rtc_base:logging", "../rtc_base:net_helper", "../rtc_base:network_route", "../rtc_base:rtc_base_tests_utils", + "../rtc_base:rtc_event", "../rtc_base:socket", "../rtc_base:socket_address", "../rtc_base:socket_server", diff --git a/third_party/libwebrtc/pc/datagram_connection_internal.cc b/third_party/libwebrtc/pc/datagram_connection_internal.cc @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2025 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/datagram_connection_internal.h" + +#include <string> +#include <utility> + +#include "api/ice_transport_interface.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "p2p/base/p2p_constants.h" +#include "p2p/base/p2p_transport_channel.h" +#include "p2p/base/port.h" +#include "p2p/dtls/dtls_transport.h" +#include "rtc_base/crypto_random.h" +#include "rtc_base/logging.h" +#include "rtc_base/ssl_stream_adapter.h" + +namespace webrtc { +namespace { +// Fixed SSRC for DatagramConnections. Transport won't be shared with any +// other streams, so a single fixed SSRC is safe. +constexpr uint32_t kDatagramConnectionSsrc = 0x1EE7; + +// Helper function to create IceTransportInit +IceTransportInit CreateIceTransportInit(const Environment& env, + PortAllocator* allocator) { + IceTransportInit init(env); + init.set_port_allocator(allocator); + return init; +} + +// Helper function to create DtlsTransportInternal +std::unique_ptr<DtlsTransportInternal> CreateDtlsTransportInternal( + const Environment& env, + IceTransportInternal* transport_channel) { + return std::make_unique<DtlsTransportInternalImpl>( + env, transport_channel, CryptoOptions{}, + /*ssl_max_version=*/SSL_PROTOCOL_DTLS_13); +} +} // namespace + +DatagramConnectionInternal::DatagramConnectionInternal( + const Environment& env, + std::unique_ptr<PortAllocator> port_allocator, + absl::string_view transport_name, + bool ice_controlling, + scoped_refptr<RTCCertificate> certificate, + std::unique_ptr<Observer> observer, + std::unique_ptr<IceTransportInternal> custom_ice_transport_internal) + : port_allocator_(std::move(port_allocator)), + transport_channel_( + custom_ice_transport_internal + ? std::move(custom_ice_transport_internal) + : P2PTransportChannel::Create( + transport_name, + ICE_CANDIDATE_COMPONENT_RTP, + CreateIceTransportInit(env, port_allocator_.get()))), + dtls_transport_(make_ref_counted<DtlsTransport>( + CreateDtlsTransportInternal(env, transport_channel_.get()))), + dtls_srtp_transport_(std::make_unique<DtlsSrtpTransport>( + /*rtcp_mux_enabled=*/true, + env.field_trials())), + observer_(std::move(observer)) { + RTC_CHECK(observer_); + + dtls_srtp_transport_->SetDtlsTransports(dtls_transport_->internal(), + /*rtcp_dtls_transport=*/nullptr); + + dtls_transport_->ice_transport()->internal()->SubscribeCandidateGathered( + std::bind_front(&DatagramConnectionInternal::OnCandidateGathered, this)); + + dtls_srtp_transport_->SubscribeWritableState( + this, [this](bool) { this->OnWritableStatePossiblyChanged(); }); + + transport_channel_->SubscribeIceTransportStateChanged( + [this](IceTransportInternal* transport) { + if (transport->GetIceTransportState() == + webrtc::IceTransportState::kFailed) { + OnConnectionError(); + } + }); + dtls_transport_->internal()->SubscribeDtlsHandshakeError( + [this](webrtc::SSLHandshakeError) { OnConnectionError(); }); + + // TODO(crbug.com/443019066): Bind to SetCandidateErrorCallback() and + // propagate back to the Observer. + constexpr int kIceUfragLength = 16; + std::string ufrag = CreateRandomString(kIceUfragLength); + std::string icepw = CreateRandomString(ICE_PWD_LENGTH); + dtls_transport_->ice_transport()->internal()->SetIceParameters( + IceParameters(ufrag, icepw, + /*ice_renomination=*/false)); + dtls_transport_->ice_transport()->internal()->SetIceRole( + ice_controlling ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED); + dtls_transport_->ice_transport()->internal()->MaybeStartGathering(); + + // Match everything for our fixed SSRC (should be everything). + RtpDemuxerCriteria demuxer_criteria(/*mid=*/""); + demuxer_criteria.ssrcs().insert(kDatagramConnectionSsrc); + + dtls_srtp_transport_->RegisterRtpDemuxerSink(demuxer_criteria, this); + + RTC_CHECK(dtls_transport_->internal()->SetLocalCertificate(certificate)); +} + +void DatagramConnectionInternal::SetRemoteIceParameters( + const IceParameters& ice_parameters) { + if (current_state_ != State::kActive) { + // TODO(crbug.com/443019066): Propagate an error back to the caller. + return; + } + + dtls_transport_->ice_transport()->internal()->SetRemoteIceParameters( + ice_parameters); +} + +void DatagramConnectionInternal::AddRemoteCandidate( + const Candidate& candidate) { + if (current_state_ != State::kActive) { + // TODO(crbug.com/443019066): Propagate an error back to the caller. + return; + } + + dtls_transport_->ice_transport()->internal()->AddRemoteCandidate(candidate); +} + +bool DatagramConnectionInternal::Writable() { + return current_state_ == State::kActive && + dtls_transport_->ice_transport()->internal()->writable() && + dtls_srtp_transport_->IsSrtpActive(); +} + +void DatagramConnectionInternal::SetRemoteDtlsParameters( + absl::string_view digestAlgorithm, + const uint8_t* digest, + size_t digest_len, + DatagramConnection::SSLRole ssl_role) { + if (current_state_ != State::kActive) { + // TODO(crbug.com/443019066): Propagate an error back to the caller. + return; + } + + webrtc::SSLRole mapped_ssl_role = + ssl_role == DatagramConnection::SSLRole::kClient ? SSL_CLIENT + : SSL_SERVER; + dtls_transport_->internal()->SetRemoteParameters(digestAlgorithm, digest, + digest_len, mapped_ssl_role); +} + +bool DatagramConnectionInternal::SendPacket(ArrayView<const uint8_t> data) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + if (current_state_ != State::kActive) { + return false; + } + + if (!dtls_srtp_transport_->IsSrtpActive()) { + // TODO(crbug.com/443019066): Propagate an error back to the caller. + RTC_LOG(LS_ERROR) << "Dropping packet on non-active DTLS"; + return false; + } + // TODO(crbug.com/443019066): Update this representation inside an SRTP + // packet as the spec level discussions continue. + RtpPacket packet; + packet.SetSequenceNumber(next_seq_num_++); + packet.SetTimestamp(next_ts_++); + packet.SetSsrc(kDatagramConnectionSsrc); + packet.SetPayload(data); + CopyOnWriteBuffer buffer = packet.Buffer(); + // Provide the flag PF_SRTP_BYPASS as these packets are being encrypted by + // SRTP, so should bypass DTLS encryption. + return dtls_srtp_transport_->SendRtpPacket(&buffer, + AsyncSocketPacketOptions(), + /*flags=*/PF_SRTP_BYPASS); +} + +void DatagramConnectionInternal::Terminate( + absl::AnyInvocable<void()> terminate_complete_callback) { + if (current_state_ != State::kActive) { + terminate_complete_callback(); + return; + } + + dtls_srtp_transport_->UnregisterRtpDemuxerSink(this); + // TODO(crbug.com/443019066): Once we need asynchronous termination, set state + // to TerminationInProgress here and Terminated later once done. + current_state_ = State::kTerminated; + terminate_complete_callback(); +} + +void DatagramConnectionInternal::OnCandidateGathered( + IceTransportInternal*, + const Candidate& candidate) { + if (current_state_ != State::kActive) { + return; + } + observer_->OnCandidateGathered(candidate); +} + +void DatagramConnectionInternal::OnTransportWritableStateChanged( + PacketTransportInternal*) { + OnWritableStatePossiblyChanged(); +} + +void DatagramConnectionInternal::OnWritableStatePossiblyChanged() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + if (current_state_ != State::kActive) { + return; + } + bool writable = Writable(); + if (last_writable_state_ != writable) { + observer_->OnWritableChange(); + last_writable_state_ = writable; + } +} + +void DatagramConnectionInternal::OnConnectionError() { + if (current_state_ != State::kActive) { + return; + } + observer_->OnConnectionError(); +} + +void DatagramConnectionInternal::OnRtpPacket(const RtpPacketReceived& packet) { + if (current_state_ != State::kActive) { + return; + } + observer_->OnPacketReceived(packet.payload()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/pc/datagram_connection_internal.h b/third_party/libwebrtc/pc/datagram_connection_internal.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025 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. + */ +#ifndef PC_DATAGRAM_CONNECTION_INTERNAL_H_ +#define PC_DATAGRAM_CONNECTION_INTERNAL_H_ + +#include <memory> + +#include "api/datagram_connection.h" +#include "api/environment/environment.h" +#include "api/ref_count.h" +#include "call/rtp_packet_sink_interface.h" +#include "p2p/base/p2p_transport_channel.h" +#include "p2p/base/port_allocator.h" +#include "pc/dtls_srtp_transport.h" +#include "pc/dtls_transport.h" +#include "rtc_base/system/rtc_export.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class RTC_EXPORT DatagramConnectionInternal : public DatagramConnection, + public RtpPacketSinkInterface { + public: + DatagramConnectionInternal(const Environment& env, + std::unique_ptr<PortAllocator> port_allocator, + absl::string_view transport_name, + bool ice_controlling, + scoped_refptr<RTCCertificate> certificate, + std::unique_ptr<Observer> observer, + std::unique_ptr<IceTransportInternal> + custom_ice_transport_internal = nullptr); + + void SetRemoteIceParameters(const IceParameters& ice_parameters) override; + void AddRemoteCandidate(const Candidate& candidate) override; + bool Writable() override; + void SetRemoteDtlsParameters(absl::string_view digestAlgorithm, + const uint8_t* digest, + size_t digest_len, + DatagramConnection::SSLRole ssl_role) override; + bool SendPacket(ArrayView<const uint8_t> data) override; + + void Terminate( + absl::AnyInvocable<void()> terminate_complete_callback) override; + + void OnCandidateGathered(IceTransportInternal* ice_transport, + const Candidate& candidate); + + void OnWritableStatePossiblyChanged(); + void OnTransportWritableStateChanged(PacketTransportInternal*); + void OnConnectionError(); + + // RtpPacketSinkInterface + void OnRtpPacket(const RtpPacketReceived& packet) override; + +#if RTC_DCHECK_IS_ON + DtlsSrtpTransport* GetDtlsSrtpTransportForTesting() { + return dtls_srtp_transport_.get(); + } +#endif + + private: + enum class State { kActive, kTerminated }; + State current_state_ = State::kActive; + + // Note the destruction order of these transport objects must be preserved. + const std::unique_ptr<PortAllocator> port_allocator_; + const std::unique_ptr<IceTransportInternal> transport_channel_; + const scoped_refptr<DtlsTransport> dtls_transport_; + const std::unique_ptr<DtlsSrtpTransport> dtls_srtp_transport_; + + const std::unique_ptr<Observer> observer_; + + bool last_writable_state_ = false; + const SequenceChecker sequence_checker_; + uint16_t next_seq_num_ RTC_GUARDED_BY(sequence_checker_) = 0; + uint32_t next_ts_ RTC_GUARDED_BY(sequence_checker_) = 10000; +}; + +} // namespace webrtc +#endif // PC_DATAGRAM_CONNECTION_INTERNAL_H_ diff --git a/third_party/libwebrtc/pc/datagram_connection_unittest.cc b/third_party/libwebrtc/pc/datagram_connection_unittest.cc @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2025 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 "api/datagram_connection.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" +#include "api/ice_transport_interface.h" +#include "api/rtc_error.h" +#include "api/test/mock_datagram_connection_observer.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_util.h" +#include "p2p/base/fake_port_allocator.h" +#include "p2p/base/p2p_constants.h" +#include "p2p/base/p2p_transport_channel.h" +#include "p2p/base/port_interface.h" +#include "p2p/base/transport_description.h" +#include "p2p/test/fake_ice_transport.h" +#include "pc/datagram_connection_internal.h" +#include "pc/test/fake_rtc_certificate_generator.h" +#include "rtc_base/event.h" +#include "rtc_base/gunit.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/ssl_fingerprint.h" +#include "rtc_base/ssl_identity.h" +#include "rtc_base/third_party/sigslot/sigslot.h" +#include "rtc_base/thread.h" +#include "test/gmock.h" + +namespace webrtc { +namespace { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; + +class DatagramConnectionTest : public ::testing::Test, + public sigslot::has_slots<> { + public: + DatagramConnectionTest() : env_(CreateEnvironment()) {} + + ~DatagramConnectionTest() override { + conn1_->Terminate([] {}); + conn2_->Terminate([] {}); + } + + void CreateConnections() { + auto observer1 = + std::make_unique<NiceMock<MockDatagramConnectionObserver>>(); + observer1_ptr_ = observer1.get(); + auto observer2 = + std::make_unique<NiceMock<MockDatagramConnectionObserver>>(); + observer2_ptr_ = observer2.get(); + + cert1_ = FakeRTCCertificateGenerator::GenerateCertificate(); + cert2_ = FakeRTCCertificateGenerator::GenerateCertificate(); + std::string transport_name1 = "FakeTransport1"; + std::string transport_name2 = "FakeTransport2"; + + auto ice1 = std::make_unique<FakeIceTransport>(transport_name1, + ICE_CANDIDATE_COMPONENT_RTP); + ice1->SetAsync(true); + auto ice2 = std::make_unique<FakeIceTransport>(transport_name2, + ICE_CANDIDATE_COMPONENT_RTP); + ice2->SetAsync(true); + ice1_ = ice1.get(); + ice2_ = ice2.get(); + + conn1_ = make_ref_counted<DatagramConnectionInternal>( + env_, /*port_allocator=*/nullptr, transport_name1, true, cert1_, + std::move(observer1), std::move(ice1)); + + conn2_ = make_ref_counted<DatagramConnectionInternal>( + env_, /*port_allocator=*/nullptr, transport_name2, false, cert2_, + std::move(observer2), std::move(ice2)); + } + + void Connect() { + auto fingerprint1 = SSLFingerprint::CreateFromCertificate(*cert1_); + auto fingerprint2 = SSLFingerprint::CreateFromCertificate(*cert2_); + + conn1_->SetRemoteDtlsParameters( + fingerprint2->algorithm, fingerprint2->digest.data(), + fingerprint2->digest.size(), DatagramConnection::SSLRole::kClient); + conn2_->SetRemoteDtlsParameters( + fingerprint1->algorithm, fingerprint1->digest.data(), + fingerprint1->digest.size(), DatagramConnection::SSLRole::kServer); + + ice1_->SetDestination(ice2_); + } + + protected: + AutoThread main_thread_; + const Environment env_; + NiceMock<MockDatagramConnectionObserver>* observer1_ptr_ = nullptr; + NiceMock<MockDatagramConnectionObserver>* observer2_ptr_ = nullptr; + scoped_refptr<RTCCertificate> cert1_; + scoped_refptr<RTCCertificate> cert2_; + scoped_refptr<DatagramConnectionInternal> conn1_; + scoped_refptr<DatagramConnectionInternal> conn2_; + FakeIceTransport* ice1_; + FakeIceTransport* ice2_; +}; + +TEST_F(DatagramConnectionTest, CreateAndDestroy) { + CreateConnections(); + EXPECT_TRUE(conn1_); + EXPECT_TRUE(conn2_); +} + +TEST_F(DatagramConnectionTest, TransportsBecomeWritable) { + main_thread_.BlockingCall([&]() { + CreateConnections(); + Connect(); + WAIT(conn1_->Writable() && conn2_->Writable(), 1000); + EXPECT_TRUE(conn1_->Writable()); + EXPECT_TRUE(conn2_->Writable()); + }); +} + +TEST_F(DatagramConnectionTest, ObserverNotifiedOnWritableChange) { + CreateConnections(); + EXPECT_FALSE(conn1_->Writable()); + + Event event; + EXPECT_CALL(*observer1_ptr_, OnWritableChange()).WillOnce([&]() { + event.Set(); + }); + + main_thread_.BlockingCall([&]() { Connect(); }); + + WAIT(conn1_->Writable() && conn2_->Writable(), 1000); + + ASSERT_TRUE(event.Wait(TimeDelta::Millis(1000))); + EXPECT_TRUE(conn1_->Writable()); +} + +TEST_F(DatagramConnectionTest, ObserverCalledOnReceivedPacket) { + CreateConnections(); + + Event event; + std::vector<uint8_t> packet_data = {1, 2, 3, 4}; + RtpPacketReceived packet; + packet.SetPayload(packet_data); + + EXPECT_CALL(*observer1_ptr_, OnPacketReceived(_)) + .WillOnce([&](ArrayView<const uint8_t> data) { + EXPECT_EQ(data.size(), packet_data.size()); + EXPECT_EQ(memcmp(data.data(), packet_data.data(), packet_data.size()), + 0); + event.Set(); + }); + + main_thread_.BlockingCall([&]() { conn1_->OnRtpPacket(packet); }); + + ASSERT_TRUE(event.Wait(TimeDelta::Millis(1000))); +} + +TEST_F(DatagramConnectionTest, PacketsAreSent) { + // Calling SendPacket causes the packet to be sent on ice1_ + CreateConnections(); + Connect(); + WAIT(conn1_->Writable() && conn2_->Writable(), 1000); + + std::vector<uint8_t> data = {1, 2, 3, 4, 5}; + EXPECT_TRUE(conn1_->SendPacket(data)); + // Pull the RTP sequence number from ice1's last_sent_packet + uint16_t seq_num = ParseRtpSequenceNumber(ice1_->last_sent_packet()); + EXPECT_EQ(seq_num, 0); +} + +TEST_F(DatagramConnectionTest, PacketsAreReceived) { + CreateConnections(); + Connect(); + WAIT(conn1_->Writable() && conn2_->Writable(), 1000); + + std::vector<uint8_t> data = {1, 2, 3, 4, 5}; + Event event; + EXPECT_CALL(*observer2_ptr_, OnPacketReceived(_)) + .WillOnce([&](ArrayView<const uint8_t> received_data) { + EXPECT_EQ(received_data.size(), data.size()); + EXPECT_EQ(memcmp(received_data.data(), data.data(), data.size()), 0); + event.Set(); + }); + + EXPECT_TRUE(conn1_->SendPacket(data)); + // Process the message queue to ensure the packet is sent. + Thread::Current()->ProcessMessages(0); + ASSERT_TRUE(event.Wait(TimeDelta::Millis(1000))); +} + +TEST_F(DatagramConnectionTest, SendPacketFailsWhenNotWritable) { + CreateConnections(); + // Don't call Connect(), so the transports are not writable. + std::vector<uint8_t> data = {1, 2, 3, 4, 5}; + EXPECT_FALSE(conn1_->Writable()); + EXPECT_FALSE(conn1_->SendPacket(data)); +} + +TEST_F(DatagramConnectionTest, SendPacketFailsWhenDtlsNotActive) { + CreateConnections(); + // Set destination to make the transport channel writable, but don't set DTLS + // parameters, so DTLS is not active. + ice1_->SetDestination(ice2_); + WAIT(ice1_->writable(), 1000); + EXPECT_TRUE(ice1_->writable()); + EXPECT_FALSE( + conn1_->Writable()); // Should be false because DTLS is not active. + + std::vector<uint8_t> data = {1, 2, 3, 4, 5}; + EXPECT_FALSE(conn1_->SendPacket(data)); +} + +TEST_F(DatagramConnectionTest, OnCandidateGathered) { + CreateConnections(); + + Candidate candidate(ICE_CANDIDATE_COMPONENT_RTP, "udp", + SocketAddress("1.1.1.1", 1234), 100, "", "", + IceCandidateType::kHost, 0, "1"); + Event event; + EXPECT_CALL(*observer1_ptr_, OnCandidateGathered(_)) + .WillOnce([&](const Candidate& c) { + EXPECT_EQ(c.address(), candidate.address()); + event.Set(); + }); + + main_thread_.BlockingCall( + [&]() { conn1_->OnCandidateGathered(ice1_, candidate); }); + + ASSERT_TRUE(event.Wait(TimeDelta::Millis(1000))); +} + +TEST_F(DatagramConnectionTest, ObserverNotifiedOnConnectionError) { + CreateConnections(); + + Event event; + EXPECT_CALL(*observer1_ptr_, OnConnectionError()).WillOnce([&]() { + event.Set(); + }); + + main_thread_.BlockingCall([&]() { + ice1_->SetTransportState(webrtc::IceTransportState::kFailed, + webrtc::IceTransportStateInternal::STATE_FAILED); + }); + + ASSERT_TRUE(event.Wait(TimeDelta::Millis(1000))); +} + +} // namespace +} // namespace webrtc