xref: /aosp_15_r20/external/webrtc/pc/webrtc_session_description_factory.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright 2013 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "pc/webrtc_session_description_factory.h"
12 
13 #include <stddef.h>
14 
15 #include <queue>
16 #include <string>
17 #include <type_traits>
18 #include <utility>
19 #include <vector>
20 
21 #include "absl/algorithm/container.h"
22 #include "absl/types/optional.h"
23 #include "api/jsep.h"
24 #include "api/jsep_session_description.h"
25 #include "api/rtc_error.h"
26 #include "api/sequence_checker.h"
27 #include "pc/connection_context.h"
28 #include "pc/sdp_state_provider.h"
29 #include "pc/session_description.h"
30 #include "rtc_base/checks.h"
31 #include "rtc_base/logging.h"
32 #include "rtc_base/ssl_identity.h"
33 #include "rtc_base/ssl_stream_adapter.h"
34 #include "rtc_base/string_encode.h"
35 #include "rtc_base/unique_id_generator.h"
36 
37 using cricket::MediaSessionOptions;
38 using rtc::UniqueRandomIdGenerator;
39 
40 namespace webrtc {
41 namespace {
42 static const char kFailedDueToIdentityFailed[] =
43     " failed because DTLS identity request failed";
44 static const char kFailedDueToSessionShutdown[] =
45     " failed because the session was shut down";
46 
47 static const uint64_t kInitSessionVersion = 2;
48 
49 // Check that each sender has a unique ID.
ValidMediaSessionOptions(const cricket::MediaSessionOptions & session_options)50 static bool ValidMediaSessionOptions(
51     const cricket::MediaSessionOptions& session_options) {
52   std::vector<cricket::SenderOptions> sorted_senders;
53   for (const cricket::MediaDescriptionOptions& media_description_options :
54        session_options.media_description_options) {
55     sorted_senders.insert(sorted_senders.end(),
56                           media_description_options.sender_options.begin(),
57                           media_description_options.sender_options.end());
58   }
59   absl::c_sort(sorted_senders, [](const cricket::SenderOptions& sender1,
60                                   const cricket::SenderOptions& sender2) {
61     return sender1.track_id < sender2.track_id;
62   });
63   return absl::c_adjacent_find(sorted_senders,
64                                [](const cricket::SenderOptions& sender1,
65                                   const cricket::SenderOptions& sender2) {
66                                  return sender1.track_id == sender2.track_id;
67                                }) == sorted_senders.end();
68 }
69 }  // namespace
70 
71 // static
CopyCandidatesFromSessionDescription(const SessionDescriptionInterface * source_desc,const std::string & content_name,SessionDescriptionInterface * dest_desc)72 void WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
73     const SessionDescriptionInterface* source_desc,
74     const std::string& content_name,
75     SessionDescriptionInterface* dest_desc) {
76   if (!source_desc) {
77     return;
78   }
79   const cricket::ContentInfos& contents =
80       source_desc->description()->contents();
81   const cricket::ContentInfo* cinfo =
82       source_desc->description()->GetContentByName(content_name);
83   if (!cinfo) {
84     return;
85   }
86   size_t mediasection_index = static_cast<int>(cinfo - &contents[0]);
87   const IceCandidateCollection* source_candidates =
88       source_desc->candidates(mediasection_index);
89   const IceCandidateCollection* dest_candidates =
90       dest_desc->candidates(mediasection_index);
91   if (!source_candidates || !dest_candidates) {
92     return;
93   }
94   for (size_t n = 0; n < source_candidates->count(); ++n) {
95     const IceCandidateInterface* new_candidate = source_candidates->at(n);
96     if (!dest_candidates->HasCandidate(new_candidate)) {
97       dest_desc->AddCandidate(source_candidates->at(n));
98     }
99   }
100 }
101 
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)102 WebRtcSessionDescriptionFactory::WebRtcSessionDescriptionFactory(
103     ConnectionContext* context,
104     const SdpStateProvider* sdp_info,
105     const std::string& session_id,
106     bool dtls_enabled,
107     std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator,
108     rtc::scoped_refptr<rtc::RTCCertificate> certificate,
109     std::function<void(const rtc::scoped_refptr<rtc::RTCCertificate>&)>
110         on_certificate_ready,
111     const FieldTrialsView& field_trials)
112     : signaling_thread_(context->signaling_thread()),
113       transport_desc_factory_(field_trials),
114       session_desc_factory_(context->media_engine(),
115                             context->use_rtx(),
116                             context->ssrc_generator(),
117                             &transport_desc_factory_),
118       // RFC 4566 suggested a Network Time Protocol (NTP) format timestamp
119       // as the session id and session version. To simplify, it should be fine
120       // to just use a random number as session id and start version from
121       // `kInitSessionVersion`.
122       session_version_(kInitSessionVersion),
123       cert_generator_(dtls_enabled ? std::move(cert_generator) : nullptr),
124       sdp_info_(sdp_info),
125       session_id_(session_id),
126       certificate_request_state_(CERTIFICATE_NOT_NEEDED),
127       on_certificate_ready_(on_certificate_ready) {
128   RTC_DCHECK(signaling_thread_);
129 
130   if (!dtls_enabled) {
131     SetSdesPolicy(cricket::SEC_REQUIRED);
132     RTC_LOG(LS_VERBOSE) << "DTLS-SRTP disabled.";
133     return;
134   }
135 
136   // SRTP-SDES is disabled if DTLS is on.
137   SetSdesPolicy(cricket::SEC_DISABLED);
138   if (certificate) {
139     // Use `certificate`.
140     certificate_request_state_ = CERTIFICATE_WAITING;
141 
142     RTC_LOG(LS_VERBOSE) << "DTLS-SRTP enabled; has certificate parameter.";
143     RTC_LOG(LS_INFO) << "Using certificate supplied to the constructor.";
144     SetCertificate(certificate);
145   } else {
146     // Generate certificate.
147     certificate_request_state_ = CERTIFICATE_WAITING;
148 
149     auto callback = [weak_ptr = weak_factory_.GetWeakPtr()](
150                         rtc::scoped_refptr<rtc::RTCCertificate> certificate) {
151       if (!weak_ptr) {
152         return;
153       }
154       if (certificate) {
155         weak_ptr->SetCertificate(std::move(certificate));
156       } else {
157         weak_ptr->OnCertificateRequestFailed();
158       }
159     };
160 
161     rtc::KeyParams key_params = rtc::KeyParams();
162     RTC_LOG(LS_VERBOSE)
163         << "DTLS-SRTP enabled; sending DTLS identity request (key type: "
164         << key_params.type() << ").";
165 
166     // Request certificate. This happens asynchronously on a different thread.
167     cert_generator_->GenerateCertificateAsync(key_params, absl::nullopt,
168                                               std::move(callback));
169   }
170 }
171 
~WebRtcSessionDescriptionFactory()172 WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
173   RTC_DCHECK_RUN_ON(signaling_thread_);
174 
175   // Fail any requests that were asked for before identity generation completed.
176   FailPendingRequests(kFailedDueToSessionShutdown);
177 
178   // Process all pending notifications. If we don't do this, requests will
179   // linger and not know they succeeded or failed.
180   // All tasks that suppose to run them are protected with weak_factory_ and
181   // will be cancelled. If we don't protect them, they might trigger after peer
182   // connection is destroyed, which might be surprising.
183   while (!callbacks_.empty()) {
184     std::move(callbacks_.front())();
185     callbacks_.pop();
186   }
187 }
188 
CreateOffer(CreateSessionDescriptionObserver * observer,const PeerConnectionInterface::RTCOfferAnswerOptions & options,const cricket::MediaSessionOptions & session_options)189 void WebRtcSessionDescriptionFactory::CreateOffer(
190     CreateSessionDescriptionObserver* observer,
191     const PeerConnectionInterface::RTCOfferAnswerOptions& options,
192     const cricket::MediaSessionOptions& session_options) {
193   RTC_DCHECK_RUN_ON(signaling_thread_);
194   std::string error = "CreateOffer";
195   if (certificate_request_state_ == CERTIFICATE_FAILED) {
196     error += kFailedDueToIdentityFailed;
197     RTC_LOG(LS_ERROR) << error;
198     PostCreateSessionDescriptionFailed(observer, error);
199     return;
200   }
201 
202   if (!ValidMediaSessionOptions(session_options)) {
203     error += " called with invalid session options";
204     RTC_LOG(LS_ERROR) << error;
205     PostCreateSessionDescriptionFailed(observer, error);
206     return;
207   }
208 
209   CreateSessionDescriptionRequest request(
210       CreateSessionDescriptionRequest::kOffer, observer, session_options);
211   if (certificate_request_state_ == CERTIFICATE_WAITING) {
212     create_session_description_requests_.push(request);
213   } else {
214     RTC_DCHECK(certificate_request_state_ == CERTIFICATE_SUCCEEDED ||
215                certificate_request_state_ == CERTIFICATE_NOT_NEEDED);
216     InternalCreateOffer(request);
217   }
218 }
219 
CreateAnswer(CreateSessionDescriptionObserver * observer,const cricket::MediaSessionOptions & session_options)220 void WebRtcSessionDescriptionFactory::CreateAnswer(
221     CreateSessionDescriptionObserver* observer,
222     const cricket::MediaSessionOptions& session_options) {
223   std::string error = "CreateAnswer";
224   if (certificate_request_state_ == CERTIFICATE_FAILED) {
225     error += kFailedDueToIdentityFailed;
226     RTC_LOG(LS_ERROR) << error;
227     PostCreateSessionDescriptionFailed(observer, error);
228     return;
229   }
230   if (!sdp_info_->remote_description()) {
231     error += " can't be called before SetRemoteDescription.";
232     RTC_LOG(LS_ERROR) << error;
233     PostCreateSessionDescriptionFailed(observer, error);
234     return;
235   }
236   if (sdp_info_->remote_description()->GetType() != SdpType::kOffer) {
237     error += " failed because remote_description is not an offer.";
238     RTC_LOG(LS_ERROR) << error;
239     PostCreateSessionDescriptionFailed(observer, error);
240     return;
241   }
242 
243   if (!ValidMediaSessionOptions(session_options)) {
244     error += " called with invalid session options.";
245     RTC_LOG(LS_ERROR) << error;
246     PostCreateSessionDescriptionFailed(observer, error);
247     return;
248   }
249 
250   CreateSessionDescriptionRequest request(
251       CreateSessionDescriptionRequest::kAnswer, observer, session_options);
252   if (certificate_request_state_ == CERTIFICATE_WAITING) {
253     create_session_description_requests_.push(request);
254   } else {
255     RTC_DCHECK(certificate_request_state_ == CERTIFICATE_SUCCEEDED ||
256                certificate_request_state_ == CERTIFICATE_NOT_NEEDED);
257     InternalCreateAnswer(request);
258   }
259 }
260 
SetSdesPolicy(cricket::SecurePolicy secure_policy)261 void WebRtcSessionDescriptionFactory::SetSdesPolicy(
262     cricket::SecurePolicy secure_policy) {
263   session_desc_factory_.set_secure(secure_policy);
264 }
265 
SdesPolicy() const266 cricket::SecurePolicy WebRtcSessionDescriptionFactory::SdesPolicy() const {
267   return session_desc_factory_.secure();
268 }
269 
InternalCreateOffer(CreateSessionDescriptionRequest request)270 void WebRtcSessionDescriptionFactory::InternalCreateOffer(
271     CreateSessionDescriptionRequest request) {
272   if (sdp_info_->local_description()) {
273     // If the needs-ice-restart flag is set as described by JSEP, we should
274     // generate an offer with a new ufrag/password to trigger an ICE restart.
275     for (cricket::MediaDescriptionOptions& options :
276          request.options.media_description_options) {
277       if (sdp_info_->NeedsIceRestart(options.mid)) {
278         options.transport_options.ice_restart = true;
279       }
280     }
281   }
282 
283   std::unique_ptr<cricket::SessionDescription> desc =
284       session_desc_factory_.CreateOffer(
285           request.options, sdp_info_->local_description()
286                                ? sdp_info_->local_description()->description()
287                                : nullptr);
288   if (!desc) {
289     PostCreateSessionDescriptionFailed(request.observer.get(),
290                                        "Failed to initialize the offer.");
291     return;
292   }
293 
294   // RFC 3264
295   // When issuing an offer that modifies the session,
296   // the "o=" line of the new SDP MUST be identical to that in the
297   // previous SDP, except that the version in the origin field MUST
298   // increment by one from the previous SDP.
299 
300   // Just increase the version number by one each time when a new offer
301   // is created regardless if it's identical to the previous one or not.
302   // The `session_version_` is a uint64_t, the wrap around should not happen.
303   RTC_DCHECK(session_version_ + 1 > session_version_);
304   auto offer = std::make_unique<JsepSessionDescription>(
305       SdpType::kOffer, std::move(desc), session_id_,
306       rtc::ToString(session_version_++));
307   if (sdp_info_->local_description()) {
308     for (const cricket::MediaDescriptionOptions& options :
309          request.options.media_description_options) {
310       if (!options.transport_options.ice_restart) {
311         CopyCandidatesFromSessionDescription(sdp_info_->local_description(),
312                                              options.mid, offer.get());
313       }
314     }
315   }
316   PostCreateSessionDescriptionSucceeded(request.observer.get(),
317                                         std::move(offer));
318 }
319 
InternalCreateAnswer(CreateSessionDescriptionRequest request)320 void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
321     CreateSessionDescriptionRequest request) {
322   if (sdp_info_->remote_description()) {
323     for (cricket::MediaDescriptionOptions& options :
324          request.options.media_description_options) {
325       // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
326       // an answer should also contain new ICE ufrag and password if an offer
327       // has been received with new ufrag and password.
328       options.transport_options.ice_restart =
329           sdp_info_->IceRestartPending(options.mid);
330       // We should pass the current DTLS role to the transport description
331       // factory, if there is already an existing ongoing session.
332       absl::optional<rtc::SSLRole> dtls_role =
333           sdp_info_->GetDtlsRole(options.mid);
334       if (dtls_role) {
335         options.transport_options.prefer_passive_role =
336             (rtc::SSL_SERVER == *dtls_role);
337       }
338     }
339   }
340 
341   std::unique_ptr<cricket::SessionDescription> desc =
342       session_desc_factory_.CreateAnswer(
343           sdp_info_->remote_description()
344               ? sdp_info_->remote_description()->description()
345               : nullptr,
346           request.options,
347           sdp_info_->local_description()
348               ? sdp_info_->local_description()->description()
349               : nullptr);
350   if (!desc) {
351     PostCreateSessionDescriptionFailed(request.observer.get(),
352                                        "Failed to initialize the answer.");
353     return;
354   }
355 
356   // RFC 3264
357   // If the answer is different from the offer in any way (different IP
358   // addresses, ports, etc.), the origin line MUST be different in the answer.
359   // In that case, the version number in the "o=" line of the answer is
360   // unrelated to the version number in the o line of the offer.
361   // Get a new version number by increasing the `session_version_answer_`.
362   // The `session_version_` is a uint64_t, the wrap around should not happen.
363   RTC_DCHECK(session_version_ + 1 > session_version_);
364   auto answer = std::make_unique<JsepSessionDescription>(
365       SdpType::kAnswer, std::move(desc), session_id_,
366       rtc::ToString(session_version_++));
367   if (sdp_info_->local_description()) {
368     // Include all local ICE candidates in the SessionDescription unless
369     // the remote peer has requested an ICE restart.
370     for (const cricket::MediaDescriptionOptions& options :
371          request.options.media_description_options) {
372       if (!options.transport_options.ice_restart) {
373         CopyCandidatesFromSessionDescription(sdp_info_->local_description(),
374                                              options.mid, answer.get());
375       }
376     }
377   }
378   PostCreateSessionDescriptionSucceeded(request.observer.get(),
379                                         std::move(answer));
380 }
381 
FailPendingRequests(const std::string & reason)382 void WebRtcSessionDescriptionFactory::FailPendingRequests(
383     const std::string& reason) {
384   RTC_DCHECK_RUN_ON(signaling_thread_);
385   while (!create_session_description_requests_.empty()) {
386     const CreateSessionDescriptionRequest& request =
387         create_session_description_requests_.front();
388     PostCreateSessionDescriptionFailed(
389         request.observer.get(),
390         ((request.type == CreateSessionDescriptionRequest::kOffer)
391              ? "CreateOffer"
392              : "CreateAnswer") +
393             reason);
394     create_session_description_requests_.pop();
395   }
396 }
397 
PostCreateSessionDescriptionFailed(CreateSessionDescriptionObserver * observer,const std::string & error)398 void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed(
399     CreateSessionDescriptionObserver* observer,
400     const std::string& error) {
401   Post([observer =
402             rtc::scoped_refptr<CreateSessionDescriptionObserver>(observer),
403         error]() mutable {
404     observer->OnFailure(
405         RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error)));
406   });
407   RTC_LOG(LS_ERROR) << "Create SDP failed: " << error;
408 }
409 
PostCreateSessionDescriptionSucceeded(CreateSessionDescriptionObserver * observer,std::unique_ptr<SessionDescriptionInterface> description)410 void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionSucceeded(
411     CreateSessionDescriptionObserver* observer,
412     std::unique_ptr<SessionDescriptionInterface> description) {
413   Post([observer =
414             rtc::scoped_refptr<CreateSessionDescriptionObserver>(observer),
415         description = std::move(description)]() mutable {
416     observer->OnSuccess(description.release());
417   });
418 }
419 
Post(absl::AnyInvocable<void ()&&> callback)420 void WebRtcSessionDescriptionFactory::Post(
421     absl::AnyInvocable<void() &&> callback) {
422   RTC_DCHECK_RUN_ON(signaling_thread_);
423   callbacks_.push(std::move(callback));
424   signaling_thread_->PostTask([weak_ptr = weak_factory_.GetWeakPtr()] {
425     if (weak_ptr) {
426       auto& callbacks = weak_ptr->callbacks_;
427       // Callbacks are pushed from the same thread, thus this task should
428       // corresond to the first entry in the queue.
429       RTC_DCHECK(!callbacks.empty());
430       std::move(callbacks.front())();
431       callbacks.pop();
432     }
433   });
434 }
435 
OnCertificateRequestFailed()436 void WebRtcSessionDescriptionFactory::OnCertificateRequestFailed() {
437   RTC_DCHECK_RUN_ON(signaling_thread_);
438 
439   RTC_LOG(LS_ERROR) << "Asynchronous certificate generation request failed.";
440   certificate_request_state_ = CERTIFICATE_FAILED;
441 
442   FailPendingRequests(kFailedDueToIdentityFailed);
443 }
444 
SetCertificate(rtc::scoped_refptr<rtc::RTCCertificate> certificate)445 void WebRtcSessionDescriptionFactory::SetCertificate(
446     rtc::scoped_refptr<rtc::RTCCertificate> certificate) {
447   RTC_DCHECK(certificate);
448   RTC_LOG(LS_VERBOSE) << "Setting new certificate.";
449 
450   certificate_request_state_ = CERTIFICATE_SUCCEEDED;
451 
452   on_certificate_ready_(certificate);
453 
454   transport_desc_factory_.set_certificate(std::move(certificate));
455   transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
456 
457   while (!create_session_description_requests_.empty()) {
458     if (create_session_description_requests_.front().type ==
459         CreateSessionDescriptionRequest::kOffer) {
460       InternalCreateOffer(create_session_description_requests_.front());
461     } else {
462       InternalCreateAnswer(create_session_description_requests_.front());
463     }
464     create_session_description_requests_.pop();
465   }
466 }
467 }  // namespace webrtc
468