/**
 * Copyright (c) 2013 - 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.
 *
 */
/** @file
 *
 * @defgroup ble_sdk_6lowpan 6LoWPAN Adaptation Layer
 * @{
 * @ingroup ble_sdk_iot
 * @brief 6LoWPAN Adaptation Layer
 *
 * @details This module enables 6LoWPAN over Bluetooth Low Energy.
 *
 */

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "nrf_soc.h"
#include "nordic_common.h"
#include "ble_ipsp.h"
#include "ble_6lowpan.h"
#include "iot_common.h"
#include "iot_context_manager.h"
#include "app_util_platform.h"
#include "mem_manager.h"

/**
 * @defgroup ble_sdk_6lowpan Module's Log Macros
 * @details Macros used for creating module logs which can be useful in understanding handling
 *          of events or actions on API requests. These are intended for debugging purposes and
 *          can be enabled by defining the IOT_BLE_6LOWPAN_CONFIG_LOG_ENABLED.
 * @note If NRF_LOG_ENABLED is disabled, having IOT_BLE_6LOWPAN_CONFIG_LOG_ENABLED
 *       has no effect.
 * @{
 */

#if IOT_BLE_6LOWPAN_CONFIG_LOG_ENABLED

#define NRF_LOG_MODULE_NAME 6lowpan

#define NRF_LOG_LEVEL       IOT_BLE_6LOWPAN_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR  IOT_BLE_6LOWPAN_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR IOT_BLE_6LOWPAN_CONFIG_DEBUG_COLOR

#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

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

#define BLE_6LOWPAN_ENTRY() BLE_6LOWPAN_TRC(">> %s", __func__)
#define BLE_6LOWPAN_EXIT()  BLE_6LOWPAN_TRC("<< %s", __func__)

#else // IOT_BLE_6LOWPAN_CONFIG_LOG_ENABLED

#define BLE_6LOWPAN_TRC(...)                                                                           /**< Disables traces. */
#define BLE_6LOWPAN_DUMP(...)                                                                          /**< Disables dumping of octet streams. */
#define BLE_6LOWPAN_ERR(...)                                                                           /**< Disables error logs. */

#define BLE_6LOWPAN_ENTRY(...)
#define BLE_6LOWPAN_EXIT(...)

#endif // IOT_BLE_6LOWPAN_CONFIG_LOG_ENABLED



/** @} */

/**
 * @defgroup ble_6lowpan_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 BLE_6LOWPAN_MUTEX_LOCK()   SDK_MUTEX_LOCK(m_6lowpan_mutex)                                  /**< Lock module using mutex */
#define BLE_6LOWPAN_MUTEX_UNLOCK() SDK_MUTEX_UNLOCK(m_6lowpan_mutex)                                /**< Unlock module using mutex */
/** @} */

/**
 * @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_6LOWPAN_DISABLE_API_PARAM_CHECK should be set to 0 to enable these checks.
 *
 * @{
 */

#if (BLE_6LOWPAN_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_event_handler == NULL)                                                                   \
    {                                                                                              \
        return (SDK_ERR_MODULE_NOT_INITIALIZED | BLE_6LOWPAN_ERR_BASE);                            \
    }

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

/**@brief Check if packet has at least IP Header in it (40 bytes). */
#define PACKET_LENGTH_CHECK(PARAM)                                                                 \
    if ((PARAM) < IPV6_IP_HEADER_SIZE)                                                             \
    {                                                                                              \
        return (NRF_ERROR_INVALID_PARAM | BLE_6LOWPAN_ERR_BASE);                                   \
    }

#else // BLE_6LOWPAN_DISABLE_API_PARAM_CHECK

#define VERIFY_MODULE_IS_INITIALIZED()
#define NULL_PARAM_CHECK(PARAM)
#define PACKET_LENGTH_CHECK(PARAM)

#endif // BLE_6LOWPAN_DISABLE_API_PARAM_CHECK

/** @} */

/**@brief Maximum different between compressed and uncompressed packet. */
#define IPHC_MAX_COMPRESSED_DIFF (IPV6_IP_HEADER_SIZE + UDP_HEADER_SIZE - 7)

/**@brief Transmit FIFO mask. */
#define TX_FIFO_MASK (BLE_6LOWPAN_TX_FIFO_SIZE - 1)

/**@brief Value and position of IPHC dispatch. */
#define IPHC_START_DISPATCH               0x03
#define IPHC_START_DISPATCH_POS           5

/**@brief Values and positions of IPHC fields. */
#define IPHC_TF_MASK                      0x18
#define IPHC_TF_POS                       3
#define IPHC_NH_MASK                      0x04
#define IPHC_NH_POS                       2
#define IPHC_HLIM_MASK                    0x03
#define IPHC_HLIM_POS                     0
#define IPHC_CID_MASK                     0x80
#define IPHC_CID_POS                      7
#define IPHC_SAC_MASK                     0x40
#define IPHC_SAC_POS                      6
#define IPHC_SAM_MASK                     0x30
#define IPHC_SAM_POS                      4
#define IPHC_M_MASK                       0x08
#define IPHC_M_POS                        3
#define IPHC_DAC_MASK                     0x04
#define IPHC_DAC_POS                      2
#define IPHC_DAM_MASK                     0x03
#define IPHC_DAM_POS                      0

/**@brief IPHC Traffic Flow compression. */
#define IPHC_TF_DSCP_MASK                 0x3F
#define IPHC_TF_ECN_MASK                  0xC0
#define IPHC_TF_ECN_POS                   6

/**@brief IPHC values of fields. */
#define IPHC_TF_00                        0x00
#define IPHC_TF_01                        0x01
#define IPHC_TF_10                        0x02
#define IPHC_TF_11                        0x03
#define IPHC_NH_0                         0x00
#define IPHC_NH_1                         0x01
#define IPHC_HLIM_00                      0x00
#define IPHC_HLIM_01                      0x01
#define IPHC_HLIM_10                      0x02
#define IPHC_HLIM_11                      0x03
#define IPHC_CID_0                        0x00
#define IPHC_CID_1                        0x01
#define IPHC_SAC_0                        0x00
#define IPHC_SAC_1                        0x01
#define IPHC_SAM_00                       0x00
#define IPHC_SAM_01                       0x01
#define IPHC_SAM_10                       0x02
#define IPHC_SAM_11                       0x03
#define IPHC_M_0                          0x00
#define IPHC_M_1                          0x01
#define IPHC_DAC_0                        0x00
#define IPHC_DAC_1                        0x01
#define IPHC_DAM_00                       0x00
#define IPHC_DAM_01                       0x01
#define IPHC_DAM_10                       0x02
#define IPHC_DAM_11                       0x03

/**@brief IPHC Context Identifier compression. */
#define IPHC_CID_SOURCE_MASK              0xF0
#define IPHC_CID_SOURCE_POS               4
#define IPHC_CID_DESTINATION_MASK         0x0F
#define IPHC_CID_DESTINATION_POS          0

/**@brief IPHC Next Header Compression dispatches. */
#define IPHC_NHC_UDP_DISPATCH             0xF0
#define IPHC_NHC_UDP_MASK                 0xF8
#define IPHC_NHC_EXT_DISPATCH             0xE0
#define IPHC_NHC_EXT_MASK                 0xF0

/**@brief IPHC Next Header Compression UDP fields. */
#define IPHC_NHC_UDP_CSUM_MASK            0x04
#define IPHC_NHC_UDP_CSUM_POS             0x02
#define IPHC_NHC_UDP_PORTS_MASK           0x03
#define IPHC_NHC_UDP_PORTS_POS            0x00
#define IPHC_NHC_UDP_PORTS_00             0x00
#define IPHC_NHC_UDP_PORTS_01             0x01
#define IPHC_NHC_UDP_PORTS_10             0x02
#define IPHC_NHC_UDP_PORTS_11             0x03

#define IPHC_NHC_UDP_COMPRESSION_MAX_MASK 0xFFF0
#define IPHC_NHC_UDP_COMPRESSION_MAX      0xF0B0
#define IPHC_NHC_UDP_COMPRESSION_MIN_MASK 0xFF00
#define IPHC_NHC_UDP_COMPRESSION_MIN      0xF000

/**@brief IPHC Next Header Compression Extended Header fields. */
#define IPHC_NHC_EXT_EID_MASK             0x0E
#define IPHC_NHC_EXT_EID_POS              0x01
#define IPHC_NHC_EXT_EID_HOP_BY_HOP       0x00
#define IPHC_NHC_EXT_EID_ROUTING          0x01
#define IPHC_NHC_EXT_EID_FRAGMENT         0x02
#define IPHC_NHC_EXT_EID_DESTINATION      0x03
#define IPHC_NHC_EXT_EID_MOBILITY         0x04
#define IPHC_NHC_EXT_EID_IPV6             0x07

/**@brief IPHC default value of IPv6 Header fields. */
#define IPHC_IPHEADER_VER_TC              0x60
#define IPHC_IPHEADER_TC_FL               0x00
#define IPHC_IPHEADER_FL                  0x00

