/*
 *  Copyright (c) 2021 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.
 */

#include "modules/rtp_rtcp/source/packet_sequencer.h"

#include "api/units/timestamp.h"
#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
#include "system_wrappers/include/clock.h"
#include "test/gmock.h"
#include "test/gtest.h"

namespace webrtc {
namespace {
constexpr Timestamp kStartTime = Timestamp::Millis(98765);
constexpr uint32_t kMediaSsrc = 123456;
constexpr uint32_t kRtxSsrc = 123457;
constexpr uint8_t kMediaPayloadType = 42;
constexpr uint16_t kMediaStartSequenceNumber = 123;
constexpr uint16_t kRtxStartSequenceNumber = 234;
constexpr uint16_t kDefaultSequenceNumber = 0x1234;
constexpr uint32_t kStartRtpTimestamp = 798;

class PacketSequencerTest : public ::testing::Test {
 public:
  PacketSequencerTest()
      : clock_(kStartTime),
        sequencer_(kMediaSsrc,
                   kRtxSsrc,
                   /*require_marker_before_media_padding=*/true,
                   &clock_) {}

  RtpPacketToSend CreatePacket(RtpPacketMediaType type, uint32_t ssrc) {
    RtpPacketToSend packet(/*extension_manager=*/nullptr);
    packet.set_packet_type(type);
    packet.SetSsrc(ssrc);
    packet.SetSequenceNumber(kDefaultSequenceNumber);
    packet.set_capture_time(clock_.CurrentTime());
    packet.SetTimestamp(
        kStartRtpTimestamp +
        static_cast<uint32_t>(packet.capture_time().ms() - kStartTime.ms()));
    return packet;
  }

 protected:
  SimulatedClock clock_;
  PacketSequencer sequencer_;
};

TEST_F(PacketSequencerTest, IgnoresMediaSsrcRetransmissions) {
  RtpPacketToSend packet =
      CreatePacket(RtpPacketMediaType::kRetransmission, kMediaSsrc);
  sequencer_.set_media_sequence_number(kMediaStartSequenceNumber);
  sequencer_.Sequence(packet);
  EXPECT_EQ(packet.SequenceNumber(), kDefaultSequenceNumber);
  EXPECT_EQ(sequencer_.media_sequence_number(), kMediaStartSequenceNumber);
}

TEST_F(PacketSequencerTest, SequencesAudio) {
  RtpPacketToSend packet = CreatePacket(RtpPacketMediaType::kAudio, kMediaSsrc);
  sequencer_.set_media_sequence_number(kMediaStartSequenceNumber);
  sequencer_.Sequence(packet);
  EXPECT_EQ(packet.SequenceNumber(), kMediaStartSequenceNumber);
  EXPECT_EQ(sequencer_.media_sequence_number(), kMediaStartSequenceNumber + 1);
}

TEST_F(PacketSequencerTest, SequencesVideo) {
  RtpPacketToSend packet = CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc);
  sequencer_.set_media_sequence_number(kMediaStartSequenceNumber);
  sequencer_.Sequence(packet);
  EXPECT_EQ(packet.SequenceNumber(), kMediaStartSequenceNumber);
  EXPECT_EQ(sequencer_.media_sequence_number(), kMediaStartSequenceNumber + 1);
}

TEST_F(PacketSequencerTest, SequencesUlpFec) {
  RtpPacketToSend packet =
      CreatePacket(RtpPacketMediaType::kForwardErrorCorrection, kMediaSsrc);
  sequencer_.set_media_sequence_number(kMediaStartSequenceNumber);
  sequencer_.Sequence(packet);
  EXPECT_EQ(packet.SequenceNumber(), kMediaStartSequenceNumber);
  EXPECT_EQ(sequencer_.media_sequence_number(), kMediaStartSequenceNumber + 1);
}

TEST_F(PacketSequencerTest, SequencesRtxRetransmissions) {
  RtpPacketToSend packet =
      CreatePacket(RtpPacketMediaType::kRetransmission, kRtxSsrc);
  sequencer_.set_rtx_sequence_number(kRtxStartSequenceNumber);
  sequencer_.Sequence(packet);
  EXPECT_EQ(packet.SequenceNumber(), kRtxStartSequenceNumber);
  EXPECT_EQ(sequencer_.rtx_sequence_number(), kRtxStartSequenceNumber + 1);
}

TEST_F(PacketSequencerTest, ProhibitsPaddingWithinVideoFrame) {
  // Send a video packet with the marker bit set to false (indicating it is not
  // the last packet of a frame).
  RtpPacketToSend media_packet =
      CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc);
  media_packet.SetPayloadType(kMediaPayloadType);
  media_packet.SetMarker(false);
  sequencer_.Sequence(media_packet);

