/** @file
  BaseRng Library that uses the TimerLib to provide reasonably random numbers.
  Do not use this on a production system.

  Copyright (c) 2023, Arm Limited. All rights reserved.
  Copyright (c) Microsoft Corporation.
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Base.h>
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/TimerLib.h>
#include <Guid/RngAlgorithm.h>

#define DEFAULT_DELAY_TIME_IN_MICROSECONDS  10

/**
 Using the TimerLib GetPerformanceCounterProperties() we delay
 for enough time for the PerformanceCounter to increment.

 If the return value from GetPerformanceCounterProperties (TimerLib)
 is zero, this function will return 10 and attempt to assert.
 **/
STATIC
UINT32
CalculateMinimumDecentDelayInMicroseconds (
  VOID
  )
{
  UINT64  CounterHz;

  // Get the counter properties
  CounterHz = GetPerformanceCounterProperties (NULL, NULL);
  // Make sure we won't divide by zero
  if (CounterHz == 0) {
    ASSERT (CounterHz != 0); // Assert so the developer knows something is wrong
    return DEFAULT_DELAY_TIME_IN_MICROSECONDS;
  }

  // Calculate the minimum delay based on 1.5 microseconds divided by the hertz.
  // We calculate the length of a cycle (1/CounterHz) and multiply it by 1.5 microseconds
  // This ensures that the performance counter has increased by at least one
  return (UINT32)(MAX (DivU64x64Remainder (1500000, CounterHz, NULL), 1));
}

/**
  Generates a 16-bit random number.

  if Rand is NULL, then ASSERT().

  @param[out] Rand     Buffer pointer to store the 16-bit random value.

  @retval TRUE         Random number generated successfully.
  @retval FALSE        Failed to generate the random number.

**/
BOOLEAN
EFIAPI
GetRandomNumber16 (
  OUT     UINT16  *Rand
  )
{
  UINT32  Index;
  UINT8   *RandPtr;
  UINT32  DelayInMicroSeconds;

  ASSERT (Rand != NULL);

  if (Rand == NULL) {
    return FALSE;
  }

  DelayInMicroSeconds = CalculateMinimumDecentDelayInMicroseconds ();
  RandPtr             = (UINT8 *)Rand;
  // Get 2 bytes of random ish data
  for (Index = 0; Index < sizeof (UINT16); Index++) {
    *RandPtr = (UINT8)(GetPerformanceCounter () & 0xFF);
    // Delay to give the performance counter a chance to change
    MicroSecondDelay (DelayInMicroSeconds);
    RandPtr++;
  }

  return TRUE;
}

/**
  Generates a 32-bit random number.

  if Rand is NULL, then ASSERT().

  @param[out] Rand     Buffer pointer to store the 32-bit random value.

  @retval TRUE         Random number generated successfully.
  @retval FALSE        Failed to generate the random number.

**/
BOOLEAN
EFIAPI
GetRandomNumber32 (
  OUT     UINT32  *Rand
  )
{
  UINT32  Index;
  UINT8   *RandPtr;
  UINT32  DelayInMicroSeconds;

  ASSERT (Rand != NULL);

  if (NULL == Rand) {
    return FALSE;
  }

  RandPtr             = (UINT8 *)Rand;
  DelayInMicroSeconds = CalculateMinimumDecentDelayInMicroseconds ();
  // Get 4 bytes of random ish data
  for (Index = 0; Index < sizeof (UINT32); Index++) {
    *RandPtr = (UINT8)(GetPerformanceCounter () & 0xFF);
    // Delay to give the performance counter a chance to change
    MicroSecondDelay (DelayInMicroSeconds);
    RandPtr++;
  }

  return TRUE;
}

/**
  Generates a 64-bit random number.

  if Rand is NULL, then ASSERT().

  @param[out] Rand     Buffer pointer to store the 64-bit random value.

  @retval TRUE         Random number generated successfully.
  @retval FALSE        Failed to generate the random number.

**/
BOOLEAN
EFIAPI
GetRandomNumber64 (
  OUT     UINT64  *Rand
  )
{
  UINT32  Index;
  UINT8   *RandPtr;
  UINT32  DelayInMicroSeconds;

  ASSERT (Rand != NULL);

  if (NULL == Rand) {
    return FALSE;
  }

  RandPtr             = (UINT8 *)Rand;
  DelayInMicroSeconds = CalculateMinimumDecentDelayInMicroseconds ();
  // Get 8 bytes of random ish data
  for (Index = 0; Index < sizeof (UINT64); Index++) {
    *RandPtr = (UINT8)(GetPerformanceCounter () & 0xFF);
    // Delay to give the performance counter a chance to change
    MicroSecondDelay (DelayInMicroSeconds);
    RandPtr++;
  }

  return TRUE;
}

/**
  Generates a 128-bit random number.

  if Rand is NULL, then ASSERT().

  @param[out] Rand     Buffer pointer to store the 128-bit random value.

  @retval TRUE         Random number generated successfully.
  @retval FALSE        Failed to generate the random number.

**/
BOOLEAN
EFIAPI
GetRandomNumber128 (
  OUT     UINT64  *Rand
  )
{
  ASSERT (Rand != NULL);
  // This should take around 80ms

  // Read first 64 bits
  if (!GetRandomNumber64 (Rand)) {
    return FALSE;
  }

  // Read second 64 bits
  return GetRandomNumber64 (++Rand);
}

/**
  Get a GUID identifying the RNG algorithm implementation.

  @param [out] RngGuid  If success, contains the GUID identifying
                        the RNG algorithm implementation.

  @retval EFI_SUCCESS             Success.
  @retval EFI_UNSUPPORTED         Not supported.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
GetRngGuid (
  GUID  *RngGuid
  )
{
  if (RngGuid == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  CopyMem (RngGuid, &gEdkiiRngAlgorithmUnSafe, sizeof (*RngGuid));
  return EFI_SUCCESS;
}
