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   // 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 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::SetSdesPolicy(
    265     cricket::SecurePolicy secure_policy) {
    266   session_desc_factory_.set_secure(secure_policy);
    267 }
    268 
    269 cricket::SecurePolicy WebRtcSessionDescriptionFactory::SdesPolicy() 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,
    322                                        "Failed to initialize the offer.");
    323     return;
    324   }
    325   if (session_->local_description() &&
    326       !request.options.transport_options.ice_restart) {
    327     // Include all local ice candidates in the SessionDescription unless
    328     // the an ice restart has been requested.
    329     CopyCandidatesFromSessionDescription(session_->local_description(), offer);
    330   }
    331   PostCreateSessionDescriptionSucceeded(request.observer, offer);
    332 }
    333 
    334 void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
    335     CreateSessionDescriptionRequest request) {
    336   // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
    337   // an answer should also contain new ice ufrag and password if an offer has
    338   // been received with new ufrag and password.
    339   request.options.transport_options.ice_restart = session_->IceRestartPending();
    340   // We should pass current ssl role to the transport description factory, if
    341   // there is already an existing ongoing session.
    342   talk_base::SSLRole ssl_role;
    343   if (session_->GetSslRole(&ssl_role)) {
    344     request.options.transport_options.prefer_passive_role =
    345         (talk_base::SSL_SERVER == ssl_role);
    346   }
    347 
    348   cricket::SessionDescription* desc(session_desc_factory_.CreateAnswer(
    349       static_cast<cricket::BaseSession*>(session_)->remote_description(),
    350       request.options,
    351       static_cast<cricket::BaseSession*>(session_)->local_description()));
    352   // RFC 3264
    353   // If the answer is different from the offer in any way (different IP
    354   // addresses, ports, etc.), the origin line MUST be different in the answer.
    355   // In that case, the version number in the "o=" line of the answer is
    356   // unrelated to the version number in the o line of the offer.
    357   // Get a new version number by increasing the |session_version_answer_|.
    358   // The |session_version_| is a uint64, the wrap around should not happen.
    359   ASSERT(session_version_ + 1 > session_version_);
    360   JsepSessionDescription* answer(new JsepSessionDescription(
    361       JsepSessionDescription::kAnswer));
    362   if (!answer->Initialize(desc, session_id_,
    363                           talk_base::ToString(session_version_++))) {
    364     delete answer;
    365     PostCreateSessionDescriptionFailed(request.observer,
    366                                        "Failed to initialize the answer.");
    367     return;
    368   }
    369   if (session_->local_description() &&
    370       !request.options.transport_options.ice_restart) {
    371     // Include all local ice candidates in the SessionDescription unless
    372     // the remote peer has requested an ice restart.
    373     CopyCandidatesFromSessionDescription(session_->local_description(), answer);
    374   }
    375   session_->ResetIceRestartLatch();
    376   PostCreateSessionDescriptionSucceeded(request.observer, answer);
    377 }
    378 
    379 void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionFailed(
    380     CreateSessionDescriptionObserver* observer, const std::string& error) {
    381   CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
    382   msg->error = error;
    383   signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
    384   LOG(LS_ERROR) << "Create SDP failed: " << error;
    385 }
    386 
    387 void WebRtcSessionDescriptionFactory::PostCreateSessionDescriptionSucceeded(
    388     CreateSessionDescriptionObserver* observer,
    389     SessionDescriptionInterface* description) {
    390   CreateSessionDescriptionMsg* msg = new CreateSessionDescriptionMsg(observer);
    391   msg->description.reset(description);
    392   signaling_thread_->Post(this, MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, msg);
    393 }
    394 
    395 void WebRtcSessionDescriptionFactory::OnIdentityRequestFailed(int error) {
    396   ASSERT(signaling_thread_->IsCurrent());
    397 
    398   LOG(LS_ERROR) << "Async identity request failed: error = " << error;
    399   identity_request_state_ = IDENTITY_FAILED;
    400 
    401   std::string msg = kFailedDueToIdentityFailed;
    402   while (!create_session_description_requests_.empty()) {
    403     const CreateSessionDescriptionRequest& request =
    404         create_session_description_requests_.front();
    405     PostCreateSessionDescriptionFailed(
    406         request.observer,
    407         ((request.type == CreateSessionDescriptionRequest::kOffer) ?
    408             "CreateOffer" : "CreateAnswer") + msg);
    409     create_session_description_requests_.pop();
    410   }
    411 }
    412 
    413 void WebRtcSessionDescriptionFactory::OnIdentityReady(
    414     const std::string& der_cert,
    415     const std::string& der_private_key) {
    416   ASSERT(signaling_thread_->IsCurrent());
    417   LOG(LS_VERBOSE) << "Identity is successfully generated.";
    418 
    419   std::string pem_cert = talk_base::SSLIdentity::DerToPem(
    420       talk_base::kPemTypeCertificate,
    421       reinterpret_cast<const unsigned char*>(der_cert.data()),
    422       der_cert.length());
    423   std::string pem_key = talk_base::SSLIdentity::DerToPem(
    424       talk_base::kPemTypeRsaPrivateKey,
    425       reinterpret_cast<const unsigned char*>(der_private_key.data()),
    426       der_private_key.length());
    427 
    428   talk_base::SSLIdentity* identity =
    429       talk_base::SSLIdentity::FromPEMStrings(pem_key, pem_cert);
    430   SetIdentity(identity);
    431 }
    432 
    433 void WebRtcSessionDescriptionFactory::SetIdentity(
    434     talk_base::SSLIdentity* identity) {
    435   identity_request_state_ = IDENTITY_SUCCEEDED;
    436   SignalIdentityReady(identity);
    437 
    438   transport_desc_factory_.set_identity(identity);
    439   transport_desc_factory_.set_secure(cricket::SEC_ENABLED);
    440 
    441   while (!create_session_description_requests_.empty()) {
    442     if (create_session_description_requests_.front().type ==
    443         CreateSessionDescriptionRequest::kOffer) {
    444       InternalCreateOffer(create_session_description_requests_.front());
    445     } else {
    446       InternalCreateAnswer(create_session_description_requests_.front());
    447     }
    448     create_session_description_requests_.pop();
    449   }
    450 }
    451 }  // namespace webrtc
    452