/*
 *  Copyright (c) 2020 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/rtp_video_layers_allocation_extension.h"

#include "api/video/video_layers_allocation.h"
#include "rtc_base/bit_buffer.h"
#include "rtc_base/buffer.h"
#include "test/gmock.h"

namespace webrtc {
namespace {

TEST(RtpVideoLayersAllocationExtension, WriteEmptyLayersAllocationReturnsTrue) {
  VideoLayersAllocation written_allocation;
  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));
}

TEST(RtpVideoLayersAllocationExtension,
     CanWriteAndParseLayersAllocationWithZeroSpatialLayers) {
  // We require the resolution_and_frame_rate_is_valid to be set to true in
  // order to send an "empty" allocation.
  VideoLayersAllocation written_allocation;
  written_allocation.resolution_and_frame_rate_is_valid = true;
  written_allocation.rtp_stream_index = 0;

  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));

  VideoLayersAllocation parsed_allocation;
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation));
  EXPECT_EQ(written_allocation, parsed_allocation);
}

TEST(RtpVideoLayersAllocationExtension,
     CanWriteAndParse2SpatialWith2TemporalLayers) {
  VideoLayersAllocation written_allocation;
  written_allocation.rtp_stream_index = 1;
  written_allocation.active_spatial_layers = {
      {
          /*rtp_stream_index*/ 0,
          /*spatial_id*/ 0,
          /*target_bitrate_per_temporal_layer*/
          {DataRate::KilobitsPerSec(25), DataRate::KilobitsPerSec(50)},
          /*width*/ 0,
          /*height*/ 0,
          /*frame_rate_fps*/ 0,
      },
      {
          /*rtp_stream_index*/ 1,
          /*spatial_id*/ 0,
          /*target_bitrate_per_temporal_layer*/
          {DataRate::KilobitsPerSec(100), DataRate::KilobitsPerSec(200)},
          /*width*/ 0,
          /*height*/ 0,
          /*frame_rate_fps*/ 0,
      },
  };
  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));
  VideoLayersAllocation parsed_allocation;
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation));
  EXPECT_EQ(written_allocation, parsed_allocation);
}

TEST(RtpVideoLayersAllocationExtension,
     CanWriteAndParseAllocationWithDifferentNumerOfSpatialLayers) {
  VideoLayersAllocation written_allocation;
  written_allocation.rtp_stream_index = 1;
  written_allocation.active_spatial_layers = {
      {/*rtp_stream_index*/ 0,
       /*spatial_id*/ 0,
       /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)},
       /*width*/ 0,
       /*height*/ 0,
       /*frame_rate_fps*/ 0},
      {/*rtp_stream_index*/ 1,
       /*spatial_id*/ 0,
       /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(100)},
       /*width*/ 0,
       /*height*/ 0,
       /*frame_rate_fps*/ 0},
      {/*rtp_stream_index*/ 1,
       /*spatial_id*/ 1,
       /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)},
       /*width*/ 0,
       /*height*/ 0,
       /*frame_rate_fps*/ 0},
  };
  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));
  VideoLayersAllocation parsed_allocation;
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation));
  EXPECT_EQ(written_allocation, parsed_allocation);
}

TEST(RtpVideoLayersAllocationExtension,
     CanWriteAndParseAllocationWithSkippedLowerSpatialLayer) {
  VideoLayersAllocation written_allocation;
  written_allocation.rtp_stream_index = 1;
  written_allocation.active_spatial_layers = {
      {/*rtp_stream_index*/ 0,
       /*spatial_id*/ 0,
       /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)},
       /*width*/ 0,
       /*height*/ 0,
       /*frame_rate_fps*/ 0},
      {/*rtp_stream_index*/ 1,
       /*spatial_id*/ 1,
       /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)},
       /*width*/ 0,
       /*height*/ 0,
       /*frame_rate_fps*/ 0},
  };
  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));
  VideoLayersAllocation parsed_allocation;
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation));
  EXPECT_EQ(written_allocation, parsed_allocation);
}

TEST(RtpVideoLayersAllocationExtension,
     CanWriteAndParseAllocationWithSkippedRtpStreamIds) {
  VideoLayersAllocation written_allocation;
  written_allocation.rtp_stream_index = 2;
  written_allocation.active_spatial_layers = {
      {/*rtp_stream_index*/ 0,
       /*spatial_id*/ 0,
       /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)},
       /*width*/ 0,
       /*height*/ 0,
       /*frame_rate_fps*/ 0},
      {/*rtp_stream_index*/ 2,
       /*spatial_id*/ 0,
       /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)},
       /*width*/ 0,
       /*height*/ 0,
       /*frame_rate_fps*/ 0},
  };
  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));
  VideoLayersAllocation parsed_allocation;
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation));
  EXPECT_EQ(written_allocation, parsed_allocation);
}

