/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 *  http://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.
 */

/* for jrand48 */
#define _XOPEN_SOURCE
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include "syscfg/syscfg.h"
#include "os/os.h"
#include "nimble/ble.h"
#include "nimble/nimble_opt.h"
#include "controller/ble_hw.h"
#include "controller/ble_ll.h"
#if MYNEWT_VAL(TRNG)
#include "trng/trng.h"
#endif

#if BABBLESIM
extern void tm_tick(void);
#endif

#if MYNEWT_VAL(TRNG)
static struct trng_dev *g_trng;
#else
/* This is a simple circular buffer for holding N samples of random data */
struct ble_ll_rnum_data
{
    uint8_t *rnd_in;
    uint8_t *rnd_out;
    volatile uint8_t rnd_size;
};

struct ble_ll_rnum_data g_ble_ll_rnum_data;
uint8_t g_ble_ll_rnum_buf[MYNEWT_VAL(BLE_LL_RNG_BUFSIZE)];

#define IS_RNUM_BUF_END(x)  \
    (x == &g_ble_ll_rnum_buf[MYNEWT_VAL(BLE_LL_RNG_BUFSIZE) - 1])

void
ble_ll_rand_sample(uint8_t rnum)
{
    os_sr_t sr;

    OS_ENTER_CRITICAL(sr);
    if (g_ble_ll_rnum_data.rnd_size < MYNEWT_VAL(BLE_LL_RNG_BUFSIZE)) {
        ++g_ble_ll_rnum_data.rnd_size;
        g_ble_ll_rnum_data.rnd_in[0] = rnum;
        if (IS_RNUM_BUF_END(g_ble_ll_rnum_data.rnd_in)) {
            g_ble_ll_rnum_data.rnd_in = g_ble_ll_rnum_buf;
        } else {
            ++g_ble_ll_rnum_data.rnd_in;
        }
    } else {
        /* Stop generating random numbers as we are full */
        ble_hw_rng_stop();
    }
    OS_EXIT_CRITICAL(sr);
}
#endif

/* Get 'len' bytes of random data */
int
ble_ll_rand_data_get(uint8_t *buf, uint8_t len)
{
#if MYNEWT_VAL(TRNG)
    size_t num;

    while (len) {
        num = trng_read(g_trng, buf, len);
        buf += num;
        len -= num;
    }
#else
    uint8_t rnums;
    os_sr_t sr;

    while (len != 0) {
        OS_ENTER_CRITICAL(sr);
        rnums = g_ble_ll_rnum_data.rnd_size;
        if (rnums > len) {
            rnums = len;
        }
        len -= rnums;
        g_ble_ll_rnum_data.rnd_size -= rnums;
        while (rnums) {
            buf[0] = g_ble_ll_rnum_data.rnd_out[0];
            if (IS_RNUM_BUF_END(g_ble_ll_rnum_data.rnd_out)) {
                g_ble_ll_rnum_data.rnd_out = g_ble_ll_rnum_buf;
            } else {
                ++g_ble_ll_rnum_data.rnd_out;
            }
            ++buf;
            --rnums;
        }
        OS_EXIT_CRITICAL(sr);

        /* Make sure rng is started! */
        ble_hw_rng_start();

        /* Wait till bytes are in buffer. */
        if (len) {
            while ((g_ble_ll_rnum_data.rnd_size < len) &&
                   (g_ble_ll_rnum_data.rnd_size < MYNEWT_VAL(BLE_LL_RNG_BUFSIZE))) {
                /* Spin here */
#if BABBLESIM
                tm_tick();
#endif
            }
        }
    }
#endif
    return BLE_ERR_SUCCESS;
}

/* Simple wrapper to allow easy replacement of rand() */
uint32_t
ble_ll_rand(void)
{
    static unsigned short xsubi[3];
    static bool init = true;

    if (init) {
        init = false;
        ble_ll_rand_data_get((uint8_t *)xsubi, sizeof(xsubi));
    }

    return (uint32_t) jrand48(xsubi);
}

/**
 * Called to obtain a "prand" as defined in core V4.2 Vol 6 Part B 1.3.2.2
 *
 * @param prand
 */
void
ble_ll_rand_prand_get(uint8_t *prand)
{
    uint16_t sum;

    while (1) {
        /* Get 24 bits of random data */
        ble_ll_rand_data_get(prand, 3);

        /* Prand cannot be all zeros or 1's. */
        sum = prand[0] + prand[1] + prand[2];
        if ((sum != 0) && (sum != (3 * 0xff))) {
            break;
        }
    }

    /* Upper two bits must be 01 */
    prand[2] &= ~0xc0;
    prand[2] |= 0x40;
}

/**
 * Start the generation of random numbers
 *
 * @return int
 */
int
ble_ll_rand_start(void)
{
#if MYNEWT_VAL(TRNG)
    /* Nothing to do - this is handled by driver */
#else
    /* Start the generation of numbers if we are not full */
    if (g_ble_ll_rnum_data.rnd_size < MYNEWT_VAL(BLE_LL_RNG_BUFSIZE)) {
        ble_hw_rng_start();
    }
#endif
    return 0;
}

/**
 * Initialize LL random number generation. Should be called only once on
 * initialization.
 *
 * @return int
 */
int
ble_ll_rand_init(void)
{
#if MYNEWT_VAL(TRNG)
    g_trng = (struct trng_dev *) os_dev_open("trng", OS_TIMEOUT_NEVER, NULL);
#else
    g_ble_ll_rnum_data.rnd_in = g_ble_ll_rnum_buf;
    g_ble_ll_rnum_data.rnd_out = g_ble_ll_rnum_buf;
    ble_hw_rng_init(ble_ll_rand_sample, 1);
#endif
    return 0;
}
