/*
 *  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.
 */

// Integration tests for PeerConnection.
// These tests exercise a full stack over a simulated network.
//
// NOTE: If your test takes a while (guideline: more than 5 seconds),
// do NOT add it here, but instead add it to the file
// slow_peer_connection_integrationtest.cc

#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/candidate.h"
#include "api/crypto/crypto_options.h"
#include "api/data_channel_interface.h"
#include "api/dtmf_sender_interface.h"
#include "api/jsep.h"
#include "api/make_ref_counted.h"
#include "api/media_stream_interface.h"
#include "api/media_types.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtc_event_log/rtc_event.h"
#include "api/rtc_event_log/rtc_event_log.h"
#include "api/rtp_parameters.h"
#include "api/rtp_receiver_interface.h"
#include "api/rtp_sender_interface.h"
#include "api/rtp_transceiver_direction.h"
#include "api/rtp_transceiver_interface.h"
#include "api/scoped_refptr.h"
#include "api/stats/rtc_stats_report.h"
#include "api/stats/rtcstats_objects.h"
#include "api/test/mock_async_dns_resolver.h"
#include "api/test/mock_encoder_selector.h"
#include "api/test/rtc_error_matchers.h"
#include "api/transport/rtp/rtp_source.h"
#include "api/uma_metrics.h"
#include "api/units/data_rate.h"
#include "api/units/time_delta.h"
#include "api/video/video_rotation.h"
#include "api/video_codecs/sdp_video_format.h"
#include "logging/rtc_event_log/fake_rtc_event_log.h"
#include "logging/rtc_event_log/fake_rtc_event_log_factory.h"
#include "media/base/codec.h"
#include "media/base/media_constants.h"
#include "media/base/stream_params.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "p2p/dtls/dtls_transport_internal.h"
#include "p2p/test/test_turn_server.h"
#include "pc/media_session.h"
#include "pc/peer_connection.h"
#include "pc/peer_connection_factory.h"
#include "pc/sctp_transport.h"
#include "pc/session_description.h"
#include "pc/test/fake_periodic_video_source.h"
#include "pc/test/integration_test_helpers.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "rtc_base/event.h"
#include "rtc_base/fake_mdns_responder.h"
#include "rtc_base/firewall_socket_server.h"
#include "rtc_base/logging.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/random.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/ssl_fingerprint.h"
#include "rtc_base/ssl_identity.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/system/plan_b_only.h"
#include "rtc_base/task_queue_for_test.h"
#include "rtc_base/test_certificate_verifier.h"
#include "rtc_base/thread.h"
#include "rtc_base/virtual_socket_server.h"
#include "system_wrappers/include/metrics.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/wait_until.h"

namespace webrtc {

namespace {

using ::testing::AtLeast;
using ::testing::ByMove;
using ::testing::Contains;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Gt;
using ::testing::InSequence;
using ::testing::IsTrue;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SizeIs;
using ::testing::WithParamInterface;

class PeerConnectionIntegrationTest : public PeerConnectionIntegrationBaseTest,
                                      public WithParamInterface<SdpSemantics> {
 protected:
  PeerConnectionIntegrationTest()
      : PeerConnectionIntegrationBaseTest(GetParam()) {}
};

class PeerConnectionIntegrationTestWithFakeClock
    : public PeerConnectionIntegrationTestWithSimulatedTime,
      public WithParamInterface<SdpSemantics> {
 protected:
  PeerConnectionIntegrationTestWithFakeClock()
      : PeerConnectionIntegrationTestWithSimulatedTime(GetParam()) {}
};

class PeerConnectionIntegrationTestPlanB
    : public PeerConnectionIntegrationBaseTest {
 protected:
  PeerConnectionIntegrationTestPlanB()
      : PeerConnectionIntegrationBaseTest(SdpSemantics::kPlanB_DEPRECATED) {}
};

class PeerConnectionIntegrationTestUnifiedPlan
    : public PeerConnectionIntegrationBaseTest {
 protected:
  PeerConnectionIntegrationTestUnifiedPlan()
      : PeerConnectionIntegrationBaseTest(SdpSemantics::kUnifiedPlan) {}
};

// 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_P(PeerConnectionIntegrationTest,
       RtpReceiverObserverOnFirstPacketReceived) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  // Start offer/answer exchange and wait for it to complete.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Should be one receiver each for audio/video.
  EXPECT_EQ(2U, caller()->rtp_receiver_observers().size());
  EXPECT_EQ(2U, callee()->rtp_receiver_observers().size());
  // Wait for all "first packet received" callbacks to be fired.
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return absl::c_all_of(
                        caller()->rtp_receiver_observers(),
                        [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
                          return o->first_packet_received();
                        });
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return absl::c_all_of(
                        callee()->rtp_receiver_observers(),
                        [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
                          return o->first_packet_received();
                        });
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  // If new observers are set after the first packet was already received, the
  // callback should still be invoked.
  caller()->ResetRtpReceiverObservers();
  callee()->ResetRtpReceiverObservers();
  EXPECT_EQ(2U, caller()->rtp_receiver_observers().size());
  EXPECT_EQ(2U, callee()->rtp_receiver_observers().size());
  EXPECT_TRUE(
      absl::c_all_of(caller()->rtp_receiver_observers(),
                     [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
                       return o->first_packet_received();
                     }));
  EXPECT_TRUE(
      absl::c_all_of(callee()->rtp_receiver_observers(),
                     [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
                       return o->first_packet_received();
                     }));
}

TEST_P(PeerConnectionIntegrationTest,
       RtpReceiverObserverOnFirstPacketReceivedAfterInactive) {
  if (sdp_semantics_ != SdpSemantics::kUnifiedPlan) {
    GTEST_SKIP() << "Only supported in unified plan.";
  }
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  // Start offer/answer exchange and wait for it to complete.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Should be one receiver each for audio/video.
  EXPECT_EQ(2U, caller()->rtp_receiver_observers().size());
  // Wait for all "first packet received" callbacks to be fired.
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return absl::c_all_of(
                        caller()->rtp_receiver_observers(),
                        [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
                          return o->first_packet_received();
                        });
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  // Renegotiate, going inactive and back to sendrecv.
  for (auto& transceiver : caller()->pc()->GetTransceivers()) {
    transceiver->SetDirectionWithError(RtpTransceiverDirection::kInactive);
  }
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  callee()->ResetRtpReceiverObservers();
  for (auto& transceiver : caller()->pc()->GetTransceivers()) {
    transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv);
  }
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(
      WaitUntil(
          [&] {
            return absl::c_all_of(
                caller()->rtp_receiver_observers(),
                [](const std::unique_ptr<MockRtpReceiverObserver>& o) {
                  return o->first_packet_received_after_receptive_change();
                });
          },
          IsTrue(), {.timeout = kMaxWaitForFrames}),
      IsRtcOk());
}

TEST_P(PeerConnectionIntegrationTest, RtpSenderObserverOnFirstPacketSent) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  // Start offer/answer exchange and wait for it to complete.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Should be one sender each for audio/video.
  EXPECT_EQ(2U, caller()->rtp_sender_observers().size());
  EXPECT_EQ(2U, callee()->rtp_sender_observers().size());
  // Wait for all "first packet sent" callbacks to be fired.
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return absl::c_all_of(
                        caller()->rtp_sender_observers(),
                        [](const std::unique_ptr<MockRtpSenderObserver>& o) {
                          return o->first_packet_sent();
                        });
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return absl::c_all_of(
                        callee()->rtp_sender_observers(),
                        [](const std::unique_ptr<MockRtpSenderObserver>& o) {
                          return o->first_packet_sent();
                        });
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  // If new observers are set after the first packet was already sent, the
  // callback should still be invoked.
  caller()->ResetRtpSenderObservers();
  callee()->ResetRtpSenderObservers();
  EXPECT_EQ(2U, caller()->rtp_sender_observers().size());
  EXPECT_EQ(2U, callee()->rtp_sender_observers().size());
  EXPECT_TRUE(
      absl::c_all_of(caller()->rtp_sender_observers(),
                     [](const std::unique_ptr<MockRtpSenderObserver>& o) {
                       return o->first_packet_sent();
                     }));
  EXPECT_TRUE(
      absl::c_all_of(callee()->rtp_sender_observers(),
                     [](const std::unique_ptr<MockRtpSenderObserver>& o) {
                       return o->first_packet_sent();
                     }));
}

class DummyDtmfObserver : public DtmfSenderObserverInterface {
 public:
  DummyDtmfObserver() : completed_(false) {}

  // Implements DtmfSenderObserverInterface.
  void OnToneChange(const std::string& tone,
                    const std::string& /* tone_buffer */) 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(PeerConnectionIntegrationWrapper* sender,
                                  PeerConnectionIntegrationWrapper* receiver) {
  // We should be able to get a DTMF sender from the local sender.
  scoped_refptr<DtmfSenderInterface> dtmf_sender =
      sender->pc()->GetSenders().at(0)->GetDtmfSender();
  ASSERT_TRUE(dtmf_sender);
  DummyDtmfObserver observer;
  dtmf_sender->RegisterObserver(&observer);

  // Test the DtmfSender object just created.
  EXPECT_TRUE(dtmf_sender->CanInsertDtmf());
  EXPECT_TRUE(dtmf_sender->InsertDtmf("1a", 100, 50));

  EXPECT_THAT(WaitUntil([&] { return observer.completed(); }, IsTrue()),
              IsRtcOk());
  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_P(PeerConnectionIntegrationTest, DtmfSenderObserver) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Only need audio for DTMF.
  caller()->AddAudioTrack();
  callee()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // DTLS must finish before the DTMF sender can be used reliably.
  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());
  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_P(PeerConnectionIntegrationTest, EndToEndCallWithDtls) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  // Do normal offer/answer and wait for some frames to be received in each
  // direction.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Basic end-to-end test specifying the `enable_encrypted_rtp_header_extensions`
// option to offer encrypted versions of all header extensions alongside the
// unencrypted versions.
TEST_P(PeerConnectionIntegrationTest,
       EndToEndCallWithEncryptedRtpHeaderExtensions) {
  CryptoOptions crypto_options;
  crypto_options.srtp.enable_encrypted_rtp_header_extensions = true;
  PeerConnectionInterface::RTCConfiguration config;
  config.crypto_options = crypto_options;
  // Note: This allows offering >14 RTP header extensions.
  config.offer_extmap_allow_mixed = true;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();

  // Do normal offer/answer and wait for some frames to be received in each
  // direction.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// 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_P(PeerConnectionIntegrationTest,
       Send1280By720ResolutionAndReceive16To9AspectRatio) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  // Add video tracks with 16:9 aspect ratio, size 1280 x 720.
  FakePeriodicVideoSource::Config config;
  config.width = 1280;
  config.height = 720;
  config.timestamp_offset = env_.clock().CurrentTime();
  caller()->AddTrack(caller()->CreateLocalVideoTrackWithConfig(config));
  callee()->AddTrack(callee()->CreateLocalVideoTrackWithConfig(config));

  // Do normal offer/answer and wait for at least one frame to be received in
  // each direction.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil(
                  [&] {
                    return caller()->min_video_frames_received_per_track() >
                               0 &&
                           callee()->min_video_frames_received_per_track() > 0;
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  // 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_P(PeerConnectionIntegrationTest, OneWayMediaCall) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudioAndVideo();
  media_expectations.CallerExpectsNoAudio();
  media_expectations.CallerExpectsNoVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Tests that send only works without the caller having a decoder factory and
// the callee having an encoder factory.
TEST_P(PeerConnectionIntegrationTest, EndToEndCallWithSendOnlyVideo) {
  ASSERT_TRUE(
      CreateOneDirectionalPeerConnectionWrappers(/*caller_to_callee=*/true));
  ConnectFakeSignaling();
  // Add one-directional video, from caller to callee.
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  caller()->AddTrack(caller_track);
  PeerConnectionInterface::RTCOfferAnswerOptions options;
  options.offer_to_receive_video = 0;
  caller()->SetOfferAnswerOptions(options);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);

  // Expect video to be received in one direction.
  MediaExpectations media_expectations;
  media_expectations.CallerExpectsNoVideo();
  media_expectations.CalleeExpectsSomeVideo();

  EXPECT_TRUE(ExpectNewFrames(media_expectations));
}

// Tests that receive only works without the caller having an encoder factory
// and the callee having a decoder factory.
TEST_P(PeerConnectionIntegrationTest, EndToEndCallWithReceiveOnlyVideo) {
  ASSERT_TRUE(
      CreateOneDirectionalPeerConnectionWrappers(/*caller_to_callee=*/false));
  ConnectFakeSignaling();
  // Add one-directional video, from callee to caller.
  scoped_refptr<VideoTrackInterface> callee_track =
      callee()->CreateLocalVideoTrack();
  callee()->AddTrack(callee_track);
  PeerConnectionInterface::RTCOfferAnswerOptions options;
  options.offer_to_receive_video = 1;
  caller()->SetOfferAnswerOptions(options);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(caller()->pc()->GetReceivers().size(), 1u);

  // Expect video to be received in one direction.
  MediaExpectations media_expectations;
  media_expectations.CallerExpectsSomeVideo();
  media_expectations.CalleeExpectsNoVideo();

  EXPECT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest,
       EndToEndCallAddReceiveVideoToSendOnlyCall) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add one-directional video, from caller to callee.
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  caller()->AddTrack(caller_track);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Add receive video.
  scoped_refptr<VideoTrackInterface> callee_track =
      callee()->CreateLocalVideoTrack();
  callee()->AddTrack(callee_track);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Ensure that video frames are received end-to-end.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest,
       EndToEndCallAddSendVideoToReceiveOnlyCall) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add one-directional video, from callee to caller.
  scoped_refptr<VideoTrackInterface> callee_track =
      callee()->CreateLocalVideoTrack();
  callee()->AddTrack(callee_track);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Add send video.
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  caller()->AddTrack(caller_track);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Expect video to be received in one direction.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest,
       EndToEndCallRemoveReceiveVideoFromSendReceiveCall) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add send video, from caller to callee.
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  scoped_refptr<RtpSenderInterface> caller_sender =
      caller()->AddTrack(caller_track);
  // Add receive video, from callee to caller.
  scoped_refptr<VideoTrackInterface> callee_track =
      callee()->CreateLocalVideoTrack();

  scoped_refptr<RtpSenderInterface> callee_sender =
      callee()->AddTrack(callee_track);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Remove receive video (i.e., callee sender track).
  callee()->pc()->RemoveTrackOrError(callee_sender);

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Expect one-directional video.
  MediaExpectations media_expectations;
  media_expectations.CallerExpectsNoVideo();
  media_expectations.CalleeExpectsSomeVideo();

  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest,
       EndToEndCallRemoveSendVideoFromSendReceiveCall) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add send video, from caller to callee.
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  scoped_refptr<RtpSenderInterface> caller_sender =
      caller()->AddTrack(caller_track);
  // Add receive video, from callee to caller.
  scoped_refptr<VideoTrackInterface> callee_track =
      callee()->CreateLocalVideoTrack();

  scoped_refptr<RtpSenderInterface> callee_sender =
      callee()->AddTrack(callee_track);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Remove send video (i.e., caller sender track).
  caller()->pc()->RemoveTrackOrError(caller_sender);

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Expect one-directional video.
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsNoVideo();
  media_expectations.CallerExpectsSomeVideo();

  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// 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_P(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()->AddAudioVideoTracks();
  callee()->AddAudioTrack();
  if (sdp_semantics_ == SdpSemantics::kPlanB_DEPRECATED) {
    PeerConnectionInterface::RTCOfferAnswerOptions options;
    options.offer_to_receive_video = 0;
    callee()->SetOfferAnswerOptions(options);
  } else {
    callee()->SetRemoteOfferHandler([this] {
      callee()->GetFirstTransceiverOfType(MediaType::VIDEO)->StopInternal();
    });
  }
  // Do offer/answer and make sure audio is still received end-to-end.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  {
    MediaExpectations media_expectations;
    media_expectations.ExpectBidirectionalAudio();
    media_expectations.ExpectNoVideo();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
  // 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()->AddVideoTrack();
  if (sdp_semantics_ == SdpSemantics::kPlanB_DEPRECATED) {
    PeerConnectionInterface::RTCOfferAnswerOptions options;
    options.offer_to_receive_video = 1;
    callee()->SetOfferAnswerOptions(options);
  } else {
    callee()->SetRemoteOfferHandler(nullptr);
    caller()->SetRemoteOfferHandler([this] {
      // The caller creates a new transceiver to receive video on when receiving
      // the offer, but by default it is send only.
      auto transceivers = caller()->pc()->GetTransceivers();
      ASSERT_EQ(2U, transceivers.size());
      ASSERT_EQ(MediaType::VIDEO, transceivers[1]->receiver()->media_type());
      transceivers[1]->sender()->SetTrack(
          caller()->CreateLocalVideoTrack().get());
      transceivers[1]->SetDirectionWithError(
          RtpTransceiverDirection::kSendRecv);
    });
  }
  callee()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  {
    // Expect additional audio frames to be received after the upgrade.
    MediaExpectations media_expectations;
    media_expectations.ExpectBidirectionalAudioAndVideo();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
}

// Simpler than the above test; just add an audio track to an established
// video-only connection.
TEST_P(PeerConnectionIntegrationTest, AddAudioToVideoOnlyCall) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Do initial offer/answer with just a video track.
  caller()->AddVideoTrack();
  callee()->AddVideoTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Now add an audio track and do another offer/answer.
  caller()->AddAudioTrack();
  callee()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Ensure both audio and video frames are received end-to-end.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// 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_P(PeerConnectionIntegrationTest, BundlingEnabledWhileIceRestartOccurs) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  // Remove the bundle group from the SDP received by the callee.
  callee()->SetReceivedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        sdp->description()->RemoveGroupByName("BUNDLE");
      });
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  {
    MediaExpectations media_expectations;
    media_expectations.ExpectBidirectionalAudioAndVideo();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
  // Now stop removing the BUNDLE group, and trigger an ICE restart.
  callee()->SetReceivedSdpMunger(nullptr);
  caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions());
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Expect additional frames to be received after the ICE restart.
  {
    MediaExpectations media_expectations;
    media_expectations.ExpectBidirectionalAudioAndVideo();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
}

// 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_P(PeerConnectionIntegrationTest, RotatedVideoWithCVOExtension) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add rotated video tracks.
  caller()->AddTrack(
      caller()->CreateLocalVideoTrackWithRotation(kVideoRotation_90));
  callee()->AddTrack(
      callee()->CreateLocalVideoTrackWithRotation(kVideoRotation_270));

  // Wait for video frames to be received by both sides.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(WaitUntil(
                  [&] {
                    return caller()->min_video_frames_received_per_track() >
                               0 &&
                           callee()->min_video_frames_received_per_track() > 0;
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  // 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(kVideoRotation_270, caller()->rendered_rotation());
  EXPECT_EQ(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_P(PeerConnectionIntegrationTest, RotatedVideoWithoutCVOExtension) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add rotated video tracks.
  caller()->AddTrack(
      caller()->CreateLocalVideoTrackWithRotation(kVideoRotation_90));
  callee()->AddTrack(
      callee()->CreateLocalVideoTrackWithRotation(kVideoRotation_270));

  // Remove the CVO extension from the offered SDP.
  callee()->SetReceivedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        VideoContentDescription* video =
            GetFirstVideoContentDescription(sdp->description());
        video->set_rtp_header_extensions({});
      });
  // Wait for video frames to be received by both sides.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(WaitUntil(
                  [&] {
                    return caller()->min_video_frames_received_per_track() >
                               0 &&
                           callee()->min_video_frames_received_per_track() > 0;
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  // 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(kVideoRotation_0, caller()->rendered_rotation());
  EXPECT_EQ(kVideoRotation_0, callee()->rendered_rotation());
}

// Test that if the answerer rejects the audio m= section, no audio is sent or
// received, but video still can be.
TEST_P(PeerConnectionIntegrationTest, AnswererRejectsAudioSection) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  if (sdp_semantics_ == SdpSemantics::kPlanB_DEPRECATED) {
    // 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);
  } else {
    // Stopping the audio RtpTransceiver will cause the media section to be
    // rejected in the answer.
    callee()->SetRemoteOfferHandler([this] {
      callee()->GetFirstTransceiverOfType(MediaType::AUDIO)->StopInternal();
    });
  }
  callee()->AddTrack(callee()->CreateLocalVideoTrack());
  // Do offer/answer and wait for successful end-to-end video frames.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalVideo();
  media_expectations.ExpectNoAudio();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));

  // 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);
  if (sdp_semantics_ == SdpSemantics::kUnifiedPlan) {
    // The caller's transceiver should have stopped after receiving the answer,
    // and thus no longer listed in transceivers.
    EXPECT_EQ(nullptr, caller()->GetFirstTransceiverOfType(MediaType::AUDIO));
  }
}

// Test that if the answerer rejects the video m= section, no video is sent or
// received, but audio still can be.
TEST_P(PeerConnectionIntegrationTest, AnswererRejectsVideoSection) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  if (sdp_semantics_ == SdpSemantics::kPlanB_DEPRECATED) {
    // 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);
  } else {
    // Stopping the video RtpTransceiver will cause the media section to be
    // rejected in the answer.
    callee()->SetRemoteOfferHandler([this] {
      callee()->GetFirstTransceiverOfType(MediaType::VIDEO)->StopInternal();
    });
  }
  callee()->AddTrack(callee()->CreateLocalAudioTrack());
  // Do offer/answer and wait for successful end-to-end audio frames.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudio();
  media_expectations.ExpectNoVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));

  // 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);
  if (sdp_semantics_ == SdpSemantics::kUnifiedPlan) {
    // The caller's transceiver should have stopped after receiving the answer,
    // and thus is no longer present.
    EXPECT_EQ(nullptr, caller()->GetFirstTransceiverOfType(MediaType::VIDEO));
  }
}

