/*
 * Copyright (c) 2021 - 2022, Nordic Semiconductor ASA
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * 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 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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 "nrf_802154_encrypt.h"

#include <assert.h>
#include <string.h>

#include "nrf_802154_aes_ccm.h"
#include "nrf_802154_const.h"
#include "nrf_802154_pib.h"
#include "nrf_802154_types.h"
#include "mac_features/nrf_802154_frame_parser.h"
#include "mac_features/nrf_802154_security_pib.h"

/**
 * @brief Copies memory in reversed byte order.
 *
 * @note Source and destination buffers must not be NULL.
 *
 * @param[out] p_dst   Pointer to the destination buffer.
 * @param[in]  p_src   Pointer to the source buffer.
 * @param[in]  n       The number of bytes to copy.
 */
static inline void memcpy_rev(void * p_dst, const void * p_src, size_t n)
{
    for (size_t i = 0; i < n; i++)
    {
        *((uint8_t *)p_dst + i) = *((uint8_t *)p_src + n - 1 - i);
    }
}

static bool frame_version_is_2015_or_above(const nrf_802154_frame_parser_data_t * p_frame_data)
{
    switch (nrf_802154_frame_parser_frame_version_get(p_frame_data))
    {
        case FRAME_VERSION_0:
        case FRAME_VERSION_1:
            return false;

        case FRAME_VERSION_2:
            return true;

        default:
            return true;
    }
}

static uint8_t mic_level_from_security_level_get(uint8_t security_level)
{
    // According to the specification, two least significant bits of the security level
    // indicate provided level of data authenticity
    return security_level & SECURITY_LEVEL_MIC_LEVEL_MASK;
}

static bool a_data_and_m_data_prepare(
    const nrf_802154_frame_parser_data_t * p_frame_data,
    nrf_802154_aes_ccm_data_t            * p_aes_ccm_data)
{
    bool result = true;

    uint8_t open_payload_len = 0;

    if ((nrf_802154_frame_parser_frame_type_get(p_frame_data) == FRAME_TYPE_COMMAND) &&
        (!frame_version_is_2015_or_above(p_frame_data)))
    {
        open_payload_len = MAC_CMD_COMMAND_ID_SIZE;
    }

    // It is assumed that the provided frame has a placeholder of appropriate length for MIC
    // at the end. The algorithm inputs should only contain MAC payload, so the MIC placeholder
    // of the below length should be removed
    uint8_t   mic_len     = nrf_802154_frame_parser_mic_size_get(p_frame_data);
    uint8_t * p_mac_hdr   = (uint8_t *)nrf_802154_frame_parser_psdu_get(p_frame_data);
    uint8_t   mac_hdr_len =
        nrf_802154_frame_parser_mac_header_length_get(p_frame_data) + open_payload_len;
    uint8_t * p_mac_payload =
        (uint8_t *)(nrf_802154_frame_parser_mac_payload_get(p_frame_data) + open_payload_len);
    uint8_t mac_payload_len =
        nrf_802154_frame_parser_mac_payload_length_get(p_frame_data) - mic_len - open_payload_len;

    switch (nrf_802154_frame_parser_sec_ctrl_sec_lvl_get(p_frame_data))
    {
        case SECURITY_LEVEL_NONE:
            // No data authenticity nor data confidentiality
            p_aes_ccm_data->auth_data           = NULL;
            p_aes_ccm_data->auth_data_len       = 0;
            p_aes_ccm_data->plain_text_data     = NULL;
            p_aes_ccm_data->plain_text_data_len = 0;
            break;

        case SECURITY_LEVEL_MIC_32:
        case SECURITY_LEVEL_MIC_64:
        case SECURITY_LEVEL_MIC_128:
            // Data authenticity without data confidentiality
            p_aes_ccm_data->auth_data           = p_mac_hdr;
            p_aes_ccm_data->auth_data_len       = mac_hdr_len + mac_payload_len;
            p_aes_ccm_data->plain_text_data     = NULL;
            p_aes_ccm_data->plain_text_data_len = 0;
            break;

        case SECURITY_LEVEL_ENC_MIC_32:
        case SECURITY_LEVEL_ENC_MIC_64:
        case SECURITY_LEVEL_ENC_MIC_128:
            // Data authenticity and data confidentiality
            p_aes_ccm_data->auth_data           = p_mac_hdr;
            p_aes_ccm_data->auth_data_len       = mac_hdr_len;
            p_aes_ccm_data->plain_text_data     = p_mac_payload;
            p_aes_ccm_data->plain_text_data_len = mac_payload_len;
            break;

        default:
            result = false;
            break;
    }

    return result;
}

