mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-29 23:39:35 +00:00 
			
		
		
		
	When the ast_data_buffer_put rejects to add a packet, for example because the buffer already contains a packet with the same sequence number, the payload will never be freed, resulting in a memory leak. The data buffer will now return an error if this situation occurs allowing the caller to free the payload. The res_rtp_asterisk module has also been updated to do this. ASTERISK-28826 Change-Id: Ie6c49495d1c921d5f997651c7d0f79646f095cf1
		
			
				
	
	
		
			369 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Asterisk -- An open source telephony toolkit.
 | |
|  *
 | |
|  * Copyright (C) 2018, Digium, Inc.
 | |
|  *
 | |
|  * Joshua Colp <jcolp@digium.com>
 | |
|  * Ben Ford <bford@digium.com>
 | |
|  *
 | |
|  * See http://www.asterisk.org for more information about
 | |
|  * the Asterisk project. Please do not directly contact
 | |
|  * any of the maintainers of this project for assistance;
 | |
|  * the project provides a web site, mailing lists and IRC
 | |
|  * channels for your use.
 | |
|  *
 | |
|  * This program is free software, distributed under the terms of
 | |
|  * the GNU General Public License Version 2. See the LICENSE file
 | |
|  * at the top of the source tree.
 | |
|  */
 | |
| 
 | |
| /*! \file
 | |
|  *
 | |
|  * \brief Data Buffer API
 | |
|  *
 | |
|  * \author Joshua Colp <jcolp@digium.com>
 | |
|  * \author Ben Ford <bford@digium.com>
 | |
|  */
 | |
| 
 | |
| /*** MODULEINFO
 | |
| 	<support_level>core</support_level>
 | |
|  ***/
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| #include "asterisk/logger.h"
 | |
| #include "asterisk/strings.h"
 | |
| #include "asterisk/data_buffer.h"
 | |
| #include "asterisk/linkedlists.h"
 | |
| 
 | |
| /*!
 | |
|  * \brief The number of payloads to increment the cache by
 | |
|  */
 | |
| #define CACHED_PAYLOAD_MAX 5
 | |
| 
 | |
| /*!
 | |
|  * \brief Payload entry placed inside of the data buffer list
 | |
|  */
 | |
