|  | /* | 
|  | *  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 { | 
|  | const char kFailedDueToIdentityFailed[] = | 
|  | " failed because DTLS identity request failed"; | 
|  | const char kFailedDueToSessionShutdown[] = | 
|  | " failed because the session was shut down"; | 
|  |  | 
|  | const uint64_t kInitSessionVersion = 2; | 
|  |  | 
|  | // Check that each sender has a unique ID. | 
|  | 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 |