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