/**
 * Copyright (c) 2018 - 2020, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "sdk_common.h"
#if NRF_MODULE_ENABLED(NRF_BLE_LESC)

#include "nrf_ble_lesc.h"
#include "nrf_crypto.h"

#define NRF_LOG_MODULE_NAME nrf_ble_lesc
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

/**@brief Descriptor of the peer public key. */
typedef struct
{
    nrf_crypto_ecc_public_key_t value;        /**< Peer public key. */
    bool                        is_requested; /**< Flag indicating that the public key has been requested to compute DH key. */
    bool                        is_valid;     /**< Flag indicating that the public key is valid. */
} nrf_ble_lesc_peer_pub_key_t;

/**@brief   The maximum number of peripheral and central connections combined.
 *          This value is based on what is configured in the SoftDevice handler sdk_config.
 */
#define NRF_BLE_LESC_LINK_COUNT (NRF_SDH_BLE_PERIPHERAL_LINK_COUNT + NRF_SDH_BLE_CENTRAL_LINK_COUNT)

__ALIGN(4) static ble_gap_lesc_p256_pk_t m_lesc_public_key;                             /**< LESC ECC Public Key. */
__ALIGN(4) static ble_gap_lesc_dhkey_t   m_lesc_dh_key;                                 /**< LESC ECC DH Key. */

static nrf_crypto_ecdh_context_t                  m_ecdh_context;                       /**< Context to do the LESC ECDH calculation */

static bool                                       m_ble_lesc_internal_error;            /**< Flag indicating that the module encountered an internal error. */
static bool                                       m_keypair_generated;                  /**< Flag indicating that the local ECDH key pair was generated. */
static nrf_crypto_ecc_key_pair_generate_context_t m_keygen_context;                     /**< Context to generate private/public key pair. */
static nrf_crypto_ecc_private_key_t               m_private_key;                        /**< Allocated private key type to use for LESC DH generation. */
static nrf_crypto_ecc_public_key_t                m_public_key;                         /**< Allocated public key type to use for LESC DH generation. */
static nrf_ble_lesc_peer_pub_key_t                m_peer_keys[NRF_BLE_LESC_LINK_COUNT]; /**< Array of pointers to peer public keys, used for LESC DH generation. */

static bool                                       m_lesc_oobd_own_generated;
static ble_gap_lesc_oob_data_t                    m_ble_lesc_oobd_own;                  /**< LESC OOB data used in LESC OOB pairing mode. */
static nrf_ble_lesc_peer_oob_data_handler         m_lesc_oobd_peer_handler;

ret_code_t nrf_ble_lesc_init(void)
{
    ret_code_t err_code;

    memset((void *) m_peer_keys, 0, sizeof(m_peer_keys));

#if NRF_CRYPTO_ALLOCATOR == NRF_CRYPTO_ALLOCATOR_NRF_MALLOC
    // Initialize mem_manager if used by nrf_crypto.
    err_code = nrf_mem_init();
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("nrf_mem_init() returned error 0x%x.", err_code);
        return err_code;
    }
#endif

    // Ensure that nrf_crypto has been initialized.
    err_code = nrf_crypto_init();
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("nrf_crypto_init() returned error 0x%x.", err_code);
        return err_code;
    }
    NRF_LOG_DEBUG("Initialized nrf_crypto.");

#if defined(NRF_CRYPTO_RNG_AUTO_INIT_ENABLED) && (NRF_CRYPTO_RNG_AUTO_INIT_ENABLED == 1)
    // Do nothing. RNG is initialized with nrf_crypto_init call.
#elif defined(NRF_CRYPTO_RNG_AUTO_INIT_ENABLED) && (NRF_CRYPTO_RNG_AUTO_INIT_ENABLED == 0)
    // Initialize the RNG.
    err_code = nrf_crypto_rng_init(NULL, NULL);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("nrf_crypto_rng_init() returned error 0x%x.", err_code);
        return err_code;
    }
