/**
 * 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 <stdbool.h>
#include <string.h>
#include "nordic_common.h"
#include "nrf.h"
#include "coap_api.h"
#include "coap.h"
#include "coap_queue.h"
#include "coap_transport.h"
#include "sdk_common.h"
#include "iot_common.h"
#include "mem_manager.h"
#include "coap_resource.h"
#include "coap_observe_api.h"
#include "coap_observe.h"

#if IOT_COAP_CONFIG_LOG_ENABLED

#define NRF_LOG_MODULE_NAME coap

#define NRF_LOG_LEVEL       IOT_COAP_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR  IOT_COAP_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR IOT_COAP_CONFIG_DEBUG_COLOR

#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

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

#define COAP_ENTRY()                       COAP_TRC(">> %s", __func__)
#define COAP_EXIT()                        COAP_TRC("<< %s", __func__)
#define COAP_EXIT_WITH_RESULT(result)      COAP_TRC("<< %s, result: %d", __func__, result)

#else // IOT_COAP_CONFIG_LOG_ENABLED

#define COAP_TRC(...)                                                                           /**< Disables traces. */
#define COAP_DUMP(...)                                                                          /**< Disables dumping of octet streams. */
#define COAP_ERR(...)                                                                           /**< Disables error logs. */

#define COAP_ENTRY(...)
#define COAP_EXIT(...)
#define COAP_EXIT_WITH_RESULT(...)

#endif // IOT_COAP_CONFIG_LOG_ENABLED

#define COAP_REQUEST_ENTITY_MAX_SIZE (BLE_IPSP_RX_BUFFER_SIZE - (IPV6_IP_HEADER_SIZE + \
                                                                 UDP_HEADER_SIZE))       /** Maximum request entity size. */

SDK_MUTEX_DEFINE(m_coap_mutex)                                                           /**< Mutex variable. Currently unused, this declaration does not occupy any space in RAM. */

static uint32_t              m_token_seed;                                               /**< Token seed provided by application to be used for generating token numbers. */
static uint32_t              m_message_id_counter;                                       /**< Message ID counter, used to generate unique message IDs. */
static coap_error_callback_t m_error_callback;                                           /**< Function pointer to an application CoAP error handler. */

static coap_request_handler_t m_request_handler = NULL;                                  /**< Request handler where to forward all incoming requests. */

#define COAP_MESSAGE_ACK_SET(REMOTE, LOCAL_PORT, MID) {                    \
    memcpy(&m_coap_empty_message.remote, (REMOTE), sizeof(coap_remote_t)); \
    m_coap_empty_message.port.port_number = (LOCAL_PORT);                  \
    m_coap_empty_message.header.id        = (MID);                         \
    m_coap_empty_message.header.type      = COAP_TYPE_ACK;                 \
}

#define COAP_MESSAGE_RST_SET(REMOTE, LOCAL_PORT, MID) {                    \
    memcpy(&m_coap_empty_message.remote, (REMOTE), sizeof(coap_remote_t)); \
    m_coap_empty_message.port.port_number = (LOCAL_PORT);                  \
    m_coap_empty_message.header.id        = (MID);                         \
    m_coap_empty_message.header.type      = COAP_TYPE_RST;                 \
}

static coap_message_t m_coap_empty_message = {
    .header = {
        .version       = 1,
        .type          = COAP_TYPE_ACK,
        .token_len     = 0,
        .code          = COAP_CODE_EMPTY_MESSAGE,
        .id            = 0,
    },
    .p_payload         = NULL,
    .payload_len       = 0,
    .options_count     = 0,
    .p_arg             = NULL,
    .response_callback = NULL,
    .port = {
        .port_number   = 0
    },
    .options_len       = 0,
    .options_offset    = 0,
    .p_data            = NULL,
    .data_len          = 0
};

static inline bool is_ping(coap_message_t * p_message)
{
    return (p_message->header.code == COAP_CODE_EMPTY_MESSAGE) &&
           (p_message->header.type == COAP_TYPE_CON);
}

static inline bool is_ack(coap_message_t * p_message)
{
    return (p_message->header.code == COAP_CODE_EMPTY_MESSAGE) &&
           (p_message->header.type == COAP_TYPE_ACK);
}

