/**
 * Copyright (c) 2014 - 2018, 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 "nrf_soc.h"
#include "nordic_common.h"
#include "sdk_common.h"
#include "sdk_config.h"
#include "iot_common.h"
#include "iot_pbuffer.h"
#include "mem_manager.h"

#if IOT_PBUFFER_CONFIG_LOG_ENABLED

#define NRF_LOG_MODULE_NAME pbuffer

#define NRF_LOG_LEVEL       IOT_PBUFFER_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR  IOT_PBUFFER_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR IOT_PBUFFER_CONFIG_DEBUG_COLOR

#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

#define PBUFFER_TRC     NRF_LOG_DEBUG                                                               /**< Used for getting trace of execution in the module. */
#define PBUFFER_ERR     NRF_LOG_ERROR                                                               /**< Used for logging errors in the module. */
#define PBUFFER_DUMP    NRF_LOG_HEXDUMP_DEBUG                                                       /**< Used for dumping octet information to get details of bond information etc. */

#define PBUFFER_ENTRY() PBUFFER_TRC(">> %s", __func__)
#define PBUFFER_EXIT()  PBUFFER_TRC("<< %s", __func__)

#else // IOT_PBUFFER_CONFIG_LOG_ENABLED

#define PBUFFER_TRC(...)                                                                            /**< Disables traces. */
#define PBUFFER_DUMP(...)                                                                           /**< Disables dumping of octet streams. */
#define PBUFFER_ERR(...)                                                                            /**< Disables error logs. */

#define PBUFFER_ENTRY(...)
#define PBUFFER_EXIT(...)

#endif // IOT_PBUFFER_CONFIG_LOG_ENABLED

/**
 * @defgroup api_param_check API Parameters check macros.
 *
 * @details Macros that verify parameters passed to the module in the APIs. These macros
 *          could be mapped to nothing in final versions of code to save execution and size.
 *          BLE_HPS_DISABLE_API_PARAM_CHECK should be defined to disable these checks.
 *
 * @{
 */
#if (IOT_PBUFFER_DISABLE_API_PARAM_CHECK == 0)

/**@brief Macro to check is module is initialized before requesting one of the module procedures. */
#define VERIFY_MODULE_IS_INITIALIZED()                                                             \
        if (m_initialization_state == false)                                                       \
        {                                                                                          \
            return (SDK_ERR_MODULE_NOT_INITIALIZED | IOT_PBUFFER_ERR_BASE);                        \
        }

/**@brief Macro to check is module is initialized before requesting one of the module
         procedures but does not use any return code. */
#define VERIFY_MODULE_IS_INITIALIZED_VOID()                                                        \
        if (m_initialization_state == false)                                                       \
        {                                                                                          \
            return;                                                                                \
        }

/**
 * @brief Verify NULL parameters are not passed to API by application.
 */
#define NULL_PARAM_CHECK(PARAM)                                                                    \
        if ((PARAM) == NULL)                                                                       \
        {                                                                                          \
            return (NRF_ERROR_NULL | IOT_PBUFFER_ERR_BASE);                                        \
        }

/**
 * @brief Verify Type field has valid value.
 */
#define VERIFY_PBUFFER_TYPE(TYPE)                                                                  \
        if (((TYPE) == 0) || ((TYPE) > COAP_PACKET_TYPE))                                          \
        {                                                                                          \
           return (NRF_ERROR_INVALID_PARAM | IOT_PBUFFER_ERR_BASE);                                \
        }


/**
 * @brief Verify flags field has valid value.
 */
#define VERIFY_PBUFFER_FLAGS(FLAG)                                                                 \
        if ((uint32_t)(FLAG) > PBUFFER_FLAG_NO_MEM_ALLOCATION)                                     \
        {                                                                                          \
           return (NRF_ERROR_INVALID_PARAM | IOT_PBUFFER_ERR_BASE);                                \
        }