  // Sending padding on the media SSRC should not be allowed at this point.
  EXPECT_FALSE(sequencer_.CanSendPaddingOnMediaSsrc());

  // Send a video packet with marker set to true, padding should be allowed
  // again.
  media_packet.SetMarker(true);
  sequencer_.Sequence(media_packet);
  EXPECT_TRUE(sequencer_.CanSendPaddingOnMediaSsrc());
}

TEST_F(PacketSequencerTest, AllowsPaddingAtAnyTimeIfSoConfigured) {
  PacketSequencer packet_sequencer(
      kMediaSsrc, kRtxSsrc,
      /*require_marker_before_media_padding=*/false, &clock_);

  // Send an audio packet with the marker bit set to false.
  RtpPacketToSend media_packet =
      CreatePacket(RtpPacketMediaType::kAudio, kMediaSsrc);
  media_packet.SetPayloadType(kMediaPayloadType);
  media_packet.SetMarker(false);
  packet_sequencer.Sequence(media_packet);

  // Sending padding on the media SSRC should be allowed despite no marker bit.
  EXPECT_TRUE(packet_sequencer.CanSendPaddingOnMediaSsrc());
}

TEST_F(PacketSequencerTest, UpdatesPaddingBasedOnLastMediaPacket) {
  // First send a media packet.
  RtpPacketToSend media_packet =
      CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc);
  media_packet.SetPayloadType(kMediaPayloadType);
  media_packet.SetMarker(true);
  // Advance time so current time doesn't exactly match timestamp.
  clock_.AdvanceTime(TimeDelta::Millis(5));
  sequencer_.set_media_sequence_number(kMediaStartSequenceNumber);
  sequencer_.Sequence(media_packet);

  // Next send a padding packet and verify the media packet's timestamps and
  // payload type is transferred to the padding packet.
  RtpPacketToSend padding_packet =
      CreatePacket(RtpPacketMediaType::kPadding, kMediaSsrc);
  padding_packet.SetPadding(/*padding_size=*/100);
  sequencer_.Sequence(padding_packet);

  EXPECT_EQ(padding_packet.SequenceNumber(), kMediaStartSequenceNumber + 1);
  EXPECT_EQ(padding_packet.PayloadType(), kMediaPayloadType);
  EXPECT_EQ(padding_packet.Timestamp(), media_packet.Timestamp());
  EXPECT_EQ(padding_packet.capture_time(), media_packet.capture_time());
}

TEST_F(PacketSequencerTest, UpdatesPaddingBasedOnLastRedPacket) {
  // First send a media packet.
  RtpPacketToSend media_packet =
      CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc);
  media_packet.SetPayloadType(kMediaPayloadType);
  // Simulate a packet with RED encapsulation;
  media_packet.set_is_red(true);
  uint8_t* payload_buffer = media_packet.SetPayloadSize(1);
  payload_buffer[0] = kMediaPayloadType + 1;

  media_packet.SetMarker(true);
  // Advance time so current time doesn't exactly match timestamp.
  clock_.AdvanceTime(TimeDelta::Millis(5));
  sequencer_.set_media_sequence_number(kMediaStartSequenceNumber);
  sequencer_.Sequence(media_packet);

  // Next send a padding packet and verify the media packet's timestamps and
  // payload type is transferred to the padding packet.
  RtpPacketToSend padding_packet =
      CreatePacket(RtpPacketMediaType::kPadding, kMediaSsrc);
  padding_packet.SetPadding(100);
  sequencer_.Sequence(padding_packet);

  EXPECT_EQ(padding_packet.SequenceNumber(), kMediaStartSequenceNumber + 1);
  EXPECT_EQ(padding_packet.PayloadType(), kMediaPayloadType + 1);
  EXPECT_EQ(padding_packet.Timestamp(), media_packet.Timestamp());
  EXPECT_EQ(padding_packet.capture_time(), media_packet.capture_time());
}

