diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/pc/webrtc_session_description_factory.cc | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/pc/webrtc_session_description_factory.cc')
-rw-r--r-- | third_party/libwebrtc/pc/webrtc_session_description_factory.cc | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/webrtc_session_description_factory.cc b/third_party/libwebrtc/pc/webrtc_session_description_factory.cc new file mode 100644 index 0000000000..3d398e3b6e --- /dev/null +++ b/third_party/libwebrtc/pc/webrtc_session_description_factory.cc @@ -0,0 +1,467 @@ +/* + * Copyright 2013 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/webrtc_session_description_factory.h" + +#include <stddef.h> + +#include <queue> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include "absl/algorithm/container.h" +#include "absl/types/optional.h" +#include "api/jsep.h" +#include "api/jsep_session_description.h" +#include "api/rtc_error.h" +#include "api/sequence_checker.h" +#include "pc/connection_context.h" +#include "pc/sdp_state_provider.h" +#include "pc/session_description.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/ssl_identity.h" +#include "rtc_base/ssl_stream_adapter.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/unique_id_generator.h" + +using cricket::MediaSessionOptions; +using rtc::UniqueRandomIdGenerator; + +namespace webrtc { +namespace { +static const char kFailedDueToIdentityFailed[] = + " failed because DTLS identity request failed"; +static const char kFailedDueToSessionShutdown[] = + " failed because the session was shut down"; + +static const uint64_t kInitSessionVersion = 2; + +// Check that each sender has a unique ID. +static bool ValidMediaSessionOptions( + const cricket::MediaSessionOptions& session_options) { + std::vector<cricket::SenderOptions> sorted_senders; + for (const cricket::MediaDescriptionOptions& media_description_options : + session_options.media_description_options) { + sorted_senders.insert(sorted_senders.end(), + media_description_options.sender_options.begin(), + media_description_options.sender_options.end()); + } + absl::c_sort(sorted_senders, [](const cricket::SenderOptions& sender1, + const cricket::SenderOptions& sender2) { + return sender1.track_id < sender2.track_id; + }); + return absl::c_adjacent_find(sorted_senders, + [](const cricket::SenderOptions& sender1, + const cricket::SenderOptions& sender2) { + return sender1.track_id == sender2.track_id; + }) == sorted_senders.end(); +} +} // namespace + +// static +void WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription( + const SessionDescriptionInterface* source_desc, + const std::string& content_name, + SessionDescriptionInterface* dest_desc) { + if (!source_desc) { + return; + } + const cricket::ContentInfos& contents = + source_desc->description()->contents(); + const cricket::ContentInfo* cinfo = + source_desc->description()->GetContentByName(content_name); + if (!cinfo) { + return; + } + size_t mediasection_index = static_cast<int>(cinfo - &contents[0]); + const IceCandidateCollection* source_candidates = + source_desc->candidates(mediasection_index); + const IceCandidateCollection* dest_candidates = + dest_desc->candidates(mediasection_index); + if (!source_candidates || !dest_candidates) { + return; + } + for (size_t n = 0; n < source_candidates->count(); ++n) { + const IceCandidateInterface* new_candidate = source_candidates->at(n); + if (!dest_candidates->HasCandidate(new_candidate)) { + dest_desc->AddCandidate(source_candidates->at(n)); + } + } +} + +WebRtcSessionDescriptionFactory::WebRtcSessionDescriptionFactory( + ConnectionContext* context, + const SdpStateProvider* sdp_info, + const std::string& session_id, + bool dtls_enabled, + std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator, + rtc::scoped_refptr<rtc::RTCCertificate> certificate, + std::function<void(const rtc::scoped_refptr<rtc::RTCCertificate>&)> + on_certificate_ready, + const FieldTrialsView& field_trials) + : signaling_thread_(context->signaling_thread()), + transport_desc_factory_(field_trials), + session_desc_factory_(context->media_engine(), + context->use_rtx(), + context->ssrc_generator(), + &transport_desc_factory_), + // RFC 4566 suggested a Network Time Protocol (NTP) format timestamp + // as the session id and session version. To simplify, it should be fine + // to just use a random number as session id and start version from + // `kInitSessionVersion`. + session_version_(kInitSessionVersion), + cert_generator_(dtls_enabled ? std::move(cert_generator) : nullptr), + sdp_info_(sdp_info), + session_id_(session_id), + certificate_request_state_(CERTIFICATE_NOT_NEEDED), + on_certificate_ready_(on_certificate_ready) { + RTC_DCHECK(signaling_thread_); + + if (!dtls_enabled) { + SetSdesPolicy(cricket::SEC_REQUIRED); + RTC_LOG(LS_VERBOSE) << "DTLS-SRTP disabled."; + return; + } + + // SRTP-SDES is disabled if DTLS is on. + SetSdesPolicy(cricket::SEC_DISABLED); + if (certificate) { + // Use `certificate`. + certificate_request_state_ = CERTIFICATE_WAITING; + + RTC_LOG(LS_VERBOSE) << "DTLS-SRTP enabled; has certificate parameter."; + RTC_LOG(LS_INFO) << "Using certificate supplied to the constructor."; + SetCertificate(certificate); + } else { + // Generate certificate. + certificate_request_state_ = CERTIFICATE_WAITING; + + auto callback = [weak_ptr = weak_factory_.GetWeakPtr()]( + rtc::scoped_refptr<rtc::RTCCertificate> certificate) { + if (!weak_ptr) { + return; + } + if (certificate) { + weak_ptr->SetCertificate(std::move(certificate)); + } else { + weak_ptr->OnCertificateRequestFailed(); + } + }; + + rtc::KeyParams key_params = rtc::KeyParams(); + RTC_LOG(LS_VERBOSE) + << "DTLS-SRTP enabled; sending DTLS identity request (key type: " + << key_params.type() << ")."; + + // Request certificate. This happens asynchronously on a different thread. + cert_generator_->GenerateCertificateAsync(key_params, absl::nullopt, + std::move(callback)); + } +} + +WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() { + RTC_DCHECK_RUN_ON(signaling_thread_); + + // Fail any requests that were asked for before identity generation completed. + FailPendingRequests(kFailedDueToSessionShutdown); + + // Process all pending notifications. If we don't do this, requests will + // linger and not know they succeeded or failed. + // All tasks that suppose to run them are protected with weak_factory_ and + // will be cancelled. If we don't protect them, they might trigger after peer + // connection is destroyed, which might be surprising. + while (!callbacks_.empty()) { + std::move(callbacks_.front())(); + callbacks_.pop(); + } +} + +void WebRtcSessionDescriptionFactory::CreateOffer( + CreateSessionDescriptionObserver* observer, + const PeerConnectionInterface::RTCOfferAnswerOptions& options, + const cricket::MediaSessionOptions& session_options) { + RTC_DCHECK_RUN_ON(signaling_thread_); + std::string error = "CreateOffer"; + if (certificate_request_state_ == CERTIFICATE_FAILED) { + error += kFailedDueToIdentityFailed; + RTC_LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailed(observer, error); + return; + } + + if (!ValidMediaSessionOptions(session_options)) { + error += " called with invalid session options"; + RTC_LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailed(observer, error); + return; + } + + CreateSessionDescriptionRequest request( + CreateSessionDescriptionRequest::kOffer, observer, session_options); + if (certificate_request_state_ == CERTIFICATE_WAITING) { + create_session_description_requests_.push(request); + } else { + RTC_DCHECK(certificate_request_state_ == CERTIFICATE_SUCCEEDED || + certificate_request_state_ == CERTIFICATE_NOT_NEEDED); + InternalCreateOffer(request); + } +} + +void WebRtcSessionDescriptionFactory::CreateAnswer( + CreateSessionDescriptionObserver* observer, + const cricket::MediaSessionOptions& session_options) { + std::string error = "CreateAnswer"; + if (certificate_request_state_ == CERTIFICATE_FAILED) { + error += kFailedDueToIdentityFailed; + RTC_LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailed(observer, error); + return; + } + if (!sdp_info_->remote_description()) { + error += " can't be called before SetRemoteDescription."; + RTC_LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailed(observer, error); + return; + } + if (sdp_info_->remote_description()->GetType() != SdpType::kOffer) { + error += " failed because remote_description is not an offer."; + RTC_LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailed(observer, error); + return; + } + + if (!ValidMediaSessionOptions(session_options)) { + error += " called with invalid session options."; + RTC_LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailed(observer, error); + return; + } + + CreateSessionDescriptionRequest request( + CreateSessionDescriptionRequest::kAnswer, observer, session_options); + if (certificate_request_state_ == CERTIFICATE_WAITING) { + create_session_description_requests_.push(request); + } else { + RTC_DCHECK(certificate_request_state_ == CERTIFICATE_SUCCEEDED || + certificate_request_state_ == CERTIFICATE_NOT_NEEDED); + InternalCreateAnswer(request); + } +} + +void WebRtcSessionDescriptionFactory::SetSdesPolicy( + cricket::SecurePolicy secure_policy) { + session_desc_factory_.set_secure(secure_policy); +} + +cricket::SecurePolicy WebRtcSessionDescriptionFactory::SdesPolicy() const { + return session_desc_factory_.secure(); +} + +void WebRtcSessionDescriptionFactory::InternalCreateOffer( + CreateSessionDescriptionRequest request) { + if (sdp_info_->local_description()) { + // If the needs-ice-restart flag is set as described by JSEP, we should + // generate an offer with a new ufrag/password to trigger an ICE restart. + for (cricket::MediaDescriptionOptions& options : + request.options.media_description_options) { + if (sdp_info_->NeedsIceRestart(options.mid)) { + options.transport_options.ice_restart = true; + } + } + } + + std::unique_ptr<cricket::SessionDescription> desc = + session_desc_factory_.CreateOffer( + request.options, sdp_info_->local_description() + ? sdp_info_->local_description()->description() + : nullptr); + if (!desc) { + PostCreateSessionDescriptionFailed(request.observer.get(), + "Failed to initialize the offer."); + return; + } + + // RFC 3264 + // When issuing an offer that modifies the session, + // the "o=" line of the new SDP MUST be identical to that in the + // previous SDP, except that the version in the origin field MUST + // increment by one from the previous SDP. + + // Just increase the version number by one each time when a new offer + // is created regardless if it's identical to the previous one or not. + // The `session_version_` is a uint64_t, the wrap around should not happen. + RTC_DCHECK(session_version_ + 1 > session_version_); + auto offer = std::make_unique<JsepSessionDescription>( + SdpType::kOffer, std::move(desc), session_id_, + rtc::ToString(session_version_++)); + if (sdp_info_->local_description()) { + for (const cricket::MediaDescriptionOptions& options : + request.options.media_description_options) { + if (!options.transport_options.ice_restart) { + CopyCandidatesFromSessionDescription(sdp_info_->local_description(), + options.mid, offer.get()); + } + } + } + PostCreateSessionDescriptionSucceeded(request.observer.get(), + std::move(offer)); +} + +void WebRtcSessionDescriptionFactory::InternalCreateAnswer( + CreateSessionDescriptionRequest request) { + if (sdp_info_->remote_description()) { + for (cricket::MediaDescriptionOptions& options : + request.options.media_description_options) { + // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1 + // an answer should also contain new ICE ufrag and password if an offer + // has been received with new ufrag and password. + options.transport_options.ice_restart = + sdp_info_->IceRestartPending(options.mid); + // We should pass the current DTLS role to the transport description + // factory, if there is already an existing ongoing session. + absl::optional<rtc::SSLRole> dtls_role = + sdp_info_->GetDtlsRole(options.mid); + if (dtls_role) { + options.transport_options.prefer_passive_role = + (rtc::SSL_SERVER == *dtls_role); + } + } + } + + std::unique_ptr<cricket::SessionDescription> desc = + session_desc_factory_.CreateAnswer( + sdp_info_->remote_description() + ? sdp_info_->remote_description()->description() + : nullptr, + request.options, + sdp_info_->local_description() + ? sdp_info_->local_description()->description() + : nullptr); + if (!desc) { + PostCreateSessionDescriptionFailed(request.observer.get(), + "Failed to initialize the answer."); + return; + } + + // RFC 3264 + // If the answer is different from the offer in any way (different IP + // addresses, ports, etc.), the origin line MUST be different in the answer. + // In that case, the version number in the "o=" line of the answer is + // unrelated to the version number in the o line of the offer. + // Get a new version number by increasing the `session_version_answer_`. + // The `session_version_` is a uint64_t, the wrap around should not happen. + RTC_DCHECK(session_version_ + 1 > session_version_); + auto answer = std::make_unique<JsepSessionDescription>( + SdpType::kAnswer, std::move(desc), session_id_, + rtc::ToString(session_version_++)); + if (sdp_info_->local_description()) { + // Include all local ICE candidates in the SessionDescription unless + // the remote peer has requested an ICE restart. + for (const cricket::MediaDescriptionOptions& options : + request.options.media_description_options) { + if (!options.transport_options.ice_restart) { + CopyCandidatesFromSessionDescription(sdp_info_->local_description(), + options.mid, answer.get()); + } + } + } + PostCreateSessionDescriptionSucceeded(request.observer.get(), + std::move(answer)); +} + +void WebRtcSessionDescriptionFactory::FailPendingRequests( + const std::string& reason) { + RTC_DCHECK_RUN_ON(signaling_thread_); + while (!create_session_description_requests_.empty()) { + const CreateSessionDescriptionRequest& request = + create_session_description_requests_.front(); + PostCreateSessionDescriptionFailed( + request.observer.get(), + ((request.type == CreateSessionDescriptionRequest::kOffer) + ? "CreateOffer" + : "CreateAnswer") + + reason); + create_session_description_requests_.pop(); + } +} + +void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed( + CreateSessionDescriptionObserver* observer, + const std::string& error) { + Post([observer = + rtc::scoped_refptr<CreateSessionDescriptionObserver>(observer), + error]() mutable { + observer->OnFailure( + RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error))); + }); + RTC_LOG(LS_ERROR) << "Create SDP failed: " << error; +} + +void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionSucceeded( + CreateSessionDescriptionObserver* observer, + std::unique_ptr<SessionDescriptionInterface> description) { + Post([observer = + rtc::scoped_refptr<CreateSessionDescriptionObserver>(observer), + description = std::move(description)]() mutable { + observer->OnSuccess(description.release()); + }); +} + +void WebRtcSessionDescriptionFactory::Post( + absl::AnyInvocable<void() &&> callback) { + RTC_DCHECK_RUN_ON(signaling_thread_); + callbacks_.push(std::move(callback)); + signaling_thread_->PostTask([weak_ptr = weak_factory_.GetWeakPtr()] { + if (weak_ptr) { + auto& callbacks = weak_ptr->callbacks_; + // Callbacks are pushed from the same thread, thus this task should + // corresond to the first entry in the queue. + RTC_DCHECK(!callbacks.empty()); + std::move(callbacks.front())(); + callbacks.pop(); + } + }); +} + +void WebRtcSessionDescriptionFactory::OnCertificateRequestFailed() { + RTC_DCHECK_RUN_ON(signaling_thread_); + + RTC_LOG(LS_ERROR) << "Asynchronous certificate generation request failed."; + certificate_request_state_ = CERTIFICATE_FAILED; + + FailPendingRequests(kFailedDueToIdentityFailed); +} + +void WebRtcSessionDescriptionFactory::SetCertificate( + rtc::scoped_refptr<rtc::RTCCertificate> certificate) { + RTC_DCHECK(certificate); + RTC_LOG(LS_VERBOSE) << "Setting new certificate."; + + certificate_request_state_ = CERTIFICATE_SUCCEEDED; + + on_certificate_ready_(certificate); + + transport_desc_factory_.set_certificate(std::move(certificate)); + transport_desc_factory_.set_secure(cricket::SEC_ENABLED); + + while (!create_session_description_requests_.empty()) { + if (create_session_description_requests_.front().type == + CreateSessionDescriptionRequest::kOffer) { + InternalCreateOffer(create_session_description_requests_.front()); + } else { + InternalCreateAnswer(create_session_description_requests_.front()); + } + create_session_description_requests_.pop(); + } +} +} // namespace webrtc |