// Copyright 2017 The Abseil Authors.
//
// 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
//
//      https://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 "absl/random/internal/seed_material.h"

#include <fcntl.h>

#ifndef _WIN32
#include <unistd.h>
#else
#include <io.h>
#endif

#include <algorithm>
#include <cerrno>
#include <cstdint>
#include <cstdlib>
#include <cstring>

#include "absl/base/internal/raw_logging.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"

#if defined(__native_client__)

#include <nacl/nacl_random.h>
#define ABSL_RANDOM_USE_NACL_SECURE_RANDOM 1

#elif defined(_WIN32)

#include <windows.h>
#define ABSL_RANDOM_USE_BCRYPT 1
#pragma comment(lib, "bcrypt.lib")

#endif

#if defined(ABSL_RANDOM_USE_BCRYPT)
#include <bcrypt.h>

#ifndef BCRYPT_SUCCESS
#define BCRYPT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
// Also link bcrypt; this can be done via linker options or:
// #pragma comment(lib, "bcrypt.lib")
#endif

namespace absl {
inline namespace lts_2019_08_08 {
namespace random_internal {
namespace {

// Read OS Entropy for random number seeds.
// TODO(absl-team): Possibly place a cap on how much entropy may be read at a
// time.

#if defined(ABSL_RANDOM_USE_BCRYPT)

// On Windows potentially use the BCRYPT CNG API to read available entropy.
bool ReadSeedMaterialFromOSEntropyImpl(absl::Span<uint32_t> values) {
  BCRYPT_ALG_HANDLE hProvider;
  NTSTATUS ret;
  ret = BCryptOpenAlgorithmProvider(&hProvider, BCRYPT_RNG_ALGORITHM,
                                    MS_PRIMITIVE_PROVIDER, 0);
  if (!(BCRYPT_SUCCESS(ret))) {
    ABSL_RAW_LOG(ERROR, "Failed to open crypto provider.");
    return false;
  }
  ret = BCryptGenRandom(
      hProvider,                                             // provider
      reinterpret_cast<UCHAR*>(values.data()),               // buffer
      static_cast<ULONG>(sizeof(uint32_t) * values.size()),  // bytes
      0);                                                    // flags
  BCryptCloseAlgorithmProvider(hProvider, 0);
  return BCRYPT_SUCCESS(ret);
}

#elif defined(ABSL_RANDOM_USE_NACL_SECURE_RANDOM)

// On NaCL use nacl_secure_random to acquire bytes.
bool ReadSeedMaterialFromOSEntropyImpl(absl::Span<uint32_t> values) {
  auto buffer = reinterpret_cast<uint8_t*>(values.data());
  size_t buffer_size = sizeof(uint32_t) * values.size();

  uint8_t* output_ptr = buffer;
  while (buffer_size > 0) {
    size_t nread = 0;
    const int error = nacl_secure_random(output_ptr, buffer_size, &nread);
    if (error != 0 || nread > buffer_size) {
      ABSL_RAW_LOG(ERROR, "Failed to read secure_random seed data: %d", error);
      return false;
    }
    output_ptr += nread;
    buffer_size -= nread;
  }
  return true;
}

#else

// On *nix, read entropy from /dev/urandom.
bool ReadSeedMaterialFromOSEntropyImpl(absl::Span<uint32_t> values) {
  const char kEntropyFile[] = "/dev/urandom";

  auto buffer = reinterpret_cast<uint8_t*>(values.data());
  size_t buffer_size = sizeof(uint32_t) * values.size();

  int dev_urandom = open(kEntropyFile, O_RDONLY);
  bool success = (-1 != dev_urandom);
  if (!success) {
    return false;
  }

  while (success && buffer_size > 0) {
    int bytes_read = read(dev_urandom, buffer, buffer_size);
    int read_error = errno;
    success = (bytes_read > 0);
    if (success) {
      buffer += bytes_read;
      buffer_size -= bytes_read;
    } else if (bytes_read == -1 && read_error == EINTR) {
      success = true;  // Need to try again.
    }
  }
  close(dev_urandom);
  return success;
}

#endif

}  // namespace

bool ReadSeedMaterialFromOSEntropy(absl::Span<uint32_t> values) {
  assert(values.data() != nullptr);
  if (values.data() == nullptr) {
    return false;
  }
  if (values.empty()) {
    return true;
  }
  return ReadSeedMaterialFromOSEntropyImpl(values);
}

void MixIntoSeedMaterial(absl::Span<const uint32_t> sequence,
                         absl::Span<uint32_t> seed_material) {
  // Algorithm is based on code available at
  // https://gist.github.com/imneme/540829265469e673d045
  constexpr uint32_t kInitVal = 0x43b0d7e5;
  constexpr uint32_t kHashMul = 0x931e8875;
  constexpr uint32_t kMixMulL = 0xca01f9dd;
  constexpr uint32_t kMixMulR = 0x4973f715;
  constexpr uint32_t kShiftSize = sizeof(uint32_t) * 8 / 2;

  uint32_t hash_const = kInitVal;
  auto hash = [&](uint32_t value) {
    value ^= hash_const;
    hash_const *= kHashMul;
    value *= hash_const;
    value ^= value >> kShiftSize;
    return value;
  };

  auto mix = [&](uint32_t x, uint32_t y) {
    uint32_t result = kMixMulL * x - kMixMulR * y;
    result ^= result >> kShiftSize;
    return result;
  };

  for (const auto& seq_val : sequence) {
    for (auto& elem : seed_material) {
      elem = mix(elem, hash(seq_val));
    }
  }
}

absl::optional<uint32_t> GetSaltMaterial() {
  // Salt must be common for all generators within the same process so read it
  // only once and store in static variable.
  static const auto salt_material = []() -> absl::optional<uint32_t> {
    uint32_t salt_value = 0;

    if (random_internal::ReadSeedMaterialFromOSEntropy(
            MakeSpan(&salt_value, 1))) {
      return salt_value;
    }

    return absl::nullopt;
  }();

  return salt_material;
}

}  // namespace random_internal
}  // inline namespace lts_2019_08_08
}  // namespace absl
