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 36using cricket::MediaSessionOptions; 37 38namespace webrtc { 39namespace { 40static 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. 45static const char kWebRTCIdentityName[] = "WebRTC"; 46 47static const uint64 kInitSessionVersion = 2; 48 49static bool CompareStream(const MediaSessionOptions::Stream& stream1, 50 const MediaSessionOptions::Stream& stream2) { 51 return stream1.id < stream2.id; 52} 53 54static 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. 60static 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 69enum { 70 MSG_CREATE_SESSIONDESCRIPTION_SUCCESS, 71 MSG_CREATE_SESSIONDESCRIPTION_FAILED, 72 MSG_GENERATE_IDENTITY, 73}; 74 75struct 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 88void 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 106WebRtcSessionDescriptionFactory::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 163WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() { 164 transport_desc_factory_.set_identity(NULL); 165} 166 167void 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 211void 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 266void WebRtcSessionDescriptionFactory::SetSdesPolicy( 267 cricket::SecurePolicy secure_policy) { 268 session_desc_factory_.set_secure(secure_policy); 269} 270 271cricket::SecurePolicy WebRtcSessionDescriptionFactory::SdesPolicy() const { 272 return session_desc_factory_.secure(); 273} 274 275void 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 302void 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 336void 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 381void 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 389void 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 397void 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 415void 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 435void 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