<p>Jenkins2 <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/8604">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Sean Bright: Looks good to me, but someone else must approve
  Kevin Harwell: Looks good to me, approved
  Jenkins2: Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Add data buffer API to store packets.<br><br>Adds a data buffer with a configurable size that can store different<br>kinds of packets (like RTP packets for retransmission). Given a number<br>it will store a data packet at that position relative to the others.<br>Given a number it will retrieve the given data packet if it is present.<br>This is purposely a storage of arbitrary things so it can be used not<br>just for RTP packets but also Asterisk frames in the future if needed.<br>The API does not internally use a lock, so it will be up to the user of<br>the API to properly protect the data buffer.<br><br>For more information, refer to the wiki page:<br>https://wiki.asterisk.org/wiki/display/AST/WebRTC+User+Experience+Improvements<br><br>Change-Id: Iff13c5d4795d52356959fe2a57360cd57dfade07<br>---<br>A include/asterisk/data_buffer.h<br>A main/data_buffer.c<br>2 files changed, 458 insertions(+), 0 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/include/asterisk/data_buffer.h b/include/asterisk/data_buffer.h<br>new file mode 100644<br>index 0000000..dacbaa5<br>--- /dev/null<br>+++ b/include/asterisk/data_buffer.h<br>@@ -0,0 +1,144 @@<br>+/*<br>+ * Asterisk -- An open source telephony toolkit.<br>+ *<br>+ * Copyright (C) 2018, Digium, Inc.<br>+ *<br>+ * Joshua Colp <jcolp@digium.com><br>+ * Ben Ford <bford@digium.com><br>+ *<br>+ * See http://www.asterisk.org for more information about<br>+ * the Asterisk project. Please do not directly contact<br>+ * any of the maintainers of this project for assistance;<br>+ * the project provides a web site, mailing lists and IRC<br>+ * channels for your use.<br>+ *<br>+ * This program is free software, distributed under the terms of<br>+ * the GNU General Public License Version 2. See the LICENSE file<br>+ * at the top of the source tree.<br>+ */<br>+<br>+/*!<br>+ * \file<br>+ * \brief Data Buffer API<br>+ *<br>+ * A data buffer acts as a ring buffer of data. It is given a fixed<br>+ * number of data packets to store (which may be dynamically changed).<br>+ * Given a number it will store a data packet at that position relative<br>+ * to the others. Given a number it will retrieve the given data packet<br>+ * if it is present. This is purposely a storage of arbitrary things so<br>+ * that it can be used for multiple things.<br>+ *<br>+ * \author Joshua Colp <jcolp@digium.com><br>+ * \author Ben Ford <bford@digium.com><br>+ */<br>+<br>+#ifndef _AST_DATA_BUFFER_H_<br>+#define _AST_DATA_BUFFER_H_<br>+<br>+/*!<br>+ * \brief A buffer of data payloads.<br>+ */<br>+struct ast_data_buffer;<br>+<br>+/*!<br>+ * \brief A callback function to free a data payload in a data buffer<br>+ *<br>+ * \param The data payload<br>+ */<br>+typedef void (*ast_data_buffer_free_callback)(void *data);<br>+<br>+/*!<br>+ * \brief Allocate a data buffer<br>+ *<br>+ * \param free_fn Callback function to free a data payload<br>+ * \param size The maximum number of data payloads to contain in the data buffer<br>+ *<br>+ * \retval non-NULL success<br>+ * \retval NULL failure<br>+ *<br>+ * \note free_fn can be NULL. It is up to the consumer of this API to ensure that memory is<br>+ * managed appropriately.<br>+ *<br>+ * \since 15.4.0<br>+ */<br>+struct ast_data_buffer *ast_data_buffer_alloc(ast_data_buffer_free_callback free_fn, size_t size);<br>+<br>+/*!<br>+ * \brief Resize a data buffer<br>+ *<br>+ * \param buffer The data buffer<br>+ * \param size The new maximum size of the data buffer<br>+ *<br>+ * \note If the data buffer is shrunk any old data payloads will be freed using the configured callback.<br>+ * The data buffer is flexible and can be used for multiple purposes. Therefore it is up to the<br>+ * caller of the function to know whether or not a buffer should have its size changed. Increasing<br>+ * the size of the buffer may make sense in some scenarios, but shrinking should always be handled<br>+ * with caution since data can be lost.<br>+ *<br>+ * \since 15.4.0<br>+ */<br>+void ast_data_buffer_resize(struct ast_data_buffer *buffer, size_t size);<br>+<br>+/*!<br>+ * \brief Place a data payload at a position in the data buffer<br>+ *<br>+ * \param buffer The data buffer<br>+ * \param pos The position of the data payload<br>+ * \param payload The data payload<br>+ *<br>+ * \retval 0 success<br>+ * \retval -1 failure<br>+ *<br>+ * \note It is up to the consumer of this API to ensure proper memory management of data payloads<br>+ *<br>+ * \since 15.4.0<br>+ */<br>+int ast_data_buffer_put(struct ast_data_buffer *buffer, size_t pos, void *payload);<br>+<br>+/*!<br>+ * \brief Retrieve a data payload from the data buffer<br>+ *<br>+ * \param buffer The data buffer<br>+ * \param pos The position of the data payload<br>+ *<br>+ * \retval non-NULL success<br>+ * \retval NULL failure<br>+ *<br>+ * \note This does not remove the data payload from the data buffer. It will be removed when it is displaced.<br>+ *<br>+ * \since 15.4.0<br>+ */<br>+void *ast_data_buffer_get(const struct ast_data_buffer *buffer, size_t pos);<br>+<br>+/*!<br>+ * \brief Free a data buffer (and all held data payloads)<br>+ *<br>+ * \param buffer The data buffer<br>+ *<br>+ * \since 15.4.0<br>+ */<br>+void ast_data_buffer_free(struct ast_data_buffer *buffer);<br>+<br>+/*!<br>+ * \brief Return the number of payloads in a data buffer<br>+ *<br>+ * \param buffer The data buffer<br>+ *<br>+ * \retval the number of data payloads<br>+ *<br>+ * \since 15.4.0<br>+ */<br>+size_t ast_data_buffer_count(const struct ast_data_buffer *buffer);<br>+<br>+/*!<br>+ * \brief Return the maximum number of payloads a data buffer can hold<br>+ *<br>+ * \param buffer The data buffer<br>+ *<br>+ * \retval the maximum number of data payloads<br>+ *<br>+ * \since 15.4.0<br>+ */<br>+size_t ast_data_buffer_max(const struct ast_data_buffer *buffer);<br>+<br>+#endif /* _AST_DATA_BUFFER_H */<br>diff --git a/main/data_buffer.c b/main/data_buffer.c<br>new file mode 100644<br>index 0000000..ccbffd2<br>--- /dev/null<br>+++ b/main/data_buffer.c<br>@@ -0,0 +1,314 @@<br>+/*<br>+ * Asterisk -- An open source telephony toolkit.<br>+ *<br>+ * Copyright (C) 2018, Digium, Inc.<br>+ *<br>+ * Joshua Colp <jcolp@digium.com><br>+ * Ben Ford <bford@digium.com><br>+ *<br>+ * See http://www.asterisk.org for more information about<br>+ * the Asterisk project. Please do not directly contact<br>+ * any of the maintainers of this project for assistance;<br>+ * the project provides a web site, mailing lists and IRC<br>+ * channels for your use.<br>+ *<br>+ * This program is free software, distributed under the terms of<br>+ * the GNU General Public License Version 2. See the LICENSE file<br>+ * at the top of the source tree.<br>+ */<br>+<br>+/*! \file<br>+ *<br>+ * \brief Data Buffer API<br>+ *<br>+ * \author Joshua Colp <jcolp@digium.com><br>+ * \author Ben Ford <bford@digium.com><br>+ */<br>+<br>+/*** MODULEINFO<br>+     <support_level>core</support_level><br>+ ***/<br>+<br>+#include "asterisk.h"<br>+<br>+#include "asterisk/logger.h"<br>+#include "asterisk/strings.h"<br>+#include "asterisk/data_buffer.h"<br>+#include "asterisk/linkedlists.h"<br>+<br>+/*!<br>+ * \brief The number of payloads to increment the cache by<br>+ */<br>+#define CACHED_PAYLOAD_MAX 5<br>+<br>+/*!<br>+ * \brief Payload entry placed inside of the data buffer list<br>+ */<br>+struct data_buffer_payload_entry {<br>+        /*! \brief The payload for this position */<br>+  void *payload;<br>+       /*! \brief The provided position for this */<br>+ size_t pos;<br>+  /*! \brief Linked list information */<br>+        AST_LIST_ENTRY(data_buffer_payload_entry) list;<br>+};<br>+<br>+/*!<br>+ * \brief Data buffer containing fixed number of data payloads<br>+ */<br>+struct ast_data_buffer {<br>+      /*! \brief Callback function to free a data payload */<br>+       ast_data_buffer_free_callback free_fn;<br>+       /*! \brief A linked list of data payloads */<br>+ AST_LIST_HEAD_NOLOCK(, data_buffer_payload_entry) payloads;<br>+  /*! \brief A linked list of unused cached data payloads */<br>+   AST_LIST_HEAD_NOLOCK(, data_buffer_payload_entry) cached_payloads;<br>+   /*! \brief The current number of data payloads in the buffer */<br>+      size_t count;<br>+        /*! \brief Maximum number of data payloads in the buffer */<br>+  size_t max;<br>+  /*! \brief The current number of data payloads in the cache */<br>+       size_t cache_count;<br>+};<br>+<br>+static void free_fn_do_nothing(void *data)<br>+{<br>+ return;<br>+}<br>+<br>+/*!<br>+ * \brief Helper function to allocate a data payload<br>+ */<br>+static struct data_buffer_payload_entry *data_buffer_payload_alloc(void *payload, size_t pos)<br>+{<br>+        struct data_buffer_payload_entry *data_payload;<br>+<br>+   data_payload = ast_calloc(1, sizeof(*data_payload));<br>+ if (!data_payload) {<br>+         return NULL;<br>+ }<br>+<br>+ data_payload->payload = payload;<br>+  data_payload->pos = pos;<br>+<br>+       return data_payload;<br>+}<br>+<br>+/*!<br>+ * \brief Helper function that sets the cache to its maximum number of payloads<br>+ */<br>+static void ast_data_buffer_cache_adjust(struct ast_data_buffer *buffer)<br>+{<br>+     int buffer_space;<br>+<br>+ ast_assert(buffer != NULL);<br>+<br>+       buffer_space = buffer->max - buffer->count;<br>+<br>+ if (buffer->cache_count == buffer_space) {<br>+                return;<br>+      }<br>+<br>+ if (buffer->cache_count < buffer_space) {<br>+              /* Add payloads to the cache, if able */<br>+             while (buffer->cache_count < CACHED_PAYLOAD_MAX && buffer->cache_count < buffer_space) {<br>+                 struct data_buffer_payload_entry *buffer_payload;<br>+<br>+                 buffer_payload = data_buffer_payload_alloc(NULL, -1);<br>+                        if (buffer_payload) {<br>+                                AST_LIST_INSERT_TAIL(&buffer->cached_payloads, buffer_payload, list);<br>+                         buffer->cache_count++;<br>+                            continue;<br>+                    }<br>+<br>+                 ast_log(LOG_ERROR, "Failed to allocate memory to the cache.");<br>+                     break;<br>+               }<br>+    } else if (buffer->cache_count > buffer_space) {<br>+               /* Remove payloads from the cache */<br>+         while (buffer->cache_count > buffer_space) {<br>+                   struct data_buffer_payload_entry *buffer_payload;<br>+<br>+                 buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->cached_payloads, list);<br>+                        if (buffer_payload) {<br>+                                ast_free(buffer_payload);<br>+                            buffer->cache_count--;<br>+                            continue;<br>+                    }<br>+<br>+                 ast_log(LOG_ERROR, "Failed to remove memory from the cache.");<br>+                     break;<br>+               }<br>+    }<br>+}<br>+<br>+struct ast_data_buffer *ast_data_buffer_alloc(ast_data_buffer_free_callback free_fn, size_t size)<br>+{<br>+     struct ast_data_buffer *buffer;<br>+<br>+   ast_assert(size != 0);<br>+<br>+    buffer = ast_calloc(1, sizeof(*buffer));<br>+     if (!buffer) {<br>+               return NULL;<br>+ }<br>+<br>+ AST_LIST_HEAD_INIT_NOLOCK(&buffer->payloads);<br>+ AST_LIST_HEAD_INIT_NOLOCK(&buffer->cached_payloads);<br>+<br>+       /* If free_fn is NULL, just use free_fn_do_nothing as a default */<br>+   buffer->free_fn = free_fn ? free_fn : free_fn_do_nothing;<br>+ buffer->max = size;<br>+<br>+    ast_data_buffer_cache_adjust(buffer);<br>+<br>+     return buffer;<br>+}<br>+<br>+void ast_data_buffer_resize(struct ast_data_buffer *buffer, size_t size)<br>+{<br>+ struct data_buffer_payload_entry *existing_payload;<br>+<br>+       ast_assert(buffer != NULL);<br>+<br>+       /* The buffer must have at least a size of 1 */<br>+      ast_assert(size > 0);<br>+<br>+  if (buffer->max == size) {<br>+                return;<br>+      }<br>+<br>+ /* If the size is decreasing, some payloads will need to be freed */<br>+ if (buffer->max > size) {<br>+              int remove = buffer->max - size;<br>+<br>+               AST_LIST_TRAVERSE_SAFE_BEGIN(&buffer->payloads, existing_payload, list) {<br>+                     if (remove) {<br>+                                AST_LIST_REMOVE_HEAD(&buffer->payloads, list);<br>+                                buffer->free_fn(existing_payload->payload);<br>+                            ast_free(existing_payload);<br>+                          buffer->count--;<br>+                          remove--;<br>+                            continue;<br>+                    }<br>+                    break;<br>+               }<br>+            AST_LIST_TRAVERSE_SAFE_END;<br>+  }<br>+<br>+ buffer->max = size;<br>+       ast_data_buffer_cache_adjust(buffer);<br>+}<br>+<br>+int ast_data_buffer_put(struct ast_data_buffer *buffer, size_t pos, void *payload)<br>+{<br>+        struct data_buffer_payload_entry *buffer_payload = NULL;<br>+     struct data_buffer_payload_entry *existing_payload;<br>+  int inserted = 0;<br>+<br>+ ast_assert(buffer != NULL);<br>+  ast_assert(payload != NULL);<br>+<br>+      /* If the data buffer has reached its maximum size then the head goes away and<br>+        * we will reuse its buffer payload<br>+   */<br>+  if (buffer->count == buffer->max) {<br>+            buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->payloads, list);<br>+               buffer->free_fn(buffer_payload->payload);<br>+              buffer->count--;<br>+<br>+               /* Update this buffer payload with its new information */<br>+            buffer_payload->payload = payload;<br>+                buffer_payload->pos = pos;<br>+        }<br>+    if (!buffer_payload) {<br>+               if (!buffer->cache_count) {<br>+                       ast_data_buffer_cache_adjust(buffer);<br>+                }<br>+            buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->cached_payloads, list);<br>+                buffer->cache_count--;<br>+<br>+         /* Update the payload from the cache with its new information */<br>+             buffer_payload->payload = payload;<br>+                buffer_payload->pos = pos;<br>+        }<br>+    if (!buffer_payload) {<br>+               return -1;<br>+   }<br>+<br>+ /* Given the position find its ideal spot within the buffer */<br>+       AST_LIST_TRAVERSE_SAFE_BEGIN(&buffer->payloads, existing_payload, list) {<br>+             /* If it's already in the buffer, drop it */<br>+             if (existing_payload->pos == pos) {<br>+                       ast_debug(3, "Packet with position %zu is already in buffer. Not inserting.\n", pos);<br>+                      inserted = -1;<br>+                       break;<br>+               }<br>+<br>+         if (existing_payload->pos > pos) {<br>+                     AST_LIST_INSERT_BEFORE_CURRENT(buffer_payload, list);<br>+                        inserted = 1;<br>+                        break;<br>+               }<br>+    }<br>+    AST_LIST_TRAVERSE_SAFE_END;<br>+<br>+       if (inserted == -1) {<br>+                return 0;<br>+    }<br>+<br>+ if (!inserted) {<br>+             AST_LIST_INSERT_TAIL(&buffer->payloads, buffer_payload, list);<br>+        }<br>+<br>+ buffer->count++;<br>+<br>+       return 0;<br>+}<br>+<br>+void *ast_data_buffer_get(const struct ast_data_buffer *buffer, size_t pos)<br>+{<br>+   struct data_buffer_payload_entry *buffer_payload;<br>+<br>+ ast_assert(buffer != NULL);<br>+<br>+       AST_LIST_TRAVERSE(&buffer->payloads, buffer_payload, list) {<br>+          if (buffer_payload->pos == pos) {<br>+                 return buffer_payload->payload;<br>+           }<br>+    }<br>+<br>+ return NULL;<br>+}<br>+<br>+void ast_data_buffer_free(struct ast_data_buffer *buffer)<br>+{<br>+  struct data_buffer_payload_entry *buffer_payload;<br>+<br>+ ast_assert(buffer != NULL);<br>+<br>+       while ((buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->payloads, list))) {<br>+            buffer->free_fn(buffer_payload->payload);<br>+              ast_free(buffer_payload);<br>+    }<br>+<br>+ while ((buffer_payload = AST_LIST_REMOVE_HEAD(&buffer->cached_payloads, list))) {<br>+             ast_free(buffer_payload);<br>+    }<br>+<br>+ ast_free(buffer);<br>+}<br>+<br>+size_t ast_data_buffer_count(const struct ast_data_buffer *buffer)<br>+{<br>+    ast_assert(buffer != NULL);<br>+<br>+       return buffer->count;<br>+}<br>+<br>+size_t ast_data_buffer_max(const struct ast_data_buffer *buffer)<br>+{<br>+       ast_assert(buffer != NULL);<br>+<br>+       return buffer->max;<br>+}<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/8604">change 8604</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/8604"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 15 </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: Iff13c5d4795d52356959fe2a57360cd57dfade07 </div>
<div style="display:none"> Gerrit-Change-Number: 8604 </div>
<div style="display:none"> Gerrit-PatchSet: 11 </div>
<div style="display:none"> Gerrit-Owner: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Matthew Fredrickson <creslin@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Sean Bright <sean.bright@gmail.com> </div>