/**@brief Check if address can be fully elidable. */
#define IPV6_ADDRESS_IS_FULLY_ELIDABLE(ll_addr, addr)             \
    (((addr)->u8[8] == (((ll_addr[0]) ^ IPV6_IID_FLIP_VALUE))) && \
     ((addr)->u8[9] == ll_addr[1]) &&                             \
     ((addr)->u8[10] == ll_addr[2]) &&                            \
     ((addr)->u8[11] == ll_addr[3]) &&                            \
     ((addr)->u8[12] == ll_addr[4]) &&                            \
     ((addr)->u8[11] == 0xff) &&                                  \
     ((addr)->u8[12] == 0xfe) &&                                  \
     ((addr)->u8[13] == ll_addr[5]) &&                            \
     ((addr)->u8[14] == ll_addr[6]) &&                            \
     ((addr)->u8[15] == ll_addr[7])                               \
    )

/**@brief Check if address is 16-bit and can be compressed.
 *        16-bit COMPRESSABLE format: ::0000:00ff:fe00:XXXX.
 */
#define IPV6_ADDRESS_IS_16_BIT_COMPRESSABLE(addr)       \
    (((addr)->u8[8] == 0) &&                            \
     ((addr)->u8[9] == 0) &&                            \
     ((addr)->u8[10] == 0) &&                           \
     ((addr)->u8[11] == 0xff) &&                        \
     ((addr)->u8[12] == 0xfe) &&                        \
     ((addr)->u8[13] == 0)                              \
    )

/**@brief Check if address is 48-bit multi-cast and can be compressed.
 *        48-bit COMPRESSABLE format: FFXX::00XX:XXXX:XXXX.
 */
#define IPV6_ADDRESS_IS_48_BIT_MCAST_COMPRESSABLE(addr) \
    (((addr)->u16[1] == 0) &&                           \
     ((addr)->u16[2] == 0) &&                           \
     ((addr)->u16[3] == 0) &&                           \
     ((addr)->u16[4] == 0) &&                           \
     ((addr)->u8[10] == 0)                              \
    )

/**@brief Check if address is 32-bit multi-cast and can be compressed.
 *        32-bit COMPRESSABLE format: FFXX::00XX:XXXX.
 */
#define IPV6_ADDRESS_IS_32_BIT_MCAST_COMPRESSABLE(addr) \
    (((addr)->u16[1] == 0) &&                           \
     ((addr)->u32[1] == 0) &&                           \
     ((addr)->u32[2] == 0) &&                           \
     ((addr)->u8[12] == 0)                              \
    )

/**@brief Check if address is 8-bit multi-cast and can be compressed.
 *        8-bit COMPRESSABLE format: FF02::XX.
 */
#define IPV6_ADDRESS_IS_8_BIT_MCAST_COMPRESSABLE(addr)  \
    (((addr)->u8[1] == 2) &&                            \
     ((addr)->u16[1] == 0) &&                           \
     ((addr)->u32[1] == 0) &&                           \
     ((addr)->u32[2] == 0) &&                           \
     ((addr)->u16[6] == 0) &&                           \
     ((addr)->u8[14] == 0)                              \
    )

/******************************************************************************
 * 6LoWPAN Core and Transport structures.
 ******************************************************************************/

/**@brief Element of TX Queue. */
typedef struct
{
    uint8_t * p_mem_block;                                                                          /**< Base address of memory block, using for release the buffer. */
    uint8_t * p_data;                                                                               /**< Pointer to TX Data. */
    uint16_t  data_len;                                                                             /**< Size of TX data.  */
} tx_packet_t;

/**@brief A TX Queue (FIFO) structure. */
typedef struct
{
    tx_packet_t       packets[BLE_6LOWPAN_TX_FIFO_SIZE];                                            /**< Array of TX packet in FIFO.              */
    volatile uint32_t read_pos;                                                                     /**< Next read position in the FIFO buffer.   */
    volatile uint32_t write_pos;                                                                    /**< Next write position in the FIFO buffer.  */
} tx_fifo_t;

/**@brief Transport instance structure. */
typedef struct
{
    iot_interface_t         interface;
    ble_ipsp_handle_t       handle;
    tx_fifo_t               tx_fifo;
    tx_packet_t           * p_tx_cur_packet;
} transport_instance_t;

/******************************************************************************
 * @name Global variables
 *****************************************************************************/

/**@brief Application Event Handler. */
static ble_6lowpan_evt_handler_t m_event_handler = NULL;

/**@brief Hop Limit options. */
static const uint8_t m_hop_limit[] = {0, 1, 64, 255};

/**@brief Link-local prefix. */
static const uint8_t m_link_local_prefix[] = {0xFE, 0x80};

/**@brief Additional extenders for EUI-48. */
static const uint8_t m_link_local_16_middle[] = {0xFF, 0xFE};

/**@brief nRF EUI-64 link-layer address */
static eui64_t m_ll_addr = {{0, 0, 0, 0, 0, 0, 0, 0}};

/**@brief Transport interfaces table. */
static transport_instance_t m_instances[BLE_6LOWPAN_MAX_INTERFACE];

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

/******************************************************************************
 * @name 6LoWPAN core functions
 *****************************************************************************/

/**@brief Function for checking if IID can be completely elided. This situation
 *        may happen when receiver can still reconstruct IPv6 address by using context
 *        prefix and Link Layer address.
 *
 * @param[in]   p_addr     Pointer to IPv6 address.
 * @param[in]   p_context  Pointer to context entry that is compressed with.
 * @param[in]   p_ll_addr  Pointer to link layer address of BT-LE device.
 *
 * @return      True if IID can be elided, False otherwise.
 */
static bool is_context_cover_iid(const ipv6_addr_t   * p_addr,
                                 const iot_context_t * p_context,
                                 const eui64_t       * p_ll_addr)
{
    uint32_t start_byte, offset;

    // Context covers IPv6 address by its size.
    if (p_context->prefix_len == 128)
    {
        return true;
    }

    // Check if IID can be retrieved in case of longer prefix than 64 bits.
    if (p_context->prefix_len > 64)
    {
        // Check only IID fields that are not covered by context prefix.
        start_byte = p_context->prefix_len >> 3;
        offset = p_context->prefix_len % 8;

        // Check all bytes from the second one.
        if (start_byte == 15 ||
            0 == memcmp(&p_addr->u8[start_byte+1], &p_ll_addr->identifier[start_byte-7], 15-start_byte))
        {
            // Then check first byte.
            return (p_addr->u8[start_byte] << offset) == (p_ll_addr->identifier[start_byte-8] << offset);
        }
    }

    return false;
}

/**@brief Function for decoding Next Header Compression.
 *        It supports UDP header decompression.
 *
 * @param[in]   p_iphc   Pointer to currently process IPHC field.
 * @param[in]   p_data   Pointer to constructing uncompressed IP packet.
 * @param[in]   p_length Place of UDP header in case of Extension Header.
 * @param[out]  p_length Length of the constructed uncompressed header.
 *
 * @return      Number of processed IPHC field.
 */
static uint32_t iphc_nhc_decode(uint8_t * p_iphc, uint8_t * p_data, uint16_t * p_length)
{
    uint8_t   nhc_dispatch = *p_iphc;
    uint8_t * p_nhc        = p_iphc;

    ipv6_header_t * iphdr = (ipv6_header_t *)&p_data[0];

    // UDP Next Header Compression.
    if ((nhc_dispatch & IPHC_NHC_UDP_MASK) == IPHC_NHC_UDP_DISPATCH)
    {
        udp6_header_t * udphdr = (udp6_header_t * )&p_data[IPV6_IP_HEADER_SIZE + *p_length];

        iphdr->next_header = IPV6_NEXT_HEADER_UDP;

        // Start length from UDP Header Size.
        *p_length += UDP_HEADER_SIZE;
        p_nhc     += 1;

        switch ((nhc_dispatch & IPHC_NHC_UDP_PORTS_MASK) >> IPHC_NHC_UDP_PORTS_POS)
        {
            case IPHC_NHC_UDP_PORTS_00:
                memcpy(&udphdr->srcport, p_nhc, 2);
                memcpy(&udphdr->destport, p_nhc + 2, 2);
                p_nhc += 4;
                break;

            case IPHC_NHC_UDP_PORTS_01:
                memcpy(&udphdr->srcport, p_nhc, 2);
                udphdr->destport = HTONS(IPHC_NHC_UDP_COMPRESSION_MIN | *(p_nhc + 2));
                p_nhc           += 3;
                break;

            case IPHC_NHC_UDP_PORTS_10:
                udphdr->srcport = HTONS(IPHC_NHC_UDP_COMPRESSION_MIN | *p_nhc);
                memcpy(&udphdr->destport, p_nhc + 1, 2);
                p_nhc += 3;
                break;

            case IPHC_NHC_UDP_PORTS_11:
                udphdr->srcport  = HTONS((IPHC_NHC_UDP_COMPRESSION_MAX | ((*p_nhc & 0xf0) >> 4)));
                udphdr->destport = HTONS((IPHC_NHC_UDP_COMPRESSION_MAX | ((*p_nhc & 0x0f))));
                p_nhc           += 1;
                break;
        }

        if ((nhc_dispatch & IPHC_NHC_UDP_CSUM_MASK) >> IPHC_NHC_UDP_CSUM_POS)
        {
            udphdr->checksum = 0;
        }
        else
        {
            memcpy(&udphdr->checksum, p_nhc, 2);
            p_nhc += 2;
        }
    }

    return (p_nhc - p_iphc);
}