#else
    #error Invalid sdk_config.h (does not contain NRF_CRYPTO_RNG_AUTO_INIT_ENABLED)
#endif // defined(NRF_CRYPTO_RNG_AUTO_INIT_ENABLED) && (NRF_CRYPTO_RNG_AUTO_INIT_ENABLED == 1)
    NRF_LOG_DEBUG("Initialized nrf_ble_lesc.");

    // Reset module state.
    m_ble_lesc_internal_error = false;
    m_keypair_generated       = false;

    // Generate ECC key pair. Only one key pair is automatically generated by this module.
    err_code = nrf_ble_lesc_keypair_generate();
    return err_code;
}


ret_code_t nrf_ble_lesc_keypair_generate(void)
{
    ret_code_t err_code;
    size_t     public_len = NRF_CRYPTO_ECC_SECP256R1_RAW_PUBLIC_KEY_SIZE;

    // Check if any DH computation is pending
    for (uint32_t i = 0; i < ARRAY_SIZE(m_peer_keys); i++)
    {
        if (m_peer_keys[i].is_valid)
        {
            return NRF_ERROR_BUSY;
        }
    }

    // Update flag to indicate that there is no valid private key.
    m_keypair_generated       = false;
    m_lesc_oobd_own_generated = false;

    NRF_LOG_DEBUG("Generating ECC key pair");
    err_code = nrf_crypto_ecc_key_pair_generate(&m_keygen_context,
                                                &g_nrf_crypto_ecc_secp256r1_curve_info,
                                                &m_private_key,
                                                &m_public_key);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("nrf_crypto_ecc_key_pair_generate() returned error 0x%x.", err_code);
        return err_code;
    }

    // Convert to a raw type.
    err_code = nrf_crypto_ecc_public_key_to_raw(&m_public_key,
                                                m_lesc_public_key.pk,
                                                &public_len);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("nrf_crypto_ecc_public_key_to_raw() returned error 0x%x.", err_code);
        return err_code;
    }

    // Invert the raw type to little-endian (required for BLE).
    err_code = nrf_crypto_ecc_byte_order_invert(&g_nrf_crypto_ecc_secp256r1_curve_info,
                                                m_lesc_public_key.pk,
                                                m_lesc_public_key.pk,
                                                NRF_CRYPTO_ECC_SECP256R1_RAW_PUBLIC_KEY_SIZE);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("nrf_crypto_ecc_byte_order_invert() returned error 0x%x.", err_code);
    }
    else
    {
        // Set the flag to indicate that there is a valid ECDH key pair generated.
        m_keypair_generated = true;
    }

    return err_code;
}


ret_code_t nrf_ble_lesc_own_oob_data_generate(void)
{
    ret_code_t err_code = NRF_ERROR_INVALID_STATE;

    m_lesc_oobd_own_generated = false;

    if (m_keypair_generated)
    {
        err_code = sd_ble_gap_lesc_oob_data_get(BLE_CONN_HANDLE_INVALID,
                                                &m_lesc_public_key,
                                                &m_ble_lesc_oobd_own);
        if (err_code == NRF_SUCCESS)
        {
            m_lesc_oobd_own_generated = true;
        }
    }

    return err_code;
}


ble_gap_lesc_p256_pk_t * nrf_ble_lesc_public_key_get(void)
{
    ble_gap_lesc_p256_pk_t * p_lesc_pk = NULL;

    if (m_keypair_generated)
    {
        p_lesc_pk = &m_lesc_public_key;
    }
    else
    {
        NRF_LOG_ERROR("Trying to access LESC public key that has not been generated yet.");
    }

    return p_lesc_pk;
}


ble_gap_lesc_oob_data_t * nrf_ble_lesc_own_oob_data_get(void)
{
    ble_gap_lesc_oob_data_t * p_lesc_oobd_own = NULL;

    if (m_lesc_oobd_own_generated)
    {
        p_lesc_oobd_own = &m_ble_lesc_oobd_own;
    }
    else
    {
        NRF_LOG_ERROR("Trying to access LESC OOB data that have not been generated yet.");
    }

    return p_lesc_oobd_own;
}


