diff options
-rw-r--r-- | bridges/bridge_softmix.c | 48 | ||||
-rwxr-xr-x | configure | 203 | ||||
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | include/asterisk/data_buffer.h | 144 | ||||
-rw-r--r-- | include/asterisk/module.h | 11 | ||||
-rw-r--r-- | main/asterisk.c | 2 | ||||
-rw-r--r-- | main/data_buffer.c | 314 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_transport_events.c | 55 | ||||
-rw-r--r-- | tests/test_data_buffer.c | 313 |
9 files changed, 873 insertions, 223 deletions
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index 9986d9c73..16e1fb897 100644 --- a/bridges/bridge_softmix.c +++ b/bridges/bridge_softmix.c @@ -956,6 +956,30 @@ static void softmix_bridge_write_voice(struct ast_bridge *bridge, struct ast_bri /*! * \internal + * \brief Clear talking flag, stop contributing to mixing and notify handlers. + * \since 13.21.0, 15.4.0 + * + * \param bridge_channel Which channel's talking to clear + * + * \return Nothing + */ +static void clear_talking(struct ast_bridge_channel *bridge_channel) +{ + struct softmix_channel *sc = bridge_channel->tech_pvt; + + if (sc->talking) { + ast_mutex_lock(&sc->lock); + ast_slinfactory_flush(&sc->factory); + sc->talking = 0; + ast_mutex_unlock(&sc->lock); + + /* Notify that we are no longer talking. */ + ast_bridge_channel_notify_talking(bridge_channel, 0); + } +} + +/*! + * \internal * \brief Check for voice status updates. * \since 13.20.0 * @@ -966,23 +990,14 @@ static void softmix_bridge_write_voice(struct ast_bridge *bridge, struct ast_bri */ static void softmix_bridge_check_voice(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) { - struct softmix_channel *sc = bridge_channel->tech_pvt; - - if (sc->talking - && bridge_channel->features->mute) { + if (bridge_channel->features->mute) { /* * We were muted while we were talking. * * Immediately stop contributing to mixing * and report no longer talking. */ - ast_mutex_lock(&sc->lock); - ast_slinfactory_flush(&sc->factory); - sc->talking = 0; - ast_mutex_unlock(&sc->lock); - - /* Notify that we are no longer talking. */ - ast_bridge_channel_notify_talking(bridge_channel, 0); + clear_talking(bridge_channel); } } @@ -1113,6 +1128,17 @@ static int softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_br */ switch (frame->subclass.integer) { + case AST_CONTROL_HOLD: + /* + * Doing anything for holds in a conference bridge could be considered a bit + * odd. That being said, in most cases one would probably want the talking + * flag cleared when 'hold' is pressed by the remote endpoint, so go ahead + * and do that here. However, that is all we'll do. Meaning if for some reason + * the endpoint continues to send audio frames despite pressing 'hold' talking + * will once again be detected for that channel. + */ + clear_talking(bridge_channel); + break; case AST_CONTROL_VIDUPDATE: if (!bridge->softmix.video_mode.video_update_discard || ast_tvdiff_ms(ast_tvnow(), softmix_data->last_video_update) > bridge->softmix.video_mode.video_update_discard) { @@ -6820,10 +6820,8 @@ $as_echo "no" >&6; } fi -for ac_prog in python2 python2.7 -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 +# Extract the first word of "python", so it can be a program name with args. +set dummy python; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_PYTHON+:} false; then : @@ -6849,6 +6847,7 @@ done done IFS=$as_save_IFS + test -z "$ac_cv_path_PYTHON" && ac_cv_path_PYTHON=":" ;; esac fi @@ -6862,10 +6861,6 @@ $as_echo "no" >&6; } fi - test -n "$PYTHON" && break -done -test -n "$PYTHON" || PYTHON=":" - # Extract the first word of "find", so it can be a program name with args. set dummy find; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 @@ -26002,188 +25997,12 @@ if test -n "$PYTHONDEV_CFLAGS"; then pkg_cv_PYTHONDEV_CFLAGS="$PYTHONDEV_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python >= 3\""; } >&5 - ($PKG_CONFIG --exists --print-errors "python >= 3") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_PYTHONDEV_CFLAGS=`$PKG_CONFIG --cflags "python >= 3" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi -if test -n "$PYTHONDEV_LIBS"; then - pkg_cv_PYTHONDEV_LIBS="$PYTHONDEV_LIBS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python >= 3\""; } >&5 - ($PKG_CONFIG --exists --print-errors "python >= 3") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_PYTHONDEV_LIBS=`$PKG_CONFIG --libs "python >= 3" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi - - - -if test $pkg_failed = yes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi - if test $_pkg_short_errors_supported = yes; then - PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "python >= 3" 2>&1` - else - PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "python >= 3" 2>&1` - fi - # Put the nasty error message in config.log where it belongs - echo "$PYTHONDEV_PKG_ERRORS" >&5 - - - PBX_PYTHONDEV=0 - - -elif test $pkg_failed = untried; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - - PBX_PYTHONDEV=0 - - -else - PYTHONDEV_CFLAGS=$pkg_cv_PYTHONDEV_CFLAGS - PYTHONDEV_LIBS=$pkg_cv_PYTHONDEV_LIBS - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - - PBX_PYTHONDEV=1 - PYTHONDEV_INCLUDE="$PYTHONDEV_CFLAGS" - PYTHONDEV_LIB="$PYTHONDEV_LIBS" - -$as_echo "#define HAVE_PYTHONDEV 1" >>confdefs.h - - -fi - fi - - - if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then - -pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PYTHONDEV" >&5 -$as_echo_n "checking for PYTHONDEV... " >&6; } - -if test -n "$PYTHONDEV_CFLAGS"; then - pkg_cv_PYTHONDEV_CFLAGS="$PYTHONDEV_CFLAGS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python3\""; } >&5 - ($PKG_CONFIG --exists --print-errors "python3") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_PYTHONDEV_CFLAGS=`$PKG_CONFIG --cflags "python3" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi -if test -n "$PYTHONDEV_LIBS"; then - pkg_cv_PYTHONDEV_LIBS="$PYTHONDEV_LIBS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python3\""; } >&5 - ($PKG_CONFIG --exists --print-errors "python3") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then - pkg_cv_PYTHONDEV_LIBS=`$PKG_CONFIG --libs "python3" 2>/dev/null` - test "x$?" != "x0" && pkg_failed=yes -else - pkg_failed=yes -fi - else - pkg_failed=untried -fi - - - -if test $pkg_failed = yes; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - -if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then - _pkg_short_errors_supported=yes -else - _pkg_short_errors_supported=no -fi - if test $_pkg_short_errors_supported = yes; then - PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "python3" 2>&1` - else - PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "python3" 2>&1` - fi - # Put the nasty error message in config.log where it belongs - echo "$PYTHONDEV_PKG_ERRORS" >&5 - - - PBX_PYTHONDEV=0 - - -elif test $pkg_failed = untried; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - - PBX_PYTHONDEV=0 - - -else - PYTHONDEV_CFLAGS=$pkg_cv_PYTHONDEV_CFLAGS - PYTHONDEV_LIBS=$pkg_cv_PYTHONDEV_LIBS - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - - PBX_PYTHONDEV=1 - PYTHONDEV_INCLUDE="$PYTHONDEV_CFLAGS" - PYTHONDEV_LIB="$PYTHONDEV_LIBS" - -$as_echo "#define HAVE_PYTHONDEV 1" >>confdefs.h - - -fi - fi - - - if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then - -pkg_failed=no -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PYTHONDEV" >&5 -$as_echo_n "checking for PYTHONDEV... " >&6; } - -if test -n "$PYTHONDEV_CFLAGS"; then - pkg_cv_PYTHONDEV_CFLAGS="$PYTHONDEV_CFLAGS" - elif test -n "$PKG_CONFIG"; then - if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python-3.6\""; } >&5 - ($PKG_CONFIG --exists --print-errors "python-3.6") 2>&5 + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python\""; } >&5 + ($PKG_CONFIG --exists --print-errors "python") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_PYTHONDEV_CFLAGS=`$PKG_CONFIG --cflags "python-3.6" 2>/dev/null` + pkg_cv_PYTHONDEV_CFLAGS=`$PKG_CONFIG --cflags "python" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -26195,12 +26014,12 @@ if test -n "$PYTHONDEV_LIBS"; then pkg_cv_PYTHONDEV_LIBS="$PYTHONDEV_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python-3.6\""; } >&5 - ($PKG_CONFIG --exists --print-errors "python-3.6") 2>&5 + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python\""; } >&5 + ($PKG_CONFIG --exists --print-errors "python") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_PYTHONDEV_LIBS=`$PKG_CONFIG --libs "python-3.6" 2>/dev/null` + pkg_cv_PYTHONDEV_LIBS=`$PKG_CONFIG --libs "python" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -26221,9 +26040,9 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "python-3.6" 2>&1` + PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "python" 2>&1` else - PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "python-3.6" 2>&1` + PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "python" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$PYTHONDEV_PKG_ERRORS" >&5 diff --git a/configure.ac b/configure.ac index 2ed4cd9cd..e2af23493 100644 --- a/configure.ac +++ b/configure.ac @@ -261,7 +261,7 @@ AC_PATH_PROG([CAT], [cat], :) AC_PATH_PROG([CUT], [cut], :) AC_PATH_PROG([FLEX], [flex], :) AC_PATH_PROG([GREP], [grep], :) -AC_PATH_PROGS([PYTHON], [python2 python2.7], :) +AC_PATH_PROG([PYTHON], [python], :) AC_PATH_PROG([FIND], [find], :) AC_PATH_PROG([COMPRESS], [compress], :) AC_PATH_PROG([BASENAME], [basename], :) @@ -2309,9 +2309,7 @@ fi AC_SUBST([PYTHONDEV_LIB]) AC_SUBST([PYTHONDEV_INCLUDE]) -AST_PKG_CONFIG_CHECK([PYTHONDEV], [python >= 3]) -AST_PKG_CONFIG_CHECK([PYTHONDEV], [python3]) -AST_PKG_CONFIG_CHECK([PYTHONDEV], [python-3.6]) +AST_PKG_CONFIG_CHECK([PYTHONDEV], [python]) AST_EXT_LIB_CHECK([POPT], [popt], [poptStrerror], [popt.h]) diff --git a/include/asterisk/data_buffer.h b/include/asterisk/data_buffer.h new file mode 100644 index 000000000..dacbaa5e4 --- /dev/null +++ b/include/asterisk/data_buffer.h @@ -0,0 +1,144 @@ +/* + * 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 + * + * A data buffer acts as a ring buffer of data. It is given a fixed + * number of data packets to store (which may be dynamically changed). + * Given a number it will store a data packet at that position relative + * to the others. Given a number it will retrieve the given data packet + * if it is present. This is purposely a storage of arbitrary things so + * that it can be used for multiple things. + * + * \author Joshua Colp <jcolp@digium.com> + * \author Ben Ford <bford@digium.com> + */ + +#ifndef _AST_DATA_BUFFER_H_ +#define _AST_DATA_BUFFER_H_ + +/*! + * \brief A buffer of data payloads. + */ +struct ast_data_buffer; + +/*! + * \brief A callback function to free a data payload in a data buffer + * + * \param The data payload + */ +typedef void (*ast_data_buffer_free_callback)(void *data); + +/*! + * \brief Allocate a data buffer + * + * \param free_fn Callback function to free a data payload + * \param size The maximum number of data payloads to contain in the data buffer + * + * \retval non-NULL success + * \retval NULL failure + * + * \note free_fn can be NULL. It is up to the consumer of this API to ensure that memory is + * managed appropriately. + * + * \since 15.4.0 + */ +struct ast_data_buffer *ast_data_buffer_alloc(ast_data_buffer_free_callback free_fn, size_t size); + +/*! + * \brief Resize a data buffer + * + * \param buffer The data buffer + * \param size The new maximum size of the data buffer + * + * \note If the data buffer is shrunk any old data payloads will be freed using the configured callback. + * The data buffer is flexible and can be used for multiple purposes. Therefore it is up to the + * caller of the function to know whether or not a buffer should have its size changed. Increasing + * the size of the buffer may make sense in some scenarios, but shrinking should always be handled + * with caution since data can be lost. + * + * \since 15.4.0 + */ +void ast_data_buffer_resize(struct ast_data_buffer *buffer, size_t size); + +/*! + * \brief Place a data payload at a position in the data buffer + * + * \param buffer The data buffer + * \param pos The position of the data payload + * \param payload The data payload + * + * \retval 0 success + * \retval -1 failure + * + * \note It is up to the consumer of this API to ensure proper memory management of data payloads + * + * \since 15.4.0 + */ +int ast_data_buffer_put(struct ast_data_buffer *buffer, size_t pos, void *payload); + +/*! + * \brief Retrieve a data payload from the data buffer + * + * \param buffer The data buffer + * \param pos The position of the data payload + * + * \retval non-NULL success + * \retval NULL failure + * + * \note This does not remove the data payload from the data buffer. It will be removed when it is displaced. + * + * \since 15.4.0 + */ +void *ast_data_buffer_get(const struct ast_data_buffer *buffer, size_t pos); + +/*! + * \brief Free a data buffer (and all held data payloads) + * + * \param buffer The data buffer + * + * \since 15.4.0 + */ +void ast_data_buffer_free(struct ast_data_buffer *buffer); + +/*! + * \brief Return the number of payloads in a data buffer + * + * \param buffer The data buffer + * + * \retval the number of data payloads + * + * \since 15.4.0 + */ +size_t ast_data_buffer_count(const struct ast_data_buffer *buffer); + +/*! + * \brief Return the maximum number of payloads a data buffer can hold + * + * \param buffer The data buffer + * + * \retval the maximum number of data payloads + * + * \since 15.4.0 + */ +size_t ast_data_buffer_max(const struct ast_data_buffer *buffer); + +#endif /* _AST_DATA_BUFFER_H */ diff --git a/include/asterisk/module.h b/include/asterisk/module.h index faa4f7f67..08b4c4317 100644 --- a/include/asterisk/module.h +++ b/include/asterisk/module.h @@ -376,6 +376,13 @@ struct ast_module_info { */ const char *enhances; + /*! These reserved fields should be NULL, they exist to allow addition to this + * structure in a non-breaking way. */ + void *reserved1; + void *reserved2; + void *reserved3; + void *reserved4; + /*! The support level for the given module */ enum ast_module_support_level support_level; }; @@ -448,6 +455,10 @@ void __ast_module_unref(struct ast_module *mod, const char *file, int line, cons NULL, \ NULL, \ NULL, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ support_level, \ }; \ static void __attribute__((constructor)) __reg_module(void) \ diff --git a/main/asterisk.c b/main/asterisk.c index 36b1b54a1..2e80ffaf6 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -3963,7 +3963,7 @@ int main(int argc, char *argv[]) * * \todo Document these options */ - optind = 0; + optind = 1; while ((c = getopt(argc, argv, getopt_settings)) != -1) { /*!\note Please keep the ordering here to alphabetical, capital letters * first. This will make it easier in the future to select unused diff --git a/main/data_buffer.c b/main/data_buffer.c new file mode 100644 index 000000000..ccbffd22d --- /dev/null +++ b/main/data_buffer.c @@ -0,0 +1,314 @@ +/* + * 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 0; + } + + 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; +} + +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; +} diff --git a/res/res_pjsip/pjsip_transport_events.c b/res/res_pjsip/pjsip_transport_events.c index c701b8411..cc7b7c077 100644 --- a/res/res_pjsip/pjsip_transport_events.c +++ b/res/res_pjsip/pjsip_transport_events.c @@ -114,6 +114,36 @@ static void transport_monitor_dtor(void *vdoomed) AST_VECTOR_FREE(&monitored->monitors); } +/*! + * \internal + * \brief Do registered callbacks for the transport. + * \since 13.21.0 + * + * \param transports Active transports container + * \param transport Which transport to do callbacks for. + * + * \return Nothing + */ +static void transport_state_do_reg_callbacks(struct ao2_container *transports, pjsip_transport *transport) +{ + struct transport_monitor *monitored; + + monitored = ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_UNLINK); + if (monitored) { + int idx; + + for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { + struct transport_monitor_notifier *notifier; + + notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); + ast_debug(3, "running callback %p(%p) for transport %s\n", + notifier->cb, notifier->data, transport->obj_name); + notifier->cb(notifier->data); + } + ao2_ref(monitored, -1); + } +} + /*! \brief Callback invoked when transport state changes occur */ static void transport_state_callback(pjsip_transport *transport, pjsip_transport_state state, const pjsip_transport_state_info *info) @@ -147,6 +177,7 @@ static void transport_state_callback(pjsip_transport *transport, if (!transport->is_shutdown) { pjsip_transport_shutdown(transport); } + transport_state_do_reg_callbacks(transports, transport); break; case PJSIP_TP_STATE_SHUTDOWN: /* @@ -157,23 +188,17 @@ static void transport_state_callback(pjsip_transport *transport, */ transport->is_shutdown = PJ_TRUE; - monitored = ao2_find(transports, transport->obj_name, - OBJ_SEARCH_KEY | OBJ_UNLINK); - if (monitored) { - int idx; - - for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { - struct transport_monitor_notifier *notifier; - - notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); - ast_debug(3, "running callback %p(%p) for transport %s\n", - notifier->cb, notifier->data, transport->obj_name); - notifier->cb(notifier->data); - } - ao2_ref(monitored, -1); - } + transport_state_do_reg_callbacks(transports, transport); + break; + case PJSIP_TP_STATE_DESTROY: + transport_state_do_reg_callbacks(transports, transport); break; default: + /* + * We have to have a default case because the enum is + * defined by a third-party library. + */ + ast_assert(0); break; } diff --git a/tests/test_data_buffer.c b/tests/test_data_buffer.c new file mode 100644 index 000000000..11fdc7b6f --- /dev/null +++ b/tests/test_data_buffer.c @@ -0,0 +1,313 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2018, Digium, Inc. + * + * 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 Media Stream API Unit Tests + * + * \author Ben Ford <bford@digium.com> + * + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +#include "asterisk/module.h" +#include "asterisk/test.h" +#include "asterisk/data_buffer.h" + +#define BUFFER_MAX_NOMINAL 10 + +struct mock_payload +{ + int id; +}; + +/* Ensures that RAII_VAR will not trip ast_assert(buffer != NULL) in the callback */ +static void ast_data_buffer_free_wrapper(struct ast_data_buffer *buffer) +{ + if (!buffer) { + return; + } + + ast_data_buffer_free(buffer); +} + +AST_TEST_DEFINE(buffer_create) +{ + RAII_VAR(struct ast_data_buffer *, buffer, NULL, ast_data_buffer_free_wrapper); + + switch (cmd) { + case TEST_INIT: + info->name = "buffer_create"; + info->category = "/main/data_buffer/"; + info->summary = "buffer create unit test"; + info->description = + "Test that creating a data buffer results in a buffer with the expected values"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + buffer = ast_data_buffer_alloc(ast_free_ptr, BUFFER_MAX_NOMINAL); + + ast_test_validate(test, buffer != NULL, + "Failed to create buffer with valid arguments"); + ast_test_validate(test, ast_data_buffer_count(buffer) == 0, + "Newly created buffer does not have the expected payload count"); + ast_test_validate(test, ast_data_buffer_max(buffer) == BUFFER_MAX_NOMINAL, + "Newly created buffer does not have the expected max size"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(buffer_put) +{ + RAII_VAR(struct ast_data_buffer *, buffer, NULL, ast_data_buffer_free_wrapper); + struct mock_payload *payload; + struct mock_payload *fetched_payload; + int ret; + + switch (cmd) { + case TEST_INIT: + info->name = "buffer_put"; + info->category = "/main/data_buffer/"; + info->summary = "buffer put unit test"; + info->description = + "Test that putting payloads in the buffer yields the expected results"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + buffer = ast_data_buffer_alloc(ast_free_ptr, 2); + + ast_test_validate(test, buffer != NULL, + "Failed to create buffer with valid arguments"); + ast_test_validate(test, ast_data_buffer_count(buffer) == 0, + "Newly created buffer is not empty"); + + payload = ast_calloc(1, sizeof(*payload)); + + ast_test_validate(test, payload != NULL, + "Failed to allocate memory for first payload"); + + payload->id = 2; + ret = ast_data_buffer_put(buffer, 2, payload); + + ast_test_validate(test, ret == 0, + "Adding a payload to an empty buffer did not return the expected value"); + ast_test_validate(test, ast_data_buffer_count(buffer) == 1, + "Adding a payload to an empty buffer did not update count to the expected value"); + + fetched_payload = (struct mock_payload *)ast_data_buffer_get(buffer, 2); + + ast_test_validate(test, fetched_payload != NULL, + "Failed to get only payload from buffer given valid arguments"); + + ast_data_buffer_put(buffer, 2, payload); + + ast_test_validate(test, ast_data_buffer_count(buffer) == 1, + "Adding a payload that is already in the buffer should not do anything"); + + payload = ast_calloc(1, sizeof(*payload)); + + ast_test_validate(test, payload != NULL, + "Failed to allocate memory for second payload"); + + payload->id = 1; + ast_data_buffer_put(buffer, 1, payload); + fetched_payload = ast_data_buffer_get(buffer, 1); + + ast_test_validate(test, fetched_payload != NULL, + "Failed to get a payload from buffer given valid arguments"); + ast_test_validate(test, ast_data_buffer_count(buffer) == 2, + "Buffer does not have the expected count after removing a payload"); + ast_test_validate(test, fetched_payload->id == 1, + "Did not get the expected payload from the buffer"); + + payload = ast_calloc(1, sizeof(*payload)); + + ast_test_validate(test, payload != NULL, + "Failed to allocate memory for third payload"); + + payload->id = 3; + ret = ast_data_buffer_put(buffer, 3, payload); + + ast_test_validate(test, ret == 0, + "Failed to replace a payload in the buffer"); + ast_test_validate(test, ast_data_buffer_count(buffer) <= 2, + "Buffer count exceeded the max"); + + fetched_payload = (struct mock_payload *)ast_data_buffer_get(buffer, 3); + + ast_test_validate(test, fetched_payload != NULL, + "Failed to get a payload from buffer at position 3 given valid arguments"); + ast_test_validate(test, fetched_payload->id == 3, + "Did not get the expected payload at position 3 from the buffer"); + + fetched_payload = (struct mock_payload *)ast_data_buffer_get(buffer, 2); + + ast_test_validate(test, fetched_payload != NULL, + "Failed to get a payload from buffer at position 2 given valid arguments"); + ast_test_validate(test, fetched_payload->id == 2, + "Did not get the expected payload at position 2 from the buffer"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(buffer_resize) +{ + RAII_VAR(struct ast_data_buffer *, buffer, NULL, ast_data_buffer_free_wrapper); + + switch (cmd) { + case TEST_INIT: + info->name = "buffer_resize"; + info->category = "/main/data_buffer/"; + info->summary = "buffer resize unit test"; + info->description = + "Tests resizing a data buffer to make sure it has the expected outcome"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + buffer = ast_data_buffer_alloc(ast_free_ptr, BUFFER_MAX_NOMINAL); + + ast_test_validate(test, buffer != NULL, + "Failed to create buffer with valid arguments"); + + ast_data_buffer_resize(buffer, BUFFER_MAX_NOMINAL); + + ast_test_validate(test, ast_data_buffer_max(buffer) == BUFFER_MAX_NOMINAL, + "Trying to resize buffer to same size should not change its max size"); + + ast_data_buffer_resize(buffer, BUFFER_MAX_NOMINAL + 2); + + ast_test_validate(test, ast_data_buffer_max(buffer) == BUFFER_MAX_NOMINAL + 2, + "Increasing buffer size did not return the expected max"); + + ast_data_buffer_resize(buffer, 1); + + ast_test_validate(test, ast_data_buffer_max(buffer) == 1, + "Decreasing buffer size did not return the expected max"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(buffer_nominal) +{ + RAII_VAR(struct ast_data_buffer *, buffer, NULL, ast_data_buffer_free_wrapper); + struct mock_payload *payload; + struct mock_payload *fetched_payload; + int ret; + int i; + + switch (cmd) { + case TEST_INIT: + info->name = "buffer_nominal"; + info->category = "/main/data_buffer/"; + info->summary = "buffer nominal unit test"; + info->description = + "Tests the normal usage of a data buffer to ensure the expected payloads " + "are present after multiple insertions"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + buffer = ast_data_buffer_alloc(ast_free_ptr, BUFFER_MAX_NOMINAL); + + ast_test_validate(test, buffer != NULL, + "Failed to create buffer with valid arguments"); + + for (i = 1; i <= BUFFER_MAX_NOMINAL; i++) { + payload = ast_calloc(1, sizeof(*payload)); + + ast_test_validate(test, payload != NULL, + "Failed to allocate memory for payload %d", i); + + ret = ast_data_buffer_put(buffer, i, payload); + + ast_test_validate(test, ret == 0, + "Failed to add payload %d to buffer", i); + } + + ast_test_validate(test, ast_data_buffer_count(buffer) == BUFFER_MAX_NOMINAL, + "Buffer does not have the expected count after adding payloads"); + + for (i = 1; i <= BUFFER_MAX_NOMINAL; i++) { + fetched_payload = (struct mock_payload *)ast_data_buffer_get(buffer, i); + + ast_test_validate(test, fetched_payload != NULL, + "Failed to get payload at position %d during first loop", i); + } + + for (i = 1; i <= BUFFER_MAX_NOMINAL; i++) { + payload = ast_calloc(1, sizeof(*payload)); + + ast_test_validate(test, payload != NULL, + "Failed to allocate memory for payload %d", i + BUFFER_MAX_NOMINAL); + + ret = ast_data_buffer_put(buffer, i + BUFFER_MAX_NOMINAL, payload); + + ast_test_validate(test, ret == 0, + "Failed to add payload %d to buffer", i + BUFFER_MAX_NOMINAL); + } + + ast_test_validate(test, ast_data_buffer_count(buffer) == BUFFER_MAX_NOMINAL, + "Buffer does not have the expected count after replacing payloads"); + + for (i = 1; i <= BUFFER_MAX_NOMINAL; i++) { + fetched_payload = (struct mock_payload *)ast_data_buffer_get(buffer, i); + + ast_test_validate(test, fetched_payload == NULL, + "Got an unexpected payload at position %d", i); + + fetched_payload = (struct mock_payload *)ast_data_buffer_get(buffer, i + BUFFER_MAX_NOMINAL); + + ast_test_validate(test, fetched_payload != NULL, + "Failed to get payload at position %d during second loop", i + BUFFER_MAX_NOMINAL); + } + + return AST_TEST_PASS; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(buffer_create); + AST_TEST_UNREGISTER(buffer_put); + AST_TEST_UNREGISTER(buffer_resize); + AST_TEST_UNREGISTER(buffer_nominal); + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(buffer_create); + AST_TEST_REGISTER(buffer_put); + AST_TEST_REGISTER(buffer_resize); + AST_TEST_REGISTER(buffer_nominal); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Data buffer API test module"); |