1/*
2 * Copyright (C) 2017 Apple Inc.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26#include "MockLibWebRTCPeerConnection.h"
27
28#if USE(LIBWEBRTC)
29
30#include "LibWebRTCProvider.h"
31#include <sstream>
32#include <webrtc/pc/mediastream.h>
33#include <wtf/Function.h>
34#include <wtf/MainThread.h>
35#include <wtf/NeverDestroyed.h>
36#include <wtf/Threading.h>
37
38namespace WebCore {
39
40static inline rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>& getRealPeerConnectionFactory()
41{
42 static NeverDestroyed<rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>> realPeerConnectionFactory;
43 return realPeerConnectionFactory;
44}
45
46static inline webrtc::PeerConnectionFactoryInterface* realPeerConnectionFactory()
47{
48 return getRealPeerConnectionFactory().get();
49}
50
51void useRealRTCPeerConnectionFactory(LibWebRTCProvider& provider)
52{
53 auto& factory = getRealPeerConnectionFactory();
54 if (!factory)
55 return;
56 provider.setPeerConnectionFactory(factory.get());
57 factory = nullptr;
58}
59
60void useMockRTCPeerConnectionFactory(LibWebRTCProvider* provider, const String& testCase)
61{
62 if (!provider)
63 return;
64
65 if (!realPeerConnectionFactory()) {
66 auto& factory = getRealPeerConnectionFactory();
67 factory = provider->factory();
68 }
69 provider->setPeerConnectionFactory(MockLibWebRTCPeerConnectionFactory::create(testCase));
70}
71
72MockLibWebRTCPeerConnection::~MockLibWebRTCPeerConnection()
73{
74 // Free senders in a different thread like an actual peer connection would probably do.
75 Thread::create("MockLibWebRTCPeerConnection thread", [senders = WTFMove(m_senders)] { });
76}
77
78class MockLibWebRTCPeerConnectionForIceCandidates : public MockLibWebRTCPeerConnection {
79public:
80 explicit MockLibWebRTCPeerConnectionForIceCandidates(webrtc::PeerConnectionObserver& observer) : MockLibWebRTCPeerConnection(observer) { }
81 virtual ~MockLibWebRTCPeerConnectionForIceCandidates() = default;
82private:
83 void gotLocalDescription() final;
84};
85
86void MockLibWebRTCPeerConnectionForIceCandidates::gotLocalDescription()
87{
88 // Let's gather candidates
89 LibWebRTCProvider::callOnWebRTCSignalingThread([this]() {
90 MockLibWebRTCIceCandidate candidate("2013266431 1 udp 2013266432 192.168.0.100 38838 typ host generation 0", "1");
91 m_observer.OnIceCandidate(&candidate);
92 });
93 LibWebRTCProvider::callOnWebRTCSignalingThread([this]() {
94 MockLibWebRTCIceCandidate candidate("1019216383 1 tcp 1019216384 192.168.0.100 9 typ host tcptype passive generation 0", "1");
95 m_observer.OnIceCandidate(&candidate);
96 });
97 LibWebRTCProvider::callOnWebRTCSignalingThread([this]() {
98 MockLibWebRTCIceCandidate candidate("1677722111 1 tcp 1677722112 172.18.0.1 47989 typ srflx raddr 192.168.0.100 rport 47989 generation 0", "1");
99 m_observer.OnIceCandidate(&candidate);
100 });
101 LibWebRTCProvider::callOnWebRTCSignalingThread([this]() {
102 m_observer.OnIceGatheringChange(webrtc::PeerConnectionInterface::kIceGatheringComplete);
103 });
104}
105
106class MockLibWebRTCPeerConnectionForIceConnectionState : public MockLibWebRTCPeerConnection {
107public:
108 explicit MockLibWebRTCPeerConnectionForIceConnectionState(webrtc::PeerConnectionObserver& observer) : MockLibWebRTCPeerConnection(observer) { }
109 virtual ~MockLibWebRTCPeerConnectionForIceConnectionState() = default;
110
111private:
112 void gotLocalDescription() final;
113};
114
115void MockLibWebRTCPeerConnectionForIceConnectionState::gotLocalDescription()
116{
117 m_observer.OnIceConnectionChange(kIceConnectionChecking);
118 m_observer.OnIceConnectionChange(kIceConnectionConnected);
119 m_observer.OnIceConnectionChange(kIceConnectionCompleted);
120 m_observer.OnIceConnectionChange(kIceConnectionFailed);
121 m_observer.OnIceConnectionChange(kIceConnectionDisconnected);
122 m_observer.OnIceConnectionChange(kIceConnectionNew);
123}
124
125template<typename U> static inline void releaseInNetworkThread(MockLibWebRTCPeerConnection& mock, U& observer)
126{
127 mock.AddRef();
128 observer.AddRef();
129 callOnMainThread([&mock, &observer] {
130 LibWebRTCProvider::callOnWebRTCNetworkThread([&mock, &observer]() {
131 observer.Release();
132 mock.Release();
133 });
134 });
135}
136
137class MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileCreatingOffer : public MockLibWebRTCPeerConnection {
138public:
139 explicit MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileCreatingOffer(webrtc::PeerConnectionObserver& observer) : MockLibWebRTCPeerConnection(observer) { }
140 virtual ~MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileCreatingOffer() = default;
141
142private:
143 void CreateOffer(webrtc::CreateSessionDescriptionObserver* observer, const webrtc::PeerConnectionInterface::RTCOfferAnswerOptions&) final { releaseInNetworkThread(*this, *observer); }
144};
145
146class MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileGettingStats : public MockLibWebRTCPeerConnection {
147public:
148 explicit MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileGettingStats(webrtc::PeerConnectionObserver& observer) : MockLibWebRTCPeerConnection(observer) { }
149 virtual ~MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileGettingStats() = default;
150
151private:
152 bool GetStats(webrtc::StatsObserver*, webrtc::MediaStreamTrackInterface*, StatsOutputLevel) final;
153};
154
155bool MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileGettingStats::GetStats(webrtc::StatsObserver* observer, webrtc::MediaStreamTrackInterface*, StatsOutputLevel)
156{
157 releaseInNetworkThread(*this, *observer);
158 return true;
159}
160
161class MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileSettingDescription : public MockLibWebRTCPeerConnection {
162public:
163 explicit MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileSettingDescription(webrtc::PeerConnectionObserver& observer) : MockLibWebRTCPeerConnection(observer) { }
164 virtual ~MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileSettingDescription() = default;
165
166private:
167 void SetLocalDescription(webrtc::SetSessionDescriptionObserver* observer, webrtc::SessionDescriptionInterface* sessionDescription) final
168 {
169 std::unique_ptr<webrtc::SessionDescriptionInterface> toBeFreed(sessionDescription);
170 releaseInNetworkThread(*this, *observer);
171 }
172};
173
174MockLibWebRTCPeerConnectionFactory::MockLibWebRTCPeerConnectionFactory(const String& testCase)
175 : m_testCase(testCase.isolatedCopy())
176{
177}
178
179rtc::scoped_refptr<webrtc::PeerConnectionInterface> MockLibWebRTCPeerConnectionFactory::CreatePeerConnection(const webrtc::PeerConnectionInterface::RTCConfiguration&, webrtc::PeerConnectionDependencies dependencies)
180{
181 if (m_testCase == "ICECandidates")
182 return new rtc::RefCountedObject<MockLibWebRTCPeerConnectionForIceCandidates>(*dependencies.observer);
183
184 if (m_testCase == "ICEConnectionState")
185 return new rtc::RefCountedObject<MockLibWebRTCPeerConnectionForIceConnectionState>(*dependencies.observer);
186
187 if (m_testCase == "LibWebRTCReleasingWhileCreatingOffer")
188 return new rtc::RefCountedObject<MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileCreatingOffer>(*dependencies.observer);
189
190 if (m_testCase == "LibWebRTCReleasingWhileGettingStats")
191 return new rtc::RefCountedObject<MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileGettingStats>(*dependencies.observer);
192
193 if (m_testCase == "LibWebRTCReleasingWhileSettingDescription")
194 return new rtc::RefCountedObject<MockLibWebRTCPeerConnectionReleasedInNetworkThreadWhileSettingDescription>(*dependencies.observer);
195
196 return new rtc::RefCountedObject<MockLibWebRTCPeerConnection>(*dependencies.observer);
197}
198
199rtc::scoped_refptr<webrtc::VideoTrackInterface> MockLibWebRTCPeerConnectionFactory::CreateVideoTrack(const std::string& id, webrtc::VideoTrackSourceInterface* source)
200{
201 return new rtc::RefCountedObject<MockLibWebRTCVideoTrack>(id, source);
202}
203
204rtc::scoped_refptr<webrtc::AudioTrackInterface> MockLibWebRTCPeerConnectionFactory::CreateAudioTrack(const std::string& id, webrtc::AudioSourceInterface* source)
205{
206 return new rtc::RefCountedObject<MockLibWebRTCAudioTrack>(id, source);
207}
208
209rtc::scoped_refptr<webrtc::MediaStreamInterface> MockLibWebRTCPeerConnectionFactory::CreateLocalMediaStream(const std::string& label)
210{
211 return new rtc::RefCountedObject<webrtc::MediaStream>(label);
212}
213
214void MockLibWebRTCPeerConnection::SetLocalDescription(webrtc::SetSessionDescriptionObserver* observer, webrtc::SessionDescriptionInterface* sessionDescription)
215{
216 std::unique_ptr<webrtc::SessionDescriptionInterface> toBeFreed(sessionDescription);
217 LibWebRTCProvider::callOnWebRTCSignalingThread([this, observer] {
218 observer->OnSuccess();
219 gotLocalDescription();
220 });
221}
222
223void MockLibWebRTCPeerConnection::SetRemoteDescription(webrtc::SetSessionDescriptionObserver* observer, webrtc::SessionDescriptionInterface* sessionDescription)
224{
225 std::unique_ptr<webrtc::SessionDescriptionInterface> toBeFreed(sessionDescription);
226 LibWebRTCProvider::callOnWebRTCSignalingThread([observer] {
227 observer->OnSuccess();
228 });
229 ASSERT(sessionDescription);
230 if (sessionDescription->type() == "offer") {
231 std::string sdp;
232 sessionDescription->ToString(&sdp);
233
234 m_isInitiator = false;
235 m_isReceivingAudio = sdp.find("m=audio") != std::string::npos;
236 m_isReceivingVideo = sdp.find("m=video") != std::string::npos;
237 }
238}
239
240rtc::scoped_refptr<webrtc::DataChannelInterface> MockLibWebRTCPeerConnection::CreateDataChannel(const std::string& label, const webrtc::DataChannelInit* init)
241{
242 webrtc::DataChannelInit parameters;
243 if (init)
244 parameters = *init;
245 return new rtc::RefCountedObject<MockLibWebRTCDataChannel>(std::string(label), parameters.ordered, parameters.reliable, parameters.id);
246}
247
248webrtc::RTCErrorOr<rtc::scoped_refptr<webrtc::RtpSenderInterface>> MockLibWebRTCPeerConnection::AddTrack(rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track, const std::vector<std::string>& streamIds)
249{
250 LibWebRTCProvider::callOnWebRTCSignalingThread([observer = &m_observer] {
251 observer->OnRenegotiationNeeded();
252 });
253
254 if (!streamIds.empty())
255 m_streamLabel = streamIds.front();
256
257 m_senders.append(new rtc::RefCountedObject<MockRtpSender>(WTFMove(track)));
258 return rtc::scoped_refptr<webrtc::RtpSenderInterface>(m_senders.last().get());
259}
260
261bool MockLibWebRTCPeerConnection::RemoveTrack(webrtc::RtpSenderInterface* sender)
262{
263 LibWebRTCProvider::callOnWebRTCSignalingThread([observer = &m_observer] {
264 observer->OnRenegotiationNeeded();
265 });
266 bool isRemoved = false;
267 return m_senders.removeFirstMatching([&](auto& item) {
268 if (item.get() != sender)
269 return false;
270 isRemoved = true;
271 return true;
272 });
273}
274
275void MockLibWebRTCPeerConnection::CreateOffer(webrtc::CreateSessionDescriptionObserver* observer, const webrtc::PeerConnectionInterface::RTCOfferAnswerOptions&)
276{
277 LibWebRTCProvider::callOnWebRTCSignalingThread([this, observer] {
278 std::ostringstream sdp;
279 sdp <<
280 "v=0\r\n"
281 "o=- 5667094644266930845 " << m_counter++ << " IN IP4 127.0.0.1\r\n"
282 "s=-\r\n"
283 "t=0 0\r\n";
284 if (m_senders.size()) {
285 unsigned partCounter = 1;
286 sdp << "a=msid-semantic:WMS " << m_streamLabel << "\r\n";
287 for (auto& sender : m_senders) {
288 auto track = sender->track();
289 if (track->kind() != "audio")
290 continue;
291 sdp <<
292 "m=audio 9 UDP/TLS/RTP/SAVPF 111 8 0\r\n"
293 "c=IN IP4 0.0.0.0\r\n"
294 "a=rtcp-mux\r\n"
295 "a=sendrecv\r\n"
296 "a=mid:part" << partCounter++ << "\r\n"
297 "a=rtpmap:111 OPUS/48000/2\r\n"
298 "a=rtpmap:8 PCMA/8000\r\n"
299 "a=rtpmap:0 PCMU/8000\r\n"
300 "a=ssrc:3409173717 cname:/chKzCS9K6KOgL0n\r\n"
301 "a=msid:" << m_streamLabel << " " << track->id() << "\r\n"
302 "a=ice-ufrag:e/B1\r\n"
303 "a=ice-pwd:Yotk3Im3mnyi+1Q38p51MDub\r\n"
304 "a=fingerprint:sha-256 8B:87:09:8A:5D:C2:F3:33:EF:C5:B1:F6:84:3A:3D:D6:A3:E2:9C:17:4C:E7:46:3B:1B:CE:84:98:DD:8E:AF:7B\r\n"
305 "a=setup:actpass\r\n";
306 }
307 for (auto& sender : m_senders) {
308 auto track = sender->track();
309 if (track->kind() != "video")
310 continue;
311 sdp <<
312 "m=video 9 UDP/TLS/RTP/SAVPF 103 100 120\r\n"
313 "c=IN IP4 0.0.0.0\r\n"
314 "a=rtcp-mux\r\n"
315 "a=sendrecv\r\n"
316 "a=mid:part" << partCounter++ << "\r\n"
317 "a=rtpmap:103 H264/90000\r\n"
318 "a=rtpmap:100 VP8/90000\r\n"
319 "a=rtpmap:120 RTX/90000\r\n"
320 "a=fmtp:103 packetization-mode=1\r\n"
321 "a=fmtp:120 apt=100;rtx-time=200\r\n"
322 "a=rtcp-fb:100 nack\r\n"
323 "a=rtcp-fb:103 nack pli\r\n"
324 "a=rtcp-fb:100 nack pli\r\n"
325 "a=rtcp-fb:103 ccm fir\r\n"
326 "a=rtcp-fb:100 ccm fir\r\n"
327 "a=ssrc:3409173718 cname:/chKzCS9K6KOgL0n\r\n"
328 "a=msid:" << m_streamLabel << " " << track->id() << "\r\n"
329 "a=ice-ufrag:e/B1\r\n"
330 "a=ice-pwd:Yotk3Im3mnyi+1Q38p51MDub\r\n"
331 "a=fingerprint:sha-256 8B:87:09:8A:5D:C2:F3:33:EF:C5:B1:F6:84:3A:3D:D6:A3:E2:9C:17:4C:E7:46:3B:1B:CE:84:98:DD:8E:AF:7B\r\n"
332 "a=setup:actpass\r\n";
333 }
334 }
335 observer->OnSuccess(new MockLibWebRTCSessionDescription(sdp.str()));
336 });
337}
338
339 void MockLibWebRTCPeerConnection::CreateAnswer(webrtc::CreateSessionDescriptionObserver* observer, const webrtc::PeerConnectionInterface::RTCOfferAnswerOptions&)
340{
341 LibWebRTCProvider::callOnWebRTCSignalingThread([this, observer] {
342 std::ostringstream sdp;
343 sdp <<
344 "v=0\r\n"
345 "o=- 5667094644266930846 " << m_counter++ << " IN IP4 127.0.0.1\r\n"
346 "s=-\r\n"
347 "t=0 0\r\n";
348 if (m_senders.size()) {
349 for (auto& sender : m_senders) {
350 auto track = sender->track();
351 if (track->kind() != "audio")
352 continue;
353 sdp <<
354 "m=audio 9 UDP/TLS/RTP/SAVPF 111 8 0\r\n"
355 "c=IN IP4 0.0.0.0\r\n"
356 "a=rtcp-mux\r\n"
357 "a=recvonly\r\n"
358 "a=mid:part1\r\n"
359 "a=rtpmap:111 OPUS/48000/2\r\n"
360 "a=rtpmap:8 PCMA/8000\r\n"
361 "a=rtpmap:0 PCMU/8000\r\n"
362 "a=ssrc:3409173717 cname:/chKzCS9K6KOgL0m\r\n"
363 "a=ice-ufrag:e/B1\r\n"
364 "a=ice-pwd:Yotk3Im3mnyi+1Q38p51MDub\r\n"
365 "a=fingerprint:sha-256 8B:87:09:8A:5D:C2:F3:33:EF:C5:B1:F6:84:3A:3D:D6:A3:E2:9C:17:4C:E7:46:3B:1B:CE:84:98:DD:8E:AF:7B\r\n"
366 "a=setup:active\r\n";
367 }
368 for (auto& sender : m_senders) {
369 auto track = sender->track();
370 if (track->kind() != "video")
371 continue;
372 sdp <<
373 "m=video 9 UDP/TLS/RTP/SAVPF 103 100 120\r\n"
374 "c=IN IP4 0.0.0.0\r\n"
375 "a=rtcp-mux\r\n"
376 "a=recvonly\r\n"
377 "a=mid:part2\r\n"
378 "a=rtpmap:103 H264/90000\r\n"
379 "a=rtpmap:100 VP8/90000\r\n"
380 "a=rtpmap:120 RTX/90000\r\n"
381 "a=fmtp:103 packetization-mode=1\r\n"
382 "a=fmtp:120 apt=100;rtx-time=200\r\n"
383 "a=rtcp-fb:100 nack\r\n"
384 "a=rtcp-fb:103 nack pli\r\n"
385 "a=rtcp-fb:100 nack pli\r\n"
386 "a=rtcp-fb:103 ccm fir\r\n"
387 "a=rtcp-fb:100 ccm fir\r\n"
388 "a=ssrc:3409173718 cname:/chKzCS9K6KOgL0n\r\n"
389 "a=ice-ufrag:e/B1\r\n"
390 "a=ice-pwd:Yotk3Im3mnyi+1Q38p51MDub\r\n"
391 "a=fingerprint:sha-256 8B:87:09:8A:5D:C2:F3:33:EF:C5:B1:F6:84:3A:3D:D6:A3:E2:9C:17:4C:E7:46:3B:1B:CE:84:98:DD:8E:AF:7B\r\n"
392 "a=setup:active\r\n";
393 }
394 } else if (!m_isInitiator) {
395 if (m_isReceivingAudio) {
396 sdp <<
397 "m=audio 9 UDP/TLS/RTP/SAVPF 111 8 0\r\n"
398 "c=IN IP4 0.0.0.0\r\n"
399 "a=rtcp-mux\r\n"
400 "a=recvonly\r\n"
401 "a=mid:part1\r\n"
402 "a=rtpmap:111 OPUS/48000/2\r\n"
403 "a=rtpmap:8 PCMA/8000\r\n"
404 "a=rtpmap:0 PCMU/8000\r\n"
405 "a=ssrc:3409173717 cname:/chKzCS9K6KOgL0m\r\n"
406 "a=ice-ufrag:e/B1\r\n"
407 "a=ice-pwd:Yotk3Im3mnyi+1Q38p51MDub\r\n"
408 "a=fingerprint:sha-256 8B:87:09:8A:5D:C2:F3:33:EF:C5:B1:F6:84:3A:3D:D6:A3:E2:9C:17:4C:E7:46:3B:1B:CE:84:98:DD:8E:AF:7B\r\n"
409 "a=setup:active\r\n";
410 }
411 if (m_isReceivingVideo) {
412 sdp <<
413 "m=video 9 UDP/TLS/RTP/SAVPF 103 100 120\r\n"
414 "c=IN IP4 0.0.0.0\r\n"
415 "a=rtcp-mux\r\n"
416 "a=recvonly\r\n"
417 "a=mid:part2\r\n"
418 "a=rtpmap:103 H264/90000\r\n"
419 "a=rtpmap:100 VP8/90000\r\n"
420 "a=rtpmap:120 RTX/90000\r\n"
421 "a=fmtp:103 packetization-mode=1\r\n"
422 "a=fmtp:120 apt=100;rtx-time=200\r\n"
423 "a=rtcp-fb:100 nack\r\n"
424 "a=rtcp-fb:103 nack pli\r\n"
425 "a=rtcp-fb:100 nack pli\r\n"
426 "a=rtcp-fb:103 ccm fir\r\n"
427 "a=rtcp-fb:100 ccm fir\r\n"
428 "a=ssrc:3409173718 cname:/chKzCS9K6KOgL0n\r\n"
429 "a=ice-ufrag:e/B1\r\n"
430 "a=ice-pwd:Yotk3Im3mnyi+1Q38p51MDub\r\n"
431 "a=fingerprint:sha-256 8B:87:09:8A:5D:C2:F3:33:EF:C5:B1:F6:84:3A:3D:D6:A3:E2:9C:17:4C:E7:46:3B:1B:CE:84:98:DD:8E:AF:7B\r\n"
432 "a=setup:active\r\n";
433 }
434 }
435 observer->OnSuccess(new MockLibWebRTCSessionDescription(sdp.str()));
436 });
437}
438
439} // namespace WebCore
440
441#endif // USE(LIBWEBRTC)
442