/**
 * @brief Verify flags field has valid value.
 */
#define VERIFY_NON_ZERO_LENGTH(LEN)                                                                \
        if ((LEN) ==0)                                                                             \
        {                                                                                          \
           return (NRF_ERROR_INVALID_LENGTH | IOT_PBUFFER_ERR_BASE);                               \
        }

#else //IOT_PBUFFER_DISABLE_API_PARAM_CHECK

#define VERIFY_MODULE_IS_INITIALIZED()
#define VERIFY_MODULE_IS_INITIALIZED_VOID()
#define NULL_PARAM_CHECK(PARAM)
#define VERIFY_PBUFFER_TYPE(TYPE)
#define VERIFY_PBUFFER_FLAGS(FLAG)
#define VERIFY_NON_ZERO_LENGTH(LEN)

#endif //IOT_PBUFFER_DISABLE_API_PARAM_CHECK

/**
 * @defgroup ble_ipsp_mutex_lock_unlock Module's Mutex Lock/Unlock Macros.
 *
 * @details Macros used to lock and unlock modules. Currently, SDK does not use mutexes but
 *          framework is provided in case need arises to use an alternative architecture.
 * @{
 */
#define PBUFFER_MUTEX_LOCK()   SDK_MUTEX_LOCK(m_pbuffer_mutex)                                /**< Lock module using mutex */
#define PBUFFER_MUTEX_UNLOCK() SDK_MUTEX_UNLOCK(m_pbuffer_mutex)                              /**< Unlock module using mutex */
/** @} */

/** @brief Packet buffer type managed by the module. */
typedef struct
{
   iot_pbuffer_t  buffer;                                                                     /**< Packet buffer being managed. */
   uint32_t       allocated_length;                                                           /**< Length allocated for the buffer. */
}pbuffer_t;

SDK_MUTEX_DEFINE(m_pbuffer_mutex)                                                             /**< Mutex variable. Currently unused, this declaration does not occupy any space in RAM. */
static bool      m_initialization_state  = false;                                             /**< Variable to maintain module initialization state. */
static pbuffer_t m_pbuffer[IOT_PBUFFER_MAX_COUNT];                                            /**< Table of packet buffers managed by the module. */


/**@brief Initializes packet buffer. */
static void pbuffer_init(pbuffer_t * p_buffer)
{
    p_buffer->buffer.p_memory  = NULL;
    p_buffer->buffer.p_payload = NULL;
    p_buffer->buffer.length    = 0;
    p_buffer->buffer.type      = UNASSIGNED_TYPE;
    p_buffer->allocated_length = 0;
}


/**@brief Get size of offset to be used based on the requested type. */
static uint32_t type_offset_get(iot_pbuffer_type_t type)
{
    uint32_t offset = 0;

    switch (type)
    {
        case RAW_PACKET_TYPE:
        {
            offset = 0;
            break;
        }
        case IPV6_PACKET_TYPE:
        {
           offset = IPV6_IP_HEADER_SIZE;
           break;
        }
        case ICMP6_PACKET_TYPE:
        {
            offset = IPV6_IP_HEADER_SIZE + ICMP6_HEADER_SIZE;
            break;
        }
        case UDP6_PACKET_TYPE:   // Fall through.
        {
            offset = IPV6_IP_HEADER_SIZE + UDP_HEADER_SIZE;
            break;
        }
        case COAP_PACKET_TYPE:
        {
            offset = IPV6_IP_HEADER_SIZE + UDP_HEADER_SIZE + COAP_HEADER_SIZE;
            break;
        }
        default:
        {
            // Should never happen.
            break;
        }
    }

    return offset;
}


