/*
 * Copyright (c) 2011-2019, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nsconfig.h"
#include "string.h"
#include "ns_types.h"
#include "nsdynmemLIB.h"
#include "Core/include/ns_address_internal.h"
#include "Core/include/ns_buffer.h"
#include "Core/include/ns_socket.h"
#include "ns_trace.h"
#include "platform/arm_hal_interrupt.h"
#include "NWK_INTERFACE/Include/protocol_stats.h"
#include "ip_fsc.h"
#include "net_interface.h"

#define TRACE_GROUP "buff"

volatile unsigned int buffer_count = 0;

uint8_t *(buffer_corrupt_check)(buffer_t *buf)
{
    if (buf == NULL) {
        return NULL;
    }

    if (buf->buf_ptr > buf->buf_end) {
        tr_error("Buffer pointer end not set");
    } else if (buffer_data_length(buf) < 0) {
        tr_error("Buffer length overflow");
        while (1);
    } else if (buf->buf_end > buf->size || buf->buf_ptr > buf->size) {
        tr_error("buffer pointer overridden");
        while (1);
    }
    return buffer_data_pointer(buf);
}

buffer_t *buffer_get(uint16_t size)
{
    return buffer_get_specific(BUFFER_DEFAULT_HEADROOM, size, BUFFER_DEFAULT_MIN_SIZE);
}

buffer_t *buffer_get_minimal(uint16_t size)
{
    return buffer_get_specific(0, size, 0);
}

/**
 * Get pointer to a buffer_t structure and reserve memory for it from the dynamic heap.
 *
 * \param headroom required headroom in addition to basic size
 * \param size basic size of data allocate memory for
 * \param minspace minimum size of buffer
 * \return a pointer of type buffer_t to the allocated memory area
 *
 */
buffer_t *buffer_get_specific(uint16_t headroom, uint16_t size, uint16_t minspace)
{
    buffer_t *buf;
    uint16_t total_size;

    total_size = headroom + size;
    if (total_size < minspace) {
        total_size = minspace;
    }

    /* Round total size up to at least be a neat multiple - allocation must
     * anyway be this much aligned. */
    total_size = (total_size + 3) & ~ 3;

    // Note - as well as this alloc+init, buffers can also be "realloced"
    // in buffer_headroom()

    buf = ns_dyn_mem_temporary_alloc(sizeof(buffer_t) + total_size);
    if (buf) {
        platform_enter_critical();
        buffer_count++;
        platform_exit_critical();
        memset(buf, 0, sizeof(buffer_t));
        buf->buf_ptr = total_size - size;
        buf->buf_end = buf->buf_ptr;
        buf->socket = NULL;
        buf->interface = NULL;
        //buf->bad_channel = 0xffff;
        //buf->bc_sending_superframe = 0xff;
        buf->rpl_instance = 0xff;
        // Socket TX always overrides this, so this is the default for non-socket buffers.
        // Setting it to 0 would remove flow labels on internal ICMP messages without affecting sockets.
        buf->options.flow_label = IPV6_FLOW_UNSPECIFIED;
        buf->options.hop_limit = 255;
        buf->options.mpl_permitted = true;
        buf->link_specific.ieee802_15_4.useDefaultPanId = true;
#ifndef NO_IPV6_PMTUD
        buf->options.ipv6_use_min_mtu = -1;
#endif
        buf->size = total_size;
    } else {
        tr_error("buffer_get failed: alloc(%d)", (int) sizeof(buffer_t) + total_size);
    }

    protocol_stats_update(STATS_BUFFER_ALLOC, 1);
    return (buf);
}

/**
 * Make sure buffer has enough room for header.
 *
 * \param buf buffer to check
 * \param size required header space
 * \return a pointer of type buffer_t to the newly allocated buffer (or the original one)
 *
 */
buffer_t *buffer_headroom(buffer_t *buf, uint16_t size)
{
    uint16_t curr_len = buffer_data_length(buf);

    if (buf->size < (curr_len + size)) {
        /* This buffer isn't big enough at all - allocate a new block */
        // TODO - should we be giving them extra? probably
        uint16_t new_total = (curr_len + size + 3) & ~ 3;
        buffer_t *restrict new_buf = ns_dyn_mem_temporary_alloc(sizeof(buffer_t) + new_total);
        if (new_buf) {
            // Copy the buffer_t header
            *new_buf = *buf;
            // Set new pointers, leaving specified headroom
            new_buf->buf_ptr = size;
            new_buf->buf_end = size + curr_len;
            new_buf->size = new_total;
            // Copy the current data
            memcpy(buffer_data_pointer(new_buf), buffer_data_pointer(buf), curr_len);
            protocol_stats_update(STATS_BUFFER_HEADROOM_REALLOC, 1);
            ns_dyn_mem_free(buf);
            buf = new_buf;
        } else {
            tr_error("HeadRoom Fail");
            protocol_stats_update(STATS_BUFFER_HEADROOM_FAIL, 1);
            socket_tx_buffer_event_and_free(buf, SOCKET_NO_RAM);
            buf = NULL;
        }
    } else if (buf->buf_ptr < size) {
        /* This buffer is big enough, but not enough headroom - shuffle */
        // TODO - surely better to shuffle all the way to the end in one go?
        uint8_t *orig_ptr = buffer_data_pointer(buf);
        buf->buf_ptr = size;
        buf->buf_end = size + curr_len;
        if (curr_len != 0) {
            memmove(buffer_data_pointer(buf), orig_ptr, curr_len);
            protocol_stats_update(STATS_BUFFER_HEADROOM_SHUFFLE, 1);
        }
    }
    buffer_corrupt_check(buf);
    return buf;
}