// 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_P(PeerConnectionIntegrationTest, AnswererRejectsAudioAndVideoSections) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  if (sdp_semantics_ == SdpSemantics::kPlanB_DEPRECATED) {
    // 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);
  } else {
    callee()->SetRemoteOfferHandler([this] {
      // Stopping all transceivers will cause all media sections to be rejected.
      for (const auto& transceiver : callee()->pc()->GetTransceivers()) {
        transceiver->StopInternal();
      }
    });
  }
  // Do offer/answer and wait for stable signaling state.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // 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_P(PeerConnectionIntegrationTest, VideoRejectedInSubsequentOffer) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  {
    MediaExpectations media_expectations;
    media_expectations.ExpectBidirectionalAudioAndVideo();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
  // Renegotiate, rejecting the video m= section.
  if (sdp_semantics_ == SdpSemantics::kPlanB_DEPRECATED) {
    caller()->SetGeneratedSdpMunger(
        [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
          for (ContentInfo& content : sdp->description()->contents()) {
            if (IsVideoContent(&content)) {
              content.rejected = true;
            }
          }
        });
  } else {
    caller()->GetFirstTransceiverOfType(MediaType::VIDEO)->StopInternal();
  }
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue(),
                        {.timeout = kMaxWaitForActivation}),
              IsRtcOk());

  // 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);
  // Wait for some additional audio frames to be received.
  {
    MediaExpectations media_expectations;
    media_expectations.ExpectBidirectionalAudio();
    media_expectations.ExpectNoVideo();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
}

// Do one offer/answer with audio, another that disables it (rejecting the m=
// section), and another that re-enables it. Regression test for:
// bugs.webrtc.org/6023
TEST_F(PeerConnectionIntegrationTestPlanB, EnableAudioAfterRejecting) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  // Add audio track, do normal offer/answer.
  scoped_refptr<AudioTrackInterface> track = caller()->CreateLocalAudioTrack();
  scoped_refptr<RtpSenderInterface> sender =
      caller()->pc()->AddTrack(track, {"stream"}).MoveValue();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Remove audio track, and set offer_to_receive_audio to false to cause the
  // m= section to be completely disabled, not just "recvonly".
  caller()->pc()->RemoveTrackOrError(sender);
  PeerConnectionInterface::RTCOfferAnswerOptions options;
  options.offer_to_receive_audio = 0;
  caller()->SetOfferAnswerOptions(options);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Add the audio track again, expecting negotiation to succeed and frames to
  // flow.
  sender = caller()->pc()->AddTrack(track, {"stream"}).MoveValue();
  options.offer_to_receive_audio = 1;
  caller()->SetOfferAnswerOptions(options);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio();
  EXPECT_TRUE(ExpectNewFrames(media_expectations));
}

// 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_P(PeerConnectionIntegrationTest, EndToEndCallWithoutSsrcOrMsidSignaling) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add audio and video, testing that packets can be demuxed on payload type.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  // Remove SSRCs and MSIDs from the received offer SDP.
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Basic end-to-end test, without SSRC signaling. This means that the track
// was created properly and frames are delivered when the MSIDs are communicated
// with a=msid lines and no a=ssrc lines.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       EndToEndCallWithoutSsrcSignaling) {
  const char kStreamId[] = "streamId";
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add just audio tracks.
  caller()->AddTrack(caller()->CreateLocalAudioTrack(), {kStreamId});
  callee()->AddAudioTrack();

  // Remove SSRCs from the received offer SDP.
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndKeepMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudio();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       EndToEndCallAddReceiveVideoToSendOnlyCall) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add one-directional video, from caller to callee.
  scoped_refptr<VideoTrackInterface> track = caller()->CreateLocalVideoTrack();

  RtpTransceiverInit video_transceiver_init;
  video_transceiver_init.stream_ids = {"video1"};
  video_transceiver_init.direction = RtpTransceiverDirection::kSendOnly;
  auto video_sender =
      caller()->pc()->AddTransceiver(track, video_transceiver_init).MoveValue();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Add receive direction.
  video_sender->SetDirectionWithError(RtpTransceiverDirection::kSendRecv);

  scoped_refptr<VideoTrackInterface> callee_track =
      callee()->CreateLocalVideoTrack();

  callee()->AddTrack(callee_track);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Ensure that video frames are received end-to-end.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Tests that video flows between multiple video tracks when SSRCs are not
// signaled. This exercises the MID RTP header extension which is needed to
// demux the incoming video tracks.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       EndToEndCallWithTwoVideoTracksAndNoSignaledSsrc) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  caller()->AddVideoTrack();
  callee()->AddVideoTrack();
  callee()->AddVideoTrack();

  caller()->SetReceivedSdpMunger(&RemoveSsrcsAndKeepMsids);
  callee()->SetReceivedSdpMunger(&RemoveSsrcsAndKeepMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(2u, caller()->pc()->GetReceivers().size());
  ASSERT_EQ(2u, callee()->pc()->GetReceivers().size());

  // Expect video to be received in both directions on both tracks.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalVideo();
  EXPECT_TRUE(ExpectNewFrames(media_expectations));
}

// Used for the test below.
void RemoveBundleGroupSsrcsAndMidExtension(
    std::unique_ptr<SessionDescriptionInterface>& sdp) {
  RemoveSsrcsAndKeepMsids(sdp);
  sdp->description()->RemoveGroupByName("BUNDLE");
  for (ContentInfo& content : sdp->description()->contents()) {
    MediaContentDescription* media = content.media_description();
    RtpHeaderExtensions extensions = media->rtp_header_extensions();
    std::erase_if(extensions, [](const RtpExtension& extension) {
      return extension.uri == RtpExtension::kMidUri;
    });
    media->set_rtp_header_extensions(extensions);
  }
}

// Tests that video flows between multiple video tracks when BUNDLE is not used,
// SSRCs are not signaled and the MID RTP header extension is not used. This
// relies on demuxing by payload type, which normally doesn't work if you have
// multiple media sections using the same payload type, but which should work as
// long as the media sections aren't bundled.
// Regression test for: http://crbug.com/webrtc/12023
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       EndToEndCallWithTwoVideoTracksNoBundleNoSignaledSsrcAndNoMid) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  caller()->AddVideoTrack();
  callee()->AddVideoTrack();
  callee()->AddVideoTrack();
  caller()->SetReceivedSdpMunger(&RemoveBundleGroupSsrcsAndMidExtension);
  callee()->SetReceivedSdpMunger(&RemoveBundleGroupSsrcsAndMidExtension);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(2u, caller()->pc()->GetReceivers().size());
  ASSERT_EQ(2u, callee()->pc()->GetReceivers().size());
  // Make sure we are not bundled.
  ASSERT_NE(caller()->pc()->GetSenders()[0]->dtls_transport(),
            caller()->pc()->GetSenders()[1]->dtls_transport());

  // Expect video to be received in both directions on both tracks.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalVideo();
  EXPECT_TRUE(ExpectNewFrames(media_expectations));
}

// Used for the test below.
void ModifyPayloadTypesAndRemoveMidExtension(
    std::unique_ptr<SessionDescriptionInterface>& sdp) {
  int pt = 96;
  for (ContentInfo& content : sdp->description()->contents()) {
    MediaContentDescription* media = content.media_description();
    RtpHeaderExtensions extensions = media->rtp_header_extensions();
    std::erase_if(extensions, [](const RtpExtension& extension) {
      return extension.uri == RtpExtension::kMidUri;
    });
    media->set_rtp_header_extensions(extensions);
    media->set_codecs({CreateVideoCodec(pt++, "VP8")});
  }
}

// Tests that two video tracks can be demultiplexed by payload type alone, by
// using different payload types for the same codec in different m= sections.
// This practice is discouraged but historically has been supported.
// Regression test for: http://crbug.com/webrtc/12029
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       EndToEndCallWithTwoVideoTracksDemultiplexedByPayloadType) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  caller()->AddVideoTrack();
  callee()->AddVideoTrack();
  callee()->AddVideoTrack();
  caller()->SetGeneratedSdpMunger(&ModifyPayloadTypesAndRemoveMidExtension);
  callee()->SetGeneratedSdpMunger(&ModifyPayloadTypesAndRemoveMidExtension);
  // We can't remove SSRCs from the generated SDP because then no send streams
  // would be created.
  caller()->SetReceivedSdpMunger(&RemoveSsrcsAndKeepMsids);
  callee()->SetReceivedSdpMunger(&RemoveSsrcsAndKeepMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(2u, caller()->pc()->GetReceivers().size());
  ASSERT_EQ(2u, callee()->pc()->GetReceivers().size());
  // Make sure we are bundled.
  ASSERT_EQ(caller()->pc()->GetSenders()[0]->dtls_transport(),
            caller()->pc()->GetSenders()[1]->dtls_transport());

  // Expect video to be received in both directions on both tracks.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalVideo();
  EXPECT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan, NoStreamsMsidLinePresent) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  caller()->AddVideoTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  auto callee_receivers = callee()->pc()->GetReceivers();
  ASSERT_EQ(2u, callee_receivers.size());
  EXPECT_TRUE(callee_receivers[0]->stream_ids().empty());
  EXPECT_TRUE(callee_receivers[1]->stream_ids().empty());
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan, NoStreamsMsidLineMissing) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  caller()->AddVideoTrack();
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  auto callee_receivers = callee()->pc()->GetReceivers();
  ASSERT_EQ(2u, callee_receivers.size());
  ASSERT_EQ(1u, callee_receivers[0]->stream_ids().size());
  ASSERT_EQ(1u, callee_receivers[1]->stream_ids().size());
  EXPECT_EQ(callee_receivers[0]->stream_ids()[0],
            callee_receivers[1]->stream_ids()[0]);
  EXPECT_EQ(callee_receivers[0]->streams()[0],
            callee_receivers[1]->streams()[0]);
}

// Used by the CSRC tests below.
MATCHER_P(IsCsrcWithId, csrc, "") {
  return arg.source_type() == RtpSourceType::CSRC && arg.source_id() == csrc;
}

// Test that CSRCs can be set upon adding tracks and that they are received by
// the other side.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan, EndToEndCallForwardsCsrcs) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  constexpr uint32_t kAudioCsrc = 1234;
  constexpr uint32_t kVideoCsrc = 5678;
  RtpEncodingParameters audio_encoding;
  RtpEncodingParameters video_encoding;
  audio_encoding.csrcs = {kAudioCsrc};
  video_encoding.csrcs = {kVideoCsrc};
  ASSERT_THAT(caller()->pc()->AddTrack(caller()->CreateLocalAudioTrack(), {},
                                       {audio_encoding}),
              IsRtcOk());
  ASSERT_THAT(caller()->pc()->AddTrack(caller()->CreateLocalVideoTrack(), {},
                                       {video_encoding}),
              IsRtcOk());

  caller()->CreateAndSetAndSignalOffer();

  // Wait for some packets to arrive.
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(WaitUntil(
                  [&] {
                    return callee()->audio_frames_received() > 0 &&
                           callee()->min_video_frames_received_per_track() > 0;
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  std::vector<scoped_refptr<RtpReceiverInterface>> audio_receivers =
      callee()->GetReceiversOfType(webrtc::MediaType::AUDIO);
  ASSERT_EQ(audio_receivers.size(), 1u);
  std::vector<scoped_refptr<RtpReceiverInterface>> video_receivers =
      callee()->GetReceiversOfType(webrtc::MediaType::VIDEO);
  ASSERT_EQ(video_receivers.size(), 1u);

  EXPECT_THAT(audio_receivers[0]->GetSources(),
              Contains(IsCsrcWithId(kAudioCsrc)));
  EXPECT_THAT(video_receivers[0]->GetSources(),
              Contains(IsCsrcWithId(kVideoCsrc)));
}

// Test that CSRCs can be updated on active tracks.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan, EndToEndCallCanUpdateCsrcs) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  RtpEncodingParameters audio_encoding;
  RtpEncodingParameters video_encoding;
  audio_encoding.csrcs = {1234};
  video_encoding.csrcs = {5678};

  scoped_refptr<RtpSenderInterface> audio_sender =
      caller()
          ->pc()
          ->AddTrack(caller()->CreateLocalAudioTrack(), {}, {audio_encoding})
          .MoveValue();
  scoped_refptr<RtpSenderInterface> video_sender =
      caller()
          ->pc()
          ->AddTrack(caller()->CreateLocalVideoTrack(), {}, {video_encoding})
          .MoveValue();

  caller()->CreateAndSetAndSignalOffer();

  // Wait for some packets to arrive.
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(WaitUntil(
                  [&] {
                    return callee()->audio_frames_received() > 0 &&
                           callee()->min_video_frames_received_per_track() > 0;
                  },
                  IsTrue(), {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  // Update the CSRCs.
  constexpr uint32_t kUpdatedAudioCsrc = 4321;
  constexpr uint32_t kUpdatedVideoCsrc = 8765;
  RtpParameters audio_parameters = audio_sender->GetParameters();
  RtpParameters video_parameters = video_sender->GetParameters();
  audio_parameters.encodings[0].csrcs = {kUpdatedAudioCsrc};
  video_parameters.encodings[0].csrcs = {kUpdatedVideoCsrc};
  ASSERT_THAT(audio_sender->SetParameters(audio_parameters), IsRtcOk());
  ASSERT_THAT(video_sender->SetParameters(video_parameters), IsRtcOk());

  std::vector<scoped_refptr<RtpReceiverInterface>> audio_receivers =
      callee()->GetReceiversOfType(webrtc::MediaType::AUDIO);
  ASSERT_EQ(audio_receivers.size(), 1u);
  std::vector<scoped_refptr<RtpReceiverInterface>> video_receivers =
      callee()->GetReceiversOfType(webrtc::MediaType::VIDEO);
  ASSERT_EQ(video_receivers.size(), 1u);

  // Ensure that the new CSRCs are used.
  EXPECT_THAT(WaitUntil([&] { return audio_receivers[0]->GetSources(); },
                        Contains(IsCsrcWithId(kUpdatedAudioCsrc)),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return video_receivers[0]->GetSources(); },
                        Contains(IsCsrcWithId(kUpdatedVideoCsrc)),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
}

// Test that if two video tracks are sent (from caller to callee, in this test),
// they're transmitted correctly end-to-end.
TEST_P(PeerConnectionIntegrationTest, EndToEndCallWithTwoVideoTracks) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Add one audio/video stream, and one video-only stream.
  caller()->AddAudioVideoTracks();
  caller()->AddVideoTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(3u, callee()->pc()->GetReceivers().size());

  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

void MakeSpecCompliantMaxBundleOffer(
    std::unique_ptr<SessionDescriptionInterface>& sdp) {
  bool first = true;
  for (ContentInfo& content : sdp->description()->contents()) {
    if (first) {
      first = false;
      continue;
    }
    content.bundle_only = true;
  }
  first = true;
  for (TransportInfo& transport : sdp->description()->transport_infos()) {
    if (first) {
      first = false;
      continue;
    }
    transport.description.ice_ufrag.clear();
    transport.description.ice_pwd.clear();
    transport.description.connection_role = 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_P(PeerConnectionIntegrationTest,
       EndToEndCallWithSpecCompliantMaxBundleOffer) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  // 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_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// 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_P(PeerConnectionIntegrationTest, GetAudioOutputLevelStatsWithOldStatsApi) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Just add an audio track.
  caller()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Get the audio output level stats. Note that the level is not available
  // until an RTCP packet has been received.
  EXPECT_THAT(
      WaitUntil([&] { return callee()->OldGetStats()->AudioOutputLevel(); },
                Gt(0), {.timeout = kMaxWaitForFrames}),
      IsRtcOk());
}

// 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_P(PeerConnectionIntegrationTest, GetAudioInputLevelStatsWithOldStatsApi) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Just add an audio track.
  caller()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Get the audio input level stats. The level should be available very
  // soon after the test starts.
  EXPECT_THAT(
      WaitUntil([&] { return caller()->OldGetStats()->AudioInputLevel(); },
                Gt(0), {.timeout = kMaxWaitForStats}),
      IsRtcOk());
}

// Test that we can get incoming byte counts from both audio and video tracks.
TEST_P(PeerConnectionIntegrationTest, GetBytesReceivedStatsWithOldStatsApi) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  // Do offer/answer, wait for the callee to receive some frames.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));

  // Get a handle to the remote tracks created, so they can be used as GetStats
  // filters.
  for (const auto& receiver : callee()->pc()->GetReceivers()) {
    // We received frames, so we definitely should have nonzero "received bytes"
    // stats at this point.
    EXPECT_GT(
        callee()->OldGetStatsForTrack(receiver->track().get())->BytesReceived(),
        0);
  }
}

// Test that we can get outgoing byte counts from both audio and video tracks.
TEST_P(PeerConnectionIntegrationTest, GetBytesSentStatsWithOldStatsApi) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  auto audio_track = caller()->CreateLocalAudioTrack();
  auto video_track = caller()->CreateLocalVideoTrack();
  caller()->AddTrack(audio_track);
  caller()->AddTrack(video_track);
  // Do offer/answer, wait for the callee to receive some frames.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));

  // The callee received frames, so we definitely should have nonzero "sent
  // bytes" stats at this point.
  EXPECT_GT(caller()->OldGetStatsForTrack(audio_track.get())->BytesSent(), 0);
  EXPECT_GT(caller()->OldGetStatsForTrack(video_track.get())->BytesSent(), 0);
}

// Test that the track ID is associated with all local and remote SSRC stats
// using the old GetStats() and more than 1 audio and more than 1 video track.
// This is a regression test for crbug.com/906988
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       OldGetStatsAssociatesTrackIdForManyMediaSections) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  auto audio_sender_1 = caller()->AddAudioTrack();
  auto video_sender_1 = caller()->AddVideoTrack();
  auto audio_sender_2 = caller()->AddAudioTrack();
  auto video_sender_2 = caller()->AddVideoTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudioAndVideo();
  ASSERT_THAT(
      WaitUntil([&] { return ExpectNewFrames(media_expectations); }, IsTrue()),
      IsRtcOk());

  std::vector<std::string> track_ids = {
      audio_sender_1->track()->id(), video_sender_1->track()->id(),
      audio_sender_2->track()->id(), video_sender_2->track()->id()};

  auto caller_stats = caller()->OldGetStats();
  EXPECT_THAT(caller_stats->TrackIds(), UnorderedElementsAreArray(track_ids));
  auto callee_stats = callee()->OldGetStats();
  EXPECT_THAT(callee_stats->TrackIds(), UnorderedElementsAreArray(track_ids));
}

// Test that the new GetStats() returns stats for all outgoing/incoming streams
// with the correct track identifiers if there are more than one audio and more
// than one video senders/receivers.
TEST_P(PeerConnectionIntegrationTest, NewGetStatsManyAudioAndManyVideoStreams) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  auto audio_sender_1 = caller()->AddAudioTrack();
  auto video_sender_1 = caller()->AddVideoTrack();
  auto audio_sender_2 = caller()->AddAudioTrack();
  auto video_sender_2 = caller()->AddVideoTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudioAndVideo();
  ASSERT_THAT(
      WaitUntil([&] { return ExpectNewFrames(media_expectations); }, IsTrue()),
      IsRtcOk());

  std::vector<std::string> track_ids = {
      audio_sender_1->track()->id(), video_sender_1->track()->id(),
      audio_sender_2->track()->id(), video_sender_2->track()->id()};

  scoped_refptr<const RTCStatsReport> caller_report =
      caller()->NewGetStats(run_loop());
  ASSERT_TRUE(caller_report);
  auto outbound_stream_stats =
      caller_report->GetStatsOfType<RTCOutboundRtpStreamStats>();
  ASSERT_EQ(outbound_stream_stats.size(), 4u);
  std::vector<std::string> outbound_track_ids;
  for (const auto& stat : outbound_stream_stats) {
    ASSERT_TRUE(stat->bytes_sent.has_value());
    EXPECT_LT(0u, *stat->bytes_sent);
    if (*stat->kind == "video") {
      ASSERT_TRUE(stat->key_frames_encoded.has_value());
      EXPECT_GT(*stat->key_frames_encoded, 0u);
      ASSERT_TRUE(stat->frames_encoded.has_value());
      EXPECT_GE(*stat->frames_encoded, *stat->key_frames_encoded);
    }
    ASSERT_TRUE(stat->media_source_id.has_value());
    const RTCMediaSourceStats* media_source =
        static_cast<const RTCMediaSourceStats*>(
            caller_report->Get(*stat->media_source_id));
    ASSERT_TRUE(media_source);
    outbound_track_ids.push_back(*media_source->track_identifier);
  }
  EXPECT_THAT(outbound_track_ids, UnorderedElementsAreArray(track_ids));

  scoped_refptr<const RTCStatsReport> callee_report =
      callee()->NewGetStats(run_loop());
  ASSERT_TRUE(callee_report);
  auto inbound_stream_stats =
      callee_report->GetStatsOfType<RTCInboundRtpStreamStats>();
  ASSERT_EQ(4u, inbound_stream_stats.size());
  std::vector<std::string> inbound_track_ids;
  for (const auto& stat : inbound_stream_stats) {
    ASSERT_TRUE(stat->bytes_received.has_value());
    EXPECT_LT(0u, *stat->bytes_received);
    if (*stat->kind == "video") {
      ASSERT_TRUE(stat->key_frames_decoded.has_value());
      EXPECT_GT(*stat->key_frames_decoded, 0u);
      ASSERT_TRUE(stat->frames_decoded.has_value());
      EXPECT_GE(*stat->frames_decoded, *stat->key_frames_decoded);
    }
    inbound_track_ids.push_back(*stat->track_identifier);
  }
  EXPECT_THAT(inbound_track_ids, UnorderedElementsAreArray(track_ids));
}

// Test that we can get stats (using the new stats implementation) for
// unsignaled streams. Meaning when SSRCs/MSIDs aren't signaled explicitly in
// SDP.
TEST_P(PeerConnectionIntegrationTest,
       GetStatsForUnsignaledStreamWithNewStatsApi) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  // Remove SSRCs and MSIDs from the received offer SDP.
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio(1);
  ASSERT_TRUE(ExpectNewFrames(media_expectations));

  // We received a frame, so we should have nonzero "bytes received" stats for
  // the unsignaled stream, if stats are working for it.
  scoped_refptr<const RTCStatsReport> report =
      callee()->NewGetStats(run_loop());
  ASSERT_NE(nullptr, report);
  auto inbound_stream_stats =
      report->GetStatsOfType<RTCInboundRtpStreamStats>();
  ASSERT_EQ(1U, inbound_stream_stats.size());
  ASSERT_TRUE(inbound_stream_stats[0]->bytes_received.has_value());
  ASSERT_GT(*inbound_stream_stats[0]->bytes_received, 0U);
}