/**@brief Function for encoding Next Header Compression.
 *        It supports UDP header compression.
 *
 * @param[in]   p_iphc   Pointer to currently process IPHC field.
 * @param[in]   p_data   Pointer to constructing uncompressed IP packet.
 * @param[in]   p_length Place of UDP header in case of Extension Header.
 * @param[out]  p_length Length of the constructed uncompressed header.
 *
 * @return      Number of processed IPHC field.
 */
static uint32_t iphc_nhc_encode(uint8_t * p_iphc, const uint8_t * p_data, uint16_t * p_length)
{
    uint8_t      * p_nhc = p_iphc;
    ipv6_header_t * iphdr = (ipv6_header_t *)p_data;

    switch (iphdr->next_header)
    {
        case IPV6_NEXT_HEADER_UDP:
        {
            udp6_header_t * udphdr = (udp6_header_t * )&p_data[IPV6_IP_HEADER_SIZE + *p_length];
            *p_iphc = IPHC_NHC_UDP_DISPATCH;
            p_nhc  += 1;

            // Full 4-bit compression for source and destination ports.
            if ( ((HTONS(udphdr->srcport) & IPHC_NHC_UDP_COMPRESSION_MAX_MASK) ==
                  IPHC_NHC_UDP_COMPRESSION_MAX) &&
                 ((HTONS(udphdr->destport) & IPHC_NHC_UDP_COMPRESSION_MAX_MASK) ==
                  IPHC_NHC_UDP_COMPRESSION_MAX))
            {
                *p_iphc |= (IPHC_NHC_UDP_PORTS_11 >> IPHC_NHC_UDP_PORTS_POS);

                *p_nhc =
                    (((HTONS(udphdr->srcport) & 0x0f) << 4) | (HTONS(udphdr->destport) & 0x0f));
                p_nhc += 1;
            }
            // Source port compressed, destination in-line.
            else if ((HTONS(udphdr->srcport) & IPHC_NHC_UDP_COMPRESSION_MIN_MASK) ==
                     IPHC_NHC_UDP_COMPRESSION_MIN)
            {
                *p_iphc |= (IPHC_NHC_UDP_PORTS_10 >> IPHC_NHC_UDP_PORTS_POS);

                *p_nhc = (HTONS(udphdr->srcport) & 0xff);
                p_nhc += 1;

                memcpy(p_nhc, &udphdr->destport, 2);
                p_nhc += 2;
            }
            // Source port in-line, destination compressed.
            else if ((HTONS(udphdr->destport) & IPHC_NHC_UDP_COMPRESSION_MIN_MASK) ==
                     IPHC_NHC_UDP_COMPRESSION_MIN)
            {
                *p_iphc |= (IPHC_NHC_UDP_PORTS_01 >> IPHC_NHC_UDP_PORTS_POS);

                memcpy(p_nhc, &udphdr->srcport, 2);
                p_nhc += 2;

                *p_nhc = (HTONS(udphdr->destport) & 0xff);
                p_nhc += 1;
            }
            // Source and destination port in-line.
            else
            {
                *p_iphc |= (IPHC_NHC_UDP_PORTS_00 >> IPHC_NHC_UDP_PORTS_POS);

                memcpy(p_nhc, &udphdr->srcport, 2);
                memcpy(p_nhc + 2, &udphdr->destport, 2);
                p_nhc += 4;
            }

            // Checksum always in-line, [RFC4944] disallows the compression of the
            // UDP checksum. The compressor MUST NOT set the C bit unless it has received
            // authorization.
            memcpy(p_nhc, &udphdr->checksum, 2);
            p_nhc += 2;

            // Set UDP ext header size.
            *p_length = UDP_HEADER_SIZE;

            break;
        }
    }

    return (p_nhc - p_iphc);
}


 /**@brief Function for checking if it's possible to use NHC.
 *
 * @param[in]   next_header Value of Next Header field in IPv6 packet.
 *
 * @return      Returns 1 if header can be compressed, otherwise 0.
 */
static uint32_t iphc_nhc_compressable(uint8_t next_header)
{
    switch (next_header)
    {
        case IPV6_NEXT_HEADER_UDP:
            return 1;
    }

    return 0;
}