/**
 * @brief Retrieves key to be used for the AES CCM transformation.
 *
 * @param[in]   p_frame_data     Pointer to the frame parser data.
 * @param[out]  p_aes_ccm_data   Pointer to AES CCM transformation data to be filled.
 */
bool aes_ccm_data_key_prepare(const nrf_802154_frame_parser_data_t * p_frame_data,
                              nrf_802154_aes_ccm_data_t            * p_aes_ccm_data)
{
    nrf_802154_key_id_t key_id =
    {
        .mode     = nrf_802154_frame_parser_sec_ctrl_key_id_mode_get(p_frame_data),
        .p_key_id = (uint8_t *)nrf_802154_frame_parser_key_id_get(p_frame_data),
    };

    return NRF_802154_SECURITY_ERROR_NONE ==
           nrf_802154_security_pib_key_use(&key_id, p_aes_ccm_data->key);
}

/**
 * @brief Generates a CCM nonce.
 *
 * @param[in]  p_frame_data   Pointer to the frame parser data.
 * @param[out] p_nonce        Pointer to the buffer to be filled with generated nonce.
 *
 * @retval  true   Nonce was generated successfully.
 * @retval  false  Nonce could not be generated.
 */
bool aes_ccm_nonce_generate(const nrf_802154_frame_parser_data_t * p_frame_data,
                            uint8_t                              * p_nonce)
{
    if ((p_frame_data == NULL) || (p_nonce == NULL))
    {
        return false;
    }

    const uint8_t * p_src_addr = nrf_802154_pib_extended_address_get();
    uint8_t         offset     = 0;

    memcpy_rev(p_nonce, p_src_addr, EXTENDED_ADDRESS_SIZE);
    offset += EXTENDED_ADDRESS_SIZE;

    // Byte order for Frame Counter gets reversed as defined
    // in 802.15.4-2015 Std Chapters 9.3.1 and Annex B.2
    memcpy_rev((p_nonce + offset),
               nrf_802154_frame_parser_frame_counter_get(p_frame_data),
               FRAME_COUNTER_SIZE);
    offset += FRAME_COUNTER_SIZE;

    p_nonce[offset] = nrf_802154_frame_parser_sec_ctrl_sec_lvl_get(p_frame_data);

    return true;
}

/**
 * @brief Prepares _a_ data and _m_ data strings as defined in IEEE 802.15.4-2015 9.3.4.2.
 *
 * @param[in]   p_frame_data      Pointer to the frame parser data.
 * @param[in]   p_aux_sec_hdr     Pointer to Auxiliary Security Header data.
 * @param[out]  p_aes_ccm_data    Pointer to AES CCM transformation data to be filled.
 *
 * @retval  true   Data was prepared successfully.
 * @retval  false  Data could not be prepared.
 */
bool aes_ccm_data_a_data_and_m_data_prepare(
    const nrf_802154_frame_parser_data_t * p_frame_data,
    nrf_802154_aes_ccm_data_t            * p_aes_ccm_data)
{
    bool result;

    switch (nrf_802154_frame_parser_frame_type_get(p_frame_data))
    {
        case FRAME_TYPE_ACK:
        // Fallthrough
        case FRAME_TYPE_DATA:
        // Fallthrough
        case FRAME_TYPE_COMMAND:
            result = a_data_and_m_data_prepare(p_frame_data, p_aes_ccm_data);
            break;

        case FRAME_TYPE_BEACON:
        // Fallthrough
        case FRAME_TYPE_EXTENDED:
        // Fallthrough
        case FRAME_TYPE_FRAGMENT:
        // Fallthrough
        case FRAME_TYPE_MULTIPURPOSE:
            // This frame type is currently unsupported.
            result = false;
            break;

        default:
            // No more frame types exist.
            result = false;
            assert(false);
            break;
    }

    return result;
}

/**
 * @brief Prepares data for AES CCM transformation.
 *
 * @param[in]   p_frame_data     Pointer to the frame parser data.
 * @param[out]  p_aes_ccm_data   Pointer to AES CCM transformation data to be filled.
 *
 * @retval  true    AES CCM transformation data was prepared successfully.
 * @retval  false   AES CCM transformation could not be prepared.
 */
