mbed-os/source/Core/buffer_dyn.c

366 lines
11 KiB
C

/*
* 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 <limits.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"
// Get module working also on 16-bit platform
#if INT_MAX < 0xFFFF
#define BUFFER_MAX_SIZE ((size_t)INT_MAX)
#else
#define BUFFER_MAX_SIZE ((size_t)0xFFFF)
#endif
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 || buf->buf_end > buf->size) {
tr_error("Invalid buffer, size=%"PRIu16", buf_ptr=%"PRIu16", buf_end=%"PRIu16"", buf->size, buf->buf_ptr, buf->buf_end);
tr_error("Data: %s", tr_array(buffer_data_pointer(buf), 56));
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 = NULL;
uint32_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;
if (total_size <= BUFFER_MAX_SIZE) {
// 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(%"PRIu32")", (uint32_t)(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)) {
buffer_t *restrict new_buf = NULL;
/* This buffer isn't big enough at all - allocate a new block */
// TODO - should we be giving them extra? probably
uint32_t new_total = (curr_len + size + 3) & ~ 3;
if (new_total <= BUFFER_MAX_SIZE) {
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);
}