void nrf_ble_lesc_peer_oob_data_handler_set(nrf_ble_lesc_peer_oob_data_handler handler)
{
    m_lesc_oobd_peer_handler = handler;
}


/**@brief Function for calculating a DH key and responding to the DH key request on a given
 *        connection handle.
 *
 * @param[in]  p_peer_public_key  ECC peer public key, used to compute shared secret.
 * @param[in]  conn_handle        Connection handle.
 *
 * @retval NRF_SUCCESS If the operation was successful.
 * @retval Other       Other error codes might be returned by the @ref nrf_crypto_ecdh_compute, @ref
 *                     nrf_crypto_ecc_byte_order_invert, and @ref sd_ble_gap_lesc_dhkey_reply functions.
 */
static ret_code_t compute_and_give_dhkey(nrf_ble_lesc_peer_pub_key_t * p_peer_public_key,
                                         uint16_t                      conn_handle)
{
    ret_code_t err_code           = NRF_ERROR_INTERNAL;
    size_t     shared_secret_size = BLE_GAP_LESC_DHKEY_LEN;
    uint8_t  * p_shared_secret    = m_lesc_dh_key.key;

    // Check if there is a valid generated and set a local ECDH public key.
    if (!m_keypair_generated)
    {
        return NRF_ERROR_INTERNAL;
    }

    // Check if the public_key is valid
    if (p_peer_public_key->is_valid)
    {
        err_code = nrf_crypto_ecdh_compute(&m_ecdh_context,
                                           &m_private_key,
                                           &p_peer_public_key->value,
                                           p_shared_secret,
                                           &shared_secret_size);
    }

    if (err_code == NRF_SUCCESS)
    {
        // Invert the shared secret for little endian format.
        err_code = nrf_crypto_ecc_byte_order_invert(&g_nrf_crypto_ecc_secp256r1_curve_info,
                                                    p_shared_secret,
                                                    p_shared_secret,
                                                    BLE_GAP_LESC_DHKEY_LEN);
        VERIFY_SUCCESS(err_code);
    }
    else
    {
        NRF_LOG_WARNING("Creating invalid shared secret to make LESC fail.");
        err_code = nrf_crypto_rng_vector_generate(p_shared_secret, BLE_GAP_LESC_DHKEY_LEN);
        VERIFY_SUCCESS(err_code);
    }

    NRF_LOG_INFO("Calling sd_ble_gap_lesc_dhkey_reply on conn_handle: %d", conn_handle);
    err_code = sd_ble_gap_lesc_dhkey_reply(conn_handle, &m_lesc_dh_key);

    return err_code;
}


ret_code_t nrf_ble_lesc_request_handler(void)
{
    ret_code_t err_code = NRF_SUCCESS;

    // If the LESC module is in an invalid state, a restart is required.
    if (m_ble_lesc_internal_error)
    {
        return NRF_ERROR_INTERNAL;
    }

    for (uint16_t i = 0; i < NRF_BLE_LESC_LINK_COUNT; i++)
    {
        if (m_peer_keys[i].is_requested)
        {
            err_code                    = compute_and_give_dhkey(&m_peer_keys[i], i);
            m_peer_keys[i].is_requested = false;
            m_peer_keys[i].is_valid     = false;
            VERIFY_SUCCESS(err_code);

        }
    }

    return err_code;
}


/**@brief Function for handling a DH key request event.
 *
 * @param[in]  conn_handle      Connection handle.
 * @param[in]  p_dhkey_request  DH key request descriptor.
 *
 * @retval NRF_SUCCESS      If the operation was successful.
 * @retval Other            Other error codes might be returned by the @ref nrf_crypto_ecc_byte_order_invert
 *                          and @ref nrf_crypto_ecc_public_key_from_raw functions.
 */
