blob: 8f1792b71726d809fbe9846b31dbb9648c956c8d [file] [log] [blame]
Steve Anton8d3444d2017-10-20 15:30:51 -07001/*
2 * Copyright 2017 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11// This file contains tests that check the PeerConnection's signaling state
12// machine, as well as tests that check basic, media-agnostic aspects of SDP.
13
14#include <tuple>
15
16#include "api/audio_codecs/builtin_audio_decoder_factory.h"
17#include "api/audio_codecs/builtin_audio_encoder_factory.h"
18#include "api/peerconnectionproxy.h"
19#include "pc/peerconnection.h"
20#include "pc/peerconnectionwrapper.h"
21#include "pc/sdputils.h"
22#ifdef WEBRTC_ANDROID
23#include "pc/test/androidtestinitializer.h"
24#endif
25#include "pc/test/fakeaudiocapturemodule.h"
26#include "pc/test/fakertccertificategenerator.h"
27#include "rtc_base/gunit.h"
28#include "rtc_base/ptr_util.h"
29#include "rtc_base/stringutils.h"
30#include "rtc_base/virtualsocketserver.h"
31#include "test/gmock.h"
32
33namespace webrtc {
34
35using SignalingState = PeerConnectionInterface::SignalingState;
36using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
37using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
38using ::testing::Bool;
39using ::testing::Combine;
40using ::testing::Values;
41
42class PeerConnectionWrapperForSignalingTest : public PeerConnectionWrapper {
43 public:
44 using PeerConnectionWrapper::PeerConnectionWrapper;
45
46 bool initial_offerer() {
47 return GetInternalPeerConnection()->initial_offerer();
48 }
49
50 PeerConnection* GetInternalPeerConnection() {
Mirko Bonadeie97de912017-12-13 11:29:34 +010051 auto* pci =
52 static_cast<PeerConnectionProxyWithInternal<PeerConnectionInterface>*>(
53 pc());
54 return static_cast<PeerConnection*>(pci->internal());
Steve Anton8d3444d2017-10-20 15:30:51 -070055 }
56};
57
Oleh Prypinc22d6a82018-02-02 08:42:18 +000058class PeerConnectionSignalingTest : public ::testing::Test {
Steve Anton8d3444d2017-10-20 15:30:51 -070059 protected:
60 typedef std::unique_ptr<PeerConnectionWrapperForSignalingTest> WrapperPtr;
61
Oleh Prypinc22d6a82018-02-02 08:42:18 +000062 PeerConnectionSignalingTest()
63 : vss_(new rtc::VirtualSocketServer()), main_(vss_.get()) {
Steve Anton8d3444d2017-10-20 15:30:51 -070064#ifdef WEBRTC_ANDROID
65 InitializeAndroidObjects();
66#endif
67 pc_factory_ = CreatePeerConnectionFactory(
68 rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
69 FakeAudioCaptureModule::Create(), CreateBuiltinAudioEncoderFactory(),
70 CreateBuiltinAudioDecoderFactory(), nullptr, nullptr);
71 }
72
73 WrapperPtr CreatePeerConnection() {
74 return CreatePeerConnection(RTCConfiguration());
75 }
76
77 WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
78 auto observer = rtc::MakeUnique<MockPeerConnectionObserver>();
Oleh Prypinc22d6a82018-02-02 08:42:18 +000079 auto pc = pc_factory_->CreatePeerConnection(config, nullptr, nullptr,
80 observer.get());
Steve Anton8d3444d2017-10-20 15:30:51 -070081 if (!pc) {
82 return nullptr;
83 }
84
85 return rtc::MakeUnique<PeerConnectionWrapperForSignalingTest>(
86 pc_factory_, pc, std::move(observer));
87 }
88
89 // Accepts the same arguments as CreatePeerConnection and adds default audio
90 // and video tracks.
91 template <typename... Args>
92 WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) {
93 auto wrapper = CreatePeerConnection(std::forward<Args>(args)...);
94 if (!wrapper) {
95 return nullptr;
96 }
97 wrapper->AddAudioTrack("a");
98 wrapper->AddVideoTrack("v");
99 return wrapper;
100 }
101
102 std::unique_ptr<rtc::VirtualSocketServer> vss_;
103 rtc::AutoSocketServerThread main_;
104 rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
105};
106
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000107TEST_F(PeerConnectionSignalingTest, SetLocalOfferTwiceWorks) {
Steve Anton8d3444d2017-10-20 15:30:51 -0700108 auto caller = CreatePeerConnection();
109
110 EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
111 EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
112}
113
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000114TEST_F(PeerConnectionSignalingTest, SetRemoteOfferTwiceWorks) {
Steve Anton8d3444d2017-10-20 15:30:51 -0700115 auto caller = CreatePeerConnection();
116 auto callee = CreatePeerConnection();
117
118 EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOffer()));
119 EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOffer()));
120}
121
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000122TEST_F(PeerConnectionSignalingTest, FailToSetNullLocalDescription) {
Steve Anton8d3444d2017-10-20 15:30:51 -0700123 auto caller = CreatePeerConnection();
124 std::string error;
125 ASSERT_FALSE(caller->SetLocalDescription(nullptr, &error));
126 EXPECT_EQ("SessionDescription is NULL.", error);
127}
128
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000129TEST_F(PeerConnectionSignalingTest, FailToSetNullRemoteDescription) {
Steve Anton8d3444d2017-10-20 15:30:51 -0700130 auto caller = CreatePeerConnection();
131 std::string error;
132 ASSERT_FALSE(caller->SetRemoteDescription(nullptr, &error));
133 EXPECT_EQ("SessionDescription is NULL.", error);
134}
135
136// The following parameterized test verifies that calls to various signaling
137// methods on PeerConnection will succeed/fail depending on what is the
138// PeerConnection's signaling state. Note that the test tries many different
139// forms of SignalingState::kClosed by arriving at a valid state then calling
140// |Close()|. This is intended to catch cases where the PeerConnection signaling
141// method ignores the closed flag but may work/not work because of the single
142// state the PeerConnection was created in before it was closed.
143
144class PeerConnectionSignalingStateTest
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000145 : public PeerConnectionSignalingTest,
146 public ::testing::WithParamInterface<std::tuple<SignalingState, bool>> {
Steve Anton8d3444d2017-10-20 15:30:51 -0700147 protected:
148 RTCConfiguration GetConfig() {
149 RTCConfiguration config;
150 config.certificates.push_back(
151 FakeRTCCertificateGenerator::GenerateCertificate());
152 return config;
153 }
154
155 WrapperPtr CreatePeerConnectionInState(SignalingState state) {
156 return CreatePeerConnectionInState(std::make_tuple(state, false));
157 }
158
159 WrapperPtr CreatePeerConnectionInState(
160 std::tuple<SignalingState, bool> state_tuple) {
161 SignalingState state = std::get<0>(state_tuple);
162 bool closed = std::get<1>(state_tuple);
163
164 auto wrapper = CreatePeerConnectionWithAudioVideo(GetConfig());
165 switch (state) {
166 case SignalingState::kStable: {
167 break;
168 }
169 case SignalingState::kHaveLocalOffer: {
170 wrapper->SetLocalDescription(wrapper->CreateOffer());
171 break;
172 }
173 case SignalingState::kHaveLocalPrAnswer: {
174 auto caller = CreatePeerConnectionWithAudioVideo(GetConfig());
175 wrapper->SetRemoteDescription(caller->CreateOffer());
176 auto answer = wrapper->CreateAnswer();
Steve Antona3a92c22017-12-07 10:27:41 -0800177 wrapper->SetLocalDescription(
178 CloneSessionDescriptionAsType(answer.get(), SdpType::kPrAnswer));
Steve Anton8d3444d2017-10-20 15:30:51 -0700179 break;
180 }
181 case SignalingState::kHaveRemoteOffer: {
182 auto caller = CreatePeerConnectionWithAudioVideo(GetConfig());
183 wrapper->SetRemoteDescription(caller->CreateOffer());
184 break;
185 }
186 case SignalingState::kHaveRemotePrAnswer: {
187 auto callee = CreatePeerConnectionWithAudioVideo(GetConfig());
188 callee->SetRemoteDescription(wrapper->CreateOfferAndSetAsLocal());
189 auto answer = callee->CreateAnswer();
Steve Antona3a92c22017-12-07 10:27:41 -0800190 wrapper->SetRemoteDescription(
191 CloneSessionDescriptionAsType(answer.get(), SdpType::kPrAnswer));
Steve Anton8d3444d2017-10-20 15:30:51 -0700192 break;
193 }
194 case SignalingState::kClosed: {
195 RTC_NOTREACHED() << "Set the second member of the tuple to true to "
196 "achieve a closed state from an existing, valid "
197 "state.";
198 }
199 }
200
201 RTC_DCHECK_EQ(state, wrapper->pc()->signaling_state());
202
203 if (closed) {
204 wrapper->pc()->Close();
205 RTC_DCHECK_EQ(SignalingState::kClosed, wrapper->signaling_state());
206 }
207
208 return wrapper;
209 }
210};
211
Steve Anton8d3444d2017-10-20 15:30:51 -0700212TEST_P(PeerConnectionSignalingStateTest, CreateOffer) {
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000213 auto wrapper = CreatePeerConnectionInState(GetParam());
Steve Anton8d3444d2017-10-20 15:30:51 -0700214 if (wrapper->signaling_state() != SignalingState::kClosed) {
215 EXPECT_TRUE(wrapper->CreateOffer());
216 } else {
217 std::string error;
218 ASSERT_FALSE(wrapper->CreateOffer(RTCOfferAnswerOptions(), &error));
219 EXPECT_PRED_FORMAT2(AssertStartsWith, error,
220 "CreateOffer called when PeerConnection is closed.");
221 }
222}
223
224TEST_P(PeerConnectionSignalingStateTest, CreateAnswer) {
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000225 auto wrapper = CreatePeerConnectionInState(GetParam());
Steve Anton8d3444d2017-10-20 15:30:51 -0700226 if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
227 wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
228 EXPECT_TRUE(wrapper->CreateAnswer());
229 } else {
230 std::string error;
231 ASSERT_FALSE(wrapper->CreateAnswer(RTCOfferAnswerOptions(), &error));
Steve Antondffead82018-02-06 10:31:29 -0800232 EXPECT_EQ(error,
233 "PeerConnection cannot create an answer in a state other than "
234 "have-remote-offer or have-local-pranswer.");
Steve Anton8d3444d2017-10-20 15:30:51 -0700235 }
236}
237
238TEST_P(PeerConnectionSignalingStateTest, SetLocalOffer) {
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000239 auto wrapper = CreatePeerConnectionInState(GetParam());
Steve Anton8d3444d2017-10-20 15:30:51 -0700240 if (wrapper->signaling_state() == SignalingState::kStable ||
241 wrapper->signaling_state() == SignalingState::kHaveLocalOffer) {
242 // Need to call CreateOffer on the PeerConnection under test, otherwise when
243 // setting the local offer it will want to verify the DTLS fingerprint
244 // against the locally generated certificate, but without a call to
245 // CreateOffer the certificate will never be generated.
246 EXPECT_TRUE(wrapper->SetLocalDescription(wrapper->CreateOffer()));
247 } else {
248 auto wrapper_for_offer =
249 CreatePeerConnectionInState(SignalingState::kHaveLocalOffer);
250 auto offer =
251 CloneSessionDescription(wrapper_for_offer->pc()->local_description());
252
253 std::string error;
254 ASSERT_FALSE(wrapper->SetLocalDescription(std::move(offer), &error));
255 EXPECT_PRED_FORMAT2(
256 AssertStartsWith, error,
257 "Failed to set local offer sdp: Called in wrong state:");
258 }
259}
260
261TEST_P(PeerConnectionSignalingStateTest, SetLocalPrAnswer) {
262 auto wrapper_for_pranswer =
263 CreatePeerConnectionInState(SignalingState::kHaveLocalPrAnswer);
264 auto pranswer =
265 CloneSessionDescription(wrapper_for_pranswer->pc()->local_description());
266
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000267 auto wrapper = CreatePeerConnectionInState(GetParam());
Steve Anton8d3444d2017-10-20 15:30:51 -0700268 if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
269 wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
270 EXPECT_TRUE(wrapper->SetLocalDescription(std::move(pranswer)));
271 } else {
272 std::string error;
273 ASSERT_FALSE(wrapper->SetLocalDescription(std::move(pranswer), &error));
274 EXPECT_PRED_FORMAT2(
275 AssertStartsWith, error,
276 "Failed to set local pranswer sdp: Called in wrong state:");
277 }
278}
279
280TEST_P(PeerConnectionSignalingStateTest, SetLocalAnswer) {
281 auto wrapper_for_answer =
282 CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
283 auto answer = wrapper_for_answer->CreateAnswer();
284
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000285 auto wrapper = CreatePeerConnectionInState(GetParam());
Steve Anton8d3444d2017-10-20 15:30:51 -0700286 if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
287 wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
288 EXPECT_TRUE(wrapper->SetLocalDescription(std::move(answer)));
289 } else {
290 std::string error;
291 ASSERT_FALSE(wrapper->SetLocalDescription(std::move(answer), &error));
292 EXPECT_PRED_FORMAT2(
293 AssertStartsWith, error,
294 "Failed to set local answer sdp: Called in wrong state:");
295 }
296}
297
298TEST_P(PeerConnectionSignalingStateTest, SetRemoteOffer) {
299 auto wrapper_for_offer =
300 CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
301 auto offer =
302 CloneSessionDescription(wrapper_for_offer->pc()->remote_description());
303
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000304 auto wrapper = CreatePeerConnectionInState(GetParam());
Steve Anton8d3444d2017-10-20 15:30:51 -0700305 if (wrapper->signaling_state() == SignalingState::kStable ||
306 wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
307 EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(offer)));
308 } else {
309 std::string error;
310 ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(offer), &error));
311 EXPECT_PRED_FORMAT2(
312 AssertStartsWith, error,
313 "Failed to set remote offer sdp: Called in wrong state:");
314 }
315}
316
317TEST_P(PeerConnectionSignalingStateTest, SetRemotePrAnswer) {
318 auto wrapper_for_pranswer =
319 CreatePeerConnectionInState(SignalingState::kHaveRemotePrAnswer);
320 auto pranswer =
321 CloneSessionDescription(wrapper_for_pranswer->pc()->remote_description());
322
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000323 auto wrapper = CreatePeerConnectionInState(GetParam());
Steve Anton8d3444d2017-10-20 15:30:51 -0700324 if (wrapper->signaling_state() == SignalingState::kHaveLocalOffer ||
325 wrapper->signaling_state() == SignalingState::kHaveRemotePrAnswer) {
326 EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(pranswer)));
327 } else {
328 std::string error;
329 ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(pranswer), &error));
330 EXPECT_PRED_FORMAT2(
331 AssertStartsWith, error,
332 "Failed to set remote pranswer sdp: Called in wrong state:");
333 }
334}
335
336TEST_P(PeerConnectionSignalingStateTest, SetRemoteAnswer) {
337 auto wrapper_for_answer =
338 CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
339 auto answer = wrapper_for_answer->CreateAnswer();
340
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000341 auto wrapper = CreatePeerConnectionInState(GetParam());
Steve Anton8d3444d2017-10-20 15:30:51 -0700342 if (wrapper->signaling_state() == SignalingState::kHaveLocalOffer ||
343 wrapper->signaling_state() == SignalingState::kHaveRemotePrAnswer) {
344 EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(answer)));
345 } else {
346 std::string error;
347 ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(answer), &error));
348 EXPECT_PRED_FORMAT2(
349 AssertStartsWith, error,
350 "Failed to set remote answer sdp: Called in wrong state:");
351 }
352}
353
354INSTANTIATE_TEST_CASE_P(PeerConnectionSignalingTest,
355 PeerConnectionSignalingStateTest,
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000356 Combine(Values(SignalingState::kStable,
Steve Anton8d3444d2017-10-20 15:30:51 -0700357 SignalingState::kHaveLocalOffer,
358 SignalingState::kHaveLocalPrAnswer,
359 SignalingState::kHaveRemoteOffer,
360 SignalingState::kHaveRemotePrAnswer),
361 Bool()));
362
Steve Antondffead82018-02-06 10:31:29 -0800363// Test that CreateAnswer fails if a round of offer/answer has been done and
364// the PeerConnection is in the stable state.
365TEST_F(PeerConnectionSignalingTest, CreateAnswerFailsIfStable) {
Steve Anton8d3444d2017-10-20 15:30:51 -0700366 auto caller = CreatePeerConnection();
367 auto callee = CreatePeerConnection();
368
Steve Antondffead82018-02-06 10:31:29 -0800369 ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000370
371 ASSERT_EQ(SignalingState::kStable, caller->signaling_state());
Steve Antondffead82018-02-06 10:31:29 -0800372 EXPECT_FALSE(caller->CreateAnswer());
373
374 ASSERT_EQ(SignalingState::kStable, callee->signaling_state());
375 EXPECT_FALSE(callee->CreateAnswer());
Steve Anton8d3444d2017-10-20 15:30:51 -0700376}
377
378// According to https://tools.ietf.org/html/rfc3264#section-8, the session id
379// stays the same but the version must be incremented if a later, different
380// session description is generated. These two tests verify that is the case for
381// both offers and answers.
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000382TEST_F(PeerConnectionSignalingTest,
Steve Anton8d3444d2017-10-20 15:30:51 -0700383 SessionVersionIncrementedInSubsequentDifferentOffer) {
384 auto caller = CreatePeerConnection();
385 auto callee = CreatePeerConnection();
386
387 auto original_offer = caller->CreateOfferAndSetAsLocal();
388 const std::string original_id = original_offer->session_id();
389 const std::string original_version = original_offer->session_version();
390
391 ASSERT_TRUE(callee->SetRemoteDescription(std::move(original_offer)));
392 ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateAnswer()));
393
394 // Add track to get a different offer.
395 caller->AddAudioTrack("a");
396
397 auto later_offer = caller->CreateOffer();
398
399 EXPECT_EQ(original_id, later_offer->session_id());
400 EXPECT_LT(rtc::FromString<uint64_t>(original_version),
401 rtc::FromString<uint64_t>(later_offer->session_version()));
402}
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000403TEST_F(PeerConnectionSignalingTest,
Steve Anton8d3444d2017-10-20 15:30:51 -0700404 SessionVersionIncrementedInSubsequentDifferentAnswer) {
405 auto caller = CreatePeerConnection();
406 auto callee = CreatePeerConnection();
407
408 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
409
Steve Antondffead82018-02-06 10:31:29 -0800410 auto original_answer = callee->CreateAnswer();
Steve Anton8d3444d2017-10-20 15:30:51 -0700411 const std::string original_id = original_answer->session_id();
412 const std::string original_version = original_answer->session_version();
413
414 // Add track to get a different answer.
415 callee->AddAudioTrack("a");
416
417 auto later_answer = callee->CreateAnswer();
418
419 EXPECT_EQ(original_id, later_answer->session_id());
420 EXPECT_LT(rtc::FromString<uint64_t>(original_version),
421 rtc::FromString<uint64_t>(later_answer->session_version()));
422}
423
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000424TEST_F(PeerConnectionSignalingTest, InitiatorFlagSetOnCallerAndNotOnCallee) {
Steve Anton8d3444d2017-10-20 15:30:51 -0700425 auto caller = CreatePeerConnectionWithAudioVideo();
426 auto callee = CreatePeerConnectionWithAudioVideo();
427
428 EXPECT_FALSE(caller->initial_offerer());
429 EXPECT_FALSE(callee->initial_offerer());
430
431 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
432
433 EXPECT_TRUE(caller->initial_offerer());
434 EXPECT_FALSE(callee->initial_offerer());
435
436 ASSERT_TRUE(
437 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
438
439 EXPECT_TRUE(caller->initial_offerer());
440 EXPECT_FALSE(callee->initial_offerer());
441}
442
443// Test creating a PeerConnection, request multiple offers, destroy the
444// PeerConnection and make sure we get success/failure callbacks for all of the
445// requests.
446// Background: crbug.com/507307
Oleh Prypinc22d6a82018-02-02 08:42:18 +0000447TEST_F(PeerConnectionSignalingTest, CreateOffersAndShutdown) {
Steve Anton8d3444d2017-10-20 15:30:51 -0700448 auto caller = CreatePeerConnection();
449
450 RTCOfferAnswerOptions options;
451 options.offer_to_receive_audio =
452 RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
453
454 rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observers[100];
455 for (auto& observer : observers) {
456 observer =
457 new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>();
458 caller->pc()->CreateOffer(observer, options);
459 }
460
461 // Destroy the PeerConnection.
462 caller.reset(nullptr);
463
464 for (auto& observer : observers) {
465 // We expect to have received a notification now even if the PeerConnection
466 // was terminated. The offer creation may or may not have succeeded, but we
467 // must have received a notification.
468 EXPECT_TRUE(observer->called());
469 }
470}
471
Steve Anton8d3444d2017-10-20 15:30:51 -0700472} // namespace webrtc