3 * Copyright 2013, Google Inc.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
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.
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.
28 #include "talk/app/webrtc/webrtcsessiondescriptionfactory.h"
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"
40 static const char kFailedDueToIdentityFailed[] =
41 " failed because DTLS identity request failed";
43 // Arbitrary constant used as common name for the identity.
44 // Chosen to make the certificates more readable.
45 static const char kWebRTCIdentityName[] = "WebRTC";
47 static const uint64 kInitSessionVersion = 2;
49 typedef cricket::MediaSessionOptions::Stream Stream;
50 typedef cricket::MediaSessionOptions::Streams Streams;
52 static bool CompareStream(const Stream& stream1, const Stream& stream2) {
53 return (stream1.id < stream2.id);
56 static bool SameId(const Stream& stream1, const Stream& stream2) {
57 return (stream1.id == stream2.id);
60 // Checks if each Stream within the |streams| has unique id.
61 static 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(),
67 return (it == sorted_streams.end());
71 MSG_CREATE_SESSIONDESCRIPTION_SUCCESS,
72 MSG_CREATE_SESSIONDESCRIPTION_FAILED,
73 MSG_GENERATE_IDENTITY,
76 struct CreateSessionDescriptionMsg : public talk_base::MessageData {
77 explicit CreateSessionDescriptionMsg(
78 webrtc::CreateSessionDescriptionObserver* observer)
79 : observer(observer) {
82 talk_base::scoped_refptr<webrtc::CreateSessionDescriptionObserver> observer;
84 talk_base::scoped_ptr<webrtc::SessionDescriptionInterface> description;
90 void WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
91 const SessionDescriptionInterface* source_desc,
92 SessionDescriptionInterface* dest_desc) {
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));
108 WebRtcSessionDescriptionFactory::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,
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),
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);
136 if (identity_service_.get()) {
137 identity_request_observer_ =
138 new talk_base::RefCountedObject<WebRtcIdentityRequestObserver>();
140 identity_request_observer_->SignalRequestFailed.connect(
141 this, &WebRtcSessionDescriptionFactory::OnIdentityRequestFailed);
142 identity_request_observer_->SignalIdentityReady.connect(
143 this, &WebRtcSessionDescriptionFactory::OnIdentityReady);
145 if (identity_service_->RequestIdentity(kWebRTCIdentityName,
147 identity_request_observer_)) {
148 LOG(LS_VERBOSE) << "DTLS-SRTP enabled; sent DTLS identity request.";
149 identity_request_state_ = IDENTITY_WAITING;
151 LOG(LS_ERROR) << "Failed to send DTLS identity request.";
152 identity_request_state_ = IDENTITY_FAILED;
155 identity_request_state_ = IDENTITY_WAITING;
156 // Do not generate the identity in the constructor since the caller has
157 // not got a chance to connect to SignalIdentityReady.
158 signaling_thread_->Post(this, MSG_GENERATE_IDENTITY, NULL);
163 WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
164 transport_desc_factory_.set_identity(NULL);
167 void WebRtcSessionDescriptionFactory::CreateOffer(
168 CreateSessionDescriptionObserver* observer,
169 const MediaConstraintsInterface* constraints) {
170 cricket::MediaSessionOptions options;
171 std::string error = "CreateOffer";
172 if (identity_request_state_ == IDENTITY_FAILED) {
173 error += kFailedDueToIdentityFailed;
174 LOG(LS_ERROR) << error;
175 PostCreateSessionDescriptionFailed(observer, error);
179 if (!mediastream_signaling_->GetOptionsForOffer(constraints, &options)) {
180 error += " called with invalid constraints.";
181 LOG(LS_ERROR) << error;
182 PostCreateSessionDescriptionFailed(observer, error);
186 if (!ValidStreams(options.streams)) {
187 error += " called with invalid media streams.";
188 LOG(LS_ERROR) << error;
189 PostCreateSessionDescriptionFailed(observer, error);
193 if (data_channel_type_ == cricket::DCT_SCTP &&
194 mediastream_signaling_->HasDataChannels()) {
195 options.data_channel_type = cricket::DCT_SCTP;
198 CreateSessionDescriptionRequest request(
199 CreateSessionDescriptionRequest::kOffer, observer, options);
200 if (identity_request_state_ == IDENTITY_WAITING) {
201 create_session_description_requests_.push(request);
203 ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
204 identity_request_state_ == IDENTITY_NOT_NEEDED);
205 InternalCreateOffer(request);
209 void WebRtcSessionDescriptionFactory::CreateAnswer(
210 CreateSessionDescriptionObserver* observer,
211 const MediaConstraintsInterface* constraints) {
212 std::string error = "CreateAnswer";
213 if (identity_request_state_ == IDENTITY_FAILED) {
214 error += kFailedDueToIdentityFailed;
215 LOG(LS_ERROR) << error;
216 PostCreateSessionDescriptionFailed(observer, error);
219 if (!session_->remote_description()) {
220 error += " can't be called before SetRemoteDescription.";
221 LOG(LS_ERROR) << error;
222 PostCreateSessionDescriptionFailed(observer, error);
225 if (session_->remote_description()->type() !=
226 JsepSessionDescription::kOffer) {
227 error += " failed because remote_description is not an offer.";
228 LOG(LS_ERROR) << error;
229 PostCreateSessionDescriptionFailed(observer, error);
233 cricket::MediaSessionOptions options;
234 if (!mediastream_signaling_->GetOptionsForAnswer(constraints, &options)) {
235 error += " called with invalid constraints.";
236 LOG(LS_ERROR) << error;
237 PostCreateSessionDescriptionFailed(observer, error);
240 if (!ValidStreams(options.streams)) {
241 error += " called with invalid media streams.";
242 LOG(LS_ERROR) << error;
243 PostCreateSessionDescriptionFailed(observer, error);
246 // RTP data channel is handled in MediaSessionOptions::AddStream. SCTP streams
247 // are not signaled in the SDP so does not go through that path and must be
249 if (data_channel_type_ == cricket::DCT_SCTP) {
250 options.data_channel_type = cricket::DCT_SCTP;
253 CreateSessionDescriptionRequest request(
254 CreateSessionDescriptionRequest::kAnswer, observer, options);
255 if (identity_request_state_ == IDENTITY_WAITING) {
256 create_session_description_requests_.push(request);
258 ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
259 identity_request_state_ == IDENTITY_NOT_NEEDED);
260 InternalCreateAnswer(request);
264 void WebRtcSessionDescriptionFactory::set_secure(
265 cricket::SecureMediaPolicy secure_policy) {
266 session_desc_factory_.set_secure(secure_policy);
269 cricket::SecureMediaPolicy WebRtcSessionDescriptionFactory::secure() const {
270 return session_desc_factory_.secure();
273 bool WebRtcSessionDescriptionFactory::waiting_for_identity() const {
274 return identity_request_state_ == IDENTITY_WAITING;
277 void WebRtcSessionDescriptionFactory::OnMessage(talk_base::Message* msg) {
278 switch (msg->message_id) {
279 case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
280 CreateSessionDescriptionMsg* param =
281 static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
282 param->observer->OnSuccess(param->description.release());
286 case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
287 CreateSessionDescriptionMsg* param =
288 static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
289 param->observer->OnFailure(param->error);
293 case MSG_GENERATE_IDENTITY: {
294 LOG(LS_INFO) << "Generating identity.";
295 SetIdentity(talk_base::SSLIdentity::Generate(kWebRTCIdentityName));
304 void WebRtcSessionDescriptionFactory::InternalCreateOffer(
305 CreateSessionDescriptionRequest request) {
306 cricket::SessionDescription* desc(
307 session_desc_factory_.CreateOffer(
309 static_cast<cricket::BaseSession*>(session_)->local_description()));
311 // When issuing an offer that modifies the session,
312 // the "o=" line of the new SDP MUST be identical to that in the
313 // previous SDP, except that the version in the origin field MUST
314 // increment by one from the previous SDP.
316 // Just increase the version number by one each time when a new offer
317 // is created regardless if it's identical to the previous one or not.
318 // The |session_version_| is a uint64, the wrap around should not happen.
319 ASSERT(session_version_ + 1 > session_version_);
320 JsepSessionDescription* offer(new JsepSessionDescription(
321 JsepSessionDescription::kOffer));
322 if (!offer->Initialize(desc, session_id_,
323 talk_base::ToString(session_version_++))) {
325 PostCreateSessionDescriptionFailed(request.observer, "CreateOffer failed.");
328 if (session_->local_description() &&
329 !request.options.transport_options.ice_restart) {
330 // Include all local ice candidates in the SessionDescription unless
331 // the an ice restart has been requested.
332 CopyCandidatesFromSessionDescription(session_->local_description(), offer);
334 PostCreateSessionDescriptionSucceeded(request.observer, offer);
337 void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
338 CreateSessionDescriptionRequest request) {
339 // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
340 // an answer should also contain new ice ufrag and password if an offer has
341 // been received with new ufrag and password.
342 request.options.transport_options.ice_restart = session_->IceRestartPending();
343 // We should pass current ssl role to the transport description factory, if
344 // there is already an existing ongoing session.
345 talk_base::SSLRole ssl_role;
346 if (session_->GetSslRole(&ssl_role)) {
347 request.options.transport_options.prefer_passive_role =
348 (talk_base::SSL_SERVER == ssl_role);
351 cricket::SessionDescription* desc(session_desc_factory_.CreateAnswer(
352 static_cast<cricket::BaseSession*>(session_)->remote_description(),
354 static_cast<cricket::BaseSession*>(session_)->local_description()));
356 // If the answer is different from the offer in any way (different IP
357 // addresses, ports, etc.), the origin line MUST be different in the answer.
358 // In that case, the version number in the "o=" line of the answer is
359 // unrelated to the version number in the o line of the offer.
360 // Get a new version number by increasing the |session_version_answer_|.
361 // The |session_version_| is a uint64, the wrap around should not happen.
362 ASSERT(session_version_ + 1 > session_version_);
363 JsepSessionDescription* answer(new JsepSessionDescription(
364 JsepSessionDescription::kAnswer));
365 if (!answer->Initialize(desc, session_id_,
366 talk_base::ToString(session_version_++))) {
368 PostCreateSessionDescriptionFailed(request.observer,
369 "CreateAnswer failed.");
372 if (session_->local_description() &&
373 !request.options.transport_options.ice_restart) {
374 // Include all local ice candidates in the SessionDescription unless
375 // the remote peer has requested an ice restart.
376 CopyCandidatesFromSessionDescription(session_->local_description(), answer);
378 session_->ResetIceRestartLatch();
379 PostCreateSessionDescriptionSucceeded(request.observer, answer);
382 void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed(
383 CreateSessionDescriptionObserver* observer, const std::string& error) {
384 CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
386 signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
389 void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionSucceeded(
390 CreateSessionDescriptionObserver* observer,
391 SessionDescriptionInterface* description) {
392 CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
393 msg->description.reset(description);
394 signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, msg);
397 void WebRtcSessionDescriptionFactory::OnIdentityRequestFailed(int error) {
398 ASSERT(signaling_thread_->IsCurrent());
400 LOG(LS_ERROR) << "Async identity request failed: error = " << error;
401 identity_request_state_ = IDENTITY_FAILED;
403 std::string msg = kFailedDueToIdentityFailed;
404 while (!create_session_description_requests_.empty()) {
405 const CreateSessionDescriptionRequest& request =
406 create_session_description_requests_.front();
407 PostCreateSessionDescriptionFailed(
409 ((request.type == CreateSessionDescriptionRequest::kOffer) ?
410 "CreateOffer" : "CreateAnswer") + msg);
411 create_session_description_requests_.pop();
415 void WebRtcSessionDescriptionFactory::OnIdentityReady(
416 const std::string& der_cert,
417 const std::string& der_private_key) {
418 ASSERT(signaling_thread_->IsCurrent());
419 LOG(LS_VERBOSE) << "Identity is successfully generated.";
421 std::string pem_cert = talk_base::SSLIdentity::DerToPem(
422 talk_base::kPemTypeCertificate,
423 reinterpret_cast<const unsigned char*>(der_cert.data()),
425 std::string pem_key = talk_base::SSLIdentity::DerToPem(
426 talk_base::kPemTypeRsaPrivateKey,
427 reinterpret_cast<const unsigned char*>(der_private_key.data()),
428 der_private_key.length());
430 talk_base::SSLIdentity* identity =
431 talk_base::SSLIdentity::FromPEMStrings(pem_key, pem_cert);
432 SetIdentity(identity);
435 void WebRtcSessionDescriptionFactory::SetIdentity(
436 talk_base::SSLIdentity* identity) {
437 identity_request_state_ = IDENTITY_SUCCEEDED;
438 SignalIdentityReady(identity);
440 transport_desc_factory_.set_identity(identity);
441 transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
443 while (!create_session_description_requests_.empty()) {
444 if (create_session_description_requests_.front().type ==
445 CreateSessionDescriptionRequest::kOffer) {
446 InternalCreateOffer(create_session_description_requests_.front());
448 InternalCreateAnswer(create_session_description_requests_.front());
450 create_session_description_requests_.pop();
454 } // namespace webrtc