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
CompareStream(const MediaSessionOptions::Stream & stream1,const MediaSessionOptions::Stream & stream2)49 static bool CompareStream(const MediaSessionOptions::Stream& stream1,
50 const MediaSessionOptions::Stream& stream2) {
51 return stream1.id < stream2.id;
52 }
53
SameId(const MediaSessionOptions::Stream & stream1,const MediaSessionOptions::Stream & stream2)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.
ValidStreams(const MediaSessionOptions::Streams & streams)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 rtc::MessageData {
CreateSessionDescriptionMsgwebrtc::__anon0fc4bbd60111::CreateSessionDescriptionMsg76 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
CopyCandidatesFromSessionDescription(const SessionDescriptionInterface * source_desc,SessionDescriptionInterface * dest_desc)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
WebRtcSessionDescriptionFactory(rtc::Thread * signaling_thread,cricket::ChannelManager * channel_manager,MediaStreamSignaling * mediastream_signaling,DTLSIdentityServiceInterface * dtls_identity_service,WebRtcSession * session,const std::string & session_id,cricket::DataChannelType dct,bool dtls_enabled)106 WebRtcSessionDescriptionFactory::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
~WebRtcSessionDescriptionFactory()163 WebRtcSessionDescriptionFactory::~WebRtcSessionDescriptionFactory() {
164 transport_desc_factory_.set_identity(NULL);
165 }
166
CreateOffer(CreateSessionDescriptionObserver * observer,const PeerConnectionInterface::RTCOfferAnswerOptions & options)167 void 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
CreateAnswer(CreateSessionDescriptionObserver * observer,const MediaConstraintsInterface * constraints)211 void 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
SetSdesPolicy(cricket::SecurePolicy secure_policy)266 void WebRtcSessionDescriptionFactory::SetSdesPolicy(
267 cricket::SecurePolicy secure_policy) {
268 session_desc_factory_.set_secure(secure_policy);
269 }
270
SdesPolicy() const271 cricket::SecurePolicy WebRtcSessionDescriptionFactory::SdesPolicy() const {
272 return session_desc_factory_.secure();
273 }
274
OnMessage(rtc::Message * msg)275 void 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
InternalCreateOffer(CreateSessionDescriptionRequest request)302 void 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
InternalCreateAnswer(CreateSessionDescriptionRequest request)336 void 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
PostCreateSessionDescriptionFailed(CreateSessionDescriptionObserver * observer,const std::string & error)381 void 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
PostCreateSessionDescriptionSucceeded(CreateSessionDescriptionObserver * observer,SessionDescriptionInterface * description)389 void 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
OnIdentityRequestFailed(int error)397 void 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
OnIdentityReady(const std::string & der_cert,const std::string & der_private_key)415 void 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
SetIdentity(rtc::SSLIdentity * identity)435 void 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