static inline bool is_reset(coap_message_t * p_message)
{
    return (p_message->header.type == COAP_TYPE_RST);
}

static inline bool is_con(coap_message_t * p_message)
{
    return (p_message->header.type == COAP_TYPE_CON);
}

static inline bool is_non(coap_message_t * p_message)
{
    return (p_message->header.type == COAP_TYPE_NON);
}

static inline bool is_request(uint8_t message_code)
{
    return (message_code >= 1) && (message_code < 32);
}

static inline bool is_response(uint8_t message_code)
{
    return (message_code >= 64) && (message_code < 192);
}

static inline void app_error_notify(uint32_t err_code, coap_message_t * p_message)
{
    if (m_error_callback != NULL)
    {
        COAP_MUTEX_UNLOCK();

        m_error_callback(err_code, p_message);

        COAP_MUTEX_LOCK();
    }
}

uint32_t coap_init(uint32_t token_rand_seed, coap_transport_init_t * p_transport_param)
{
    COAP_ENTRY();

    uint32_t err_code;

    SDK_MUTEX_INIT(m_coap_mutex);

    COAP_MUTEX_LOCK();

    internal_coap_observe_init();

    m_error_callback = NULL;

    m_token_seed = token_rand_seed;
    (void)m_token_seed;

    m_message_id_counter = 1;

    err_code = coap_transport_init(p_transport_param);
    if (err_code != NRF_SUCCESS)
    {
        COAP_MUTEX_UNLOCK();
        return err_code;
    }

    err_code = coap_queue_init();
    if (err_code != NRF_SUCCESS)
    {
        COAP_MUTEX_UNLOCK();
        return err_code;
    }

    err_code = coap_resource_init();

    COAP_MUTEX_UNLOCK();

    COAP_EXIT();

    return err_code;

}

uint32_t coap_error_handler_register(coap_error_callback_t error_callback)
{
    // TODO: error handling, null pointer, module initilized etc.
    COAP_MUTEX_LOCK();

    m_error_callback = error_callback;

    COAP_MUTEX_UNLOCK();

    return NRF_SUCCESS;
}

uint32_t internal_coap_message_send(uint32_t * p_handle, coap_message_t * p_message)
{
    if (p_message == NULL)
    {
        return (NRF_ERROR_NULL | IOT_COAP_ERR_BASE);
    }

    // Compiled away if COAP_ENABLE_OBSERVE_CLIENT is not set to 1.
    coap_observe_client_send_handle(p_message);

    COAP_ENTRY();

    // Fetch the expected length of the packet serialized by passing length of 0.
    uint16_t expected_length = 0;
    uint32_t err_code = coap_message_encode(p_message, NULL, &expected_length);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    // Allocate a buffer to serialize the message into.
    uint8_t * p_buffer;
    uint32_t request_length = expected_length;
    err_code = nrf_mem_reserve(&p_buffer, &request_length);
    if (err_code != NRF_SUCCESS)
    {
        COAP_TRC("p_buffer alloc error = 0x%08lX!", err_code);
        return err_code;
    }
    memset(p_buffer, 0, request_length);
    COAP_TRC("Alloc mem, p_buffer = %p", (uint8_t *)p_buffer);

    // Serialize the message.
    uint16_t buffer_length = (uint16_t)request_length;
    err_code = coap_message_encode(p_message, p_buffer, &buffer_length);
    if (err_code != NRF_SUCCESS)
    {
        COAP_TRC("Encode error!");
        COAP_TRC("Free mem, p_buffer = %p", p_buffer);
        UNUSED_VARIABLE(nrf_free(p_buffer));

        return err_code;
    }

    err_code = coap_transport_write(&p_message->port, &p_message->remote, p_buffer, buffer_length);

    if (err_code == NRF_SUCCESS)
    {
        if (is_con(p_message) || (is_non(p_message) &&
                                  is_request(p_message->header.code) &&
                                  (p_message->response_callback != NULL)))
        {
            coap_queue_item_t item;
            item.p_arg         = p_message->p_arg;
            item.mid           = p_message->header.id;
            item.callback      = p_message->response_callback;
            item.p_buffer      = p_buffer;
            item.buffer_len    = buffer_length;
            item.timeout_val   = COAP_ACK_TIMEOUT * COAP_ACK_RANDOM_FACTOR;

            if (p_message->header.type == COAP_TYPE_CON)
            {
                item.timeout       = item.timeout_val;
                item.retrans_count = 0;
            }
            else
            {
                item.timeout       = COAP_MAX_TRANSMISSION_SPAN;
                item.retrans_count = COAP_MAX_RETRANSMIT_COUNT;
            }

            item.port      = p_message->port;
            item.token_len = p_message->header.token_len;

            memcpy(&item.remote, &p_message->remote, sizeof(coap_remote_t));
            memcpy(item.token, p_message->token, p_message->header.token_len);

            err_code = coap_queue_add(&item);
            if (err_code != NRF_SUCCESS)
            {
                COAP_TRC("Message queue error = 0x%08lX!", err_code);

                COAP_TRC("Free mem, p_buffer = %p", p_buffer);
                UNUSED_VARIABLE(nrf_free(p_buffer));

                return err_code;
            }

            *p_handle = item.handle;
        }
        else
        {
            *p_handle = COAP_MESSAGE_QUEUE_SIZE;

            COAP_TRC("Free mem, p_buffer = %p", p_buffer);
            UNUSED_VARIABLE(nrf_free(p_buffer));
        }
    }
    else
    {
        COAP_TRC("Free mem, p_buffer = %p", p_buffer);
        UNUSED_VARIABLE(nrf_free(p_buffer));
    }

    COAP_EXIT();

    return err_code;
}


