/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/ssl/OpenSSLHash.h>

#include <folly/io/IOBufQueue.h>
#include <folly/portability/GTest.h>

using namespace std;
using namespace folly;
using namespace folly::ssl;

namespace {

class OpenSSLHashTest : public testing::Test {};
} // namespace

TEST_F(OpenSSLHashTest, sha256) {
  IOBuf buf;
  buf.prependChain(IOBuf::wrapBuffer(ByteRange(StringPiece("foo"))));
  buf.prependChain(IOBuf::wrapBuffer(ByteRange(StringPiece("bar"))));
  EXPECT_EQ(3, buf.countChainElements());
  EXPECT_EQ(6, buf.computeChainDataLength());

  auto expected = vector<uint8_t>(32);
  auto combined = ByteRange(StringPiece("foobar"));
  SHA256(combined.data(), combined.size(), expected.data());

  auto out = vector<uint8_t>(32);
  OpenSSLHash::sha256(range(out), buf);
  EXPECT_EQ(expected, out);
}

TEST_F(OpenSSLHashTest, sha256_hashcopy) {
  std::array<uint8_t, 32> expected, actual1, actual2;
  constexpr StringPiece data{"foobar"};

  OpenSSLHash::hash(range(expected), EVP_sha256(), data);

  OpenSSLHash::Digest digest;
  digest.hash_init(EVP_sha256());
  digest.hash_update(ByteRange(data));

  OpenSSLHash::Digest copy1(digest); // copy constructor
  OpenSSLHash::Digest copy2 = digest; // copy assignment operator

  copy1.hash_final(range(actual1));
  copy2.hash_final(range(actual2));

  EXPECT_EQ(expected, actual1);
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, sha256_hashcopy_self) {
  std::array<uint8_t, 32> expected, actual;
  constexpr StringPiece data{"foobar"};

  OpenSSLHash::hash(range(expected), EVP_sha256(), data);

  OpenSSLHash::Digest digest;
  digest.hash_init(EVP_sha256());
  digest.hash_update(ByteRange(data));

  OpenSSLHash::Digest* ptr = &digest;
  digest = *ptr; // test copy of an object to itself

  digest.hash_final(range(actual));

  EXPECT_EQ(expected, actual);
}

TEST_F(OpenSSLHashTest, sha256_hashcopy_from_default_constructed) {
  std::array<uint8_t, 32> expected, actual1, actual2;
  constexpr StringPiece data{"foobar"};

  OpenSSLHash::hash(range(expected), EVP_sha256(), data);

  OpenSSLHash::Digest digest;
  OpenSSLHash::Digest copy1(digest); // copy constructor
  OpenSSLHash::Digest copy2 = digest; // copy assignment operator

  copy1.hash_init(EVP_sha256());
  copy1.hash_update(ByteRange(data));
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  copy2.hash_init(EVP_sha256());
  copy2.hash_update(ByteRange(data));
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, sha256_hashmove) {
  std::array<uint8_t, 32> expected, actual1, actual2;
  constexpr StringPiece data{"foobar"};

  OpenSSLHash::hash(range(expected), EVP_sha256(), data);

  OpenSSLHash::Digest digest;
  digest.hash_init(EVP_sha256());
  digest.hash_update(ByteRange(data));
  OpenSSLHash::Digest copy1(std::move(digest)); // move constructor
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  digest = OpenSSLHash::Digest{}; // should be safe to reassign to moved object
  digest.hash_init(EVP_sha256());
  digest.hash_update(ByteRange(data));
  OpenSSLHash::Digest copy2 = std::move(digest); // move assignment operator
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, sha256_hashmove_self) {
  std::array<uint8_t, 32> expected, actual;
  constexpr StringPiece data{"foobar"};

  OpenSSLHash::hash(range(expected), EVP_sha256(), data);

  OpenSSLHash::Digest digest;
  digest.hash_init(EVP_sha256());
  digest.hash_update(ByteRange(data));

  OpenSSLHash::Digest* ptr = &digest;
  digest = std::move(*ptr); // test move of an object to itself

  digest.hash_final(range(actual));

  EXPECT_EQ(expected, actual);
}

