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

#include "components/gcm_driver/crypto/encryption_header_parsers.h"

#include <stddef.h>
#include <stdint.h>

#include <array>

#include "base/compiler_specific.h"
#include "base/strings/string_number_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace gcm {

namespace {

const uint64_t kDefaultRecordSize = 4096;

TEST(EncryptionHeaderParsersTest, ParseValidEncryptionHeaders) {
  struct ExpectedResults {
    const char* const header;
    const char* const parsed_keyid;
    const char* const parsed_salt;
    uint64_t parsed_rs;
  };
  auto expected_results = std::to_array<ExpectedResults>({
      {"keyid=foo;salt=c2l4dGVlbmNvb2xieXRlcw;rs=1024", "foo",
       "sixteencoolbytes", 1024},
      {"keyid=foo; salt=c2l4dGVlbmNvb2xieXRlcw; rs=1024", "foo",
       "sixteencoolbytes", 1024},
      {"KEYID=foo;SALT=c2l4dGVlbmNvb2xieXRlcw;RS=1024", "foo",
       "sixteencoolbytes", 1024},
      {" keyid = foo ; salt = c2l4dGVlbmNvb2xieXRlcw ; rs = 1024 ", "foo",
       "sixteencoolbytes", 1024},
      {"keyid=foo", "foo", "", kDefaultRecordSize},
      {"keyid=foo;", "foo", "", kDefaultRecordSize},
      {"keyid=\"foo\"", "foo", "", kDefaultRecordSize},
      {"salt=c2l4dGVlbmNvb2xieXRlcw", "", "sixteencoolbytes",
       kDefaultRecordSize},
      {"rs=2048", "", "", 2048},
      {"keyid=foo;someothervalue=1;rs=42", "foo", "", 42},
  });

  for (size_t i = 0; i < std::size(expected_results); i++) {
    SCOPED_TRACE(i);

    std::string header(expected_results[i].header);

    EncryptionHeaderIterator iterator(header.begin(), header.end());
    ASSERT_TRUE(iterator.GetNext());

    EXPECT_EQ(expected_results[i].parsed_keyid, iterator.keyid());
    EXPECT_EQ(expected_results[i].parsed_salt, iterator.salt());
    EXPECT_EQ(expected_results[i].parsed_rs, iterator.rs());

    EXPECT_FALSE(iterator.GetNext());
  }
}

TEST(EncryptionHeaderParsersTest, ParseValidMultiValueEncryptionHeaders) {
  const size_t kNumberOfValues = 2u;

  struct ExpectedResults {
    const char* const header;
    struct ParsedValues {
      const char* const keyid;
      const char* const salt;
      uint64_t rs;
    };
    std::array<ParsedValues, kNumberOfValues> parsed_values;
  };
  auto expected_results = std::to_array<ExpectedResults>({
      {"keyid=foo;salt=c2l4dGVlbmNvb2xieXRlcw;rs=1024,keyid=foo;salt=c2l4dGVlbm"
       "Nvb2xieXRlcw;rs=1024",
       {{{"foo", "sixteencoolbytes", 1024}, {"foo", "sixteencoolbytes", 1024}}}},
      {"keyid=foo,salt=c2l4dGVlbmNvb2xieXRlcw;rs=1024",
       {{{"foo", "", kDefaultRecordSize}, {"", "sixteencoolbytes", 1024}}}},
      {"keyid=foo,keyid=bar;salt=c2l4dGVlbmNvb2xieXRlcw;rs=1024",
       {{{"foo", "", kDefaultRecordSize}, {"bar", "sixteencoolbytes", 1024}}}},
      {"keyid=\"foo,keyid=bar\",salt=c2l4dGVlbmNvb2xieXRlcw",
       {{{"foo,keyid=bar", "", kDefaultRecordSize},
         {"", "sixteencoolbytes", kDefaultRecordSize}}}},
  });

  for (size_t i = 0; i < std::size(expected_results); i++) {
    SCOPED_TRACE(i);

    std::string header(expected_results[i].header);

    EncryptionHeaderIterator iterator(header.begin(), header.end());
    for (size_t j = 0; j < kNumberOfValues; ++j) {
      ASSERT_TRUE(iterator.GetNext());

      EXPECT_EQ(expected_results[i].parsed_values[j].keyid, iterator.keyid());
      EXPECT_EQ(expected_results[i].parsed_values[j].salt, iterator.salt());
      EXPECT_EQ(expected_results[i].parsed_values[j].rs, iterator.rs());
    }

    EXPECT_FALSE(iterator.GetNext());
  }
}

TEST(EncryptionHeaderParsersTest, ParseInvalidEncryptionHeaders) {
  const auto expected_failures = std::to_array<const char*>({
      // Values in the name-value pairs are not optional.
      "keyid",
      "keyid=",
      "keyid=foo;keyid",
      "salt",
      "salt=",
      "rs",
      "rs=",

      // Supplying the same name multiple times in the same value is invalid.
      "keyid=foo;keyid=bar",
      "keyid=foo;bar=baz;keyid=qux",

      // The salt must be a URL-safe base64 decodable string.
      "salt=YmV/2ZXJ-sMDA",
      "salt=dHdlbHZlY29vbGJ5dGVz=====",
      "salt=c2l4dGVlbmNvb2xieXRlcw;salt=123$xyz",
      "salt=123$xyz",

      // The record size must be a positive decimal integer greater than one
      // that does not start with a plus.
      "rs=0",
      "rs=0x13",
      "rs=1",
      "rs=-1",
      "rs=+5",
      "rs=99999999999999999999999999999999",
      "rs=foobar",
  });

  const auto expected_failures_second_iter = std::to_array<const char*>({
      // Valid first field, missing value in the second field.
      "keyid=foo,novaluekey",

      // Valid first field, undecodable salt in the second field.
      "salt=c2l4dGVlbmNvb2xieXRlcw,salt=123$xyz",

      // Valid first field, invalid record size in the second field.
      "rs=2,rs=0",
  });

  for (size_t i = 0; i < std::size(expected_failures); i++) {
    SCOPED_TRACE(i);

    std::string header(expected_failures[i]);

    EncryptionHeaderIterator iterator(header.begin(), header.end());
    EXPECT_FALSE(iterator.GetNext());
  }

  for (size_t i = 0; i < std::size(expected_failures_second_iter); i++) {
    SCOPED_TRACE(i);

    std::string header(expected_failures_second_iter[i]);

    EncryptionHeaderIterator iterator(header.begin(), header.end());
    EXPECT_TRUE(iterator.GetNext());
    EXPECT_FALSE(iterator.GetNext());
  }
}

TEST(EncryptionHeaderParsersTest, ParseValidCryptoKeyHeaders) {
  struct ExpectedResults {
    const char* const header;
    const char* const parsed_keyid;
    const char* const parsed_aesgcm128;
    const char* const parsed_dh;
  };
  auto expected_results = std::to_array<ExpectedResults>({
      {"keyid=foo;aesgcm128=c2l4dGVlbmNvb2xieXRlcw;dh=dHdlbHZlY29vbGJ5dGVz",
       "foo", "sixteencoolbytes", "twelvecoolbytes"},
      {"keyid=foo; aesgcm128=c2l4dGVlbmNvb2xieXRlcw; dh=dHdlbHZlY29vbGJ5dGVz",
       "foo", "sixteencoolbytes", "twelvecoolbytes"},
      {"keyid = foo ; aesgcm128 = c2l4dGVlbmNvb2xieXRlcw ; dh = "
       "dHdlbHZlY29vbGJ5"
       "dGVz ",
       "foo", "sixteencoolbytes", "twelvecoolbytes"},
      {"KEYID=foo;AESGCM128=c2l4dGVlbmNvb2xieXRlcw;DH=dHdlbHZlY29vbGJ5dGVz",
       "foo", "sixteencoolbytes", "twelvecoolbytes"},
      {"keyid=foo", "foo", "", ""},
      {"aesgcm128=c2l4dGVlbmNvb2xieXRlcw", "", "sixteencoolbytes", ""},
      {"aesgcm128=\"c2l4dGVlbmNvb2xieXRlcw\"", "", "sixteencoolbytes", ""},
      {"dh=dHdlbHZlY29vbGJ5dGVz", "", "", "twelvecoolbytes"},
      {"keyid=foo;someothervalue=bar;aesgcm128=dHdlbHZlY29vbGJ5dGVz", "foo",
       "twelvecoolbytes", ""},
  });

  for (size_t i = 0; i < std::size(expected_results); i++) {
    SCOPED_TRACE(i);

    std::string header(expected_results[i].header);

    CryptoKeyHeaderIterator iterator(header.begin(), header.end());
    ASSERT_TRUE(iterator.GetNext());

    EXPECT_EQ(expected_results[i].parsed_keyid, iterator.keyid());
    EXPECT_EQ(expected_results[i].parsed_aesgcm128, iterator.aesgcm128());
    EXPECT_EQ(expected_results[i].parsed_dh, iterator.dh());

    EXPECT_FALSE(iterator.GetNext());
  }
}

TEST(EncryptionHeaderParsersTest, ParseValidMultiValueCryptoKeyHeaders) {
  const size_t kNumberOfValues = 2u;

  struct ExpectedResults {
    const char* const header;
    struct ParsedValues {
      const char* const keyid;
      const char* const aesgcm128;
      const char* const dh;
    };
    std::array<ParsedValues, kNumberOfValues> parsed_values;
  };
  auto expected_results = std::to_array<ExpectedResults>({
      {"keyid=foo;aesgcm128=c2l4dGVlbmNvb2xieXRlcw;dh=dHdlbHZlY29vbGJ5dGVz,"
       "keyid=bar;aesgcm128=dHdlbHZlY29vbGJ5dGVz;dh=c2l4dGVlbmNvb2xieXRlcw",
       {{{"foo", "sixteencoolbytes", "twelvecoolbytes"},
         {"bar", "twelvecoolbytes", "sixteencoolbytes"}}}},
      {"keyid=foo,aesgcm128=c2l4dGVlbmNvb2xieXRlcw",
       {{{"foo", "", ""}, {"", "sixteencoolbytes", ""}}}},
      {"keyid=foo,keyid=bar;dh=dHdlbHZlY29vbGJ5dGVz",
       {{{"foo", "", ""}, {"bar", "", "twelvecoolbytes"}}}},
      {"keyid=\"foo,keyid=bar\",aesgcm128=c2l4dGVlbmNvb2xieXRlcw",
       {{{"foo,keyid=bar", "", ""}, {"", "sixteencoolbytes", ""}}}},
  });

  for (size_t i = 0; i < std::size(expected_results); i++) {
    SCOPED_TRACE(i);

    std::string header(expected_results[i].header);

    CryptoKeyHeaderIterator iterator(header.begin(), header.end());
    for (size_t j = 0; j < kNumberOfValues; ++j) {
      ASSERT_TRUE(iterator.GetNext());

      EXPECT_EQ(expected_results[i].parsed_values[j].keyid, iterator.keyid());
      EXPECT_EQ(expected_results[i].parsed_values[j].aesgcm128,
                iterator.aesgcm128());
      EXPECT_EQ(expected_results[i].parsed_values[j].dh, iterator.dh());
    }

    EXPECT_FALSE(iterator.GetNext());
  }
}

TEST(EncryptionHeaderParsersTest, ParseInvalidCryptoKeyHeaders) {
  const auto expected_failures = std::to_array<const char*>({
      // Values in the name-value pairs are not optional.
      "keyid",
      "keyid=",
      "keyid=foo;keyid",
      "aesgcm128",
      "aesgcm128=",
      "dh",
      "dh=",

      // Supplying the same name multiple times in the same value is invalid.
      "keyid=foo;keyid=bar",
      "keyid=foo;bar=baz;keyid=qux",

      // The "aesgcm128" parameter must be a URL-safe base64 decodable string.
      "aesgcm128=123$xyz",
      "aesgcm128=foobar;aesgcm128=123$xyz",

      // The "dh" parameter must be a URL-safe base64 decodable string.
      "dh=YmV/2ZXJ-sMDA",
      "dh=dHdlbHZlY29vbGJ5dGVz=====",
      "dh=123$xyz",
  });

  const auto expected_failures_second_iter = std::to_array<const char*>({
      // Valid first field, missing value in the second field.
      "keyid=foo,novaluekey",

      // Valid first field, undecodable aesgcm128 value in the second field.
      "dh=dHdlbHZlY29vbGJ5dGVz,aesgcm128=123$xyz",
  });

  for (size_t i = 0; i < std::size(expected_failures); i++) {
    SCOPED_TRACE(i);

    std::string header(expected_failures[i]);

    CryptoKeyHeaderIterator iterator(header.begin(), header.end());
    EXPECT_FALSE(iterator.GetNext());
  }

  for (size_t i = 0; i < std::size(expected_failures_second_iter); i++) {
    SCOPED_TRACE(i);

    std::string header(expected_failures_second_iter[i]);

    CryptoKeyHeaderIterator iterator(header.begin(), header.end());
    EXPECT_TRUE(iterator.GetNext());
    EXPECT_FALSE(iterator.GetNext());
  }
}

TEST(EncryptionHeaderParsersTest, SixValueHeader) {
  const std::string header("keyid=0,keyid=1,keyid=2,keyid=3,keyid=4,keyid=5");

  EncryptionHeaderIterator encryption_iterator(header.begin(), header.end());
  CryptoKeyHeaderIterator crypto_key_iterator(header.begin(), header.end());

  for (size_t i = 0; i < 6; ++i) {
    SCOPED_TRACE(i);

    ASSERT_TRUE(encryption_iterator.GetNext());
    ASSERT_TRUE(crypto_key_iterator.GetNext());
  }

  EXPECT_FALSE(encryption_iterator.GetNext());
  EXPECT_FALSE(crypto_key_iterator.GetNext());
}

TEST(EncryptionHeaderParsersTest, InvalidHeadersResetOutput) {
  // Valid first field, invalid record size parameter in the second field.
  const std::string encryption_header(
      "keyid=foo;salt=c2l4dGVlbmNvb2xieXRlcw;rs=1024,rs=foobar");

  // Valid first field, undecodable aesgcm128 parameter in the second field.
  const std::string crypto_key_header(
      "keyid=foo;aesgcm128=c2l4dGVlbmNvb2xieXRlcw;dh=dHdlbHZlY29vbGJ5dGVz,"
      "aesgcm128=$$$");

  EncryptionHeaderIterator encryption_iterator(
      encryption_header.begin(), encryption_header.end());

  ASSERT_EQ(0u, encryption_iterator.keyid().size());
  ASSERT_EQ(0u, encryption_iterator.salt().size());
  ASSERT_EQ(4096u, encryption_iterator.rs());

  ASSERT_TRUE(encryption_iterator.GetNext());

  EXPECT_EQ("foo", encryption_iterator.keyid());
  EXPECT_EQ("sixteencoolbytes", encryption_iterator.salt());
  EXPECT_EQ(1024u, encryption_iterator.rs());

  ASSERT_FALSE(encryption_iterator.GetNext());

  EXPECT_EQ(0u, encryption_iterator.keyid().size());
  EXPECT_EQ(0u, encryption_iterator.salt().size());
  EXPECT_EQ(4096u, encryption_iterator.rs());

  CryptoKeyHeaderIterator crypto_key_iterator(
      crypto_key_header.begin(), crypto_key_header.end());

  ASSERT_EQ(0u, crypto_key_iterator.keyid().size());
  ASSERT_EQ(0u, crypto_key_iterator.aesgcm128().size());
  ASSERT_EQ(0u, crypto_key_iterator.dh().size());

  ASSERT_TRUE(crypto_key_iterator.GetNext());

  EXPECT_EQ("foo", crypto_key_iterator.keyid());
  EXPECT_EQ("sixteencoolbytes", crypto_key_iterator.aesgcm128());
  EXPECT_EQ("twelvecoolbytes", crypto_key_iterator.dh());

  ASSERT_FALSE(crypto_key_iterator.GetNext());

  EXPECT_EQ(0u, crypto_key_iterator.keyid().size());
  EXPECT_EQ(0u, crypto_key_iterator.aesgcm128().size());
  EXPECT_EQ(0u, crypto_key_iterator.dh().size());
}

}  // namespace

}  // namespace gcm