/**@brief Allocates 'length' sized packet buffer. */
static uint32_t pbuffer_allocate(pbuffer_t ** pp_buffer, uint32_t length, iot_pbuffer_flags_t flags)
{
    uint32_t index;
    uint32_t err_code = (NRF_ERROR_NO_MEM | IOT_PBUFFER_ERR_BASE);


    for (index = 0; index < IOT_PBUFFER_MAX_COUNT; index ++)
    {
        if (m_pbuffer[index].allocated_length == 0)
        {
            // Found a free buffer, allocate.
            PBUFFER_TRC("Found free buffer. Requesting memory allocation.");

            m_pbuffer[index].allocated_length = length;

            if (flags == PBUFFER_FLAG_DEFAULT)
            {
                err_code = nrf_mem_reserve(&m_pbuffer[index].buffer.p_memory, &m_pbuffer[index].allocated_length);
                if (err_code == NRF_SUCCESS)
                {
                    PBUFFER_TRC("Allocated pbuffer at index 0x%08lX", index);
                    (*pp_buffer)  = &m_pbuffer[index];
                }
                else
                {
                    PBUFFER_ERR("Failed to allocate memory for packet buffer of size %ld", length);
                    m_pbuffer[index].allocated_length  = 0;
                }
            }
            else
            {
                PBUFFER_TRC("Allocated pbuffer at index 0x%08lX without any memory allocation.", index);
                (*pp_buffer)  = &m_pbuffer[index];
                err_code = NRF_SUCCESS;
            }
            break;
        }
    }

    return err_code;
}


/**@brief Finds the internal buffer based on the external iot_pbuffer_t. */
static uint32_t pbuffer_find(pbuffer_t ** p_internal_buffer, iot_pbuffer_t * p_buffer)
{
    const uint32_t size  = sizeof (pbuffer_t);
    const uint32_t diff  = (((uint32_t)p_buffer) - ((uint32_t)m_pbuffer));

    if ((diff > (size * IOT_PBUFFER_MAX_COUNT)) ||
        ((diff % size) != 0))
    {
        return (NRF_ERROR_INVALID_ADDR | IOT_PBUFFER_ERR_BASE);
    }

    (*p_internal_buffer) = (pbuffer_t *) p_buffer;

    return NRF_SUCCESS;
}


uint32_t iot_pbuffer_init(void)
{
    uint32_t index;

    PBUFFER_ENTRY();

    SDK_MUTEX_INIT(m_pbuffer_mutex);

    PBUFFER_MUTEX_LOCK();

    for (index = 0; index < IOT_PBUFFER_MAX_COUNT; index++)
    {
        pbuffer_init(&m_pbuffer[index]);
    }

    m_initialization_state = true;

    PBUFFER_EXIT();

    PBUFFER_MUTEX_UNLOCK();

    return NRF_SUCCESS;
}


uint32_t iot_pbuffer_allocate(iot_pbuffer_alloc_param_t    *  p_param,
                              iot_pbuffer_t                ** pp_pbuffer)
{
    VERIFY_MODULE_IS_INITIALIZED();
    NULL_PARAM_CHECK(p_param);
    NULL_PARAM_CHECK(pp_pbuffer);
    VERIFY_PBUFFER_TYPE(p_param->type);
    VERIFY_PBUFFER_FLAGS(p_param->flags);
    VERIFY_NON_ZERO_LENGTH(p_param->length);

    PBUFFER_ENTRY();

    PBUFFER_MUTEX_LOCK();

    uint32_t  err_code;
    uint32_t  offset;
    pbuffer_t * p_alloc_buffer;

    // Get offset to be added to length.
    offset = type_offset_get(p_param->type);

    err_code = pbuffer_allocate(&p_alloc_buffer, ((p_param->length) + offset), p_param->flags);
    if (err_code == NRF_SUCCESS)
    {
        p_alloc_buffer->buffer.length = p_param->length;
        p_alloc_buffer->buffer.type   = p_param->type;

        if (p_param->flags != PBUFFER_FLAG_NO_MEM_ALLOCATION)
        {
            p_alloc_buffer->buffer.p_payload = ((p_alloc_buffer->buffer.p_memory) + offset);
        }

        (*pp_pbuffer) = &p_alloc_buffer->buffer;
    }

    PBUFFER_MUTEX_UNLOCK();

    PBUFFER_EXIT();

    return err_code;
}


