/* * 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 #include #include #include #include #include #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 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(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 cert_generator, rtc::scoped_refptr certificate, std::function&)> 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 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 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( 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 dtls_role = sdp_info_->GetDtlsRole(options.mid); if (dtls_role) { options.transport_options.prefer_passive_role = (rtc::SSL_SERVER == *dtls_role); } } } std::unique_ptr 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( 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(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 description) { Post([observer = rtc::scoped_refptr(observer), description = std::move(description)]() mutable { observer->OnSuccess(description.release()); }); } void WebRtcSessionDescriptionFactory::Post( absl::AnyInvocable 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 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