/**@brief Function for decoding IPHC (IP Header Compression) defined in
 *        IETF RFC 6282.
 *
 * @param[in]   p_instance   Transport instance from where packet came.
 * @param[in]   p_input      Pointer to received packet from IPSP module.
 * @param[in]   input_len    Length of received packet.
 * @param[in]   p_output     Pointer to allocated buffer for decompressed packet.
 * @param[out]  p_output     Pointer to decompressed IPv6 packet.
 * @param[out]  p_output_len Length of decompressed IPv6 packet.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t iphc_decode(iot_interface_t      * p_interface,
                            uint8_t              * p_output,
                            uint16_t             * p_output_len,
                            uint8_t              * p_input,
                            uint16_t               input_len,
                            iot_context_id_t     * p_rx_contexts)
{
    uint32_t                retval      = NRF_SUCCESS;
    uint32_t                err_code    = NRF_SUCCESS;
    uint8_t               * p_iphc      = p_input;
    uint8_t                 sci         = IPV6_CONTEXT_IDENTIFIER_NONE;
    uint8_t                 dci         = IPV6_CONTEXT_IDENTIFIER_NONE;
    uint16_t                nhc_length  = 0;
    iot_context_t         * p_ctx       = NULL;

    // IPv6 headers used in decompression.
    ipv6_header_t         * p_iphdr     = (ipv6_header_t *)p_output;
    udp6_header_t         * p_udphdr    = (udp6_header_t *)&p_output[IPV6_IP_HEADER_SIZE];

    // Check if format of packet is correct.
    if ((p_input[0] >> IPHC_START_DISPATCH_POS) != IPHC_START_DISPATCH)
    {
        BLE_6LOWPAN_ERR("[6LoWPAN] Packet has incorrect IPHC structure!");

        return NRF_ERROR_INVALID_DATA;
    }

    // IPHC basic form has 2 bytes.
    p_iphc += 2;

    // RFC6282: An additional 8-bit Context Identifier Extension field
    // immediately follows the Destination Address Mode (DAM) field.
    if ((p_input[1] & IPHC_CID_MASK))
    {
        sci     = ((*p_iphc & IPHC_CID_SOURCE_MASK) >> IPHC_CID_SOURCE_POS);
        dci     = ((*p_iphc & IPHC_CID_DESTINATION_MASK) >> IPHC_CID_DESTINATION_POS);
        p_iphc += 1;
    }

    // Set proper context identifiers.
    p_rx_contexts->src_cntxt_id  = sci;
    p_rx_contexts->dest_cntxt_id = dci;

    switch ((p_input[0] & IPHC_TF_MASK) >> IPHC_TF_POS)
    {
        case IPHC_TF_11:
            // Elide Traffic Class and Flow Label.
            p_iphdr->version_traffic_class   = IPHC_IPHEADER_VER_TC;
            p_iphdr->traffic_class_flowlabel = IPHC_IPHEADER_TC_FL;
            p_iphdr->flowlabel               = IPHC_IPHEADER_FL;
            break;

        case IPHC_TF_10:
            // Elide Flow Label.
            p_iphdr->version_traffic_class   = IPHC_IPHEADER_VER_TC | ((*p_iphc & IPHC_TF_DSCP_MASK) >> 2);
            p_iphdr->traffic_class_flowlabel = ((*p_iphc & 0x03) << 6) |
                                             ((*p_iphc & IPHC_TF_ECN_MASK) >> 2);
            p_iphdr->flowlabel = IPHC_IPHEADER_FL;
            p_iphc          += 1;
            break;

        case IPHC_TF_01:
            // Elide DSCP, carry ECN and Flow Label.
            p_iphdr->version_traffic_class   = IPHC_IPHEADER_VER_TC;
            p_iphdr->traffic_class_flowlabel = ((*p_iphc & IPHC_TF_ECN_MASK) >> 2) |
                                             ((*p_iphc & 0x0f));
            memcpy(&p_iphdr->flowlabel, p_iphc + 1, 2);
            p_iphc += 3;
            break;

        case IPHC_TF_00:
            // Flow Label and Traffic Class in-line.
            p_iphdr->version_traffic_class   = IPHC_IPHEADER_VER_TC | ((*p_iphc & IPHC_TF_DSCP_MASK) >> 2);
            p_iphdr->traffic_class_flowlabel = ((*p_iphc & 0x03) << 6) |
                                             ((*p_iphc & IPHC_TF_ECN_MASK) >> 2) |
                                             ((*(p_iphc + 1) & 0x0f));
            memcpy(&p_iphdr->flowlabel, p_iphc + 2, 2);
            p_iphc += 4;
            break;
    }

    if (!((p_input[0] & IPHC_NH_MASK)))
    {
        // Set next header.
        p_iphdr->next_header = *p_iphc++;
    }

    if ((p_input[0] & IPHC_HLIM_MASK))
    {
        p_iphdr->hoplimit = m_hop_limit[((p_input[0] & IPHC_HLIM_MASK) >> IPHC_HLIM_POS)];
    }
    else
    {
        p_iphdr->hoplimit = *p_iphc++;
    }

    // Clear IPv6 addresses.
    memset(p_iphdr->srcaddr.u8, 0, IPV6_ADDR_SIZE);
    memset(p_iphdr->destaddr.u8, 0, IPV6_ADDR_SIZE);

    // Source address decompression.
    if ((p_input[1] & IPHC_SAC_MASK) >> IPHC_SAC_POS)
    {
        if ( ((p_input[1] & IPHC_SAM_MASK) >> IPHC_SAM_POS) == IPHC_SAM_00 )
        {
            // Unspecified address.
            memset(p_iphdr->srcaddr.u8, 0, IPV6_ADDR_SIZE);
        }
        else
        {
            switch ((p_input[1] & IPHC_SAM_MASK) >> IPHC_SAM_POS)
            {
                case IPHC_SAM_01:
                    // 64 bits in-line, first IID then prefix in case prefix > 64.
                    memcpy(p_iphdr->srcaddr.u8 + 8, p_iphc, 8);
                    p_iphc += 8;
                    break;

                case IPHC_SAM_10:
                    // 16 bits in-line.
                    memcpy(p_iphdr->srcaddr.u8 + 14, p_iphc, 2);
                    memcpy(p_iphdr->srcaddr.u8 + 11, m_link_local_16_middle, 2);
                    p_iphc += 2;
                    break;

                case IPHC_SAM_11:
                    // Take the address from lower layer.
                    memcpy(p_iphdr->srcaddr.u8 + 8, p_interface->peer_addr.identifier, 8);
                    p_iphdr->srcaddr.u8[8] ^= IPV6_IID_FLIP_VALUE;
                    break;
            }

            /* Look up for context */
            BLE_6LOWPAN_MUTEX_UNLOCK();
            err_code = iot_context_manager_get_by_cid(p_interface, sci, &p_ctx);
            BLE_6LOWPAN_MUTEX_LOCK();

            if (err_code == NRF_SUCCESS)
            {
              /* Add prefix */
              IPV6_ADDRESS_PREFIX_SET(p_iphdr->srcaddr.u8, p_ctx->prefix.u8, p_ctx->prefix_len);
            }
            else
            {
              /* Set error and continue decompression. */
              retval = BLE_6LOWPAN_CID_NOT_FOUND;

              BLE_6LOWPAN_ERR("Cannot find context identifier in the context table.");
            }
        }
    }
    else
    {
        switch ((p_input[1] & IPHC_SAM_MASK) >> IPHC_SAM_POS)
        {
            case IPHC_SAM_00:
                // Full 128-bits in-line.
                memcpy(p_iphdr->srcaddr.u8, p_iphc, IPV6_ADDR_SIZE);
                p_iphc += IPV6_ADDR_SIZE;
                break;

            case IPHC_SAM_01:
                // 64 bits in-line, first IID then prefix in case prefix > 64.
                memcpy(p_iphdr->srcaddr.u8, m_link_local_prefix, 2);
                memcpy(p_iphdr->srcaddr.u8 + 8, p_iphc, 8);
                p_iphc += 8;
                break;

            case IPHC_SAM_10:
                // 16 bits in-line.
                memcpy(p_iphdr->srcaddr.u8, m_link_local_prefix, 2);
                memcpy(p_iphdr->srcaddr.u8 + 11, m_link_local_16_middle, 2);
                memcpy(p_iphdr->srcaddr.u8 + 14, p_iphc, 2);
                p_iphc += 2;
                break;

            case IPHC_SAM_11:
                memcpy(p_iphdr->srcaddr.u8, m_link_local_prefix, 2);
                memcpy(p_iphdr->srcaddr.u8 + 8, p_interface->peer_addr.identifier, 8);
                p_iphdr->srcaddr.u8[8] ^= IPV6_IID_FLIP_VALUE;
                break;
        }
    }

    // Destination address decompression.
    if ((p_input[1] & IPHC_DAC_MASK) >> IPHC_DAC_POS)
    {
        if ((p_input[1] & IPHC_M_MASK) >> IPHC_M_POS)
        {
            switch ((p_input[1] & IPHC_DAM_MASK) >> IPHC_DAM_POS)
            {
                case IPHC_DAM_00:
                    // 48-bits in-line.
                    p_iphdr->destaddr.u8[0] = 0xff;
                    memcpy(p_iphdr->destaddr.u8 + 1, p_iphc, 2);
                    memcpy(p_iphdr->destaddr.u8 + 12, p_iphc + 2, 4);
                    p_iphc += 6;
                    break;

                default:
                    BLE_6LOWPAN_ERR("Reserved value in IPHC header!");
                    return NRF_ERROR_INVALID_DATA;
            }
        }
        else
        {
            switch ((p_input[1] & IPHC_DAM_MASK) >> IPHC_DAM_POS)
            {
                case IPHC_DAM_01:
                    // 64 bits in-line.
                    memcpy(p_iphdr->destaddr.u8 + 8, p_iphc, 8);
                    p_iphc += 8;
                    break;

                case IPHC_DAM_10:
                    // 16 bits in-line.
                    memcpy(p_iphdr->destaddr.u8 + 11, m_link_local_16_middle, 2);
                    memcpy(p_iphdr->destaddr.u8 + 14, p_iphc, 2);
                    p_iphc += 2;
                    break;

                case IPHC_DAM_11:
                    // Take the address from lower layer.
                    memcpy(p_iphdr->destaddr.u8 + 8, p_interface->local_addr.identifier, 8);
                    p_iphdr->destaddr.u8[8] ^= IPV6_IID_FLIP_VALUE;
                    break;

                default:
                    BLE_6LOWPAN_ERR("Reserved value in IPHC header!");
                    return NRF_ERROR_INVALID_DATA;
            }

            /* Look up for context */
            BLE_6LOWPAN_MUTEX_UNLOCK();
            err_code = iot_context_manager_get_by_cid(p_interface, dci, &p_ctx);
            BLE_6LOWPAN_MUTEX_LOCK();

            if (err_code == NRF_SUCCESS)
            {
              /* Add prefix */
              IPV6_ADDRESS_PREFIX_SET(p_iphdr->destaddr.u8, p_ctx->prefix.u8, p_ctx->prefix_len);
            }
            else
            {
              /* Set error and continue decompression. */
              retval = BLE_6LOWPAN_CID_NOT_FOUND;

              BLE_6LOWPAN_ERR("Cannot find context identifier in the context table.");
            }
        }
    }
    else
    {
        if ((p_input[1] & IPHC_M_MASK) >> IPHC_M_POS)
        {
            switch ((p_input[1] & IPHC_DAM_MASK) >> IPHC_DAM_POS)
            {
                case IPHC_DAM_00:
                    // 128 bits in-line.
                    memcpy(p_iphdr->destaddr.u8, p_iphc, IPV6_ADDR_SIZE);
                    p_iphc += IPV6_ADDR_SIZE;
                    break;

                case IPHC_DAM_01:
                    // 48 bits in-line.
                    p_iphdr->destaddr.u8[0] = 0xFF;
                    p_iphdr->destaddr.u8[1] = *p_iphc;
                    memcpy(p_iphdr->destaddr.u8 + 11, p_iphc + 1, 5);
                    p_iphc += 6;
                    break;

                case IPHC_DAM_10:
                    // 32 bits in-line.
                    p_iphdr->destaddr.u8[0] = 0xFF;
                    p_iphdr->destaddr.u8[1] = *p_iphc;
                    memcpy(p_iphdr->destaddr.u8 + 13, p_iphc + 1, 3);
                    p_iphc += 4;
                    break;

                case IPHC_DAM_11:
                    // 8 bits in-line.
                    p_iphdr->destaddr.u8[0]  = 0xFF;
                    p_iphdr->destaddr.u8[1]  = 0x02;
                    p_iphdr->destaddr.u8[15] = *p_iphc;
                    p_iphc                  += 1;
                    break;
            }
        }
        else
        {
            switch ((p_input[1] & IPHC_DAM_MASK) >> IPHC_DAM_POS)
            {
                case IPHC_DAM_00:
                    // 128 bits in-line.
                    memcpy(p_iphdr->destaddr.u8, p_iphc, IPV6_ADDR_SIZE);
                    p_iphc += IPV6_ADDR_SIZE;
                    break;

                case IPHC_DAM_01:
                    // 64 bits in-line, first IID then prefix in case prefix > 64.
                    memcpy(p_iphdr->destaddr.u8, m_link_local_prefix, 2);
                    memcpy(p_iphdr->destaddr.u8 + 8, p_iphc, 8);
                    p_iphc += 8;
                    break;

                case IPHC_DAM_10:
                    // 16 bits in-line.
                    memcpy(p_iphdr->destaddr.u8, m_link_local_prefix, 2);
                    memcpy(p_iphdr->destaddr.u8 + 11, m_link_local_16_middle, 2);
                    memcpy(p_iphdr->destaddr.u8 + 14, p_iphc, 2);
                    p_iphc += 2;
                    break;

                case IPHC_DAM_11:
                    // Take the address from lower layer.
                    memcpy(p_iphdr->destaddr.u8, m_link_local_prefix, 2);
                    memcpy(p_iphdr->destaddr.u8 + 8, p_interface->local_addr.identifier, 8);
                    p_iphdr->destaddr.u8[8] ^= IPV6_IID_FLIP_VALUE;
                    break;
            }
        }
    }

    // Perform next header compression.
    if (((p_input[0] & IPHC_NH_MASK) >> IPHC_NH_POS))
    {
        p_iphc += iphc_nhc_decode(p_iphc, p_output, &nhc_length);

        if (nhc_length == 0)
        {
            // Unknown NHC field.
            BLE_6LOWPAN_ERR("IPHC contains unsupported NHC header!");

            return NRF_ERROR_INVALID_DATA;
        }
    }

    // Calculate IPv6 Header Length.
    p_iphdr->length = input_len - (p_iphc - p_input);

    // Check if IPHC contains more bytes than whole packet.
    if (p_iphdr->length > input_len)
    {
        // We cannot decompress it.
        BLE_6LOWPAN_ERR("IPHC contains more bytes than expected!");

        return NRF_ERROR_INVALID_DATA;
    }

    // Copy the data.
    memcpy(p_output + IPV6_IP_HEADER_SIZE + nhc_length, p_iphc, p_iphdr->length);

    // Add uncompressed headers length if any.
    p_iphdr->length += nhc_length;

    // Return length of whole IPv6 packet.
    *p_output_len = p_iphdr->length + IPV6_IP_HEADER_SIZE;

    // Keep proper endianness.
    p_iphdr->length = HTONS(p_iphdr->length);

    // Restore UDP length if compression was used.
    if ( p_iphdr->next_header == IPV6_NEXT_HEADER_UDP )
    {
        memcpy(&p_udphdr->length, &p_iphdr->length, 2);
    }

    return retval;
}