static bool aes_ccm_data_content_prepare(const nrf_802154_frame_parser_data_t * p_frame_data,
                                         nrf_802154_aes_ccm_data_t            * p_aes_ccm_data)
{
    bool retval = false;

    do
    {
        if (!aes_ccm_data_key_prepare(p_frame_data, p_aes_ccm_data))
        {
            // Return immediately if specified key could not be found
            break;
        }

        if (!aes_ccm_nonce_generate(p_frame_data, p_aes_ccm_data->nonce))
        {
            // Return immediately if nonce could not be generated
            break;
        }

        // Fill _a_ data (authenticity) and _m_ data (confidentiality)
        if (!aes_ccm_data_a_data_and_m_data_prepare(p_frame_data, p_aes_ccm_data))
        {
            // Return immediately if transformation data could not be generated
            break;
        }

        // Fill in the remaining data
        p_aes_ccm_data->mic_level = mic_level_from_security_level_get(
            nrf_802154_frame_parser_sec_ctrl_sec_lvl_get(p_frame_data));
        p_aes_ccm_data->raw_frame = (uint8_t *)p_frame_data->p_frame;

        retval = true;
    }
    while (0);

    return retval;
}

bool nrf_802154_encrypt_ack_prepare(const nrf_802154_frame_parser_data_t * p_ack_data)
{
    nrf_802154_aes_ccm_data_t aes_ccm_data;
    bool                      success = false;

    if (!nrf_802154_frame_parser_security_enabled_bit_is_set(p_ack_data))
    {
        success = true;
    }
    else if (aes_ccm_data_content_prepare(p_ack_data, &aes_ccm_data))
    {
        // Algorithm's inputs prepared. Schedule transformation
        success = nrf_802154_aes_ccm_transform_prepare(&aes_ccm_data);
    }
    else
    {
        // Intentionally empty
    }

    return success;
}

void nrf_802154_encrypt_ack_reset(void)
{
    nrf_802154_aes_ccm_transform_reset();
}

bool nrf_802154_encrypt_tx_setup(
    uint8_t                                 * p_frame,
    nrf_802154_transmit_params_t            * p_params,
    nrf_802154_transmit_failed_notification_t notify_function)
{
    nrf_802154_aes_ccm_transform_reset();

    if (p_params->frame_props.is_secured)
    {
        // The frame is already secured. Pass.
        return true;
    }

    nrf_802154_frame_parser_data_t frame_data;
    nrf_802154_aes_ccm_data_t      aes_ccm_data;
    bool                           success = false;

    success = nrf_802154_frame_parser_data_init(p_frame,
                                                p_frame[PHR_OFFSET] + PHR_SIZE,
                                                PARSE_LEVEL_FULL,
                                                &frame_data);
    assert(success);
    (void)success;

    if (!nrf_802154_frame_parser_security_enabled_bit_is_set(&frame_data))
    {
        success = true;
    }
    else if (aes_ccm_data_content_prepare(&frame_data, &aes_ccm_data))
    {
        // Algorithm's inputs prepared. Schedule transformation
        success = nrf_802154_aes_ccm_transform_prepare(&aes_ccm_data);
    }
    else
    {
        success = false;
    }

    if (!success)
    {
        nrf_802154_transmit_done_metadata_t metadata = {};

        metadata.frame_props = p_params->frame_props;
        notify_function(p_frame, NRF_802154_TX_ERROR_KEY_ID_INVALID, &metadata);
    }

    return success;
}

bool nrf_802154_encrypt_tx_started_hook(uint8_t * p_frame)
{
    // The provided pointer is the original buffer. It doesn't need to be changed,
    // because the AES-CCM* module is aware of two separate buffers (original vs work buffer)
    nrf_802154_aes_ccm_transform_start(p_frame);

    return true;
}

void nrf_802154_encrypt_tx_ack_started_hook(uint8_t * p_ack)
{
    nrf_802154_aes_ccm_transform_start(p_ack);
}

bool nrf_802154_encrypt_tx_failed_hook(uint8_t * p_frame, nrf_802154_tx_error_t error)
{
    (void)error;

    nrf_802154_aes_ccm_transform_abort(p_frame);

    return true;
}

void nrf_802154_encrypt_tx_ack_failed_hook(uint8_t * p_ack, nrf_802154_tx_error_t error)
{
    (void)error;

    nrf_802154_aes_ccm_transform_abort(p_ack);
}
