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 talk_base::MessageData {
     76   explicit CreateSessionDescriptionMsg(
     77       webrtc::CreateSessionDescriptionObserver* observer)
     78       : observer(observer) {
     79   }
     80 
     81   talk_base::scoped_refptr<webrtc::CreateSessionDescriptionObserver> observer;
     82   std::string error;
     83   talk_base::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     talk_base::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   // By default SRTP-SDES is enabled in WebRtc.
    131   SetSecure(cricket::SEC_REQUIRED);
    132 
    133   if (!dtls_enabled) {
    134     return;
    135   }
    136 
    137   if (identity_service_.get()) {
    138     identity_request_observer_ =
    139       new talk_base::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 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::SetSecure(
    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 void WebRtcSessionDescriptionFactory::OnMessage(talk_base::Message* msg) {
    274   switch (msg->message_id) {
    275     case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
    276       CreateSessionDescriptionMsg* param =
    277           static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
    278       param->observer->OnSuccess(param->description.release());
    279       delete param;
    280       break;
    281     }
    282     case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
    283       CreateSessionDescriptionMsg* param =
    284           static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
    285       param->observer->OnFailure(param->error);
    286       delete param;
    287       break;
    288     }
    289     case MSG_GENERATE_IDENTITY: {
    290       LOG(LS_INFO) << "Generating identity.";
    291       SetIdentity(talk_base::SSLIdentity::Generate(kWebRTCIdentityName));
    292       break;
    293     }
    294     default:
    295       ASSERT(false);
    296       break;
    297   }
    298 }
    299 
    300 void WebRtcSessionDescriptionFactory::InternalCreateOffer(
    301     CreateSessionDescriptionRequest request) {
    302   cricket::SessionDescription* desc(
    303       session_desc_factory_.CreateOffer(
    304           request.options,
    305           static_cast<cricket::BaseSession*>(session_)->local_description()));
    306   // RFC 3264
    307   // When issuing an offer that modifies the session,
    308   // the "o=" line of the new SDP MUST be identical to that in the
    309   // previous SDP, except that the version in the origin field MUST
    310   // increment by one from the previous SDP.
    311 
    312   // Just increase the version number by one each time when a new offer
    313   // is created regardless if it's identical to the previous one or not.
    314   // The |session_version_| is a uint64, the wrap around should not happen.
    315   ASSERT(session_version_ + 1 > session_version_);
    316   JsepSessionDescription* offer(new JsepSessionDescription(
    317       JsepSessionDescription::kOffer));
    318   if (!offer->Initialize(desc, session_id_,
    319                          talk_base::ToString(session_version_++))) {
    320     delete offer;
    321     PostCreateSessionDescriptionFailed(request.observer, "CreateOffer failed.");
    322     return;
    323   }
    324   if (session_->local_description() &&
    325       !request.options.transport_options.ice_restart) {
    326     // Include all local ice candidates in the SessionDescription unless
    327     // the an ice restart has been requested.
    328     CopyCandidatesFromSessionDescription(session_->local_description(), offer);
    329   }
    330   PostCreateSessionDescriptionSucceeded(request.observer, offer);
    331 }
    332 
    333 void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
    334     CreateSessionDescriptionRequest request) {
    335   // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
    336   // an answer should also contain new ice ufrag and password if an offer has
    337   // been received with new ufrag and password.
    338   request.options.transport_options.ice_restart = session_->IceRestartPending();
    339   // We should pass current ssl role to the transport description factory, if
    340   // there is already an existing ongoing session.
    341   talk_base::SSLRole ssl_role;
    342   if (session_->GetSslRole(&ssl_role)) {
    343     request.options.transport_options.prefer_passive_role =
    344         (talk_base::SSL_SERVER == ssl_role);
    345   }
    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 
    378 void 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 
    385 void 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 
    393 void 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 
    411 void 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 
    431 void 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_secure(cricket::SEC_ENABLED);
    438 
    439   while (!create_session_description_requests_.empty()) {
    440     if (create_session_description_requests_.front().type ==
    441         CreateSessionDescriptionRequest::kOffer) {
    442       InternalCreateOffer(create_session_description_requests_.front());
    443     } else {
    444       InternalCreateAnswer(create_session_description_requests_.front());
    445     }
    446     create_session_description_requests_.pop();
    447   }
    448 }
    449 }  // namespace webrtc
    450