/**@brief Function for encoding IPHC (IP Header Compression) defined in
 *        IETF RFC 6282. Instead of having separate buffer for compression,
 *        needed compression is performed on the IPv6 packet and buffer holding
 *        the packet is reused to overwrite the headers compressed.
 *
 * @param[in]   p_interface  IoT interface where packet must be sent.
 * @param[in]   p_input      Pointer to full IPv6 packet.
 * @param[in]   input_len    Length of IPv6 packet.
 * @param[out]  p_output     Pointer to place of start IPHC packet.
 * @param[out]  p_output_len Length of compressed packet.
 *
 * @return      NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t iphc_encode(const iot_interface_t  * p_interface,
                            uint8_t               ** p_output,
                            uint16_t               * p_output_len,
                            const uint8_t          * p_input,
                            uint16_t                 input_len)
{
    // Create a buffer with maximum of IPHC value.
    uint32_t                err_code;
    uint8_t                 iphc_buff[IPV6_IP_HEADER_SIZE + UDP_HEADER_SIZE];
    uint8_t                 traffic_class;
    uint8_t               * p_iphc         = &iphc_buff[2];
    uint16_t                iphc_len       = 0;
    uint16_t                nhc_length     = 0;
    iot_context_t         * p_ctx          = NULL;
    uint8_t                 sci            = p_interface->tx_contexts.src_cntxt_id;
    uint8_t                 dci            = p_interface->tx_contexts.dest_cntxt_id;
    bool                    sci_cover      = false;
    bool                    dci_cover      = false;

    // IPv6 header.
    ipv6_header_t * p_iphdr = (ipv6_header_t *)p_input;

    *p_iphc = 0;

    // Set IPHC dispatch.
    iphc_buff[0] = IPHC_START_DISPATCH << IPHC_START_DISPATCH_POS;

    // Check if address can be compressed using context identifier.
    if (sci == IPV6_CONTEXT_IDENTIFIER_NONE)
    {
        BLE_6LOWPAN_MUTEX_UNLOCK();
        err_code = iot_context_manager_get_by_addr(p_interface, &p_iphdr->srcaddr, &p_ctx);
        BLE_6LOWPAN_MUTEX_LOCK();

        if (err_code == NRF_SUCCESS)
        {
            sci_cover = is_context_cover_iid(&p_iphdr->srcaddr, p_ctx, &p_interface->local_addr);
            sci = p_ctx->context_id;
        }
    }

    if (dci == IPV6_CONTEXT_IDENTIFIER_NONE)
    {
        BLE_6LOWPAN_MUTEX_UNLOCK();
        err_code = iot_context_manager_get_by_addr(p_interface, &p_iphdr->destaddr, &p_ctx);
        BLE_6LOWPAN_MUTEX_LOCK();

        if (err_code == NRF_SUCCESS)
        {
            dci_cover = is_context_cover_iid(&p_iphdr->destaddr, p_ctx, &p_interface->peer_addr);
            dci = p_ctx->context_id;
        }
    }

    if (((sci != IPV6_CONTEXT_IDENTIFIER_NONE) || dci != IPV6_CONTEXT_IDENTIFIER_NONE))
    {
        iphc_buff[1] = (IPHC_CID_1 << IPHC_CID_POS);

        // Add Source Context if exists.
        if (sci != IPV6_CONTEXT_IDENTIFIER_NONE)
        {
            *p_iphc |= (sci << 4);
        }

        // Add Destination Context if exists.
        if (dci != IPV6_CONTEXT_IDENTIFIER_NONE)
        {
            *p_iphc |= dci;
        }

        p_iphc += 1;
    }
    else
    {
        // Unset Context Identifier bit.
        iphc_buff[1] = (IPHC_CID_0 << IPHC_CID_POS);
    }

    // Change ECN with DSCP in Traffic Class.
    traffic_class  = (p_iphdr->version_traffic_class & 0x0f) << 4;
    traffic_class |= ((p_iphdr->traffic_class_flowlabel & 0xf0) >> 4);
    traffic_class  = (((traffic_class & 0x03) << 6) | (traffic_class >> 2));

    if ((p_iphdr->flowlabel == 0) && ((p_iphdr->traffic_class_flowlabel & 0x0f) == 0))
    {
        if (traffic_class == 0)
        {
            // Elide Flow Label and Traffic Class.
            iphc_buff[0] |= (IPHC_TF_11 << IPHC_TF_POS);
        }
        else
        {
            // Elide Flow Label and carry Traffic Class in-line.
            iphc_buff[0] |= (IPHC_TF_10 << IPHC_TF_POS);

            *p_iphc++ = traffic_class;
        }
    }
    else
    {
        if (traffic_class & IPHC_TF_DSCP_MASK)
        {
            // Carry Flow Label and Traffic Class in-line.
            iphc_buff[0] |= (IPHC_TF_00 << IPHC_TF_POS);

            *p_iphc++ = traffic_class;
            *p_iphc++ = (p_iphdr->traffic_class_flowlabel & 0x0f);
            memcpy(p_iphc, &p_iphdr->flowlabel, 2);
            p_iphc += 2;
        }
        else
        {
            // Carry Flow Label and ECN only with 2-bit padding.
            iphc_buff[0] |= (IPHC_TF_01 << IPHC_TF_POS);

            *p_iphc++ =
                ((traffic_class & IPHC_TF_ECN_MASK) | (p_iphdr->traffic_class_flowlabel & 0x0f));
            memcpy(p_iphc, &p_iphdr->flowlabel, 2);
            p_iphc += 2;
        }
    }

    // Checks if next header is compressed.
    if (iphc_nhc_compressable(p_iphdr->next_header))
    {
        iphc_buff[0] |= (IPHC_NH_1 << IPHC_NH_POS);
    }
    else
    {
        iphc_buff[0] |= (IPHC_NH_0 << IPHC_NH_POS);
        *p_iphc++     = p_iphdr->next_header;
    }

    // Hop limit compression.
    switch (p_iphdr->hoplimit)
    {
        case 1:
            iphc_buff[0] |= (IPHC_HLIM_01 << IPHC_HLIM_POS);
            break;

        case 64:
            iphc_buff[0] |= (IPHC_HLIM_10 << IPHC_HLIM_POS);
            break;

        case 255:
            iphc_buff[0] |= (IPHC_HLIM_11 << IPHC_HLIM_POS);
            break;

        default:
            // Carry Hop Limit in-line.
            iphc_buff[0] |= (IPHC_HLIM_00 << IPHC_HLIM_POS);
            *p_iphc++     = p_iphdr->hoplimit;
            break;
    }

    // Source address compression.
    if (IPV6_ADDRESS_IS_UNSPECIFIED(&p_iphdr->srcaddr))
    {
        iphc_buff[1] |= (IPHC_SAC_1  << IPHC_SAC_POS);
        iphc_buff[1] |= (IPHC_SAM_00 << IPHC_SAM_POS);
    }
    else if (sci != IPV6_CONTEXT_IDENTIFIER_NONE || IPV6_ADDRESS_IS_LINK_LOCAL(&p_iphdr->srcaddr))
    {
        if (sci != IPV6_CONTEXT_IDENTIFIER_NONE)
        {
            // Set stateful source address compression.
            iphc_buff[1] |= (IPHC_SAC_1 << IPHC_SAC_POS);
        }

        if (IPV6_ADDRESS_IS_FULLY_ELIDABLE(p_interface->local_addr.identifier,
                                           &p_iphdr->srcaddr)
                                           ||
                                           sci_cover == true)
        {
            iphc_buff[1] |= (IPHC_SAM_11 << IPHC_SAM_POS);
        }
        else if (IPV6_ADDRESS_IS_16_BIT_COMPRESSABLE(&p_iphdr->srcaddr))
        {
            iphc_buff[1] |= (IPHC_SAM_10 << IPHC_SAM_POS);
            memcpy(p_iphc, &p_iphdr->srcaddr.u8[14], 2);
            p_iphc += 2;
        }
        else
        {
            iphc_buff[1] |= (IPHC_SAM_01 << IPHC_SAM_POS);
            memcpy(p_iphc, &p_iphdr->srcaddr.u8[8], 8);
            p_iphc += 8;
        }
    }
    else
    {
        // Carry full source address in-line.
        iphc_buff[1] |= (IPHC_SAC_0  << IPHC_SAC_POS);
        iphc_buff[1] |= (IPHC_SAM_00 << IPHC_SAM_POS);
        memcpy(p_iphc, p_iphdr->srcaddr.u8, IPV6_ADDR_SIZE);
        p_iphc += IPV6_ADDR_SIZE;
    }

    // Destination compression.
    if (IPV6_ADDRESS_IS_MULTICAST(&p_iphdr->destaddr))
    {
        iphc_buff[1] |= (IPHC_M_1 << IPHC_M_POS);

        if (dci != IPV6_CONTEXT_IDENTIFIER_NONE)
        {
            iphc_buff[1] |= (IPHC_DAC_1 << IPHC_DAC_POS);
            iphc_buff[1] |= (IPHC_DAM_00 << IPHC_DAM_POS);

            p_iphdr->destaddr.u8[0] = 0xff;
            memcpy(p_iphc, &p_iphdr->destaddr.u8[1], 2);
            memcpy(p_iphc + 2, &p_iphdr->destaddr.u8[12], 4);
            p_iphc += 6;
        }
        else if (IPV6_ADDRESS_IS_8_BIT_MCAST_COMPRESSABLE(&p_iphdr->destaddr))
        {
            iphc_buff[1] |= (IPHC_DAC_0 << IPHC_DAC_POS);
            iphc_buff[1] |= (IPHC_DAM_11 << IPHC_DAM_POS);
            *p_iphc++     = p_iphdr->destaddr.u8[15];

        }
        else if (IPV6_ADDRESS_IS_32_BIT_MCAST_COMPRESSABLE(&p_iphdr->destaddr))
        {
            iphc_buff[1] |= (IPHC_DAC_0 << IPHC_DAC_POS);
            iphc_buff[1] |= (IPHC_DAM_10 << IPHC_DAM_POS);

            *p_iphc = p_iphdr->destaddr.u8[1];
            memcpy(p_iphc + 1, &p_iphdr->destaddr.u8[13], 3);
            p_iphc += 4;
        }
        else if (IPV6_ADDRESS_IS_48_BIT_MCAST_COMPRESSABLE(&p_iphdr->destaddr))
        {
            iphc_buff[1] |= (IPHC_DAC_0 << IPHC_DAC_POS);
            iphc_buff[1] |= (IPHC_DAM_01 << IPHC_DAM_POS);

            *p_iphc = p_iphdr->destaddr.u8[1];
            memcpy(p_iphc + 1, &p_iphdr->destaddr.u8[11], 5);
            p_iphc += 6;
        }
        else
        {
            // Carry full destination multi-cast address in-line.
            iphc_buff[1] |= (IPHC_DAC_0 << IPHC_DAC_POS);
            iphc_buff[1] |= (IPHC_DAM_00 << IPHC_DAM_POS);
            memcpy(p_iphc, p_iphdr->destaddr.u8, IPV6_ADDR_SIZE);
            p_iphc += IPV6_ADDR_SIZE;
        }
    }
    else
    {
        iphc_buff[1] |= (IPHC_M_0 << IPHC_M_POS);

        if (dci != IPV6_CONTEXT_IDENTIFIER_NONE || IPV6_ADDRESS_IS_LINK_LOCAL(&p_iphdr->destaddr))
        {
            if (dci != IPV6_CONTEXT_IDENTIFIER_NONE)
            {
                iphc_buff[1] |= (IPHC_DAC_1 << IPHC_DAC_POS);
            }

            if (IPV6_ADDRESS_IS_FULLY_ELIDABLE(p_interface->peer_addr.identifier,
                                               &p_iphdr->destaddr)
                                               ||
                                               dci_cover == true)
            {
                iphc_buff[1] |= (IPHC_DAM_11 << IPHC_DAM_POS);
            }
            else if (IPV6_ADDRESS_IS_16_BIT_COMPRESSABLE(&p_iphdr->destaddr))
            {
                iphc_buff[1] |= (IPHC_DAM_10 << IPHC_DAM_POS);
                memcpy(p_iphc, &p_iphdr->destaddr.u8[14], 2);
                p_iphc += 2;
            }
            else
            {
                iphc_buff[1] |= (IPHC_DAM_01 << IPHC_DAM_POS);
                memcpy(p_iphc, &p_iphdr->destaddr.u8[8], 8);
                p_iphc += 8;
            }
        }
        else
        {
            // Carry full destination address in-line.
            iphc_buff[1] |= (IPHC_DAC_0  << IPHC_DAC_POS);
            iphc_buff[1] |= (IPHC_DAM_00 << IPHC_DAM_POS);
            memcpy(p_iphc, p_iphdr->destaddr.u8, IPV6_ADDR_SIZE);
            p_iphc += IPV6_ADDR_SIZE;
        }
    }

    if ( iphc_buff[0] & IPHC_NH_MASK)
    {
        p_iphc += iphc_nhc_encode(p_iphc, p_input, &nhc_length);
    }

    // Calculate IPHC size.
    iphc_len = (p_iphc - iphc_buff);

    // Calculate IPv6 packet size.
    *p_output_len = input_len - (IPV6_IP_HEADER_SIZE - iphc_len + nhc_length);

    // Use p_data as final buffer (since IPHC+NHC <= IPv6 Header + NHC (UDP)).
    memcpy((uint8_t *)&p_input[IPV6_IP_HEADER_SIZE + nhc_length - iphc_len], iphc_buff, iphc_len);

    // Set correct address of output data.
    *p_output = (uint8_t *) &p_input[IPV6_IP_HEADER_SIZE + nhc_length - iphc_len];

    return NRF_SUCCESS;
}


/******************************************************************************
 * @name 6LoWPAN transport functions
 *****************************************************************************/