static uint32_t create_response(coap_message_t ** pp_response, coap_message_t * p_request, uint16_t data_size)
{
    uint32_t err_code;

    // Allocate space for a new message.
    uint32_t size = sizeof(coap_message_t);
    err_code = nrf_mem_reserve((uint8_t **)pp_response, &size);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    coap_message_t * p_response = (*pp_response);

    memset(p_response, 0, sizeof(coap_message_t));
    COAP_TRC("Alloc mem, p_response = %p", (uint8_t *)p_response);

    if (data_size > 0)
    {
        // Allocate a scratch buffer for payload and options.
        size = data_size;
        err_code = nrf_mem_reserve(&(p_response->p_data), &size);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        memset(p_response->p_data, 0, size);
        p_response->data_len = size;
        COAP_TRC("Alloc mem, p_response->p_data = %p", p_response->p_data);
    }

    coap_message_conf_t config;
    memset (&config, 0, sizeof(coap_message_conf_t));

    config.token_len        = p_request->header.token_len;
    config.id               = p_request->header.id;
    config.code             = COAP_CODE_404_NOT_FOUND;
    config.port.port_number = p_request->port.port_number;

    memcpy(config.token, p_request->token, p_request->header.token_len);

    if ((coap_msg_type_t)p_request->header.type == COAP_TYPE_CON)
    {
        config.type = COAP_TYPE_ACK;
    }
    else
    {
        config.type = (coap_msg_type_t)p_request->header.type;
    }

    err_code = coap_message_create(p_response, &config);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    (void)coap_message_remote_addr_set(p_response, &p_request->remote);

    return NRF_SUCCESS;
}


/**@brief Common function for sending response error message
 *
 * @param[in] p_message Pointer to the original request message.
 * @param[in] code      CoAP message code to send in the reponse.
 *
 * @retval NRF_SUCCESS  If the response was sent out successfully.
 */
static uint32_t send_error_response(coap_message_t * p_message, uint8_t code)
{
    coap_message_t * p_error_response = NULL;

    uint32_t err_code = create_response(&p_error_response, p_message, COAP_MESSAGE_DATA_MAX_SIZE);
    if (err_code != NRF_SUCCESS)
    {
        // If message could not be created, notify the application.
        app_error_notify(err_code, p_message);
        return err_code;
    }

    // Set the response code.
    p_error_response->header.code = code;

    if (p_error_response != NULL)
    {
        uint32_t handle;
        err_code = internal_coap_message_send(&handle, p_error_response);

        COAP_TRC("Free mem, p_response->p_data = %p", p_error_response->p_data);
        UNUSED_VARIABLE(nrf_free(p_error_response->p_data));

        COAP_TRC("Free mem, p_response = %p", (uint8_t *)p_error_response);
        UNUSED_VARIABLE(nrf_free((uint8_t *)p_error_response));
    }

    return err_code;
}