TEST_F(OpenSSLHashTest, sha256_hashmove_from_default_constructed) {
  std::array<uint8_t, 32> expected, actual1, actual2;
  constexpr StringPiece data{"foobar"};

  OpenSSLHash::hash(range(expected), EVP_sha256(), data);

  OpenSSLHash::Digest digest1;
  OpenSSLHash::Digest copy1(std::move(digest1)); // move constructor
  OpenSSLHash::Digest digest2;
  OpenSSLHash::Digest copy2 = std::move(digest2); // move assignment operator

  copy1.hash_init(EVP_sha256());
  copy1.hash_update(ByteRange(data));
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  copy2.hash_init(EVP_sha256());
  copy2.hash_update(ByteRange(data));
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, sha256_hashcopy_intermediate) {
  std::array<uint8_t, 32> expected, actual1, actual2;
  constexpr StringPiece data1("foo");
  constexpr StringPiece data2("bar");

  OpenSSLHash::Digest digest;
  digest.hash_init(EVP_sha256());
  digest.hash_update(ByteRange(data1));

  OpenSSLHash::Digest copy1(digest); // copy constructor
  OpenSSLHash::Digest copy2 = digest; // copy assignment operator

  digest.hash_update(ByteRange(data2));
  digest.hash_final(range(expected));

  copy1.hash_update(ByteRange(data2));
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  copy2.hash_update(ByteRange(data2));
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, sha256_hashmove_intermediate) {
  std::array<uint8_t, 32> expected, actual1, actual2;
  constexpr StringPiece fulldata("foobar");
  constexpr StringPiece data1("foo");
  constexpr StringPiece data2("bar");

  OpenSSLHash::hash(range(expected), EVP_sha256(), fulldata);

  OpenSSLHash::Digest digest;
  digest.hash_init(EVP_sha256());
  digest.hash_update(ByteRange(data1));
  OpenSSLHash::Digest copy1(std::move(digest)); // move constructor
  copy1.hash_update(ByteRange(data2));
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  digest.hash_init(EVP_sha256()); // should be safe to re-init moved object
  digest.hash_update(ByteRange(data1));
  OpenSSLHash::Digest copy2 = std::move(digest); // move assignment operator
  copy2.hash_update(ByteRange(data2));
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);

  // Make sure it's safe to re-init moved object after move operator=()
  digest.hash_init(EVP_sha256());
}

TEST_F(OpenSSLHashTest, digest_update_without_init_throws) {
  OpenSSLHash::Digest digest;
  EXPECT_THROW(digest.hash_update(ByteRange{}), std::runtime_error);
}

TEST_F(OpenSSLHashTest, digest_final_without_init_throws) {
  OpenSSLHash::Digest digest;
  std::array<uint8_t, 32> out;
  EXPECT_THROW(digest.hash_final(range(out)), std::runtime_error);
}

TEST_F(OpenSSLHashTest, hmac_sha256) {
  auto key = ByteRange(StringPiece("qwerty"));

  IOBuf buf;
  buf.prependChain(IOBuf::wrapBuffer(ByteRange(StringPiece("foo"))));
  buf.prependChain(IOBuf::wrapBuffer(ByteRange(StringPiece("bar"))));
  EXPECT_EQ(3, buf.countChainElements());
  EXPECT_EQ(6, buf.computeChainDataLength());

  auto expected = vector<uint8_t>(32);
  auto combined = ByteRange(StringPiece("foobar"));
  HMAC(
      EVP_sha256(),
      key.data(),
      int(key.size()),
      combined.data(),
      combined.size(),
      expected.data(),
      nullptr);

  auto out = vector<uint8_t>(32);
  OpenSSLHash::hmac_sha256(range(out), key, buf);
  EXPECT_EQ(expected, out);
}

