blob: 2021085aa2769187c6f8c275bf3a9df054514af0 [file] [log] [blame]
wu@webrtc.org91053e72013-08-10 07:18:04 +00001/*
2 * libjingle
3 * Copyright 2013, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/app/webrtc/webrtcsessiondescriptionfactory.h"
29
30#include "talk/app/webrtc/jsep.h"
31#include "talk/app/webrtc/jsepsessiondescription.h"
32#include "talk/app/webrtc/mediaconstraintsinterface.h"
33#include "talk/app/webrtc/mediastreamsignaling.h"
34#include "talk/app/webrtc/webrtcsession.h"
35
36namespace webrtc {
37
38namespace {
39
40static const char kFailedDueToIdentityFailed[] =
41 " failed because DTLS identity request failed";
42
43// Arbitrary constant used as common name for the identity.
44// Chosen to make the certificates more readable.
45static const char kWebRTCIdentityName[] = "WebRTC";
46
47static const uint64 kInitSessionVersion = 2;
48
49typedef cricket::MediaSessionOptions::Stream Stream;
50typedef cricket::MediaSessionOptions::Streams Streams;
51
52static bool CompareStream(const Stream& stream1, const Stream& stream2) {
53 return (stream1.id < stream2.id);
54}
55
56static bool SameId(const Stream& stream1, const Stream& stream2) {
57 return (stream1.id == stream2.id);
58}
59
60// Checks if each Stream within the |streams| has unique id.
61static bool ValidStreams(const Streams& streams) {
62 Streams sorted_streams = streams;
63 std::sort(sorted_streams.begin(), sorted_streams.end(), CompareStream);
64 Streams::iterator it =
65 std::adjacent_find(sorted_streams.begin(), sorted_streams.end(),
66 SameId);
67 return (it == sorted_streams.end());
68}
69
70enum {
71 MSG_CREATE_SESSIONDESCRIPTION_SUCCESS,
72 MSG_CREATE_SESSIONDESCRIPTION_FAILED,
73 MSG_GENERATE_IDENTITY,
74};
75
76struct CreateSessionDescriptionMsg : public talk_base::MessageData {
77 explicit CreateSessionDescriptionMsg(
78 webrtc::CreateSessionDescriptionObserver* observer)
79 : observer(observer) {
80 }
81
82 talk_base::scoped_refptr<webrtc::CreateSessionDescriptionObserver> observer;
83 std::string error;
84 talk_base::scoped_ptr<webrtc::SessionDescriptionInterface> description;
85};
86
87} // namespace
88
89// static
90void WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
91 const SessionDescriptionInterface* source_desc,
92 SessionDescriptionInterface* dest_desc) {
93 if (!source_desc)
94 return;
95 for (size_t m = 0; m < source_desc->number_of_mediasections() &&
96 m < dest_desc->number_of_mediasections(); ++m) {
97 const IceCandidateCollection* source_candidates =
98 source_desc->candidates(m);
99 const IceCandidateCollection* dest_candidates = dest_desc->candidates(m);
100 for (size_t n = 0; n < source_candidates->count(); ++n) {
101 const IceCandidateInterface* new_candidate = source_candidates->at(n);
102 if (!dest_candidates->HasCandidate(new_candidate))
103 dest_desc->AddCandidate(source_candidates->at(n));
104 }
105 }
106}
107
108WebRtcSessionDescriptionFactory::WebRtcSessionDescriptionFactory(
109 talk_base::Thread* signaling_thread,
110 cricket::ChannelManager* channel_manager,
111 MediaStreamSignaling* mediastream_signaling,
112 DTLSIdentityServiceInterface* dtls_identity_service,
113 WebRtcSession* session,
114 const std::string& session_id,
115 cricket::DataChannelType dct,
116 const MediaConstraintsInterface* constraints)
117 : signaling_thread_(signaling_thread),
118 mediastream_signaling_(mediastream_signaling),
119 session_desc_factory_(channel_manager, &transport_desc_factory_),
120 // RFC 4566 suggested a Network Time Protocol (NTP) format timestamp
121 // as the session id and session version. To simplify, it should be fine
122 // to just use a random number as session id and start version from
123 // |kInitSessionVersion|.
124 session_version_(kInitSessionVersion),
125 identity_service_(dtls_identity_service),
126 session_(session),
127 session_id_(session_id),
128 data_channel_type_(dct),
129 identity_request_state_(IDENTITY_NOT_NEEDED) {
130 transport_desc_factory_.set_protocol(cricket::ICEPROTO_HYBRID);
131 session_desc_factory_.set_add_legacy_streams(false);
132 // By default SRTP-SDES is enabled in WebRtc.
133 set_secure(cricket::SEC_REQUIRED);
134
135 // Enable DTLS-SRTP if the constraint is set.
136 bool dtls_enabled = false;
137 if (!FindConstraint(
138 constraints, MediaConstraintsInterface::kEnableDtlsSrtp,
139 &dtls_enabled, NULL) ||
140 !dtls_enabled) {
141 return;
142 }
143 // DTLS is enabled.
144 if (identity_service_.get()) {
145 identity_request_observer_ =
146 new talk_base::RefCountedObject<WebRtcIdentityRequestObserver>();
147
148 identity_request_observer_->SignalRequestFailed.connect(
149 this, &WebRtcSessionDescriptionFactory::OnIdentityRequestFailed);
150 identity_request_observer_->SignalIdentityReady.connect(
151 this, &WebRtcSessionDescriptionFactory::OnIdentityReady);
152
153 if (identity_service_->RequestIdentity(kWebRTCIdentityName,
154 kWebRTCIdentityName,
155 identity_request_observer_)) {
156 LOG(LS_VERBOSE) << "DTLS-SRTP enabled; sent DTLS identity request.";
157 identity_request_state_ = IDENTITY_WAITING;
158 } else {
159 LOG(LS_ERROR) << "Failed to send DTLS identity request.";
160 identity_request_state_ = IDENTITY_FAILED;
161 }
162 } else {
163 identity_request_state_ = IDENTITY_WAITING;
164 // Do not generate the identity in the constructor since the caller has
165 // not got a chance to connect to SignalIdentityReady.
166 signaling_thread_->Post(this, MSG_GENERATE_IDENTITY, NULL);
167 }
168}
169
170WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
171 transport_desc_factory_.set_identity(NULL);
172}
173
174void WebRtcSessionDescriptionFactory::CreateOffer(
175 CreateSessionDescriptionObserver* observer,
176 const MediaConstraintsInterface* constraints) {
177 cricket::MediaSessionOptions options;
178 std::string error = "CreateOffer";
179 if (identity_request_state_ == IDENTITY_FAILED) {
180 error += kFailedDueToIdentityFailed;
181 LOG(LS_ERROR) << error;
182 PostCreateSessionDescriptionFailed(observer, error);
183 return;
184 }
185
186 if (!mediastream_signaling_->GetOptionsForOffer(constraints, &options)) {
187 error += " called with invalid constraints.";
188 LOG(LS_ERROR) << error;
189 PostCreateSessionDescriptionFailed(observer, error);
190 return;
191 }
192
193 if (!ValidStreams(options.streams)) {
194 error += " called with invalid media streams.";
195 LOG(LS_ERROR) << error;
196 PostCreateSessionDescriptionFailed(observer, error);
197 return;
198 }
199
200 if (data_channel_type_ == cricket::DCT_SCTP) {
201 options.data_channel_type = cricket::DCT_SCTP;
202 }
203
204 CreateSessionDescriptionRequest request(
205 CreateSessionDescriptionRequest::kOffer, observer, options);
206 if (identity_request_state_ == IDENTITY_WAITING) {
207 create_session_description_requests_.push(request);
208 } else {
209 ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
210 identity_request_state_ == IDENTITY_NOT_NEEDED);
211 InternalCreateOffer(request);
212 }
213}
214
215void WebRtcSessionDescriptionFactory::CreateAnswer(
216 CreateSessionDescriptionObserver* observer,
217 const MediaConstraintsInterface* constraints) {
218 std::string error = "CreateAnswer";
219 if (identity_request_state_ == IDENTITY_FAILED) {
220 error += kFailedDueToIdentityFailed;
221 LOG(LS_ERROR) << error;
222 PostCreateSessionDescriptionFailed(observer, error);
223 return;
224 }
225 if (!session_->remote_description()) {
226 error += " can't be called before SetRemoteDescription.";
227 LOG(LS_ERROR) << error;
228 PostCreateSessionDescriptionFailed(observer, error);
229 return;
230 }
231 if (session_->remote_description()->type() !=
232 JsepSessionDescription::kOffer) {
233 error += " failed because remote_description is not an offer.";
234 LOG(LS_ERROR) << error;
235 PostCreateSessionDescriptionFailed(observer, error);
236 return;
237 }
238
239 cricket::MediaSessionOptions options;
240 if (!mediastream_signaling_->GetOptionsForAnswer(constraints, &options)) {
241 error += " called with invalid constraints.";
242 LOG(LS_ERROR) << error;
243 PostCreateSessionDescriptionFailed(observer, error);
244 return;
245 }
246 if (!ValidStreams(options.streams)) {
247 error += " called with invalid media streams.";
248 LOG(LS_ERROR) << error;
249 PostCreateSessionDescriptionFailed(observer, error);
250 return;
251 }
252 if (data_channel_type_ == cricket::DCT_SCTP) {
253 options.data_channel_type = cricket::DCT_SCTP;
254 }
255
256 CreateSessionDescriptionRequest request(
257 CreateSessionDescriptionRequest::kAnswer, observer, options);
258 if (identity_request_state_ == IDENTITY_WAITING) {
259 create_session_description_requests_.push(request);
260 } else {
261 ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
262 identity_request_state_ == IDENTITY_NOT_NEEDED);
263 InternalCreateAnswer(request);
264 }
265}
266
267void WebRtcSessionDescriptionFactory::set_secure(
268 cricket::SecureMediaPolicy secure_policy) {
269 session_desc_factory_.set_secure(secure_policy);
270}
271
272cricket::SecureMediaPolicy WebRtcSessionDescriptionFactory::secure() const {
273 return session_desc_factory_.secure();
274}
275
276bool WebRtcSessionDescriptionFactory::waiting_for_identity() const {
277 return identity_request_state_ == IDENTITY_WAITING;
278}
279
280void WebRtcSessionDescriptionFactory::OnMessage(talk_base::Message* msg) {
281 switch (msg->message_id) {
282 case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
283 CreateSessionDescriptionMsg* param =
284 static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
285 param->observer->OnSuccess(param->description.release());
286 delete param;
287 break;
288 }
289 case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
290 CreateSessionDescriptionMsg* param =
291 static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
292 param->observer->OnFailure(param->error);
293 delete param;
294 break;
295 }
296 case MSG_GENERATE_IDENTITY: {
297 LOG(LS_INFO) << "Generating identity.";
298 SetIdentity(talk_base::SSLIdentity::Generate(kWebRTCIdentityName));
299 break;
300 }
301 default:
302 ASSERT(false);
303 break;
304 }
305}
306
307void WebRtcSessionDescriptionFactory::InternalCreateOffer(
308 CreateSessionDescriptionRequest request) {
309 cricket::SessionDescription* desc(
310 session_desc_factory_.CreateOffer(
311 request.options,
312 static_cast<cricket::BaseSession*>(session_)->local_description()));
313 // RFC 3264
314 // When issuing an offer that modifies the session,
315 // the "o=" line of the new SDP MUST be identical to that in the
316 // previous SDP, except that the version in the origin field MUST
317 // increment by one from the previous SDP.
318
319 // Just increase the version number by one each time when a new offer
320 // is created regardless if it's identical to the previous one or not.
321 // The |session_version_| is a uint64, the wrap around should not happen.
322 ASSERT(session_version_ + 1 > session_version_);
323 JsepSessionDescription* offer(new JsepSessionDescription(
324 JsepSessionDescription::kOffer));
325 if (!offer->Initialize(desc, session_id_,
326 talk_base::ToString(session_version_++))) {
327 delete offer;
328 PostCreateSessionDescriptionFailed(request.observer, "CreateOffer failed.");
329 return;
330 }
331 if (session_->local_description() &&
332 !request.options.transport_options.ice_restart) {
333 // Include all local ice candidates in the SessionDescription unless
334 // the an ice restart has been requested.
335 CopyCandidatesFromSessionDescription(session_->local_description(), offer);
336 }
337 PostCreateSessionDescriptionSucceeded(request.observer, offer);
338}
339
340void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
341 CreateSessionDescriptionRequest request) {
342 // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
343 // an answer should also contain new ice ufrag and password if an offer has
344 // been received with new ufrag and password.
345 request.options.transport_options.ice_restart = session_->IceRestartPending();
346
347 cricket::SessionDescription* desc(session_desc_factory_.CreateAnswer(
348 static_cast<cricket::BaseSession*>(session_)->remote_description(),
349 request.options,
350 static_cast<cricket::BaseSession*>(session_)->local_description()));
351 // RFC 3264
352 // If the answer is different from the offer in any way (different IP
353 // addresses, ports, etc.), the origin line MUST be different in the answer.
354 // In that case, the version number in the "o=" line of the answer is
355 // unrelated to the version number in the o line of the offer.
356 // Get a new version number by increasing the |session_version_answer_|.
357 // The |session_version_| is a uint64, the wrap around should not happen.
358 ASSERT(session_version_ + 1 > session_version_);
359 JsepSessionDescription* answer(new JsepSessionDescription(
360 JsepSessionDescription::kAnswer));
361 if (!answer->Initialize(desc, session_id_,
362 talk_base::ToString(session_version_++))) {
363 delete answer;
364 PostCreateSessionDescriptionFailed(request.observer,
365 "CreateAnswer failed.");
366 return;
367 }
368 if (session_->local_description() &&
369 !request.options.transport_options.ice_restart) {
370 // Include all local ice candidates in the SessionDescription unless
371 // the remote peer has requested an ice restart.
372 CopyCandidatesFromSessionDescription(session_->local_description(), answer);
373 }
374 session_->ResetIceRestartLatch();
375 PostCreateSessionDescriptionSucceeded(request.observer, answer);
376}
377
378void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed(
379 CreateSessionDescriptionObserver* observer, const std::string& error) {
380 CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
381 msg->error = error;
382 signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
383}
384
385void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionSucceeded(
386 CreateSessionDescriptionObserver* observer,
387 SessionDescriptionInterface* description) {
388 CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
389 msg->description.reset(description);
390 signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, msg);
391}
392
393void WebRtcSessionDescriptionFactory::OnIdentityRequestFailed(int error) {
394 ASSERT(signaling_thread_->IsCurrent());
395
396 LOG(LS_ERROR) << "Async identity request failed: error = " << error;
397 identity_request_state_ = IDENTITY_FAILED;
398
399 std::string msg = kFailedDueToIdentityFailed;
400 while (!create_session_description_requests_.empty()) {
401 const CreateSessionDescriptionRequest& request =
402 create_session_description_requests_.front();
403 PostCreateSessionDescriptionFailed(
404 request.observer,
405 ((request.type == CreateSessionDescriptionRequest::kOffer) ?
406 "CreateOffer" : "CreateAnswer") + msg);
407 create_session_description_requests_.pop();
408 }
409}
410
411void WebRtcSessionDescriptionFactory::OnIdentityReady(
412 const std::string& der_cert,
413 const std::string& der_private_key) {
414 ASSERT(signaling_thread_->IsCurrent());
415 LOG(LS_VERBOSE) << "Identity is successfully generated.";
416
417 std::string pem_cert = talk_base::SSLIdentity::DerToPem(
418 talk_base::kPemTypeCertificate,
419 reinterpret_cast<const unsigned char*>(der_cert.data()),
420 der_cert.length());
421 std::string pem_key = talk_base::SSLIdentity::DerToPem(
422 talk_base::kPemTypeRsaPrivateKey,
423 reinterpret_cast<const unsigned char*>(der_private_key.data()),
424 der_private_key.length());
425
426 talk_base::SSLIdentity* identity =
427 talk_base::SSLIdentity::FromPEMStrings(pem_key, pem_cert);
428 SetIdentity(identity);
429}
430
431void WebRtcSessionDescriptionFactory::SetIdentity(
432 talk_base::SSLIdentity* identity) {
433 identity_request_state_ = IDENTITY_SUCCEEDED;
434 SignalIdentityReady(identity);
435
436 transport_desc_factory_.set_identity(identity);
437 transport_desc_factory_.set_digest_algorithm(talk_base::DIGEST_SHA_256);
438 transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
439
440 while (!create_session_description_requests_.empty()) {
441 if (create_session_description_requests_.front().type ==
442 CreateSessionDescriptionRequest::kOffer) {
443 InternalCreateOffer(create_session_description_requests_.front());
444 } else {
445 InternalCreateAnswer(create_session_description_requests_.front());
446 }
447 create_session_description_requests_.pop();
448 }
449}
450
451} // namespace webrtc