Home | History | Annotate | Download | only in webrtc
      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 
     49 static bool CompareStream(const MediaSessionOptions::Stream& stream1,
     50                           const MediaSessionOptions::Stream& stream2) {
     51   return stream1.id < stream2.id;
     52 }
     53 
     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.
     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 {
     76   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
     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 
    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 
    163 WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
    164   transport_desc_factory_.set_identity(NULL);
    165 }
    166 
    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 
    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 
    266 void WebRtcSessionDescriptionFactory::SetSdesPolicy(
    267     cricket::SecurePolicy secure_policy) {
    268   session_desc_factory_.set_secure(secure_policy);
    269 }
    270 
    271 cricket::SecurePolicy WebRtcSessionDescriptionFactory::SdesPolicy() const {
    272   return session_desc_factory_.secure();
    273 }
    274 
    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 
    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 
    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 
    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 
    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 = 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 
    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