uint32_t iot_pbuffer_reallocate(iot_pbuffer_alloc_param_t  * p_param,
                                iot_pbuffer_t              * p_pbuffer)
{
    VERIFY_MODULE_IS_INITIALIZED();
    NULL_PARAM_CHECK(p_param);
    NULL_PARAM_CHECK(p_pbuffer);
    VERIFY_PBUFFER_TYPE(p_param->type);
    VERIFY_PBUFFER_FLAGS(p_param->flags);
    VERIFY_NON_ZERO_LENGTH(p_param->length);

    PBUFFER_ENTRY();

    PBUFFER_MUTEX_LOCK();

    uint32_t    err_code;
    uint32_t    realloc_len;
    pbuffer_t * p_alloc_buffer;

    // Ensure pointer provided is in the ranged managed by the module.
    err_code = pbuffer_find(&p_alloc_buffer, p_pbuffer);

    if (err_code == NRF_SUCCESS)
    {
        // Get realloc_len to be added to length.
        const uint32_t offset = type_offset_get(p_param->type);
        realloc_len = p_param->length + offset;

        // Check if requested length cannot be accommodated in the allocated buffer.
        if (realloc_len > p_alloc_buffer->allocated_length)
        {
            // No, it cannot be, request a new buffer.
            uint8_t * p_new_mem;

            if (p_param->flags != PBUFFER_FLAG_NO_MEM_ALLOCATION)
            {
                err_code = nrf_mem_reserve(&p_new_mem, &realloc_len);
                if (err_code == NRF_SUCCESS)
                {
                    // Copy data into the new buffer.
                    memcpy (p_new_mem,
                            p_pbuffer->p_memory,
                            p_alloc_buffer->allocated_length);

                    // Now free the old buffer. Perform the free
                    nrf_free(p_alloc_buffer->buffer.p_memory);

                    p_alloc_buffer->allocated_length = realloc_len;
                    p_alloc_buffer->buffer.p_memory  = p_new_mem;
                    p_alloc_buffer->buffer.length    = p_param->length;
                }
            }
        }

        if (err_code == NRF_SUCCESS)
        {
            p_alloc_buffer->buffer.length    = p_param->length;
            p_alloc_buffer->buffer.type      = p_param->type;

            if (p_param->flags == PBUFFER_FLAG_DEFAULT)
            {
                p_alloc_buffer->buffer.p_payload = (p_alloc_buffer->buffer.p_memory + offset);
            }
        }
    }
    else
    {
        PBUFFER_ERR("Cannot find buffer to be freed.");
    }

    PBUFFER_MUTEX_UNLOCK();

    PBUFFER_EXIT();

    return err_code;
}


uint32_t iot_pbuffer_free(iot_pbuffer_t  * p_pbuffer, bool free_flag)
{
    VERIFY_MODULE_IS_INITIALIZED();
    NULL_PARAM_CHECK(p_pbuffer);

    PBUFFER_ENTRY();

    PBUFFER_MUTEX_LOCK();

    uint32_t   err_code;
    pbuffer_t * p_alloc_buffer;

    // Ensure pointer provided is in the ranged managed by the module.
    err_code = pbuffer_find(&p_alloc_buffer, p_pbuffer);
    if (err_code == NRF_SUCCESS)
    {
        if (free_flag == true)
        {
            nrf_free(p_alloc_buffer->buffer.p_memory);
        }
        pbuffer_init(p_alloc_buffer);
    }
    else
    {
        PBUFFER_ERR("Cannot find buffer to be freed.");
    }

    PBUFFER_MUTEX_UNLOCK();

    PBUFFER_EXIT();

    return err_code;
}
