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

#include "media/formats/webm/webm_content_encodings_client.h"

#include <stdint.h>

#include <string>

#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/mock_media_log.h"
#include "media/formats/webm/webm_constants.h"
#include "media/formats/webm/webm_parser.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::StrictMock;

namespace media {

// Matchers for verifying common media log entry strings.
MATCHER(MissingContentEncoding, "") {
  return CONTAINS_STRING(arg, "Missing ContentEncoding.");
}

MATCHER(UnexpectedContentEncodingOrder, "") {
  return CONTAINS_STRING(arg, "Unexpected ContentEncodingOrder.");
}

MATCHER(UnexpectedContentEncodingScope, "") {
  return CONTAINS_STRING(arg, "Unexpected ContentEncodingScope.");
}

MATCHER(ContentCompressionNotSupported, "") {
  return CONTAINS_STRING(arg, "ContentCompression not supported.");
}

MATCHER(MissingContentEncryption, "") {
  return CONTAINS_STRING(
      arg,
      "ContentEncodingType is encryption but ContentEncryption is missing.");
}

MATCHER_P(UnexpectedContentEncAlgo, algo, "") {
  return CONTAINS_STRING(
      arg, "Unexpected ContentEncAlgo " + base::NumberToString(algo) + ".");
}

class WebMContentEncodingsClientTest : public testing::Test {
 public:
  WebMContentEncodingsClientTest()
      : client_(&media_log_), parser_(kWebMIdContentEncodings, &client_) {}

  void ParseAndExpectToFail(base::span<const uint8_t> buf) {
    int result = parser_.Parse(buf);
    EXPECT_EQ(-1, result);
  }

 protected:
  StrictMock<MockMediaLog> media_log_;
  WebMContentEncodingsClient client_;
  WebMListParser parser_;
};

TEST_F(WebMContentEncodingsClientTest, EmptyContentEncodings) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0x80,  // ContentEncodings (size = 0)
  };
  EXPECT_MEDIA_LOG(MissingContentEncoding());
  ParseAndExpectToFail(kContentEncodings);
}

TEST_F(WebMContentEncodingsClientTest, EmptyContentEncoding) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0x83,  // ContentEncodings (size = 3)
      0x63, 0x40, 0x80,  //   ContentEncoding (size = 0)
  };
  ParseAndExpectToFail(kContentEncodings);
}

TEST_F(WebMContentEncodingsClientTest, SingleContentEncoding) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0xA1,        // ContentEncodings (size = 33)
      0x62, 0x40, 0x9e,        //   ContentEncoding (size = 30)
      0x50, 0x31, 0x81, 0x00,  //     ContentEncodingOrder (size = 1)
      0x50, 0x32, 0x81, 0x01,  //     ContentEncodingScope (size = 1)
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
      0x50, 0x35, 0x8F,        //     ContentEncryption (size = 15)
      0x47, 0xE1, 0x81, 0x05,  //       ContentEncAlgo (size = 1)
      0x47, 0xE2, 0x88,        //       ContentEncKeyID (size = 8)
      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
  };
  int size = sizeof(kContentEncodings);

  int result = parser_.Parse(kContentEncodings);
  ASSERT_EQ(size, result);
  const ContentEncodings& content_encodings = client_.content_encodings();

  ASSERT_EQ(1u, content_encodings.size());
  ASSERT_TRUE(content_encodings[0]);
  EXPECT_EQ(0, content_encodings[0]->order());
  EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
            content_encodings[0]->scope());
  EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
  EXPECT_EQ(ContentEncoding::kEncAlgoAes,
            content_encodings[0]->encryption_algo());
  EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size());
}

TEST_F(WebMContentEncodingsClientTest, MultipleContentEncoding) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0xC2,        // ContentEncodings (size = 66)
      0x62, 0x40, 0x9e,        //   ContentEncoding (size = 30)
      0x50, 0x31, 0x81, 0x00,  //     ContentEncodingOrder (size = 1)
      0x50, 0x32, 0x81, 0x03,  //     ContentEncodingScope (size = 1)
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
      0x50, 0x35, 0x8F,        //     ContentEncryption (size = 15)
      0x47, 0xE1, 0x81, 0x05,  //       ContentEncAlgo (size = 1)
      0x47, 0xE2, 0x88,        //       ContentEncKeyID (size = 8)
      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
      0x62, 0x40, 0x9e,        //   ContentEncoding (size = 30)
      0x50, 0x31, 0x81, 0x01,  //     ContentEncodingOrder (size = 1)
      0x50, 0x32, 0x81, 0x03,  //     ContentEncodingScope (size = 1)
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
      0x50, 0x35, 0x8F,        //     ContentEncryption (size = 15)
      0x47, 0xE1, 0x81, 0x01,  //       ContentEncAlgo (size = 1)
      0x47, 0xE2, 0x88,        //       ContentEncKeyID (size = 8)
      0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
  };
  int size = sizeof(kContentEncodings);

  int result = parser_.Parse(kContentEncodings);
  ASSERT_EQ(size, result);
  const ContentEncodings& content_encodings = client_.content_encodings();
  ASSERT_EQ(2u, content_encodings.size());

  for (int i = 0; i < 2; ++i) {
    ASSERT_TRUE(content_encodings[i]);
    EXPECT_EQ(i, content_encodings[i]->order());
    EXPECT_EQ(ContentEncoding::kScopeAllFrameContents |
              ContentEncoding::kScopeTrackPrivateData,
              content_encodings[i]->scope());
    EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[i]->type());
    EXPECT_EQ(!i ? ContentEncoding::kEncAlgoAes : ContentEncoding::kEncAlgoDes,
              content_encodings[i]->encryption_algo());
    EXPECT_EQ(8u, content_encodings[i]->encryption_key_id().size());
  }
}