uint32_t coap_transport_read(const coap_port_t    * p_port,
                             const coap_remote_t  * p_remote,
                             const coap_remote_t  * p_local,
                             uint32_t               result,
                             const uint8_t        * p_data,
                             uint16_t               datalen)
{
    COAP_ENTRY();

    // Discard all packets if not success or truncated.
    if (!(result == NRF_SUCCESS || result == UDP_TRUNCATED_PACKET))
    {
        return NRF_SUCCESS;
    }

    uint32_t err_code;
    coap_message_t * p_message;

    // Allocate space for a new message.
    uint32_t size = sizeof(coap_message_t);
    err_code = nrf_mem_reserve((uint8_t **)&p_message, &size);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    memset(p_message, 0, sizeof(coap_message_t));
    COAP_TRC("Alloc mem, p_message = %p", (uint8_t *)p_message);

    err_code = coap_message_decode(p_message, p_data, datalen);
    if (err_code != NRF_SUCCESS)
    {
        app_error_notify(err_code, p_message);

        UNUSED_VARIABLE(nrf_free((uint8_t *)p_message));
        return err_code;
    }

    // Copy the remote address information.
    memcpy(&p_message->remote, p_remote, sizeof(coap_remote_t));

    // Copy the destination address information.
    if (p_local)
    {
        memcpy(&p_message->local, p_local, sizeof(coap_remote_t));
    }

    // Copy the local port information.
    memcpy(&p_message->port, p_port, sizeof(coap_port_t));

    if (result == UDP_TRUNCATED_PACKET)
    {
        app_error_notify(result, p_message);
    }
    else if (is_ping(p_message))
    {
        COAP_MESSAGE_RST_SET(&p_message->remote, p_message->port.port_number, p_message->header.id);

        uint32_t handle;
        err_code = internal_coap_message_send(&handle, &m_coap_empty_message);
    }
    else if (is_ack(p_message) ||
             is_reset(p_message))
    {
        // Populate the token with the one used sending, before passing it to the application.
        coap_queue_item_t * p_item = NULL;
        err_code = coap_queue_item_by_mid_get(&p_item, p_message->header.id);

        if (err_code == NRF_SUCCESS)
        {
            if (p_item->callback != NULL)
            {
                // As the token is missing from peer, it will be added before giving it to the application.
                memcpy(p_message->token, p_item->token, p_item->token_len);
                p_message->header.token_len = p_item->token_len;

                // Compiled away if COAP_ENABLE_OBSERVE_CLIENT is not set to 1.
                coap_observe_client_response_handle(p_message, p_item);

                COAP_TRC(">> application callback");

                COAP_MUTEX_UNLOCK();

                if (is_ack(p_message))
                {
                    p_item->callback(NRF_SUCCESS, p_item->p_arg, p_message);
                }
                else
                {
                    p_item->callback(COAP_TRANSMISSION_RESET_BY_PEER, p_item->p_arg, p_message);
                }

                COAP_MUTEX_LOCK();

                COAP_TRC("<< application callback");
            }

            COAP_TRC("Free mem, p_item->p_buffer = %p", p_item->p_buffer);
            UNUSED_VARIABLE(nrf_free(p_item->p_buffer));

            // Remove the queue element, as a match occured.
            err_code = coap_queue_remove(p_item);
        }
    }
    else if (is_response(p_message->header.code))
    {
        COAP_TRC("CoAP message type: RESPONSE");

        coap_queue_item_t * p_item;
        err_code = coap_queue_item_by_token_get(&p_item, p_message->token, p_message->header.token_len);
        if (err_code != NRF_SUCCESS)
        {
            // Compiled away if COAP_ENABLE_OBSERVE_CLIENT is not set to 1.
            coap_observe_client_response_handle(p_message, NULL);

            UNUSED_VARIABLE(nrf_free((uint8_t *)p_message));

            COAP_MUTEX_UNLOCK();

            return err_code;
        }

        if (p_item->callback != NULL)
        {
            // Compiled away if COAP_ENABLE_OBSERVE_CLIENT is not set to 1.
            coap_observe_client_response_handle(p_message, p_item);

            COAP_TRC(">> application callback");

            COAP_MUTEX_UNLOCK();

            p_item->callback(NRF_SUCCESS, p_item->p_arg, p_message);

            COAP_MUTEX_LOCK();

            COAP_TRC("<< application callback");
        }

        COAP_TRC("Free mem, p_item->p_buffer = %p", p_item->p_buffer);
        UNUSED_VARIABLE(nrf_free(p_item->p_buffer));

        err_code = coap_queue_remove(p_item);

    }
    else if (is_request(p_message->header.code))
    {
        COAP_TRC("CoAP message type: REQUEST");

        if (m_request_handler != NULL)
        {
            uint32_t return_code = m_request_handler(p_message);

            // If success, then all processing and any responses has been sent
            // by the application callback.

            // If not success, then send an appropriate error message back to the
            // origin with the return_code from the callback.
            if (return_code != NRF_SUCCESS)
            {
                if (return_code == NRF_ERROR_NOT_FOUND)
                {
                    // Send response with provided CoAP code.
                    (void)send_error_response(p_message, COAP_CODE_404_NOT_FOUND);
                }
                else if (return_code == NRF_ERROR_NULL)
                {
                    (void)send_error_response(p_message, COAP_CODE_405_METHOD_NOT_ALLOWED);
                }
                else
                {
                    (void)send_error_response(p_message, COAP_CODE_400_BAD_REQUEST);
                }
            }
        }
        else
        {
            uint8_t * uri_pointers[COAP_RESOURCE_MAX_DEPTH] = {0, };

            uint8_t  uri_path_count = 0;
            uint16_t index;

            for (index = 0; index < p_message->options_count; index++)
            {
                if (p_message->options[index].number == COAP_OPT_URI_PATH)
                {
                    uri_pointers[uri_path_count++] = p_message->options[index].p_data;
                }
            }

            coap_resource_t * found_resource;
            err_code = coap_resource_get(&found_resource, uri_pointers, uri_path_count);

            if (found_resource != NULL)
            {
                if (found_resource->callback != NULL)
                {
                    if (((found_resource->permission) & (1 << ((p_message->header.code) - 1))) > 0) // Has permission for the requested CoAP method.
                    {
                        COAP_MUTEX_UNLOCK();

                        found_resource->callback(found_resource, p_message);

                        COAP_MUTEX_LOCK();
                    }
                    else
                    {
                        // Reply with Method Not Allowed.
                        err_code = send_error_response(p_message, COAP_CODE_405_METHOD_NOT_ALLOWED);
                    }
                }
                else
                {
                    // Reply with Method Not Allowed.
                    err_code = send_error_response(p_message, COAP_CODE_405_METHOD_NOT_ALLOWED);
                }
            }
            else
            {
                // Reply with NOT FOUND.
                err_code = send_error_response(p_message, COAP_CODE_404_NOT_FOUND);
            }
        }
    }

    COAP_TRC("Free mem, p_message = %p", (uint8_t *)p_message);
    UNUSED_VARIABLE(nrf_free((uint8_t *)p_message));

    COAP_EXIT();
    return err_code;
}