TEST_F(PacketSequencerTest, DoesNotUpdateFieldsOnPayloadPadding) {
  // First send a media packet.
  RtpPacketToSend media_packet =
      CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc);
  media_packet.SetPayloadType(kMediaPayloadType);
  media_packet.SetMarker(true);
  // Advance time so current time doesn't exactly match timestamp.
  clock_.AdvanceTime(TimeDelta::Millis(5));
  sequencer_.set_media_sequence_number(kMediaStartSequenceNumber);
  sequencer_.Sequence(media_packet);

  // Simulate a payload padding packet on the RTX SSRC.
  RtpPacketToSend padding_packet =
      CreatePacket(RtpPacketMediaType::kPadding, kRtxSsrc);
  padding_packet.SetPayloadSize(100);
  padding_packet.SetPayloadType(kMediaPayloadType + 1);
  padding_packet.SetTimestamp(kStartRtpTimestamp + 1);
  padding_packet.set_capture_time(kStartTime + TimeDelta::Millis(1));
  sequencer_.set_rtx_sequence_number(kRtxStartSequenceNumber);
  sequencer_.Sequence(padding_packet);

  // The sequence number should be updated, but timestamps kept.
  EXPECT_EQ(padding_packet.SequenceNumber(), kRtxStartSequenceNumber);
  EXPECT_EQ(padding_packet.PayloadType(), kMediaPayloadType + 1);
  EXPECT_EQ(padding_packet.Timestamp(), kStartRtpTimestamp + 1);
  EXPECT_EQ(padding_packet.capture_time(), kStartTime + TimeDelta::Millis(1));
}

TEST_F(PacketSequencerTest, UpdatesRtxPaddingBasedOnLastMediaPacket) {
  constexpr uint32_t kTimestampTicksPerMs = 90;

  // First send a media packet.
  RtpPacketToSend media_packet =
      CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc);
  media_packet.SetPayloadType(kMediaPayloadType);
  media_packet.SetMarker(true);
  sequencer_.set_media_sequence_number(kMediaStartSequenceNumber);
  sequencer_.Sequence(media_packet);

  // Advance time, this time delta will be used to interpolate padding
  // timestamps.
  constexpr TimeDelta kTimeDelta = TimeDelta::Millis(10);
  clock_.AdvanceTime(kTimeDelta);

  RtpPacketToSend padding_packet =
      CreatePacket(RtpPacketMediaType::kPadding, kRtxSsrc);
  padding_packet.SetPadding(100);
  padding_packet.SetPayloadType(kMediaPayloadType + 1);
  sequencer_.set_rtx_sequence_number(kRtxStartSequenceNumber);
  sequencer_.Sequence(padding_packet);

  // Assigned RTX sequence number, but payload type unchanged in this case.
  EXPECT_EQ(padding_packet.SequenceNumber(), kRtxStartSequenceNumber);
  EXPECT_EQ(padding_packet.PayloadType(), kMediaPayloadType + 1);
  // Timestamps are offset realtive to last media packet.
  EXPECT_EQ(
      padding_packet.Timestamp(),
      media_packet.Timestamp() + (kTimeDelta.ms() * kTimestampTicksPerMs));
  EXPECT_EQ(padding_packet.capture_time(),
            media_packet.capture_time() + kTimeDelta);
}

}  // namespace
}  // namespace webrtc