TEST(RtpVideoLayersAllocationExtension,
     CanWriteAndParseAllocationWithDifferentNumerOfTemporalLayers) {
  VideoLayersAllocation written_allocation;
  written_allocation.rtp_stream_index = 1;
  written_allocation.active_spatial_layers = {
      {
          /*rtp_stream_index*/ 0,
          /*spatial_id*/ 0,
          /*target_bitrate_per_temporal_layer*/
          {DataRate::KilobitsPerSec(25), DataRate::KilobitsPerSec(50)},
          /*width*/ 0,
          /*height*/ 0,
          /*frame_rate_fps*/ 0,
      },
      {
          /*rtp_stream_index*/ 1,
          /*spatial_id*/ 0,
          /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(100)},
          /*width*/ 0,
          /*height*/ 0,
          /*frame_rate_fps*/ 0,
      },
  };
  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));
  VideoLayersAllocation parsed_allocation;
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation));
  EXPECT_EQ(written_allocation, parsed_allocation);
}

TEST(RtpVideoLayersAllocationExtension,
     CanWriteAndParseAllocationWithResolution) {
  VideoLayersAllocation written_allocation;
  written_allocation.rtp_stream_index = 1;
  written_allocation.resolution_and_frame_rate_is_valid = true;
  written_allocation.active_spatial_layers = {
      {
          /*rtp_stream_index*/ 0,
          /*spatial_id*/ 0,
          /*target_bitrate_per_temporal_layer*/
          {DataRate::KilobitsPerSec(25), DataRate::KilobitsPerSec(50)},
          /*width*/ 320,
          /*height*/ 240,
          /*frame_rate_fps*/ 8,
      },
      {
          /*rtp_stream_index*/ 1,
          /*spatial_id*/ 1,
          /*target_bitrate_per_temporal_layer*/
          {DataRate::KilobitsPerSec(100), DataRate::KilobitsPerSec(200)},
          /*width*/ 640,
          /*height*/ 320,
          /*frame_rate_fps*/ 30,
      },
  };

  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));
  VideoLayersAllocation parsed_allocation;
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation));
  EXPECT_EQ(written_allocation, parsed_allocation);
}

TEST(RtpVideoLayersAllocationExtension,
     WriteEmptyAllocationCanHaveAnyRtpStreamIndex) {
  VideoLayersAllocation written_allocation;
  written_allocation.rtp_stream_index = 1;
  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  EXPECT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));
}

TEST(RtpVideoLayersAllocationExtension, DiscardsOverLargeDataRate) {
  constexpr uint8_t buffer[] = {0x4b, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff,
                                0xff, 0xcb, 0x78, 0xeb, 0x8d, 0xb5, 0x31};
  VideoLayersAllocation allocation;
  EXPECT_FALSE(RtpVideoLayersAllocationExtension::Parse(buffer, &allocation));
}

TEST(RtpVideoLayersAllocationExtension, DiscardsInvalidHeight) {
  VideoLayersAllocation written_allocation;
  written_allocation.rtp_stream_index = 0;
  written_allocation.resolution_and_frame_rate_is_valid = true;
  written_allocation.active_spatial_layers = {
      {
          /*rtp_stream_index*/ 0,
          /*spatial_id*/ 0,
          /*target_bitrate_per_temporal_layer*/
          {DataRate::KilobitsPerSec(25), DataRate::KilobitsPerSec(50)},
          /*width*/ 320,
          /*height*/ 240,
          /*frame_rate_fps*/ 8,
      },
  };
  rtc::Buffer buffer(
      RtpVideoLayersAllocationExtension::ValueSize(written_allocation));
  ASSERT_TRUE(
      RtpVideoLayersAllocationExtension::Write(buffer, written_allocation));

  // Modify the height to be invalid.
  buffer[buffer.size() - 3] = 0xff;
  buffer[buffer.size() - 2] = 0xff;
  VideoLayersAllocation allocation;
  EXPECT_FALSE(RtpVideoLayersAllocationExtension::Parse(buffer, &allocation));
}

}  // namespace
}  // namespace webrtc
