/*
 *  POC: Stale Syncable* in RtpStreamsSynchronizer after
 *  Call::OnUpdateSyncGroup leads to UAF (virtual call) on freed
 *  AudioReceiveStreamImpl.
 *
 *  This test exercises the production sequence that
 *  WebRtcVoiceReceiveChannel::AddRecvStream performs when an unsignaled
 *  audio stream is "promoted" to a signaled stream with a *different*
 *  msid (sync_group):
 *
 *      Call::CreateVideoReceiveStream(V, sync_group="A")
 *      Call::CreateAudioReceiveStream(X, sync_group="A")
 *          -> ConfigureSync("A") -> V.SetSync(X)
 *             RtpStreamsSynchronizer::syncable_audio_ = X
 *             1 Hz UpdateDelay() repeating task started.
 *      Call::OnUpdateSyncGroup(X, "B")
 *          -> X.sync_group becomes "B"
 *          -> ConfigureSync("B") iterates only video streams in "B"
 *             ==> V (still group "A") is skipped; syncable_audio_ stays X.
 *      Call::DestroyAudioReceiveStream(X)
 *          -> ConfigureSync(X.sync_group()) == ConfigureSync("B")
 *             ==> V skipped again.
 *          -> delete X.       <-- syncable_audio_ now dangling.
 *      <wait 1 s>
 *      RtpStreamsSynchronizer::UpdateDelay()
 *          -> syncable_audio_->GetInfo()    <-- UAF, virtual call on
 *                                               freed AudioReceiveStreamImpl.
 *
 *  Under ASAN this produces a heap-use-after-free at
 *  rtp_streams_synchronizer2.cc:107.
 */

#include <memory>

#include "api/audio_codecs/audio_decoder_factory.h"
#include "api/make_ref_counted.h"
#include "api/scoped_refptr.h"
#include "api/test/video/function_video_decoder_factory.h"
#include "api/units/time_delta.h"
#include "api/video_codecs/sdp_video_format.h"
#include "call/audio_receive_stream.h"
#include "call/audio_state.h"
#include "call/call.h"
#include "call/call_config.h"
#include "call/video_receive_stream.h"
#include "modules/audio_device/include/mock_audio_device.h"
#include "modules/audio_mixer/audio_mixer_impl.h"
#include "test/create_test_environment.h"
#include "test/fake_decoder.h"
#include "test/fake_videorenderer.h"
#include "test/gtest.h"
#include "test/mock_audio_decoder_factory.h"
#include "test/mock_transport.h"
#include "test/run_loop.h"

namespace webrtc {
namespace {

TEST(SyncGroupUafPoc, OnUpdateSyncGroupLeavesStaleSyncableAudio) {
  test::RunLoop loop;

  AudioState::Config audio_state_config;
  audio_state_config.audio_mixer = AudioMixerImpl::Create();
  audio_state_config.audio_processing = nullptr;
  audio_state_config.audio_device_module =
      test::MockAudioDeviceModule::CreateNice();

  CallConfig config(CreateTestEnvironment());
  config.audio_state = AudioState::Create(audio_state_config);
  std::unique_ptr<Call> call = Call::Create(std::move(config));

  ::testing::NiceMock<MockTransport> transport;
  scoped_refptr<AudioDecoderFactory> audio_decoder_factory =
      MockAudioDecoderFactory::CreateEmptyFactory();
  test::FunctionVideoDecoderFactory video_decoder_factory(
      []() { return std::make_unique<test::FakeDecoder>(); });
  test::FakeVideoRenderer renderer;

  // 1) Video receive stream V in sync_group "A".
  VideoReceiveStreamInterface::Config video_config(&transport);
  video_config.rtp.remote_ssrc = 0x1111;
  video_config.sync_group = "A";
  video_config.decoders.emplace_back(SdpVideoFormat("VP8"),
                                     /*payload_type=*/96);
  video_config.decoder_factory = &video_decoder_factory;
  video_config.renderer = &renderer;
  VideoReceiveStreamInterface* video_stream =
      call->CreateVideoReceiveStream(std::move(video_config));
  ASSERT_NE(video_stream, nullptr);

  // 2) Audio receive stream X in sync_group "A".
  //    Call::CreateAudioReceiveStream -> ConfigureSync("A") sets
  //    V's RtpStreamsSynchronizer::syncable_audio_ = X and starts the
  //    1 Hz UpdateDelay() repeating task on this RunLoop's task queue.
  AudioReceiveStreamInterface::Config audio_config;
  audio_config.rtp.remote_ssrc = 0x2222;
  audio_config.rtcp_send_transport = &transport;
  audio_config.decoder_factory = audio_decoder_factory;
  audio_config.sync_group = "A";
  AudioReceiveStreamInterface* audio_stream =
      call->CreateAudioReceiveStream(audio_config);
  ASSERT_NE(audio_stream, nullptr);

  // 3) Promote X to sync_group "B" -- this is exactly what
  //    WebRtcVoiceReceiveChannel::AddRecvStream does at
  //    media/engine/webrtc_voice_engine.cc when an unsignaled SSRC is
  //    later signaled with a different msid.  ConfigureSync("B") only
  //    touches video streams whose sync_group()=="B"; V (group "A") is
  //    skipped and keeps its raw Syncable* pointing at X.
  call->OnUpdateSyncGroup(*audio_stream, "B");

  // 4) Destroy X.  Call::DestroyAudioReceiveStream calls
  //    ConfigureSync(X.sync_group()) == ConfigureSync("B"), which again
  //    skips V, then `delete X`.  V's syncable_audio_ is now dangling.
  call->DestroyAudioReceiveStream(audio_stream);

  // 5) Let the 1 Hz repeating task fire.  RtpStreamsSynchronizer::
  //    UpdateDelay() runs `syncable_audio_->GetInfo()` -- a virtual call
  //    through the freed AudioReceiveStreamImpl's vtable.
  //
  //    *** Under ASAN this is a heap-use-after-free. ***
  loop.RunFor(TimeDelta::Millis(1500));

  // (Only reached without ASAN / if the bug is fixed.)
  call->DestroyVideoReceiveStream(video_stream);
}

}  // namespace
}  // namespace webrtc

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
