/*************************************************
* Global RNG Source File                         *
* (C) 1999-2003 The Botan Project                *
*************************************************/

#include <botan/rng.h>
#include <botan/mutex.h>
#include <botan/lookup.h>
#include <botan/botan.h>
#include <memory>

namespace Botan {

namespace {

RandomNumberGenerator* global_rng = 0;
RandomNumberGenerator* nonce_rng = 0;
Mutex* global_rng_lock = 0;

/*************************************************
* Seed the nonce RNG                             *
*************************************************/
void seed_nonce_rng()
   {
   if(!global_rng->is_seeded())
      return;
   SecureBuffer<byte, 256> entropy;
   global_rng->randomize(entropy, entropy.size());
   nonce_rng->add_entropy(entropy, entropy.size());
   }

}

namespace Global_RNG {

/*************************************************
* Get entropy from the global RNG                *
*************************************************/
void randomize(byte output[], u32bit size, RNG_Quality level)
   {
   if(!global_rng)
      throw Invalid_State("Global_RNG::randomize: The global RNG is not set");
   Mutex_Holder lock(global_rng_lock);

   if(level == Nonce)
      nonce_rng->randomize(output, size);
   else if(level == SessionKey)
      global_rng->randomize(output, size);
   else if(level == LongTermKey)
      {
      global_rng->randomize(output, size);
      if(have_stream_cipher("ARC4") && Config::get_bool("rng/safe_longterm"))
         {
         std::auto_ptr<StreamCipher> cipher(get_stream_cipher("ARC4"));
         SecureVector<byte> key(cipher->MAXIMUM_KEYLENGTH);
         global_rng->randomize(key, key.size());
         cipher->set_key(key, key.size());
         cipher->encrypt(output, size);
         }
      }
   else
      throw Invalid_Argument("Global_RNG::randomize: Invalid RNG_Quality");
   }

/*************************************************
* Get entropy from the global RNG                *
*************************************************/
byte random(RNG_Quality level)
   {
   byte ret = 0;
   randomize(&ret, 1, level);
   return ret;
   }

/*************************************************
* Add entropy to the global RNG                  *
*************************************************/
void add_entropy(const byte entropy[], u32bit size)
   {
   if(!global_rng) return;
   Mutex_Holder lock(global_rng_lock);
   global_rng->add_entropy(entropy, size);
   seed_nonce_rng();
   }

/*************************************************
* Add entropy to the global RNG                  *
*************************************************/
void add_entropy(EntropySource& src, bool slow_poll)
   {
   if(!global_rng) return;
   Mutex_Holder lock(global_rng_lock);
   global_rng->add_entropy(src, slow_poll);
   seed_nonce_rng();
   }

}

/*************************************************
* Setup the global RNG                           *
*************************************************/
void setup_global_rng(RandomNumberGenerator* rng1,
                      RandomNumberGenerator* rng2)
   {
   if(rng1 == 0 || rng2 == 0)
      throw Invalid_Argument("setup_global_rng: Both RNGs must be specified");

   initialize_mutex(global_rng_lock);
   Mutex_Holder lock(global_rng_lock);

   if(global_rng)
      delete global_rng;
   global_rng = rng1;

   if(nonce_rng)
      delete nonce_rng;
   nonce_rng = rng2;
   }

/*************************************************
* Shut down the global RNG                       *
*************************************************/
void destroy_global_rng()
   {
   delete global_rng;
   delete nonce_rng;
   delete global_rng_lock;
   global_rng = 0;
   global_rng_lock = 0;
   }

}
