/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "blapi.h"
#include "blapit.h"
#include "Hacl_Chacha20.h"
#include "prlock.h"
#include "seccomon.h"
#include "secerr.h"
#include "prinit.h"

#define GLOBAL_BYTES_SIZE 100
static PRUint8 globalBytes[GLOBAL_BYTES_SIZE];
static unsigned long globalNumCalls = 0;
static PRLock *rng_lock = NULL;
static PRCallOnceType coRNGInit;
static const PRCallOnceType pristineCallOnce;

static PRStatus
rng_init(void)
{
    rng_lock = PR_NewLock();
    if (!rng_lock) {
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return PR_FAILURE;
    }
    /* --- LOCKED --- */
    PR_Lock(rng_lock);
    memset(globalBytes, 0, GLOBAL_BYTES_SIZE);
    PR_Unlock(rng_lock);
    /* --- UNLOCKED --- */

    return PR_SUCCESS;
}

SECStatus
RNG_RNGInit(void)
{
    /* Allow only one call to initialize the context */
    if (PR_CallOnce(&coRNGInit, rng_init) != PR_SUCCESS) {
        return SECFailure;
    }

    return SECSuccess;
}

/* Take min(size, GLOBAL_BYTES_SIZE) bytes from data and use as seed and reset
 * the rng state. */
SECStatus
RNG_RandomUpdate(const void *data, size_t bytes)
{
    /* Check for a valid RNG lock. */
    PORT_Assert(rng_lock != NULL);
    if (rng_lock == NULL) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* --- LOCKED --- */
    PR_Lock(rng_lock);
    memset(globalBytes, 0, GLOBAL_BYTES_SIZE);
    globalNumCalls = 0;
    if (data) {
        memcpy(globalBytes, (PRUint8 *)data, PR_MIN(bytes, GLOBAL_BYTES_SIZE));
    }
    PR_Unlock(rng_lock);
    /* --- UNLOCKED --- */

    return SECSuccess;
}

SECStatus
RNG_GenerateGlobalRandomBytes(void *dest, size_t len)
{
    static const uint8_t key[32] = { 0 };
    uint8_t nonce[12] = { 0 };

    /* Check for a valid RNG lock. */
    PORT_Assert(rng_lock != NULL);
    if (rng_lock == NULL) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* --- LOCKED --- */
    PR_Lock(rng_lock);

    memcpy(nonce, &globalNumCalls, sizeof(globalNumCalls));
    globalNumCalls++;

    ChaCha20Poly1305Context *cx =
        ChaCha20Poly1305_CreateContext(key, sizeof(key), 16);
    if (!cx) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        PR_Unlock(rng_lock);
        return SECFailure;
    }

    memset(dest, 0, len);
    memcpy(dest, globalBytes, PR_MIN(len, GLOBAL_BYTES_SIZE));
    Hacl_Chacha20_chacha20_encrypt(len, (uint8_t *)dest, (uint8_t *)dest,
                                   (uint8_t *)key, nonce, 0);
    ChaCha20Poly1305_DestroyContext(cx, PR_TRUE);

    PR_Unlock(rng_lock);
    /* --- UNLOCKED --- */

    return SECSuccess;
}

void
RNG_RNGShutdown(void)
{
    if (rng_lock) {
        PR_DestroyLock(rng_lock);
        rng_lock = NULL;
    }
    coRNGInit = pristineCallOnce;
}

/* Test functions are not implemented! */
SECStatus
PRNGTEST_Instantiate(const PRUint8 *entropy, unsigned int entropy_len,
                     const PRUint8 *nonce, unsigned int nonce_len,
                     const PRUint8 *personal_string, unsigned int ps_len)
{
    return SECFailure;
}

SECStatus
PRNGTEST_Reseed(const PRUint8 *entropy, unsigned int entropy_len,
                const PRUint8 *additional, unsigned int additional_len)
{
    return SECFailure;
}

SECStatus
PRNGTEST_Generate(PRUint8 *bytes, unsigned int bytes_len,
                  const PRUint8 *additional, unsigned int additional_len)
{
    return SECFailure;
}

SECStatus
PRNGTEST_Uninstantiate()
{
    return SECFailure;
}

SECStatus
PRNGTEST_RunHealthTests()
{
    return SECFailure;
}

SECStatus
PRNGTEST_Instantiate_Kat(const PRUint8 *entropy, unsigned int entropy_len,
                         const PRUint8 *nonce, unsigned int nonce_len,
                         const PRUint8 *personal_string, unsigned int ps_len)
{
    return SECFailure;
}