// Same as above but for the legacy stats implementation.
TEST_P(PeerConnectionIntegrationTest,
       GetStatsForUnsignaledStreamWithOldStatsApi) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  // Remove SSRCs and MSIDs from the received offer SDP.
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Note that, since the old stats implementation associates SSRCs with tracks
  // using SDP, when SSRCs aren't signaled in SDP these stats won't have an
  // associated track ID. So we can't use the track "selector" argument.
  //
  // Also, we use "EXPECT_TRUE_WAIT" because the stats collector may decide to
  // return cached stats if not enough time has passed since the last update.
  EXPECT_THAT(
      WaitUntil([&] { return callee()->OldGetStats()->BytesReceived(); },
                Gt(0)),
      IsRtcOk());
}

// Test that we can successfully get the media related stats (audio level
// etc.) for the unsignaled stream.
TEST_P(PeerConnectionIntegrationTest,
       GetMediaStatsForUnsignaledStreamWithNewStatsApi) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  // Remove SSRCs and MSIDs from the received offer SDP.
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio(1);
  media_expectations.CalleeExpectsSomeVideo(1);
  ASSERT_TRUE(ExpectNewFrames(media_expectations));

  scoped_refptr<const RTCStatsReport> report =
      callee()->NewGetStats(run_loop());
  ASSERT_NE(nullptr, report);

  auto inbound_rtps = report->GetStatsOfType<RTCInboundRtpStreamStats>();
  auto index = FindFirstMediaStatsIndexByKind("audio", inbound_rtps);
  ASSERT_GE(index, 0);
  EXPECT_TRUE(inbound_rtps[index]->audio_level.has_value());
}

// Test that DTLS 1.0 is used if both sides only support DTLS 1.0.
TEST_P(PeerConnectionIntegrationTest, EndToEndCallWithDtls10) {
  PeerConnectionFactory::Options dtls_10_options;
  dtls_10_options.ssl_max_version = 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()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Test getting cipher stats and UMA metrics when DTLS 1.0 is negotiated.
TEST_P(PeerConnectionIntegrationTest, Dtls10CipherStatsAndUmaMetrics) {
  PeerConnectionFactory::Options dtls_10_options;
  dtls_10_options.ssl_max_version = SSL_PROTOCOL_DTLS_10;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(dtls_10_options,
                                                      dtls_10_options));
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return SSLStreamAdapter::IsAcceptableCipher(
                        caller()->DtlsCipher(), KT_DEFAULT);
                  },
                  IsTrue()),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return caller()->SrtpCipher(); },
                        Eq(SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite))),
              IsRtcOk());
}

// Test getting cipher stats and UMA metrics when DTLS 1.2 is negotiated.
TEST_P(PeerConnectionIntegrationTest, Dtls12CipherStatsAndUmaMetrics) {
  PeerConnectionFactory::Options dtls_12_options;
  dtls_12_options.ssl_max_version = SSL_PROTOCOL_DTLS_12;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithOptions(dtls_12_options,
                                                      dtls_12_options));
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return SSLStreamAdapter::IsAcceptableCipher(
                        caller()->DtlsCipher(), KT_DEFAULT);
                  },
                  IsTrue()),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return caller()->SrtpCipher(); },
                        Eq(SrtpCryptoSuiteToName(kDefaultSrtpCryptoSuite))),
              IsRtcOk());
}

// Test that DTLS 1.0 can be used if the caller supports DTLS 1.2 and the
// callee only supports 1.0.
TEST_P(PeerConnectionIntegrationTest, CallerDtls12ToCalleeDtls10) {
  PeerConnectionFactory::Options caller_options;
  caller_options.ssl_max_version = SSL_PROTOCOL_DTLS_12;
  PeerConnectionFactory::Options callee_options;
  callee_options.ssl_max_version = 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()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Test that DTLS 1.0 can be used if the caller only supports DTLS 1.0 and the
// callee supports 1.2.
TEST_P(PeerConnectionIntegrationTest, CallerDtls10ToCalleeDtls12) {
  PeerConnectionFactory::Options caller_options;
  caller_options.ssl_max_version = SSL_PROTOCOL_DTLS_10;
  PeerConnectionFactory::Options callee_options;
  callee_options.ssl_max_version = 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()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// The three tests below verify that "enable_aes128_sha1_32_crypto_cipher"
// works as expected; the cipher should only be used if enabled by both sides.
TEST_P(PeerConnectionIntegrationTest,
       Aes128Sha1_32_CipherNotUsedWhenOnlyCallerSupported) {
  RTCConfiguration caller_config;
  CryptoOptions caller_crypto;
  caller_crypto.srtp.enable_aes128_sha1_32_crypto_cipher = true;
  caller_config.crypto_options = caller_crypto;
  RTCConfiguration callee_config;
  CryptoOptions callee_crypto;
  callee_crypto.srtp.enable_aes128_sha1_32_crypto_cipher = false;
  callee_config.crypto_options = callee_crypto;
  int expected_cipher_suite = kSrtpAes128CmSha1_80;
  TestNegotiatedCipherSuite(caller_config, callee_config,
                            expected_cipher_suite);
}

TEST_P(PeerConnectionIntegrationTest,
       Aes128Sha1_32_CipherNotUsedWhenOnlyCalleeSupported) {
  RTCConfiguration caller_config;
  CryptoOptions caller_crypto;
  caller_crypto.srtp.enable_aes128_sha1_32_crypto_cipher = false;
  caller_config.crypto_options = caller_crypto;
  RTCConfiguration callee_config;
  CryptoOptions callee_crypto;
  callee_crypto.srtp.enable_aes128_sha1_32_crypto_cipher = true;
  callee_config.crypto_options = callee_crypto;
  int expected_cipher_suite = kSrtpAes128CmSha1_80;
  TestNegotiatedCipherSuite(caller_config, callee_config,
                            expected_cipher_suite);
}

TEST_P(PeerConnectionIntegrationTest, Aes128Sha1_32_CipherUsedWhenSupported) {
  RTCConfiguration caller_config;
  CryptoOptions caller_crypto;
  caller_crypto.srtp.enable_aes128_sha1_32_crypto_cipher = true;
  caller_config.crypto_options = caller_crypto;
  RTCConfiguration callee_config;
  CryptoOptions callee_crypto;
  callee_crypto.srtp.enable_aes128_sha1_32_crypto_cipher = true;
  callee_config.crypto_options = callee_crypto;
  int expected_cipher_suite = kSrtpAes128CmSha1_32;
  TestNegotiatedCipherSuite(caller_config, callee_config,
                            expected_cipher_suite);
}

// Test that a non-GCM cipher is used if both sides only support non-GCM.
TEST_P(PeerConnectionIntegrationTest, NonGcmCipherUsedWhenGcmNotSupported) {
  bool local_gcm_enabled = false;
  bool remote_gcm_enabled = false;
  bool aes_ctr_enabled = true;
  int expected_cipher_suite = kDefaultSrtpCryptoSuite;
  TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled,
                                    aes_ctr_enabled, expected_cipher_suite);
}

// Test that a GCM cipher is used if both ends support it and non-GCM is
// disabled.
TEST_P(PeerConnectionIntegrationTest, GcmCipherUsedWhenOnlyGcmSupported) {
  bool local_gcm_enabled = true;
  bool remote_gcm_enabled = true;
  bool aes_ctr_enabled = false;
  int expected_cipher_suite = kDefaultSrtpCryptoSuiteGcm;
  TestGcmNegotiationUsesCipherSuite(local_gcm_enabled, remote_gcm_enabled,
                                    aes_ctr_enabled, expected_cipher_suite);
}

// Verify that media can be transmitted end-to-end when GCM crypto suites are
// enabled. Note that the above tests, such as GcmCipherUsedWhenGcmSupported,
// only verify that a GCM cipher is negotiated, and not necessarily that SRTP
// works with it.
TEST_P(PeerConnectionIntegrationTest, EndToEndCallWithGcmCipher) {
  RTCConfiguration gcm_config;
  CryptoOptions gcm_options;
  gcm_options.srtp.enable_gcm_crypto_suites = true;
  gcm_options.srtp.enable_aes128_sha1_80_crypto_cipher = false;
  gcm_config.crypto_options = gcm_options;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(gcm_config, gcm_config));
  ConnectFakeSignaling();
  // Do normal offer/answer and wait for some frames to be received in each
  // direction.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Test that the ICE connection and gathering states eventually reach
// "complete".
TEST_P(PeerConnectionIntegrationTest, IceStatesReachCompletion) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Do normal offer/answer.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return caller()->ice_gathering_state(); },
                        Eq(PeerConnectionInterface::kIceGatheringComplete),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_gathering_state(); },
                        Eq(PeerConnectionInterface::kIceGatheringComplete),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  // 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_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionCompleted)),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected)),
              IsRtcOk());
}

constexpr int kOnlyLocalPorts = PORTALLOCATOR_DISABLE_STUN |
                                PORTALLOCATOR_DISABLE_RELAY |
                                PORTALLOCATOR_DISABLE_TCP;

// Use a mock resolver to resolve the hostname back to the original IP on both
// sides and check that the ICE connection connects.
TEST_P(PeerConnectionIntegrationTest,
       IceStatesReachCompletionWithRemoteHostname) {
  auto caller_resolver_factory =
      std::make_unique<NiceMock<MockAsyncDnsResolverFactory>>();
  auto callee_resolver_factory =
      std::make_unique<NiceMock<MockAsyncDnsResolverFactory>>();
  auto callee_async_resolver =
      std::make_unique<NiceMock<MockAsyncDnsResolver>>();
  auto caller_async_resolver =
      std::make_unique<NiceMock<MockAsyncDnsResolver>>();
  // Keep raw pointers to the mock resolvers, for use after init,
  // where the std::unique_ptr values have been moved away.
  auto* callee_resolver_ptr = callee_async_resolver.get();
  auto* caller_resolver_ptr = caller_async_resolver.get();

  // This also verifies that the injected AsyncResolverFactory is used by
  // P2PTransportChannel.
  EXPECT_CALL(*caller_resolver_factory, Create())
      .WillOnce(Return(ByMove(std::move(caller_async_resolver))));
  PeerConnectionDependencies caller_deps(nullptr);
  caller_deps.async_dns_resolver_factory = std::move(caller_resolver_factory);

  // Create() will be called twice.
  EXPECT_CALL(*callee_resolver_factory, Create())
      .WillOnce(Return(ByMove(std::move(callee_async_resolver))))
      .WillOnce(
          Return(ByMove(std::make_unique<NiceMock<MockAsyncDnsResolver>>())));
  PeerConnectionDependencies callee_deps(nullptr);
  callee_deps.async_dns_resolver_factory = std::move(callee_resolver_factory);

  PeerConnectionInterface::RTCConfiguration config;
  config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle;
  config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
  config.port_allocator_config.flags = kOnlyLocalPorts;

  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndDeps(
      config, std::move(caller_deps), config, std::move(callee_deps)));

  // TEMP NOTE - this is probably bogus since the resolvers have been
  // moved out of their slots prior to these code lines.
  RTC_LOG(LS_ERROR) << "callee async resolver is "
                    << callee_async_resolver.get();
  caller()->SetRemoteAsyncResolver(callee_resolver_ptr);
  callee()->SetRemoteAsyncResolver(caller_resolver_ptr);

  // Enable hostname candidates with mDNS names.
  caller()->SetMdnsResponder(
      std::make_unique<FakeMdnsResponder>(network_thread()));
  callee()->SetMdnsResponder(
      std::make_unique<FakeMdnsResponder>(network_thread()));

  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionCompleted)),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected)),
              IsRtcOk());

  // Part of reporting the stats will occur on the network thread, so flush it
  // before checking NumEvents.
  SendTask(network_thread(), [] {});

  EXPECT_METRIC_EQ(
      1, metrics::NumEvents("WebRTC.PeerConnection.CandidatePairType_UDP",
                            kIceCandidatePairHostNameHostName));
  DestroyPeerConnections();
}

// Test that firewalling the ICE connection causes the clients to identify the
// disconnected state and then removing the firewall causes them to reconnect.

using PeerConnectionIntegrationIceStatesTest =
    PeerConnectionIntegrationIceStatesTestBase<
        PeerConnectionIntegrationBaseTest>;

using PeerConnectionIntegrationIceStatesTestWithFakeClock =
    PeerConnectionIntegrationIceStatesTestBase<
        PeerConnectionIntegrationTestWithSimulatedTime>;

#if !defined(THREAD_SANITIZER)
// This test provokes TSAN errors. bugs.webrtc.org/11282

// Tests that if the connection doesn't get set up properly we eventually reach
// the "failed" iceConnectionState.
TEST_P(PeerConnectionIntegrationIceStatesTestWithFakeClock,
       IceStateSetupFailure) {
  // Block connections to/from the caller and wait for ICE to become
  // disconnected.
  for (const auto& caller_address : CallerAddresses()) {
    firewall()->AddRule(false, FP_ANY, FD_ANY, caller_address);
  }

  PeerConnectionInterface::RTCConfiguration config;
  config.port_allocator_config.flags = port_allocator_flags();
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  SetUpNetworkInterfaces();
  caller()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();

  // According to RFC7675, if there is no response within 30 seconds then the
  // peer should consider the other side to have rejected the connection. This
  // is signaled by the state transitioning to "failed".
  constexpr TimeDelta kConsentTimeout = TimeDelta::Millis(30000);
  ASSERT_THAT(
      WaitUntil([&] { return caller()->standardized_ice_connection_state(); },
                Eq(PeerConnectionInterface::kIceConnectionFailed),
                {.timeout = kConsentTimeout,
                 .polling_interval = TimeDelta::Millis(100),
                 .clock = time_controller_.get()}),
      IsRtcOk());
}

#endif  // !defined(THREAD_SANITIZER)

// Tests that the best connection is set to the appropriate IPv4/IPv6 connection
// and that the statistics in the metric observers are updated correctly.
// TODO(bugs.webrtc.org/12591): Flaky on Windows.
#if defined(WEBRTC_WIN)
#define MAYBE_VerifyBestConnection DISABLED_VerifyBestConnection
#else
#define MAYBE_VerifyBestConnection VerifyBestConnection
#endif
TEST_P(PeerConnectionIntegrationIceStatesTest, MAYBE_VerifyBestConnection) {
  PeerConnectionInterface::RTCConfiguration config;
  config.port_allocator_config.flags = port_allocator_flags();
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  SetUpNetworkInterfaces();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();

  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionCompleted)),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected)),
              IsRtcOk());

  // Part of reporting the stats will occur on the network thread, so flush it
  // before checking NumEvents.
  SendTask(network_thread(), [] {});

  // TODO(bugs.webrtc.org/9456): Fix it.
  const int num_best_ipv4 = metrics::NumEvents(
      "WebRTC.PeerConnection.IPMetrics", kBestConnections_IPv4);
  const int num_best_ipv6 = metrics::NumEvents(
      "WebRTC.PeerConnection.IPMetrics", kBestConnections_IPv6);
  if (TestIPv6()) {
    // When IPv6 is enabled, we should prefer an IPv6 connection over an IPv4
    // connection.
    EXPECT_METRIC_EQ(0, num_best_ipv4);
    EXPECT_METRIC_EQ(1, num_best_ipv6);
  } else {
    EXPECT_METRIC_EQ(1, num_best_ipv4);
    EXPECT_METRIC_EQ(0, num_best_ipv6);
  }

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  EXPECT_METRIC_EQ(
      0, metrics::NumEvents("WebRTC.PeerConnection.CandidatePairType_UDP",
                            kIceCandidatePairHostHost));
#pragma clang diagnostic pop
  EXPECT_METRIC_EQ(
      1, metrics::NumEvents("WebRTC.PeerConnection.CandidatePairType_UDP",
                            kIceCandidatePairHostPublicHostPublic));
}

constexpr uint32_t kFlagsIPv4NoStun = PORTALLOCATOR_DISABLE_TCP |
                                      PORTALLOCATOR_DISABLE_STUN |
                                      PORTALLOCATOR_DISABLE_RELAY;
constexpr uint32_t kFlagsIPv6NoStun =
    PORTALLOCATOR_DISABLE_TCP | PORTALLOCATOR_DISABLE_STUN |
    PORTALLOCATOR_ENABLE_IPV6 | PORTALLOCATOR_DISABLE_RELAY;
constexpr uint32_t kFlagsIPv4Stun =
    PORTALLOCATOR_DISABLE_TCP | PORTALLOCATOR_DISABLE_RELAY;

INSTANTIATE_TEST_SUITE_P(
    PeerConnectionIntegrationTest,
    PeerConnectionIntegrationIceStatesTest,
    Combine(Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan),
            Values(std::make_pair("IPv4 no STUN", kFlagsIPv4NoStun),
                   std::make_pair("IPv6 no STUN", kFlagsIPv6NoStun),
                   std::make_pair("IPv4 with STUN", kFlagsIPv4Stun))));

INSTANTIATE_TEST_SUITE_P(
    PeerConnectionIntegrationTest,
    PeerConnectionIntegrationIceStatesTestWithFakeClock,
    Combine(Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan),
            Values(std::make_pair("IPv4 no STUN", kFlagsIPv4NoStun),
                   std::make_pair("IPv6 no STUN", kFlagsIPv6NoStun),
                   std::make_pair("IPv4 with STUN", kFlagsIPv4Stun))));

// 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_P(PeerConnectionIntegrationTest, MediaContinuesFlowingAfterIceRestart) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Do normal offer/answer and wait for ICE to complete.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionCompleted),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  // 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 IceCandidateCollection* audio_candidates_caller =
      caller()->pc()->local_description()->candidates(0);
  const 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 =
      audio_candidates_caller->at(0)->ToString();
  std::string callee_candidate_pre_restart =
      audio_candidates_callee->at(0)->ToString();
  const 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;

  EXPECT_EQ(caller()->ice_candidate_pair_change_history().size(), 1u);
  // Have the caller initiate an ICE restart.
  caller()->SetOfferAnswerOptions(IceRestartOfferAnswerOptions());
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionCompleted),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  // 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 =
      audio_candidates_caller->at(0)->ToString();
  std::string callee_candidate_post_restart =
      audio_candidates_callee->at(0)->ToString();
  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);
  EXPECT_THAT(
      WaitUntil(
          [&] { return caller()->ice_candidate_pair_change_history().size(); },
          Gt(1U), {.timeout = kMaxWaitForFrames}),
      IsRtcOk());

  // Ensure that additional frames are received after the ICE restart.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Verify that audio/video can be received end-to-end when ICE renomination is
// enabled.
TEST_P(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()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Sanity check that ICE renomination was actually negotiated.
  const SessionDescription* desc =
      caller()->pc()->local_description()->description();
  for (const TransportInfo& info : desc->transport_infos()) {
    ASSERT_THAT(info.description.transport_options, Contains("renomination"));
  }
  desc = callee()->pc()->local_description()->description();
  for (const TransportInfo& info : desc->transport_infos()) {
    ASSERT_THAT(info.description.transport_options, Contains("renomination"));
  }
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// With a max bundle policy and RTCP muxing, adding a new media description to
// the connection should not affect ICE at all because the new media will use
// the existing connection.
TEST_P(PeerConnectionIntegrationTest,
       AddMediaToConnectedBundleDoesNotRestartIce) {
  PeerConnectionInterface::RTCConfiguration config;
  config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle;
  config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(
      config, PeerConnectionInterface::RTCConfiguration()));
  ConnectFakeSignaling();

  caller()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionCompleted)),
              IsRtcOk());

  caller()->clear_ice_connection_state_history();

  caller()->AddVideoTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  EXPECT_EQ(0u, caller()->ice_connection_state_history().size());
}

// 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_P(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()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Negotiate again, disabling the video "m=" section (the callee will set the
  // port to 0 due to offer_to_receive_video = 0).
  if (sdp_semantics_ == SdpSemantics::kPlanB_DEPRECATED) {
    PeerConnectionInterface::RTCOfferAnswerOptions options;
    options.offer_to_receive_video = 0;
    callee()->SetOfferAnswerOptions(options);
  } else {
    callee()->SetRemoteOfferHandler([this] {
      callee()->GetFirstTransceiverOfType(MediaType::VIDEO)->StopInternal();
    });
  }
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Sanity check that video "m=" section was actually rejected.
  const ContentInfo* answer_video_content =
      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.
  if (sdp_semantics_ == SdpSemantics::kPlanB_DEPRECATED) {
    PeerConnectionInterface::RTCOfferAnswerOptions options;
    options.offer_to_receive_video = 1;
    callee()->SetOfferAnswerOptions(options);
  } else {
    // The caller's transceiver is stopped, so we need to add another track.
    auto caller_transceiver =
        caller()->GetFirstTransceiverOfType(MediaType::VIDEO);
    EXPECT_EQ(nullptr, caller_transceiver.get());
    caller()->AddVideoTrack();
  }
  callee()->AddVideoTrack();
  callee()->SetRemoteOfferHandler(nullptr);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Verify the caller receives frames from the newly added stream, and the
  // callee receives additional frames from the re-enabled video m= section.
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio();
  media_expectations.ExpectBidirectionalVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// 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.