uint32_t coap_message_send(uint32_t * p_handle, coap_message_t * p_message)
{
    COAP_MUTEX_LOCK();

    uint32_t err_code = internal_coap_message_send(p_handle, p_message);

    COAP_MUTEX_UNLOCK();

    return err_code;
}

uint32_t coap_message_abort(uint32_t handle)
{

    return NRF_ERROR_NOT_SUPPORTED;
}

uint32_t coap_message_new(coap_message_t ** p_request, coap_message_conf_t * p_config)
{
    COAP_ENTRY();

    uint32_t err_code;

    // If port is not configured, return error and skip initialization of the message.
    if (p_config->port.port_number == 0)
    {
        return (NRF_ERROR_INVALID_PARAM | IOT_COAP_ERR_BASE);
    }

    COAP_MUTEX_LOCK();

    // Allocate space for a new message.
    uint32_t size = sizeof(coap_message_t);
    err_code = nrf_mem_reserve((uint8_t **)p_request, &size);
    if (err_code != NRF_SUCCESS)
    {
        COAP_MUTEX_UNLOCK();
        return err_code;
    }

    memset(*p_request, 0, sizeof(coap_message_t));
    COAP_TRC("Alloc mem, *p_request = %p", (uint8_t *)(*p_request));

    // Allocate a scratch buffer for payload and options.
    size = COAP_MESSAGE_DATA_MAX_SIZE;
    err_code = nrf_mem_reserve(&((*p_request)->p_data), &size);
    if (err_code != NRF_SUCCESS)
    {
        COAP_TRC("Allocation of message data buffer failed!");

        COAP_TRC("Free mem, *p_request = %p", (uint8_t *)(*p_request));
        UNUSED_VARIABLE(nrf_free((uint8_t *)(*p_request)));

        COAP_MUTEX_UNLOCK();
        return err_code;
    }

    memset((*p_request)->p_data, 0, size);
    (*p_request)->data_len = size;

    COAP_TRC("Alloc mem, (*p_request)->p_data = %p", (uint8_t *)((*p_request)->p_data));

    if (p_config->id == 0) // Message id is not set, generate one.
    {
        p_config->id = m_message_id_counter++;
    }

    err_code = coap_message_create(*p_request, p_config);

    COAP_MUTEX_UNLOCK();

    COAP_EXIT_WITH_RESULT(err_code);

    return err_code;
}

