/*
* CPU Jitter Random Number Generator
* (C) 2024 Planck Security S.A.
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/jitter_rng.h>

#include <botan/assert.h>
#include <botan/exceptn.h>
#include <botan/mutex.h>

#include <jitterentropy.h>

namespace Botan {

class Jitter_RNG_Internal final {
   public:
      Jitter_RNG_Internal();
      ~Jitter_RNG_Internal();
      void collect_into_buffer(std::span<uint8_t> buf);

      Jitter_RNG_Internal(const Jitter_RNG_Internal& other) = delete;
      Jitter_RNG_Internal(Jitter_RNG_Internal&& other) = delete;
      Jitter_RNG_Internal& operator=(const Jitter_RNG_Internal& other) = delete;
      Jitter_RNG_Internal& operator=(Jitter_RNG_Internal&& other) = delete;

   private:
      mutex_type m_mutex;
      rand_data* m_rand_data;
};

Jitter_RNG_Internal::Jitter_RNG_Internal() {
   constexpr unsigned int oversampling_rate = 0;    // use default oversampling
   constexpr unsigned int flags = JENT_FORCE_FIPS;  // enable health tests

   // if flags and osr are used, use the same values for init and alloc
   static const int result = jent_entropy_init_ex(oversampling_rate, flags);

   // no further details documented regarding the return value
   if(result != 0) {
      throw Internal_Error("Jitter_RNG_Internal initialization failed");
   }

   m_rand_data = jent_entropy_collector_alloc(oversampling_rate, flags);
   if(m_rand_data == nullptr) {
      throw Internal_Error("Jitter_RNG_Internal collector allocation failed");
   }
}

Jitter_RNG_Internal::~Jitter_RNG_Internal() {
   const lock_guard_type<mutex_type> lock(m_mutex);
   if(m_rand_data != nullptr) {
      jent_entropy_collector_free(m_rand_data);
      m_rand_data = nullptr;
   }
}

void Jitter_RNG_Internal::collect_into_buffer(std::span<uint8_t> buf) {
   if(buf.empty()) {
      return;
   }

   const lock_guard_type<mutex_type> lock(m_mutex);
   BOTAN_STATE_CHECK(m_rand_data != nullptr);

   ssize_t num_bytes = jent_read_entropy_safe(&m_rand_data, reinterpret_cast<char*>(buf.data()), buf.size());
   if(num_bytes < 0) {
      const auto error_msg = [&]() -> std::string_view {
         switch(num_bytes) {
            case -1:  // should never happen because of the check above
               return "JitterRNG: Uninitilialized";
            case -2:
               return "JitterRNG: SP800-90B repetition count online health test failed";
            case -3:
               return "JitterRNG: SP800-90B adaptive proportion online health test failed";
            case -4:
               return "JitterRNG: Internal timer generator could not be initialized";
            case -5:
               return "JitterRNG: LAG predictor health test failed";
            case -6:
               return "JitterRNG: Repetitive count test (RCT) failed permanently";
            case -7:
               return "JitterRNG: Adaptive proportion test (APT) failed permanently";
            case -8:
               return "JitterRNG: LAG prediction test failed permanently";
            default:
               return "JitterRNG: Error reading entropy";
         }
      }();
      throw Internal_Error(error_msg);
   }

   // According to the docs, `jent_read_entropy` itself runs its logic as often
   // as necessary to gather the requested number of bytes,
   // so this should actually never happen.
   BOTAN_ASSERT(static_cast<size_t>(num_bytes) == buf.size(), "JitterRNG produced the expected number of bytes");
}

Jitter_RNG::Jitter_RNG() : m_jitter{std::make_unique<Jitter_RNG_Internal>()} {}

Jitter_RNG::~Jitter_RNG() = default;

void Jitter_RNG::clear() {
   /* ignored - jitterentropy library does not support this */
}

void Jitter_RNG::fill_bytes_with_input(std::span<uint8_t> out, std::span<const uint8_t> in) {
   BOTAN_UNUSED(in);

   m_jitter->collect_into_buffer(out);
}

}  // namespace Botan
