/* * Copyright (c) 2011-2020, Pelion 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); }