TEST_F(PeerConnectionIntegrationTestPlanB,
       MediaFlowsAfterEarlyWarmupWithCreateSender) {
  RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN()
  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");
  RTC_ALLOW_PLAN_B_DEPRECATION_END()
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue(),
                        {.timeout = kMaxWaitForActivation}),
              IsRtcOk());
  // Wait for ICE to complete, without any tracks being set.
  EXPECT_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionCompleted),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  // Now set the tracks, and expect frames to immediately start flowing.
  EXPECT_TRUE(
      caller_audio_sender->SetTrack(caller()->CreateLocalAudioTrack().get()));
  EXPECT_TRUE(
      caller_video_sender->SetTrack(caller()->CreateLocalVideoTrack().get()));
  EXPECT_TRUE(
      callee_audio_sender->SetTrack(callee()->CreateLocalAudioTrack().get()));
  EXPECT_TRUE(
      callee_video_sender->SetTrack(callee()->CreateLocalVideoTrack().get()));
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// This tests that if we negotiate after calling AddTransceiver but before we
// have a track, then set a track later, frames from the newly-set tracks are
// received end-to-end.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       MediaFlowsAfterEarlyWarmupWithAddTransceiver) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  auto audio_result = caller()->pc()->AddTransceiver(MediaType::AUDIO);
  ASSERT_EQ(RTCErrorType::NONE, audio_result.error().type());
  auto caller_audio_sender = audio_result.MoveValue()->sender();
  auto video_result = caller()->pc()->AddTransceiver(MediaType::VIDEO);
  ASSERT_EQ(RTCErrorType::NONE, video_result.error().type());
  auto caller_video_sender = video_result.MoveValue()->sender();
  callee()->SetRemoteOfferHandler([this] {
    ASSERT_EQ(2u, callee()->pc()->GetTransceivers().size());
    callee()->pc()->GetTransceivers()[0]->SetDirectionWithError(
        RtpTransceiverDirection::kSendRecv);
    callee()->pc()->GetTransceivers()[1]->SetDirectionWithError(
        RtpTransceiverDirection::kSendRecv);
  });
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue(),
                        {.timeout = kMaxWaitForActivation}),
              IsRtcOk());
  // Wait for ICE to complete, without any tracks being set.
  EXPECT_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionCompleted),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
  // Now set the tracks, and expect frames to immediately start flowing.
  auto callee_audio_sender = callee()->pc()->GetSenders()[0];
  auto callee_video_sender = callee()->pc()->GetSenders()[1];
  ASSERT_TRUE(
      caller_audio_sender->SetTrack(caller()->CreateLocalAudioTrack().get()));
  ASSERT_TRUE(
      caller_video_sender->SetTrack(caller()->CreateLocalVideoTrack().get()));
  ASSERT_TRUE(
      callee_audio_sender->SetTrack(callee()->CreateLocalAudioTrack().get()));
  ASSERT_TRUE(
      callee_video_sender->SetTrack(callee()->CreateLocalVideoTrack().get()));
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// 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(PeerConnectionIntegrationTestPlanB, CanSendRemoteVideoTrack) {
  RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN()
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  // Just send a video track from the caller.
  caller()->AddVideoTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue(),
                        {.timeout = kMaxWaitForActivation}),
              IsRtcOk());
  ASSERT_EQ(1U, 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));
  RTC_ALLOW_PLAN_B_DEPRECATION_END()
  callee()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue(),
                        {.timeout = kMaxWaitForActivation}),
              IsRtcOk());

  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

#if !defined(THREAD_SANITIZER)
// This test provokes TSAN errors. bugs.webrtc.org/11282

// 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_P(PeerConnectionIntegrationTestWithFakeClock,
       EndToEndConnectionTimeWithTurnTurnPair) {
  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 asynchronously).
  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 SocketAddress turn_server_1_internal_address{"88.88.88.0", 3478};
  static const SocketAddress turn_server_1_external_address{"88.88.88.1", 0};
  static const SocketAddress turn_server_2_internal_address{"99.99.99.0", 3478};
  static const SocketAddress turn_server_2_external_address{"99.99.99.1", 0};
  TestTurnServer* turn_server_1 = CreateTurnServer(
      turn_server_1_internal_address, turn_server_1_external_address);

  TestTurnServer* turn_server_2 = CreateTurnServer(
      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.
  SendTask(network_thread(), [turn_server_1] {
    turn_server_1->set_enable_permission_checks(false);
  });
  SendTask(network_thread(), [turn_server_2] {
    turn_server_2->set_enable_permission_checks(false);
  });

  PeerConnectionInterface::RTCConfiguration client_1_config;
  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 = PeerConnectionInterface::kRelay;
  client_1_config.presume_writable_when_fully_relayed = true;

  PeerConnectionInterface::RTCConfiguration client_2_config;
  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 = 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();
  EXPECT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue(),
                        {.timeout = TimeDelta::Millis(total_connection_time_ms),
                         .clock = time_controller_.get()}),
              IsRtcOk());
  // Closing the PeerConnections destroys the ports before the ScopedFakeClock.
  // If this is not done a DCHECK can be hit in ports.cc, because a large
  // negative number is calculated for the rtt due to the global clock changing.
  ClosePeerConnections();
}

TEST_P(PeerConnectionIntegrationTestWithFakeClock,
       OnIceCandidateFlushesGetStatsCache) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();

  // Call getStats, assert there are no candidates.
  scoped_refptr<const RTCStatsReport> first_report =
      caller()->NewGetStats({.clock = time_controller_.get()});
  ASSERT_TRUE(first_report);
  auto first_candidate_stats =
      first_report->GetStatsOfType<RTCLocalIceCandidateStats>();
  ASSERT_EQ(first_candidate_stats.size(), 0u);

  // Create an offer at the caller and set it as remote description on the
  // callee.
  caller()->CreateAndSetAndSignalOffer();
  // Call getStats again, assert there are candidates now.
  scoped_refptr<const RTCStatsReport> second_report =
      caller()->NewGetStats({.clock = time_controller_.get()});
  ASSERT_TRUE(second_report);
  auto second_candidate_stats =
      second_report->GetStatsOfType<RTCLocalIceCandidateStats>();
  ASSERT_NE(second_candidate_stats.size(), 0u);

  // The fake clock ensures that no time has passed so the cache must have been
  // explicitly invalidated.
  EXPECT_EQ(first_report->timestamp(), second_report->timestamp());
}

TEST_P(PeerConnectionIntegrationTestWithFakeClock,
       AddIceCandidateFlushesGetStatsCache) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignalingForSdpOnly();
  caller()->AddAudioTrack();

  // Start candidate gathering and wait for it to complete. Candidates are not
  // signalled.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return caller()->IceGatheringStateComplete(); },
                        IsTrue(), {.clock = time_controller_.get()}),
              IsRtcOk());

  // Call getStats, assert there are no candidates.
  scoped_refptr<const RTCStatsReport> first_report =
      caller()->NewGetStats({.clock = time_controller_.get()});
  ASSERT_TRUE(first_report);
  auto first_candidate_stats =
      first_report->GetStatsOfType<RTCRemoteIceCandidateStats>();
  ASSERT_EQ(first_candidate_stats.size(), 0u);

  // Add a "fake" candidate.
  std::optional<RTCError> result;
  caller()->pc()->AddIceCandidate(
      absl::WrapUnique(CreateIceCandidate(
          "", 0,
          "candidate:2214029314 1 udp 2122260223 127.0.0.1 49152 typ host",
          nullptr)),
      [&result](RTCError r) { result = r; });
  ASSERT_THAT(WaitUntil([&] { return result.has_value(); }, IsTrue(),
                        {.clock = time_controller_.get()}),
              IsRtcOk());
  ASSERT_TRUE(result.value().ok());

  // Call getStats again, assert there is a remote candidate now.
  scoped_refptr<const RTCStatsReport> second_report =
      caller()->NewGetStats({.clock = time_controller_.get()});
  ASSERT_TRUE(second_report);
  auto second_candidate_stats =
      second_report->GetStatsOfType<RTCRemoteIceCandidateStats>();
  ASSERT_EQ(second_candidate_stats.size(), 1u);

  // The fake clock ensures that no time has passed so the cache must have been
  // explicitly invalidated.
  EXPECT_EQ(first_report->timestamp(), second_report->timestamp());
}

#endif  // !defined(THREAD_SANITIZER)

// Verify that a TurnCustomizer passed in through RTCConfiguration
// is actually used by the underlying TURN candidate pair.
// Note that turnport_unittest.cc contains more detailed, lower-level tests.
TEST_P(PeerConnectionIntegrationTest, TurnCustomizerUsedForTurnConnections) {
  static const SocketAddress turn_server_1_internal_address{"88.88.88.0", 3478};
  static const SocketAddress turn_server_1_external_address{"88.88.88.1", 0};
  static const SocketAddress turn_server_2_internal_address{"99.99.99.0", 3478};
  static const SocketAddress turn_server_2_external_address{"99.99.99.1", 0};
  CreateTurnServer(turn_server_1_internal_address,
                   turn_server_1_external_address);
  CreateTurnServer(turn_server_2_internal_address,
                   turn_server_2_external_address);

  PeerConnectionInterface::RTCConfiguration client_1_config;
  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 = PeerConnectionInterface::kRelay;
  auto* customizer1 = CreateTurnCustomizer();
  client_1_config.turn_customizer = customizer1;

  PeerConnectionInterface::RTCConfiguration client_2_config;
  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 = PeerConnectionInterface::kRelay;
  auto* customizer2 = CreateTurnCustomizer();
  client_2_config.turn_customizer = customizer2;

  ASSERT_TRUE(
      CreatePeerConnectionWrappersWithConfig(client_1_config, client_2_config));
  ConnectFakeSignaling();

  // 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();
  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());

  ExpectTurnCustomizerCountersIncremented(customizer1);
  ExpectTurnCustomizerCountersIncremented(customizer2);
}

// Verifies that you can use TCP instead of UDP to connect to a TURN server and
// send media between the caller and the callee.
TEST_P(PeerConnectionIntegrationTest, TCPUsedForTurnConnections) {
  static const SocketAddress turn_server_internal_address{"88.88.88.0", 3478};
  static const SocketAddress turn_server_external_address{"88.88.88.1", 0};

  // Enable TCP for the fake turn server.
  CreateTurnServer(turn_server_internal_address, turn_server_external_address,
                   PROTO_TCP);

  PeerConnectionInterface::IceServer ice_server;
  ice_server.urls.push_back("turn:88.88.88.0:3478?transport=tcp");
  ice_server.username = "test";
  ice_server.password = "test";

  PeerConnectionInterface::RTCConfiguration client_1_config;
  client_1_config.servers.push_back(ice_server);
  client_1_config.type = PeerConnectionInterface::kRelay;

  PeerConnectionInterface::RTCConfiguration client_2_config;
  client_2_config.servers.push_back(ice_server);
  client_2_config.type = PeerConnectionInterface::kRelay;

  ASSERT_TRUE(
      CreatePeerConnectionWrappersWithConfig(client_1_config, client_2_config));

  // Do normal offer/answer and wait for ICE to complete.
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());

  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  EXPECT_TRUE(ExpectNewFrames(media_expectations));
}

// Verify that a SSLCertificateVerifier passed in through
// PeerConnectionDependencies is actually used by the underlying SSL
// implementation to determine whether a certificate presented by the TURN
// server is accepted by the client. Note that openssladapter_unittest.cc
// contains more detailed, lower-level tests.
TEST_P(PeerConnectionIntegrationTest,
       SSLCertificateVerifierUsedForTurnConnections) {
  static const SocketAddress turn_server_internal_address{"88.88.88.0", 3478};
  static const SocketAddress turn_server_external_address{"88.88.88.1", 0};

  // Enable TCP-TLS for the fake turn server. We need to pass in 88.88.88.0 so
  // that host name verification passes on the fake certificate.
  CreateTurnServer(turn_server_internal_address, turn_server_external_address,
                   PROTO_TLS, "88.88.88.0");

  PeerConnectionInterface::IceServer ice_server;
  ice_server.urls.push_back("turns:88.88.88.0:3478?transport=tcp");
  ice_server.username = "test";
  ice_server.password = "test";

  PeerConnectionInterface::RTCConfiguration client_1_config;
  client_1_config.servers.push_back(ice_server);
  client_1_config.type = PeerConnectionInterface::kRelay;

  PeerConnectionInterface::RTCConfiguration client_2_config;
  client_2_config.servers.push_back(ice_server);
  // Setting the type to kRelay forces the connection to go through a TURN
  // server.
  client_2_config.type = PeerConnectionInterface::kRelay;

  // Get a copy to the pointer so we can verify calls later.
  TestCertificateVerifier* client_1_cert_verifier =
      new TestCertificateVerifier();
  client_1_cert_verifier->verify_certificate_ = true;
  TestCertificateVerifier* client_2_cert_verifier =
      new TestCertificateVerifier();
  client_2_cert_verifier->verify_certificate_ = true;

  // Create the dependencies with the test certificate verifier.
  PeerConnectionDependencies client_1_deps(nullptr);
  client_1_deps.tls_cert_verifier =
      std::unique_ptr<TestCertificateVerifier>(client_1_cert_verifier);
  PeerConnectionDependencies client_2_deps(nullptr);
  client_2_deps.tls_cert_verifier =
      std::unique_ptr<TestCertificateVerifier>(client_2_cert_verifier);

  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndDeps(
      client_1_config, std::move(client_1_deps), client_2_config,
      std::move(client_2_deps)));
  ConnectFakeSignaling();

  // 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();
  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());

  EXPECT_GT(client_1_cert_verifier->call_count_, 0u);
  EXPECT_GT(client_2_cert_verifier->call_count_, 0u);
}

// Test that the injected ICE transport factory is used to create ICE transports
// for WebRTC connections.
TEST_P(PeerConnectionIntegrationTest, IceTransportFactoryUsedForConnections) {
  PeerConnectionInterface::RTCConfiguration default_config;
  PeerConnectionDependencies dependencies(nullptr);
  auto ice_transport_factory = std::make_unique<MockIceTransportFactory>();
  EXPECT_CALL(*ice_transport_factory, RecordIceTransportCreated()).Times(1);
  dependencies.ice_transport_factory = std::move(ice_transport_factory);
  auto wrapper = CreatePeerConnectionWrapper("Caller", nullptr, &default_config,
                                             std::move(dependencies), nullptr,
                                             /*reset_encoder_factory=*/false,
                                             /*reset_decoder_factory=*/false);
  ASSERT_TRUE(wrapper);
  wrapper->CreateDataChannel();
  auto observer = make_ref_counted<MockSetSessionDescriptionObserver>();
  wrapper->pc()->SetLocalDescription(observer.get(),
                                     wrapper->CreateOfferAndWait().release());
}

// Test that audio and video flow end-to-end when codec names don't use the
// expected casing, given that they're supposed to be case insensitive. To test
// this, all but one codec is removed from each media description, and its
// casing is changed.
//
// In the past, this has regressed and caused crashes/black video, due to the
// fact that code at some layers was doing case-insensitive comparisons and
// code at other layers was not.
TEST_P(PeerConnectionIntegrationTest, CodecNamesAreCaseInsensitive) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();

  // Remove all but one audio/video codec (opus and VP8), and change the
  // casing of the caller's generated offer.
  caller()->SetGeneratedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        AudioContentDescription* audio =
            GetFirstAudioContentDescription(sdp->description());
        ASSERT_NE(nullptr, audio);
        auto audio_codecs = audio->codecs();
        std::erase_if(audio_codecs,
                      [](const Codec& codec) { return codec.name != "opus"; });
        ASSERT_EQ(1u, audio_codecs.size());
        audio_codecs[0].name = "OpUs";
        audio->set_codecs(audio_codecs);

        VideoContentDescription* video =
            GetFirstVideoContentDescription(sdp->description());
        ASSERT_NE(nullptr, video);
        auto video_codecs = video->codecs();
        std::erase_if(video_codecs,
                      [](const Codec& codec) { return codec.name != "VP8"; });
        ASSERT_EQ(1u, video_codecs.size());
        video_codecs[0].name = "vP8";
        video->set_codecs(video_codecs);
      });

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Verify frames are still received end-to-end.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest, GetSourcesAudio) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Wait for one audio frame to be received by the callee.
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio(1);
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);
  auto receiver = callee()->pc()->GetReceivers()[0];
  ASSERT_EQ(receiver->media_type(), MediaType::AUDIO);
  auto sources = receiver->GetSources();
  ASSERT_GT(receiver->GetParameters().encodings.size(), 0u);
  EXPECT_EQ(receiver->GetParameters().encodings[0].ssrc,
            sources[0].source_id());
  EXPECT_EQ(RtpSourceType::SSRC, sources[0].source_type());
}

TEST_P(PeerConnectionIntegrationTest, GetSourcesVideo) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Wait for two video frames to be received by the callee.
  // TODO: https://issues.webrtc.org/42220900 - wait for only one frame again
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeVideo(2);
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);
  auto receiver = callee()->pc()->GetReceivers()[0];
  ASSERT_EQ(receiver->media_type(), MediaType::VIDEO);
  auto sources = receiver->GetSources();
  ASSERT_GT(receiver->GetParameters().encodings.size(), 0u);
  ASSERT_GT(sources.size(), 0u);
  EXPECT_EQ(receiver->GetParameters().encodings[0].ssrc,
            sources[0].source_id());
  EXPECT_EQ(RtpSourceType::SSRC, sources[0].source_type());
}

TEST_P(PeerConnectionIntegrationTest, ConcurrentUnsignaledSsrcPackets) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Wait until the connection is established.
  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());

  // Drop all subsequent packets temporarily so we can coordinate the
  // concurrency.
  firewall()->AddRule(false);

  // Block the shared worker thread.
  Event worker_blocked;
  Event worker_continue;
  callee()->pc_internal()->worker_thread()->PostTask(
      [&worker_blocked, &worker_continue] {
        worker_blocked.Set();
        worker_continue.Wait(Event::kForever);
      });
  worker_blocked.Wait(Event::kForever);

  uint32_t initial_sent = virtual_socket_server()->sent_packets();

  // Clear the firewall rules to allow packets to flow again.
  // Since the worker thread is blocked, incoming RTP packets on the network
  // thread will result in tasks posted to the worker thread to handle the
  // unsignaled SSRC packet (specifically to create the default receive stream).
  firewall()->ClearRules();

  // Wait until at least 2 packets are sent through the virtual socket server.
  ASSERT_THAT(WaitUntil(
                  [&] {
                    return virtual_socket_server()->sent_packets() -
                           initial_sent;
                  },
                  ::testing::Ge(2u)),
              IsRtcOk());

  // Let the worker thread resume. It will process the queued tasks.
  // The first task will create the default stream, and subsequent tasks
  // for the same SSRC will discover that the stream already exists or is
  // being created, and safely skip creation.
  worker_continue.Set();

  // Wait deterministically for the packets to be delivered and processed.
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio(1);
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest, UnsignaledSsrcGetSourcesAudio) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);
  auto receiver = callee()->pc()->GetReceivers()[0];
  std::vector<RtpSource> sources;
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return ([&receiver, &sources]() {
                      sources = receiver->GetSources();
                      return !sources.empty();
                    })();
                  },
                  IsTrue()),
              IsRtcOk());
  ASSERT_GT(sources.size(), 0u);
  EXPECT_EQ(RtpSourceType::SSRC, sources[0].source_type());
}

TEST_P(PeerConnectionIntegrationTest, UnsignaledSsrcGetSourcesVideo) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);
  auto receiver = callee()->pc()->GetReceivers()[0];
  std::vector<RtpSource> sources;
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return ([&receiver, &sources]() {
                      sources = receiver->GetSources();
                      return !sources.empty();
                    })();
                  },
                  IsTrue()),
              IsRtcOk());
  ASSERT_GT(sources.size(), 0u);
  EXPECT_EQ(RtpSourceType::SSRC, sources[0].source_type());
}

// Similar to the above test, except instead of waiting until GetSources() is
// non-empty we wait until media is flowing and then assert that GetSources()
// is not empty. This provides test coverage for https://crbug.com/webrtc/14817
// where a race due to the re-creation of the unsignaled ssrc stream would
// clear the GetSources() history. This test not flaking confirms the bug fix.
// TODO(crbug.com/webrtc/441652589): Figure out why this is flaking and
// re-enable the test.
TEST_P(PeerConnectionIntegrationTest,
       DISABLED_UnsignaledSsrcGetSourcesNonEmptyIfMediaFlowing) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Wait for one video frame to be received by the callee.
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeVideo(1);
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);
  auto receiver = callee()->pc()->GetReceivers()[0];
  std::vector<RtpSource> sources = receiver->GetSources();
  // SSRC history must not be cleared since the reception of the first frame.
  ASSERT_GT(sources.size(), 0u);
  EXPECT_EQ(RtpSourceType::SSRC, sources[0].source_type());
}

TEST_P(PeerConnectionIntegrationTest, UnsignaledSsrcGetParametersAudio) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);
  auto receiver = callee()->pc()->GetReceivers()[0];
  RtpParameters parameters;
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return ([&receiver, &parameters]() {
                      parameters = receiver->GetParameters();
                      return !parameters.encodings.empty() &&
                             parameters.encodings[0].ssrc.has_value();
                    })();
                  },
                  IsTrue()),
              IsRtcOk());
  ASSERT_EQ(parameters.encodings.size(), 1u);
  EXPECT_TRUE(parameters.encodings[0].ssrc.has_value());
}

TEST_P(PeerConnectionIntegrationTest, UnsignaledSsrcGetParametersVideo) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  callee()->SetReceivedSdpMunger(RemoveSsrcsAndMsids);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);
  auto receiver = callee()->pc()->GetReceivers()[0];
  RtpParameters parameters;
  EXPECT_THAT(WaitUntil(
                  [&] {
                    return ([&receiver, &parameters]() {
                      parameters = receiver->GetParameters();
                      return !parameters.encodings.empty() &&
                             parameters.encodings[0].ssrc.has_value();
                    })();
                  },
                  IsTrue()),
              IsRtcOk());
  ASSERT_EQ(parameters.encodings.size(), 1u);
  EXPECT_TRUE(parameters.encodings[0].ssrc.has_value());
}

TEST_P(PeerConnectionIntegrationTest,
       GetParametersHasEncodingsBeforeNegotiation) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  auto sender = caller()->AddTrack(caller()->CreateLocalVideoTrack());
  auto parameters = sender->GetParameters();
  EXPECT_EQ(parameters.encodings.size(), 1u);
}

