// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cast/streaming/packet_receive_stats_tracker.h"

#include <chrono>
#include <limits>

#include "cast/streaming/constants.h"
#include "gtest/gtest.h"
#include "util/chrono_helpers.h"

namespace openscreen {
namespace cast {
namespace {

// Returns a RtcpReportBlock with all fields set to known values to see how the
// fields are modified by functions called during the tests.
RtcpReportBlock GetSentinel() {
  RtcpReportBlock report;
  report.ssrc = Ssrc{0x1337beef};
  report.packet_fraction_lost_numerator = -999;
  report.cumulative_packets_lost = -0x1337cafe;
  report.extended_high_sequence_number = 0x98765432;
  report.jitter =
      RtpTimeDelta::FromTicks(std::numeric_limits<int64_t>::max() - 42);
  report.last_status_report_id = StatusReportId{2222222222};
  report.delay_since_last_report = RtcpReportBlock::Delay(-0x3550641);
  return report;
}

// Run gtest expectations, that no fields were changed.
#define EXPECT_FIELDS_NOT_POPULATED(x)                                        \
  do {                                                                        \
    const RtcpReportBlock sentinel = GetSentinel();                           \
    EXPECT_EQ(sentinel.ssrc, (x).ssrc);                                       \
    EXPECT_EQ(sentinel.packet_fraction_lost_numerator,                        \
              (x).packet_fraction_lost_numerator);                            \
    EXPECT_EQ(sentinel.cumulative_packets_lost, (x).cumulative_packets_lost); \
    EXPECT_EQ(sentinel.extended_high_sequence_number,                         \
              (x).extended_high_sequence_number);                             \
    EXPECT_EQ(sentinel.jitter, (x).jitter);                                   \
    EXPECT_EQ(sentinel.last_status_report_id, (x).last_status_report_id);     \
    EXPECT_EQ(sentinel.delay_since_last_report, (x).delay_since_last_report); \
  } while (false)

// Run gtest expectations, that only the fields changed by
// PacketReceiveStatsTracker::PopulateNextReport() were changed.
#define EXPECT_FIELDS_POPULATED(x)                                            \
  do {                                                                        \
    const RtcpReportBlock sentinel = GetSentinel();                           \
    /* Fields that should remain untouched by PopulateNextReport(). */        \
    EXPECT_EQ(sentinel.ssrc, (x).ssrc);                                       \
    EXPECT_EQ(sentinel.last_status_report_id, (x).last_status_report_id);     \
    EXPECT_EQ(sentinel.delay_since_last_report, (x).delay_since_last_report); \
    /* Fields that should have changed.*/                                     \
    EXPECT_NE(sentinel.packet_fraction_lost_numerator,                        \
              (x).packet_fraction_lost_numerator);                            \
    EXPECT_NE(sentinel.cumulative_packets_lost, (x).cumulative_packets_lost); \
    EXPECT_NE(sentinel.extended_high_sequence_number,                         \
              (x).extended_high_sequence_number);                             \
    EXPECT_NE(sentinel.jitter, (x).jitter);                                   \
  } while (false)

TEST(PacketReceiveStatsTrackerTest, DoesNotPopulateReportWithoutData) {
  PacketReceiveStatsTracker tracker(kRtpVideoTimebase);
  RtcpReportBlock report = GetSentinel();
  tracker.PopulateNextReport(&report);
  EXPECT_FIELDS_NOT_POPULATED(report);
}

TEST(PacketReceiveStatsTrackerTest, PopulatesReportWithOnePacketTracked) {
  constexpr uint16_t kSequenceNumber = 1234;
  constexpr RtpTimeTicks kRtpTimestamp =
      RtpTimeTicks() + RtpTimeDelta::FromTicks(42);
  constexpr auto kArrivalTime = Clock::time_point() + seconds(3600);

  PacketReceiveStatsTracker tracker(kRtpVideoTimebase);
  tracker.OnReceivedValidRtpPacket(kSequenceNumber, kRtpTimestamp,
                                   kArrivalTime);

  RtcpReportBlock report = GetSentinel();
  tracker.PopulateNextReport(&report);
  EXPECT_FIELDS_POPULATED(report);
  EXPECT_EQ(0, report.packet_fraction_lost_numerator);
  EXPECT_EQ(0, report.cumulative_packets_lost);
  EXPECT_EQ(kSequenceNumber, report.extended_high_sequence_number);
  EXPECT_EQ(RtpTimeDelta(), report.jitter);
}

TEST(PacketReceiveStatsTrackerTest, WhenReceivingAllPackets) {
  // Set the first sequence number such that wraparound is going to be tested.
  constexpr uint16_t kFirstSequenceNumber =
      std::numeric_limits<uint16_t>::max() - 2;
  constexpr RtpTimeTicks kFirstRtpTimestamp =
      RtpTimeTicks() + RtpTimeDelta::FromTicks(42);
  constexpr auto kFirstArrivalTime = Clock::time_point() + seconds(3600);

  PacketReceiveStatsTracker tracker(kRtpVideoTimebase);

  // Record 10 packets arrived exactly one second apart with media timestamps
  // also exactly one second apart.
  for (int i = 0; i < 10; ++i) {
    tracker.OnReceivedValidRtpPacket(
        kFirstSequenceNumber + i,
        kFirstRtpTimestamp + RtpTimeDelta::FromTicks(kRtpVideoTimebase) * i,
        kFirstArrivalTime + seconds(i));
  }

  RtcpReportBlock report = GetSentinel();
  tracker.PopulateNextReport(&report);
  EXPECT_FIELDS_POPULATED(report);

  // Nothing should indicate to the tracker that any packets were dropped.
  EXPECT_EQ(0, report.packet_fraction_lost_numerator);
  EXPECT_EQ(0, report.cumulative_packets_lost);

  // The |extended_high_sequence_number| should reflect the wraparound of the
  // 16-bit counter value.
  EXPECT_EQ(uint32_t{65542}, report.extended_high_sequence_number);

  // There should be zero jitter, based on the timing information that was given
  // for each RTP packet.
  EXPECT_EQ(RtpTimeDelta(), report.jitter);
}

TEST(PacketReceiveStatsTrackerTest, WhenReceivingAboutHalfThePackets) {
  constexpr uint16_t kFirstSequenceNumber = 3;
  constexpr RtpTimeTicks kFirstRtpTimestamp =
      RtpTimeTicks() + RtpTimeDelta::FromTicks(99);
  constexpr auto kFirstArrivalTime = Clock::time_point() + seconds(8888);

  PacketReceiveStatsTracker tracker(kRtpVideoTimebase);

  // Record 10 packet arrivals whose sequence numbers step by 2, which should
  // indicate half of the packets didn't arrive.
  //
  // Ten arrived: 1, 3, 5, 7, 9, 11, 13, 15, 17, 19
  // Nine inferred missing: 2, 4, 6, 8, 10, 12, 14, 16, 18
  for (int i = 0; i < 10; ++i) {
    tracker.OnReceivedValidRtpPacket(
        kFirstSequenceNumber + (i * 2 + 1),
        kFirstRtpTimestamp + RtpTimeDelta::FromTicks(kRtpVideoTimebase) * i,
        kFirstArrivalTime + seconds(i));
  }

  RtcpReportBlock report = GetSentinel();
  tracker.PopulateNextReport(&report);
  EXPECT_FIELDS_POPULATED(report);
  EXPECT_EQ(121, report.packet_fraction_lost_numerator);
  EXPECT_EQ(9, report.cumulative_packets_lost);
  EXPECT_EQ(uint32_t{22}, report.extended_high_sequence_number);
  // There should be zero jitter, based on the timing information that was given
  // for each RTP packet.
  EXPECT_EQ(RtpTimeDelta(), report.jitter);
}

TEST(PacketReceiveStatsTrackerTest, ComputesJitterCorrectly) {
  constexpr uint16_t kFirstSequenceNumber = 3;
  constexpr RtpTimeTicks kFirstRtpTimestamp =
      RtpTimeTicks() + RtpTimeDelta::FromTicks(99);
  constexpr auto kFirstArrivalTime = Clock::time_point() + seconds(8888);

  // Record 100 packet arrivals, one second apart, where each packet's RTP
  // timestamps are progressing 2 seconds forward. Thus, the jitter calculation
  // should gradually converge towards a difference of one second.
  constexpr auto kTrueJitter = Clock::to_duration(seconds(1));
  PacketReceiveStatsTracker tracker(kRtpVideoTimebase);
  Clock::duration last_diff = Clock::duration::max();
  for (int i = 0; i < 100; ++i) {
    tracker.OnReceivedValidRtpPacket(
        kFirstSequenceNumber + i,
        kFirstRtpTimestamp +
            RtpTimeDelta::FromTicks(kRtpVideoTimebase) * (i * 2),
        kFirstArrivalTime + seconds(i));

    // Expect that the jitter is becoming closer to the actual value in each
    // iteration.
    RtcpReportBlock report;
    tracker.PopulateNextReport(&report);
    const auto diff = kTrueJitter - report.jitter.ToDuration<Clock::duration>(
                                        kRtpVideoTimebase);
    EXPECT_LT(diff, last_diff);
    last_diff = diff;
  }

  // Because the jitter calculation is a weighted moving average, and also
  // because the timebase has to be converted here, the metric might not ever
  // become exactly kTrueJitter. Ensure that it has converged reasonably close
  // to that value.
  RtcpReportBlock report;
  tracker.PopulateNextReport(&report);
  const auto diff = kTrueJitter - report.jitter.ToDuration<Clock::duration>(
                                      kRtpVideoTimebase);
  constexpr auto kMaxDiffAtEnd = Clock::to_duration(milliseconds(2));
  EXPECT_NEAR(0, diff.count(), kMaxDiffAtEnd.count());
}

}  // namespace
}  // namespace cast
}  // namespace openscreen
