• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 using cricket::MediaSessionOptions;
37 
38 namespace webrtc {
39 namespace {
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 
CompareStream(const MediaSessionOptions::Stream & stream1,const MediaSessionOptions::Stream & stream2)49 static bool CompareStream(const MediaSessionOptions::Stream& stream1,
50                           const MediaSessionOptions::Stream& stream2) {
51   return stream1.id < stream2.id;
52 }
53 
SameId(const MediaSessionOptions::Stream & stream1,const MediaSessionOptions::Stream & stream2)54 static bool SameId(const MediaSessionOptions::Stream& stream1,
55                    const MediaSessionOptions::Stream& stream2) {
56   return stream1.id == stream2.id;
57 }
58 
59 // Checks if each Stream within the |streams| has unique id.
ValidStreams(const MediaSessionOptions::Streams & streams)60 static bool ValidStreams(const MediaSessionOptions::Streams& streams) {
61   MediaSessionOptions::Streams sorted_streams = streams;
62   std::sort(sorted_streams.begin(), sorted_streams.end(), CompareStream);
63   MediaSessionOptions::Streams::iterator it =
64       std::adjacent_find(sorted_streams.begin(), sorted_streams.end(),
65                          SameId);
66   return it == sorted_streams.end();
67 }
68 
69 enum {
70   MSG_CREATE_SESSIONDESCRIPTION_SUCCESS,
71   MSG_CREATE_SESSIONDESCRIPTION_FAILED,
72   MSG_GENERATE_IDENTITY,
73 };
74 
75 struct CreateSessionDescriptionMsg : public rtc::MessageData {
CreateSessionDescriptionMsgwebrtc::__anon0fc4bbd60111::CreateSessionDescriptionMsg76   explicit CreateSessionDescriptionMsg(
77       webrtc::CreateSessionDescriptionObserver* observer)
78       : observer(observer) {
79   }
80 
81   rtc::scoped_refptr<webrtc::CreateSessionDescriptionObserver> observer;
82   std::string error;
83   rtc::scoped_ptr<webrtc::SessionDescriptionInterface> description;
84 };
85 }  // namespace
86 
87 // static
CopyCandidatesFromSessionDescription(const SessionDescriptionInterface * source_desc,SessionDescriptionInterface * dest_desc)88 void WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
89     const SessionDescriptionInterface* source_desc,
90     SessionDescriptionInterface* dest_desc) {
91   if (!source_desc)
92     return;
93   for (size_t m = 0; m < source_desc->number_of_mediasections() &&
94                      m < dest_desc->number_of_mediasections(); ++m) {
95     const IceCandidateCollection* source_candidates =
96         source_desc->candidates(m);
97     const IceCandidateCollection* dest_candidates = dest_desc->candidates(m);
98     for  (size_t n = 0; n < source_candidates->count(); ++n) {
99       const IceCandidateInterface* new_candidate = source_candidates->at(n);
100       if (!dest_candidates->HasCandidate(new_candidate))
101         dest_desc->AddCandidate(source_candidates->at(n));
102     }
103   }
104 }
105 
WebRtcSessionDescriptionFactory(rtc::Thread * signaling_thread,cricket::ChannelManager * channel_manager,MediaStreamSignaling * mediastream_signaling,DTLSIdentityServiceInterface * dtls_identity_service,WebRtcSession * session,const std::string & session_id,cricket::DataChannelType dct,bool dtls_enabled)106 WebRtcSessionDescriptionFactory::WebRtcSessionDescriptionFactory(
107     rtc::Thread* signaling_thread,
108     cricket::ChannelManager* channel_manager,
109     MediaStreamSignaling* mediastream_signaling,
110     DTLSIdentityServiceInterface* dtls_identity_service,
111     WebRtcSession* session,
112     const std::string& session_id,
113     cricket::DataChannelType dct,
114     bool dtls_enabled)
115     : signaling_thread_(signaling_thread),
116       mediastream_signaling_(mediastream_signaling),
117       session_desc_factory_(channel_manager, &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       identity_service_(dtls_identity_service),
124       session_(session),
125       session_id_(session_id),
126       data_channel_type_(dct),
127       identity_request_state_(IDENTITY_NOT_NEEDED) {
128   transport_desc_factory_.set_protocol(cricket::ICEPROTO_HYBRID);
129   session_desc_factory_.set_add_legacy_streams(false);
130   // SRTP-SDES is disabled if DTLS is on.
131   SetSdesPolicy(dtls_enabled ? cricket::SEC_DISABLED : cricket::SEC_REQUIRED);
132 
133   if (!dtls_enabled) {
134     return;
135   }
136 
137   if (identity_service_.get()) {
138     identity_request_observer_ =
139       new rtc::RefCountedObject<WebRtcIdentityRequestObserver>();
140 
141     identity_request_observer_->SignalRequestFailed.connect(
142         this, &WebRtcSessionDescriptionFactory::OnIdentityRequestFailed);
143     identity_request_observer_->SignalIdentityReady.connect(
144         this, &WebRtcSessionDescriptionFactory::OnIdentityReady);
145 
146     if (identity_service_->RequestIdentity(kWebRTCIdentityName,
147                                            kWebRTCIdentityName,
148                                            identity_request_observer_)) {
149       LOG(LS_VERBOSE) << "DTLS-SRTP enabled; sent DTLS identity request.";
150       identity_request_state_ = IDENTITY_WAITING;
151     } else {
152       LOG(LS_ERROR) << "Failed to send DTLS identity request.";
153       identity_request_state_ = IDENTITY_FAILED;
154     }
155   } else {
156     identity_request_state_ = IDENTITY_WAITING;
157     // Do not generate the identity in the constructor since the caller has
158     // not got a chance to connect to SignalIdentityReady.
159     signaling_thread_->Post(this, MSG_GENERATE_IDENTITY, NULL);
160   }
161 }
162 
~WebRtcSessionDescriptionFactory()163 WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
164   transport_desc_factory_.set_identity(NULL);
165 }
166 
CreateOffer(CreateSessionDescriptionObserver * observer,const PeerConnectionInterface::RTCOfferAnswerOptions & options)167 void WebRtcSessionDescriptionFactory::CreateOffer(
168     CreateSessionDescriptionObserver* observer,
169     const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
170   cricket::MediaSessionOptions session_options;
171 
172   std::string error = "CreateOffer";
173   if (identity_request_state_ == IDENTITY_FAILED) {
174     error += kFailedDueToIdentityFailed;
175     LOG(LS_ERROR) << error;
176     PostCreateSessionDescriptionFailed(observer, error);
177     return;
178   }
179 
180   if (!mediastream_signaling_->GetOptionsForOffer(options,
181                                                   &session_options)) {
182     error += " called with invalid options.";
183     LOG(LS_ERROR) << error;
184     PostCreateSessionDescriptionFailed(observer, error);
185     return;
186   }
187 
188   if (!ValidStreams(session_options.streams)) {
189     error += " called with invalid media streams.";
190     LOG(LS_ERROR) << error;
191     PostCreateSessionDescriptionFailed(observer, error);
192     return;
193   }
194 
195   if (data_channel_type_ == cricket::DCT_SCTP &&
196       mediastream_signaling_->HasDataChannels()) {
197     session_options.data_channel_type = cricket::DCT_SCTP;
198   }
199 
200   CreateSessionDescriptionRequest request(
201       CreateSessionDescriptionRequest::kOffer, observer, session_options);
202   if (identity_request_state_ == IDENTITY_WAITING) {
203     create_session_description_requests_.push(request);
204   } else {
205     ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
206            identity_request_state_ == IDENTITY_NOT_NEEDED);
207     InternalCreateOffer(request);
208   }
209 }
210 
CreateAnswer(CreateSessionDescriptionObserver * observer,const MediaConstraintsInterface * constraints)211 void WebRtcSessionDescriptionFactory::CreateAnswer(
212     CreateSessionDescriptionObserver* observer,
213     const MediaConstraintsInterface* constraints) {
214   std::string error = "CreateAnswer";
215   if (identity_request_state_ == IDENTITY_FAILED) {
216     error += kFailedDueToIdentityFailed;
217     LOG(LS_ERROR) << error;
218     PostCreateSessionDescriptionFailed(observer, error);
219     return;
220   }
221   if (!session_->remote_description()) {
222     error += " can't be called before SetRemoteDescription.";
223     LOG(LS_ERROR) << error;
224     PostCreateSessionDescriptionFailed(observer, error);
225     return;
226   }
227   if (session_->remote_description()->type() !=
228       JsepSessionDescription::kOffer) {
229     error += " failed because remote_description is not an offer.";
230     LOG(LS_ERROR) << error;
231     PostCreateSessionDescriptionFailed(observer, error);
232     return;
233   }
234 
235   cricket::MediaSessionOptions options;
236   if (!mediastream_signaling_->GetOptionsForAnswer(constraints, &options)) {
237     error += " called with invalid constraints.";
238     LOG(LS_ERROR) << error;
239     PostCreateSessionDescriptionFailed(observer, error);
240     return;
241   }
242   if (!ValidStreams(options.streams)) {
243     error += " called with invalid media streams.";
244     LOG(LS_ERROR) << error;
245     PostCreateSessionDescriptionFailed(observer, error);
246     return;
247   }
248   // RTP data channel is handled in MediaSessionOptions::AddStream. SCTP streams
249   // are not signaled in the SDP so does not go through that path and must be
250   // handled here.
251   if (data_channel_type_ == cricket::DCT_SCTP) {
252     options.data_channel_type = cricket::DCT_SCTP;
253   }
254 
255   CreateSessionDescriptionRequest request(
256       CreateSessionDescriptionRequest::kAnswer, observer, options);
257   if (identity_request_state_ == IDENTITY_WAITING) {
258     create_session_description_requests_.push(request);
259   } else {
260     ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
261            identity_request_state_ == IDENTITY_NOT_NEEDED);
262     InternalCreateAnswer(request);
263   }
264 }
265 
SetSdesPolicy(cricket::SecurePolicy secure_policy)266 void WebRtcSessionDescriptionFactory::SetSdesPolicy(
267     cricket::SecurePolicy secure_policy) {
268   session_desc_factory_.set_secure(secure_policy);
269 }
270 
SdesPolicy() const271 cricket::SecurePolicy WebRtcSessionDescriptionFactory::SdesPolicy() const {
272   return session_desc_factory_.secure();
273 }
274 
OnMessage(rtc::Message * msg)275 void WebRtcSessionDescriptionFactory::OnMessage(rtc::Message* msg) {
276   switch (msg->message_id) {
277     case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
278       CreateSessionDescriptionMsg* param =
279           static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
280       param->observer->OnSuccess(param->description.release());
281       delete param;
282       break;
283     }
284     case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
285       CreateSessionDescriptionMsg* param =
286           static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
287       param->observer->OnFailure(param->error);
288       delete param;
289       break;
290     }
291     case MSG_GENERATE_IDENTITY: {
292       LOG(LS_INFO) << "Generating identity.";
293       SetIdentity(rtc::SSLIdentity::Generate(kWebRTCIdentityName));
294       break;
295     }
296     default:
297       ASSERT(false);
298       break;
299   }
300 }
301 
InternalCreateOffer(CreateSessionDescriptionRequest request)302 void WebRtcSessionDescriptionFactory::InternalCreateOffer(
303     CreateSessionDescriptionRequest request) {
304   cricket::SessionDescription* desc(
305       session_desc_factory_.CreateOffer(
306           request.options,
307           static_cast<cricket::BaseSession*>(session_)->local_description()));
308   // RFC 3264
309   // When issuing an offer that modifies the session,
310   // the "o=" line of the new SDP MUST be identical to that in the
311   // previous SDP, except that the version in the origin field MUST
312   // increment by one from the previous SDP.
313 
314   // Just increase the version number by one each time when a new offer
315   // is created regardless if it's identical to the previous one or not.
316   // The |session_version_| is a uint64, the wrap around should not happen.
317   ASSERT(session_version_ + 1 > session_version_);
318   JsepSessionDescription* offer(new JsepSessionDescription(
319       JsepSessionDescription::kOffer));
320   if (!offer->Initialize(desc, session_id_,
321                          rtc::ToString(session_version_++))) {
322     delete offer;
323     PostCreateSessionDescriptionFailed(request.observer,
324                                        "Failed to initialize the offer.");
325     return;
326   }
327   if (session_->local_description() &&
328       !request.options.transport_options.ice_restart) {
329     // Include all local ice candidates in the SessionDescription unless
330     // the an ice restart has been requested.
331     CopyCandidatesFromSessionDescription(session_->local_description(), offer);
332   }
333   PostCreateSessionDescriptionSucceeded(request.observer, offer);
334 }
335 
InternalCreateAnswer(CreateSessionDescriptionRequest request)336 void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
337     CreateSessionDescriptionRequest request) {
338   // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
339   // an answer should also contain new ice ufrag and password if an offer has
340   // been received with new ufrag and password.
341   request.options.transport_options.ice_restart = session_->IceRestartPending();
342   // We should pass current ssl role to the transport description factory, if
343   // there is already an existing ongoing session.
344   rtc::SSLRole ssl_role;
345   if (session_->GetSslRole(&ssl_role)) {
346     request.options.transport_options.prefer_passive_role =
347         (rtc::SSL_SERVER == ssl_role);
348   }
349 
350   cricket::SessionDescription* desc(session_desc_factory_.CreateAnswer(
351       static_cast<cricket::BaseSession*>(session_)->remote_description(),
352       request.options,
353       static_cast<cricket::BaseSession*>(session_)->local_description()));
354   // RFC 3264
355   // If the answer is different from the offer in any way (different IP
356   // addresses, ports, etc.), the origin line MUST be different in the answer.
357   // In that case, the version number in the "o=" line of the answer is
358   // unrelated to the version number in the o line of the offer.
359   // Get a new version number by increasing the |session_version_answer_|.
360   // The |session_version_| is a uint64, the wrap around should not happen.
361   ASSERT(session_version_ + 1 > session_version_);
362   JsepSessionDescription* answer(new JsepSessionDescription(
363       JsepSessionDescription::kAnswer));
364   if (!answer->Initialize(desc, session_id_,
365                           rtc::ToString(session_version_++))) {
366     delete answer;
367     PostCreateSessionDescriptionFailed(request.observer,
368                                        "Failed to initialize the answer.");
369     return;
370   }
371   if (session_->local_description() &&
372       !request.options.transport_options.ice_restart) {
373     // Include all local ice candidates in the SessionDescription unless
374     // the remote peer has requested an ice restart.
375     CopyCandidatesFromSessionDescription(session_->local_description(), answer);
376   }
377   session_->ResetIceRestartLatch();
378   PostCreateSessionDescriptionSucceeded(request.observer, answer);
379 }
380 
PostCreateSessionDescriptionFailed(CreateSessionDescriptionObserver * observer,const std::string & error)381 void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed(
382     CreateSessionDescriptionObserver* observer, const std::string& error) {
383   CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
384   msg->error = error;
385   signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
386   LOG(LS_ERROR) << "Create SDP failed: " << error;
387 }
388 
PostCreateSessionDescriptionSucceeded(CreateSessionDescriptionObserver * observer,SessionDescriptionInterface * description)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 
OnIdentityRequestFailed(int error)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 
OnIdentityReady(const std::string & der_cert,const std::string & der_private_key)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 = rtc::SSLIdentity::DerToPem(
422       rtc::kPemTypeCertificate,
423       reinterpret_cast<const unsigned char*>(der_cert.data()),
424       der_cert.length());
425   std::string pem_key = rtc::SSLIdentity::DerToPem(
426       rtc::kPemTypeRsaPrivateKey,
427       reinterpret_cast<const unsigned char*>(der_private_key.data()),
428       der_private_key.length());
429 
430   rtc::SSLIdentity* identity =
431       rtc::SSLIdentity::FromPEMStrings(pem_key, pem_cert);
432   SetIdentity(identity);
433 }
434 
SetIdentity(rtc::SSLIdentity * identity)435 void WebRtcSessionDescriptionFactory::SetIdentity(
436     rtc::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 }  // namespace webrtc
454