TEST_F(WebMContentEncodingsClientTest, DefaultValues) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0x8A,        // ContentEncodings (size = 10)
      0x62, 0x40, 0x87,        //   ContentEncoding (size = 7)
                               //     ContentEncodingOrder missing
                               //     ContentEncodingScope missing
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
      0x50, 0x35, 0x80,        //     ContentEncryption (size = 0)
                               //     ContentEncAlgo missing
  };
  int size = sizeof(kContentEncodings);

  int result = parser_.Parse(kContentEncodings);
  ASSERT_EQ(size, result);
  const ContentEncodings& content_encodings = client_.content_encodings();

  ASSERT_EQ(1u, content_encodings.size());
  ASSERT_TRUE(content_encodings[0]);
  EXPECT_EQ(0, content_encodings[0]->order());
  EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
            content_encodings[0]->scope());
  EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
  EXPECT_EQ(ContentEncoding::kEncAlgoNotEncrypted,
            content_encodings[0]->encryption_algo());
  EXPECT_TRUE(content_encodings[0]->encryption_key_id().empty());
}

TEST_F(WebMContentEncodingsClientTest, ContentEncodingsClientReuse) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0xA1,        // ContentEncodings (size = 33)
      0x62, 0x40, 0x9e,        //   ContentEncoding (size = 30)
      0x50, 0x31, 0x81, 0x00,  //     ContentEncodingOrder (size = 1)
      0x50, 0x32, 0x81, 0x01,  //     ContentEncodingScope (size = 1)
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
      0x50, 0x35, 0x8F,        //     ContentEncryption (size = 15)
      0x47, 0xE1, 0x81, 0x05,  //       ContentEncAlgo (size = 1)
      0x47, 0xE2, 0x88,        //       ContentEncKeyID (size = 8)
      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
  };
  int size = sizeof(kContentEncodings);

  // Parse for the first time.
  int result = parser_.Parse(kContentEncodings);
  ASSERT_EQ(size, result);

  // Parse again.
  parser_.Reset();
  result = parser_.Parse(kContentEncodings);
  ASSERT_EQ(size, result);
  const ContentEncodings& content_encodings = client_.content_encodings();

  ASSERT_EQ(1u, content_encodings.size());
  ASSERT_TRUE(content_encodings[0]);
  EXPECT_EQ(0, content_encodings[0]->order());
  EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
            content_encodings[0]->scope());
  EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
  EXPECT_EQ(ContentEncoding::kEncAlgoAes,
            content_encodings[0]->encryption_algo());
  EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size());
}

TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingOrder) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0x8E,        // ContentEncodings (size = 14)
      0x62, 0x40, 0x8B,        //   ContentEncoding (size = 11)
      0x50, 0x31, 0x81, 0xEE,  //     ContentEncodingOrder (size = 1), invalid
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
      0x50, 0x35, 0x80,        //     ContentEncryption (size = 0)
  };
  EXPECT_MEDIA_LOG(UnexpectedContentEncodingOrder());
  ParseAndExpectToFail(kContentEncodings);
}

TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingScope) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0x8E,        // ContentEncodings (size = 14)
      0x62, 0x40, 0x8B,        //   ContentEncoding (size = 11)
      0x50, 0x32, 0x81, 0xEE,  //     ContentEncodingScope (size = 1), invalid
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
      0x50, 0x35, 0x80,        //     ContentEncryption (size = 0)
  };
  EXPECT_MEDIA_LOG(UnexpectedContentEncodingScope());
  ParseAndExpectToFail(kContentEncodings);
}

TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingType) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0x8E,        // ContentEncodings (size = 14)
      0x62, 0x40, 0x8B,        //   ContentEncoding (size = 11)
      0x50, 0x33, 0x81, 0x00,  //     ContentEncodingType (size = 1), invalid
      0x50, 0x35, 0x80,        //     ContentEncryption (size = 0)
  };
  EXPECT_MEDIA_LOG(ContentCompressionNotSupported());
  ParseAndExpectToFail(kContentEncodings);
}

// ContentEncodingType is encryption but no ContentEncryption present.
TEST_F(WebMContentEncodingsClientTest, MissingContentEncryption) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0x87,        // ContentEncodings (size = 7)
      0x62, 0x40, 0x84,        //   ContentEncoding (size = 4)
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
                               //     ContentEncryption missing
  };
  EXPECT_MEDIA_LOG(MissingContentEncryption());
  ParseAndExpectToFail(kContentEncodings);
}

TEST_F(WebMContentEncodingsClientTest, InvalidContentEncAlgo) {
  const uint8_t kContentEncodings[] = {
      0x6D, 0x80, 0x99,        // ContentEncodings (size = 25)
      0x62, 0x40, 0x96,        //   ContentEncoding (size = 22)
      0x50, 0x33, 0x81, 0x01,  //     ContentEncodingType (size = 1)
      0x50, 0x35, 0x8F,        //     ContentEncryption (size = 15)
      0x47, 0xE1, 0x81, 0xEE,  //       ContentEncAlgo (size = 1), invalid
      0x47, 0xE2, 0x88,        //       ContentEncKeyID (size = 8)
      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
  };
  EXPECT_MEDIA_LOG(UnexpectedContentEncAlgo(0xEE));
  ParseAndExpectToFail(kContentEncodings);
}

}  // namespace media
