1/*
2 * Copyright (C) 2015 Ericsson AB. All rights reserved.
3 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * 3. Neither the name of Ericsson nor the names of its contributors
16 * may be used to endorse or promote products derived from this
17 * software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "PeerConnectionBackend.h"
34
35#if ENABLE(WEB_RTC)
36
37#include "EventNames.h"
38#include "JSRTCSessionDescription.h"
39#include "LibWebRTCCertificateGenerator.h"
40#include "Logging.h"
41#include "Page.h"
42#include "RTCIceCandidate.h"
43#include "RTCPeerConnection.h"
44#include "RTCPeerConnectionIceEvent.h"
45#include "RTCRtpCapabilities.h"
46#include "RTCTrackEvent.h"
47#include "RuntimeEnabledFeatures.h"
48#include <wtf/text/StringBuilder.h>
49#include <wtf/text/StringConcatenateNumbers.h>
50
51namespace WebCore {
52
53using namespace PAL;
54
55#if !USE(LIBWEBRTC)
56static std::unique_ptr<PeerConnectionBackend> createNoPeerConnectionBackend(RTCPeerConnection&)
57{
58 return nullptr;
59}
60
61CreatePeerConnectionBackend PeerConnectionBackend::create = createNoPeerConnectionBackend;
62
63Optional<RTCRtpCapabilities> PeerConnectionBackend::receiverCapabilities(ScriptExecutionContext&, const String&)
64{
65 ASSERT_NOT_REACHED();
66 return { };
67}
68
69Optional<RTCRtpCapabilities> PeerConnectionBackend::senderCapabilities(ScriptExecutionContext&, const String&)
70{
71 ASSERT_NOT_REACHED();
72 return { };
73}
74#endif
75
76PeerConnectionBackend::PeerConnectionBackend(RTCPeerConnection& peerConnection)
77 : m_peerConnection(peerConnection)
78#if !RELEASE_LOG_DISABLED
79 , m_logger(peerConnection.logger())
80 , m_logIdentifier(peerConnection.logIdentifier())
81#endif
82{
83}
84
85void PeerConnectionBackend::createOffer(RTCOfferOptions&& options, PeerConnection::SessionDescriptionPromise&& promise)
86{
87 ASSERT(!m_offerAnswerPromise);
88 ASSERT(!m_peerConnection.isClosed());
89
90 m_offerAnswerPromise = WTFMove(promise);
91 doCreateOffer(WTFMove(options));
92}
93
94void PeerConnectionBackend::createOfferSucceeded(String&& sdp)
95{
96 ASSERT(isMainThread());
97 ALWAYS_LOG(LOGIDENTIFIER, "Create offer succeeded:\n", sdp);
98
99 if (m_peerConnection.isClosed())
100 return;
101
102 ASSERT(m_offerAnswerPromise);
103 m_offerAnswerPromise->resolve(RTCSessionDescription::Init { RTCSdpType::Offer, filterSDP(WTFMove(sdp)) });
104 m_offerAnswerPromise = WTF::nullopt;
105}
106
107void PeerConnectionBackend::createOfferFailed(Exception&& exception)
108{
109 ASSERT(isMainThread());
110 ALWAYS_LOG(LOGIDENTIFIER, "Create offer failed:", exception.message());
111
112 if (m_peerConnection.isClosed())
113 return;
114
115 ASSERT(m_offerAnswerPromise);
116 m_offerAnswerPromise->reject(WTFMove(exception));
117 m_offerAnswerPromise = WTF::nullopt;
118}
119
120void PeerConnectionBackend::createAnswer(RTCAnswerOptions&& options, PeerConnection::SessionDescriptionPromise&& promise)
121{
122 ASSERT(!m_offerAnswerPromise);
123 ASSERT(!m_peerConnection.isClosed());
124
125 m_offerAnswerPromise = WTFMove(promise);
126 doCreateAnswer(WTFMove(options));
127}
128
129void PeerConnectionBackend::createAnswerSucceeded(String&& sdp)
130{
131 ASSERT(isMainThread());
132 ALWAYS_LOG(LOGIDENTIFIER, "Create answer succeeded:\n", sdp);
133
134 if (m_peerConnection.isClosed())
135 return;
136
137 ASSERT(m_offerAnswerPromise);
138 m_offerAnswerPromise->resolve(RTCSessionDescription::Init { RTCSdpType::Answer, WTFMove(sdp) });
139 m_offerAnswerPromise = WTF::nullopt;
140}
141
142void PeerConnectionBackend::createAnswerFailed(Exception&& exception)
143{
144 ASSERT(isMainThread());
145 ALWAYS_LOG(LOGIDENTIFIER, "Create answer failed:", exception.message());
146
147 if (m_peerConnection.isClosed())
148 return;
149
150 ASSERT(m_offerAnswerPromise);
151 m_offerAnswerPromise->reject(WTFMove(exception));
152 m_offerAnswerPromise = WTF::nullopt;
153}
154
155static inline bool isLocalDescriptionTypeValidForState(RTCSdpType type, RTCSignalingState state)
156{
157 switch (state) {
158 case RTCSignalingState::Stable:
159 return type == RTCSdpType::Offer;
160 case RTCSignalingState::HaveLocalOffer:
161 return type == RTCSdpType::Offer;
162 case RTCSignalingState::HaveRemoteOffer:
163 return type == RTCSdpType::Answer || type == RTCSdpType::Pranswer;
164 case RTCSignalingState::HaveLocalPranswer:
165 return type == RTCSdpType::Answer || type == RTCSdpType::Pranswer;
166 default:
167 return false;
168 };
169
170 ASSERT_NOT_REACHED();
171 return false;
172}
173
174void PeerConnectionBackend::setLocalDescription(RTCSessionDescription& sessionDescription, DOMPromiseDeferred<void>&& promise)
175{
176 ASSERT(!m_peerConnection.isClosed());
177
178 if (!isLocalDescriptionTypeValidForState(sessionDescription.type(), m_peerConnection.signalingState())) {
179 promise.reject(InvalidStateError, "Description type incompatible with current signaling state");
180 return;
181 }
182
183 m_setDescriptionPromise = WTFMove(promise);
184 doSetLocalDescription(sessionDescription);
185}
186
187void PeerConnectionBackend::setLocalDescriptionSucceeded()
188{
189 ASSERT(isMainThread());
190 ALWAYS_LOG(LOGIDENTIFIER);
191
192 if (m_peerConnection.isClosed())
193 return;
194
195 ASSERT(m_setDescriptionPromise);
196
197 m_setDescriptionPromise->resolve();
198 m_setDescriptionPromise = WTF::nullopt;
199}
200
201void PeerConnectionBackend::setLocalDescriptionFailed(Exception&& exception)
202{
203 ASSERT(isMainThread());
204 ALWAYS_LOG(LOGIDENTIFIER, "Set local description failed:", exception.message());
205
206 if (m_peerConnection.isClosed())
207 return;
208
209 ASSERT(m_setDescriptionPromise);
210
211 m_setDescriptionPromise->reject(WTFMove(exception));
212 m_setDescriptionPromise = WTF::nullopt;
213}
214
215static inline bool isRemoteDescriptionTypeValidForState(RTCSdpType type, RTCSignalingState state)
216{
217 switch (state) {
218 case RTCSignalingState::Stable:
219 return type == RTCSdpType::Offer;
220 case RTCSignalingState::HaveLocalOffer:
221 return type == RTCSdpType::Answer || type == RTCSdpType::Pranswer;
222 case RTCSignalingState::HaveRemoteOffer:
223 return type == RTCSdpType::Offer;
224 case RTCSignalingState::HaveRemotePranswer:
225 return type == RTCSdpType::Answer || type == RTCSdpType::Pranswer;
226 default:
227 return false;
228 };
229
230 ASSERT_NOT_REACHED();
231 return false;
232}
233
234void PeerConnectionBackend::setRemoteDescription(RTCSessionDescription& sessionDescription, DOMPromiseDeferred<void>&& promise)
235{
236 ASSERT(!m_peerConnection.isClosed());
237
238 if (!isRemoteDescriptionTypeValidForState(sessionDescription.type(), m_peerConnection.signalingState())) {
239 promise.reject(InvalidStateError, "Description type incompatible with current signaling state");
240 return;
241 }
242
243 m_setDescriptionPromise = WTFMove(promise);
244 doSetRemoteDescription(sessionDescription);
245}
246
247void PeerConnectionBackend::setRemoteDescriptionSucceeded()
248{
249 ASSERT(isMainThread());
250 ALWAYS_LOG(LOGIDENTIFIER, "Set remote description succeeded");
251
252 ASSERT(!m_peerConnection.isClosed());
253
254 auto events = WTFMove(m_pendingTrackEvents);
255 for (auto& event : events) {
256 auto& track = event.track.get();
257
258 m_peerConnection.fireEvent(RTCTrackEvent::create(eventNames().trackEvent, Event::CanBubble::No, Event::IsCancelable::No, WTFMove(event.receiver), WTFMove(event.track), WTFMove(event.streams), WTFMove(event.transceiver)));
259
260 if (m_peerConnection.isClosed())
261 return;
262
263 // FIXME: As per spec, we should set muted to 'false' when starting to receive the content from network.
264 track.source().setMuted(false);
265 }
266
267 if (m_peerConnection.isClosed())
268 return;
269
270 ASSERT(m_setDescriptionPromise);
271
272 m_setDescriptionPromise->resolve();
273 m_setDescriptionPromise = WTF::nullopt;
274}
275
276void PeerConnectionBackend::setRemoteDescriptionFailed(Exception&& exception)
277{
278 ASSERT(isMainThread());
279 ALWAYS_LOG(LOGIDENTIFIER, "Set remote description failed:", exception.message());
280
281 ASSERT(m_pendingTrackEvents.isEmpty());
282 m_pendingTrackEvents.clear();
283
284 ASSERT(!m_peerConnection.isClosed());
285 ASSERT(m_setDescriptionPromise);
286
287 m_setDescriptionPromise->reject(WTFMove(exception));
288 m_setDescriptionPromise = WTF::nullopt;
289}
290
291void PeerConnectionBackend::addPendingTrackEvent(PendingTrackEvent&& event)
292{
293 ASSERT(!m_peerConnection.isClosed());
294 m_pendingTrackEvents.append(WTFMove(event));
295}
296
297static String extractIPAddres(const String& sdp)
298{
299 ASSERT(sdp.contains(" host "));
300 unsigned counter = 0;
301 for (auto item : StringView { sdp }.split(' ')) {
302 if (++counter == 5)
303 return item.toString();
304 }
305 return { };
306}
307
308void PeerConnectionBackend::addIceCandidate(RTCIceCandidate* iceCandidate, DOMPromiseDeferred<void>&& promise)
309{
310 ASSERT(!m_peerConnection.isClosed());
311
312 if (!iceCandidate) {
313 endOfIceCandidates(WTFMove(promise));
314 return;
315 }
316
317 // FIXME: As per https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addicecandidate(), this check should be done before enqueuing the task.
318 if (iceCandidate->sdpMid().isNull() && !iceCandidate->sdpMLineIndex()) {
319 promise.reject(Exception { TypeError, "Trying to add a candidate that is missing both sdpMid and sdpMLineIndex"_s });
320 return;
321 }
322 m_addIceCandidatePromise = WTFMove(promise);
323 doAddIceCandidate(*iceCandidate);
324}
325
326void PeerConnectionBackend::addIceCandidateSucceeded()
327{
328 ASSERT(isMainThread());
329 ALWAYS_LOG(LOGIDENTIFIER, "Adding ice candidate succeeded");
330
331 if (m_peerConnection.isClosed())
332 return;
333
334 ASSERT(m_addIceCandidatePromise);
335
336 m_addIceCandidatePromise->resolve();
337 m_addIceCandidatePromise = WTF::nullopt;
338}
339
340void PeerConnectionBackend::addIceCandidateFailed(Exception&& exception)
341{
342 ASSERT(isMainThread());
343 ALWAYS_LOG(LOGIDENTIFIER, "Adding ice candidate failed:", exception.message());
344
345 if (m_peerConnection.isClosed())
346 return;
347
348 ASSERT(m_addIceCandidatePromise);
349
350 m_addIceCandidatePromise->reject(WTFMove(exception));
351 m_addIceCandidatePromise = WTF::nullopt;
352}
353
354void PeerConnectionBackend::fireICECandidateEvent(RefPtr<RTCIceCandidate>&& candidate, String&& serverURL)
355{
356 ASSERT(isMainThread());
357
358 m_peerConnection.fireEvent(RTCPeerConnectionIceEvent::create(Event::CanBubble::No, Event::IsCancelable::No, WTFMove(candidate), WTFMove(serverURL)));
359}
360
361void PeerConnectionBackend::enableICECandidateFiltering()
362{
363 m_shouldFilterICECandidates = true;
364}
365
366void PeerConnectionBackend::disableICECandidateFiltering()
367{
368 m_shouldFilterICECandidates = false;
369 for (auto& pendingICECandidate : m_pendingICECandidates)
370 fireICECandidateEvent(RTCIceCandidate::create(WTFMove(pendingICECandidate.sdp), WTFMove(pendingICECandidate.mid), pendingICECandidate.sdpMLineIndex), WTFMove(pendingICECandidate.serverURL));
371 m_pendingICECandidates.clear();
372}
373
374static String filterICECandidate(String&& sdp)
375{
376 ASSERT(!sdp.contains(" host "));
377
378 if (!sdp.contains(" raddr "))
379 return WTFMove(sdp);
380
381 bool skipNextItem = false;
382 bool isFirst = true;
383 StringBuilder filteredSDP;
384 sdp.split(' ', [&](StringView item) {
385 if (skipNextItem) {
386 skipNextItem = false;
387 return;
388 }
389 if (item == "raddr") {
390 filteredSDP.append(" raddr 0.0.0.0");
391 skipNextItem = true;
392 return;
393 }
394 if (item == "rport") {
395 filteredSDP.append(" rport 0");
396 skipNextItem = true;
397 return;
398 }
399 if (isFirst)
400 isFirst = false;
401 else
402 filteredSDP.append(' ');
403 filteredSDP.append(item);
404 });
405 return filteredSDP.toString();
406}
407
408String PeerConnectionBackend::filterSDP(String&& sdp) const
409{
410 if (!m_shouldFilterICECandidates)
411 return WTFMove(sdp);
412
413 StringBuilder filteredSDP;
414 sdp.split('\n', [&filteredSDP](StringView line) {
415 if (!line.startsWith("a=candidate"))
416 filteredSDP.append(line);
417 else if (line.find(" host ", 11) == notFound)
418 filteredSDP.append(filterICECandidate(line.toString()));
419 else
420 return;
421 filteredSDP.append('\n');
422 });
423 return filteredSDP.toString();
424}
425
426void PeerConnectionBackend::newICECandidate(String&& sdp, String&& mid, unsigned short sdpMLineIndex, String&& serverURL)
427{
428 ALWAYS_LOG(LOGIDENTIFIER, "Gathered ice candidate:", sdp);
429 m_finishedGatheringCandidates = false;
430
431 if (!m_shouldFilterICECandidates) {
432 fireICECandidateEvent(RTCIceCandidate::create(WTFMove(sdp), WTFMove(mid), sdpMLineIndex), WTFMove(serverURL));
433 return;
434 }
435 if (sdp.find(" host ", 0) != notFound) {
436 // FIXME: We might need to clear all pending candidates when setting again local description.
437 m_pendingICECandidates.append(PendingICECandidate { String { sdp }, WTFMove(mid), sdpMLineIndex, WTFMove(serverURL) });
438 if (RuntimeEnabledFeatures::sharedFeatures().webRTCMDNSICECandidatesEnabled()) {
439 auto ipAddress = extractIPAddres(sdp);
440 // We restrict to IPv4 candidates for now.
441 if (ipAddress.contains('.'))
442 registerMDNSName(ipAddress);
443 }
444 return;
445 }
446 fireICECandidateEvent(RTCIceCandidate::create(filterICECandidate(WTFMove(sdp)), WTFMove(mid), sdpMLineIndex), WTFMove(serverURL));
447}
448
449void PeerConnectionBackend::doneGatheringCandidates()
450{
451 ASSERT(isMainThread());
452 ALWAYS_LOG(LOGIDENTIFIER, "Finished ice candidate gathering");
453 m_finishedGatheringCandidates = true;
454
455 if (m_waitingForMDNSRegistration)
456 return;
457
458 m_peerConnection.fireEvent(RTCPeerConnectionIceEvent::create(Event::CanBubble::No, Event::IsCancelable::No, nullptr, { }));
459 m_peerConnection.updateIceGatheringState(RTCIceGatheringState::Complete);
460 m_pendingICECandidates.clear();
461}
462
463void PeerConnectionBackend::registerMDNSName(const String& ipAddress)
464{
465 ++m_waitingForMDNSRegistration;
466 auto& document = downcast<Document>(*m_peerConnection.scriptExecutionContext());
467 auto& provider = document.page()->libWebRTCProvider();
468 provider.registerMDNSName(document.sessionID(), document.identifier().toUInt64(), ipAddress, [peerConnection = makeRef(m_peerConnection), this, ipAddress] (LibWebRTCProvider::MDNSNameOrError&& result) {
469 if (peerConnection->isStopped())
470 return;
471
472 --m_waitingForMDNSRegistration;
473 if (!result.has_value()) {
474 m_peerConnection.scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, makeString("MDNS registration of a host candidate failed with error", (unsigned)result.error()));
475 return;
476 }
477
478 this->finishedRegisteringMDNSName(ipAddress, result.value());
479 });
480}
481
482void PeerConnectionBackend::finishedRegisteringMDNSName(const String& ipAddress, const String& name)
483{
484 Vector<PendingICECandidate*> candidates;
485 for (auto& candidate : m_pendingICECandidates) {
486 if (candidate.sdp.find(ipAddress) != notFound) {
487 auto sdp = candidate.sdp;
488 sdp.replace(ipAddress, name);
489 fireICECandidateEvent(RTCIceCandidate::create(WTFMove(sdp), WTFMove(candidate.mid), candidate.sdpMLineIndex), WTFMove(candidate.serverURL));
490 candidates.append(&candidate);
491 }
492 }
493 m_pendingICECandidates.removeAllMatching([&] (const auto& candidate) {
494 return candidates.contains(&candidate);
495 });
496
497 if (!m_waitingForMDNSRegistration && m_finishedGatheringCandidates)
498 doneGatheringCandidates();
499}
500
501void PeerConnectionBackend::updateSignalingState(RTCSignalingState newSignalingState)
502{
503 ASSERT(isMainThread());
504
505 if (newSignalingState != m_peerConnection.signalingState()) {
506 m_peerConnection.setSignalingState(newSignalingState);
507 m_peerConnection.fireEvent(Event::create(eventNames().signalingstatechangeEvent, Event::CanBubble::No, Event::IsCancelable::No));
508 }
509}
510
511void PeerConnectionBackend::stop()
512{
513 m_offerAnswerPromise = WTF::nullopt;
514 m_setDescriptionPromise = WTF::nullopt;
515 m_addIceCandidatePromise = WTF::nullopt;
516
517 m_pendingTrackEvents.clear();
518
519 doStop();
520}
521
522void PeerConnectionBackend::markAsNeedingNegotiation()
523{
524 if (m_negotiationNeeded)
525 return;
526
527 m_negotiationNeeded = true;
528
529 if (m_peerConnection.signalingState() == RTCSignalingState::Stable)
530 m_peerConnection.scheduleNegotiationNeededEvent();
531}
532
533ExceptionOr<Ref<RTCRtpSender>> PeerConnectionBackend::addTrack(MediaStreamTrack&, Vector<String>&&)
534{
535 return Exception { NotSupportedError, "Not implemented"_s };
536}
537
538ExceptionOr<Ref<RTCRtpTransceiver>> PeerConnectionBackend::addTransceiver(const String&, const RTCRtpTransceiverInit&)
539{
540 return Exception { NotSupportedError, "Not implemented"_s };
541}
542
543ExceptionOr<Ref<RTCRtpTransceiver>> PeerConnectionBackend::addTransceiver(Ref<MediaStreamTrack>&&, const RTCRtpTransceiverInit&)
544{
545 return Exception { NotSupportedError, "Not implemented"_s };
546}
547
548void PeerConnectionBackend::generateCertificate(Document& document, const CertificateInformation& info, DOMPromiseDeferred<IDLInterface<RTCCertificate>>&& promise)
549{
550#if USE(LIBWEBRTC)
551 LibWebRTCCertificateGenerator::generateCertificate(document.securityOrigin(), document.page()->libWebRTCProvider(), info, WTFMove(promise));
552#else
553 UNUSED_PARAM(document);
554 UNUSED_PARAM(expires);
555 UNUSED_PARAM(type);
556 promise.reject(NotSupportedError);
557#endif
558}
559
560#if !RELEASE_LOG_DISABLED
561WTFLogChannel& PeerConnectionBackend::logChannel() const
562{
563 return LogWebRTC;
564}
565#endif
566
567} // namespace WebCore
568
569#endif // ENABLE(WEB_RTC)
570