// Test that if a track is removed and added again with a different stream ID,
// the new stream ID is successfully communicated in SDP and media continues to
// flow end-to-end.
// TODO(webrtc.bugs.org/8734): This test does not work for Unified Plan because
// it will not reuse a transceiver that has already been sending. After creating
// a new transceiver it tries to create an offer with two senders of the same
// track ids and it fails.
TEST_F(PeerConnectionIntegrationTestPlanB, RemoveAndAddTrackWithNewStreamId) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  // Add track using stream 1, do offer/answer.
  scoped_refptr<AudioTrackInterface> track = caller()->CreateLocalAudioTrack();
  scoped_refptr<RtpSenderInterface> sender =
      caller()->AddTrack(track, {"stream_1"});
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  {
    MediaExpectations media_expectations;
    media_expectations.CalleeExpectsSomeAudio(1);
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
  // Remove the sender, and create a new one with the new stream.
  caller()->pc()->RemoveTrackOrError(sender);
  sender = caller()->AddTrack(track, {"stream_2"});
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Wait for additional audio frames to be received by the callee.
  {
    MediaExpectations media_expectations;
    media_expectations.CalleeExpectsSomeAudio();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
}

TEST_P(PeerConnectionIntegrationTest, RtcEventLogOutputWriteCalled) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  auto output = std::make_unique<NiceMock<MockRtcEventLogOutput>>();
  ON_CALL(*output, IsActive).WillByDefault(Return(true));
  ON_CALL(*output, Write).WillByDefault(Return(true));
  EXPECT_CALL(*output, Write).Times(AtLeast(1));
  EXPECT_TRUE(caller()->pc()->StartRtcEventLog(std::move(output),
                                               RtcEventLog::kImmediateOutput));

  caller()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
}

TEST_P(PeerConnectionIntegrationTest, RtcEventLogOutputWriteCalledOnStop) {
  // This test uses check point to ensure log is written before peer connection
  // is destroyed.
  // https://google.github.io/googletest/gmock_cook_book.html#UsingCheckPoints
  MockFunction<void()> test_is_complete;
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  auto output = std::make_unique<NiceMock<MockRtcEventLogOutput>>();
  ON_CALL(*output, IsActive).WillByDefault(Return(true));
  ON_CALL(*output, Write).WillByDefault(Return(true));
  InSequence s;
  EXPECT_CALL(*output, Write).Times(AtLeast(1));
  EXPECT_CALL(test_is_complete, Call);

  // Use large output period to prevent this test pass for the wrong reason.
  EXPECT_TRUE(caller()->pc()->StartRtcEventLog(std::move(output),
                                               /*output_period_ms=*/100'000));

  caller()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  caller()->pc()->StopRtcEventLog();
  test_is_complete.Call();
}

TEST_P(PeerConnectionIntegrationTest, RtcEventLogOutputWriteCalledOnClose) {
  // This test uses check point to ensure log is written before peer connection
  // is destroyed.
  // https://google.github.io/googletest/gmock_cook_book.html#UsingCheckPoints
  MockFunction<void()> test_is_complete;
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  auto output = std::make_unique<NiceMock<MockRtcEventLogOutput>>();
  ON_CALL(*output, IsActive).WillByDefault(Return(true));
  ON_CALL(*output, Write).WillByDefault(Return(true));
  InSequence s;
  EXPECT_CALL(*output, Write).Times(AtLeast(1));
  EXPECT_CALL(test_is_complete, Call);

  // Use large output period to prevent this test pass for the wrong reason.
  EXPECT_TRUE(caller()->pc()->StartRtcEventLog(std::move(output),
                                               /*output_period_ms=*/100'000));

  caller()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  caller()->pc()->Close();
  test_is_complete.Call();
}

// Test that if candidates are only signaled by applying full session
// descriptions (instead of using AddIceCandidate), the peers can connect to
// each other and exchange media.
TEST_P(PeerConnectionIntegrationTest, MediaFlowsWhenCandidatesSetOnlyInSdp) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  // Each side will signal the session descriptions but not candidates.
  ConnectFakeSignalingForSdpOnly();

  // Add audio video track and exchange the initial offer/answer with media
  // information only. This will start ICE gathering on each side.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();

  // Wait for all candidates to be gathered on both the caller and callee.
  ASSERT_THAT(WaitUntil([&] { return caller()->ice_gathering_state(); },
                        Eq(PeerConnectionInterface::kIceGatheringComplete)),
              IsRtcOk());
  ASSERT_THAT(WaitUntil([&] { return callee()->ice_gathering_state(); },
                        Eq(PeerConnectionInterface::kIceGatheringComplete)),
              IsRtcOk());

  // The candidates will now be included in the session description, so
  // signaling them will start the ICE connection.
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Ensure that media flows in both directions.
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

#if !defined(THREAD_SANITIZER)
// These tests provokes TSAN errors. See bugs.webrtc.org/11305.

// Test that SetAudioPlayout can be used to disable audio playout from the
// start, then later enable it. This may be useful, for example, if the caller
// needs to play a local ringtone until some event occurs, after which it
// switches to playing the received audio.
TEST_P(PeerConnectionIntegrationTest, DisableAndEnableAudioPlayout) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  // Set up audio-only call where audio playout is disabled on caller's side.
  caller()->pc()->SetAudioPlayout(false);
  caller()->AddAudioTrack();
  callee()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Pump messages for a second.
  run_loop().RunFor(TimeDelta::Seconds(1));

  // Since audio playout is disabled, the caller shouldn't have received
  // anything (at the playout level, at least).
  EXPECT_EQ(0, caller()->audio_frames_received());
  // As a sanity check, make sure the callee (for which playout isn't disabled)
  // did still see frames on its audio level.
  ASSERT_GT(callee()->audio_frames_received(), 0);

  // Enable playout again, and ensure audio starts flowing.
  caller()->pc()->SetAudioPlayout(true);
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudio();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

double GetAudioEnergyStat(PeerConnectionIntegrationWrapper* pc) {
  auto report = pc->NewGetStats();
  auto inbound_rtps = report->GetStatsOfType<RTCInboundRtpStreamStats>();
  if (inbound_rtps.empty()) {
    return 0.0;
  }
  auto* inbound_rtp = inbound_rtps[0];
  if (!inbound_rtp->total_audio_energy.has_value()) {
    return 0.0;
  }
  return *inbound_rtp->total_audio_energy;
}

// Test that if audio playout is disabled via the SetAudioPlayout() method, then
// incoming audio is still processed and statistics are generated.
TEST_P(PeerConnectionIntegrationTest,
       DisableAudioPlayoutStillGeneratesAudioStats) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  // Set up audio-only call where playout is disabled but audio-processing is
  // still active.
  caller()->AddAudioTrack();
  callee()->AddAudioTrack();
  caller()->pc()->SetAudioPlayout(false);

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Wait for the callee to receive audio stats.
  EXPECT_THAT(WaitUntil([&] { return GetAudioEnergyStat(caller()); }, Gt(0),
                        {.timeout = kMaxWaitForFrames}),
              IsRtcOk());
}

#endif  // !defined(THREAD_SANITIZER)

// Test that SetAudioRecording can be used to disable audio recording from the
// start, then later enable it. This may be useful, for example, if the caller
// wants to ensure that no audio resources are active before a certain state
// is reached.
TEST_P(PeerConnectionIntegrationTest, DisableAndEnableAudioRecording) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();

  // Set up audio-only call where audio recording is disabled on caller's side.
  caller()->pc()->SetAudioRecording(false);
  caller()->AddAudioTrack();
  callee()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Pump messages for a second.
  run_loop().RunFor(TimeDelta::Seconds(1));
  // Since caller has disabled audio recording, the callee shouldn't have
  // received anything.
  EXPECT_EQ(0, callee()->audio_frames_received());
  // As a sanity check, make sure the caller did still see frames on its
  // audio level since audio recording is enabled on the calle side.
  ASSERT_GT(caller()->audio_frames_received(), 0);

  // Enable audio recording again, and ensure audio starts flowing.
  caller()->pc()->SetAudioRecording(true);
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudio();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest,
       IceEventsGeneratedAndLoggedInRtcEventLog) {
  ASSERT_TRUE(CreatePeerConnectionWrappersWithFakeRtcEventLog());
  ConnectFakeSignaling();
  PeerConnectionInterface::RTCOfferAnswerOptions options;
  options.offer_to_receive_audio = 1;
  caller()->SetOfferAnswerOptions(options);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());
  ASSERT_NE(nullptr, caller()->event_log_factory());
  ASSERT_NE(nullptr, callee()->event_log_factory());
  FakeRtcEventLog* caller_event_log =
      caller()->event_log_factory()->last_log_created();
  FakeRtcEventLog* callee_event_log =
      callee()->event_log_factory()->last_log_created();
  ASSERT_NE(nullptr, caller_event_log);
  ASSERT_NE(nullptr, callee_event_log);
  int caller_ice_config_count =
      caller_event_log->GetEventCount(RtcEvent::Type::IceCandidatePairConfig);
  int caller_ice_event_count =
      caller_event_log->GetEventCount(RtcEvent::Type::IceCandidatePairEvent);
  int callee_ice_config_count =
      callee_event_log->GetEventCount(RtcEvent::Type::IceCandidatePairConfig);
  int callee_ice_event_count =
      callee_event_log->GetEventCount(RtcEvent::Type::IceCandidatePairEvent);
  EXPECT_LT(0, caller_ice_config_count);
  EXPECT_LT(0, caller_ice_event_count);
  EXPECT_LT(0, callee_ice_config_count);
  EXPECT_LT(0, callee_ice_event_count);
}

TEST_P(PeerConnectionIntegrationTest, RegatherAfterChangingIceTransportType) {
  static const SocketAddress turn_server_internal_address{"88.88.88.0", 3478};
  static const SocketAddress turn_server_external_address{"88.88.88.1", 0};

  CreateTurnServer(turn_server_internal_address, turn_server_external_address);

  PeerConnectionInterface::IceServer ice_server;
  ice_server.urls.push_back("turn:88.88.88.0:3478");
  ice_server.username = "test";
  ice_server.password = "test";

  PeerConnectionInterface::RTCConfiguration caller_config;
  caller_config.servers.push_back(ice_server);
  caller_config.type = PeerConnectionInterface::kRelay;
  caller_config.continual_gathering_policy = PeerConnection::GATHER_CONTINUALLY;
  caller_config.surface_ice_candidates_on_ice_transport_type_changed = true;

  PeerConnectionInterface::RTCConfiguration callee_config;
  callee_config.servers.push_back(ice_server);
  callee_config.type = PeerConnectionInterface::kRelay;
  callee_config.continual_gathering_policy = PeerConnection::GATHER_CONTINUALLY;
  callee_config.surface_ice_candidates_on_ice_transport_type_changed = true;

  ASSERT_TRUE(
      CreatePeerConnectionWrappersWithConfig(caller_config, callee_config));

  // Do normal offer/answer and wait for ICE to complete.
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Since we are doing continual gathering, the ICE transport does not reach
  // kIceGatheringComplete (see
  // P2PTransportChannel::OnCandidatesAllocationDone), and consequently not
  // kIceConnectionComplete.
  EXPECT_THAT(WaitUntil([&] { return caller()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected)),
              IsRtcOk());
  EXPECT_THAT(WaitUntil([&] { return callee()->ice_connection_state(); },
                        Eq(PeerConnectionInterface::kIceConnectionConnected)),
              IsRtcOk());
  // Note that we cannot use the metric
  // `WebRTC.PeerConnection.CandidatePairType_UDP` in this test since this
  // metric is only populated when we reach kIceConnectionComplete in the
  // current implementation.
  EXPECT_TRUE(caller()->last_candidate_gathered().is_relay());
  EXPECT_TRUE(callee()->last_candidate_gathered().is_relay());

  // Loosen the caller's candidate filter.
  caller_config = caller()->pc()->GetConfiguration();
  caller_config.type = PeerConnectionInterface::kAll;
  caller()->pc()->SetConfiguration(caller_config);
  // We should have gathered a new host candidate.
  EXPECT_THAT(
      WaitUntil([&] { return caller()->last_candidate_gathered().is_local(); },
                IsTrue()),
      IsRtcOk());

  // Loosen the callee's candidate filter.
  callee_config = callee()->pc()->GetConfiguration();
  callee_config.type = PeerConnectionInterface::kAll;
  callee()->pc()->SetConfiguration(callee_config);
  EXPECT_THAT(
      WaitUntil([&] { return callee()->last_candidate_gathered().is_local(); },
                IsTrue()),
      IsRtcOk());

  // Create an offer and verify that it does not contain an ICE restart (i.e new
  // ice credentials).
  std::string caller_ufrag_pre_offer = caller()
                                           ->pc()
                                           ->local_description()
                                           ->description()
                                           ->transport_infos()[0]
                                           .description.ice_ufrag;
  caller()->CreateAndSetAndSignalOffer();
  std::string caller_ufrag_post_offer = caller()
                                            ->pc()
                                            ->local_description()
                                            ->description()
                                            ->transport_infos()[0]
                                            .description.ice_ufrag;
  EXPECT_EQ(caller_ufrag_pre_offer, caller_ufrag_post_offer);
}

TEST_P(PeerConnectionIntegrationTest, OnIceCandidateError) {
  static const SocketAddress turn_server_internal_address{"88.88.88.0", 3478};
  static const SocketAddress turn_server_external_address{"88.88.88.1", 0};

  CreateTurnServer(turn_server_internal_address, turn_server_external_address);

  PeerConnectionInterface::IceServer ice_server;
  ice_server.urls.push_back("turn:88.88.88.0:3478");
  ice_server.username = "test";
  ice_server.password = "123";

  PeerConnectionInterface::RTCConfiguration caller_config;
  caller_config.servers.push_back(ice_server);
  caller_config.type = PeerConnectionInterface::kRelay;
  caller_config.continual_gathering_policy = PeerConnection::GATHER_CONTINUALLY;

  PeerConnectionInterface::RTCConfiguration callee_config;
  callee_config.servers.push_back(ice_server);
  callee_config.type = PeerConnectionInterface::kRelay;
  callee_config.continual_gathering_policy = PeerConnection::GATHER_CONTINUALLY;

  ASSERT_TRUE(
      CreatePeerConnectionWrappersWithConfig(caller_config, callee_config));

  // Do normal offer/answer and wait for ICE to complete.
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(
      WaitUntil([&] { return caller()->error_event().error_code; }, Eq(401)),
      IsRtcOk());
  EXPECT_EQ("Unauthorized.", caller()->error_event().error_text);
  EXPECT_EQ("turn:88.88.88.0:3478?transport=udp", caller()->error_event().url);
  EXPECT_NE(caller()->error_event().address, "");
}

TEST_P(PeerConnectionIntegrationTest, OnIceCandidateErrorWithEmptyAddress) {
  PeerConnectionInterface::IceServer ice_server;
  ice_server.urls.push_back("turn:127.0.0.1:3478?transport=tcp");
  ice_server.username = "test";
  ice_server.password = "test";

  PeerConnectionInterface::RTCConfiguration caller_config;
  caller_config.servers.push_back(ice_server);
  caller_config.type = PeerConnectionInterface::kRelay;
  caller_config.continual_gathering_policy = PeerConnection::GATHER_CONTINUALLY;

  PeerConnectionInterface::RTCConfiguration callee_config;
  callee_config.servers.push_back(ice_server);
  callee_config.type = PeerConnectionInterface::kRelay;
  callee_config.continual_gathering_policy = PeerConnection::GATHER_CONTINUALLY;

  ASSERT_TRUE(
      CreatePeerConnectionWrappersWithConfig(caller_config, callee_config));

  // Do normal offer/answer and wait for ICE to complete.
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(
      WaitUntil([&] { return caller()->error_event().error_code; }, Eq(701)),
      IsRtcOk());
  EXPECT_EQ(caller()->error_event().address, "");
}

// TODO(https://crbug.com/webrtc/14947): Investigate why this is flaking and
// find a way to re-enable the test.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       DISABLED_AudioKeepsFlowingAfterImplicitRollback) {
  PeerConnectionInterface::RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.enable_implicit_rollback = true;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  callee()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudio();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
  SetSignalIceCandidates(false);  // Workaround candidate outrace sdp.
  caller()->AddVideoTrack();
  callee()->AddVideoTrack();
  auto observer = make_ref_counted<MockSetSessionDescriptionObserver>();
  callee()->pc()->SetLocalDescription(observer.get(),
                                      callee()->CreateOfferAndWait().release());
  EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()),
              IsRtcOk());
  caller()->CreateAndSetAndSignalOffer();  // Implicit rollback.
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       ImplicitRollbackVisitsStableState) {
  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.enable_implicit_rollback = true;

  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));

  auto sld_observer = make_ref_counted<MockSetSessionDescriptionObserver>();
  callee()->pc()->SetLocalDescription(sld_observer.get(),
                                      callee()->CreateOfferAndWait().release());
  EXPECT_THAT(WaitUntil([&] { return sld_observer->called(); }, IsTrue()),
              IsRtcOk());
  EXPECT_EQ(sld_observer->error(), "");

  auto srd_observer = make_ref_counted<MockSetSessionDescriptionObserver>();
  callee()->pc()->SetRemoteDescription(
      srd_observer.get(), caller()->CreateOfferAndWait().release());
  EXPECT_THAT(WaitUntil([&] { return srd_observer->called(); }, IsTrue()),
              IsRtcOk());
  EXPECT_EQ(srd_observer->error(), "");

  EXPECT_THAT(callee()->peer_connection_signaling_state_history(),
              ElementsAre(PeerConnectionInterface::kHaveLocalOffer,
                          PeerConnectionInterface::kStable,
                          PeerConnectionInterface::kHaveRemoteOffer));
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       H264FmtpSpsPpsIdrInKeyframeParameterUsage) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  callee()->AddVideoTrack();
  auto munger = [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
    VideoContentDescription* video =
        GetFirstVideoContentDescription(sdp->description());
    auto codecs = video->codecs();
    for (auto&& codec : codecs) {
      if (codec.name == "H264") {
        std::string value;
        // The parameter is not supposed to be present in SDP by default.
        EXPECT_FALSE(codec.GetParam(kH264FmtpSpsPpsIdrInKeyframe, &value));
        codec.SetParam(std::string(kH264FmtpSpsPpsIdrInKeyframe),
                       std::string(""));
      }
    }
    video->set_codecs(codecs);
  };
  // Munge local offer for SLD.
  caller()->SetGeneratedSdpMunger(munger);
  // Munge remote answer for SRD.
  caller()->SetReceivedSdpMunger(munger);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Observe that after munging the parameter is present in generated SDP.
  caller()->SetGeneratedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        VideoContentDescription* video =
            GetFirstVideoContentDescription(sdp->description());
        for (auto&& codec : video->codecs()) {
          if (codec.name == "H264") {
            std::string value;
            EXPECT_TRUE(codec.GetParam(kH264FmtpSpsPpsIdrInKeyframe, &value));
          }
        }
      });
  caller()->CreateOfferAndWait();
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       RenegotiateManyAudioTransceivers) {
  OverrideLoggingLevelForTest(LS_WARNING);

  PeerConnectionInterface::RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  caller()->pc()->AddTransceiver(MediaType::AUDIO);

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  int current_size = caller()->pc()->GetTransceivers().size();
  // Add more tracks until we get close to having issues.
  // Issues have been seen at:
  // - 32 tracks on android_arm64_rel and android_arm_dbg bots
  // - 16 tracks on android_arm_dbg (flaky)
  while (current_size < 8) {
    // Double the number of tracks
    for (int i = 0; i < current_size; i++) {
      caller()->pc()->AddTransceiver(MediaType::AUDIO);
    }
    current_size = caller()->pc()->GetTransceivers().size();
    RTC_LOG(LS_INFO) << "Renegotiating with " << current_size << " tracks";
    auto start_time_ms = env_.clock().TimeInMilliseconds();
    caller()->CreateAndSetAndSignalOffer();
    // We want to stop when the time exceeds one second.
    ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
                IsRtcOk());
    auto elapsed_time_ms = env_.clock().TimeInMilliseconds() - start_time_ms;
    RTC_LOG(LS_INFO) << "Renegotiating took " << elapsed_time_ms << " ms";
    ASSERT_GT(1000, elapsed_time_ms)
        << "Audio transceivers: Negotiation took too long after "
        << current_size << " tracks added";
  }
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       DISABLED_RenegotiateManyVideoTransceivers) {
  OverrideLoggingLevelForTest(LS_WARNING);

  PeerConnectionInterface::RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  caller()->pc()->AddTransceiver(MediaType::VIDEO);

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  int current_size = caller()->pc()->GetTransceivers().size();
  // Add more tracks until we get close to having issues.
  // Issues have been seen at:
  // - 96 on a Linux workstation
  // - 64 at win_x86_more_configs and win_x64_msvc_dbg
  // - 32 on android_arm64_rel and linux_dbg bots
  // - 16 on Android 64 (Nexus 5x)
  while (current_size < 8) {
    // Double the number of tracks
    for (int i = 0; i < current_size; i++) {
      caller()->pc()->AddTransceiver(MediaType::VIDEO);
    }
    current_size = caller()->pc()->GetTransceivers().size();
    RTC_LOG(LS_INFO) << "Renegotiating with " << current_size << " tracks";
    auto start_time_ms = env_.clock().TimeInMilliseconds();
    caller()->CreateAndSetAndSignalOffer();
    // We want to stop when the time exceeds one second.
    ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
                IsRtcOk());
    auto elapsed_time_ms = env_.clock().TimeInMilliseconds() - start_time_ms;
    RTC_LOG(LS_INFO) << "Renegotiating took " << elapsed_time_ms << " ms";
    ASSERT_GT(1000, elapsed_time_ms)
        << "Video transceivers: Negotiation took too long after "
        << current_size << " tracks added";
  }
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       RenegotiateManyVideoTransceiversAndWatchAudioDelay) {
  OverrideLoggingLevelForTest(LS_WARNING);

  PeerConnectionInterface::RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  callee()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  // Wait until we can see the audio flowing.
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));

  // Get the baseline numbers for audio_packets and audio_delay
  // in both directions.
  caller()->StartWatchingDelayStats();
  callee()->StartWatchingDelayStats();

  int current_size = caller()->pc()->GetTransceivers().size();
  // Add more tracks until we get close to having issues.
  // Making this number very large makes the test very slow.
  while (current_size < 16) {
    // Double the number of tracks
    for (int i = 0; i < current_size; i++) {
      caller()->pc()->AddTransceiver(MediaType::VIDEO);
    }
    current_size = caller()->pc()->GetTransceivers().size();
    RTC_LOG(LS_INFO) << "Renegotiating with " << current_size << " tracks";
    auto start_time_ms = env_.clock().TimeInMilliseconds();
    caller()->CreateAndSetAndSignalOffer();
    // We want to stop when the time exceeds one second.
    ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
                IsRtcOk());
    auto elapsed_time_ms = env_.clock().TimeInMilliseconds() - start_time_ms;
    RTC_LOG(LS_INFO) << "Renegotiating took " << elapsed_time_ms << " ms";
    // This is a guard against the test using excessive amounts of time.
    ASSERT_GT(5000, elapsed_time_ms)
        << "Video transceivers: Negotiation took too long after "
        << current_size << " tracks added";
    caller()->UpdateDelayStats("caller reception", current_size);
    callee()->UpdateDelayStats("callee reception", current_size);
  }
  EXPECT_TRUE(caller()->AudioDelayStatsPercentageChecked());
  EXPECT_TRUE(callee()->AudioDelayStatsPercentageChecked());
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       GetParametersHasEncodingsBeforeNegotiation) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  auto result = caller()->pc()->AddTransceiver(MediaType::VIDEO);
  auto transceiver = result.MoveValue();
  auto parameters = transceiver->sender()->GetParameters();
  EXPECT_EQ(parameters.encodings.size(), 1u);
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       GetParametersHasInitEncodingsBeforeNegotiation) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  RtpTransceiverInit init;
  init.send_encodings.push_back({});
  init.send_encodings[0].max_bitrate_bps = 12345;
  auto result = caller()->pc()->AddTransceiver(MediaType::VIDEO, init);
  auto transceiver = result.MoveValue();
  auto parameters = transceiver->sender()->GetParameters();
  ASSERT_EQ(parameters.encodings.size(), 1u);
  EXPECT_EQ(parameters.encodings[0].max_bitrate_bps, 12345);
}