| struct data_buffer_payload_entry {
 | |
| 	/*! \brief The payload for this position */
 | |
| 	void *payload;
 | |
| 	/*! \brief The provided position for this */
 | |
| 	size_t pos;
 | |
| 	/*! \brief Linked list information */
 | |
| 	AST_LIST_ENTRY(data_buffer_payload_entry) list;
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * \brief Data buffer containing fixed number of data payloads
 | |
|  */
 | |
| struct ast_data_buffer {
 | |
| 	/*! \brief Callback function to free a data payload */
 | |
| 	ast_data_buffer_free_callback free_fn;
 | |
| 	/*! \brief A linked list of data payloads */
 | |
| 	AST_LIST_HEAD_NOLOCK(, data_buffer_payload_entry) payloads;
 | |
| 	/*! \brief A linked list of unused cached data payloads */
 | |
| 	AST_LIST_HEAD_NOLOCK(, data_buffer_payload_entry) cached_payloads;
 | |
| 	/*! \brief The current number of data payloads in the buffer */
 | |
| 	size_t count;
 | |
| 	/*! \brief Maximum number of data payloads in the buffer */
 | |
| 	size_t max;
 | |
| 	/*! \brief The current number of data payloads in the cache */
 | |
| 	size_t cache_count;
 | |
| };
 | |
| 
 | |
| static void free_fn_do_nothing(void *data)
 | |
| {
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Helper function to allocate a data payload
 | |
|  */
 | |
| static struct data_buffer_payload_entry *data_buffer_payload_alloc(void *payload, size_t pos)
 | |
| {
 | |
| 	struct data_buffer_payload_entry *data_payload;
 | |
| 
 | |
| 	data_payload = ast_calloc(1, sizeof(*data_payload));
 | |
| 	if (!data_payload) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	data_payload->payload = payload;
 | |
| 	data_payload->pos = pos;
 | |
| 
 | |
| 	return data_payload;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Helper function that sets the cache to its maximum number of payloads
 | |
|  */
 | |
| static void ast_data_buffer_cache_adjust(struct ast_data_buffer *buffer)
 | |
| {
 | |
| 	int buffer_space;
 | |
| 
 | |
| 	ast_assert(buffer != NULL);
 | |
| 
 | |
| 	buffer_space = buffer->max - buffer->count;
 | |
| 
 | |
| 	if (buffer->cache_count == buffer_space) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (buffer->cache_count < buffer_space) {
 | |
| 		/* Add payloads to the cache, if able */
 | |
| 		while (buffer->cache_count < CACHED_PAYLOAD_MAX && buffer->cache_count < buffer_space) {
 | |
| 			struct data_buffer_payload_entry *buffer_payload;
 | |
| 
 | |
| 			buffer_payload = data_buffer_payload_alloc(NULL, -1);
 | |
| 			if (buffer_payload) {
 | |
| 				AST_LIST_INSERT_TAIL(&buffer->cached_payloads, buffer_payload, list);
 | |
| 				buffer->cache_count++;
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			ast_log(LOG_ERROR, "Failed to allocate memory to the cache.");
 | |
| 			break;
 | |
| 		}
 | |
| 	} else if (buffer->cache_count > buffer_space) {
 | |
| 		/* Remove payloads from the cache */
 | |
| 		while (buffer->cache_count > buffer_space) {
 | |
| 			struct data_buffer_payload_entry *buffer_payload;
 | |
| 
 | |
| 			buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->cached_payloads, list);
 | |
| 			if (buffer_payload) {
 | |
| 				ast_free(buffer_payload);
 | |
| 				buffer->cache_count--;
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			ast_log(LOG_ERROR, "Failed to remove memory from the cache.");
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| struct ast_data_buffer *ast_data_buffer_alloc(ast_data_buffer_free_callback free_fn, size_t size)
 | |
| {
 | |
| 	struct ast_data_buffer *buffer;
 | |
| 
 | |
| 	ast_assert(size != 0);
 | |
| 
 | |
| 	buffer = ast_calloc(1, sizeof(*buffer));
 | |
| 	if (!buffer) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	AST_LIST_HEAD_INIT_NOLOCK(&buffer->payloads);
 | |
| 	AST_LIST_HEAD_INIT_NOLOCK(&buffer->cached_payloads);
 | |
| 
 | |
| 	/* If free_fn is NULL, just use free_fn_do_nothing as a default */
 | |
| 	buffer->free_fn = free_fn ? free_fn : free_fn_do_nothing;
 | |
| 	buffer->max = size;
 | |
| 
 | |
| 	ast_data_buffer_cache_adjust(buffer);
 | |
| 
 | |
| 	return buffer;
 | |
| }
 | |
| 
 | |
| void ast_data_buffer_resize(struct ast_data_buffer *buffer, size_t size)
 | |
| {
 | |
| 	struct data_buffer_payload_entry *existing_payload;
 | |
| 
 | |
| 	ast_assert(buffer != NULL);
 | |
| 
 | |
| 	/* The buffer must have at least a size of 1 */
 | |
| 	ast_assert(size > 0);
 | |
| 
 | |
| 	if (buffer->max == size) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* If the size is decreasing, some payloads will need to be freed */
 | |
| 	if (buffer->max > size) {
 | |
| 		int remove = buffer->max - size;
 | |
| 
 | |
| 		AST_LIST_TRAVERSE_SAFE_BEGIN(&buffer->payloads, existing_payload, list) {
 | |
| 			if (remove) {
 | |
| 				AST_LIST_REMOVE_HEAD(&buffer->payloads, list);
 | |
| 				buffer->free_fn(existing_payload->payload);
 | |
| 				ast_free(existing_payload);
 | |
| 				buffer->count--;
 | |
| 				remove--;
 | |
| 				continue;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 		AST_LIST_TRAVERSE_SAFE_END;
 | |
| 	}
 | |
| 
 | |
| 	buffer->max = size;
 | |
| 	ast_data_buffer_cache_adjust(buffer);
 | |
| }
 | |
| 
 | |
| int ast_data_buffer_put(struct ast_data_buffer *buffer, size_t pos, void *payload)
 | |
| {
 | |
| 	struct data_buffer_payload_entry *buffer_payload = NULL;
 | |
| 	struct data_buffer_payload_entry *existing_payload;
 | |
| 	int inserted = 0;
 | |
| 
 | |
| 	ast_assert(buffer != NULL);
 | |
| 	ast_assert(payload != NULL);
 | |
| 
 | |
| 	/* If the data buffer has reached its maximum size then the head goes away and
 | |
| 	 * we will reuse its buffer payload
 | |
| 	 */
 | |
| 	if (buffer->count == buffer->max) {
 | |
| 		buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->payloads, list);
 | |
| 		buffer->free_fn(buffer_payload->payload);
 | |
| 		buffer->count--;
 | |
| 
 | |
| 		/* Update this buffer payload with its new information */
 | |
| 		buffer_payload->payload = payload;
 | |
| 		buffer_payload->pos = pos;
 | |
| 	}
 | |
| 	if (!buffer_payload) {
 | |
| 		if (!buffer->cache_count) {
 | |
| 			ast_data_buffer_cache_adjust(buffer);
 | |
| 		}
 | |
| 		buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->cached_payloads, list);
 | |
| 		buffer->cache_count--;
 | |
| 
 | |
| 		/* Update the payload from the cache with its new information */
 | |
| 		buffer_payload->payload = payload;
 | |
| 		buffer_payload->pos = pos;
 | |
| 	}
 | |
| 	if (!buffer_payload) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* Given the position find its ideal spot within the buffer */
 | |
| 	AST_LIST_TRAVERSE_SAFE_BEGIN(&buffer->payloads, existing_payload, list) {
 | |
| 		/* If it's already in the buffer, drop it */
 | |
| 		if (existing_payload->pos == pos) {
 | |
| 			ast_debug(3, "Packet with position %zu is already in buffer. Not inserting.\n", pos);
 | |
| 			inserted = -1;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		if (existing_payload->pos > pos) {
 | |
| 			AST_LIST_INSERT_BEFORE_CURRENT(buffer_payload, list);
 | |
| 			inserted = 1;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	AST_LIST_TRAVERSE_SAFE_END;
 | |
| 
 | |
| 	if (inserted == -1) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (!inserted) {
 | |
| 		AST_LIST_INSERT_TAIL(&buffer->payloads, buffer_payload, list);
 | |
| 	}
 | |
| 
 | |
| 	buffer->count++;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void *ast_data_buffer_get(const struct ast_data_buffer *buffer, size_t pos)
 | |
| {
 | |
| 	struct data_buffer_payload_entry *buffer_payload;
 | |
| 
 | |
| 	ast_assert(buffer != NULL);
 | |
| 
 | |
| 	AST_LIST_TRAVERSE(&buffer->payloads, buffer_payload, list) {
 | |
| 		if (buffer_payload->pos == pos) {
 | |
| 			return buffer_payload->payload;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static void data_buffer_free_buffer_payload(struct ast_data_buffer *buffer,
 | |
| 	struct data_buffer_payload_entry *buffer_payload)
 | |
| {
 | |
| 	buffer_payload->payload = NULL;
 | |
| 	buffer->count--;
 | |
| 
 | |
| 	if (buffer->cache_count < CACHED_PAYLOAD_MAX
 | |
| 			&& buffer->cache_count < (buffer->max - buffer->count)) {
 | |
| 		AST_LIST_INSERT_TAIL(&buffer->cached_payloads, buffer_payload, list);
 | |
| 		buffer->cache_count++;
 | |
| 	} else {
 | |
| 		ast_free(buffer_payload);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void *ast_data_buffer_remove(struct ast_data_buffer *buffer, size_t pos)
 | |
| {
 | |
| 	struct data_buffer_payload_entry *buffer_payload;
 | |
| 
 | |
| 	ast_assert(buffer != NULL);
 | |
| 
 | |
| 	AST_LIST_TRAVERSE_SAFE_BEGIN(&buffer->payloads, buffer_payload, list) {
 | |
| 		if (buffer_payload->pos == pos) {
 | |
| 			void *payload = buffer_payload->payload;
 | |
| 
 | |
| 			AST_LIST_REMOVE_CURRENT(list);
 | |
| 			data_buffer_free_buffer_payload(buffer, buffer_payload);
 | |
| 
 | |
| 			return payload;
 | |
| 		}
 | |
| 	}
 | |
| 	AST_LIST_TRAVERSE_SAFE_END;
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| void *ast_data_buffer_remove_head(struct ast_data_buffer *buffer)
 | |
| {
 | |
| 	ast_assert(buffer != NULL);
 | |
| 
 | |
| 	if (buffer->count > 0) {
 | |
| 		struct data_buffer_payload_entry *buffer_payload;
 | |
| 		void *payload;
 | |
| 
 | |
| 		buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->payloads, list);
 | |
| 		payload = buffer_payload->payload;
 | |
| 		data_buffer_free_buffer_payload(buffer, buffer_payload);
 | |
| 
 | |
| 		return payload;
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| void ast_data_buffer_free(struct ast_data_buffer *buffer)
 | |
| {
 | |
| 	struct data_buffer_payload_entry *buffer_payload;
 | |
| 
 | |
| 	ast_assert(buffer != NULL);
 | |
| 
 | |
| 	while ((buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->payloads, list))) {
 | |
| 		buffer->free_fn(buffer_payload->payload);
 | |
| 		ast_free(buffer_payload);
 | |
| 	}
 | |
| 
 | |
| 	while ((buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->cached_payloads, list))) {
 | |
| 		ast_free(buffer_payload);
 | |
| 	}
 | |
| 
 | |
| 	ast_free(buffer);
 | |
| }
 | |
| 
 | |
| size_t ast_data_buffer_count(const struct ast_data_buffer *buffer)
 | |
| {
 | |
| 	ast_assert(buffer != NULL);
 | |
| 
 | |
| 	return buffer->count;
 | |
| }
 | |
| 
 | |
| size_t ast_data_buffer_max(const struct ast_data_buffer *buffer)
 | |
| {
 | |
| 	ast_assert(buffer != NULL);
 | |
| 
 | |
| 	return buffer->max;
 | |
| }
 |