TEST_F(OpenSSLHashTest, hmac_sha256_hashcopy) {
  std::array<uint8_t, 32> expected, actual1, actual2;

  constexpr StringPiece data{"foobar"};
  constexpr StringPiece key{"qwerty"};

  OpenSSLHash::Hmac hmac;
  hmac.hash_init(EVP_sha256(), {key});
  hmac.hash_update({data});

  OpenSSLHash::Hmac copy1{hmac};
  OpenSSLHash::Hmac copy2 = hmac;

  hmac.hash_final(range(expected));
  copy1.hash_final(range(actual1));
  copy2.hash_final(range(actual2));

  EXPECT_EQ(expected, actual1);
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, hmac_sha256_hashmove) {
  std::array<uint8_t, 32> expected, actual1, actual2;

  constexpr StringPiece data{"foobar"};
  constexpr StringPiece key{"qwerty"};

  HMAC(
      EVP_sha256(),
      key.data(),
      int(key.size()),
      ByteRange{data}.data(),
      data.size(),
      expected.data(),
      nullptr);

  OpenSSLHash::Hmac hmac;
  hmac.hash_init(EVP_sha256(), {key});
  hmac.hash_update({data});

  OpenSSLHash::Hmac copy1{std::move(hmac)};
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  hmac = OpenSSLHash::Hmac{};
  hmac.hash_init(EVP_sha256(), {key});
  hmac.hash_update({data});

  OpenSSLHash::Hmac copy2 = std::move(hmac);
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, hmac_sha256_hashcopy_self) {
  std::array<uint8_t, 32> expected, actual;

  constexpr StringPiece data{"foobar"};
  constexpr StringPiece key{"qwerty"};

  HMAC(
      EVP_sha256(),
      key.data(),
      int(key.size()),
      ByteRange{data}.data(),
      data.size(),
      expected.data(),
      nullptr);

  OpenSSLHash::Hmac hmac;
  hmac.hash_init(EVP_sha256(), {key});
  hmac.hash_update({data});

  OpenSSLHash::Hmac* ptr = &hmac;
  hmac = *ptr;

  hmac.hash_final(range(actual));
  EXPECT_EQ(expected, actual);
}

TEST_F(OpenSSLHashTest, hmac_sha256_hashmove_self) {
  std::array<uint8_t, 32> expected, actual;

  constexpr StringPiece data{"foobar"};
  constexpr StringPiece key{"qwerty"};

  HMAC(
      EVP_sha256(),
      key.data(),
      int(key.size()),
      ByteRange{data}.data(),
      data.size(),
      expected.data(),
      nullptr);

  OpenSSLHash::Hmac hmac;
  hmac.hash_init(EVP_sha256(), {key});
  hmac.hash_update({data});

  OpenSSLHash::Hmac* ptr = &hmac;
  hmac = std::move(*ptr);

  hmac.hash_final(range(actual));
  EXPECT_EQ(expected, actual);
}