INSTANTIATE_TEST_SUITE_P(PeerConnectionIntegrationTest,
                         PeerConnectionIntegrationTest,
                         Values(SdpSemantics::kPlanB_DEPRECATED,
                                SdpSemantics::kUnifiedPlan));

INSTANTIATE_TEST_SUITE_P(PeerConnectionIntegrationTest,
                         PeerConnectionIntegrationTestWithFakeClock,
                         Values(SdpSemantics::kPlanB_DEPRECATED,
                                SdpSemantics::kUnifiedPlan));

// Tests that verify interoperability between Plan B and Unified Plan
// PeerConnections.
class PeerConnectionIntegrationInteropTest
    : public PeerConnectionIntegrationBaseTest,
      public WithParamInterface<std::tuple<SdpSemantics, SdpSemantics>> {
 protected:
  // Setting the SdpSemantics for the base test to kDefault does not matter
  // because we specify not to use the test semantics when creating
  // PeerConnectionIntegrationWrappers.
  PeerConnectionIntegrationInteropTest()
      : PeerConnectionIntegrationBaseTest(SdpSemantics::kPlanB_DEPRECATED),
        caller_semantics_(std::get<0>(GetParam())),
        callee_semantics_(std::get<1>(GetParam())) {}

  bool CreatePeerConnectionWrappersWithSemantics() {
    return CreatePeerConnectionWrappersWithSdpSemantics(caller_semantics_,
                                                        callee_semantics_);
  }

  const SdpSemantics caller_semantics_;
  const SdpSemantics callee_semantics_;
};

TEST_P(PeerConnectionIntegrationInteropTest, NoMediaLocalToNoMediaRemote) {
  ASSERT_TRUE(CreatePeerConnectionWrappersWithSemantics());
  ConnectFakeSignaling();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
}

TEST_P(PeerConnectionIntegrationInteropTest, OneAudioLocalToNoMediaRemote) {
  ASSERT_TRUE(CreatePeerConnectionWrappersWithSemantics());
  ConnectFakeSignaling();
  auto audio_sender = caller()->AddAudioTrack();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Verify that one audio receiver has been created on the remote and that it
  // has the same track ID as the sending track.
  auto receivers = callee()->pc()->GetReceivers();
  ASSERT_EQ(1u, receivers.size());
  EXPECT_EQ(MediaType::AUDIO, receivers[0]->media_type());
  EXPECT_EQ(receivers[0]->track()->id(), audio_sender->track()->id());

  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationInteropTest, OneAudioOneVideoToNoMediaRemote) {
  ASSERT_TRUE(CreatePeerConnectionWrappersWithSemantics());
  ConnectFakeSignaling();
  auto video_sender = caller()->AddVideoTrack();
  auto audio_sender = caller()->AddAudioTrack();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Verify that one audio and one video receiver have been created on the
  // remote and that they have the same track IDs as the sending tracks.
  auto audio_receivers = callee()->GetReceiversOfType(MediaType::AUDIO);
  ASSERT_EQ(1u, audio_receivers.size());
  EXPECT_EQ(audio_receivers[0]->track()->id(), audio_sender->track()->id());
  auto video_receivers = callee()->GetReceiversOfType(MediaType::VIDEO);
  ASSERT_EQ(1u, video_receivers.size());
  EXPECT_EQ(video_receivers[0]->track()->id(), video_sender->track()->id());

  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationInteropTest,
       OneAudioOneVideoLocalToOneAudioOneVideoRemote) {
  ASSERT_TRUE(CreatePeerConnectionWrappersWithSemantics());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  MediaExpectations media_expectations;
  media_expectations.ExpectBidirectionalAudioAndVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationInteropTest,
       ReverseRolesOneAudioLocalToOneVideoRemote) {
  ASSERT_TRUE(CreatePeerConnectionWrappersWithSemantics());
  ConnectFakeSignaling();
  caller()->AddAudioTrack();
  callee()->AddVideoTrack();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Verify that only the audio track has been negotiated.
  EXPECT_EQ(0u, caller()->GetReceiversOfType(MediaType::VIDEO).size());
  // Might also check that the callee's NegotiationNeeded flag is set.

  // Reverse roles.
  callee()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  MediaExpectations media_expectations;
  media_expectations.CallerExpectsSomeVideo();
  media_expectations.CalleeExpectsSomeAudio();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest, NewTracksDoNotCauseNewCandidates) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(WaitUntil([&] { return DtlsConnected(); }, IsTrue()), IsRtcOk());
  caller()->ExpectCandidates(0);
  callee()->ExpectCandidates(0);
  caller()->AddAudioTrack();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
}

TEST_P(PeerConnectionIntegrationTest, MediaCallWithoutMediaEngineFails) {
  ASSERT_TRUE(CreatePeerConnectionWrappersWithoutMediaEngine());
  // AddTrack should fail.
  EXPECT_FALSE(
      caller()->pc()->AddTrack(caller()->CreateLocalAudioTrack(), {}).ok());
}

INSTANTIATE_TEST_SUITE_P(
    PeerConnectionIntegrationTest,
    PeerConnectionIntegrationInteropTest,
    Values(std::make_tuple(SdpSemantics::kPlanB_DEPRECATED,
                           SdpSemantics::kUnifiedPlan),
           std::make_tuple(SdpSemantics::kUnifiedPlan,
                           SdpSemantics::kPlanB_DEPRECATED)));

// Test that if the Unified Plan side offers two video tracks then the Plan B
// side will only see the first one and ignore the second.
TEST_F(PeerConnectionIntegrationTestPlanB, TwoVideoUnifiedPlanToNoMediaPlanB) {
  ASSERT_TRUE(CreatePeerConnectionWrappersWithSdpSemantics(
      SdpSemantics::kUnifiedPlan, SdpSemantics::kPlanB_DEPRECATED));
  ConnectFakeSignaling();
  auto first_sender = caller()->AddVideoTrack();
  caller()->AddVideoTrack();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  // Verify that there is only one receiver and it corresponds to the first
  // added track.
  auto receivers = callee()->pc()->GetReceivers();
  ASSERT_EQ(1u, receivers.size());
  EXPECT_TRUE(receivers[0]->track()->enabled());
  EXPECT_EQ(first_sender->track()->id(), receivers[0]->track()->id());

  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeVideo();
  ASSERT_TRUE(ExpectNewFrames(media_expectations));
}

// Test that if the initial offer tagged BUNDLE section is rejected due to its
// associated RtpTransceiver being stopped and another transceiver is added,
// then renegotiation causes the callee to receive the new video track without
// error.
// This is a regression test for bugs.webrtc.org/9954
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       ReOfferWithStoppedBundleTaggedTransceiver) {
  RTCConfiguration config;
  config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  auto audio_transceiver_or_error =
      caller()->pc()->AddTransceiver(caller()->CreateLocalAudioTrack());
  ASSERT_TRUE(audio_transceiver_or_error.ok());
  auto audio_transceiver = audio_transceiver_or_error.MoveValue();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  {
    MediaExpectations media_expectations;
    media_expectations.CalleeExpectsSomeAudio();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }

  audio_transceiver->StopInternal();
  caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack());

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  {
    MediaExpectations media_expectations;
    media_expectations.CalleeExpectsSomeVideo();
    ASSERT_TRUE(ExpectNewFrames(media_expectations));
  }
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       StopTransceiverRemovesDtlsTransports) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  auto audio_transceiver_or_error =
      caller()->pc()->AddTransceiver(caller()->CreateLocalAudioTrack());
  ASSERT_TRUE(audio_transceiver_or_error.ok());
  auto audio_transceiver = audio_transceiver_or_error.MoveValue();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());

  audio_transceiver->StopStandard();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(0U, caller()->pc()->GetTransceivers().size());
  EXPECT_EQ(PeerConnectionInterface::kIceGatheringNew,
            caller()->pc()->ice_gathering_state());
  EXPECT_THAT(caller()->ice_gathering_state_history(),
              ElementsAre(PeerConnectionInterface::kIceGatheringGathering,
                          PeerConnectionInterface::kIceGatheringComplete,
                          PeerConnectionInterface::kIceGatheringNew));
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       StopTransceiverStopsAndRemovesTransceivers) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  auto audio_transceiver_or_error =
      caller()->pc()->AddTransceiver(caller()->CreateLocalAudioTrack());
  ASSERT_TRUE(audio_transceiver_or_error.ok());
  auto caller_transceiver = audio_transceiver_or_error.MoveValue();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  caller_transceiver->StopStandard();

  auto callee_transceiver = callee()->pc()->GetTransceivers()[0];
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_EQ(0U, caller()->pc()->GetTransceivers().size());
  EXPECT_EQ(0U, callee()->pc()->GetTransceivers().size());
  EXPECT_EQ(0U, caller()->pc()->GetSenders().size());
  EXPECT_EQ(0U, callee()->pc()->GetSenders().size());
  EXPECT_EQ(0U, caller()->pc()->GetReceivers().size());
  EXPECT_EQ(0U, callee()->pc()->GetReceivers().size());
  EXPECT_TRUE(caller_transceiver->stopped());
  EXPECT_TRUE(callee_transceiver->stopped());
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       StopTransceiverEndsIncomingAudioTrack) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  auto audio_transceiver_or_error =
      caller()->pc()->AddTransceiver(caller()->CreateLocalAudioTrack());
  ASSERT_TRUE(audio_transceiver_or_error.ok());
  auto audio_transceiver = audio_transceiver_or_error.MoveValue();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  auto caller_track = audio_transceiver->receiver()->track();
  auto callee_track = callee()->pc()->GetReceivers()[0]->track();
  audio_transceiver->StopStandard();
  EXPECT_EQ(MediaStreamTrackInterface::TrackState::kEnded,
            caller_track->state());
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_EQ(MediaStreamTrackInterface::TrackState::kEnded,
            callee_track->state());
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       StopTransceiverEndsIncomingVideoTrack) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  auto audio_transceiver_or_error =
      caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack());
  ASSERT_TRUE(audio_transceiver_or_error.ok());
  auto audio_transceiver = audio_transceiver_or_error.MoveValue();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  auto caller_track = audio_transceiver->receiver()->track();
  auto callee_track = callee()->pc()->GetReceivers()[0]->track();
  audio_transceiver->StopStandard();
  EXPECT_EQ(MediaStreamTrackInterface::TrackState::kEnded,
            caller_track->state());
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_EQ(MediaStreamTrackInterface::TrackState::kEnded,
            callee_track->state());
}

TEST_P(PeerConnectionIntegrationTest, EndToEndRtpSenderVideoEncoderSelector) {
  ASSERT_TRUE(
      CreateOneDirectionalPeerConnectionWrappers(/*caller_to_callee=*/true));
  ConnectFakeSignaling();
  // Add one-directional video, from caller to callee.
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  auto sender = caller()->AddTrack(caller_track);
  PeerConnectionInterface::RTCOfferAnswerOptions options;
  options.offer_to_receive_video = 0;
  caller()->SetOfferAnswerOptions(options);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);

  scoped_refptr<MockEncoderSelector> encoder_selector =
      make_ref_counted<MockEncoderSelector>();
  EXPECT_CALL(*encoder_selector, OnCurrentEncoder);

  sender->SetEncoderSelector(encoder_selector);

  // Expect video to be received in one direction.
  MediaExpectations media_expectations;
  media_expectations.CallerExpectsNoVideo();
  media_expectations.CalleeExpectsSomeVideo();

  EXPECT_TRUE(ExpectNewFrames(media_expectations));
}

TEST_P(PeerConnectionIntegrationTest,
       EndToEndRtpSenderVideoEncoderSelectorSwitchCodec) {
  ASSERT_TRUE(
      CreateOneDirectionalPeerConnectionWrappers(/*caller_to_callee=*/true));
  ConnectFakeSignaling();
  // Add one-directional video, from caller to callee.
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  auto sender = caller()->AddTrack(caller_track);
  PeerConnectionInterface::RTCOfferAnswerOptions options;
  options.offer_to_receive_video = 0;
  caller()->SetOfferAnswerOptions(options);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);

  scoped_refptr<MockEncoderSelector> encoder_selector =
      make_ref_counted<MockEncoderSelector>();
  std::optional<SdpVideoFormat> next_format;
  EXPECT_CALL(*encoder_selector, OnCurrentEncoder)
      .WillOnce([&](const SdpVideoFormat& format) {
        EXPECT_EQ(format.name, "VP8");
        next_format = SdpVideoFormat::VP9Profile0();
      })
      .WillOnce(
          [&](const SdpVideoFormat& format) { EXPECT_EQ(format.name, "VP9"); });
  EXPECT_CALL(*encoder_selector, OnAvailableBitrate)
      .WillRepeatedly([&](const DataRate& rate) { return next_format; });

  sender->SetEncoderSelector(encoder_selector);

  // Expect video to be received in one direction.
  MediaExpectations media_expectations;
  media_expectations.CallerExpectsNoVideo();
  media_expectations.CalleeExpectsSomeVideo();

  EXPECT_TRUE(ExpectNewFrames(media_expectations));

  caller()->pc()->Close();
}

int NacksReceivedCount(PeerConnectionIntegrationWrapper& pc) {
  scoped_refptr<const RTCStatsReport> report = pc.NewGetStats();
  auto sender_stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
  if (sender_stats.size() != 1) {
    ADD_FAILURE();
    return 0;
  }
  if (!sender_stats[0]->nack_count.has_value()) {
    return 0;
  }
  return *sender_stats[0]->nack_count;
}

int NacksSentCount(PeerConnectionIntegrationWrapper& pc) {
  scoped_refptr<const RTCStatsReport> report = pc.NewGetStats();
  auto receiver_stats = report->GetStatsOfType<RTCInboundRtpStreamStats>();
  if (receiver_stats.size() != 1) {
    ADD_FAILURE();
    return 0;
  }
  if (!receiver_stats[0]->nack_count.has_value()) {
    return 0;
  }
  return *receiver_stats[0]->nack_count;
}

// Test disabled because it is flaky.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       DISABLED_AudioPacketLossCausesNack) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  auto audio_transceiver_or_error =
      caller()->pc()->AddTransceiver(caller()->CreateLocalAudioTrack());
  ASSERT_TRUE(audio_transceiver_or_error.ok());
  auto send_transceiver = audio_transceiver_or_error.MoveValue();
  // Munge the SDP to include NACK and RRTR on Opus, and remove all other
  // codecs.
  caller()->SetGeneratedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        for (ContentInfo& content : sdp->description()->contents()) {
          MediaContentDescription* media = content.media_description();
          std::vector<Codec> codecs = media->codecs();
          std::vector<Codec> codecs_out;
          for (Codec codec : codecs) {
            if (codec.name == "opus") {
              codec.AddFeedbackParam(
                  FeedbackParam(kRtcpFbParamNack, kParamValueEmpty));
              codec.AddFeedbackParam(
                  FeedbackParam(kRtcpFbParamRrtr, kParamValueEmpty));
              codecs_out.push_back(codec);
            }
          }
          EXPECT_FALSE(codecs_out.empty());
          media->set_codecs(codecs_out);
        }
      });

  caller()->CreateAndSetAndSignalOffer();
  // Check for failure in helpers
  ASSERT_FALSE(HasFailure());
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeAudio(1);
  ExpectNewFrames(media_expectations);
  ASSERT_FALSE(HasFailure());

  virtual_socket_server()->set_drop_probability(0.2);

  // Wait until callee has sent at least one NACK.
  // Note that due to stats caching, this might only be visible 50 ms
  // after the nack was in fact sent.
  EXPECT_THAT(WaitUntil([&] { return NacksSentCount(*callee()); }, Gt(0)),
              IsRtcOk());
  ASSERT_FALSE(HasFailure());

  virtual_socket_server()->set_drop_probability(0.0);
  // Wait until caller has received at least one NACK
  EXPECT_THAT(WaitUntil([&] { return NacksReceivedCount(*caller()); }, Gt(0)),
              IsRtcOk());
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan, VideoPacketLossCausesNack) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  auto video_transceiver_or_error =
      caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack());
  ASSERT_TRUE(video_transceiver_or_error.ok());
  auto send_transceiver = video_transceiver_or_error.MoveValue();
  // Munge the SDP to include NACK and RRTR on VP8, and remove all other
  // codecs.
  caller()->SetGeneratedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        for (ContentInfo& content : sdp->description()->contents()) {
          MediaContentDescription* media = content.media_description();
          std::vector<Codec> codecs = media->codecs();
          std::vector<Codec> codecs_out;
          for (const Codec& codec : codecs) {
            if (codec.name == "VP8") {
              ASSERT_TRUE(codec.HasFeedbackParam(
                  FeedbackParam(kRtcpFbParamNack, kParamValueEmpty)));
              codecs_out.push_back(codec);
            }
          }
          EXPECT_FALSE(codecs_out.empty());
          media->set_codecs(codecs_out);
        }
      });

  caller()->CreateAndSetAndSignalOffer();
  // Check for failure in helpers
  ASSERT_FALSE(HasFailure());
  MediaExpectations media_expectations;
  media_expectations.CalleeExpectsSomeVideo(1);
  ExpectNewFrames(media_expectations);
  ASSERT_FALSE(HasFailure());

  virtual_socket_server()->set_drop_probability(0.2);

  // Wait until callee has sent at least one NACK.
  // Note that due to stats caching, this might only be visible 50 ms
  // after the nack was in fact sent.
  EXPECT_THAT(WaitUntil([&] { return NacksSentCount(*callee()); }, Gt(0)),
              IsRtcOk());
  ASSERT_FALSE(HasFailure());

  // Wait until caller has received at least one NACK
  EXPECT_THAT(WaitUntil([&] { return NacksReceivedCount(*caller()); }, Gt(0)),
              IsRtcOk());
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan, PrAnswerStateTransitions) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  caller()->pc()->AddTransceiver(caller()->CreateLocalAudioTrack());
  caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack());

  callee()->SetGeneratedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        SetSdpType(sdp, SdpType::kPrAnswer);
      });
  std::unique_ptr<SessionDescriptionInterface> answer;
  caller()->SetReceivedSdpMunger(
      [&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        answer = sdp->Clone();
      });
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_FALSE(HasFailure());
  EXPECT_EQ(caller()->pc()->signaling_state(),
            PeerConnectionInterface::kHaveRemotePrAnswer);
  EXPECT_EQ(callee()->pc()->signaling_state(),
            PeerConnectionInterface::kHaveLocalPrAnswer);

  // // Apply the pranswer as a definitive one.
  SetSdpType(answer, SdpType::kAnswer);
  EXPECT_TRUE(caller()->SetRemoteDescription(std::move(answer)));
  EXPECT_EQ(caller()->pc()->signaling_state(),
            PeerConnectionInterface::kStable);
}

// Let caller get a prAnswer followed by answer.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       PrAnswerStateTransitionsAsymmetric) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  caller()->pc()->AddTransceiver(caller()->CreateLocalAudioTrack());
  caller()->pc()->AddTransceiver(caller()->CreateLocalVideoTrack());

  std::unique_ptr<SessionDescriptionInterface> answer;
  caller()->SetReceivedSdpMunger(
      [&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        answer = sdp->Clone();
        SetSdpType(sdp, SdpType::kPrAnswer);
      });
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_FALSE(HasFailure());
  EXPECT_EQ(caller()->pc()->signaling_state(),
            PeerConnectionInterface::kHaveRemotePrAnswer);
  EXPECT_EQ(callee()->pc()->signaling_state(),
            PeerConnectionInterface::kStable);

  // // Apply the pranswer as a definitive one.
  EXPECT_TRUE(caller()->SetRemoteDescription(std::move(answer)));
  EXPECT_EQ(caller()->pc()->signaling_state(),
            PeerConnectionInterface::kStable);
}

int ReassignPayloadIds(std::unique_ptr<SessionDescriptionInterface>& sdp) {
  int swaps = 0;
  for (ContentInfo& content : sdp->description()->contents()) {
    if (!content.media_description()) {
      continue;
    }
    std::vector<Codec> codecs = content.media_description()->codecs();
    int left = 0;
    int right = codecs.size() - 1;
    while (left < right) {
      if (!codecs[left].IsMediaCodec()) {
        left++;
        continue;
      }
      if (!codecs[right].IsMediaCodec()) {
        right--;
        continue;
      }
      auto tmp = codecs[left].id;
      codecs[left].id = codecs[right].id;
      codecs[right].id = tmp;
      left++;
      right--;
      swaps++;
    }
    content.media_description()->set_codecs(codecs);
  }
  return swaps;
}

