- add third_party src.
[platform/framework/web/crosswalk.git] / src / third_party / libjingle / source / talk / app / webrtc / webrtcsessiondescriptionfactory.cc
1 /*
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
36 namespace webrtc {
37
38 namespace {
39
40 static 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.
45 static const char kWebRTCIdentityName[] = "WebRTC";
46
47 static const uint64 kInitSessionVersion = 2;
48
49 typedef cricket::MediaSessionOptions::Stream Stream;
50 typedef cricket::MediaSessionOptions::Streams Streams;
51
52 static bool CompareStream(const Stream& stream1, const Stream& stream2) {
53   return (stream1.id < stream2.id);
54 }
55
56 static 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.
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(),
66                          SameId);
67   return (it == sorted_streams.end());
68 }
69
70 enum {
71   MSG_CREATE_SESSIONDESCRIPTION_SUCCESS,
72   MSG_CREATE_SESSIONDESCRIPTION_FAILED,
73   MSG_GENERATE_IDENTITY,
74 };
75
76 struct 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
90 void 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
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,
116     bool dtls_enabled)
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   if (dtls_enabled) {
136     if (identity_service_.get()) {
137       identity_request_observer_ =
138         new talk_base::RefCountedObject<WebRtcIdentityRequestObserver>();
139
140       identity_request_observer_->SignalRequestFailed.connect(
141           this, &WebRtcSessionDescriptionFactory::OnIdentityRequestFailed);
142       identity_request_observer_->SignalIdentityReady.connect(
143           this, &WebRtcSessionDescriptionFactory::OnIdentityReady);
144
145       if (identity_service_->RequestIdentity(kWebRTCIdentityName,
146                                              kWebRTCIdentityName,
147                                              identity_request_observer_)) {
148         LOG(LS_VERBOSE) << "DTLS-SRTP enabled; sent DTLS identity request.";
149         identity_request_state_ = IDENTITY_WAITING;
150       } else {
151         LOG(LS_ERROR) << "Failed to send DTLS identity request.";
152         identity_request_state_ = IDENTITY_FAILED;
153       }
154     } else {
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);
159     }
160   }
161 }
162
163 WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
164   transport_desc_factory_.set_identity(NULL);
165 }
166
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);
176     return;
177   }
178
179   if (!mediastream_signaling_->GetOptionsForOffer(constraints, &options)) {
180     error += " called with invalid constraints.";
181     LOG(LS_ERROR) << error;
182     PostCreateSessionDescriptionFailed(observer, error);
183     return;
184   }
185
186   if (!ValidStreams(options.streams)) {
187     error += " called with invalid media streams.";
188     LOG(LS_ERROR) << error;
189     PostCreateSessionDescriptionFailed(observer, error);
190     return;
191   }
192
193   if (data_channel_type_ == cricket::DCT_SCTP &&
194       mediastream_signaling_->HasDataChannels()) {
195     options.data_channel_type = cricket::DCT_SCTP;
196   }
197
198   CreateSessionDescriptionRequest request(
199       CreateSessionDescriptionRequest::kOffer, observer, options);
200   if (identity_request_state_ == IDENTITY_WAITING) {
201     create_session_description_requests_.push(request);
202   } else {
203     ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
204            identity_request_state_ == IDENTITY_NOT_NEEDED);
205     InternalCreateOffer(request);
206   }
207 }
208
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);
217     return;
218   }
219   if (!session_->remote_description()) {
220     error += " can't be called before SetRemoteDescription.";
221     LOG(LS_ERROR) << error;
222     PostCreateSessionDescriptionFailed(observer, error);
223     return;
224   }
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);
230     return;
231   }
232
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);
238     return;
239   }
240   if (!ValidStreams(options.streams)) {
241     error += " called with invalid media streams.";
242     LOG(LS_ERROR) << error;
243     PostCreateSessionDescriptionFailed(observer, error);
244     return;
245   }
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
248   // handled here.
249   if (data_channel_type_ == cricket::DCT_SCTP) {
250     options.data_channel_type = cricket::DCT_SCTP;
251   }
252
253   CreateSessionDescriptionRequest request(
254       CreateSessionDescriptionRequest::kAnswer, observer, options);
255   if (identity_request_state_ == IDENTITY_WAITING) {
256     create_session_description_requests_.push(request);
257   } else {
258     ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
259            identity_request_state_ == IDENTITY_NOT_NEEDED);
260     InternalCreateAnswer(request);
261   }
262 }
263
264 void WebRtcSessionDescriptionFactory::set_secure(
265     cricket::SecureMediaPolicy secure_policy) {
266   session_desc_factory_.set_secure(secure_policy);
267 }
268
269 cricket::SecureMediaPolicy WebRtcSessionDescriptionFactory::secure() const {
270   return session_desc_factory_.secure();
271 }
272
273 bool WebRtcSessionDescriptionFactory::waiting_for_identity() const {
274   return identity_request_state_ == IDENTITY_WAITING;
275 }
276
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());
283       delete param;
284       break;
285     }
286     case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
287       CreateSessionDescriptionMsg* param =
288           static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
289       param->observer->OnFailure(param->error);
290       delete param;
291       break;
292     }
293     case MSG_GENERATE_IDENTITY: {
294       LOG(LS_INFO) << "Generating identity.";
295       SetIdentity(talk_base::SSLIdentity::Generate(kWebRTCIdentityName));
296       break;
297     }
298     default:
299       ASSERT(false);
300       break;
301   }
302 }
303
304 void WebRtcSessionDescriptionFactory::InternalCreateOffer(
305     CreateSessionDescriptionRequest request) {
306   cricket::SessionDescription* desc(
307       session_desc_factory_.CreateOffer(
308           request.options,
309           static_cast<cricket::BaseSession*>(session_)->local_description()));
310   // RFC 3264
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.
315
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_++))) {
324     delete offer;
325     PostCreateSessionDescriptionFailed(request.observer, "CreateOffer failed.");
326     return;
327   }
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);
333   }
334   PostCreateSessionDescriptionSucceeded(request.observer, offer);
335 }
336
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);
349   }
350
351   cricket::SessionDescription* desc(session_desc_factory_.CreateAnswer(
352       static_cast<cricket::BaseSession*>(session_)->remote_description(),
353       request.options,
354       static_cast<cricket::BaseSession*>(session_)->local_description()));
355   // RFC 3264
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_++))) {
367     delete answer;
368     PostCreateSessionDescriptionFailed(request.observer,
369                                        "CreateAnswer failed.");
370     return;
371   }
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);
377   }
378   session_->ResetIceRestartLatch();
379   PostCreateSessionDescriptionSucceeded(request.observer, answer);
380 }
381
382 void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed(
383     CreateSessionDescriptionObserver* observer, const std::string& error) {
384   CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
385   msg->error = error;
386   signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
387 }
388
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);
395 }
396
397 void WebRtcSessionDescriptionFactory::OnIdentityRequestFailed(int error) {
398   ASSERT(signaling_thread_->IsCurrent());
399
400   LOG(LS_ERROR) << "Async identity request failed: error = " << error;
401   identity_request_state_ = IDENTITY_FAILED;
402
403   std::string msg = kFailedDueToIdentityFailed;
404   while (!create_session_description_requests_.empty()) {
405     const CreateSessionDescriptionRequest& request =
406         create_session_description_requests_.front();
407     PostCreateSessionDescriptionFailed(
408         request.observer,
409         ((request.type == CreateSessionDescriptionRequest::kOffer) ?
410             "CreateOffer" : "CreateAnswer") + msg);
411     create_session_description_requests_.pop();
412   }
413 }
414
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.";
420
421   std::string pem_cert = talk_base::SSLIdentity::DerToPem(
422       talk_base::kPemTypeCertificate,
423       reinterpret_cast<const unsigned char*>(der_cert.data()),
424       der_cert.length());
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());
429
430   talk_base::SSLIdentity* identity =
431       talk_base::SSLIdentity::FromPEMStrings(pem_key, pem_cert);
432   SetIdentity(identity);
433 }
434
435 void WebRtcSessionDescriptionFactory::SetIdentity(
436     talk_base::SSLIdentity* identity) {
437   identity_request_state_ = IDENTITY_SUCCEEDED;
438   SignalIdentityReady(identity);
439
440   transport_desc_factory_.set_identity(identity);
441   transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
442
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());
447     } else {
448       InternalCreateAnswer(create_session_description_requests_.front());
449     }
450     create_session_description_requests_.pop();
451   }
452 }
453
454 }  // namespace webrtc