Rewrite PeerConnection integration tests using better testing practices.

Also renames "peerconnection_unittests" to "peerconnection_integrationtests",
and moves the ICE URL parsing code to separate files.

The main problem previously was that the test assertions
occurred in various places in the main test class, and this shared test
code was overly complex and stateful. As a result, it was difficult to
tell what a test even does, let alone what assertions it's meant to be
making. And writing a new test that does what you want can be a
frustrating ordeal.

The new code still uses helper methods, but they have intuitive names
and a smaller role; all of the important parts of the test's logic are
in the test case itself.

We're planning on merging PeerConnection and WebRtcSession at some point
soon, so it seemed valuable to do this, so that the WebRtcSession tests
can be rewritten as PeerConnection tests using better patterns.

BUG=None

Review-Url: https://codereview.webrtc.org/2738353003
Cr-Commit-Position: refs/heads/master@{#17458}
diff --git a/webrtc/pc/peerconnection_integrationtest.cc b/webrtc/pc/peerconnection_integrationtest.cc
new file mode 100644
index 0000000..8a4df9b
--- /dev/null
+++ b/webrtc/pc/peerconnection_integrationtest.cc
@@ -0,0 +1,2706 @@
+/*
+ *  Copyright 2012 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+// Disable for TSan v2, see
+// https://code.google.com/p/webrtc/issues/detail?id=1205 for details.
+#if !defined(THREAD_SANITIZER)
+
+#include <stdio.h>
+
+#include <algorithm>
+#include <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "webrtc/api/fakemetricsobserver.h"
+#include "webrtc/api/mediastreaminterface.h"
+#include "webrtc/api/peerconnectioninterface.h"
+#include "webrtc/api/test/fakeconstraints.h"
+#include "webrtc/base/asyncinvoker.h"
+#include "webrtc/base/fakenetwork.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/helpers.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/ssladapter.h"
+#include "webrtc/base/sslstreamadapter.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/virtualsocketserver.h"
+#include "webrtc/media/engine/fakewebrtcvideoengine.h"
+#include "webrtc/p2p/base/p2pconstants.h"
+#include "webrtc/p2p/base/portinterface.h"
+#include "webrtc/p2p/base/sessiondescription.h"
+#include "webrtc/p2p/base/testturnserver.h"
+#include "webrtc/p2p/client/basicportallocator.h"
+#include "webrtc/pc/dtmfsender.h"
+#include "webrtc/pc/localaudiosource.h"
+#include "webrtc/pc/mediasession.h"
+#include "webrtc/pc/peerconnection.h"
+#include "webrtc/pc/peerconnectionfactory.h"
+#include "webrtc/pc/test/fakeaudiocapturemodule.h"
+#include "webrtc/pc/test/fakeperiodicvideocapturer.h"
+#include "webrtc/pc/test/fakertccertificategenerator.h"
+#include "webrtc/pc/test/fakevideotrackrenderer.h"
+#include "webrtc/pc/test/mockpeerconnectionobservers.h"
+
+using cricket::ContentInfo;
+using cricket::FakeWebRtcVideoDecoder;
+using cricket::FakeWebRtcVideoDecoderFactory;
+using cricket::FakeWebRtcVideoEncoder;
+using cricket::FakeWebRtcVideoEncoderFactory;
+using cricket::MediaContentDescription;
+using webrtc::DataBuffer;
+using webrtc::DataChannelInterface;
+using webrtc::DtmfSender;
+using webrtc::DtmfSenderInterface;
+using webrtc::DtmfSenderObserverInterface;
+using webrtc::FakeConstraints;
+using webrtc::MediaConstraintsInterface;
+using webrtc::MediaStreamInterface;
+using webrtc::MediaStreamTrackInterface;
+using webrtc::MockCreateSessionDescriptionObserver;
+using webrtc::MockDataChannelObserver;
+using webrtc::MockSetSessionDescriptionObserver;
+using webrtc::MockStatsObserver;
+using webrtc::ObserverInterface;
+using webrtc::PeerConnectionInterface;
+using webrtc::PeerConnectionFactory;
+using webrtc::SessionDescriptionInterface;
+using webrtc::StreamCollectionInterface;
+
+namespace {
+
+static const int kDefaultTimeout = 10000;
+static const int kMaxWaitForStatsMs = 3000;
+static const int kMaxWaitForActivationMs = 5000;
+static const int kMaxWaitForFramesMs = 10000;
+// Default number of audio/video frames to wait for before considering a test
+// successful.
+static const int kDefaultExpectedAudioFrameCount = 3;
+static const int kDefaultExpectedVideoFrameCount = 3;
+
+static const char kDefaultStreamLabel[] = "stream_label";
+static const char kDefaultVideoTrackId[] = "video_track";
+static const char kDefaultAudioTrackId[] = "audio_track";
+static const char kDataChannelLabel[] = "data_channel";
+
+// SRTP cipher name negotiated by the tests. This must be updated if the
+// default changes.
+static const int kDefaultSrtpCryptoSuite = rtc::SRTP_AES128_CM_SHA1_32;
+static const int kDefaultSrtpCryptoSuiteGcm = rtc::SRTP_AEAD_AES_256_GCM;
+
+// Helper function for constructing offer/answer options to initiate an ICE
+// restart.
+PeerConnectionInterface::RTCOfferAnswerOptions IceRestartOfferAnswerOptions() {
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.ice_restart = true;
+  return options;
+}
+
+class SignalingMessageReceiver {
+ public:
+  virtual void ReceiveSdpMessage(const std::string& type,
+                                 const std::string& msg) = 0;
+  virtual void ReceiveIceMessage(const std::string& sdp_mid,
+                                 int sdp_mline_index,
+                                 const std::string& msg) = 0;
+
+ protected:
+  SignalingMessageReceiver() {}
+  virtual ~SignalingMessageReceiver() {}
+};
+
+class MockRtpReceiverObserver : public webrtc::RtpReceiverObserverInterface {
+ public:
+  explicit MockRtpReceiverObserver(cricket::MediaType media_type)
+      : expected_media_type_(media_type) {}
+
+  void OnFirstPacketReceived(cricket::MediaType media_type) override {
+    ASSERT_EQ(expected_media_type_, media_type);
+    first_packet_received_ = true;
+  }
+
+  bool first_packet_received() const { return first_packet_received_; }
+
+  virtual ~MockRtpReceiverObserver() {}
+
+ private:
+  bool first_packet_received_ = false;
+  cricket::MediaType expected_media_type_;
+};
+
+// Helper class that wraps a peer connection, observes it, and can accept
+// signaling messages from another wrapper.
+//
+// Uses a fake network, fake A/V capture, and optionally fake
+// encoders/decoders, though they aren't used by default since they don't
+// advertise support of any codecs.
+class PeerConnectionWrapper : public webrtc::PeerConnectionObserver,
+                              public SignalingMessageReceiver,
+                              public ObserverInterface {
+ public:
+  // Different factory methods for convenience.
+  // TODO(deadbeef): Could use the pattern of:
+  //
+  // PeerConnectionWrapper =
+  //     WrapperBuilder.WithConfig(...).WithOptions(...).build();
+  //
+  // To reduce some code duplication.
+  static PeerConnectionWrapper* CreateWithDtlsIdentityStore(
+      const std::string& debug_name,
+      std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator,
+      rtc::Thread* network_thread,
+      rtc::Thread* worker_thread) {
+    PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name));
+    if (!client->Init(nullptr, nullptr, nullptr, std::move(cert_generator),
+                      network_thread, worker_thread)) {
+      delete client;
+      return nullptr;
+    }
+    return client;
+  }
+
+  static PeerConnectionWrapper* CreateWithConfig(
+      const std::string& debug_name,
+      const PeerConnectionInterface::RTCConfiguration& config,
+      rtc::Thread* network_thread,
+      rtc::Thread* worker_thread) {
+    std::unique_ptr<FakeRTCCertificateGenerator> cert_generator(
+        new FakeRTCCertificateGenerator());
+    PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name));
+    if (!client->Init(nullptr, nullptr, &config, std::move(cert_generator),
+                      network_thread, worker_thread)) {
+      delete client;
+      return nullptr;
+    }
+    return client;
+  }
+
+  static PeerConnectionWrapper* CreateWithOptions(
+      const std::string& debug_name,
+      const PeerConnectionFactory::Options& options,
+      rtc::Thread* network_thread,
+      rtc::Thread* worker_thread) {
+    std::unique_ptr<FakeRTCCertificateGenerator> cert_generator(
+        new FakeRTCCertificateGenerator());
+    PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name));
+    if (!client->Init(nullptr, &options, nullptr, std::move(cert_generator),
+                      network_thread, worker_thread)) {
+      delete client;
+      return nullptr;
+    }
+    return client;
+  }
+
+  static PeerConnectionWrapper* CreateWithConstraints(
+      const std::string& debug_name,
+      const MediaConstraintsInterface* constraints,
+      rtc::Thread* network_thread,
+      rtc::Thread* worker_thread) {
+    std::unique_ptr<FakeRTCCertificateGenerator> cert_generator(
+        new FakeRTCCertificateGenerator());
+    PeerConnectionWrapper* client(new PeerConnectionWrapper(debug_name));
+    if (!client->Init(constraints, nullptr, nullptr, std::move(cert_generator),
+                      network_thread, worker_thread)) {
+      delete client;
+      return nullptr;
+    }
+    return client;
+  }
+
+  webrtc::PeerConnectionInterface* pc() const { return peer_connection_.get(); }
+
+  // If a signaling message receiver is set (via ConnectFakeSignaling), this
+  // will set the whole offer/answer exchange in motion. Just need to wait for
+  // the signaling state to reach "stable".
+  void CreateAndSetAndSignalOffer() {
+    auto offer = CreateOffer();
+    ASSERT_NE(nullptr, offer);
+    EXPECT_TRUE(SetLocalDescriptionAndSendSdpMessage(std::move(offer)));
+  }
+
+  // Sets the options to be used when CreateAndSetAndSignalOffer is called, or
+  // when a remote offer is received (via fake signaling) and an answer is
+  // generated. By default, uses default options.
+  void SetOfferAnswerOptions(
+      const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
+    offer_answer_options_ = options;
+  }
+
+  // Set a callback to be invoked when SDP is received via the fake signaling
+  // channel, which provides an opportunity to munge (modify) the SDP. This is
+  // used to test SDP being applied that a PeerConnection would normally not
+  // generate, but a non-JSEP endpoint might.
+  void SetReceivedSdpMunger(
+      std::function<void(cricket::SessionDescription*)> munger) {
+    received_sdp_munger_ = munger;
+  }
+
+  // Siimlar to the above, but this is run on SDP immediately after it's
+  // generated.
+  void SetGeneratedSdpMunger(
+      std::function<void(cricket::SessionDescription*)> munger) {
+    generated_sdp_munger_ = munger;
+  }
+
+  // Number of times the gathering state has transitioned to "gathering".
+  // Useful for telling if an ICE restart occurred as expected.
+  int transitions_to_gathering_state() const {
+    return transitions_to_gathering_state_;
+  }
+
+  // TODO(deadbeef): Switch the majority of these tests to use AddTrack instead
+  // of AddStream since AddStream is deprecated.
+  void AddAudioVideoMediaStream() {
+    AddMediaStreamFromTracks(CreateLocalAudioTrack(), CreateLocalVideoTrack());
+  }
+
+  void AddAudioOnlyMediaStream() {
+    AddMediaStreamFromTracks(CreateLocalAudioTrack(), nullptr);
+  }
+
+  void AddVideoOnlyMediaStream() {
+    AddMediaStreamFromTracks(nullptr, CreateLocalVideoTrack());
+  }
+
+  rtc::scoped_refptr<webrtc::AudioTrackInterface> CreateLocalAudioTrack() {
+    FakeConstraints constraints;
+    // Disable highpass filter so that we can get all the test audio frames.
+    constraints.AddMandatory(MediaConstraintsInterface::kHighpassFilter, false);
+    rtc::scoped_refptr<webrtc::AudioSourceInterface> source =
+        peer_connection_factory_->CreateAudioSource(&constraints);
+    // TODO(perkj): Test audio source when it is implemented. Currently audio
+    // always use the default input.
+    return peer_connection_factory_->CreateAudioTrack(kDefaultAudioTrackId,
+                                                      source);
+  }
+
+  rtc::scoped_refptr<webrtc::VideoTrackInterface> CreateLocalVideoTrack() {
+    return CreateLocalVideoTrackInternal(
+        kDefaultVideoTrackId, FakeConstraints(), webrtc::kVideoRotation_0);
+  }
+
+  rtc::scoped_refptr<webrtc::VideoTrackInterface>
+  CreateLocalVideoTrackWithConstraints(const FakeConstraints& constraints) {
+    return CreateLocalVideoTrackInternal(kDefaultVideoTrackId, constraints,
+                                         webrtc::kVideoRotation_0);
+  }
+
+  rtc::scoped_refptr<webrtc::VideoTrackInterface>
+  CreateLocalVideoTrackWithRotation(webrtc::VideoRotation rotation) {
+    return CreateLocalVideoTrackInternal(kDefaultVideoTrackId,
+                                         FakeConstraints(), rotation);
+  }
+
+  rtc::scoped_refptr<webrtc::VideoTrackInterface> CreateLocalVideoTrackWithId(
+      const std::string& id) {
+    return CreateLocalVideoTrackInternal(id, FakeConstraints(),
+                                         webrtc::kVideoRotation_0);
+  }
+
+  void AddMediaStreamFromTracks(
+      rtc::scoped_refptr<webrtc::AudioTrackInterface> audio,
+      rtc::scoped_refptr<webrtc::VideoTrackInterface> video) {
+    AddMediaStreamFromTracksWithLabel(audio, video, kDefaultStreamLabel);
+  }
+
+  void AddMediaStreamFromTracksWithLabel(
+      rtc::scoped_refptr<webrtc::AudioTrackInterface> audio,
+      rtc::scoped_refptr<webrtc::VideoTrackInterface> video,
+      const std::string& stream_label) {
+    rtc::scoped_refptr<MediaStreamInterface> stream =
+        peer_connection_factory_->CreateLocalMediaStream(stream_label);
+    if (audio) {
+      stream->AddTrack(audio);
+    }
+    if (video) {
+      stream->AddTrack(video);
+    }
+    EXPECT_TRUE(pc()->AddStream(stream));
+  }
+
+  bool SignalingStateStable() {
+    return pc()->signaling_state() == webrtc::PeerConnectionInterface::kStable;
+  }
+
+  void CreateDataChannel() { CreateDataChannel(nullptr); }
+
+  void CreateDataChannel(const webrtc::DataChannelInit* init) {
+    data_channel_ = pc()->CreateDataChannel(kDataChannelLabel, init);
+    ASSERT_TRUE(data_channel_.get() != nullptr);
+    data_observer_.reset(new MockDataChannelObserver(data_channel_));
+  }
+
+  DataChannelInterface* data_channel() { return data_channel_; }
+  const MockDataChannelObserver* data_observer() const {
+    return data_observer_.get();
+  }
+
+  int audio_frames_received() const {
+    return fake_audio_capture_module_->frames_received();
+  }
+
+  // Takes minimum of video frames received for each track.
+  //
+  // Can be used like:
+  // EXPECT_GE(expected_frames, min_video_frames_received_per_track());
+  //
+  // To ensure that all video tracks received at least a certain number of
+  // frames.
+  int min_video_frames_received_per_track() const {
+    int min_frames = INT_MAX;
+    if (video_decoder_factory_enabled_) {
+      const std::vector<FakeWebRtcVideoDecoder*>& decoders =
+          fake_video_decoder_factory_->decoders();
+      if (decoders.empty()) {
+        return 0;
+      }
+      for (FakeWebRtcVideoDecoder* decoder : decoders) {
+        min_frames = std::min(min_frames, decoder->GetNumFramesReceived());
+      }
+      return min_frames;
+    } else {
+      if (fake_video_renderers_.empty()) {
+        return 0;
+      }
+
+      for (const auto& pair : fake_video_renderers_) {
+        min_frames = std::min(min_frames, pair.second->num_rendered_frames());
+      }
+      return min_frames;
+    }
+  }
+
+  // In contrast to the above, sums the video frames received for all tracks.
+  // Can be used to verify that no video frames were received, or that the
+  // counts didn't increase.
+  int total_video_frames_received() const {
+    int total = 0;
+    if (video_decoder_factory_enabled_) {
+      const std::vector<FakeWebRtcVideoDecoder*>& decoders =
+          fake_video_decoder_factory_->decoders();
+      for (const FakeWebRtcVideoDecoder* decoder : decoders) {
+        total += decoder->GetNumFramesReceived();
+      }
+    } else {
+      for (const auto& pair : fake_video_renderers_) {
+        total += pair.second->num_rendered_frames();
+      }
+      for (const auto& renderer : removed_fake_video_renderers_) {
+        total += renderer->num_rendered_frames();
+      }
+    }
+    return total;
+  }
+
+  // Returns a MockStatsObserver in a state after stats gathering finished,
+  // which can be used to access the gathered stats.
+  rtc::scoped_refptr<MockStatsObserver> GetStatsForTrack(
+      webrtc::MediaStreamTrackInterface* track) {
+    rtc::scoped_refptr<MockStatsObserver> observer(
+        new rtc::RefCountedObject<MockStatsObserver>());
+    EXPECT_TRUE(peer_connection_->GetStats(
+        observer, nullptr, PeerConnectionInterface::kStatsOutputLevelStandard));
+    EXPECT_TRUE_WAIT(observer->called(), kDefaultTimeout);
+    return observer;
+  }
+
+  // Version that doesn't take a track "filter", and gathers all stats.
+  rtc::scoped_refptr<MockStatsObserver> GetStats() {
+    return GetStatsForTrack(nullptr);
+  }
+
+  int rendered_width() {
+    EXPECT_FALSE(fake_video_renderers_.empty());
+    return fake_video_renderers_.empty()
+               ? 0
+               : fake_video_renderers_.begin()->second->width();
+  }
+
+  int rendered_height() {
+    EXPECT_FALSE(fake_video_renderers_.empty());
+    return fake_video_renderers_.empty()
+               ? 0
+               : fake_video_renderers_.begin()->second->height();
+  }
+
+  double rendered_aspect_ratio() {
+    if (rendered_height() == 0) {
+      return 0.0;
+    }
+    return static_cast<double>(rendered_width()) / rendered_height();
+  }
+
+  webrtc::VideoRotation rendered_rotation() {
+    EXPECT_FALSE(fake_video_renderers_.empty());
+    return fake_video_renderers_.empty()
+               ? webrtc::kVideoRotation_0
+               : fake_video_renderers_.begin()->second->rotation();
+  }
+
+  int local_rendered_width() {
+    return local_video_renderer_ ? local_video_renderer_->width() : 0;
+  }
+
+  int local_rendered_height() {
+    return local_video_renderer_ ? local_video_renderer_->height() : 0;
+  }
+
+  double local_rendered_aspect_ratio() {
+    if (local_rendered_height() == 0) {
+      return 0.0;
+    }
+    return static_cast<double>(local_rendered_width()) /
+           local_rendered_height();
+  }
+
+  size_t number_of_remote_streams() {
+    if (!pc()) {
+      return 0;
+    }
+    return pc()->remote_streams()->count();
+  }
+
+  StreamCollectionInterface* remote_streams() const {
+    if (!pc()) {
+      ADD_FAILURE();
+      return nullptr;
+    }
+    return pc()->remote_streams();
+  }
+
+  StreamCollectionInterface* local_streams() {
+    if (!pc()) {
+      ADD_FAILURE();
+      return nullptr;
+    }
+    return pc()->local_streams();
+  }
+
+  webrtc::PeerConnectionInterface::SignalingState signaling_state() {
+    return pc()->signaling_state();
+  }
+
+  webrtc::PeerConnectionInterface::IceConnectionState ice_connection_state() {
+    return pc()->ice_connection_state();
+  }
+
+  webrtc::PeerConnectionInterface::IceGatheringState ice_gathering_state() {
+    return pc()->ice_gathering_state();
+  }
+
+  // Returns a MockRtpReceiverObserver for each RtpReceiver returned by
+  // GetReceivers. They're updated automatically when a remote offer/answer
+  // from the fake signaling channel is applied, or when
+  // ResetRtpReceiverObservers below is called.
+  const std::vector<std::unique_ptr<MockRtpReceiverObserver>>&
+  rtp_receiver_observers() {
+    return rtp_receiver_observers_;
+  }
+
+  void ResetRtpReceiverObservers() {
+    rtp_receiver_observers_.clear();
+    for (auto receiver : pc()->GetReceivers()) {
+      std::unique_ptr<MockRtpReceiverObserver> observer(
+          new MockRtpReceiverObserver(receiver->media_type()));
+      receiver->SetObserver(observer.get());
+      rtp_receiver_observers_.push_back(std::move(observer));
+    }
+  }
+
+ private:
+  explicit PeerConnectionWrapper(const std::string& debug_name)
+      : debug_name_(debug_name) {}
+
+  bool Init(
+      const MediaConstraintsInterface* constraints,
+      const PeerConnectionFactory::Options* options,
+      const PeerConnectionInterface::RTCConfiguration* config,
+      std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator,
+      rtc::Thread* network_thread,
+      rtc::Thread* worker_thread) {
+    // There's an error in this test code if Init ends up being called twice.
+    RTC_DCHECK(!peer_connection_);
+    RTC_DCHECK(!peer_connection_factory_);
+
+    fake_network_manager_.reset(new rtc::FakeNetworkManager());
+    fake_network_manager_->AddInterface(rtc::SocketAddress("192.168.1.1", 0));
+
+    std::unique_ptr<cricket::PortAllocator> port_allocator(
+        new cricket::BasicPortAllocator(fake_network_manager_.get()));
+    fake_audio_capture_module_ = FakeAudioCaptureModule::Create();
+    if (!fake_audio_capture_module_) {
+      return false;
+    }
+    // Note that these factories don't end up getting used unless supported
+    // codecs are added to them.
+    fake_video_decoder_factory_ = new FakeWebRtcVideoDecoderFactory();
+    fake_video_encoder_factory_ = new FakeWebRtcVideoEncoderFactory();
+    rtc::Thread* const signaling_thread = rtc::Thread::Current();
+    peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
+        network_thread, worker_thread, signaling_thread,
+        fake_audio_capture_module_, fake_video_encoder_factory_,
+        fake_video_decoder_factory_);
+    if (!peer_connection_factory_) {
+      return false;
+    }
+    if (options) {
+      peer_connection_factory_->SetOptions(*options);
+    }
+    peer_connection_ =
+        CreatePeerConnection(std::move(port_allocator), constraints, config,
+                             std::move(cert_generator));
+    return peer_connection_.get() != nullptr;
+  }
+
+  rtc::scoped_refptr<webrtc::PeerConnectionInterface> CreatePeerConnection(
+      std::unique_ptr<cricket::PortAllocator> port_allocator,
+      const MediaConstraintsInterface* constraints,
+      const PeerConnectionInterface::RTCConfiguration* config,
+      std::unique_ptr<rtc::RTCCertificateGeneratorInterface> cert_generator) {
+    PeerConnectionInterface::RTCConfiguration modified_config;
+    // If |config| is null, this will result in a default configuration being
+    // used.
+    if (config) {
+      modified_config = *config;
+    }
+    // Disable resolution adaptation; we don't want it interfering with the
+    // test results.
+    // TODO(deadbeef): Do something more robust. Since we're testing for aspect
+    // ratios and not specific resolutions, is this even necessary?
+    modified_config.set_cpu_adaptation(false);
+
+    return peer_connection_factory_->CreatePeerConnection(
+        modified_config, constraints, std::move(port_allocator),
+        std::move(cert_generator), this);
+  }
+
+  void set_signaling_message_receiver(
+      SignalingMessageReceiver* signaling_message_receiver) {
+    signaling_message_receiver_ = signaling_message_receiver;
+  }
+
+  void set_signaling_delay_ms(int delay_ms) { signaling_delay_ms_ = delay_ms; }
+
+  void EnableVideoDecoderFactory() {
+    video_decoder_factory_enabled_ = true;
+    fake_video_decoder_factory_->AddSupportedVideoCodecType(
+        webrtc::kVideoCodecVP8);
+  }
+
+  rtc::scoped_refptr<webrtc::VideoTrackInterface> CreateLocalVideoTrackInternal(
+      const std::string& track_id,
+      const FakeConstraints& constraints,
+      webrtc::VideoRotation rotation) {
+    // Set max frame rate to 10fps to reduce the risk of test flakiness.
+    // TODO(deadbeef): Do something more robust.
+    FakeConstraints source_constraints = constraints;
+    source_constraints.SetMandatoryMaxFrameRate(10);
+
+    cricket::FakeVideoCapturer* fake_capturer =
+        new webrtc::FakePeriodicVideoCapturer();
+    fake_capturer->SetRotation(rotation);
+    video_capturers_.push_back(fake_capturer);
+    rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> source =
+        peer_connection_factory_->CreateVideoSource(fake_capturer,
+                                                    &source_constraints);
+    rtc::scoped_refptr<webrtc::VideoTrackInterface> track(
+        peer_connection_factory_->CreateVideoTrack(track_id, source));
+    if (!local_video_renderer_) {
+      local_video_renderer_.reset(new webrtc::FakeVideoTrackRenderer(track));
+    }
+    return track;
+  }
+
+  void HandleIncomingOffer(const std::string& msg) {
+    LOG(LS_INFO) << debug_name_ << ": HandleIncomingOffer";
+    std::unique_ptr<SessionDescriptionInterface> desc(
+        webrtc::CreateSessionDescription("offer", msg, nullptr));
+    if (received_sdp_munger_) {
+      received_sdp_munger_(desc->description());
+    }
+
+    EXPECT_TRUE(SetRemoteDescription(std::move(desc)));
+    // Setting a remote description may have changed the number of receivers,
+    // so reset the receiver observers.
+    ResetRtpReceiverObservers();
+    auto answer = CreateAnswer();
+    ASSERT_NE(nullptr, answer);
+    EXPECT_TRUE(SetLocalDescriptionAndSendSdpMessage(std::move(answer)));
+  }
+
+  void HandleIncomingAnswer(const std::string& msg) {
+    LOG(LS_INFO) << debug_name_ << ": HandleIncomingAnswer";
+    std::unique_ptr<SessionDescriptionInterface> desc(
+        webrtc::CreateSessionDescription("answer", msg, nullptr));
+    if (received_sdp_munger_) {
+      received_sdp_munger_(desc->description());
+    }
+
+    EXPECT_TRUE(SetRemoteDescription(std::move(desc)));
+    // Set the RtpReceiverObserver after receivers are created.
+    ResetRtpReceiverObservers();
+  }
+
+  // Returns null on failure.
+  std::unique_ptr<SessionDescriptionInterface> CreateOffer() {
+    rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer(
+        new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
+    pc()->CreateOffer(observer, offer_answer_options_);
+    return WaitForDescriptionFromObserver(observer);
+  }
+
+  // Returns null on failure.
+  std::unique_ptr<SessionDescriptionInterface> CreateAnswer() {
+    rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer(
+        new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
+    pc()->CreateAnswer(observer, offer_answer_options_);
+    return WaitForDescriptionFromObserver(observer);
+  }
+
+  std::unique_ptr<SessionDescriptionInterface> WaitForDescriptionFromObserver(
+      rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer) {
+    EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout);
+    if (!observer->result()) {
+      return nullptr;
+    }
+    auto description = observer->MoveDescription();
+    if (generated_sdp_munger_) {
+      generated_sdp_munger_(description->description());
+    }
+    return description;
+  }
+
+  // Setting the local description and sending the SDP message over the fake
+  // signaling channel are combined into the same method because the SDP
+  // message needs to be sent as soon as SetLocalDescription finishes, without
+  // waiting for the observer to be called. This ensures that ICE candidates
+  // don't outrace the description.
+  bool SetLocalDescriptionAndSendSdpMessage(
+      std::unique_ptr<SessionDescriptionInterface> desc) {
+    rtc::scoped_refptr<MockSetSessionDescriptionObserver> observer(
+        new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
+    LOG(LS_INFO) << debug_name_ << ": SetLocalDescriptionAndSendSdpMessage";
+    std::string type = desc->type();
+    std::string sdp;
+    EXPECT_TRUE(desc->ToString(&sdp));
+    pc()->SetLocalDescription(observer, desc.release());
+    // As mentioned above, we need to send the message immediately after
+    // SetLocalDescription.
+    SendSdpMessage(type, sdp);
+    EXPECT_TRUE_WAIT(observer->called(), kDefaultTimeout);
+    return true;
+  }
+
+  bool SetRemoteDescription(std::unique_ptr<SessionDescriptionInterface> desc) {
+    rtc::scoped_refptr<MockSetSessionDescriptionObserver> observer(
+        new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
+    LOG(LS_INFO) << debug_name_ << ": SetRemoteDescription";
+    pc()->SetRemoteDescription(observer, desc.release());
+    EXPECT_TRUE_WAIT(observer->called(), kDefaultTimeout);
+    return observer->result();
+  }
+
+  // Simulate sending a blob of SDP with delay |signaling_delay_ms_| (0 by
+  // default).
+  void SendSdpMessage(const std::string& type, const std::string& msg) {
+    if (signaling_delay_ms_ == 0) {
+      RelaySdpMessageIfReceiverExists(type, msg);
+    } else {
+      invoker_.AsyncInvokeDelayed<void>(
+          RTC_FROM_HERE, rtc::Thread::Current(),
+          rtc::Bind(&PeerConnectionWrapper::RelaySdpMessageIfReceiverExists,
+                    this, type, msg),
+          signaling_delay_ms_);
+    }
+  }
+
+  void RelaySdpMessageIfReceiverExists(const std::string& type,
+                                       const std::string& msg) {
+    if (signaling_message_receiver_) {
+      signaling_message_receiver_->ReceiveSdpMessage(type, msg);
+    }
+  }
+
+  // Simulate trickling an ICE candidate with delay |signaling_delay_ms_| (0 by
+  // default).
+  void SendIceMessage(const std::string& sdp_mid,
+                      int sdp_mline_index,
+                      const std::string& msg) {
+    if (signaling_delay_ms_ == 0) {
+      RelayIceMessageIfReceiverExists(sdp_mid, sdp_mline_index, msg);
+    } else {
+      invoker_.AsyncInvokeDelayed<void>(
+          RTC_FROM_HERE, rtc::Thread::Current(),
+          rtc::Bind(&PeerConnectionWrapper::RelayIceMessageIfReceiverExists,
+                    this, sdp_mid, sdp_mline_index, msg),
+          signaling_delay_ms_);
+    }
+  }
+
+  void RelayIceMessageIfReceiverExists(const std::string& sdp_mid,
+                                       int sdp_mline_index,
+                                       const std::string& msg) {
+    if (signaling_message_receiver_) {
+      signaling_message_receiver_->ReceiveIceMessage(sdp_mid, sdp_mline_index,
+                                                     msg);
+    }
+  }
+
+  // SignalingMessageReceiver callbacks.
+  void ReceiveSdpMessage(const std::string& type,
+                         const std::string& msg) override {
+    if (type == webrtc::SessionDescriptionInterface::kOffer) {
+      HandleIncomingOffer(msg);
+    } else {
+      HandleIncomingAnswer(msg);
+    }
+  }
+
+  void ReceiveIceMessage(const std::string& sdp_mid,
+                         int sdp_mline_index,
+                         const std::string& msg) override {
+    LOG(LS_INFO) << debug_name_ << ": ReceiveIceMessage";
+    std::unique_ptr<webrtc::IceCandidateInterface> candidate(
+        webrtc::CreateIceCandidate(sdp_mid, sdp_mline_index, msg, nullptr));
+    EXPECT_TRUE(pc()->AddIceCandidate(candidate.get()));
+  }
+
+  // PeerConnectionObserver callbacks.
+  void OnSignalingChange(
+      webrtc::PeerConnectionInterface::SignalingState new_state) override {
+    EXPECT_EQ(pc()->signaling_state(), new_state);
+  }
+  void OnAddStream(
+      rtc::scoped_refptr<MediaStreamInterface> media_stream) override {
+    media_stream->RegisterObserver(this);
+    for (size_t i = 0; i < media_stream->GetVideoTracks().size(); ++i) {
+      const std::string id = media_stream->GetVideoTracks()[i]->id();
+      ASSERT_TRUE(fake_video_renderers_.find(id) ==
+                  fake_video_renderers_.end());
+      fake_video_renderers_[id].reset(new webrtc::FakeVideoTrackRenderer(
+          media_stream->GetVideoTracks()[i]));
+    }
+  }
+  void OnRemoveStream(
+      rtc::scoped_refptr<MediaStreamInterface> media_stream) override {}
+  void OnRenegotiationNeeded() override {}
+  void OnIceConnectionChange(
+      webrtc::PeerConnectionInterface::IceConnectionState new_state) override {
+    EXPECT_EQ(pc()->ice_connection_state(), new_state);
+  }
+  void OnIceGatheringChange(
+      webrtc::PeerConnectionInterface::IceGatheringState new_state) override {
+    if (new_state == PeerConnectionInterface::kIceGatheringGathering) {
+      ++transitions_to_gathering_state_;
+    }
+    EXPECT_EQ(pc()->ice_gathering_state(), new_state);
+  }
+  void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override {
+    LOG(LS_INFO) << debug_name_ << ": OnIceCandidate";
+
+    std::string ice_sdp;
+    EXPECT_TRUE(candidate->ToString(&ice_sdp));
+    if (signaling_message_receiver_ == nullptr) {
+      // Remote party may be deleted.
+      return;
+    }
+    SendIceMessage(candidate->sdp_mid(), candidate->sdp_mline_index(), ice_sdp);
+  }
+  void OnDataChannel(
+      rtc::scoped_refptr<DataChannelInterface> data_channel) override {
+    LOG(LS_INFO) << debug_name_ << ": OnDataChannel";
+    data_channel_ = data_channel;
+    data_observer_.reset(new MockDataChannelObserver(data_channel));
+  }
+
+  // MediaStreamInterface callback
+  void OnChanged() override {
+    // Track added or removed from MediaStream, so update our renderers.
+    rtc::scoped_refptr<StreamCollectionInterface> remote_streams =
+        pc()->remote_streams();
+    // Remove renderers for tracks that were removed.
+    for (auto it = fake_video_renderers_.begin();
+         it != fake_video_renderers_.end();) {
+      if (remote_streams->FindVideoTrack(it->first) == nullptr) {
+        auto to_remove = it++;
+        removed_fake_video_renderers_.push_back(std::move(to_remove->second));
+        fake_video_renderers_.erase(to_remove);
+      } else {
+        ++it;
+      }
+    }
+    // Create renderers for new video tracks.
+    for (size_t stream_index = 0; stream_index < remote_streams->count();
+         ++stream_index) {
+      MediaStreamInterface* remote_stream = remote_streams->at(stream_index);
+      for (size_t track_index = 0;
+           track_index < remote_stream->GetVideoTracks().size();
+           ++track_index) {
+        const std::string id =
+            remote_stream->GetVideoTracks()[track_index]->id();
+        if (fake_video_renderers_.find(id) != fake_video_renderers_.end()) {
+          continue;
+        }
+        fake_video_renderers_[id].reset(new webrtc::FakeVideoTrackRenderer(
+            remote_stream->GetVideoTracks()[track_index]));
+      }
+    }
+  }
+
+  std::string debug_name_;
+
+  std::unique_ptr<rtc::FakeNetworkManager> fake_network_manager_;
+
+  rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
+  rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
+      peer_connection_factory_;
+
+  // Needed to keep track of number of frames sent.
+  rtc::scoped_refptr<FakeAudioCaptureModule> fake_audio_capture_module_;
+  // Needed to keep track of number of frames received.
+  std::map<std::string, std::unique_ptr<webrtc::FakeVideoTrackRenderer>>
+      fake_video_renderers_;
+  // Needed to ensure frames aren't received for removed tracks.
+  std::vector<std::unique_ptr<webrtc::FakeVideoTrackRenderer>>
+      removed_fake_video_renderers_;
+  // Needed to keep track of number of frames received when external decoder
+  // used.
+  FakeWebRtcVideoDecoderFactory* fake_video_decoder_factory_ = nullptr;
+  FakeWebRtcVideoEncoderFactory* fake_video_encoder_factory_ = nullptr;
+  bool video_decoder_factory_enabled_ = false;
+
+  // For remote peer communication.
+  SignalingMessageReceiver* signaling_message_receiver_ = nullptr;
+  int signaling_delay_ms_ = 0;
+
+  // Store references to the video capturers we've created, so that we can stop
+  // them, if required.
+  std::vector<cricket::FakeVideoCapturer*> video_capturers_;
+  // |local_video_renderer_| attached to the first created local video track.
+  std::unique_ptr<webrtc::FakeVideoTrackRenderer> local_video_renderer_;
+
+  PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options_;
+  std::function<void(cricket::SessionDescription*)> received_sdp_munger_;
+  std::function<void(cricket::SessionDescription*)> generated_sdp_munger_;
+
+  rtc::scoped_refptr<DataChannelInterface> data_channel_;
+  std::unique_ptr<MockDataChannelObserver> data_observer_;
+
+  std::vector<std::unique_ptr<MockRtpReceiverObserver>> rtp_receiver_observers_;
+
+  int transitions_to_gathering_state_ = 0;
+
+  rtc::AsyncInvoker invoker_;
+
+  friend class PeerConnectionIntegrationTest;
+};
+
+// Tests two PeerConnections connecting to each other end-to-end, using a
+// virtual network, fake A/V capture and fake encoder/decoders. The
+// PeerConnections share the threads/socket servers, but use separate versions
+// of everything else (including "PeerConnectionFactory"s).
+class PeerConnectionIntegrationTest : public testing::Test {
+ public:
+  PeerConnectionIntegrationTest()
+      : pss_(new rtc::PhysicalSocketServer),
+        ss_(new rtc::VirtualSocketServer(pss_.get())),
+        network_thread_(new rtc::Thread(ss_.get())),
+        worker_thread_(rtc::Thread::Create()) {
+    RTC_CHECK(network_thread_->Start());
+    RTC_CHECK(worker_thread_->Start());
+  }
+
+  ~PeerConnectionIntegrationTest() {
+    if (caller_) {
+      caller_->set_signaling_message_receiver(nullptr);
+    }
+    if (callee_) {
+      callee_->set_signaling_message_receiver(nullptr);
+    }
+  }
+
+  bool SignalingStateStable() {
+    return caller_->SignalingStateStable() && callee_->SignalingStateStable();
+  }
+
+  bool CreatePeerConnectionWrappers() {
+    return CreatePeerConnectionWrappersWithConfig(
+        PeerConnectionInterface::RTCConfiguration(),
+        PeerConnectionInterface::RTCConfiguration());
+  }
+
+  bool CreatePeerConnectionWrappersWithConstraints(
+      MediaConstraintsInterface* caller_constraints,
+      MediaConstraintsInterface* callee_constraints) {
+    caller_.reset(PeerConnectionWrapper::CreateWithConstraints(
+        "Caller", caller_constraints, network_thread_.get(),
+        worker_thread_.get()));
+    callee_.reset(PeerConnectionWrapper::CreateWithConstraints(
+        "Callee", callee_constraints, network_thread_.get(),
+        worker_thread_.get()));
+    return caller_ && callee_;
+  }
+
+  bool CreatePeerConnectionWrappersWithConfig(
+      const PeerConnectionInterface::RTCConfiguration& caller_config,
+      const PeerConnectionInterface::RTCConfiguration& callee_config) {
+    caller_.reset(PeerConnectionWrapper::CreateWithConfig(
+        "Caller", caller_config, network_thread_.get(), worker_thread_.get()));
+    callee_.reset(PeerConnectionWrapper::CreateWithConfig(
+        "Callee", callee_config, network_thread_.get(), worker_thread_.get()));
+    return caller_ && callee_;
+  }
+
+  bool CreatePeerConnectionWrappersWithOptions(
+      const PeerConnectionFactory::Options& caller_options,
+      const PeerConnectionFactory::Options& callee_options) {
+    caller_.reset(PeerConnectionWrapper::CreateWithOptions(
+        "Caller", caller_options, network_thread_.get(), worker_thread_.get()));
+    callee_.reset(PeerConnectionWrapper::CreateWithOptions(
+        "Callee", callee_options, network_thread_.get(), worker_thread_.get()));
+    return caller_ && callee_;
+  }
+
+  PeerConnectionWrapper* CreatePeerConnectionWrapperWithAlternateKey() {
+    std::unique_ptr<FakeRTCCertificateGenerator> cert_generator(
+        new FakeRTCCertificateGenerator());
+    cert_generator->use_alternate_key();
+
+    // Make sure the new client is using a different certificate.
+    return PeerConnectionWrapper::CreateWithDtlsIdentityStore(
+        "New Peer", std::move(cert_generator), network_thread_.get(),
+        worker_thread_.get());
+  }
+
+  // Once called, SDP blobs and ICE candidates will be automatically signaled
+  // between PeerConnections.
+  void ConnectFakeSignaling() {
+    caller_->set_signaling_message_receiver(callee_.get());
+    callee_->set_signaling_message_receiver(caller_.get());
+  }
+
+  void SetSignalingDelayMs(int delay_ms) {
+    caller_->set_signaling_delay_ms(delay_ms);
+    callee_->set_signaling_delay_ms(delay_ms);
+  }
+
+  void EnableVideoDecoderFactory() {
+    caller_->EnableVideoDecoderFactory();
+    callee_->EnableVideoDecoderFactory();
+  }
+
+  // Messages may get lost on the unreliable DataChannel, so we send multiple
+  // times to avoid test flakiness.
+  void SendRtpDataWithRetries(webrtc::DataChannelInterface* dc,
+                              const std::string& data,
+                              int retries) {
+    for (int i = 0; i < retries; ++i) {
+      dc->Send(DataBuffer(data));
+    }
+  }
+
+  rtc::Thread* network_thread() { return network_thread_.get(); }
+
+  rtc::VirtualSocketServer* virtual_socket_server() { return ss_.get(); }
+
+  PeerConnectionWrapper* caller() { return caller_.get(); }
+
+  // Set the |caller_| to the |wrapper| passed in and return the
+  // original |caller_|.
+  PeerConnectionWrapper* SetCallerPcWrapperAndReturnCurrent(
+      PeerConnectionWrapper* wrapper) {
+    PeerConnectionWrapper* old = caller_.release();
+    caller_.reset(wrapper);
+    return old;
+  }
+
+  PeerConnectionWrapper* callee() { return callee_.get(); }
+
+  // Set the |callee_| to the |wrapper| passed in and return the
+  // original |callee_|.
+  PeerConnectionWrapper* SetCalleePcWrapperAndReturnCurrent(
+      PeerConnectionWrapper* wrapper) {
+    PeerConnectionWrapper* old = callee_.release();
+    callee_.reset(wrapper);
+    return old;
+  }
+
+  // Expects the provided number of new frames to be received within |wait_ms|.
+  // "New frames" meaning that it waits for the current frame counts to
+  // *increase* by the provided values. For video, uses
+  // RecievedVideoFramesForEachTrack for the case of multiple video tracks
+  // being received.
+  void ExpectNewFramesReceivedWithWait(
+      int expected_caller_received_audio_frames,
+      int expected_caller_received_video_frames,
+      int expected_callee_received_audio_frames,
+      int expected_callee_received_video_frames,
+      int wait_ms) {
+    // Add current frame counts to the provided values, in order to wait for
+    // the frame count to increase.
+    expected_caller_received_audio_frames += caller()->audio_frames_received();
+    expected_caller_received_video_frames +=
+        caller()->min_video_frames_received_per_track();
+    expected_callee_received_audio_frames += callee()->audio_frames_received();
+    expected_callee_received_video_frames +=
+        callee()->min_video_frames_received_per_track();
+
+    EXPECT_TRUE_WAIT(caller()->audio_frames_received() >=
+                             expected_caller_received_audio_frames &&
+                         caller()->min_video_frames_received_per_track() >=
+                             expected_caller_received_video_frames &&
+                         callee()->audio_frames_received() >=
+                             expected_callee_received_audio_frames &&
+                         callee()->min_video_frames_received_per_track() >=
+                             expected_callee_received_video_frames,
+                     wait_ms);
+
+    // After the combined wait, do an "expect" for each individual count, to
+    // print out a more detailed message upon failure.
+    EXPECT_GE(caller()->audio_frames_received(),
+              expected_caller_received_audio_frames);
+    EXPECT_GE(caller()->min_video_frames_received_per_track(),
+              expected_caller_received_video_frames);
+    EXPECT_GE(callee()->audio_frames_received(),
+              expected_callee_received_audio_frames);
+    EXPECT_GE(callee()->min_video_frames_received_per_track(),
+              expected_callee_received_video_frames);
+  }
+
+  void TestGcmNegotiationUsesCipherSuite(bool local_gcm_enabled,
+                                         bool remote_gcm_enabled,
+                                         int expected_cipher_suite) {
+    PeerConnectionFactory::Options caller_options;
+    caller_options.crypto_options.enable_gcm_crypto_suites = local_gcm_enabled;
+    PeerConnectionFactory::Options callee_options;
+    callee_options.crypto_options.enable_gcm_crypto_suites = remote_gcm_enabled;
+    ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(caller_options,
+                                                        callee_options));
+    rtc::scoped_refptr<webrtc::FakeMetricsObserver> caller_observer =
+        new rtc::RefCountedObject<webrtc::FakeMetricsObserver>();
+    caller()->pc()->RegisterUMAObserver(caller_observer);
+    ConnectFakeSignaling();
+    caller()->AddAudioVideoMediaStream();
+    callee()->AddAudioVideoMediaStream();
+    caller()->CreateAndSetAndSignalOffer();
+    ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+    EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(expected_cipher_suite),
+                   caller()->GetStats()->SrtpCipher(), kDefaultTimeout);
+    EXPECT_EQ(
+        1, caller_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher,
+                                           expected_cipher_suite));
+    caller()->pc()->RegisterUMAObserver(nullptr);
+  }
+
+ private:
+  // |ss_| is used by |network_thread_| so it must be destroyed later.
+  std::unique_ptr<rtc::PhysicalSocketServer> pss_;
+  std::unique_ptr<rtc::VirtualSocketServer> ss_;
+  // |network_thread_| and |worker_thread_| are used by both
+  // |caller_| and |callee_| so they must be destroyed
+  // later.
+  std::unique_ptr<rtc::Thread> network_thread_;
+  std::unique_ptr<rtc::Thread> worker_thread_;
+  std::unique_ptr<PeerConnectionWrapper> caller_;
+  std::unique_ptr<PeerConnectionWrapper> callee_;
+};
+
+// Test the OnFirstPacketReceived callback from audio/video RtpReceivers.  This
+// includes testing that the callback is invoked if an observer is connected
+// after the first packet has already been received.
+TEST_F(PeerConnectionIntegrationTest,
+       RtpReceiverObserverOnFirstPacketReceived) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  // Start offer/answer exchange and wait for it to complete.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Should be one receiver each for audio/video.
+  EXPECT_EQ(2, caller()->rtp_receiver_observers().size());
+  EXPECT_EQ(2, callee()->rtp_receiver_observers().size());
+  // Wait for all "first packet received" callbacks to be fired.
+  EXPECT_TRUE_WAIT(
+      std::all_of(caller()->rtp_receiver_observers().begin(),
+                  caller()->rtp_receiver_observers().end(),
+                  [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
+                    return o->first_packet_received();
+                  }),
+      kMaxWaitForFramesMs);
+  EXPECT_TRUE_WAIT(
+      std::all_of(callee()->rtp_receiver_observers().begin(),
+                  callee()->rtp_receiver_observers().end(),
+                  [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
+                    return o->first_packet_received();
+                  }),
+      kMaxWaitForFramesMs);
+  // If new observers are set after the first packet was already received, the
+  // callback should still be invoked.
+  caller()->ResetRtpReceiverObservers();
+  callee()->ResetRtpReceiverObservers();
+  EXPECT_EQ(2, caller()->rtp_receiver_observers().size());
+  EXPECT_EQ(2, callee()->rtp_receiver_observers().size());
+  EXPECT_TRUE(
+      std::all_of(caller()->rtp_receiver_observers().begin(),
+                  caller()->rtp_receiver_observers().end(),
+                  [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
+                    return o->first_packet_received();
+                  }));
+  EXPECT_TRUE(
+      std::all_of(callee()->rtp_receiver_observers().begin(),
+                  callee()->rtp_receiver_observers().end(),
+                  [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
+                    return o->first_packet_received();
+                  }));
+}
+
+class DummyDtmfObserver : public DtmfSenderObserverInterface {
+ public:
+  DummyDtmfObserver() : completed_(false) {}
+
+  // Implements DtmfSenderObserverInterface.
+  void OnToneChange(const std::string& tone) override {
+    tones_.push_back(tone);
+    if (tone.empty()) {
+      completed_ = true;
+    }
+  }
+
+  const std::vector<std::string>& tones() const { return tones_; }
+  bool completed() const { return completed_; }
+
+ private:
+  bool completed_;
+  std::vector<std::string> tones_;
+};
+
+// Assumes |sender| already has an audio track added and the offer/answer
+// exchange is done.
+void TestDtmfFromSenderToReceiver(PeerConnectionWrapper* sender,
+                                  PeerConnectionWrapper* receiver) {
+  DummyDtmfObserver observer;
+  rtc::scoped_refptr<DtmfSenderInterface> dtmf_sender;
+
+  // We should be able to create a DTMF sender from a local track.
+  webrtc::AudioTrackInterface* localtrack =
+      sender->local_streams()->at(0)->GetAudioTracks()[0];
+  dtmf_sender = sender->pc()->CreateDtmfSender(localtrack);
+  ASSERT_NE(nullptr, dtmf_sender.get());
+  dtmf_sender->RegisterObserver(&observer);
+
+  // Test the DtmfSender object just created.
+  EXPECT_TRUE(dtmf_sender->CanInsertDtmf());
+  EXPECT_TRUE(dtmf_sender->InsertDtmf("1a", 100, 50));
+
+  EXPECT_TRUE_WAIT(observer.completed(), kDefaultTimeout);
+  std::vector<std::string> tones = {"1", "a", ""};
+  EXPECT_EQ(tones, observer.tones());
+  dtmf_sender->UnregisterObserver();
+  // TODO(deadbeef): Verify the tones were actually received end-to-end.
+}
+
+// Verifies the DtmfSenderObserver callbacks for a DtmfSender (one in each
+// direction).
+TEST_F(PeerConnectionIntegrationTest, DtmfSenderObserver) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Only need audio for DTMF.
+  caller()->AddAudioOnlyMediaStream();
+  callee()->AddAudioOnlyMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  TestDtmfFromSenderToReceiver(caller(), callee());
+  TestDtmfFromSenderToReceiver(callee(), caller());
+}
+
+// Basic end-to-end test, verifying media can be encoded/transmitted/decoded
+// between two connections, using DTLS-SRTP.
+TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithDtls) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Do normal offer/answer and wait for some frames to be received in each
+  // direction.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// Uses SDES instead of DTLS for key agreement.
+TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithSdes) {
+  PeerConnectionInterface::RTCConfiguration sdes_config;
+  sdes_config.enable_dtls_srtp.emplace(false);
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(sdes_config, sdes_config));
+  ConnectFakeSignaling();
+
+  // Do normal offer/answer and wait for some frames to be received in each
+  // direction.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This test sets up a call between two parties (using DTLS) and tests that we
+// can get a video aspect ratio of 16:9.
+TEST_F(PeerConnectionIntegrationTest, SendAndReceive16To9AspectRatio) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+
+  // Add video tracks with 16:9 constraint.
+  FakeConstraints constraints;
+  double requested_ratio = 16.0 / 9;
+  constraints.SetMandatoryMinAspectRatio(requested_ratio);
+  caller()->AddMediaStreamFromTracks(
+      nullptr, caller()->CreateLocalVideoTrackWithConstraints(constraints));
+  callee()->AddMediaStreamFromTracks(
+      nullptr, callee()->CreateLocalVideoTrackWithConstraints(constraints));
+
+  // Do normal offer/answer and wait for at least one frame to be received in
+  // each direction.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(caller()->min_video_frames_received_per_track() > 0 &&
+                       callee()->min_video_frames_received_per_track() > 0,
+                   kMaxWaitForFramesMs);
+
+  // Check rendered aspect ratio.
+  EXPECT_EQ(requested_ratio, caller()->local_rendered_aspect_ratio());
+  EXPECT_EQ(requested_ratio, caller()->rendered_aspect_ratio());
+  EXPECT_EQ(requested_ratio, callee()->local_rendered_aspect_ratio());
+  EXPECT_EQ(requested_ratio, callee()->rendered_aspect_ratio());
+}
+
+// This test sets up a call between two parties with a source resolution of
+// 1280x720 and verifies that a 16:9 aspect ratio is received.
+TEST_F(PeerConnectionIntegrationTest,
+       Send1280By720ResolutionAndReceive16To9AspectRatio) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+
+  // Similar to above test, but uses MandatoryMin[Width/Height] constraint
+  // instead of aspect ratio constraint.
+  FakeConstraints constraints;
+  constraints.SetMandatoryMinWidth(1280);
+  constraints.SetMandatoryMinHeight(720);
+  caller()->AddMediaStreamFromTracks(
+      nullptr, caller()->CreateLocalVideoTrackWithConstraints(constraints));
+  callee()->AddMediaStreamFromTracks(
+      nullptr, callee()->CreateLocalVideoTrackWithConstraints(constraints));
+
+  // Do normal offer/answer and wait for at least one frame to be received in
+  // each direction.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(caller()->min_video_frames_received_per_track() > 0 &&
+                       callee()->min_video_frames_received_per_track() > 0,
+                   kMaxWaitForFramesMs);
+
+  // Check rendered aspect ratio.
+  EXPECT_EQ(16.0 / 9, caller()->local_rendered_aspect_ratio());
+  EXPECT_EQ(16.0 / 9, caller()->rendered_aspect_ratio());
+  EXPECT_EQ(16.0 / 9, callee()->local_rendered_aspect_ratio());
+  EXPECT_EQ(16.0 / 9, callee()->rendered_aspect_ratio());
+}
+
+// This test sets up an one-way call, with media only from caller to
+// callee.
+TEST_F(PeerConnectionIntegrationTest, OneWayMediaCall) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  int caller_received_frames = 0;
+  ExpectNewFramesReceivedWithWait(
+      caller_received_frames, caller_received_frames,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This test sets up a audio call initially, with the callee rejecting video
+// initially. Then later the callee decides to upgrade to audio/video, and
+// initiates a new offer/answer exchange.
+TEST_F(PeerConnectionIntegrationTest, AudioToVideoUpgrade) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Initially, offer an audio/video stream from the caller, but refuse to
+  // send/receive video on the callee side.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddMediaStreamFromTracks(callee()->CreateLocalAudioTrack(),
+                                     nullptr);
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.offer_to_receive_video = 0;
+  callee()->SetOfferAnswerOptions(options);
+  // Do offer/answer and make sure audio is still received end-to-end.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(kDefaultExpectedAudioFrameCount, 0,
+                                  kDefaultExpectedAudioFrameCount, 0,
+                                  kMaxWaitForFramesMs);
+  // Sanity check that the callee's description has a rejected video section.
+  ASSERT_NE(nullptr, callee()->pc()->local_description());
+  const ContentInfo* callee_video_content =
+      GetFirstVideoContent(callee()->pc()->local_description()->description());
+  ASSERT_NE(nullptr, callee_video_content);
+  EXPECT_TRUE(callee_video_content->rejected);
+  // Now negotiate with video and ensure negotiation succeeds, with video
+  // frames and additional audio frames being received.
+  callee()->AddMediaStreamFromTracksWithLabel(
+      nullptr, callee()->CreateLocalVideoTrack(), "video_only_stream");
+  options.offer_to_receive_video = 1;
+  callee()->SetOfferAnswerOptions(options);
+  callee()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Expect additional audio frames to be received after the upgrade.
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This test sets up a call that's transferred to a new caller with a different
+// DTLS fingerprint.
+TEST_F(PeerConnectionIntegrationTest, CallTransferredForCallee) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  // Keep the original peer around which will still send packets to the
+  // receiving client. These SRTP packets will be dropped.
+  std::unique_ptr<PeerConnectionWrapper> original_peer(
+      SetCallerPcWrapperAndReturnCurrent(
+          CreatePeerConnectionWrapperWithAlternateKey()));
+  // TODO(deadbeef): Why do we call Close here? That goes against the comment
+  // directly above.
+  original_peer->pc()->Close();
+
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Wait for some additional frames to be transmitted end-to-end.
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This test sets up a call that's transferred to a new callee with a different
+// DTLS fingerprint.
+TEST_F(PeerConnectionIntegrationTest, CallTransferredForCaller) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  // Keep the original peer around which will still send packets to the
+  // receiving client. These SRTP packets will be dropped.
+  std::unique_ptr<PeerConnectionWrapper> original_peer(
+      SetCalleePcWrapperAndReturnCurrent(
+          CreatePeerConnectionWrapperWithAlternateKey()));
+  // TODO(deadbeef): Why do we call Close here? That goes against the comment
+  // directly above.
+  original_peer->pc()->Close();
+
+  ConnectFakeSignaling();
+  callee()->AddAudioVideoMediaStream();
+  caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions());
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Wait for some additional frames to be transmitted end-to-end.
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This test sets up a non-bundled call and negotiates bundling at the same
+// time as starting an ICE restart. When bundling is in effect in the restart,
+// the DTLS-SRTP context should be successfully reset.
+TEST_F(PeerConnectionIntegrationTest, BundlingEnabledWhileIceRestartOccurs) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  // Remove the bundle group from the SDP received by the callee.
+  callee()->SetReceivedSdpMunger([](cricket::SessionDescription* desc) {
+    desc->RemoveGroupByName("BUNDLE");
+  });
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+
+  // Now stop removing the BUNDLE group, and trigger an ICE restart.
+  callee()->SetReceivedSdpMunger(nullptr);
+  caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions());
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  // Expect additional frames to be received after the ICE restart.
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// Test CVO (Coordination of Video Orientation). If a video source is rotated
+// and both peers support the CVO RTP header extension, the actual video frames
+// don't need to be encoded in different resolutions, since the rotation is
+// communicated through the RTP header extension.
+TEST_F(PeerConnectionIntegrationTest, RotatedVideoWithCVOExtension) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Add rotated video tracks.
+  caller()->AddMediaStreamFromTracks(
+      nullptr,
+      caller()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_90));
+  callee()->AddMediaStreamFromTracks(
+      nullptr,
+      callee()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_270));
+
+  // Wait for video frames to be received by both sides.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ASSERT_TRUE_WAIT(caller()->min_video_frames_received_per_track() > 0 &&
+                       callee()->min_video_frames_received_per_track() > 0,
+                   kMaxWaitForFramesMs);
+
+  // Ensure that the aspect ratio is unmodified.
+  // TODO(deadbeef): Where does 4:3 come from? Should be explicit in the test,
+  // not just assumed.
+  EXPECT_EQ(4.0 / 3, caller()->local_rendered_aspect_ratio());
+  EXPECT_EQ(4.0 / 3, caller()->rendered_aspect_ratio());
+  EXPECT_EQ(4.0 / 3, callee()->local_rendered_aspect_ratio());
+  EXPECT_EQ(4.0 / 3, callee()->rendered_aspect_ratio());
+  // Ensure that the CVO bits were surfaced to the renderer.
+  EXPECT_EQ(webrtc::kVideoRotation_270, caller()->rendered_rotation());
+  EXPECT_EQ(webrtc::kVideoRotation_90, callee()->rendered_rotation());
+}
+
+// Test that when the CVO extension isn't supported, video is rotated the
+// old-fashioned way, by encoding rotated frames.
+TEST_F(PeerConnectionIntegrationTest, RotatedVideoWithoutCVOExtension) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Add rotated video tracks.
+  caller()->AddMediaStreamFromTracks(
+      nullptr,
+      caller()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_90));
+  callee()->AddMediaStreamFromTracks(
+      nullptr,
+      callee()->CreateLocalVideoTrackWithRotation(webrtc::kVideoRotation_270));
+
+  // Remove the CVO extension from the offered SDP.
+  callee()->SetReceivedSdpMunger([](cricket::SessionDescription* desc) {
+    cricket::VideoContentDescription* video =
+        GetFirstVideoContentDescription(desc);
+    video->ClearRtpHeaderExtensions();
+  });
+  // Wait for video frames to be received by both sides.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ASSERT_TRUE_WAIT(caller()->min_video_frames_received_per_track() > 0 &&
+                       callee()->min_video_frames_received_per_track() > 0,
+                   kMaxWaitForFramesMs);
+
+  // Expect that the aspect ratio is inversed to account for the 90/270 degree
+  // rotation.
+  // TODO(deadbeef): Where does 4:3 come from? Should be explicit in the test,
+  // not just assumed.
+  EXPECT_EQ(3.0 / 4, caller()->local_rendered_aspect_ratio());
+  EXPECT_EQ(3.0 / 4, caller()->rendered_aspect_ratio());
+  EXPECT_EQ(3.0 / 4, callee()->local_rendered_aspect_ratio());
+  EXPECT_EQ(3.0 / 4, callee()->rendered_aspect_ratio());
+  // Expect that each endpoint is unaware of the rotation of the other endpoint.
+  EXPECT_EQ(webrtc::kVideoRotation_0, caller()->rendered_rotation());
+  EXPECT_EQ(webrtc::kVideoRotation_0, callee()->rendered_rotation());
+}
+
+// TODO(deadbeef): The tests below rely on RTCOfferAnswerOptions to reject an
+// m= section. When we implement Unified Plan SDP, the right way to do this
+// would be by stopping an RtpTransceiver.
+
+// Test that if the answerer rejects the audio m= section, no audio is sent or
+// received, but video still can be.
+TEST_F(PeerConnectionIntegrationTest, AnswererRejectsAudioSection) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  // Only add video track for callee, and set offer_to_receive_audio to 0, so
+  // it will reject the audio m= section completely.
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.offer_to_receive_audio = 0;
+  callee()->SetOfferAnswerOptions(options);
+  callee()->AddMediaStreamFromTracks(nullptr,
+                                     callee()->CreateLocalVideoTrack());
+  // Do offer/answer and wait for successful end-to-end video frames.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(0, kDefaultExpectedVideoFrameCount, 0,
+                                  kDefaultExpectedVideoFrameCount,
+                                  kMaxWaitForFramesMs);
+  // Shouldn't have received audio frames at any point.
+  EXPECT_EQ(0, caller()->audio_frames_received());
+  EXPECT_EQ(0, callee()->audio_frames_received());
+  // Sanity check that the callee's description has a rejected audio section.
+  ASSERT_NE(nullptr, callee()->pc()->local_description());
+  const ContentInfo* callee_audio_content =
+      GetFirstAudioContent(callee()->pc()->local_description()->description());
+  ASSERT_NE(nullptr, callee_audio_content);
+  EXPECT_TRUE(callee_audio_content->rejected);
+}
+
+// Test that if the answerer rejects the video m= section, no video is sent or
+// received, but audio still can be.
+TEST_F(PeerConnectionIntegrationTest, AnswererRejectsVideoSection) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  // Only add audio track for callee, and set offer_to_receive_video to 0, so
+  // it will reject the video m= section completely.
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.offer_to_receive_video = 0;
+  callee()->SetOfferAnswerOptions(options);
+  callee()->AddMediaStreamFromTracks(callee()->CreateLocalAudioTrack(),
+                                     nullptr);
+  // Do offer/answer and wait for successful end-to-end audio frames.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(kDefaultExpectedAudioFrameCount, 0,
+                                  kDefaultExpectedAudioFrameCount, 0,
+                                  kMaxWaitForFramesMs);
+  // Shouldn't have received video frames at any point.
+  EXPECT_EQ(0, caller()->total_video_frames_received());
+  EXPECT_EQ(0, callee()->total_video_frames_received());
+  // Sanity check that the callee's description has a rejected video section.
+  ASSERT_NE(nullptr, callee()->pc()->local_description());
+  const ContentInfo* callee_video_content =
+      GetFirstVideoContent(callee()->pc()->local_description()->description());
+  ASSERT_NE(nullptr, callee_video_content);
+  EXPECT_TRUE(callee_video_content->rejected);
+}
+
+// Test that if the answerer rejects both audio and video m= sections, nothing
+// bad happens.
+// TODO(deadbeef): Test that a data channel still works. Currently this doesn't
+// test anything but the fact that negotiation succeeds, which doesn't mean
+// much.
+TEST_F(PeerConnectionIntegrationTest, AnswererRejectsAudioAndVideoSections) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  // Don't give the callee any tracks, and set offer_to_receive_X to 0, so it
+  // will reject both audio and video m= sections.
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.offer_to_receive_audio = 0;
+  options.offer_to_receive_video = 0;
+  callee()->SetOfferAnswerOptions(options);
+  // Do offer/answer and wait for stable signaling state.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Sanity check that the callee's description has rejected m= sections.
+  ASSERT_NE(nullptr, callee()->pc()->local_description());
+  const ContentInfo* callee_audio_content =
+      GetFirstAudioContent(callee()->pc()->local_description()->description());
+  ASSERT_NE(nullptr, callee_audio_content);
+  EXPECT_TRUE(callee_audio_content->rejected);
+  const ContentInfo* callee_video_content =
+      GetFirstVideoContent(callee()->pc()->local_description()->description());
+  ASSERT_NE(nullptr, callee_video_content);
+  EXPECT_TRUE(callee_video_content->rejected);
+}
+
+// This test sets up an audio and video call between two parties. After the
+// call runs for a while, the caller sends an updated offer with video being
+// rejected. Once the re-negotiation is done, the video flow should stop and
+// the audio flow should continue.
+TEST_F(PeerConnectionIntegrationTest, VideoRejectedInSubsequentOffer) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+
+  // Renegotiate, rejecting the video m= section.
+  // TODO(deadbeef): When an RtpTransceiver API is available, use that to
+  // reject the video m= section.
+  caller()->SetGeneratedSdpMunger([](cricket::SessionDescription* description) {
+    for (cricket::ContentInfo& content : description->contents()) {
+      if (cricket::IsVideoContent(&content)) {
+        content.rejected = true;
+      }
+    }
+  });
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kMaxWaitForActivationMs);
+
+  // Sanity check that the caller's description has a rejected video section.
+  ASSERT_NE(nullptr, caller()->pc()->local_description());
+  const ContentInfo* caller_video_content =
+      GetFirstVideoContent(caller()->pc()->local_description()->description());
+  ASSERT_NE(nullptr, caller_video_content);
+  EXPECT_TRUE(caller_video_content->rejected);
+
+  int caller_video_received = caller()->total_video_frames_received();
+  int callee_video_received = callee()->total_video_frames_received();
+
+  // Wait for some additional audio frames to be received.
+  ExpectNewFramesReceivedWithWait(kDefaultExpectedAudioFrameCount, 0,
+                                  kDefaultExpectedAudioFrameCount, 0,
+                                  kMaxWaitForFramesMs);
+
+  // During this time, we shouldn't have received any additional video frames
+  // for the rejected video tracks.
+  EXPECT_EQ(caller_video_received, caller()->total_video_frames_received());
+  EXPECT_EQ(callee_video_received, callee()->total_video_frames_received());
+}
+
+// Basic end-to-end test, but without SSRC/MSID signaling. This functionality
+// is needed to support legacy endpoints.
+// TODO(deadbeef): When we support the MID extension and demuxing on MID, also
+// add a test for an end-to-end test without MID signaling either (basically,
+// the minimum acceptable SDP).
+TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithoutSsrcOrMsidSignaling) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Add audio and video, testing that packets can be demuxed on payload type.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  // Remove all stream information (SSRCs, track IDs, etc.) and "msid-semantic"
+  // attribute from received SDP, simulating a legacy endpoint.
+  callee()->SetReceivedSdpMunger([](cricket::SessionDescription* desc) {
+    for (ContentInfo& content : desc->contents()) {
+      MediaContentDescription* media_desc =
+          static_cast<MediaContentDescription*>(content.description);
+      media_desc->mutable_streams().clear();
+    }
+    desc->set_msid_supported(false);
+  });
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// Test that if two video tracks are sent (from caller to callee, in this test),
+// they're transmitted correctly end-to-end.
+TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithTwoVideoTracks) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Add one audio/video stream, and one video-only stream.
+  caller()->AddAudioVideoMediaStream();
+  caller()->AddMediaStreamFromTracksWithLabel(
+      nullptr, caller()->CreateLocalVideoTrackWithId("extra_track"),
+      "extra_stream");
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ASSERT_EQ(2u, callee()->number_of_remote_streams());
+  int expected_callee_received_frames = kDefaultExpectedVideoFrameCount;
+  ExpectNewFramesReceivedWithWait(0, 0, 0, expected_callee_received_frames,
+                                  kMaxWaitForFramesMs);
+}
+
+static void MakeSpecCompliantMaxBundleOffer(cricket::SessionDescription* desc) {
+  bool first = true;
+  for (cricket::ContentInfo& content : desc->contents()) {
+    if (first) {
+      first = false;
+      continue;
+    }
+    content.bundle_only = true;
+  }
+  first = true;
+  for (cricket::TransportInfo& transport : desc->transport_infos()) {
+    if (first) {
+      first = false;
+      continue;
+    }
+    transport.description.ice_ufrag.clear();
+    transport.description.ice_pwd.clear();
+    transport.description.connection_role = cricket::CONNECTIONROLE_NONE;
+    transport.description.identity_fingerprint.reset(nullptr);
+  }
+}
+
+// Test that if applying a true "max bundle" offer, which uses ports of 0,
+// "a=bundle-only", omitting "a=fingerprint", "a=setup", "a=ice-ufrag" and
+// "a=ice-pwd" for all but the audio "m=" section, negotiation still completes
+// successfully and media flows.
+// TODO(deadbeef): Update this test to also omit "a=rtcp-mux", once that works.
+// TODO(deadbeef): Won't need this test once we start generating actual
+// standards-compliant SDP.
+TEST_F(PeerConnectionIntegrationTest,
+       EndToEndCallWithSpecCompliantMaxBundleOffer) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  // Do the equivalent of setting the port to 0, adding a=bundle-only, and
+  // removing a=ice-ufrag, a=ice-pwd, a=fingerprint and a=setup from all
+  // but the first m= section.
+  callee()->SetReceivedSdpMunger(MakeSpecCompliantMaxBundleOffer);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// Test that we can receive the audio output level from a remote audio track.
+// TODO(deadbeef): Use a fake audio source and verify that the output level is
+// exactly what the source on the other side was configured with.
+TEST_F(PeerConnectionIntegrationTest, GetAudioOutputLevelStats) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Just add an audio track.
+  caller()->AddMediaStreamFromTracks(caller()->CreateLocalAudioTrack(),
+                                     nullptr);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  // Get the audio output level stats. Note that the level is not available
+  // until an RTCP packet has been received.
+  EXPECT_TRUE_WAIT(callee()->GetStats()->AudioOutputLevel() > 0,
+                   kMaxWaitForFramesMs);
+}
+
+// Test that an audio input level is reported.
+// TODO(deadbeef): Use a fake audio source and verify that the input level is
+// exactly what the source was configured with.
+TEST_F(PeerConnectionIntegrationTest, GetAudioInputLevelStats) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Just add an audio track.
+  caller()->AddMediaStreamFromTracks(caller()->CreateLocalAudioTrack(),
+                                     nullptr);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  // Get the audio input level stats. The level should be available very
+  // soon after the test starts.
+  EXPECT_TRUE_WAIT(caller()->GetStats()->AudioInputLevel() > 0,
+                   kMaxWaitForStatsMs);
+}
+
+// Test that we can get incoming byte counts from both audio and video tracks.
+TEST_F(PeerConnectionIntegrationTest, GetBytesReceivedStats) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  // Do offer/answer, wait for the callee to receive some frames.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  int expected_caller_received_frames = 0;
+  ExpectNewFramesReceivedWithWait(
+      expected_caller_received_frames, expected_caller_received_frames,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+
+  // Get a handle to the remote tracks created, so they can be used as GetStats
+  // filters.
+  StreamCollectionInterface* remote_streams = callee()->remote_streams();
+  ASSERT_EQ(1u, remote_streams->count());
+  ASSERT_EQ(1u, remote_streams->at(0)->GetAudioTracks().size());
+  ASSERT_EQ(1u, remote_streams->at(0)->GetVideoTracks().size());
+  MediaStreamTrackInterface* remote_audio_track =
+      remote_streams->at(0)->GetAudioTracks()[0];
+  MediaStreamTrackInterface* remote_video_track =
+      remote_streams->at(0)->GetVideoTracks()[0];
+
+  // We received frames, so we definitely should have nonzero "received bytes"
+  // stats at this point.
+  EXPECT_GT(callee()->GetStatsForTrack(remote_audio_track)->BytesReceived(), 0);
+  EXPECT_GT(callee()->GetStatsForTrack(remote_video_track)->BytesReceived(), 0);
+}
+
+// Test that we can get outgoing byte counts from both audio and video tracks.
+TEST_F(PeerConnectionIntegrationTest, GetBytesSentStats) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  auto audio_track = caller()->CreateLocalAudioTrack();
+  auto video_track = caller()->CreateLocalVideoTrack();
+  caller()->AddMediaStreamFromTracks(audio_track, video_track);
+  // Do offer/answer, wait for the callee to receive some frames.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  int expected_caller_received_frames = 0;
+  ExpectNewFramesReceivedWithWait(
+      expected_caller_received_frames, expected_caller_received_frames,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+
+  // The callee received frames, so we definitely should have nonzero "sent
+  // bytes" stats at this point.
+  EXPECT_GT(caller()->GetStatsForTrack(audio_track)->BytesSent(), 0);
+  EXPECT_GT(caller()->GetStatsForTrack(video_track)->BytesSent(), 0);
+}
+
+// Test that DTLS 1.0 is used if both sides only support DTLS 1.0.
+TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithDtls10) {
+  PeerConnectionFactory::Options dtls_10_options;
+  dtls_10_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10;
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(dtls_10_options,
+                                                      dtls_10_options));
+  ConnectFakeSignaling();
+  // Do normal offer/answer and wait for some frames to be received in each
+  // direction.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// Test getting cipher stats and UMA metrics when DTLS 1.0 is negotiated.
+TEST_F(PeerConnectionIntegrationTest, Dtls10CipherStatsAndUmaMetrics) {
+  PeerConnectionFactory::Options dtls_10_options;
+  dtls_10_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10;
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(dtls_10_options,
+                                                      dtls_10_options));
+  ConnectFakeSignaling();
+  // Register UMA observer before signaling begins.
+  rtc::scoped_refptr<webrtc::FakeMetricsObserver> caller_observer =
+      new rtc::RefCountedObject<webrtc::FakeMetricsObserver>();
+  caller()->pc()->RegisterUMAObserver(caller_observer);
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(rtc::SSLStreamAdapter::IsAcceptableCipher(
+                       caller()->GetStats()->DtlsCipher(), rtc::KT_DEFAULT),
+                   kDefaultTimeout);
+  EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite),
+                 caller()->GetStats()->SrtpCipher(), kDefaultTimeout);
+  EXPECT_EQ(1,
+            caller_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher,
+                                            kDefaultSrtpCryptoSuite));
+}
+
+// Test getting cipher stats and UMA metrics when DTLS 1.2 is negotiated.
+TEST_F(PeerConnectionIntegrationTest, Dtls12CipherStatsAndUmaMetrics) {
+  PeerConnectionFactory::Options dtls_12_options;
+  dtls_12_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12;
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(dtls_12_options,
+                                                      dtls_12_options));
+  ConnectFakeSignaling();
+  // Register UMA observer before signaling begins.
+  rtc::scoped_refptr<webrtc::FakeMetricsObserver> caller_observer =
+      new rtc::RefCountedObject<webrtc::FakeMetricsObserver>();
+  caller()->pc()->RegisterUMAObserver(caller_observer);
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(rtc::SSLStreamAdapter::IsAcceptableCipher(
+                       caller()->GetStats()->DtlsCipher(), rtc::KT_DEFAULT),
+                   kDefaultTimeout);
+  EXPECT_EQ_WAIT(rtc::SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite),
+                 caller()->GetStats()->SrtpCipher(), kDefaultTimeout);
+  EXPECT_EQ(1,
+            caller_observer->GetEnumCounter(webrtc::kEnumCounterAudioSrtpCipher,
+                                            kDefaultSrtpCryptoSuite));
+}
+
+// Test that DTLS 1.0 can be used if the caller supports DTLS 1.2 and the
+// callee only supports 1.0.
+TEST_F(PeerConnectionIntegrationTest, CallerDtls12ToCalleeDtls10) {
+  PeerConnectionFactory::Options caller_options;
+  caller_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12;
+  PeerConnectionFactory::Options callee_options;
+  callee_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10;
+  ASSERT_TRUE(
+      CreatePeerConnectionWrappersWithOptions(caller_options, callee_options));
+  ConnectFakeSignaling();
+  // Do normal offer/answer and wait for some frames to be received in each
+  // direction.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// Test that DTLS 1.0 can be used if the caller only supports DTLS 1.0 and the
+// callee supports 1.2.
+TEST_F(PeerConnectionIntegrationTest, CallerDtls10ToCalleeDtls12) {
+  PeerConnectionFactory::Options caller_options;
+  caller_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_10;
+  PeerConnectionFactory::Options callee_options;
+  callee_options.ssl_max_version = rtc::SSL_PROTOCOL_DTLS_12;
+  ASSERT_TRUE(
+      CreatePeerConnectionWrappersWithOptions(caller_options, callee_options));
+  ConnectFakeSignaling();
+  // Do normal offer/answer and wait for some frames to be received in each
+  // direction.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// Test that a non-GCM cipher is used if both sides only support non-GCM.
+TEST_F(PeerConnectionIntegrationTest, NonGcmCipherUsedWhenGcmNotSupported) {
+  bool local_gcm_enabled = false;
+  bool remote_gcm_enabled = false;
+  int expected_cipher_suite = kDefaultSrtpCryptoSuite;
+  TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled,
+                                    expected_cipher_suite);
+}
+
+// Test that a GCM cipher is used if both ends support it.
+TEST_F(PeerConnectionIntegrationTest, GcmCipherUsedWhenGcmSupported) {
+  bool local_gcm_enabled = true;
+  bool remote_gcm_enabled = true;
+  int expected_cipher_suite = kDefaultSrtpCryptoSuiteGcm;
+  TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled,
+                                    expected_cipher_suite);
+}
+
+// Test that GCM isn't used if only the offerer supports it.
+TEST_F(PeerConnectionIntegrationTest,
+       NonGcmCipherUsedWhenOnlyCallerSupportsGcm) {
+  bool local_gcm_enabled = true;
+  bool remote_gcm_enabled = false;
+  int expected_cipher_suite = kDefaultSrtpCryptoSuite;
+  TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled,
+                                    expected_cipher_suite);
+}
+
+// Test that GCM isn't used if only the answerer supports it.
+TEST_F(PeerConnectionIntegrationTest,
+       NonGcmCipherUsedWhenOnlyCalleeSupportsGcm) {
+  bool local_gcm_enabled = false;
+  bool remote_gcm_enabled = true;
+  int expected_cipher_suite = kDefaultSrtpCryptoSuite;
+  TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled,
+                                    expected_cipher_suite);
+}
+
+// This test sets up a call between two parties with audio, video and an RTP
+// data channel.
+TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithRtpDataChannel) {
+  FakeConstraints setup_constraints;
+  setup_constraints.SetAllowRtpDataChannels();
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConstraints(&setup_constraints,
+                                                          &setup_constraints));
+  ConnectFakeSignaling();
+  // Expect that data channel created on caller side will show up for callee as
+  // well.
+  caller()->CreateDataChannel();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Ensure the existence of the RTP data channel didn't impede audio/video.
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_NE(nullptr, callee()->data_channel());
+  EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+
+  // Ensure data can be sent in both directions.
+  std::string data = "hello world";
+  SendRtpDataWithRetries(caller()->data_channel(), data, 5);
+  EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(),
+                 kDefaultTimeout);
+  SendRtpDataWithRetries(callee()->data_channel(), data, 5);
+  EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(),
+                 kDefaultTimeout);
+}
+
+// Ensure that an RTP data channel is signaled as closed for the caller when
+// the callee rejects it in a subsequent offer.
+TEST_F(PeerConnectionIntegrationTest,
+       RtpDataChannelSignaledClosedInCalleeOffer) {
+  // Same procedure as above test.
+  FakeConstraints setup_constraints;
+  setup_constraints.SetAllowRtpDataChannels();
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConstraints(&setup_constraints,
+                                                          &setup_constraints));
+  ConnectFakeSignaling();
+  caller()->CreateDataChannel();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_NE(nullptr, callee()->data_channel());
+  ASSERT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  ASSERT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+
+  // Close the data channel on the callee, and do an updated offer/answer.
+  callee()->data_channel()->Close();
+  callee()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  EXPECT_FALSE(caller()->data_observer()->IsOpen());
+  EXPECT_FALSE(callee()->data_observer()->IsOpen());
+}
+
+// Tests that data is buffered in an RTP data channel until an observer is
+// registered for it.
+//
+// NOTE: RTP data channels can receive data before the underlying
+// transport has detected that a channel is writable and thus data can be
+// received before the data channel state changes to open. That is hard to test
+// but the same buffering is expected to be used in that case.
+TEST_F(PeerConnectionIntegrationTest,
+       DataBufferedUntilRtpDataChannelObserverRegistered) {
+  // Use fake clock and simulated network delay so that we predictably can wait
+  // until an SCTP message has been delivered without "sleep()"ing.
+  rtc::ScopedFakeClock fake_clock;
+  // Some things use a time of "0" as a special value, so we need to start out
+  // the fake clock at a nonzero time.
+  // TODO(deadbeef): Fix this.
+  fake_clock.AdvanceTime(rtc::TimeDelta::FromSeconds(1));
+  virtual_socket_server()->set_delay_mean(5);  // 5 ms per hop.
+  virtual_socket_server()->UpdateDelayDistribution();
+
+  FakeConstraints constraints;
+  constraints.SetAllowRtpDataChannels();
+  ASSERT_TRUE(
+      CreatePeerConnectionWrappersWithConstraints(&constraints, &constraints));
+  ConnectFakeSignaling();
+  caller()->CreateDataChannel();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE(caller()->data_channel() != nullptr);
+  ASSERT_TRUE_SIMULATED_WAIT(callee()->data_channel() != nullptr,
+                             kDefaultTimeout, fake_clock);
+  ASSERT_TRUE_SIMULATED_WAIT(caller()->data_observer()->IsOpen(),
+                             kDefaultTimeout, fake_clock);
+  ASSERT_EQ_SIMULATED_WAIT(DataChannelInterface::kOpen,
+                           callee()->data_channel()->state(), kDefaultTimeout,
+                           fake_clock);
+
+  // Unregister the observer which is normally automatically registered.
+  callee()->data_channel()->UnregisterObserver();
+  // Send data and advance fake clock until it should have been received.
+  std::string data = "hello world";
+  caller()->data_channel()->Send(DataBuffer(data));
+  SIMULATED_WAIT(false, 50, fake_clock);
+
+  // Attach data channel and expect data to be received immediately. Note that
+  // EXPECT_EQ_WAIT is used, such that the simulated clock is not advanced any
+  // further, but data can be received even if the callback is asynchronous.
+  MockDataChannelObserver new_observer(callee()->data_channel());
+  EXPECT_EQ_SIMULATED_WAIT(data, new_observer.last_message(), kDefaultTimeout,
+                           fake_clock);
+}
+
+// This test sets up a call between two parties with audio, video and but only
+// the caller client supports RTP data channels.
+TEST_F(PeerConnectionIntegrationTest, RtpDataChannelsRejectedByCallee) {
+  FakeConstraints setup_constraints_1;
+  setup_constraints_1.SetAllowRtpDataChannels();
+  // Must disable DTLS to make negotiation succeed.
+  setup_constraints_1.SetMandatory(MediaConstraintsInterface::kEnableDtlsSrtp,
+                                   false);
+  FakeConstraints setup_constraints_2;
+  setup_constraints_2.SetMandatory(MediaConstraintsInterface::kEnableDtlsSrtp,
+                                   false);
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConstraints(
+      &setup_constraints_1, &setup_constraints_2));
+  ConnectFakeSignaling();
+  caller()->CreateDataChannel();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // The caller should still have a data channel, but it should be closed, and
+  // one should ever have been created for the callee.
+  EXPECT_TRUE(caller()->data_channel() != nullptr);
+  EXPECT_FALSE(caller()->data_observer()->IsOpen());
+  EXPECT_EQ(nullptr, callee()->data_channel());
+}
+
+// This test sets up a call between two parties with audio, and video. When
+// audio and video is setup and flowing, an RTP data channel is negotiated.
+TEST_F(PeerConnectionIntegrationTest, AddRtpDataChannelInSubsequentOffer) {
+  FakeConstraints setup_constraints;
+  setup_constraints.SetAllowRtpDataChannels();
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConstraints(&setup_constraints,
+                                                          &setup_constraints));
+  ConnectFakeSignaling();
+  // Do initial offer/answer with audio/video.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Create data channel and do new offer and answer.
+  caller()->CreateDataChannel();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_NE(nullptr, callee()->data_channel());
+  EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+  // Ensure data can be sent in both directions.
+  std::string data = "hello world";
+  SendRtpDataWithRetries(caller()->data_channel(), data, 5);
+  EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(),
+                 kDefaultTimeout);
+  SendRtpDataWithRetries(callee()->data_channel(), data, 5);
+  EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(),
+                 kDefaultTimeout);
+}
+
+#ifdef HAVE_SCTP
+
+// This test sets up a call between two parties with audio, video and an SCTP
+// data channel.
+TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithSctpDataChannel) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Expect that data channel created on caller side will show up for callee as
+  // well.
+  caller()->CreateDataChannel();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Ensure the existence of the SCTP data channel didn't impede audio/video.
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+  // Caller data channel should already exist (it created one). Callee data
+  // channel may not exist yet, since negotiation happens in-band, not in SDP.
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout);
+  EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+
+  // Ensure data can be sent in both directions.
+  std::string data = "hello world";
+  caller()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(),
+                 kDefaultTimeout);
+  callee()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(),
+                 kDefaultTimeout);
+}
+
+// Ensure that when the callee closes an SCTP data channel, the closing
+// procedure results in the data channel being closed for the caller as well.
+TEST_F(PeerConnectionIntegrationTest, CalleeClosesSctpDataChannel) {
+  // Same procedure as above test.
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->CreateDataChannel();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout);
+  ASSERT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  ASSERT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+
+  // Close the data channel on the callee side, and wait for it to reach the
+  // "closed" state on both sides.
+  callee()->data_channel()->Close();
+  EXPECT_TRUE_WAIT(!caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(!callee()->data_observer()->IsOpen(), kDefaultTimeout);
+}
+
+// Test usrsctp's ability to process unordered data stream, where data actually
+// arrives out of order using simulated delays. Previously there have been some
+// bugs in this area.
+TEST_F(PeerConnectionIntegrationTest, StressTestUnorderedSctpDataChannel) {
+  // Introduce random network delays.
+  // Otherwise it's not a true "unordered" test.
+  virtual_socket_server()->set_delay_mean(20);
+  virtual_socket_server()->set_delay_stddev(5);
+  virtual_socket_server()->UpdateDelayDistribution();
+  // Normal procedure, but with unordered data channel config.
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  webrtc::DataChannelInit init;
+  init.ordered = false;
+  caller()->CreateDataChannel(&init);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout);
+  ASSERT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  ASSERT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+
+  static constexpr int kNumMessages = 100;
+  // Deliberately chosen to be larger than the MTU so messages get fragmented.
+  static constexpr size_t kMaxMessageSize = 4096;
+  // Create and send random messages.
+  std::vector<std::string> sent_messages;
+  for (int i = 0; i < kNumMessages; ++i) {
+    size_t length =
+        (rand() % kMaxMessageSize) + 1;  // NOLINT (rand_r instead of rand)
+    std::string message;
+    ASSERT_TRUE(rtc::CreateRandomString(length, &message));
+    caller()->data_channel()->Send(DataBuffer(message));
+    callee()->data_channel()->Send(DataBuffer(message));
+    sent_messages.push_back(message);
+  }
+
+  // Wait for all messages to be received.
+  EXPECT_EQ_WAIT(kNumMessages,
+                 caller()->data_observer()->received_message_count(),
+                 kDefaultTimeout);
+  EXPECT_EQ_WAIT(kNumMessages,
+                 callee()->data_observer()->received_message_count(),
+                 kDefaultTimeout);
+
+  // Sort and compare to make sure none of the messages were corrupted.
+  std::vector<std::string> caller_received_messages =
+      caller()->data_observer()->messages();
+  std::vector<std::string> callee_received_messages =
+      callee()->data_observer()->messages();
+  std::sort(sent_messages.begin(), sent_messages.end());
+  std::sort(caller_received_messages.begin(), caller_received_messages.end());
+  std::sort(callee_received_messages.begin(), callee_received_messages.end());
+  EXPECT_EQ(sent_messages, caller_received_messages);
+  EXPECT_EQ(sent_messages, callee_received_messages);
+}
+
+// This test sets up a call between two parties with audio, and video. When
+// audio and video are setup and flowing, an SCTP data channel is negotiated.
+TEST_F(PeerConnectionIntegrationTest, AddSctpDataChannelInSubsequentOffer) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Do initial offer/answer with audio/video.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Create data channel and do new offer and answer.
+  caller()->CreateDataChannel();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Caller data channel should already exist (it created one). Callee data
+  // channel may not exist yet, since negotiation happens in-band, not in SDP.
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout);
+  EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+  // Ensure data can be sent in both directions.
+  std::string data = "hello world";
+  caller()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(),
+                 kDefaultTimeout);
+  callee()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(),
+                 kDefaultTimeout);
+}
+
+#endif  // HAVE_SCTP
+
+// Test that the ICE connection and gathering states eventually reach
+// "complete".
+TEST_F(PeerConnectionIntegrationTest, IceStatesReachCompletion) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Do normal offer/answer.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceGatheringComplete,
+                 caller()->ice_gathering_state(), kMaxWaitForFramesMs);
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceGatheringComplete,
+                 callee()->ice_gathering_state(), kMaxWaitForFramesMs);
+  // After the best candidate pair is selected and all candidates are signaled,
+  // the ICE connection state should reach "complete".
+  // TODO(deadbeef): Currently, the ICE "controlled" agent (the
+  // answerer/"callee" by default) only reaches "connected". When this is
+  // fixed, this test should be updated.
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted,
+                 caller()->ice_connection_state(), kDefaultTimeout);
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected,
+                 callee()->ice_connection_state(), kDefaultTimeout);
+}
+
+// This test sets up a call between two parties with audio and video.
+// During the call, the caller restarts ICE and the test verifies that
+// new ICE candidates are generated and audio and video still can flow, and the
+// ICE state reaches completed again.
+TEST_F(PeerConnectionIntegrationTest, MediaContinuesFlowingAfterIceRestart) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Do normal offer/answer and wait for ICE to complete.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted,
+                 caller()->ice_connection_state(), kMaxWaitForFramesMs);
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected,
+                 callee()->ice_connection_state(), kMaxWaitForFramesMs);
+
+  // To verify that the ICE restart actually occurs, get
+  // ufrag/password/candidates before and after restart.
+  // Create an SDP string of the first audio candidate for both clients.
+  const webrtc::IceCandidateCollection* audio_candidates_caller =
+      caller()->pc()->local_description()->candidates(0);
+  const webrtc::IceCandidateCollection* audio_candidates_callee =
+      callee()->pc()->local_description()->candidates(0);
+  ASSERT_GT(audio_candidates_caller->count(), 0u);
+  ASSERT_GT(audio_candidates_callee->count(), 0u);
+  std::string caller_candidate_pre_restart;
+  ASSERT_TRUE(
+      audio_candidates_caller->at(0)->ToString(&caller_candidate_pre_restart));
+  std::string callee_candidate_pre_restart;
+  ASSERT_TRUE(
+      audio_candidates_callee->at(0)->ToString(&callee_candidate_pre_restart));
+  const cricket::SessionDescription* desc =
+      caller()->pc()->local_description()->description();
+  std::string caller_ufrag_pre_restart =
+      desc->transport_infos()[0].description.ice_ufrag;
+  desc = callee()->pc()->local_description()->description();
+  std::string callee_ufrag_pre_restart =
+      desc->transport_infos()[0].description.ice_ufrag;
+
+  // Have the caller initiate an ICE restart.
+  caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions());
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted,
+                 caller()->ice_connection_state(), kMaxWaitForFramesMs);
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected,
+                 callee()->ice_connection_state(), kMaxWaitForFramesMs);
+
+  // Grab the ufrags/candidates again.
+  audio_candidates_caller = caller()->pc()->local_description()->candidates(0);
+  audio_candidates_callee = callee()->pc()->local_description()->candidates(0);
+  ASSERT_GT(audio_candidates_caller->count(), 0u);
+  ASSERT_GT(audio_candidates_callee->count(), 0u);
+  std::string caller_candidate_post_restart;
+  ASSERT_TRUE(
+      audio_candidates_caller->at(0)->ToString(&caller_candidate_post_restart));
+  std::string callee_candidate_post_restart;
+  ASSERT_TRUE(
+      audio_candidates_callee->at(0)->ToString(&callee_candidate_post_restart));
+  desc = caller()->pc()->local_description()->description();
+  std::string caller_ufrag_post_restart =
+      desc->transport_infos()[0].description.ice_ufrag;
+  desc = callee()->pc()->local_description()->description();
+  std::string callee_ufrag_post_restart =
+      desc->transport_infos()[0].description.ice_ufrag;
+  // Sanity check that an ICE restart was actually negotiated in SDP.
+  ASSERT_NE(caller_candidate_pre_restart, caller_candidate_post_restart);
+  ASSERT_NE(callee_candidate_pre_restart, callee_candidate_post_restart);
+  ASSERT_NE(caller_ufrag_pre_restart, caller_ufrag_post_restart);
+  ASSERT_NE(callee_ufrag_pre_restart, callee_ufrag_post_restart);
+
+  // Ensure that additional frames are received after the ICE restart.
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// Verify that audio/video can be received end-to-end when ICE renomination is
+// enabled.
+TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithIceRenomination) {
+  PeerConnectionInterface::RTCConfiguration config;
+  config.enable_ice_renomination = true;
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
+  ConnectFakeSignaling();
+  // Do normal offer/answer and wait for some frames to be received in each
+  // direction.
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Sanity check that ICE renomination was actually negotiated.
+  const cricket::SessionDescription* desc =
+      caller()->pc()->local_description()->description();
+  for (const cricket::TransportInfo& info : desc->transport_infos()) {
+    ASSERT_NE(info.description.transport_options.end(),
+              std::find(info.description.transport_options.begin(),
+                        info.description.transport_options.end(),
+                        cricket::ICE_RENOMINATION_STR));
+  }
+  desc = callee()->pc()->local_description()->description();
+  for (const cricket::TransportInfo& info : desc->transport_infos()) {
+    ASSERT_NE(info.description.transport_options.end(),
+              std::find(info.description.transport_options.begin(),
+                        info.description.transport_options.end(),
+                        cricket::ICE_RENOMINATION_STR));
+  }
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This test sets up a call between two parties with audio and video. It then
+// renegotiates setting the video m-line to "port 0", then later renegotiates
+// again, enabling video.
+TEST_F(PeerConnectionIntegrationTest,
+       VideoFlowsAfterMediaSectionIsRejectedAndRecycled) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+
+  // Do initial negotiation, only sending media from the caller. Will result in
+  // video and audio recvonly "m=" sections.
+  caller()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  // Negotiate again, disabling the video "m=" section (the callee will set the
+  // port to 0 due to offer_to_receive_video = 0).
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.offer_to_receive_video = 0;
+  callee()->SetOfferAnswerOptions(options);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Sanity check that video "m=" section was actually rejected.
+  const ContentInfo* answer_video_content = cricket::GetFirstVideoContent(
+      callee()->pc()->local_description()->description());
+  ASSERT_NE(nullptr, answer_video_content);
+  ASSERT_TRUE(answer_video_content->rejected);
+
+  // Enable video and do negotiation again, making sure video is received
+  // end-to-end, also adding media stream to callee.
+  options.offer_to_receive_video = 1;
+  callee()->SetOfferAnswerOptions(options);
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  // Verify the caller receives frames from the newly added stream, and the
+  // callee receives additional frames from the re-enabled video m= section.
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This test sets up a Jsep call between two parties with external
+// VideoDecoderFactory.
+// TODO(holmer): Disabled due to sometimes crashing on buildbots.
+// See issue webrtc/2378.
+TEST_F(PeerConnectionIntegrationTest,
+       DISABLED_EndToEndCallWithVideoDecoderFactory) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  EnableVideoDecoderFactory();
+  ConnectFakeSignaling();
+  caller()->AddAudioVideoMediaStream();
+  callee()->AddAudioVideoMediaStream();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This tests that if we negotiate after calling CreateSender but before we
+// have a track, then set a track later, frames from the newly-set track are
+// received end-to-end.
+// TODO(deadbeef): Change this test to use AddTransceiver, once that's
+// implemented.
+TEST_F(PeerConnectionIntegrationTest,
+       MediaFlowsAfterEarlyWarmupWithCreateSender) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  auto caller_audio_sender =
+      caller()->pc()->CreateSender("audio", "caller_stream");
+  auto caller_video_sender =
+      caller()->pc()->CreateSender("video", "caller_stream");
+  auto callee_audio_sender =
+      callee()->pc()->CreateSender("audio", "callee_stream");
+  auto callee_video_sender =
+      callee()->pc()->CreateSender("video", "callee_stream");
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kMaxWaitForActivationMs);
+  // Wait for ICE to complete, without any tracks being set.
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionCompleted,
+                 caller()->ice_connection_state(), kMaxWaitForFramesMs);
+  EXPECT_EQ_WAIT(webrtc::PeerConnectionInterface::kIceConnectionConnected,
+                 callee()->ice_connection_state(), kMaxWaitForFramesMs);
+  // Now set the tracks, and expect frames to immediately start flowing.
+  EXPECT_TRUE(caller_audio_sender->SetTrack(caller()->CreateLocalAudioTrack()));
+  EXPECT_TRUE(caller_video_sender->SetTrack(caller()->CreateLocalVideoTrack()));
+  EXPECT_TRUE(callee_audio_sender->SetTrack(callee()->CreateLocalAudioTrack()));
+  EXPECT_TRUE(callee_video_sender->SetTrack(callee()->CreateLocalVideoTrack()));
+  ExpectNewFramesReceivedWithWait(
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kDefaultExpectedAudioFrameCount, kDefaultExpectedVideoFrameCount,
+      kMaxWaitForFramesMs);
+}
+
+// This test verifies that a remote video track can be added via AddStream,
+// and sent end-to-end. For this particular test, it's simply echoed back
+// from the caller to the callee, rather than being forwarded to a third
+// PeerConnection.
+TEST_F(PeerConnectionIntegrationTest, CanSendRemoteVideoTrack) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  // Just send a video track from the caller.
+  caller()->AddMediaStreamFromTracks(nullptr,
+                                     caller()->CreateLocalVideoTrack());
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kMaxWaitForActivationMs);
+  ASSERT_EQ(1, callee()->remote_streams()->count());
+
+  // Echo the stream back, and do a new offer/anwer (initiated by callee this
+  // time).
+  callee()->pc()->AddStream(callee()->remote_streams()->at(0));
+  callee()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kMaxWaitForActivationMs);
+
+  int expected_caller_received_video_frames = kDefaultExpectedVideoFrameCount;
+  ExpectNewFramesReceivedWithWait(0, expected_caller_received_video_frames, 0,
+                                  0, kMaxWaitForFramesMs);
+}
+
+// Test that we achieve the expected end-to-end connection time, using a
+// fake clock and simulated latency on the media and signaling paths.
+// We use a TURN<->TURN connection because this is usually the quickest to
+// set up initially, especially when we're confident the connection will work
+// and can start sending media before we get a STUN response.
+//
+// With various optimizations enabled, here are the network delays we expect to
+// be on the critical path:
+// 1. 2 signaling trips: Signaling offer and offerer's TURN candidate, then
+//                       signaling answer (with DTLS fingerprint).
+// 2. 9 media hops: Rest of the DTLS handshake. 3 hops in each direction when
+//                  using TURN<->TURN pair, and DTLS exchange is 4 packets,
+//                  the first of which should have arrived before the answer.
+TEST_F(PeerConnectionIntegrationTest, EndToEndConnectionTimeWithTurnTurnPair) {
+  rtc::ScopedFakeClock fake_clock;
+  // Some things use a time of "0" as a special value, so we need to start out
+  // the fake clock at a nonzero time.
+  // TODO(deadbeef): Fix this.
+  fake_clock.AdvanceTime(rtc::TimeDelta::FromSeconds(1));
+
+  static constexpr int media_hop_delay_ms = 50;
+  static constexpr int signaling_trip_delay_ms = 500;
+  // For explanation of these values, see comment above.
+  static constexpr int required_media_hops = 9;
+  static constexpr int required_signaling_trips = 2;
+  // For internal delays (such as posting an event asychronously).
+  static constexpr int allowed_internal_delay_ms = 20;
+  static constexpr int total_connection_time_ms =
+      media_hop_delay_ms * required_media_hops +
+      signaling_trip_delay_ms * required_signaling_trips +
+      allowed_internal_delay_ms;
+
+  static const rtc::SocketAddress turn_server_1_internal_address{"88.88.88.0",
+                                                                 3478};
+  static const rtc::SocketAddress turn_server_1_external_address{"88.88.88.1",
+                                                                 0};
+  static const rtc::SocketAddress turn_server_2_internal_address{"99.99.99.0",
+                                                                 3478};
+  static const rtc::SocketAddress turn_server_2_external_address{"99.99.99.1",
+                                                                 0};
+  cricket::TestTurnServer turn_server_1(network_thread(),
+                                        turn_server_1_internal_address,
+                                        turn_server_1_external_address);
+  cricket::TestTurnServer turn_server_2(network_thread(),
+                                        turn_server_2_internal_address,
+                                        turn_server_2_external_address);
+  // Bypass permission check on received packets so media can be sent before
+  // the candidate is signaled.
+  turn_server_1.set_enable_permission_checks(false);
+  turn_server_2.set_enable_permission_checks(false);
+
+  PeerConnectionInterface::RTCConfiguration client_1_config;
+  webrtc::PeerConnectionInterface::IceServer ice_server_1;
+  ice_server_1.urls.push_back("turn:88.88.88.0:3478");
+  ice_server_1.username = "test";
+  ice_server_1.password = "test";
+  client_1_config.servers.push_back(ice_server_1);
+  client_1_config.type = webrtc::PeerConnectionInterface::kRelay;
+  client_1_config.presume_writable_when_fully_relayed = true;
+
+  PeerConnectionInterface::RTCConfiguration client_2_config;
+  webrtc::PeerConnectionInterface::IceServer ice_server_2;
+  ice_server_2.urls.push_back("turn:99.99.99.0:3478");
+  ice_server_2.username = "test";
+  ice_server_2.password = "test";
+  client_2_config.servers.push_back(ice_server_2);
+  client_2_config.type = webrtc::PeerConnectionInterface::kRelay;
+  client_2_config.presume_writable_when_fully_relayed = true;
+
+  ASSERT_TRUE(
+      CreatePeerConnectionWrappersWithConfig(client_1_config, client_2_config));
+  // Set up the simulated delays.
+  SetSignalingDelayMs(signaling_trip_delay_ms);
+  ConnectFakeSignaling();
+  virtual_socket_server()->set_delay_mean(media_hop_delay_ms);
+  virtual_socket_server()->UpdateDelayDistribution();
+
+  // Set "offer to receive audio/video" without adding any tracks, so we just
+  // set up ICE/DTLS with no media.
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.offer_to_receive_audio = 1;
+  options.offer_to_receive_video = 1;
+  caller()->SetOfferAnswerOptions(options);
+  caller()->CreateAndSetAndSignalOffer();
+  // TODO(deadbeef): kIceConnectionConnected currently means both ICE and DTLS
+  // are connected. This is an important distinction. Once we have separate ICE
+  // and DTLS state, this check needs to use the DTLS state.
+  EXPECT_TRUE_SIMULATED_WAIT(
+      (callee()->ice_connection_state() ==
+           webrtc::PeerConnectionInterface::kIceConnectionConnected ||
+       callee()->ice_connection_state() ==
+           webrtc::PeerConnectionInterface::kIceConnectionCompleted) &&
+          (caller()->ice_connection_state() ==
+               webrtc::PeerConnectionInterface::kIceConnectionConnected ||
+           caller()->ice_connection_state() ==
+               webrtc::PeerConnectionInterface::kIceConnectionCompleted),
+      total_connection_time_ms, fake_clock);
+  // Need to free the clients here since they're using things we created on
+  // the stack.
+  delete SetCallerPcWrapperAndReturnCurrent(nullptr);
+  delete SetCalleePcWrapperAndReturnCurrent(nullptr);
+}
+
+}  // namespace
+
+#endif  // if !defined(THREAD_SANITIZER)