/**@brief Function for notifying application of an error in an ongoing procedure.
 *
 * @param[in]   p_interface  Pointer to 6LoWPAN interface.
 * @param[in]   err_code     Internally error code.
 *
 * @return      None.
 */
static void app_notify_error(iot_interface_t * p_interface,
                             uint32_t          err_code)
{
    ble_6lowpan_event_t event;

    event.event_id     = BLE_6LO_EVT_ERROR;
    event.event_result = err_code;

    BLE_6LOWPAN_MUTEX_UNLOCK();

    m_event_handler(p_interface, &event);

    BLE_6LOWPAN_MUTEX_LOCK();
}


/**@brief Function for notifying application of the new packet received.
 *
 * @param[in]   p_interface   Pointer to iot interface.
 * @param[in]   p_packet      Pointer to decompressed IPv6 packet.
 * @param[in]   packet_len    Length of IPv6 packet.
 * @param[in]   result        Processing result of packet. Could be NRF_SUCCESS, or
 *                            NRF_ERROR_NOT_FOUND if context identifier is unknown.
 * @param[in]  p_rx_contexts  Received contexts if any.
 *
 * @return      None.
 */
static void app_notify_rx_data(iot_interface_t  * p_interface,
                               uint8_t          * p_packet,
                               uint16_t           packet_len,
                               uint32_t           result,
                               iot_context_id_t * p_rx_contexts)
{
    ble_6lowpan_event_t event;

    event.event_id                               = BLE_6LO_EVT_INTERFACE_DATA_RX;
    event.event_result                           = result;
    event.event_param.rx_event_param.p_packet    = p_packet;
    event.event_param.rx_event_param.packet_len  = packet_len;
    event.event_param.rx_event_param.rx_contexts = *(p_rx_contexts);

    BLE_6LOWPAN_MUTEX_UNLOCK();

    m_event_handler(p_interface, &event);

    BLE_6LOWPAN_MUTEX_LOCK();
}


/**@brief Function for notifying application of the new interface established.
 *
 * @param[in]   p_interface  Pointer to new iot interface.
 *
 * @return      None.
 */
static void app_notify_interface_add(iot_interface_t * p_interface)
{
    ble_6lowpan_event_t event;

    event.event_id     = BLE_6LO_EVT_INTERFACE_ADD;
    event.event_result = NRF_SUCCESS;

    BLE_6LOWPAN_MUTEX_UNLOCK();

    m_event_handler(p_interface, &event);

    BLE_6LOWPAN_MUTEX_LOCK();
}


/**@brief Function for notifying application of the interface disconnection.
 *
 * @param[in]   p_interface  Pointer to removed iot interface.
 *
 * @return      None.
 */
static void app_notify_interface_delete(iot_interface_t * p_interface)
{
    ble_6lowpan_event_t event;

    event.event_id     = BLE_6LO_EVT_INTERFACE_DELETE;
    event.event_result = NRF_SUCCESS;

    BLE_6LOWPAN_MUTEX_UNLOCK();

    m_event_handler(p_interface, &event);

    BLE_6LOWPAN_MUTEX_LOCK();
}


/**@brief Function for initialize transmit FIFO.
 *
 * @param[in]   p_fifo  Pointer to transmit FIFO instance.
 *
 * @return      None.
 */
static void tx_fifo_init(tx_fifo_t * p_fifo)
{
    memset(p_fifo->packets, 0, BLE_6LOWPAN_TX_FIFO_SIZE * sizeof (tx_packet_t));
    p_fifo->read_pos  = 0;
    p_fifo->write_pos = 0;
}


