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