int SetNewSsrcs(std::unique_ptr<SessionDescriptionInterface>& sdp) {
  int assignments = 0;
  std::unordered_set<uint32_t> already_used_ssrcs;
  for (ContentInfo& content : sdp->description()->contents()) {
    if (!content.media_description()) {
      continue;
    }
    for (const auto& stream : content.media_description()->streams()) {
      for (const auto& ssrc : stream.ssrcs) {
        already_used_ssrcs.insert(ssrc);
      }
    }
  }

  Random random(/* random_seed= */ 77);
  auto ssrc_generator = [&]() -> uint32_t {
    do {
      auto ssrc = random.Rand(1u, 0xFFFFFFF0u);
      if (already_used_ssrcs.find(ssrc) == already_used_ssrcs.end()) {
        already_used_ssrcs.insert(ssrc);
        return ssrc;
      }
    } while (true);
  };

  for (ContentInfo& content : sdp->description()->contents()) {
    if (!content.media_description()) {
      continue;
    }
    for (auto& stream : content.media_description()->mutable_streams()) {
      // Only reassign primary ssrc for now...
      // but we should maybe also reassign ssrcs for ssrc groups?.
      if (stream.ssrcs.size() == 1) {
        assignments++;
        stream.ssrcs[0] = ssrc_generator();
      }
    }
  }
  return assignments;
}

void SetNewFingerprint(std::unique_ptr<SessionDescriptionInterface>& sdp) {
  auto identity = SSLIdentity::Create("NewIdentity", KT_DEFAULT);
  auto new_fingerprint = SSLFingerprint::CreateUnique("sha-256", *identity);
  for (auto& transport_info : sdp->description()->transport_infos()) {
    transport_info.description.identity_fingerprint =
        absl::WrapUnique(new SSLFingerprint(*new_fingerprint));
  }
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       PrAnswerStateTransitionsAsymmetricScrambled) {
  RTCConfiguration config;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();
  RtpEncodingParameters init_send_encodings;
  init_send_encodings.active = false;
  caller()->pc()->AddTrack(caller()->CreateLocalAudioTrack(), {"name"},
                           {init_send_encodings});
  caller()->pc()->AddTrack(caller()->CreateLocalVideoTrack(), {"name"},
                           {init_send_encodings});
  callee()->pc()->AddTrack(callee()->CreateLocalAudioTrack(), {"name"},
                           {init_send_encodings});
  callee()->pc()->AddTrack(callee()->CreateLocalVideoTrack(), {"name"},
                           {init_send_encodings});

  std::unique_ptr<SessionDescriptionInterface> answer;
  caller()->SetReceivedSdpMunger(
      [&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        answer = sdp->Clone();
        SetSdpType(sdp, SdpType::kPrAnswer);
      });
  caller()->CreateAndSetAndSignalOffer();

  ASSERT_FALSE(HasFailure());
  ASSERT_EQ(caller()->pc()->signaling_state(),
            PeerConnectionInterface::kHaveRemotePrAnswer);
  ASSERT_EQ(callee()->pc()->signaling_state(),
            PeerConnectionInterface::kStable);

  // Now scramble the answer sdp so that it (really!) different from the first
  // prAnswer.
  // Note: this is maybe {possibly...probably?} a spec violation.
  ASSERT_GT(SetNewSsrcs(answer), 0);
  ASSERT_GT(ReassignPayloadIds(answer), 0);
  SetNewFingerprint(answer);

  // Apply the modified answer as a definitive one.
  EXPECT_TRUE(caller()->SetRemoteDescription(std::move(answer)));
  EXPECT_EQ(caller()->pc()->signaling_state(),
            PeerConnectionInterface::kStable);
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       OnlyOnePairWantsCorruptionScorePlumbingShouldFailToGetIt) {
  // In order for corruption score to be logged, encryption of RTP header
  // extensions must be allowed.
  CryptoOptions crypto_options;
  crypto_options.srtp.enable_encrypted_rtp_header_extensions = true;
  PeerConnectionInterface::RTCConfiguration config;
  config.crypto_options = crypto_options;
  config.offer_extmap_allow_mixed = true;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();

  // Do normal offer/answer and wait for some frames to be received in each
  // direction.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();

  // Negotiate the corruption detection header extension in SDP only in one
  // direction.
  caller()->NegotiateCorruptionDetectionHeader();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  std::vector<RtpHeaderExtensionCapability> negotiated_extensions =
      caller()->pc()->GetTransceivers()[0]->GetNegotiatedHeaderExtensions();
  // Even if `caller` wants to collect corruption score, since `callee` does not
  // want it, we should not send/receive any corruption score data.
  ASSERT_THAT(negotiated_extensions,
              Not(Contains(Field("uri", &RtpHeaderExtensionCapability::uri,
                                 RtpExtension::kCorruptionDetectionUri))));
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       BothPairsWantCorruptionScorePlumbing) {
  // In order for corruption score to be logged, encryption of RTP header
  // extensions must be allowed.
  CryptoOptions crypto_options;
  crypto_options.srtp.enable_encrypted_rtp_header_extensions = true;
  PeerConnectionInterface::RTCConfiguration config;
  config.crypto_options = crypto_options;
  config.offer_extmap_allow_mixed = true;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();

  // Do normal offer/answer and wait for some frames to be received in each
  // direction, and `corruption_score` to be aggregated.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();

  // Negotiate the corruption detection header extension in SDP.
  // If caller adds corruption detection header extension to its SDP offer, it
  // will receive it from the callee.
  caller()->NegotiateCorruptionDetectionHeader();
  callee()->NegotiateCorruptionDetectionHeader();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(WaitUntil([&] { return caller()->GetCorruptionScoreCount(); },
                        Gt(0), {.timeout = kMaxWaitForStats}),
              IsRtcOk());
  ASSERT_THAT(WaitUntil([&] { return callee()->GetCorruptionScoreCount(); },
                        Gt(0), {.timeout = kMaxWaitForStats}),
              IsRtcOk());

  for (const auto& pair : {caller(), callee()}) {
    scoped_refptr<const RTCStatsReport> report = pair->NewGetStats();
    ASSERT_TRUE(report);
    auto inbound_stream_stats =
        report->GetStatsOfType<RTCInboundRtpStreamStats>();
    for (const auto& stat : inbound_stream_stats) {
      if (*stat->kind == "video") {
        EXPECT_TRUE(stat->total_corruption_probability.has_value());
        EXPECT_TRUE(stat->total_squared_corruption_probability.has_value());

        double average_corruption_score =
            (*stat->total_corruption_probability) /
            static_cast<int32_t>(*stat->corruption_measurements);
        EXPECT_GE(average_corruption_score, 0.0);
        EXPECT_LE(average_corruption_score, 1.0);
      }
    }
  }
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       CorruptionScorePlumbingShouldNotWorkWhenEncryptionIsOff) {
  // In order for corruption score to be logged, encryption of RTP header
  // extensions must be allowed.
  CryptoOptions crypto_options;
  crypto_options.srtp.enable_encrypted_rtp_header_extensions = false;
  PeerConnectionInterface::RTCConfiguration config;
  config.crypto_options = crypto_options;
  config.offer_extmap_allow_mixed = true;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();

  // Negotiate the corruption detection header extension in SDP.
  // If caller adds corruption detection header extension to its SDP offer, it
  // will receive it from the callee.
  caller()->NegotiateCorruptionDetectionHeader();
  callee()->NegotiateCorruptionDetectionHeader();

  // Do normal offer/answer and wait for some frames to be received in each
  // direction, and `corruption_score` to be aggregated.
  caller()->AddAudioVideoTracks();
  callee()->AddAudioVideoTracks();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(WaitUntil([&] { return caller()->GetCorruptionScoreCount(); },
                        Eq(0), {.timeout = kMaxWaitForStats}),
              IsRtcOk());
  ASSERT_THAT(WaitUntil([&] { return callee()->GetCorruptionScoreCount(); },
                        Eq(0), {.timeout = kMaxWaitForStats}),
              IsRtcOk());

  for (const auto& pair : {caller(), callee()}) {
    scoped_refptr<const RTCStatsReport> report = pair->NewGetStats();
    ASSERT_TRUE(report);
    auto inbound_stream_stats =
        report->GetStatsOfType<RTCInboundRtpStreamStats>();
    for (const auto& stat : inbound_stream_stats) {
      if (*stat->kind == "video") {
        EXPECT_FALSE(stat->total_corruption_probability.has_value());
        EXPECT_FALSE(stat->total_squared_corruption_probability.has_value());
      }
    }
  }
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       AbsCaptureTimestampShouldBeMeteredCorrectly) {
  metrics::Reset();
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  auto transceiver = caller()->pc()->GetTransceivers()[0];
  auto extensions = transceiver->GetHeaderExtensionsToNegotiate();
  bool found = false;
  for (auto& extension : extensions) {
    if (extension.uri == RtpExtension::kAbsoluteCaptureTimeUri) {
      extension.direction = RtpTransceiverDirection::kSendRecv;
      found = true;
      break;
    }
  }
  ASSERT_TRUE(found);
  ASSERT_TRUE(transceiver->SetHeaderExtensionsToNegotiate(extensions).ok());

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  const auto& negotiated_header_extensions = caller()
                                                 ->pc()
                                                 ->remote_description()
                                                 ->description()
                                                 ->contents()[0]
                                                 .media_description()
                                                 ->rtp_header_extensions();
  ASSERT_TRUE(RtpExtension::FindHeaderExtensionByUri(
      negotiated_header_extensions, RtpExtension::kAbsoluteCaptureTimeUri,
      RtpExtension::kDiscardEncryptedExtension));
  ASSERT_THAT(
      WaitUntil(
          [&] {
            return metrics::NumSamples("WebRTC.Call.AbsCapture.ExtensionWait");
          },
          Gt(0)),
      IsRtcOk());
  // Observed deltas are more than 100 msec. Use 1 minute as tolerance;
  // this is a check against wrong timebase.
  EXPECT_LT(metrics::MinSample("WebRTC.Call.AbsCapture.Delta"), 60'000'000);
  ASSERT_THAT(
      WaitUntil(
          [&] {
            return metrics::NumSamples("WebRTC.Call.AbsCapture.OffsetWait");
          },
          Gt(0)),
      IsRtcOk());
  // On a point-to-point call, we expect the offset to be zero.
  EXPECT_LT(metrics::MinSample("WebRTC.Call.AbsCapture.Offset"), 2);
}

// Test that when SDP is munged to use a PT for a different codec,
// the old codec is added to a subsequent offer with a different PT
// Regression test for https://issues.chromium.org/395077824
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       MungeOfferCodecAndReOfferWorks) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  bool munged = false;
  auto munger = [&munged](std::unique_ptr<SessionDescriptionInterface>& sdp) {
    auto video = GetFirstVideoContentDescription(sdp->description());
    auto codecs = video->codecs();
    std::optional<Codec> replacement_codec;
    for (auto&& codec : codecs) {
      if (codec.name == "AV1") {
        replacement_codec = codec;
        break;
      }
    }
    if (replacement_codec) {
      for (auto&& codec : codecs) {
        if (codec.name == "VP9") {
          RTC_LOG(LS_INFO) << "Remapping VP9 codec " << codec << " to AV1";
          codec.name = replacement_codec->name;
          codec.params = replacement_codec->params;
          munged = true;
          break;
        }
      }
      video->set_codecs(codecs);
    } else {
      RTC_LOG(LS_INFO) << "Skipping munge, no AV1 codec found";
    }
  };
  caller()->SetGeneratedSdpMunger(munger);
  caller()->CreateAndSetAndSignalOffer();
  // Skip rest of test if munge didn't work.
  if (!munged) {
    GTEST_SKIP() << "SDP munging did not replace codec, skipping.";
  }
  ASSERT_TRUE(WaitUntil([&] { return SignalingStateStable(); }));
  caller()->SetGeneratedSdpMunger(nullptr);
  std::unique_ptr<SessionDescriptionInterface> offer =
      caller()->CreateOfferAndWait();
  ASSERT_NE(nullptr, offer);
  // The offer should be acceptable.
  EXPECT_TRUE(caller()->SetLocalDescriptionAndSendSdpMessage(std::move(offer)));
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       MungeOfferCodecAndReOfferWorksWithSetCodecPreferencesIsAFootgun) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  bool has_munged = false;
  auto munger =
      [&has_munged](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        auto video = GetFirstVideoContentDescription(sdp->description());
        auto codecs = video->codecs();
        std::optional<Codec> replacement_codec;
        for (auto&& codec : codecs) {
          if (codec.name == "VP9") {
            replacement_codec = codec;
            break;
          }
        }
        if (replacement_codec) {
          for (auto&& codec : codecs) {
            if (codec.name == "VP8") {
              RTC_LOG(LS_INFO) << "Remapping VP8 codec " << codec << " to VP9";
              codec.name = replacement_codec->name;
              codec.params = replacement_codec->params;
              has_munged = true;
              break;
            }
          }
          video->set_codecs(codecs);
        } else {
          RTC_LOG(LS_INFO) << "Skipping munge, no VP9 codec found ";
        }
      };
  caller()->SetGeneratedSdpMunger(munger);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  caller()->SetGeneratedSdpMunger(nullptr);

  if (!has_munged) {
    GTEST_SKIP() << "SDP munging did not replace codec, skipping.";
  }

  // Note currently negotiated codecs and count VP8 and VP9.
  auto codecs_after_munge = caller()
                                ->pc()
                                ->local_description()
                                ->description()
                                ->contents()[0]
                                .media_description()
                                ->codecs();
  size_t vp8_munge = 0;
  size_t vp9_munge = 0;
  for (const auto& codec : codecs_after_munge) {
    if (codec.name == "VP8")
      vp8_munge++;
    else if (codec.name == "VP9")
      vp9_munge++;
  }
  // We should have replaced VP8 with VP9.
  EXPECT_EQ(vp8_munge, 0u);
  EXPECT_GE(vp9_munge, 2u);

  // Call setCodecPreferences.
  std::vector<RtpCodecCapability> codecs =
      caller()
          ->pc_factory()
          ->GetRtpReceiverCapabilities(webrtc::MediaType::VIDEO)
          .codecs;
  auto transceivers = caller()->pc()->GetTransceivers();
  ASSERT_EQ(transceivers.size(), 1u);
  transceivers[0]->SetCodecPreferences(codecs);

  std::unique_ptr<SessionDescriptionInterface> offer =
      caller()->CreateOfferAndWait();
  ASSERT_THAT(offer, NotNull());
  // The offer should be acceptable.
  EXPECT_TRUE(caller()->SetLocalDescriptionAndSendSdpMessage(std::move(offer)));

  auto codecs_after_scp = caller()
                              ->pc()
                              ->local_description()
                              ->description()
                              ->contents()[0]
                              .media_description()
                              ->codecs();
  size_t vp8_scp = 0;
  size_t vp9_scp = 0;
  for (const auto& codec : codecs_after_scp) {
    if (codec.name == "VP8")
      vp8_scp++;
    else if (codec.name == "VP9")
      vp9_scp++;
  }
  // The SDP munging modification was reverted by sCP.
  // This is a footgun but please do not mix such munging with
  // setCodecPreferences, munging is on the way out.
  EXPECT_EQ(vp8_scp, 1u);
  EXPECT_GE(vp9_scp, 1u);
  EXPECT_GT(vp9_munge, vp9_scp);
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       SensibleRtxWithDuplicateCodecs) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  caller()->AddVideoTrack();
  // Copied from WPT test webrtc/protocol/rtx-codecs.https.html
  std::string remote_offer_string =
      "v=0\r\n"
      "o=- 1878890426675213188 2 IN IP4 127.0.0.1\r\n"
      "s=-\r\n"
      "t=0 0\r\n"
      "a=group:BUNDLE video\r\n"
      "a=msid-semantic: WMS\r\n"
      "m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99\r\n"
      "c=IN IP4 0.0.0.0\r\n"
      "a=rtcp:9 IN IP4 0.0.0.0\r\n"
      "a=ice-ufrag:RGPK\r\n"
      "a=ice-pwd:rAyHEAKC7ckxQgWaRZXukz+Z\r\n"
      "a=ice-options:trickle\r\n"
      "a=fingerprint:sha-256 "
      "8C:29:0A:8F:11:06:BF:1C:58:B3:CA:E6:F1:F1:DC:99:4C:6C:89:E9:FF:BC:D4:38:"
      "11:18:1F:40:19:C8:49:37\r\n"
      "a=setup:actpass\r\n"
      "a=mid:video\r\n"
      "a=recvonly\r\n"
      "a=rtcp-mux\r\n"
      "a=rtpmap:96 VP8/90000\r\n"
      "a=rtpmap:97 rtx/90000\r\n"
      "a=fmtp:97 apt=98\r\n"
      "a=rtpmap:98 VP8/90000\r\n"
      "a=rtcp-fb:98 ccm fir\r\n"
      "a=rtcp-fb:98 nack\r\n"
      "a=rtcp-fb:98 nack pli\r\n"
      "a=rtcp-fb:98 goog-remb\r\n"
      "a=rtcp-fb:98 transport-cc\r\n"
      "a=rtpmap:99 rtx/90000\r\n"
      "a=fmtp:99 apt=96\r\n";
  auto srd_observer = make_ref_counted<MockSetSessionDescriptionObserver>();
  std::unique_ptr<SessionDescriptionInterface> remote_offer =
      CreateSessionDescription(SdpType::kOffer, remote_offer_string);
  EXPECT_TRUE(caller()->SetRemoteDescription(std::move(remote_offer)));
  // The resulting SDP answer should have one video codec with a correctly
  // associated RTX codec.
  std::unique_ptr<SessionDescriptionInterface> answer =
      caller()->CreateAnswerForTest();
  ASSERT_THAT(answer, NotNull());
  RTC_LOG(LS_ERROR) << "Answer is " << *answer;
  ASSERT_THAT(answer->description()->contents().size(), Eq(1));
  auto codecs =
      answer->description()->contents()[0].media_description()->codecs();
  std::vector<int> apt_values;
  for (const Codec& codec : codecs) {
    if (codec.GetResiliencyType() == Codec::ResiliencyType::kRtx) {
      const auto apt_it = codec.params.find(kCodecParamAssociatedPayloadType);
      int apt_value;
      ASSERT_TRUE(FromString(apt_it->second, &apt_value));
      apt_values.push_back(apt_value);
    }
  }
  for (int apt : apt_values) {
    EXPECT_THAT(codecs, Contains(Field("id", &Codec::id, apt)));
  }
}

// This test documents the behavior expected in
// https://issues.webrtc.org/412904801. It does not constitute a promise
// that this mechanism will go on working.
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       MungeRawPacketizationChangesSubsequentSections) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  // Add first video track.
  caller()->AddVideoTrack();
  std::unique_ptr<SessionDescriptionInterface> offer =
      caller()->CreateOfferAndWait();
  EXPECT_EQ(offer->description()->contents().size(), 1U);
  // Observe that packetization is NOT raw.
  for (const auto& content : offer->description()->contents()) {
    for (const auto& codec : content.media_description()->codecs()) {
      ASSERT_THAT(codec.packetization, Eq(std::nullopt));
    }
  }
  // Mangle packetization to be raw.
  for (auto& content : offer->description()->contents()) {
    std::vector<Codec> codecs = content.media_description()->codecs();
    bool mangled_raw = false;
    for (auto& codec : codecs) {
      if (codec.name == "VP8" && codec.type == Codec::Type::kVideo) {
        codec.packetization = kPacketizationParamRaw;
        mangled_raw = true;
      }
    }
    ASSERT_TRUE(mangled_raw);
    content.media_description()->set_codecs(codecs);
  }
  // Set local description.
  auto observer = make_ref_counted<MockSetSessionDescriptionObserver>();
  caller()->pc()->SetLocalDescription(observer.get(), offer.release());
  // Wait for SLD to complete.
  EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()),
              IsRtcOk());
  // Add a second video track.
  caller()->AddVideoTrack();
  auto offer2 = caller()->CreateOfferAndWait();
  // Observe that packetization is raw on BOTH media sections.
  ASSERT_NE(offer2, nullptr);
  EXPECT_EQ(offer2->description()->contents().size(), 2U);
  for (const auto& content : offer2->description()->contents()) {
    for (const auto& codec : content.media_description()->codecs()) {
      if (codec.type == Codec::Type::kVideo && codec.name == "VP8") {
        EXPECT_EQ(codec.packetization, kPacketizationParamRaw);
      } else {
        EXPECT_THAT(codec.packetization, Eq(std::nullopt));
      }
    }
  }
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       OfferAnswerWithAddTrackAndTweak) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignalingForSdpOnly();
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  caller()->AddTrack(caller_track);
  ASSERT_TRUE(caller()
                  ->pc()
                  ->GetTransceivers()[0]
                  ->SetDirectionWithError(RtpTransceiverDirection::kSendOnly)
                  .ok());
  RtpTransceiverInit recvonly_init;
  recvonly_init.direction = RtpTransceiverDirection::kRecvOnly;
  ASSERT_TRUE(
      caller()->pc()->AddTransceiver(MediaType::VIDEO, recvonly_init).ok());

  scoped_refptr<VideoTrackInterface> callee_track =
      callee()->CreateLocalVideoTrack();
  callee()->AddTrack(callee_track);
  auto callee_transceiver = callee()->pc()->GetTransceivers()[0];
  callee_transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendOnly);

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  EXPECT_THAT(caller()->pc()->GetTransceivers().size(), Eq(2));
  EXPECT_THAT(callee()->pc()->GetTransceivers().size(), Eq(2));
  EXPECT_TRUE(callee_transceiver->mid().has_value());
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       OfferAnswerWithAddTransceiver) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignalingForSdpOnly();
  scoped_refptr<VideoTrackInterface> caller_track =
      caller()->CreateLocalVideoTrack();
  RtpTransceiverInit sendonly_init;
  sendonly_init.direction = RtpTransceiverDirection::kSendOnly;
  ASSERT_TRUE(caller()->pc()->AddTransceiver(caller_track, sendonly_init).ok());
  RtpTransceiverInit recvonly_init;
  recvonly_init.direction = RtpTransceiverDirection::kRecvOnly;
  ASSERT_TRUE(
      caller()->pc()->AddTransceiver(MediaType::VIDEO, recvonly_init).ok());
  scoped_refptr<VideoTrackInterface> callee_track =
      callee()->CreateLocalVideoTrack();
  auto receiver_video_transceiver =
      callee()->pc()->AddTransceiver(callee_track, sendonly_init).MoveValue();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
  ASSERT_THAT(caller()->pc()->GetTransceivers().size(), Eq(2));
  // Verify that the video-sending track has not gotten associated with
  // a media section.
  // This is likely not what we want. See this spec issue:
  // https://github.com/rtcweb-wg/jsep/issues/1040
  EXPECT_THAT(callee()->pc()->GetTransceivers().size(), Eq(3));
  EXPECT_FALSE(receiver_video_transceiver->mid().has_value());
}