/**@brief Function for putting new packet to transmit FIFO.
 *
 * @param[in]   p_fifo   Pointer to transmit FIFO instance.
 * @param[in]   p_packet Pointer to new packet.
 *
 * @return      NRF_SUCCESS on success, otherwise NRF_ERROR_NO_MEM error.
 */
static uint32_t tx_fifo_put(tx_fifo_t * p_fifo, tx_packet_t * p_packet)
{
    uint32_t err_code = BLE_6LOWPAN_TX_FIFO_FULL;

    // To prevent "The order of volatile accesses is undefined in this statement"
    // in subsequent conditional statement.
    uint32_t write_pos = p_fifo->write_pos;
    uint32_t read_pos  = p_fifo->read_pos;

    if ((write_pos - read_pos) <= TX_FIFO_MASK)
    {
        p_fifo->packets[p_fifo->write_pos & TX_FIFO_MASK].p_data      = p_packet->p_data;
        p_fifo->packets[p_fifo->write_pos & TX_FIFO_MASK].data_len    = p_packet->data_len;
        p_fifo->packets[p_fifo->write_pos & TX_FIFO_MASK].p_mem_block = p_packet->p_mem_block;

        p_fifo->write_pos++;

        err_code = NRF_SUCCESS;
    }

    return err_code;
}


/**@brief Function for popping currently processed packet in transmit FIFO.
 *        It releases element on FIFO only when processing of the element is done.
 *
 * @param[in]   p_fifo   Pointer to transmit FIFO instance.
 *
 * @return      None.
 */
static void tx_fifo_release(tx_fifo_t * p_fifo)
{
    p_fifo->read_pos++;
}


/**@brief Function for reading front element of transmit FIFO.
 *        After finish processing element in queue, it must be
 *        released using tx_fifo_release function.
 *
 * @param[in]   p_fifo    Pointer to transmit FIFO instance.
 * @param[in]   pp_packet Pointer to front packet.
 *
 * @return      NRF_SUCCESS on success, otherwise NRF_ERROR_NO_MEM error.
 */
static uint32_t tx_fifo_get(tx_fifo_t * p_fifo, tx_packet_t * * pp_packet)
{
    uint32_t err_code = NRF_ERROR_NOT_FOUND;

    // To prevent "The order of volatile accesses is undefined in this statement"
    // in subsequent conditional statement.
    uint32_t write_pos = p_fifo->write_pos;
    uint32_t read_pos  = p_fifo->read_pos;

    if ((write_pos - read_pos) != 0)
    {
        *pp_packet = &p_fifo->packets[p_fifo->read_pos & TX_FIFO_MASK];
        err_code   = NRF_SUCCESS;
    }

    return err_code;
}


/**@brief Function for searching transport interface by given IPSP handle.
 *
 * @param[in]   p_ipsp_handle Pointer to IPSP handle.
 *
 * @return      Transport interface related with IPSP handle, or NULL if not found.
 */
static transport_instance_t * interface_get_by_handle(const ble_ipsp_handle_t * p_ipsp_handle)
{
    uint32_t index;

    for (index = 0; index < BLE_6LOWPAN_MAX_INTERFACE; index++)
    {
        if (m_instances[index].handle.cid         == p_ipsp_handle->cid &&
            m_instances[index].handle.conn_handle == p_ipsp_handle->conn_handle)
        {
            return &m_instances[index];
        }
    }

    return NULL;
}


/**@brief Function for initializing transport instance.
 *
 * @param[in]   index  Index of instance.
 *
 * @return      None.
 */
static void instance_init(uint32_t index)
{
    memset(&m_instances[index], 0, sizeof (transport_instance_t));
    m_instances[index].handle.cid            = BLE_L2CAP_CID_INVALID;
    m_instances[index].p_tx_cur_packet       = NULL;
    m_instances[index].interface.p_transport = (void *) index;
}


/**@brief Function for resetting specific 6lowpan interface.
 *
 * @param[in]   p_interface  Pointer to transport interface.
 *
 * @return      None.
 */
static void interface_reset(transport_instance_t * p_instance)
{
    uint32_t index;
    uint32_t instance_id = (uint32_t) p_instance->interface.p_transport;

    // Free all queued packets.
    for (index = 0; index < BLE_6LOWPAN_TX_FIFO_SIZE; index++)
    {
        if (m_instances[instance_id].tx_fifo.packets[index].p_mem_block != NULL)
        {
            nrf_free(m_instances[instance_id].tx_fifo.packets[index].p_mem_block);
        }
    }

    instance_init (instance_id);
}


/**@brief Function for adding new transport instance.
 *
 * @param[in]   p_peer_addr   Pointer EUI-64 of peer address.
 * @param[in]   p_ipsp_handle Pointer IPSP handle, related with L2CAP CoC channel.
 * @param[out]  pp_instance   Pointer to added transport instances, if operation succeeded.
 *
 * @return      NRF_SUCCESS on success, otherwise NRF_ERROR_NO_MEM error.
 */
static uint32_t interface_add(const eui64_t             * p_peer_addr,
                              const ble_ipsp_handle_t   * p_ipsp_handle,
                              transport_instance_t     ** pp_instance)
{
    uint32_t index;

    for (index = 0; index < BLE_6LOWPAN_MAX_INTERFACE; index++)
    {
        if (m_instances[index].handle.cid == BLE_L2CAP_CID_INVALID)
        {
            m_instances[index].handle.cid                          = p_ipsp_handle->cid;
            m_instances[index].handle.conn_handle                  = p_ipsp_handle->conn_handle;
            m_instances[index].interface.tx_contexts.src_cntxt_id  = IPV6_CONTEXT_IDENTIFIER_NONE;
            m_instances[index].interface.tx_contexts.dest_cntxt_id = IPV6_CONTEXT_IDENTIFIER_NONE;

            memcpy(&m_instances[index].interface.peer_addr, p_peer_addr, sizeof (eui64_t));
            memcpy(&m_instances[index].interface.local_addr, &m_ll_addr, sizeof (eui64_t));

            // Initialize TX FIFO.
            tx_fifo_init(&m_instances[index].tx_fifo);

            *pp_instance = &m_instances[index];

            return NRF_SUCCESS;
        }
    }

    return NRF_ERROR_NO_MEM;
}


/**@brief Function for checking if any transmission is currently processing on specific interface.
 *        Current version of L2CAP CoC in SoftDevice has limitation to send one packet at same
 *        time.
 *
 * @param[in]   p_instance  Pointer to transport instance.
 *
 * @return      TRUE if interface not sending any packets, FALSE if busy.
 */
static bool tx_is_free(transport_instance_t * p_instance)
{
    return (NULL == p_instance->p_tx_cur_packet);
}


/**@brief Function uses for indicating transmission complete on specific interface.
 *
 * @param[in]   p_instance  Pointer to transport instance.
 *
 * @return      None.
 */
static void tx_complete(transport_instance_t * p_instance)
{
    BLE_6LOWPAN_TRC("[CID 0x%04X]: Transmission complete.",
                    p_instance->handle.cid);

    // Free the transmit buffer.
    nrf_free(p_instance->p_tx_cur_packet->p_mem_block);
    p_instance->p_tx_cur_packet = NULL;

    // Release last processed packet.
    tx_fifo_release(&p_instance->tx_fifo);
}


/**@brief Function for sending front packet from transmit FIFO on specific interface.
 *
 * @param[in]   p_instance  Pointer to transport instance.
 *
 * @return      None.
 */
static void tx_send(transport_instance_t * p_instance)
{
    uint32_t err_code = NRF_SUCCESS;

    if (NRF_SUCCESS == tx_fifo_get(&p_instance->tx_fifo,
                                   &p_instance->p_tx_cur_packet))
    {
        err_code = ble_ipsp_send(&p_instance->handle,
                                 p_instance->p_tx_cur_packet->p_data,
                                 p_instance->p_tx_cur_packet->data_len);

        if (NRF_SUCCESS != err_code)
        {
            BLE_6LOWPAN_TRC("Cannot send the packet, error = 0x%08lX", err_code);

            app_notify_error(&p_instance->interface, err_code);

            tx_complete(p_instance);

            // Try to send another packet.
            tx_send(p_instance);
        }
    }
}

/**@brief Callback registered with IPSP to receive asynchronous events from the module.
 *
 * @param[in]   p_handle  Pointer to IPSP handle.
 * @param[in]   p_evt     Pointer to specific event, generated by IPSP module.
 *
 * @return      NRF_SUCCESS on success, otherwise NRF_ERROR_NO_MEM error.
 */