TEST_F(OpenSSLHashTest, hmac_sha256_hashcopy_from_default_constructed) {
  std::array<uint8_t, 32> expected, actual1, actual2;

  constexpr StringPiece data{"foobar"};
  constexpr StringPiece key{"qwerty"};

  HMAC(
      EVP_sha256(),
      key.data(),
      int(key.size()),
      ByteRange{data}.data(),
      data.size(),
      expected.data(),
      nullptr);

  OpenSSLHash::Hmac hmac;
  OpenSSLHash::Hmac copy1{hmac};
  OpenSSLHash::Hmac copy2 = hmac;

  copy1.hash_init(EVP_sha256(), {key});
  copy1.hash_update({data});
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  copy2.hash_init(EVP_sha256(), {key});
  copy2.hash_update({data});
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, hmac_sha256_hashmove_from_default_constructed) {
  std::array<uint8_t, 32> expected, actual1, actual2;

  constexpr StringPiece data{"foobar"};
  constexpr StringPiece key{"qwerty"};

  HMAC(
      EVP_sha256(),
      key.data(),
      int(key.size()),
      ByteRange{data}.data(),
      data.size(),
      expected.data(),
      nullptr);

  OpenSSLHash::Hmac hmac1;
  OpenSSLHash::Hmac copy1{std::move(hmac1)};
  OpenSSLHash::Hmac hmac2;
  OpenSSLHash::Hmac copy2 = std::move(hmac2);

  copy1.hash_init(EVP_sha256(), {key});
  copy1.hash_update({data});
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  copy2.hash_init(EVP_sha256(), {key});
  copy2.hash_update({data});
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, hmac_sha256_hashcopy_intermediate) {
  std::array<uint8_t, 32> expected, actual1, actual2;

  constexpr StringPiece data1{"foo"};
  constexpr StringPiece data2{"bar"};
  constexpr StringPiece key{"qwerty"};

  OpenSSLHash::Hmac hmac;
  hmac.hash_init(EVP_sha256(), {key});
  hmac.hash_update({data1});

  OpenSSLHash::Hmac copy1{hmac};
  OpenSSLHash::Hmac copy2 = hmac;
  hmac.hash_update({data2});
  copy1.hash_update({data2});
  copy2.hash_update({data2});

  hmac.hash_final(range(expected));
  copy1.hash_final(range(actual1));
  copy2.hash_final(range(actual2));

  EXPECT_EQ(expected, actual1);
  EXPECT_EQ(expected, actual2);
}

TEST_F(OpenSSLHashTest, hmac_sha256_movecopy_intermediate) {
  std::array<uint8_t, 32> expected, actual1, actual2;

  constexpr StringPiece data{"foobar"};
  constexpr StringPiece data1{"foo"};
  constexpr StringPiece data2{"bar"};
  constexpr StringPiece key{"qwerty"};

  HMAC(
      EVP_sha256(),
      key.data(),
      int(key.size()),
      ByteRange{data}.data(),
      data.size(),
      expected.data(),
      nullptr);

  OpenSSLHash::Hmac hmac;
  hmac.hash_init(EVP_sha256(), {key});
  hmac.hash_update({data1});
  OpenSSLHash::Hmac copy1{std::move(hmac)};
  copy1.hash_update({data2});
  copy1.hash_final(range(actual1));
  EXPECT_EQ(expected, actual1);

  hmac.hash_init(EVP_sha256(), {key});
  hmac.hash_update({data1});
  OpenSSLHash::Hmac copy2 = std::move(hmac);
  copy2.hash_update({data2});
  copy2.hash_final(range(actual2));
  EXPECT_EQ(expected, actual2);

  hmac.hash_init(EVP_sha256(), {key});
}

TEST_F(OpenSSLHashTest, hmac_update_without_init_throws) {
  OpenSSLHash::Hmac hmac;
  EXPECT_THROW(hmac.hash_update(ByteRange{}), std::runtime_error);
}

TEST_F(OpenSSLHashTest, hmac_final_without_init_throws) {
  OpenSSLHash::Hmac hmac;
  std::array<uint8_t, 32> out;
  EXPECT_THROW(hmac.hash_final(range(out)), std::runtime_error);
}

TEST_F(OpenSSLHashTest, HashAndBlockSizes) {
  OpenSSLHash::Digest d;
  d.hash_init(EVP_sha256());
  EXPECT_EQ(d.hash_size(), 32);
  EXPECT_EQ(d.block_size(), 64);

  d.hash_init(EVP_sha384());
  EXPECT_EQ(d.hash_size(), 48);
  EXPECT_EQ(d.block_size(), 128);

  d.hash_init(EVP_sha512());
  EXPECT_EQ(d.hash_size(), 64);
  EXPECT_EQ(d.block_size(), 128);
}
