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 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     const MediaConstraintsInterface* constraints)
    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   // Enable DTLS-SRTP if the constraint is set.
    136   bool dtls_enabled = false;
    137   if (!FindConstraint(
    138            constraints, MediaConstraintsInterface::kEnableDtlsSrtp,
    139            &dtls_enabled, NULL) ||
    140       !dtls_enabled) {
    141     return;
    142   }
    143   // DTLS is enabled.
    144   if (identity_service_.get()) {
    145     identity_request_observer_ =
    146       new talk_base::RefCountedObject<WebRtcIdentityRequestObserver>();
    147 
    148     identity_request_observer_->SignalRequestFailed.connect(
    149         this, &WebRtcSessionDescriptionFactory::OnIdentityRequestFailed);
    150     identity_request_observer_->SignalIdentityReady.connect(
    151         this, &WebRtcSessionDescriptionFactory::OnIdentityReady);
    152 
    153     if (identity_service_->RequestIdentity(kWebRTCIdentityName,
    154                                            kWebRTCIdentityName,
    155                                            identity_request_observer_)) {
    156       LOG(LS_VERBOSE) << "DTLS-SRTP enabled; sent DTLS identity request.";
    157       identity_request_state_ = IDENTITY_WAITING;
    158     } else {
    159       LOG(LS_ERROR) << "Failed to send DTLS identity request.";
    160       identity_request_state_ = IDENTITY_FAILED;
    161     }
    162   } else {
    163     identity_request_state_ = IDENTITY_WAITING;
    164     // Do not generate the identity in the constructor since the caller has
    165     // not got a chance to connect to SignalIdentityReady.
    166     signaling_thread_->Post(this, MSG_GENERATE_IDENTITY, NULL);
    167   }
    168 }
    169 
    170 WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
    171   transport_desc_factory_.set_identity(NULL);
    172 }
    173 
    174 void WebRtcSessionDescriptionFactory::CreateOffer(
    175     CreateSessionDescriptionObserver* observer,
    176     const MediaConstraintsInterface* constraints) {
    177   cricket::MediaSessionOptions options;
    178   std::string error = "CreateOffer";
    179   if (identity_request_state_ == IDENTITY_FAILED) {
    180     error += kFailedDueToIdentityFailed;
    181     LOG(LS_ERROR) << error;
    182     PostCreateSessionDescriptionFailed(observer, error);
    183     return;
    184   }
    185 
    186   if (!mediastream_signaling_->GetOptionsForOffer(constraints, &options)) {
    187     error += " called with invalid constraints.";
    188     LOG(LS_ERROR) << error;
    189     PostCreateSessionDescriptionFailed(observer, error);
    190     return;
    191   }
    192 
    193   if (!ValidStreams(options.streams)) {
    194     error += " called with invalid media streams.";
    195     LOG(LS_ERROR) << error;
    196     PostCreateSessionDescriptionFailed(observer, error);
    197     return;
    198   }
    199 
    200   if (data_channel_type_ == cricket::DCT_SCTP) {
    201     options.data_channel_type = cricket::DCT_SCTP;
    202   }
    203 
    204   CreateSessionDescriptionRequest request(
    205       CreateSessionDescriptionRequest::kOffer, observer, options);
    206   if (identity_request_state_ == IDENTITY_WAITING) {
    207     create_session_description_requests_.push(request);
    208   } else {
    209     ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
    210            identity_request_state_ == IDENTITY_NOT_NEEDED);
    211     InternalCreateOffer(request);
    212   }
    213 }
    214 
    215 void WebRtcSessionDescriptionFactory::CreateAnswer(
    216     CreateSessionDescriptionObserver* observer,
    217     const MediaConstraintsInterface* constraints) {
    218   std::string error = "CreateAnswer";
    219   if (identity_request_state_ == IDENTITY_FAILED) {
    220     error += kFailedDueToIdentityFailed;
    221     LOG(LS_ERROR) << error;
    222     PostCreateSessionDescriptionFailed(observer, error);
    223     return;
    224   }
    225   if (!session_->remote_description()) {
    226     error += " can't be called before SetRemoteDescription.";
    227     LOG(LS_ERROR) << error;
    228     PostCreateSessionDescriptionFailed(observer, error);
    229     return;
    230   }
    231   if (session_->remote_description()->type() !=
    232       JsepSessionDescription::kOffer) {
    233     error += " failed because remote_description is not an offer.";
    234     LOG(LS_ERROR) << error;
    235     PostCreateSessionDescriptionFailed(observer, error);
    236     return;
    237   }
    238 
    239   cricket::MediaSessionOptions options;
    240   if (!mediastream_signaling_->GetOptionsForAnswer(constraints, &options)) {
    241     error += " called with invalid constraints.";
    242     LOG(LS_ERROR) << error;
    243     PostCreateSessionDescriptionFailed(observer, error);
    244     return;
    245   }
    246   if (!ValidStreams(options.streams)) {
    247     error += " called with invalid media streams.";
    248     LOG(LS_ERROR) << error;
    249     PostCreateSessionDescriptionFailed(observer, error);
    250     return;
    251   }
    252   if (data_channel_type_ == cricket::DCT_SCTP) {
    253     options.data_channel_type = cricket::DCT_SCTP;
    254   }
    255 
    256   CreateSessionDescriptionRequest request(
    257       CreateSessionDescriptionRequest::kAnswer, observer, options);
    258   if (identity_request_state_ == IDENTITY_WAITING) {
    259     create_session_description_requests_.push(request);
    260   } else {
    261     ASSERT(identity_request_state_ == IDENTITY_SUCCEEDED ||
    262            identity_request_state_ == IDENTITY_NOT_NEEDED);
    263     InternalCreateAnswer(request);
    264   }
    265 }
    266 
    267 void WebRtcSessionDescriptionFactory::set_secure(
    268     cricket::SecureMediaPolicy secure_policy) {
    269   session_desc_factory_.set_secure(secure_policy);
    270 }
    271 
    272 cricket::SecureMediaPolicy WebRtcSessionDescriptionFactory::secure() const {
    273   return session_desc_factory_.secure();
    274 }
    275 
    276 bool WebRtcSessionDescriptionFactory::waiting_for_identity() const {
    277   return identity_request_state_ == IDENTITY_WAITING;
    278 }
    279 
    280 void WebRtcSessionDescriptionFactory::OnMessage(talk_base::Message* msg) {
    281   switch (msg->message_id) {
    282     case MSG_CREATE_SESSIONDESCRIPTION_SUCCESS: {
    283       CreateSessionDescriptionMsg* param =
    284           static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
    285       param->observer->OnSuccess(param->description.release());
    286       delete param;
    287       break;
    288     }
    289     case MSG_CREATE_SESSIONDESCRIPTION_FAILED: {
    290       CreateSessionDescriptionMsg* param =
    291           static_cast<CreateSessionDescriptionMsg*>(msg->pdata);
    292       param->observer->OnFailure(param->error);
    293       delete param;
    294       break;
    295     }
    296     case MSG_GENERATE_IDENTITY: {
    297       LOG(LS_INFO) << "Generating identity.";
    298       SetIdentity(talk_base::SSLIdentity::Generate(kWebRTCIdentityName));
    299       break;
    300     }
    301     default:
    302       ASSERT(false);
    303       break;
    304   }
    305 }
    306 
    307 void WebRtcSessionDescriptionFactory::InternalCreateOffer(
    308     CreateSessionDescriptionRequest request) {
    309   cricket::SessionDescription* desc(
    310       session_desc_factory_.CreateOffer(
    311           request.options,
    312           static_cast<cricket::BaseSession*>(session_)->local_description()));
    313   // RFC 3264
    314   // When issuing an offer that modifies the session,
    315   // the "o=" line of the new SDP MUST be identical to that in the
    316   // previous SDP, except that the version in the origin field MUST
    317   // increment by one from the previous SDP.
    318 
    319   // Just increase the version number by one each time when a new offer
    320   // is created regardless if it's identical to the previous one or not.
    321   // The |session_version_| is a uint64, the wrap around should not happen.
    322   ASSERT(session_version_ + 1 > session_version_);
    323   JsepSessionDescription* offer(new JsepSessionDescription(
    324       JsepSessionDescription::kOffer));
    325   if (!offer->Initialize(desc, session_id_,
    326                          talk_base::ToString(session_version_++))) {
    327     delete offer;
    328     PostCreateSessionDescriptionFailed(request.observer, "CreateOffer failed.");
    329     return;
    330   }
    331   if (session_->local_description() &&
    332       !request.options.transport_options.ice_restart) {
    333     // Include all local ice candidates in the SessionDescription unless
    334     // the an ice restart has been requested.
    335     CopyCandidatesFromSessionDescription(session_->local_description(), offer);
    336   }
    337   PostCreateSessionDescriptionSucceeded(request.observer, offer);
    338 }
    339 
    340 void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
    341     CreateSessionDescriptionRequest request) {
    342   // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
    343   // an answer should also contain new ice ufrag and password if an offer has
    344   // been received with new ufrag and password.
    345   request.options.transport_options.ice_restart = session_->IceRestartPending();
    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_digest_algorithm(talk_base::DIGEST_SHA_256);
    438   transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
    439 
    440   while (!create_session_description_requests_.empty()) {
    441     if (create_session_description_requests_.front().type ==
    442         CreateSessionDescriptionRequest::kOffer) {
    443       InternalCreateOffer(create_session_description_requests_.front());
    444     } else {
    445       InternalCreateAnswer(create_session_description_requests_.front());
    446     }
    447     create_session_description_requests_.pop();
    448   }
    449 }
    450 
    451 }  // namespace webrtc
    452