uint32_t coap_message_delete(coap_message_t * p_message)
{
    COAP_ENTRY();

    COAP_MUTEX_LOCK();

    //If this is a request free the coap_message_t and the data buffer.

    COAP_TRC("Free mem, p_message->p_data = %p", p_message->p_data);
    UNUSED_VARIABLE(nrf_free(p_message->p_data));

    COAP_TRC("Free mem, p_message = %p", (uint8_t *)p_message);
    UNUSED_VARIABLE(nrf_free((uint8_t *)p_message));


    COAP_MUTEX_UNLOCK();

    COAP_EXIT();

    return NRF_SUCCESS;
}


uint32_t coap_time_tick(void)
{
    COAP_MUTEX_LOCK();

    coap_transport_process();

    // Loop through the message queue to see if any packets needs retransmission, or has timed out.
    coap_queue_item_t * p_item = NULL;
    while (coap_queue_item_next_get(&p_item, p_item) == NRF_SUCCESS)
    {
        if (p_item->timeout == 0)
        {
            // If there is still retransmission attempts left.
            if (p_item->retrans_count < COAP_MAX_RETRANSMIT_COUNT)
            {
                p_item->timeout     = p_item->timeout_val * 2;
                p_item->timeout_val = p_item->timeout;
                p_item->retrans_count++;

                // Retransmit the message.
                uint32_t err_code = coap_transport_write(&p_item->port, &p_item->remote, p_item->p_buffer, p_item->buffer_len);
                if (err_code != NRF_SUCCESS)
                {
                    app_error_notify(err_code, NULL);
                }
            }

            // No more retransmission attempts left, or max transmit span reached.
            if ((p_item->timeout > COAP_MAX_TRANSMISSION_SPAN) ||
                (p_item->retrans_count >= COAP_MAX_RETRANSMIT_COUNT))
            {

                COAP_MUTEX_UNLOCK();

                p_item->callback(COAP_TRANSMISSION_TIMEOUT, p_item->p_arg, NULL);

                COAP_MUTEX_LOCK();

                COAP_TRC("Free mem, p_item->p_buffer = %p", p_item->p_buffer);
                UNUSED_VARIABLE(nrf_free(p_item->p_buffer));

                (void)coap_queue_remove(p_item);
            }
        }
        else
        {
            p_item->timeout--;
        }
    }

    COAP_MUTEX_UNLOCK();

    return NRF_SUCCESS;
}

uint32_t coap_request_handler_register(coap_request_handler_t p_request_handler)
{
    COAP_MUTEX_LOCK();

    m_request_handler = p_request_handler;

    COAP_MUTEX_UNLOCK();

    return NRF_SUCCESS;
}

__WEAK void coap_transport_input(void)
{
    // By default not implemented. Transport specific.
}

void coap_input(void)
{
    COAP_MUTEX_LOCK();

    coap_transport_input();

    COAP_MUTEX_UNLOCK();
}