static uint32_t ipsp_evt_handler(ble_ipsp_handle_t const * p_handle, ble_ipsp_evt_t const * p_evt)
{
    BLE_6LOWPAN_ENTRY();

    VERIFY_MODULE_IS_INITIALIZED();

    uint32_t               mem_size;
    uint16_t               buff_len;
    eui64_t                peer_addr;
    iot_context_id_t       rx_contexts;
    uint32_t               retval       = NRF_SUCCESS;
    transport_instance_t * p_instance   = NULL;
    uint8_t              * p_buff       = NULL;

    BLE_6LOWPAN_MUTEX_LOCK();

    p_instance = interface_get_by_handle(p_handle);

    switch (p_evt->evt_id)
    {
        case BLE_IPSP_EVT_CHANNEL_CONNECTED:
        {
            BLE_6LOWPAN_TRC("[CID 0x%04X]: >> BLE_IPSP_EVT_CHANNEL_CONNECTED",
                            p_handle->cid);
            BLE_6LOWPAN_TRC("New channel established.");

            if (p_instance == NULL)
            {
                IPV6_EUI64_CREATE_FROM_EUI48(peer_addr.identifier,
                                        p_evt->p_evt_param->p_peer->addr,
                                        p_evt->p_evt_param->p_peer->addr_type);

                // Add interface to internal table.
                retval = interface_add(&peer_addr, p_handle, &p_instance);

                if (NRF_SUCCESS == retval)
                {
                    BLE_6LOWPAN_TRC("Added new IPSP interface.");

                    // Notify application.
                    app_notify_interface_add(&p_instance->interface);
                }
                else
                {
                    BLE_6LOWPAN_ERR("Cannot add new interface. Table is full.");
                    UNUSED_VARIABLE(ble_ipsp_disconnect(p_handle));
                }
            }
            else
            {
                // Got a connection event, when already connected.
                UNUSED_VARIABLE(ble_ipsp_disconnect(p_handle));
            }

            break;
        }

        case BLE_IPSP_EVT_CHANNEL_DISCONNECTED:
        {
            BLE_6LOWPAN_TRC("[CID 0x%04X]: >> BLE_IPSP_EVT_CHANNEL_DISCONNECTED",
                            p_handle->cid);
            BLE_6LOWPAN_TRC("Channel disconnection.");

            if (NULL != p_instance)
            {
                BLE_6LOWPAN_TRC("Removed IPSP interface.");

                // Notify application.
                app_notify_interface_delete(&p_instance->interface);

                // Remove interface from internal table.
                interface_reset(p_instance);
            }

            break;
        }

        case BLE_IPSP_EVT_CHANNEL_DATA_RX:
        {

            if (NULL != p_instance)
            {
                const uint16_t packet_len = MIN(p_evt->p_evt_param->p_l2cap_evt->params.rx.sdu_buf.len,
                                                p_evt->p_evt_param->p_l2cap_evt->params.rx.sdu_len);

                BLE_6LOWPAN_TRC("[CID 0x%04X]: >> BLE_IPSP_EVT_CHANNEL_DATA_RX",
                                p_handle->cid);

                BLE_6LOWPAN_DUMP(p_evt->p_evt_param->p_l2cap_evt->params.rx.sdu_buf.p_data,
                                 packet_len);

                BLE_6LOWPAN_TRC("Processing received data.");

                mem_size = packet_len + IPHC_MAX_COMPRESSED_DIFF;

                // Try to allocate memory for incoming data.
                retval = nrf_mem_reserve(&p_buff, &mem_size);

                if (retval == NRF_SUCCESS)
                {
                    // Decompress IPHC data into IPv6 packet.
                    retval = iphc_decode(&p_instance->interface,
                                         p_buff,
                                         &buff_len,
                                         p_evt->p_evt_param->p_l2cap_evt->params.rx.sdu_buf.p_data,
                                         packet_len,
                                         &rx_contexts);

                    // Do not discard if packet decompressed successfully,
                    // otherwise error in Context based decompression.
                    if (retval == NRF_SUCCESS || retval == BLE_6LOWPAN_CID_NOT_FOUND)
                    {
                        if ((p_evt->evt_result == NRF_ERROR_BLE_IPSP_RX_PKT_TRUNCATED) &&
                            (retval == NRF_SUCCESS))
                        {
                            // Inform the application that the packet is truncated.
                            retval = NRF_ERROR_BLE_IPSP_RX_PKT_TRUNCATED;
                        }

                        BLE_6LOWPAN_TRC("Decompressed packet:");
                        BLE_6LOWPAN_DUMP(p_buff, buff_len);

                        // Notify application.
                        app_notify_rx_data(&p_instance->interface,
                                           p_buff,
                                           buff_len,
                                           retval,
                                           &rx_contexts);
                    }
                    else
                    {
                        BLE_6LOWPAN_ERR("Decompression failed!");

                        nrf_free(p_buff);
                    }
                }
                else
                {
                    BLE_6LOWPAN_ERR(
                        "No buffer in memory pool available, packet dropped!");
                }
            }
            else
            {
                BLE_6LOWPAN_ERR("Got data to unknown interface!");
            }

            break;
        }

        case BLE_IPSP_EVT_CHANNEL_DATA_TX_COMPLETE:
        {
            BLE_6LOWPAN_TRC("[CID 0x%04X]: >> BLE_IPSP_EVT_CHANNEL_DATA_TX_COMPLETE",
                            p_handle->cid);

            // Free TX buffer.
            tx_complete(p_instance);

            // Try to send another packet.
            tx_send(p_instance);

            break;
        }

        default:
            break;
    }

    BLE_6LOWPAN_MUTEX_UNLOCK();

    BLE_6LOWPAN_EXIT();

    return retval;
}

/******************************************************************************
 * @name 6LoWPAN API functions
 *****************************************************************************/

uint32_t ble_6lowpan_init(const ble_6lowpan_init_t * p_init)
{
    BLE_6LOWPAN_ENTRY();

    uint32_t        index;
    uint32_t        retval = NRF_SUCCESS;
    ble_ipsp_init_t ipsp_init_params;

    NULL_PARAM_CHECK(p_init);
    NULL_PARAM_CHECK(p_init->p_eui64);
    NULL_PARAM_CHECK(p_init->event_handler);

    // Check if the transmit FIFO size is a power of two.
    if (!IS_POWER_OF_TWO(BLE_6LOWPAN_TX_FIFO_SIZE))
    {
        return NRF_ERROR_INVALID_LENGTH | BLE_6LOWPAN_ERR_BASE;
    }

    SDK_MUTEX_INIT(m_6lowpan_mutex);

    BLE_6LOWPAN_MUTEX_LOCK();

    // Store EUI64 in internal variable.
    memcpy(m_ll_addr.identifier, p_init->p_eui64->identifier, EUI_64_ADDR_SIZE);

    // Set application event handler.
    m_event_handler = p_init->event_handler;

    // Clear transport interfaces table.
    for (index = 0; index < BLE_6LOWPAN_MAX_INTERFACE; index++)
    {
        instance_init(index);
    }

    // IPSP module initialization.
    ipsp_init_params.evt_handler = ipsp_evt_handler;

    // Initialize the IPSP module.
    retval = ble_ipsp_init(&ipsp_init_params);

    BLE_6LOWPAN_MUTEX_UNLOCK();

    BLE_6LOWPAN_EXIT();

    return retval;
}


uint32_t  ble_6lowpan_interface_disconnect(const iot_interface_t * p_interface)
{
    BLE_6LOWPAN_ENTRY();

    VERIFY_MODULE_IS_INITIALIZED();

    NULL_PARAM_CHECK(p_interface);

    uint32_t               retval;
    transport_instance_t * p_instance = &m_instances[(uint32_t)p_interface->p_transport];

    BLE_6LOWPAN_MUTEX_LOCK();

    retval = ble_ipsp_disconnect(&p_instance->handle);

    BLE_6LOWPAN_MUTEX_UNLOCK();

    BLE_6LOWPAN_EXIT();

    return retval;
}


uint32_t ble_6lowpan_interface_send(const iot_interface_t * p_interface,
                                    const uint8_t         * p_packet,
                                    uint16_t                packet_len)
{
    BLE_6LOWPAN_ENTRY();

    VERIFY_MODULE_IS_INITIALIZED();

    NULL_PARAM_CHECK(p_packet);
    NULL_PARAM_CHECK(p_interface);
    PACKET_LENGTH_CHECK(packet_len);

    uint32_t               retval        = NRF_SUCCESS;
    uint8_t              * p_output_buff = NULL;
    uint16_t               output_len;
    tx_packet_t            tx_packet;
    transport_instance_t * p_instance = &m_instances[(uint32_t)p_interface->p_transport];

    BLE_6LOWPAN_MUTEX_LOCK();

    BLE_6LOWPAN_TRC("Uncompressed packet:");
    BLE_6LOWPAN_DUMP((uint8_t *)p_packet, packet_len);

    // Encode IP packet into IPHC.
    retval = iphc_encode(p_interface,
                         &p_output_buff,
                         &output_len,
                         p_packet,
                         packet_len);

    if (NRF_SUCCESS == retval)
    {
        BLE_6LOWPAN_TRC("Successfully compressed packet.");

        tx_packet.p_data      = p_output_buff;
        tx_packet.data_len    = output_len;
        tx_packet.p_mem_block = (uint8_t *)p_packet;

        retval = tx_fifo_put(&p_instance->tx_fifo, &tx_packet);

        if (NRF_SUCCESS == retval)
        {
            BLE_6LOWPAN_TRC("Compressed packet:");
            BLE_6LOWPAN_DUMP(p_output_buff, output_len);

            // Send packet immediately if transport interface is not busy.
            if (tx_is_free(p_instance))
            {
                tx_send(p_instance);
            }
        }
        else
        {
            BLE_6LOWPAN_ERR("No place in TX queue!");
        }
    }
    else
    {
        BLE_6LOWPAN_ERR("Error while compression!");
    }

    BLE_6LOWPAN_MUTEX_UNLOCK();

    BLE_6LOWPAN_EXIT();

    return retval;
}