#ifdef WEBRTC_HAVE_SCTP

TEST_P(PeerConnectionIntegrationTest, DtlsPqc) {
  CryptoOptions crypto_options;
  crypto_options.ephemeral_key_exchange_cipher_groups.AddFirst(
      webrtc::CryptoOptions::EphemeralKeyExchangeCipherGroups::
          kX25519_MLKEM768);

  PeerConnectionInterface::RTCConfiguration config;
  config.crypto_options = crypto_options;

  PeerConnectionFactoryInterface::Options options;
  options.ssl_max_version = SSL_PROTOCOL_DTLS_13;

  const bool create_media_engine = false;
  SetCallerPcWrapperAndReturnCurrent(CreatePeerConnectionWrapper(
      "caller", &options, &config, PeerConnectionDependencies(nullptr),
      /* event_log_factory= */ nullptr,
      /* reset_encoder_factory= */ false,
      /* reset_decoder_factory= */ false, create_media_engine));
  SetCalleePcWrapperAndReturnCurrent(CreatePeerConnectionWrapper(
      "callee", &options, &config, PeerConnectionDependencies(nullptr),
      /* event_log_factory= */ nullptr,
      /* reset_encoder_factory= */ false,
      /* reset_decoder_factory= */ false, create_media_engine));

  ConnectFakeSignaling();

  caller()->CreateDataChannel();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(
      WaitUntil(
          [&] {
            return PeerConnectionStateIs(
                PeerConnectionInterface::PeerConnectionState::kConnected);
          },
          IsTrue()),
      IsRtcOk());

  uint16_t expected =
      webrtc::CryptoOptions::EphemeralKeyExchangeCipherGroups::kX25519_MLKEM768;
  if (!SSLStreamAdapter::IsBoringSsl()) {
    expected = webrtc::CryptoOptions::EphemeralKeyExchangeCipherGroups::kX25519;
  }
  EXPECT_EQ(caller()->dtls_transport_information().ssl_group_id(), expected);
  EXPECT_EQ(caller()->dtls_transport_information().ssl_group_id(), expected);
}

TEST_P(PeerConnectionIntegrationTest, DtlsPqcFieldTrial) {
  SetFieldTrials("WebRTC-EnableDtlsPqc/Enabled/");
  PeerConnectionInterface::RTCConfiguration config;
  PeerConnectionFactoryInterface::Options options;
  options.ssl_max_version = SSL_PROTOCOL_DTLS_13;

  const bool create_media_engine = false;
  SetCallerPcWrapperAndReturnCurrent(CreatePeerConnectionWrapper(
      "caller", &options, &config, PeerConnectionDependencies(nullptr),
      /* event_log_factory= */ nullptr,
      /* reset_encoder_factory= */ false,
      /* reset_decoder_factory= */ false, create_media_engine));
  SetCalleePcWrapperAndReturnCurrent(CreatePeerConnectionWrapper(
      "callee", &options, &config, PeerConnectionDependencies(nullptr),
      /* event_log_factory= */ nullptr,
      /* reset_encoder_factory= */ false,
      /* reset_decoder_factory= */ false, create_media_engine));

  ConnectFakeSignaling();

  caller()->CreateDataChannel();

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(
      WaitUntil(
          [&] {
            return PeerConnectionStateIs(
                PeerConnectionInterface::PeerConnectionState::kConnected);
          },
          IsTrue()),
      IsRtcOk());

  uint16_t expected =
      webrtc::CryptoOptions::EphemeralKeyExchangeCipherGroups::kX25519_MLKEM768;
  if (!SSLStreamAdapter::IsBoringSsl()) {
    expected = webrtc::CryptoOptions::EphemeralKeyExchangeCipherGroups::kX25519;
  }
  EXPECT_EQ(caller()->dtls_transport_information().ssl_group_id(), expected);
  EXPECT_EQ(caller()->dtls_transport_information().ssl_group_id(), expected);
}

#endif  // WEBRTC_HAVE_SCTP

TEST_P(PeerConnectionIntegrationTest, PerPeerConnectionHeaderExtensions) {
  SetFieldTrials("caller", "WebRTC-VideoFrameTrackingIdAdvertised/Enabled/");
  SetFieldTrials("callee", "WebRTC-VideoFrameTrackingIdAdvertised/Disabled/");
  PeerConnectionInterface::RTCConfiguration config;
  PeerConnectionFactoryInterface::Options options;
  options.ssl_max_version = SSL_PROTOCOL_DTLS_13;

  const bool create_media_engine = true;
  SetCallerPcWrapperAndReturnCurrent(CreatePeerConnectionWrapper(
      "caller", &options, &config, PeerConnectionDependencies(nullptr),
      /* event_log_factory= */ nullptr,
      /* reset_encoder_factory= */ false,
      /* reset_decoder_factory= */ false, create_media_engine));
  SetCalleePcWrapperAndReturnCurrent(CreatePeerConnectionWrapper(
      "callee", &options, &config, PeerConnectionDependencies(nullptr),
      /* event_log_factory= */ nullptr,
      /* reset_encoder_factory= */ false,
      /* reset_decoder_factory= */ false, create_media_engine));

  const std::string uri =
      "http://www.webrtc.org/experiments/rtp-hdrext/video-frame-tracking-id";
  {
    scoped_refptr<VideoTrackInterface> caller_track =
        caller()->CreateLocalVideoTrack();
    scoped_refptr<RtpSenderInterface> caller_sender =
        caller()->AddTrack(caller_track);

    auto session_description = caller()->CreateOfferAndWait();
    EXPECT_THAT(session_description->description()
                    ->contents()[0]
                    .media_description()
                    ->rtp_header_extensions(),
                Contains(Field(&RtpExtension::uri, uri)));
  }

  {
    scoped_refptr<VideoTrackInterface> callee_track =
        callee()->CreateLocalVideoTrack();

    scoped_refptr<RtpSenderInterface> callee_sender =
        callee()->AddTrack(callee_track);
    auto session_description = callee()->CreateOfferAndWait();
    EXPECT_THAT(session_description->description()
                    ->contents()[0]
                    .media_description()
                    ->rtp_header_extensions(),
                Not(Contains(Field(&RtpExtension::uri, uri))));
  }
}

TEST_P(PeerConnectionIntegrationTest, CryptexRenegotiation) {
  CryptoOptions crypto_options;
  crypto_options.srtp.cryptex_policy =
      CryptoOptions::Srtp::CryptexPolicy::kNegotiate;
  PeerConnectionInterface::RTCConfiguration config;
  config.crypto_options = crypto_options;

  const bool create_media_engine = true;
  SetCallerPcWrapperAndReturnCurrent(CreatePeerConnectionWrapper(
      "caller", nullptr, &config, PeerConnectionDependencies(nullptr),
      /* event_log_factory= */ nullptr,
      /* reset_encoder_factory= */ false,
      /* reset_decoder_factory= */ false, create_media_engine));
  SetCalleePcWrapperAndReturnCurrent(CreatePeerConnectionWrapper(
      "callee", nullptr, &config, PeerConnectionDependencies(nullptr),
      /* event_log_factory= */ nullptr,
      /* reset_encoder_factory= */ false,
      /* reset_decoder_factory= */ false, create_media_engine));

  ConnectFakeSignaling();

  caller()->AddAudioVideoTracks();
  // Strip cryptex from the answer the caller receives so the caller observes
  // a peer that does not negotiate cryptex.
  caller()->SetReceivedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
        sdp->description()->set_cryptex(false);
      });

  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(
      WaitUntil(
          [&] {
            return PeerConnectionStateIs(
                PeerConnectionInterface::PeerConnectionState::kConnected);
          },
          IsTrue()),
      IsRtcOk());
  caller()->SetReceivedSdpMunger(nullptr);
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_THAT(WaitUntil([&] { return SignalingStateStable(); }, IsTrue()),
              IsRtcOk());
}

#ifdef WEBRTC_HAVE_SCTP

class DowngradeLogSink : public LogSink {
 public:
  void OnLogMessage(const std::string& message) override {
    if (message.find("Received unexpected non-DTLS packet") !=
        std::string::npos) {
      found_ = true;
    }
  }
  bool found() const { return found_; }

 private:
  bool found_ = false;
};

// This test reproduces the vulnerability where a kPrAnswer with a BUNDLE group
// but no fingerprint for a media section can cause that media section to use
// an unencrypted transport.
TEST_P(PeerConnectionIntegrationTest,
       DataChannelEncryptionDowngradeViaBundleDesyncInPrAnswer) {
  RTCConfiguration config;
  config.bundle_policy = PeerConnectionInterface::kBundlePolicyBalanced;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();

  // 1. Initial negotiation without BUNDLE.
  // We munge the answer to remove BUNDLE so that separate transports are
  // created.
  callee()->SetGeneratedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& desc) {
        desc->description()->RemoveGroupByName(GROUP_TYPE_BUNDLE);
      });

  caller()->AddVideoTrack();
  caller()->CreateDataChannel();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_TRUE(WaitUntil([&] { return SignalingStateStable(); }));
  ASSERT_TRUE(WaitUntil([&] { return DtlsConnected(); }));

  // 2. Renegotiation with BUNDLE in offer, and malicious prAnswer.
  // The malicious prAnswer will have a BUNDLE group containing mid 0 and mid 1,
  // but mid 1 (Data Channel) will have no fingerprint.
  callee()->SetGeneratedSdpMunger(nullptr);

  // Create an offer.
  std::unique_ptr<SessionDescriptionInterface> offer =
      caller()->CreateOfferAndWait();
  ASSERT_THAT(offer, NotNull());

  // Set local offer on caller.
  auto observer = make_ref_counted<FakeSetLocalDescriptionObserver>();
  caller()->pc()->SetLocalDescription(std::move(offer), observer);
  ASSERT_TRUE(WaitUntil([&] { return observer->called(); }));

  // Set remote offer on callee.
  std::unique_ptr<SessionDescriptionInterface> offer_for_callee =
      caller()->CreateOfferAndWait();
  ASSERT_THAT(offer_for_callee, NotNull());
  ASSERT_TRUE(callee()->SetRemoteDescription(std::move(offer_for_callee)));

  // Callee creates a normal answer first.
  auto answer_observer =
      make_ref_counted<MockCreateSessionDescriptionObserver>();
  callee()->pc()->CreateAnswer(
      answer_observer.get(), PeerConnectionInterface::RTCOfferAnswerOptions());
  ASSERT_TRUE(WaitUntil([&] { return answer_observer->called(); }));
  std::unique_ptr<SessionDescriptionInterface> answer =
      answer_observer->MoveDescription();
  ASSERT_THAT(answer, NotNull());

  // Convert the answer to a prAnswer by serializing and re-parsing.
  std::string sdp = answer->ToString();
  ASSERT_FALSE(sdp.empty());
  std::unique_ptr<SessionDescriptionInterface> pr_answer =
      CreateSessionDescription(SdpType::kPrAnswer, sdp);
  ASSERT_THAT(pr_answer, NotNull());

  // Munge the prAnswer:
  // - Ensure BUNDLE group is present.
  // - Remove fingerprint from the second media section (mid 1).
  SessionDescription* desc = pr_answer->description();
  const ContentGroup* bundle_group = desc->GetGroupByName(GROUP_TYPE_BUNDLE);
  ASSERT_THAT(bundle_group, NotNull());
  ASSERT_THAT(bundle_group->content_names(), SizeIs(2));

  // Remove fingerprint from the second content.
  const ContentInfos& contents = desc->contents();
  ASSERT_GE(contents.size(), 2u);
  TransportInfo* transport_info =
      desc->GetTransportInfoByName(contents[1].mid());
  ASSERT_THAT(transport_info, NotNull());
  transport_info->description.identity_fingerprint.reset();

  // Set the remote prAnswer on the caller.
  // This succeeds because VerifyCrypto skips the fingerprint check for
  // bundled sections (except the first).
  ASSERT_TRUE(caller()->SetRemoteDescription(std::move(pr_answer)));

  // With the fix, the Data Channel's transport will now be BUNDLED into mid 0.
  // Since mid 0 has DTLS active, mid 1's data will also be DTLS-wrapped.
  // We can verify this by checking that the SCTP transport's internal DTLS
  // transport is active.
  EXPECT_TRUE(network_thread()->BlockingCall([&] {
    return static_cast<SctpTransport*>(caller()->pc()->GetSctpTransport().get())
        ->internal()
        ->dtls_transport()
        ->IsDtlsActive();
  }));

  // If the bug were present, it would be sent as raw SCTP and trigger an error
  // on the peer.

  DowngradeLogSink downgrade_log_sink;
  LogMessage::AddLogToStream(&downgrade_log_sink, LS_ERROR);

  // Send data from caller.
  ASSERT_EQ(caller()->data_channels().size(), 1u);
  ASSERT_TRUE(WaitUntil([&] {
    return caller()->data_channels()[0]->state() == DataChannelInterface::kOpen;
  }));
  caller()->data_channels()[0]->Send(DataBuffer("SECRET-PLAINTEXT"));

  // If the fix is correct, no non-DTLS packets should be received.
  // We wait a bit to ensure packets have time to arrive.
  Thread::Current()->ProcessMessages(500);

  bool found = downgrade_log_sink.found();
  LogMessage::RemoveLogToStream(&downgrade_log_sink);

  EXPECT_FALSE(found) << "Security downgrade detected: raw SCTP packets sent "
                         "instead of DTLS-wrapped packets.";
}

// This test verifies that establishing BUNDLE in a prAnswer does not cause
// the destruction of transports that might be needed if the final answer
// removes BUNDLE.
TEST_P(PeerConnectionIntegrationTest,
       BundleEstablishedInPrAnswerThenRemovedInFinalAnswer) {
  RTCConfiguration config;
  config.bundle_policy = PeerConnectionInterface::kBundlePolicyBalanced;
  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
  ConnectFakeSignaling();

  // 1. Initial negotiation without BUNDLE.
  callee()->SetGeneratedSdpMunger(
      [](std::unique_ptr<SessionDescriptionInterface>& desc) {
        desc->description()->RemoveGroupByName(GROUP_TYPE_BUNDLE);
      });

  caller()->AddVideoTrack();
  caller()->CreateDataChannel();
  caller()->CreateAndSetAndSignalOffer();
  ASSERT_TRUE(WaitUntil([&] { return SignalingStateStable(); }));
  ASSERT_TRUE(WaitUntil([&] { return DtlsConnected(); }));

  // 2. Renegotiation with BUNDLE in offer and prAnswer, then NO BUNDLE in
  // answer.
  std::unique_ptr<SessionDescriptionInterface> offer =
      caller()->CreateOfferAndWait();
  ASSERT_THAT(offer, NotNull());

  // Set local offer on caller.
  auto observer = make_ref_counted<FakeSetLocalDescriptionObserver>();
  caller()->pc()->SetLocalDescription(std::move(offer), observer);
  ASSERT_TRUE(WaitUntil([&] { return observer->called(); }));

  // Set remote offer on callee.
  std::unique_ptr<SessionDescriptionInterface> offer_for_callee =
      caller()->CreateOfferAndWait();
  ASSERT_THAT(offer_for_callee, NotNull());
  ASSERT_TRUE(callee()->SetRemoteDescription(std::move(offer_for_callee)));

  // Callee creates a prAnswer WITH BUNDLE.
  auto answer_observer =
      make_ref_counted<MockCreateSessionDescriptionObserver>();
  callee()->pc()->CreateAnswer(
      answer_observer.get(), PeerConnectionInterface::RTCOfferAnswerOptions());
  ASSERT_TRUE(WaitUntil([&] { return answer_observer->called(); }));
  std::unique_ptr<SessionDescriptionInterface> answer =
      answer_observer->MoveDescription();
  ASSERT_THAT(answer, NotNull());

  std::string sdp = answer->ToString();
  ASSERT_FALSE(sdp.empty());
  std::unique_ptr<SessionDescriptionInterface> pr_answer =
      CreateSessionDescription(SdpType::kPrAnswer, sdp);
  ASSERT_THAT(pr_answer, NotNull());

  // Set the remote prAnswer on the caller.
  // This establishes BUNDLE.
  const ContentGroup* bundle_group =
      pr_answer->description()->GetGroupByName(GROUP_TYPE_BUNDLE);
  ASSERT_THAT(bundle_group, NotNull());
  ASSERT_THAT(bundle_group->content_names(), SizeIs(2));

  ASSERT_TRUE(caller()->SetRemoteDescription(std::move(pr_answer)));

  // 3. Final answer WITHOUT BUNDLE.
  auto final_answer_observer =
      make_ref_counted<MockCreateSessionDescriptionObserver>();
  callee()->pc()->CreateAnswer(
      final_answer_observer.get(),
      PeerConnectionInterface::RTCOfferAnswerOptions());
  ASSERT_TRUE(WaitUntil([&] { return final_answer_observer->called(); }));
  std::unique_ptr<SessionDescriptionInterface> final_answer =
      final_answer_observer->MoveDescription();
  ASSERT_THAT(final_answer, NotNull());

  // Munge it to remove BUNDLE.
  final_answer->description()->RemoveGroupByName(GROUP_TYPE_BUNDLE);

  // Verify it has no bundle.
  ASSERT_FALSE(final_answer->description()->GetGroupByName(GROUP_TYPE_BUNDLE));

  // Set the final remote answer on the caller.
  // This should FAIL because mid 0 was already bundled with mid 1 in the
  // prAnswer, and RFC 8843 says you can't remove an m= section from a BUNDLE
  // group once it's been established.
  ASSERT_FALSE(caller()->SetRemoteDescription(std::move(final_answer)));
}

#endif  // WEBRTC_HAVE_SCTP

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       MungeOfferCodecAndReOfferCausesNoDuplicateId) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  caller()->AddAudioTrack();
  bool munged = false;
  auto munger = [&munged](std::unique_ptr<SessionDescriptionInterface>& sdp) {
    VideoContentDescription* video =
        GetFirstVideoContentDescription(sdp->description());
    std::vector<Codec> codecs = video->codecs();
    // Check that AV1 is present. If not, the munged SDP won't be accepted
    // by SetLocalDescription.
    if (!absl::c_any_of(
            codecs, [](const Codec& codec) { return codec.name == "AV1"; })) {
      return;
    }
    for (auto& codec : codecs) {
      if (codec.name == "VP9") {
        RTC_LOG(LS_INFO) << "Remapping VP9 codec " << codec << " to AV1";
        codec.name = "AV1";
        munged = true;
      }
    }
    video->set_codecs(codecs);
  };
  caller()->SetGeneratedSdpMunger(munger);
  caller()->CreateAndSetAndSignalOffer();
  if (!munged) {
    GTEST_SKIP() << "Test skipped, codec remapping did not work";
  }
  ASSERT_TRUE(WaitUntil([&] { return SignalingStateStable(); }));
  EXPECT_TRUE(ValidateBundledPayloadTypes(
                  *caller()->pc()->local_description()->description())
                  .ok());
  EXPECT_TRUE(ValidateBundledPayloadTypes(
                  *caller()->pc()->remote_description()->description())
                  .ok());
  caller()->SetGeneratedSdpMunger(nullptr);
  auto offer = caller()->CreateOfferAndWait();
  ASSERT_THAT(offer, NotNull());
  // The offer should be acceptable.
  EXPECT_TRUE(ValidateBundledPayloadTypes(*offer->description()).ok());
  EXPECT_TRUE(caller()->SetLocalDescriptionAndSendSdpMessage(std::move(offer)));
}

TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
       MungeOfferCodecWithNonsenseFailsAtSetLocalDescription) {
  ASSERT_TRUE(CreatePeerConnectionWrappers());
  ConnectFakeSignaling();
  caller()->AddVideoTrack();
  caller()->AddAudioTrack();
  bool munged = false;
  auto munger = [&munged](std::unique_ptr<SessionDescriptionInterface>& sdp) {
    VideoContentDescription* video =
        GetFirstVideoContentDescription(sdp->description());
    std::vector<Codec> codecs = video->codecs();
    for (auto& codec : codecs) {
      if (codec.name == "VP9") {
        RTC_LOG(LS_ERROR) << "Remapping VP9 codec " << codec << " to NONSENSE";
        codec.name = "NONSENSE";
        munged = true;
      }
    }
    video->set_codecs(codecs);
  };
  caller()->SetGeneratedSdpMunger(munger);
  std::unique_ptr<SessionDescriptionInterface> offer =
      caller()->CreateOfferAndWait();
  ASSERT_THAT(offer, NotNull());
  // If the munger failed to find a VP9 codec to munge, don't test.
  if (!munged) {
    GTEST_SKIP() << "Replacement of codec failed, skipping test";
  }
  auto observer = make_ref_counted<MockSetSessionDescriptionObserver>();
  caller()->pc()->SetLocalDescription(observer.get(), offer.release());
  ASSERT_TRUE(WaitUntil([&] { return observer->called(); }));
  // Observe failure.
  EXPECT_FALSE(observer->result());
}

}  // namespace

}  // namespace webrtc
