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