buffer_t *buffer_free_route(buffer_t *buf)
{
    if (buf->route) {
        if (--buf->route->ref_count == 0) {
            ns_dyn_mem_free(buf->route);
        }
        buf->route = NULL;
    }
    return buf;
}

/**
 * Free a memory block from the heap.
 *
 * \param buf pointer to buffer to be freed
 * \return (buffer_t *) NULL
 *
 */
buffer_t *buffer_free(buffer_t *buf)
{
    if (buf) {
        platform_enter_critical();
        if (buffer_count) {
            buffer_count--;
        } else {
            tr_error("bc neg");
        }
        platform_exit_critical();

        buf = buffer_free_route(buf);
        socket_dereference(buf->socket);
        ns_dyn_mem_free(buf->predecessor);
        ns_dyn_mem_free(buf->rpl_option);
        ns_dyn_mem_free(buf);

    } else {
        tr_error("nullp F");
    }
    return NULL;
}

void buffer_free_list(buffer_list_t *list)
{
    ns_list_foreach_safe(buffer_t, buf, list) {
        ns_list_remove(list, buf);
        buffer_free(buf);
    }
}

/* Prepare a buffer that came from a received packet for use as a new
 * transmission (eg ICMP error response). Kill fields which should not be
 * carried over. This is distinct from a packet we are forwarding.
 */
buffer_t *buffer_turnaround(buffer_t *buf)
{
    if (buf->predecessor) {
        ns_dyn_mem_free(buf->predecessor);
        buf->predecessor = NULL;
    }

    if (buf->rpl_option) {
        ns_dyn_mem_free(buf->rpl_option);
        buf->rpl_option = NULL;
    }
    buf->options.tunnelled = false;
    buf->rpl_flag_error = 0;
    buf->rpl_instance_known = false;
    buf->link_specific.ieee802_15_4.useDefaultPanId = true;

    buffer_socket_set(buf, NULL);

    /* Most cases this will be a response to an RX, so no existing routing
     * info, but in the case of TX resolution failure, we're reversing and
     * need to re-evaluate routing.
     */
    return buffer_free_route(buf);
}

void buffer_note_predecessor(buffer_t *buf, const sockaddr_t *addr)
{
    if (buf->options.need_predecessor && !buf->predecessor) {
        buf->predecessor = ns_dyn_mem_temporary_alloc(sizeof * buf->predecessor);
        if (buf->predecessor) {
            memcpy(buf->predecessor, addr, sizeof * buf->predecessor);
        }
    }
}

socket_t *buffer_socket_set(buffer_t *buf, socket_t *socket)
{
    buf->socket = socket_dereference(buf->socket);
    buf->socket = socket_reference(socket);
    return buf->socket;
}

/* Copy metadata information from src into dst.
 *
 * Data size and pointers left unmodified in destination.
 * Other in-buffer metadata copied from src.
 * Any route information pointer cloned from src (reference count increased).
 * Other metadata pointers transfered either to dst or left in src, as requested
 */
void buffer_copy_metadata(buffer_t *dst, buffer_t *src, bool non_clonable_to_dst)
{
    uint16_t buf_size = dst->size;
    uint16_t buf_end = dst->buf_end;
    uint16_t buf_ptr = dst->buf_ptr;
    *dst = *src;
    if (dst->route) {
        dst->route->ref_count++;
    }
    dst->size = buf_size;
    dst->buf_ptr = buf_ptr;
    dst->buf_end = buf_end;

    /* Extra data pointers now attached to both buffers - there can be only one */
    buffer_t *to_wipe = non_clonable_to_dst ? src : dst;
    to_wipe->rpl_option = NULL;
    to_wipe->predecessor = NULL;
    to_wipe->socket = NULL;
}

/**
 * Add buffer at the end of data.
 *
 * \param buf pointer to buffer where data is added
 * \param data_ptr data pointer where data is copied
 * \data_len length of data copied.
 *
 */
void buffer_data_add(buffer_t *buf, const uint8_t *data_ptr, uint16_t data_len)
{
    memcpy(buffer_data_end(buf), data_ptr, data_len);
    buffer_data_end_set(buf, buffer_data_end(buf) + data_len);
#ifdef EXTRA_CONSISTENCY_CHECKS
    buffer_corrupt_check(buf);
#endif
    return;
}

/**
 * Create new buffer that has the same data and fields.
 *
 * \param buf pointer to buffer to be freed
 * \return (buffer_t *) new clone.
 *
 */
buffer_t *buffer_clone(buffer_t *buf)
{
    buffer_t *result_ptr = NULL;

    if (buf == NULL) {
        return NULL;
    }

    result_ptr = buffer_get(buffer_data_length(buf));

    if (result_ptr == NULL) {
        return NULL;
    }

    uint16_t buf_ptr = result_ptr->buf_ptr;
    uint16_t buf_end = result_ptr->buf_end;
    uint16_t size = result_ptr->size;

    *result_ptr = *buf;
    result_ptr->predecessor = NULL;
    result_ptr->route = NULL; // Don't clone routing info
    result_ptr->socket = NULL; // Don't clone Socket info
    result_ptr->options.multicast_loop = false; // Don't loop back more copies!
    result_ptr->rpl_option = NULL;
    result_ptr->buf_ptr = buf_ptr;
    result_ptr->buf_end = buf_end;
    result_ptr->size = size;
    buffer_data_add(result_ptr, buffer_data_pointer(buf), buffer_data_length(buf));

    return result_ptr;
}

uint16_t buffer_ipv6_fcf(const buffer_t *buf, uint8_t next_header)
{
    return ipv6_fcf(buf->src_sa.address, buf->dst_sa.address,
                    buffer_data_length(buf), buffer_data_pointer(buf),
                    next_header);
}