static ret_code_t on_dhkey_request(uint16_t                                 conn_handle,
                                   ble_gap_evt_lesc_dhkey_request_t const * p_dhkey_request)
{
    ret_code_t err_code = NRF_SUCCESS;
    uint8_t    public_raw[BLE_GAP_LESC_P256_PK_LEN];
    uint8_t  * p_public_raw;
    size_t     public_raw_len;

    // Convert the received public key from big-endian to little endian.
    p_public_raw   = p_dhkey_request->p_pk_peer->pk;
    public_raw_len = BLE_GAP_LESC_P256_PK_LEN;
    err_code       = nrf_crypto_ecc_byte_order_invert(&g_nrf_crypto_ecc_secp256r1_curve_info,
                                                      p_public_raw,
                                                      public_raw,
                                                      public_raw_len);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("nrf_crypto_ecc_byte_order_invert() returned error 0x%x.", err_code);
        return err_code;
    }

    // Copy peer public key to the allocated context. The dhkey calculation will be performed in
    // @ref nrf_ble_lesc_request_handler, so it does not block normal operation.
    err_code = nrf_crypto_ecc_public_key_from_raw(&g_nrf_crypto_ecc_secp256r1_curve_info,
                                                  &m_peer_keys[conn_handle].value,
                                                  public_raw,
                                                  public_raw_len);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("nrf_crypto_ecc_public_key_from_raw() returned error 0x%x.", err_code);
        m_peer_keys[conn_handle].is_valid = false;
    }
    else
    {
        m_peer_keys[conn_handle].is_valid = true;
    }
    m_peer_keys[conn_handle].is_requested = true;

    return NRF_SUCCESS;
}


/**@brief Function for setting LESC OOB data.
 *
 * @param[in]  conn_handle      Connection handle.
 *
 * @retval NRF_SUCCESS      If the operation was successful.
 * @retval Other            Other error codes might be returned by the @ref sd_ble_gap_lesc_oob_data_set.
 */
static ret_code_t lesc_oob_data_set(uint16_t conn_handle)
{
    ret_code_t                err_code;
    ble_gap_lesc_oob_data_t * p_lesc_oobd_own;
    ble_gap_lesc_oob_data_t * p_lesc_oobd_peer;

    p_lesc_oobd_own  = (m_lesc_oobd_own_generated) ? &m_ble_lesc_oobd_own : NULL;
    p_lesc_oobd_peer = (m_lesc_oobd_peer_handler != NULL) ?
                        m_lesc_oobd_peer_handler(conn_handle) : NULL;

    err_code = sd_ble_gap_lesc_oob_data_set(conn_handle,
                                            p_lesc_oobd_own,
                                            p_lesc_oobd_peer);
    return err_code;
}


void nrf_ble_lesc_on_ble_evt(ble_evt_t const * p_ble_evt)
{
    ret_code_t err_code    = NRF_SUCCESS;
    uint16_t   conn_handle = p_ble_evt->evt.gap_evt.conn_handle;

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_DISCONNECTED:
            m_peer_keys[conn_handle].is_valid     = false;
            m_peer_keys[conn_handle].is_requested = false;
            break;

        case BLE_GAP_EVT_LESC_DHKEY_REQUEST:
            NRF_LOG_DEBUG("BLE_GAP_EVT_LESC_DHKEY_REQUEST");

            if (p_ble_evt->evt.gap_evt.params.lesc_dhkey_request.oobd_req)
            {
                err_code = lesc_oob_data_set(conn_handle);
                if (err_code != NRF_SUCCESS)
                {
                    NRF_LOG_ERROR("sd_ble_gap_lesc_oob_data_set() returned error 0x%x.", err_code);
                    m_ble_lesc_internal_error = true;
                }
            }

            err_code = on_dhkey_request(conn_handle,
                                        &p_ble_evt->evt.gap_evt.params.lesc_dhkey_request);
            if (err_code != NRF_SUCCESS)
            {
                m_ble_lesc_internal_error = true;
            }
            break;

        default:
            break;
    }
}


#endif // NRF_BLE_LESC_ENABLED
