| /* | 
 |  *  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 <cstdint> | 
 | #include <functional> | 
 | #include <memory> | 
 | #include <optional> | 
 | #include <queue> | 
 | #include <string> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "absl/algorithm/container.h" | 
 | #include "absl/functional/any_invocable.h" | 
 | #include "absl/strings/str_cat.h" | 
 | #include "api/field_trials_view.h" | 
 | #include "api/jsep.h" | 
 | #include "api/jsep_session_description.h" | 
 | #include "api/peer_connection_interface.h" | 
 | #include "api/rtc_error.h" | 
 | #include "api/scoped_refptr.h" | 
 | #include "api/sequence_checker.h" | 
 | #include "pc/codec_vendor.h" | 
 | #include "pc/connection_context.h" | 
 | #include "pc/media_options.h" | 
 | #include "pc/media_session.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/rtc_certificate.h" | 
 | #include "rtc_base/rtc_certificate_generator.h" | 
 | #include "rtc_base/ssl_identity.h" | 
 | #include "rtc_base/ssl_stream_adapter.h" | 
 |  | 
 | 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 MediaSessionOptions& session_options) { | 
 |   std::vector<SenderOptions> sorted_senders; | 
 |   for (const 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 SenderOptions& sender1, const SenderOptions& sender2) { | 
 |                  return sender1.track_id < sender2.track_id; | 
 |                }); | 
 |   return absl::c_adjacent_find( | 
 |              sorted_senders, | 
 |              [](const SenderOptions& sender1, const 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 ContentInfos& contents = source_desc->description()->contents(); | 
 |   const 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 IceCandidate* 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<RTCCertificateGeneratorInterface> cert_generator, | 
 |     scoped_refptr<RTCCertificate> certificate, | 
 |     std::function<void(const scoped_refptr<RTCCertificate>&)> | 
 |         on_certificate_ready, | 
 |     CodecLookupHelper* codec_lookup_helper, | 
 |     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_, | 
 |                             codec_lookup_helper), | 
 |       // 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) { | 
 |     RTC_LOG(LS_INFO) << "DTLS-SRTP disabled"; | 
 |     transport_desc_factory_.SetInsecureForTesting(); | 
 |     return; | 
 |   } | 
 |   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); | 
 |     return; | 
 |   } | 
 |   // Generate certificate. | 
 |   RTC_DCHECK(cert_generator_); | 
 |   certificate_request_state_ = CERTIFICATE_WAITING; | 
 |  | 
 |   auto callback = [weak_ptr = weak_factory_.GetWeakPtr()]( | 
 |                       scoped_refptr<RTCCertificate> certificate) { | 
 |     if (!weak_ptr) { | 
 |       return; | 
 |     } | 
 |     if (certificate) { | 
 |       weak_ptr->SetCertificate(std::move(certificate)); | 
 |     } else { | 
 |       weak_ptr->OnCertificateRequestFailed(); | 
 |     } | 
 |   }; | 
 |  | 
 |   KeyParams key_params = 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, std::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 MediaSessionOptions& session_options) { | 
 |   RTC_DCHECK_RUN_ON(signaling_thread_); | 
 |   std::string error = "CreateOffer"; | 
 |   if (certificate_request_state_ == CERTIFICATE_FAILED) { | 
 |     error += kFailedDueToIdentityFailed; | 
 |     PostCreateSessionDescriptionFailed( | 
 |         observer, RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error))); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!ValidMediaSessionOptions(session_options)) { | 
 |     error += " called with invalid session options"; | 
 |     PostCreateSessionDescriptionFailed( | 
 |         observer, RTCError(RTCErrorType::INTERNAL_ERROR, std::move(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 MediaSessionOptions& session_options) { | 
 |   std::string error = "CreateAnswer"; | 
 |   if (certificate_request_state_ == CERTIFICATE_FAILED) { | 
 |     error += kFailedDueToIdentityFailed; | 
 |     PostCreateSessionDescriptionFailed( | 
 |         observer, RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error))); | 
 |     return; | 
 |   } | 
 |   if (!sdp_info_->remote_description()) { | 
 |     error += " can't be called before SetRemoteDescription."; | 
 |     PostCreateSessionDescriptionFailed( | 
 |         observer, RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error))); | 
 |     return; | 
 |   } | 
 |   if (sdp_info_->remote_description()->GetType() != SdpType::kOffer) { | 
 |     error += " failed because remote_description is not an offer."; | 
 |     PostCreateSessionDescriptionFailed( | 
 |         observer, RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error))); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!ValidMediaSessionOptions(session_options)) { | 
 |     error += " called with invalid session options."; | 
 |     PostCreateSessionDescriptionFailed( | 
 |         observer, RTCError(RTCErrorType::INTERNAL_ERROR, std::move(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::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 (MediaDescriptionOptions& options : | 
 |          request.options.media_description_options) { | 
 |       if (sdp_info_->NeedsIceRestart(options.mid)) { | 
 |         options.transport_options.ice_restart = true; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   auto result = session_desc_factory_.CreateOfferOrError( | 
 |       request.options, sdp_info_->local_description() | 
 |                            ? sdp_info_->local_description()->description() | 
 |                            : nullptr); | 
 |   if (!result.ok()) { | 
 |     PostCreateSessionDescriptionFailed(request.observer.get(), result.error()); | 
 |     return; | 
 |   } | 
 |   std::unique_ptr<SessionDescription> desc = std::move(result.value()); | 
 |   RTC_CHECK(desc); | 
 |  | 
 |   // 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_, | 
 |       absl::StrCat(session_version_++)); | 
 |   if (sdp_info_->local_description()) { | 
 |     for (const 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 (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. | 
 |       std::optional<SSLRole> dtls_role = sdp_info_->GetDtlsRole(options.mid); | 
 |       if (dtls_role) { | 
 |         options.transport_options.prefer_passive_role = | 
 |             (SSL_SERVER == *dtls_role); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   auto result = session_desc_factory_.CreateAnswerOrError( | 
 |       sdp_info_->remote_description() | 
 |           ? sdp_info_->remote_description()->description() | 
 |           : nullptr, | 
 |       request.options, | 
 |       sdp_info_->local_description() | 
 |           ? sdp_info_->local_description()->description() | 
 |           : nullptr); | 
 |   if (!result.ok()) { | 
 |     PostCreateSessionDescriptionFailed(request.observer.get(), result.error()); | 
 |     return; | 
 |   } | 
 |   std::unique_ptr<SessionDescription> desc = std::move(result.value()); | 
 |   RTC_CHECK(desc); | 
 |  | 
 |   // 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_, | 
 |       absl::StrCat(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 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(), | 
 |         RTCError(RTCErrorType::INTERNAL_ERROR, | 
 |                  ((request.type == CreateSessionDescriptionRequest::kOffer) | 
 |                       ? "CreateOffer" | 
 |                       : "CreateAnswer") + | 
 |                      reason)); | 
 |     create_session_description_requests_.pop(); | 
 |   } | 
 | } | 
 |  | 
 | void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed( | 
 |     CreateSessionDescriptionObserver* observer, | 
 |     RTCError error) { | 
 |   Post([observer = scoped_refptr<CreateSessionDescriptionObserver>(observer), | 
 |         error]() mutable { observer->OnFailure(error); }); | 
 |   RTC_LOG(LS_ERROR) << "CreateSessionDescription failed: " << error.message(); | 
 | } | 
 |  | 
 | void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionSucceeded( | 
 |     CreateSessionDescriptionObserver* observer, | 
 |     std::unique_ptr<SessionDescriptionInterface> description) { | 
 |   Post([observer = 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( | 
 |     scoped_refptr<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)); | 
 |  | 
 |   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 |