summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--channels/chan_unistim.c27
-rwxr-xr-xconfigure125
-rw-r--r--configure.ac4
-rw-r--r--include/asterisk/autoconfig.h.in4
-rw-r--r--include/asterisk/channel.h34
-rw-r--r--include/asterisk/channel_internal.h4
-rw-r--r--include/asterisk/stream.h23
-rw-r--r--main/Makefile4
-rw-r--r--main/channel.c9
-rw-r--r--main/channel_internal_api.c103
-rw-r--r--main/http.c17
-rw-r--r--main/manager.c2
-rw-r--r--main/stream.c51
-rw-r--r--res/res_config_sqlite3.c2
-rw-r--r--res/res_pjsip/pjsip_distributor.c4
-rw-r--r--res/res_pjsip/pjsip_scheduler.c32
-rw-r--r--res/res_pjsip_exten_state.c5
-rw-r--r--res/res_pjsip_pubsub.c231
-rw-r--r--res/res_rtp_asterisk.c2
-rw-r--r--tests/test_stream.c318
-rw-r--r--tests/test_voicemail_api.c4
-rw-r--r--third-party/pjproject/configure.m41
-rw-r--r--third-party/pjproject/patches/0010-evsub-Add-pjsip_evsub_set_uas_timeout.patch84
23 files changed, 939 insertions, 151 deletions
diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c
index b8ccdbb2a..f82522111 100644
--- a/channels/chan_unistim.c
+++ b/channels/chan_unistim.c
@@ -161,6 +161,7 @@ enum autoprov_extn {
#define LED_HEADPHONE_ON 0x011
#define LED_MUTE_OFF 0x018
#define LED_MUTE_ON 0x019
+#define LED_MUTE_BLINK 0x1A
#define SIZE_HEADER 6
#define SIZE_MAC_ADDR 17
@@ -357,8 +358,8 @@ struct unistim_subchannel {
int softkey; /*! Softkey assigned */
pthread_t ss_thread; /*! unistim_ss thread handle */
int alreadygone;
- char ringvolume;
- char ringstyle;
+ signed char ringvolume;
+ signed char ringstyle;
int moh; /*!< Music on hold in progress */
AST_LIST_ENTRY(unistim_subchannel) list;
};
@@ -413,13 +414,13 @@ static struct unistim_device {
char maintext2[25]; /*!< when the phone is idle, display this string on line 2 */
char titledefault[13]; /*!< title (text before date/time) */
char datetimeformat; /*!< format used for displaying time/date */
- char contrast; /*!< contrast */
+ signed char contrast; /*!< contrast */
char country[3]; /*!< country used for dial tone frequency */
struct ast_tone_zone *tz; /*!< Tone zone for res_indications (ring, busy, congestion) */
- char ringvolume; /*!< Ring volume */
- char ringstyle; /*!< Ring melody */
- char cwvolume; /*!< Ring volume on call waiting */
- char cwstyle; /*!< Ring melody on call waiting */
+ signed char ringvolume; /*!< Ring volume */
+ signed char ringstyle; /*!< Ring melody */
+ signed char cwvolume; /*!< Ring volume on call waiting */
+ signed char cwstyle; /*!< Ring melody on call waiting */
int interdigit_timer; /*!< Interdigit timer for dialing number by timeout */
int dtmfduration; /*!< DTMF playback duration */
time_t nextdial; /*!< Timer used for dial by timeout */
@@ -443,7 +444,7 @@ static struct unistim_device {
int nat; /*!< Used by the obscure ast_rtp_setnat */
enum autoprov_extn extension; /*!< See ifdef EXTENSION for valid values */
char extension_number[11]; /*!< Extension number entered by the user */
- char to_delete; /*!< Used in reload */
+ signed char to_delete; /*!< Used in reload */
struct ast_silence_generator *silence_generator;
AST_LIST_HEAD(,unistim_subchannel) subs; /*!< pointer to our current connection, channel... */
AST_LIST_HEAD(,unistim_line) lines;
@@ -1701,7 +1702,7 @@ send_select_output(struct unistimsession *pte, unsigned char output, unsigned ch
}
pte->device->output = output;
}
-static void send_ring(struct unistimsession *pte, char volume, char style)
+static void send_ring(struct unistimsession *pte, signed char volume, signed char style)
{
BUFFSEND;
if (unistimdebug) {
@@ -4835,7 +4836,7 @@ static int unistim_call(struct ast_channel *ast, const char *dest, int timeout)
int res = 0, i;
struct unistim_subchannel *sub, *sub_real;
struct unistimsession *session;
- char ringstyle, ringvolume;
+ signed char ringstyle, ringvolume;
session = channel_to_session(ast);
if (!session) {
@@ -5438,8 +5439,8 @@ static struct unistim_subchannel *find_subchannel_by_name(const char *dest)
if ((*at < '0') || (*at > '7')) { /* ring style */
ast_log(LOG_WARNING, "Invalid ring selection (%s)", at);
} else {
- char ring_volume = -1;
- char ring_style = *at - '0';
+ signed char ring_volume = -1;
+ signed char ring_style = *at - '0';
at++;
if ((*at >= '0') && (*at <= '3')) { /* ring volume */
ring_volume = *at - '0';
@@ -6483,7 +6484,7 @@ static struct unistim_device *build_device(const char *cat, const struct ast_var
int create = 1;
int nbsoftkey, dateformat, timeformat, callhistory, sharpdial, linecnt;
char linelabel[AST_MAX_EXTENSION];
- char ringvolume, ringstyle, cwvolume, cwstyle;
+ signed char ringvolume, ringstyle, cwvolume, cwstyle;
/* First, we need to know if we already have this name in our list */
/* Get a lock for the device chained list */
diff --git a/configure b/configure
index ba8d59357..42a21d7c5 100755
--- a/configure
+++ b/configure
@@ -947,6 +947,10 @@ PBX_POPT
POPT_DIR
POPT_INCLUDE
POPT_LIB
+PBX_PJSIP_EVSUB_SET_UAS_TIMEOUT
+PJSIP_EVSUB_SET_UAS_TIMEOUT_DIR
+PJSIP_EVSUB_SET_UAS_TIMEOUT_INCLUDE
+PJSIP_EVSUB_SET_UAS_TIMEOUT_LIB
PBX_PJSIP_AUTH_CLT_DEINIT
PJSIP_AUTH_CLT_DEINIT_DIR
PJSIP_AUTH_CLT_DEINIT_INCLUDE
@@ -7914,7 +7918,7 @@ if test "${WGET}" != ":" ; then
DOWNLOAD_TIMEOUT='--timeout=$1'
else if test "${CURL}" != ":" ; then
DOWNLOAD="${CURL} -O --progress-bar -w \"%{url_effective}\n\""
- DOWNLOAD_TO_STDOUT="${CURL} -L --progress-bar -w \"%{url_effective}\n\""
+ DOWNLOAD_TO_STDOUT="${CURL} -L --progress-bar"
DOWNLOAD_TIMEOUT='--max-time $(or $2,$1)'
else
# Extract the first word of "fetch", so it can be a program name with args.
@@ -9351,6 +9355,9 @@ $as_echo "#define HAVE_PJSIP_INV_SESSION_REF 1" >>confdefs.h
$as_echo "#define HAVE_PJSIP_AUTH_CLT_DEINIT 1" >>confdefs.h
+$as_echo "#define HAVE_PJSIP_EVSUB_SET_UAS_TIMEOUT 1" >>confdefs.h
+
+
@@ -11545,6 +11552,18 @@ PBX_PJSIP_AUTH_CLT_DEINIT=0
+
+PJSIP_EVSUB_SET_UAS_TIMEOUT_DESCRIP="PJSIP EVSUB Set UAS Timeout support"
+PJSIP_EVSUB_SET_UAS_TIMEOUT_OPTION=pjsip
+PJSIP_EVSUB_SET_UAS_TIMEOUT_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_EVSUB_SET_UAS_TIMEOUT=0
+
+
+
+
+
+
fi
@@ -26674,6 +26693,110 @@ _ACEOF
fi
+
+if test "x${PBX_PJSIP_EVSUB_SET_UAS_TIMEOUT}" != "x1" -a "${USE_PJSIP_EVSUB_SET_UAS_TIMEOUT}" != "no"; then
+ pbxlibdir=""
+ # if --with-PJSIP_EVSUB_SET_UAS_TIMEOUT=DIR has been specified, use it.
+ if test "x${PJSIP_EVSUB_SET_UAS_TIMEOUT_DIR}" != "x"; then
+ if test -d ${PJSIP_EVSUB_SET_UAS_TIMEOUT_DIR}/lib; then
+ pbxlibdir="-L${PJSIP_EVSUB_SET_UAS_TIMEOUT_DIR}/lib"
+ else
+ pbxlibdir="-L${PJSIP_EVSUB_SET_UAS_TIMEOUT_DIR}"
+ fi
+ fi
+ pbxfuncname="pjsip_evsub_set_uas_timeout"
+ if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers
+ AST_PJSIP_EVSUB_SET_UAS_TIMEOUT_FOUND=yes
+ else
+ ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
+ CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
+ as_ac_Lib=`$as_echo "ac_cv_lib_pjsip_${pbxfuncname}" | $as_tr_sh`
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lpjsip" >&5
+$as_echo_n "checking for ${pbxfuncname} in -lpjsip... " >&6; }
+if eval \${$as_ac_Lib+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ${pbxfuncname} ();
+int
+main ()
+{
+return ${pbxfuncname} ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ eval "$as_ac_Lib=yes"
+else
+ eval "$as_ac_Lib=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+eval ac_res=\$$as_ac_Lib
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
+ AST_PJSIP_EVSUB_SET_UAS_TIMEOUT_FOUND=yes
+else
+ AST_PJSIP_EVSUB_SET_UAS_TIMEOUT_FOUND=no
+fi
+
+ CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
+ fi
+
+ # now check for the header.
+ if test "${AST_PJSIP_EVSUB_SET_UAS_TIMEOUT_FOUND}" = "yes"; then
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB"
+ # if --with-PJSIP_EVSUB_SET_UAS_TIMEOUT=DIR has been specified, use it.
+ if test "x${PJSIP_EVSUB_SET_UAS_TIMEOUT_DIR}" != "x"; then
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_INCLUDE="-I${PJSIP_EVSUB_SET_UAS_TIMEOUT_DIR}/include"
+ fi
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_INCLUDE="${PJSIP_EVSUB_SET_UAS_TIMEOUT_INCLUDE} $PJPROJECT_CFLAGS"
+ if test "xpjsip.h" = "x" ; then # no header, assume found
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_HEADER_FOUND="1"
+ else # check for the header
+ ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
+ CPPFLAGS="${CPPFLAGS} ${PJSIP_EVSUB_SET_UAS_TIMEOUT_INCLUDE}"
+ ac_fn_c_check_header_mongrel "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
+if test "x$ac_cv_header_pjsip_h" = xyes; then :
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_HEADER_FOUND=1
+else
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_HEADER_FOUND=0
+fi
+
+
+ CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
+ fi
+ if test "x${PJSIP_EVSUB_SET_UAS_TIMEOUT_HEADER_FOUND}" = "x0" ; then
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_LIB=""
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_INCLUDE=""
+ else
+ if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library
+ PJSIP_EVSUB_SET_UAS_TIMEOUT_LIB=""
+ fi
+ PBX_PJSIP_EVSUB_SET_UAS_TIMEOUT=1
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_PJSIP_EVSUB_SET_UAS_TIMEOUT 1
+_ACEOF
+
+ fi
+ fi
+fi
+
+
fi
fi
diff --git a/configure.ac b/configure.ac
index 982412ece..fff8ecf7e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -297,7 +297,7 @@ if test "${WGET}" != ":" ; then
DOWNLOAD_TIMEOUT='--timeout=$1'
else if test "${CURL}" != ":" ; then
DOWNLOAD="${CURL} -O --progress-bar -w \"%{url_effective}\n\""
- DOWNLOAD_TO_STDOUT="${CURL} -L --progress-bar -w \"%{url_effective}\n\""
+ DOWNLOAD_TO_STDOUT="${CURL} -L --progress-bar"
DOWNLOAD_TIMEOUT='--max-time $(or $2,$1)'
else
AC_PATH_PROG([FETCH], [fetch], [:])
@@ -518,6 +518,7 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TLS_TRANSPORT_PROTO], [PJSIP TLS Transport pro
AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EVSUB_GRP_LOCK], [PJSIP EVSUB Group Lock support], [PJPROJECT], [pjsip])
AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_INV_SESSION_REF], [PJSIP INVITE Session Reference Count support], [PJPROJECT], [pjsip])
AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_AUTH_CLT_DEINIT], [pjsip_auth_clt_deinit support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EVSUB_SET_UAS_TIMEOUT], [PJSIP EVSUB Set UAS Timeout support], [PJPROJECT], [pjsip])
fi
AST_EXT_LIB_SETUP([POPT], [popt], [popt])
@@ -2242,6 +2243,7 @@ if test "$USE_PJPROJECT" != "no" ; then
AST_EXT_LIB_CHECK([PJSIP_EVSUB_GRP_LOCK], [pjsip], [pjsip_evsub_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
AST_EXT_LIB_CHECK([PJSIP_INV_SESSION_REF], [pjsip], [pjsip_inv_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
AST_EXT_LIB_CHECK([PJSIP_AUTH_CLT_DEINIT], [pjsip], [pjsip_auth_clt_deinit], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+ AST_EXT_LIB_CHECK([PJSIP_EVSUB_SET_UAS_TIMEOUT], [pjsip], [pjsip_evsub_set_uas_timeout], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
fi
fi
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index 34684920d..eecd957fa 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -596,6 +596,10 @@
/* Define to 1 if PJPROJECT has the PJSIP EVSUB Group Lock support feature. */
#undef HAVE_PJSIP_EVSUB_GRP_LOCK
+/* Define to 1 if PJPROJECT has the PJSIP EVSUB Set UAS Timeout support
+ feature. */
+#undef HAVE_PJSIP_EVSUB_SET_UAS_TIMEOUT
+
/* Define to 1 if PJPROJECT has the PJSIP External Resolver Support feature.
*/
#undef HAVE_PJSIP_EXTERNAL_RESOLVER
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index e5f792f1f..4170a8af4 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -884,6 +884,10 @@ enum {
* world
*/
AST_CHAN_TP_INTERNAL = (1 << 2),
+ /*!
+ * \brief Channels with this particular technology support multiple simultaneous streams
+ */
+ AST_CHAN_TP_MULTISTREAM = (1 << 3),
};
/*! \brief ast_channel flags */
@@ -4734,4 +4738,34 @@ enum ast_channel_error ast_channel_errno(void);
*/
int ast_channel_get_intercept_mode(void);
+/*!
+ * \brief Retrieve the topology of streams on a channel
+ *
+ * \param chan The channel to get the stream topology of
+ *
+ * \pre chan is locked
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_stream_topology *ast_channel_get_stream_topology(
+ const struct ast_channel *chan);
+
+/*!
+ * \brief Set the topology of streams on a channel
+ *
+ * \param chan The channel to set the stream topology on
+ * \param topology The stream topology to set
+ *
+ * \pre chan is locked
+ *
+ * \note If topology is NULL a new empty topology will be created
+ * and returned.
+ *
+ * \retval non-NULL Success
+ * \retval NULL failure
+ */
+struct ast_stream_topology *ast_channel_set_stream_topology(
+ struct ast_channel *chan, struct ast_stream_topology *topology);
+
#endif /* _ASTERISK_CHANNEL_H */
diff --git a/include/asterisk/channel_internal.h b/include/asterisk/channel_internal.h
index 2316e2f24..3de2b14aa 100644
--- a/include/asterisk/channel_internal.h
+++ b/include/asterisk/channel_internal.h
@@ -27,3 +27,7 @@ int ast_channel_internal_setup_topics(struct ast_channel *chan);
void ast_channel_internal_errno_set(enum ast_channel_error error);
enum ast_channel_error ast_channel_internal_errno(void);
+void ast_channel_internal_set_stream_topology(struct ast_channel *chan,
+ struct ast_stream_topology *topology);
+void ast_channel_internal_swap_stream_topology(struct ast_channel *chan1,
+ struct ast_channel *chan2);
diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h
index cffe6ea4c..edb00b9eb 100644
--- a/include/asterisk/stream.h
+++ b/include/asterisk/stream.h
@@ -84,7 +84,7 @@ enum ast_stream_state {
*
* \since 15
*/
-struct ast_stream *ast_stream_create(const char *name, enum ast_media_type type);
+struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type);
/*!
* \brief Destroy a media stream representation
@@ -93,7 +93,7 @@ struct ast_stream *ast_stream_create(const char *name, enum ast_media_type type)
*
* \since 15
*/
-void ast_stream_destroy(struct ast_stream *stream);
+void ast_stream_free(struct ast_stream *stream);
/*!
* \brief Create a deep clone of an existing stream
@@ -209,7 +209,7 @@ int ast_stream_get_position(const struct ast_stream *stream);
*
* \since 15
*/
-struct ast_stream_topology *ast_stream_topology_create(void);
+struct ast_stream_topology *ast_stream_topology_alloc(void);
/*!
* \brief Create a deep clone of an existing stream topology
@@ -233,7 +233,7 @@ struct ast_stream_topology *ast_stream_topology_clone(
*
* \since 15
*/
-void ast_stream_topology_destroy(struct ast_stream_topology *topology);
+void ast_stream_topology_free(struct ast_stream_topology *topology);
/*!
* \brief Append a stream to the topology
@@ -316,4 +316,19 @@ int ast_stream_topology_set_stream(struct ast_stream_topology *topology,
struct ast_stream_topology *ast_stream_topology_create_from_format_cap(
struct ast_format_cap *cap);
+/*!
+ * \brief Gets the first stream of a specific type from the topology
+ *
+ * \param topology The topology of streams
+ * \param type The media type
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \since 15
+ */
+struct ast_stream *ast_stream_topology_get_first_stream_by_type(
+ const struct ast_stream_topology *topology,
+ enum ast_media_type type);
+
#endif /* _AST_STREAM_H */
diff --git a/main/Makefile b/main/Makefile
index 4d1b2c41b..331da845c 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -355,7 +355,7 @@ else # Darwin
endif
endif
ifneq ($(LDCONFIG),)
- $(LDCONFIG) $(LDCONFIG_FLAGS) "$(DESTDIR)$(ASTLIBDIR)/"
+ $(LDCONFIG)
endif
$(LN) -sf asterisk "$(DESTDIR)$(ASTSBINDIR)/rasterisk"
@@ -373,7 +373,7 @@ ifneq ($(ASTPJ_LIB).$(ASTPJ_SO_VERSION),.)
rm -f "$(DESTDIR)$(ASTLIBDIR)/$(ASTPJ_LIB)"
endif
ifneq ($(LDCONFIG),)
- $(LDCONFIG) $(LDCONFIG_FLAGS) "$(DESTDIR)$(ASTLIBDIR)/"
+ $(LDCONFIG)
endif
clean::
diff --git a/main/channel.c b/main/channel.c
index 54db47351..1e7bc563e 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -73,6 +73,7 @@
#include "asterisk/test.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/max_forwards.h"
+#include "asterisk/stream.h"
/*** DOCUMENTATION
***/
@@ -806,6 +807,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
struct ast_timer *timer;
struct timeval now;
const struct ast_channel_tech *channel_tech;
+ struct ast_stream_topology *topology;
/* If shutting down, don't allocate any new channels */
if (ast_shutting_down()) {
@@ -895,6 +897,11 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
return ast_channel_unref(tmp);
}
+ if (!(topology = ast_stream_topology_alloc())) {
+ return ast_channel_unref(tmp);
+ }
+ ast_channel_internal_set_stream_topology(tmp, topology);
+
/* Always watch the alertpipe */
ast_channel_set_fd(tmp, AST_ALERT_FD, ast_channel_internal_alert_readfd(tmp));
/* And timing pipe */
@@ -7083,6 +7090,8 @@ static void channel_do_masquerade(struct ast_channel *original, struct ast_chann
ast_channel_tech(clonechan)->type, ast_channel_name(clonechan));
}
+ ast_channel_internal_swap_stream_topology(original, clonechan);
+
/*
* Now, at this point, the "clone" channel is totally F'd up.
* We mark it as a zombie so nothing tries to touch it.
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index a0cbe8643..1934eb9a4 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -46,6 +46,7 @@
#include "asterisk/stasis_channels.h"
#include "asterisk/stasis_endpoints.h"
#include "asterisk/stringfields.h"
+#include "asterisk/stream.h"
#include "asterisk/test.h"
/*!
@@ -221,6 +222,8 @@ struct ast_channel {
struct stasis_cp_single *topics; /*!< Topic for all channel's events */
struct stasis_forward *endpoint_forward; /*!< Subscription for event forwarding to endpoint's topic */
struct stasis_forward *endpoint_cache_forward; /*!< Subscription for cache updates to endpoint's topic */
+ struct ast_stream_topology *stream_topology; /*!< Stream topology */
+ struct ast_stream *default_streams[AST_MEDIA_TYPE_END]; /*!< Default streams indexed by media type */
};
/*! \brief The monotonically increasing integer counter for channel uniqueids */
@@ -825,10 +828,57 @@ struct ast_format_cap *ast_channel_nativeformats(const struct ast_channel *chan)
{
return chan->nativeformats;
}
-void ast_channel_nativeformats_set(struct ast_channel *chan, struct ast_format_cap *value)
+
+static void channel_set_default_streams(struct ast_channel *chan)
+{
+ enum ast_media_type type;
+
+ ast_assert(chan != NULL);
+
+ for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; type++) {
+ if (chan->stream_topology) {
+ chan->default_streams[type] =
+ ast_stream_topology_get_first_stream_by_type(chan->stream_topology, type);
+ } else {
+ chan->default_streams[type] = NULL;
+ }
+ }
+}
+
+void ast_channel_internal_set_stream_topology(struct ast_channel *chan,
+ struct ast_stream_topology *topology)
+{
+ ast_stream_topology_free(chan->stream_topology);
+ chan->stream_topology = topology;
+ channel_set_default_streams(chan);
+}
+
+void ast_channel_nativeformats_set(struct ast_channel *chan,
+ struct ast_format_cap *value)
{
+ ast_assert(chan != NULL);
+
ao2_replace(chan->nativeformats, value);
+
+ /* If chan->stream_topology is NULL, the channel is being destroyed
+ * and topology is destroyed.
+ */
+ if (!chan->stream_topology) {
+ return;
+ }
+
+ if (!chan->tech || !(chan->tech->properties & AST_CHAN_TP_MULTISTREAM) || !value) {
+ struct ast_stream_topology *new_topology;
+
+ if (!value) {
+ new_topology = ast_stream_topology_alloc();
+ } else {
+ new_topology = ast_stream_topology_create_from_format_cap(value);
+ }
+ ast_channel_internal_set_stream_topology(chan, new_topology);
+ }
}
+
struct ast_framehook_list *ast_channel_framehooks(const struct ast_channel *chan)
{
return chan->framehooks;
@@ -1637,6 +1687,8 @@ void ast_channel_internal_cleanup(struct ast_channel *chan)
stasis_cp_single_unsubscribe(chan->topics);
chan->topics = NULL;
+
+ ast_channel_internal_set_stream_topology(chan, NULL);
}
void ast_channel_internal_finalize(struct ast_channel *chan)
@@ -1729,3 +1781,52 @@ enum ast_channel_error ast_channel_internal_errno(void)
return *error_code;
}
+
+struct ast_stream_topology *ast_channel_get_stream_topology(
+ const struct ast_channel *chan)
+{
+ ast_assert(chan != NULL);
+
+ return chan->stream_topology;
+}
+
+struct ast_stream_topology *ast_channel_set_stream_topology(struct ast_channel *chan,
+ struct ast_stream_topology *topology)
+{
+ struct ast_stream_topology *new_topology;
+
+ ast_assert(chan != NULL);
+
+ /* A non-MULTISTREAM channel can't manipulate topology directly */
+ ast_assert(chan->tech != NULL && (chan->tech->properties & AST_CHAN_TP_MULTISTREAM));
+
+ /* Unless the channel is being destroyed, we always want a topology on
+ * it even if its empty.
+ */
+ if (!topology) {
+ new_topology = ast_stream_topology_alloc();
+ } else {
+ new_topology = topology;
+ }
+
+ if (new_topology) {
+ ast_channel_internal_set_stream_topology(chan, new_topology);
+ }
+
+ return new_topology;
+}
+
+void ast_channel_internal_swap_stream_topology(struct ast_channel *chan1,
+ struct ast_channel *chan2)
+{
+ struct ast_stream_topology *tmp_topology;
+
+ ast_assert(chan1 != NULL && chan2 != NULL);
+
+ tmp_topology = chan1->stream_topology;
+ chan1->stream_topology = chan2->stream_topology;
+ chan2->stream_topology = tmp_topology;
+
+ channel_set_default_streams(chan1);
+ channel_set_default_streams(chan2);
+}
diff --git a/main/http.c b/main/http.c
index 5f57b1eb0..0db6ee7b6 100644
--- a/main/http.c
+++ b/main/http.c
@@ -2056,22 +2056,20 @@ static int __ast_http_load(int reload)
http_tls_was_enabled = (reload && http_tls_cfg.enabled);
http_tls_cfg.enabled = 0;
- if (http_tls_cfg.certfile) {
- ast_free(http_tls_cfg.certfile);
- }
+
+ ast_free(http_tls_cfg.certfile);
http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
- if (http_tls_cfg.pvtfile) {
- ast_free(http_tls_cfg.pvtfile);
- }
+ ast_free(http_tls_cfg.capath);
+ http_tls_cfg.capath = ast_strdup("");
+
+ ast_free(http_tls_cfg.pvtfile);
http_tls_cfg.pvtfile = ast_strdup("");
/* Apply modern intermediate settings according to the Mozilla OpSec team as of July 30th, 2015 but disable TLSv1 */
ast_set_flag(&http_tls_cfg.flags, AST_SSL_DISABLE_TLSV1 | AST_SSL_SERVER_CIPHER_ORDER);
- if (http_tls_cfg.cipher) {
- ast_free(http_tls_cfg.cipher);
- }
+ ast_free(http_tls_cfg.cipher);
http_tls_cfg.cipher = ast_strdup("ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA");
AST_RWLIST_WRLOCK(&uri_redirects);
@@ -2285,6 +2283,7 @@ static void http_shutdown(void)
ast_tcptls_server_stop(&https_desc);
}
ast_free(http_tls_cfg.certfile);
+ ast_free(http_tls_cfg.capath);
ast_free(http_tls_cfg.pvtfile);
ast_free(http_tls_cfg.cipher);
diff --git a/main/manager.c b/main/manager.c
index a25497fd3..f11c8dca4 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -6629,7 +6629,6 @@ static void *session_do(void *data)
struct ast_sockaddr ser_remote_address_tmp;
if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) {
- ast_iostream_close(ser->stream);
ast_atomic_fetchadd_int(&unauth_sessions, -1);
goto done;
}
@@ -6638,7 +6637,6 @@ static void *session_do(void *data)
session = build_mansession(&ser_remote_address_tmp);
if (session == NULL) {
- ast_iostream_close(ser->stream);
ast_atomic_fetchadd_int(&unauth_sessions, -1);
goto done;
}
diff --git a/main/stream.c b/main/stream.c
index 24844c4ab..aacd33f17 100644
--- a/main/stream.c
+++ b/main/stream.c
@@ -69,7 +69,7 @@ struct ast_stream_topology {
AST_VECTOR(, struct ast_stream *) streams;
};
-struct ast_stream *ast_stream_create(const char *name, enum ast_media_type type)
+struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type)
{
struct ast_stream *stream;
@@ -108,7 +108,7 @@ struct ast_stream *ast_stream_clone(const struct ast_stream *stream)
return new_stream;
}
-void ast_stream_destroy(struct ast_stream *stream)
+void ast_stream_free(struct ast_stream *stream)
{
if (!stream) {
return;
@@ -176,7 +176,7 @@ int ast_stream_get_position(const struct ast_stream *stream)
}
#define TOPOLOGY_INITIAL_STREAM_COUNT 2
-struct ast_stream_topology *ast_stream_topology_create(void)
+struct ast_stream_topology *ast_stream_topology_alloc(void)
{
struct ast_stream_topology *topology;
@@ -201,7 +201,7 @@ struct ast_stream_topology *ast_stream_topology_clone(
ast_assert(topology != NULL);
- new_topology = ast_stream_topology_create();
+ new_topology = ast_stream_topology_alloc();
if (!new_topology) {
return NULL;
}
@@ -211,8 +211,8 @@ struct ast_stream_topology *ast_stream_topology_clone(
ast_stream_clone(AST_VECTOR_GET(&topology->streams, i));
if (!stream || AST_VECTOR_APPEND(&new_topology->streams, stream)) {
- ast_stream_destroy(stream);
- ast_stream_topology_destroy(new_topology);
+ ast_stream_free(stream);
+ ast_stream_topology_free(new_topology);
return NULL;
}
}
@@ -220,13 +220,13 @@ struct ast_stream_topology *ast_stream_topology_clone(
return new_topology;
}
-void ast_stream_topology_destroy(struct ast_stream_topology *topology)
+void ast_stream_topology_free(struct ast_stream_topology *topology)
{
if (!topology) {
return;
}
- AST_VECTOR_CALLBACK_VOID(&topology->streams, ast_stream_destroy);
+ AST_VECTOR_CALLBACK_VOID(&topology->streams, ast_stream_free);
AST_VECTOR_FREE(&topology->streams);
ast_free(topology);
}
@@ -272,7 +272,7 @@ int ast_stream_topology_set_stream(struct ast_stream_topology *topology,
if (position < AST_VECTOR_SIZE(&topology->streams)) {
existing_stream = AST_VECTOR_GET(&topology->streams, position);
- ast_stream_destroy(existing_stream);
+ ast_stream_free(existing_stream);
}
stream->position = position;
@@ -293,7 +293,7 @@ struct ast_stream_topology *ast_stream_topology_create_from_format_cap(
ast_assert(cap != NULL);
- topology = ast_stream_topology_create();
+ topology = ast_stream_topology_alloc();
if (!topology) {
return NULL;
}
@@ -308,32 +308,51 @@ struct ast_stream_topology *ast_stream_topology_create_from_format_cap(
new_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
if (!new_cap) {
- ast_stream_topology_destroy(topology);
+ ast_stream_topology_free(topology);
return NULL;
}
ast_format_cap_set_framing(new_cap, ast_format_cap_get_framing(cap));
if (ast_format_cap_append_from_cap(new_cap, cap, type)) {
ao2_cleanup(new_cap);
- ast_stream_topology_destroy(topology);
+ ast_stream_topology_free(topology);
return NULL;
}
- stream = ast_stream_create(ast_codec_media_type2str(type), type);
+ stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
if (!stream) {
ao2_cleanup(new_cap);
- ast_stream_topology_destroy(topology);
+ ast_stream_topology_free(topology);
return NULL;
}
/* We're transferring the initial ref so no bump needed */
stream->formats = new_cap;
stream->state = AST_STREAM_STATE_SENDRECV;
if (ast_stream_topology_append_stream(topology, stream) == -1) {
- ast_stream_destroy(stream);
- ast_stream_topology_destroy(topology);
+ ast_stream_free(stream);
+ ast_stream_topology_free(topology);
return NULL;
}
}
return topology;
}
+
+struct ast_stream *ast_stream_topology_get_first_stream_by_type(
+ const struct ast_stream_topology *topology,
+ enum ast_media_type type)
+{
+ int i;
+
+ ast_assert(topology != NULL);
+
+ for (i = 0; i < AST_VECTOR_SIZE(&topology->streams); i++) {
+ struct ast_stream *stream = AST_VECTOR_GET(&topology->streams, i);
+
+ if (stream->type == type) {
+ return stream;
+ }
+ }
+
+ return NULL;
+}
diff --git a/res/res_config_sqlite3.c b/res/res_config_sqlite3.c
index b5c70ec2d..f2a6b00db 100644
--- a/res/res_config_sqlite3.c
+++ b/res/res_config_sqlite3.c
@@ -1125,6 +1125,8 @@ static int parse_config(int reload)
if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
ast_log(LOG_ERROR, "%s config file '%s'\n",
config == CONFIG_STATUS_FILEMISSING ? "Missing" : "Invalid", config_filename);
+ ast_mutex_unlock(&config_lock);
+ return 0;
} else {
const char *cat;
struct realtime_sqlite3_db *db;
diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c
index 741244586..eabfa4ba9 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -729,8 +729,7 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
ao2_ref(unid, -1);
}
ast_sip_report_auth_success(endpoint, rdata);
- pjsip_tx_data_dec_ref(tdata);
- return PJ_FALSE;
+ break;
case AST_SIP_AUTHENTICATION_FAILED:
log_failed_request(rdata, "Failed to authenticate", 0, 0);
ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
@@ -743,6 +742,7 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
return PJ_TRUE;
}
+ pjsip_tx_data_dec_ref(tdata);
}
return PJ_FALSE;
diff --git a/res/res_pjsip/pjsip_scheduler.c b/res/res_pjsip/pjsip_scheduler.c
index 27202c602..e4459da66 100644
--- a/res/res_pjsip/pjsip_scheduler.c
+++ b/res/res_pjsip/pjsip_scheduler.c
@@ -373,7 +373,7 @@ static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
struct ast_tm tm;
char queued[32];
char last_start[32];
- char last_end[32];
+ char next_start[32];
int datelen;
struct timeval now = ast_tvnow();
const char *separator = "======================================";
@@ -397,19 +397,21 @@ static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
ast_cli(a->fd, "PJSIP Scheduled Tasks:\n\n");
- ast_cli(a->fd, " %1$-24s %2$-8s %3$-9s %4$-7s %6$-*5$s %7$-*5$s %8$-*5$s\n",
+ ast_cli(a->fd, " %1$-24s %2$-9s %3$-9s %4$-5s %6$-*5$s %7$-*5$s %8$-*5$s %9$7s\n",
"Task Name", "Interval", "Times Run", "State",
- datelen, "Queued", "Last Started", "Last Ended");
+ datelen, "Queued", "Last Started", "Next Start", "( secs)");
- ast_cli(a->fd, " %1$-24.24s %2$-8.8s %3$-9.9s %4$-7.7s %6$-*5$.*5$s %7$-*5$.*5$s %8$-*5$.*5$s\n",
+ ast_cli(a->fd, " %1$-24.24s %2$-9.9s %3$-9.9s %4$-5.5s %6$-*5$.*5$s %7$-*5$.*5$s %9$-*8$.*8$s\n",
separator, separator, separator, separator,
- datelen, separator, separator, separator);
+ datelen, separator, separator, datelen + 8, separator);
ao2_ref(tasks, +1);
ao2_rdlock(tasks);
i = ao2_iterator_init(tasks, 0);
while ((schtd = ao2_iterator_next(&i))) {
+ int next_run_sec = ast_sip_sched_task_get_next_run(schtd) / 1000;
+ struct timeval next = ast_tvadd(now, (struct timeval) {next_run_sec, 0});
ast_localtime(&schtd->when_queued, &tm, NULL);
ast_strftime(queued, sizeof(queued), log_format, &tm);
@@ -421,23 +423,17 @@ static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
ast_strftime(last_start, sizeof(last_start), log_format, &tm);
}
- if (ast_tvzero(schtd->last_end)) {
- if (ast_tvzero(schtd->last_start)) {
- strcpy(last_end, "not yet started");
- } else {
- strcpy(last_end, "running");
- }
- } else {
- ast_localtime(&schtd->last_end, &tm, NULL);
- ast_strftime(last_end, sizeof(last_end), log_format, &tm);
- }
+ ast_localtime(&next, &tm, NULL);
+ ast_strftime(next_start, sizeof(next_start), log_format, &tm);
- ast_cli(a->fd, " %1$-24.24s %2$-8.3f %3$-9d %4$-7s %6$-*5$s %7$-*5$s %8$-*5$s\n",
+ ast_cli(a->fd, " %1$-24.24s %2$9.3f %3$9d %4$-5s %6$-*5$s %7$-*5$s %8$-*5$s (%9$5d)\n",
schtd->name,
schtd->interval / 1000.0,
schtd->run_count,
- schtd->is_running ? "running" : "waiting",
- datelen, queued, last_start, last_end);
+ schtd->is_running ? "run" : "wait",
+ datelen, queued, last_start,
+ next_start,
+ next_run_sec);
ao2_cleanup(schtd);
}
ao2_iterator_destroy(&i);
diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index 9bb53bfe3..95a40829e 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -415,8 +415,9 @@ static int new_subscribe(struct ast_sip_endpoint *endpoint,
const char *context = S_OR(endpoint->subscription.context, endpoint->context);
if (!ast_exists_extension(NULL, context, resource, PRIORITY_HINT, NULL)) {
- ast_log(LOG_NOTICE, "Extension state subscription failed: Extension %s does not exist in context '%s' or has no associated hint\n",
- resource, context);
+ ast_log(LOG_NOTICE, "Endpoint '%s' state subscription failed: "
+ "Extension '%s' does not exist in context '%s' or has no associated hint\n",
+ ast_sorcery_object_get_id(endpoint), resource, context);
return 404;
}
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 42f0dc11e..709dc6640 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -392,6 +392,13 @@ enum sip_subscription_tree_state {
SIP_SUB_TREE_TERMINATED,
};
+static char *sub_tree_state_description[] = {
+ "Normal",
+ "TerminatePending",
+ "TerminateInProgress",
+ "Terminated"
+};
+
/*!
* \brief A tree of SIP subscriptions
*
@@ -428,6 +435,11 @@ struct sip_subscription_tree {
AST_LIST_ENTRY(sip_subscription_tree) next;
/*! Subscription tree state */
enum sip_subscription_tree_state state;
+ /*! On asterisk restart, this is the task data used
+ * to restart the expiration timer if pjproject isn't
+ * capable of restarting the timer.
+ */
+ struct ast_sip_sched_task *expiration_task;
};
/*!
@@ -482,6 +494,17 @@ static const char *sip_subscription_roles_map[] = {
[AST_SIP_NOTIFIER] = "Notifier"
};
+enum sip_persistence_update_type {
+ /*! Called from send request */
+ SUBSCRIPTION_PERSISTENCE_SEND_REQUEST = 0,
+ /*! Subscription created from initial client request */
+ SUBSCRIPTION_PERSISTENCE_CREATED,
+ /*! Subscription recreated by asterisk on startup */
+ SUBSCRIPTION_PERSISTENCE_RECREATED,
+ /*! Subscription created from client refresh */
+ SUBSCRIPTION_PERSISTENCE_REFRESHED,
+};
+
AST_RWLIST_HEAD_STATIC(subscriptions, sip_subscription_tree);
AST_RWLIST_HEAD_STATIC(body_generators, ast_sip_pubsub_body_generator);
@@ -560,7 +583,7 @@ static struct subscription_persistence *subscription_persistence_create(struct s
/*! \brief Function which updates persistence information of a subscription in sorcery */
static void subscription_persistence_update(struct sip_subscription_tree *sub_tree,
- pjsip_rx_data *rdata)
+ pjsip_rx_data *rdata, enum sip_persistence_update_type type)
{
pjsip_dialog *dlg;
@@ -568,6 +591,9 @@ static void subscription_persistence_update(struct sip_subscription_tree *sub_tr
return;
}
+ ast_debug(3, "Updating persistence for '%s->%s'\n",
+ ast_sorcery_object_get_id(sub_tree->endpoint), sub_tree->root->resource);
+
dlg = sub_tree->dlg;
sub_tree->persistence->cseq = dlg->local.cseq;
@@ -584,12 +610,15 @@ static void subscription_persistence_update(struct sip_subscription_tree *sub_tr
* persistence that is pulled from persistent storage, though, the rdata->pkt_info.packet will
* only ever have a single SIP message on it, and so we base persistence on that.
*/
- if (rdata->msg_info.msg_buf) {
- ast_copy_string(sub_tree->persistence->packet, rdata->msg_info.msg_buf,
- MIN(sizeof(sub_tree->persistence->packet), rdata->msg_info.len));
- } else {
- ast_copy_string(sub_tree->persistence->packet, rdata->pkt_info.packet,
- sizeof(sub_tree->persistence->packet));
+ if (type == SUBSCRIPTION_PERSISTENCE_CREATED
+ || type == SUBSCRIPTION_PERSISTENCE_RECREATED) {
+ if (rdata->msg_info.msg_buf) {
+ ast_copy_string(sub_tree->persistence->packet, rdata->msg_info.msg_buf,
+ MIN(sizeof(sub_tree->persistence->packet), rdata->msg_info.len));
+ } else {
+ ast_copy_string(sub_tree->persistence->packet, rdata->pkt_info.packet,
+ sizeof(sub_tree->persistence->packet));
+ }
}
ast_copy_string(sub_tree->persistence->src_name, rdata->pkt_info.src_name,
sizeof(sub_tree->persistence->src_name));
@@ -986,7 +1015,8 @@ static int build_resource_tree(struct ast_sip_endpoint *endpoint, const struct a
struct resources visited;
if (!has_eventlist_support || !(list = retrieve_resource_list(resource, handler->event_name))) {
- ast_debug(2, "Subscription to resource %s is not to a list\n", resource);
+ ast_debug(2, "Subscription '%s->%s' is not to a list\n",
+ ast_sorcery_object_get_id(endpoint), resource);
tree->root = tree_node_alloc(resource, NULL, 0);
if (!tree->root) {
return 500;
@@ -994,7 +1024,8 @@ static int build_resource_tree(struct ast_sip_endpoint *endpoint, const struct a
return handler->notifier->new_subscribe(endpoint, resource);
}
- ast_debug(2, "Subscription to resource %s is a list\n", resource);
+ ast_debug(2, "Subscription '%s->%s' is a list\n",
+ ast_sorcery_object_get_id(endpoint), resource);
if (AST_VECTOR_INIT(&visited, AST_VECTOR_SIZE(&list->items))) {
return 500;
}
@@ -1033,8 +1064,8 @@ static void remove_subscription(struct sip_subscription_tree *obj)
if (i == obj) {
AST_RWLIST_REMOVE_CURRENT(next);
if (i->root) {
- ast_debug(2, "Removing subscription to resource %s from list of subscriptions\n",
- ast_sip_subscription_get_resource_name(i->root));
+ ast_debug(2, "Removing subscription '%s->%s' from list of subscriptions\n",
+ ast_sorcery_object_get_id(i->endpoint), ast_sip_subscription_get_resource_name(i->root));
}
break;
}
@@ -1045,7 +1076,8 @@ static void remove_subscription(struct sip_subscription_tree *obj)
static void destroy_subscription(struct ast_sip_subscription *sub)
{
- ast_debug(3, "Destroying SIP subscription to resource %s\n", sub->resource);
+ ast_debug(3, "Destroying SIP subscription from '%s->%s'\n",
+ ast_sorcery_object_get_id(sub->tree->endpoint), sub->resource);
ast_free(sub->body_text);
AST_VECTOR_FREE(&sub->children);
@@ -1197,7 +1229,10 @@ static void subscription_tree_destructor(void *obj)
{
struct sip_subscription_tree *sub_tree = obj;
- ast_debug(3, "Destroying subscription tree %p\n", sub_tree);
+ ast_debug(3, "Destroying subscription tree %p '%s->%s'\n",
+ sub_tree,
+ sub_tree->endpoint ? ast_sorcery_object_get_id(sub_tree->endpoint) : "Unknown",
+ sub_tree->root ? sub_tree->root->resource : "Unknown");
ao2_cleanup(sub_tree->endpoint);
@@ -1213,7 +1248,8 @@ static void subscription_tree_destructor(void *obj)
void ast_sip_subscription_destroy(struct ast_sip_subscription *sub)
{
- ast_debug(3, "Removing subscription %p reference to subscription tree %p\n", sub, sub->tree);
+ ast_debug(3, "Removing subscription %p '%s->%s' reference to subscription tree %p\n",
+ sub, ast_sorcery_object_get_id(sub->tree->endpoint), sub->resource, sub->tree);
ao2_cleanup(sub->tree);
}
@@ -1320,7 +1356,6 @@ static struct sip_subscription_tree *create_subscription_tree(const struct ast_s
dlg->local.tag_hval = pj_hash_calc_tolower(0, NULL, &dlg->local.info->tag);
pjsip_ua_register_dlg(pjsip_ua_instance(), dlg);
dlg->local.cseq = persistence->cseq;
- dlg->remote.cseq = persistence->cseq;
}
pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &sub_tree->evsub);
@@ -1345,6 +1380,12 @@ static struct sip_subscription_tree *create_subscription_tree(const struct ast_s
return sub_tree;
}
+/*! Wrapper structure for initial_notify_task */
+struct initial_notify_data {
+ struct sip_subscription_tree *sub_tree;
+ int expires;
+};
+
static int initial_notify_task(void *obj);
static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state);
@@ -1433,9 +1474,12 @@ static int sub_persistence_recreate(void *obj)
}
pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr *) expires_header);
}
+
expires_header->ivalue = (ast_tvdiff_ms(persistence->expires, ast_tvnow()) / 1000);
if (expires_header->ivalue <= 0) {
/* The subscription expired since we started recreating the subscription. */
+ ast_debug(3, "Expired subscription retrived from persistent store '%s' %s\n",
+ persistence->endpoint, persistence->tag);
ast_sorcery_delete(ast_sip_get_sorcery(), persistence);
ao2_ref(endpoint, -1);
return 0;
@@ -1456,18 +1500,30 @@ static int sub_persistence_recreate(void *obj)
ast_sorcery_delete(ast_sip_get_sorcery(), persistence);
}
} else {
+ struct initial_notify_data *ind = ast_malloc(sizeof(*ind));
+
+ if (!ind) {
+ pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
+ goto error;
+ }
+
+ ind->sub_tree = ao2_bump(sub_tree);
+ ind->expires = expires_header->ivalue;
+
sub_tree->persistence = ao2_bump(persistence);
- subscription_persistence_update(sub_tree, rdata);
- if (ast_sip_push_task(sub_tree->serializer, initial_notify_task,
- ao2_bump(sub_tree))) {
+ subscription_persistence_update(sub_tree, rdata, SUBSCRIPTION_PERSISTENCE_RECREATED);
+ if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ind)) {
/* Could not send initial subscribe NOTIFY */
pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
ao2_ref(sub_tree, -1);
+ ast_free(ind);
}
}
} else {
ast_sorcery_delete(ast_sip_get_sorcery(), persistence);
}
+
+error:
resource_tree_destroy(&tree);
ao2_ref(endpoint, -1);
@@ -1485,6 +1541,8 @@ static int subscription_persistence_recreate(void *obj, void *arg, int flags)
/* If this subscription has already expired remove it */
if (ast_tvdiff_ms(persistence->expires, ast_tvnow()) <= 0) {
+ ast_debug(3, "Expired subscription retrived from persistent store '%s' %s\n",
+ persistence->endpoint, persistence->tag);
ast_sorcery_delete(ast_sip_get_sorcery(), persistence);
return 0;
}
@@ -1814,7 +1872,7 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
res = internal_pjsip_evsub_send_request(sub_tree, tdata);
- subscription_persistence_update(sub_tree, NULL);
+ subscription_persistence_update(sub_tree, NULL, SUBSCRIPTION_PERSISTENCE_SEND_REQUEST);
ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
"StateText: %s\r\n"
@@ -2713,21 +2771,45 @@ static int generate_initial_notify(struct ast_sip_subscription *sub)
return res;
}
+static int pubsub_on_refresh_timeout(void *userdata);
+
static int initial_notify_task(void * obj)
{
- struct sip_subscription_tree *sub_tree;
+ struct initial_notify_data *ind = obj;
- sub_tree = obj;
- if (generate_initial_notify(sub_tree->root)) {
- pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
+ if (generate_initial_notify(ind->sub_tree->root)) {
+ pjsip_evsub_terminate(ind->sub_tree->evsub, PJ_TRUE);
} else {
- send_notify(sub_tree, 1);
+ send_notify(ind->sub_tree, 1);
ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
"Resource: %s",
- sub_tree->root->resource);
+ ind->sub_tree->root->resource);
+ }
+
+ if (ind->expires > -1) {
+ char *name = ast_alloca(strlen("->/ ") +
+ strlen(ind->sub_tree->persistence->endpoint) +
+ strlen(ind->sub_tree->root->resource) +
+ strlen(ind->sub_tree->root->handler->event_name) +
+ ind->sub_tree->dlg->call_id->id.slen + 1);
+
+ sprintf(name, "%s->%s/%s %.*s", ind->sub_tree->persistence->endpoint,
+ ind->sub_tree->root->resource, ind->sub_tree->root->handler->event_name,
+ (int)ind->sub_tree->dlg->call_id->id.slen, ind->sub_tree->dlg->call_id->id.ptr);
+
+ ast_debug(3, "Scheduling timer: %s\n", name);
+ ind->sub_tree->expiration_task = ast_sip_schedule_task(ind->sub_tree->serializer,
+ ind->expires * 1000, pubsub_on_refresh_timeout, name,
+ ind->sub_tree, AST_SIP_SCHED_TASK_FIXED | AST_SIP_SCHED_TASK_DATA_AO2);
+ if (!ind->sub_tree->expiration_task) {
+ ast_log(LOG_ERROR, "Unable to create expiration timer of %d seconds for %s\n",
+ ind->expires, name);
+ }
}
- ao2_ref(sub_tree, -1);
+ ao2_ref(ind->sub_tree, -1);
+ ast_free(ind);
+
return 0;
}
@@ -2820,12 +2902,25 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
}
} else {
+ struct initial_notify_data *ind = ast_malloc(sizeof(*ind));
+
+ if (!ind) {
+ pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
+ resource_tree_destroy(&tree);
+ return PJ_TRUE;
+ }
+
+ ind->sub_tree = ao2_bump(sub_tree);
+ /* Since this is a normal subscribe, pjproject takes care of the timer */
+ ind->expires = -1;
+
sub_tree->persistence = subscription_persistence_create(sub_tree);
- subscription_persistence_update(sub_tree, rdata);
+ subscription_persistence_update(sub_tree, rdata, SUBSCRIPTION_PERSISTENCE_CREATED);
sip_subscription_accept(sub_tree, rdata, resp);
- if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ao2_bump(sub_tree))) {
+ if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ind)) {
pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
ao2_ref(sub_tree, -1);
+ ast_free(ind);
}
}
@@ -3360,7 +3455,7 @@ static void set_state_terminated(struct ast_sip_subscription *sub)
* send_notify ultimately calls pjsip_evsub_send_request
* pjsip_evsub_send_request calls evsub's set_state
* set_state calls pubsub_evsub_set_state
- * pubsub_evsub_set_state checks state == TERMINATE_IN_PROGRESS
+ * pubsub_on_evsub_state checks state == TERMINATE_IN_PROGRESS
* removes the subscriptions
* cleans up references to evsub
* sets state = TERMINATED
@@ -3378,6 +3473,15 @@ static void set_state_terminated(struct ast_sip_subscription *sub)
* serialized_pubsub_on_refresh_timeout starts
* See (1) Above
*
+ * * Transmission failure sending NOTIFY or error response from client
+ * pjproject transaction timer expires or non OK response
+ * pjproject locks dialog
+ * calls pubsub_on_evsub_state with event TSX_STATE
+ * pubsub_on_evsub_state checks event == TSX_STATE
+ * removes the subscriptions
+ * cleans up references to evsub
+ * sets state = TERMINATED
+ * pjproject unlocks dialog
*
* * ast_sip_subscription_notify is called
* checks state == NORMAL
@@ -3403,25 +3507,41 @@ static void set_state_terminated(struct ast_sip_subscription *sub)
*
* Although this function is called for every state change, we only care
* about the TERMINATED state, and only when we're actually processing the final
- * notify (SIP_SUB_TREE_TERMINATE_IN_PROGRESS). In this case, we do all
- * the subscription tree cleanup tasks and decrement the evsub reference.
+ * notify (SIP_SUB_TREE_TERMINATE_IN_PROGRESS) OR when a transmission failure
+ * occurs (PJSIP_EVENT_TSX_STATE). In this case, we do all the subscription tree
+ * cleanup tasks and decrement the evsub reference.
*/
static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
{
- struct sip_subscription_tree *sub_tree;
+ struct sip_subscription_tree *sub_tree =
+ pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
- ast_debug(3, "on_evsub_state called with state %s\n", pjsip_evsub_get_state_name(evsub));
+ ast_debug(3, "evsub %p state %s event %s sub_tree %p sub_tree state %s\n", evsub,
+ pjsip_evsub_get_state_name(evsub), pjsip_event_str(event->type), sub_tree,
+ (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
- if (pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
+ if (!sub_tree || pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
return;
}
- sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
- if (!sub_tree || sub_tree->state != SIP_SUB_TREE_TERMINATE_IN_PROGRESS) {
- ast_debug(1, "Possible terminate race prevented %p\n", sub_tree);
+ /* It's easier to write this as what we WANT to process, then negate it. */
+ if (!(sub_tree->state == SIP_SUB_TREE_TERMINATE_IN_PROGRESS
+ || (event->type == PJSIP_EVENT_TSX_STATE && sub_tree->state == SIP_SUB_TREE_NORMAL)
+ )) {
+ ast_debug(3, "Do nothing.\n");
return;
}
+ if (sub_tree->expiration_task) {
+ char task_name[256];
+
+ ast_sip_sched_task_get_name(sub_tree->expiration_task, task_name, sizeof(task_name));
+ ast_debug(3, "Cancelling timer: %s\n", task_name);
+ ast_sip_sched_task_cancel(sub_tree->expiration_task);
+ ao2_cleanup(sub_tree->expiration_task);
+ sub_tree->expiration_task = NULL;
+ }
+
remove_subscription(sub_tree);
pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL);
@@ -3443,16 +3563,17 @@ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
ao2_ref(sub_tree, -1);
}
-static int serialized_pubsub_on_refresh_timeout(void *userdata)
+static int pubsub_on_refresh_timeout(void *userdata)
{
struct sip_subscription_tree *sub_tree = userdata;
pjsip_dialog *dlg = sub_tree->dlg;
+ ast_debug(3, "sub_tree %p sub_tree state %s\n", sub_tree,
+ (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
+
pjsip_dlg_inc_lock(dlg);
if (sub_tree->state >= SIP_SUB_TREE_TERMINATE_IN_PROGRESS) {
- ast_debug(1, "Possible terminate race prevented %p %d\n", sub_tree->evsub, sub_tree->state);
pjsip_dlg_dec_lock(dlg);
- ao2_cleanup(sub_tree);
return 0;
}
@@ -3468,7 +3589,20 @@ static int serialized_pubsub_on_refresh_timeout(void *userdata)
"Resource: %s", sub_tree->root->resource);
pjsip_dlg_dec_lock(dlg);
+
+ return 0;
+}
+
+static int serialized_pubsub_on_refresh_timeout(void *userdata)
+{
+ struct sip_subscription_tree *sub_tree = userdata;
+
+ ast_debug(3, "sub_tree %p sub_tree state %s\n", sub_tree,
+ (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
+
+ pubsub_on_refresh_timeout(userdata);
ao2_cleanup(sub_tree);
+
return 0;
}
@@ -3487,11 +3621,23 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
struct sip_subscription_tree *sub_tree;
sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
+ ast_debug(3, "evsub %p sub_tree %p sub_tree state %s\n", evsub, sub_tree,
+ (sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
+
if (!sub_tree || sub_tree->state != SIP_SUB_TREE_NORMAL) {
- ast_debug(1, "Possible terminate race prevented %p %d\n", sub_tree, sub_tree ? sub_tree->state : -1 );
return;
}
+ if (sub_tree->expiration_task) {
+ char task_name[256];
+
+ ast_sip_sched_task_get_name(sub_tree->expiration_task, task_name, sizeof(task_name));
+ ast_debug(3, "Cancelling timer: %s\n", task_name);
+ ast_sip_sched_task_cancel(sub_tree->expiration_task);
+ ao2_cleanup(sub_tree->expiration_task);
+ sub_tree->expiration_task = NULL;
+ }
+
/* PJSIP will set the evsub's state to terminated before calling into this function
* if the Expires value of the incoming SUBSCRIBE is 0.
*/
@@ -3500,6 +3646,8 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
sub_tree->state = SIP_SUB_TREE_TERMINATE_PENDING;
}
+ subscription_persistence_update(sub_tree, rdata, SUBSCRIPTION_PERSISTENCE_REFRESHED);
+
if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_refresh_timeout, ao2_bump(sub_tree))) {
/* If we can't push the NOTIFY refreshing task...we'll just go with it. */
ast_log(LOG_ERROR, "Failed to push task to send NOTIFY.\n");
@@ -3577,7 +3725,6 @@ static void pubsub_on_server_timeout(pjsip_evsub *evsub)
sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
if (!sub_tree || sub_tree->state != SIP_SUB_TREE_NORMAL) {
- ast_debug(1, "Possible terminate race prevented %p %d\n", sub_tree, sub_tree ? sub_tree->state : -1 );
return;
}
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 3d124000a..5d7adcd43 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -2479,7 +2479,7 @@ static int rtp_address_is_ice_blacklisted(const pj_sockaddr_t *address)
static void rtp_add_candidates_to_ice(struct ast_rtp_instance *instance, struct ast_rtp *rtp, struct ast_sockaddr *addr, int port, int component,
int transport)
{
- pj_sockaddr address[16];
+ pj_sockaddr address[PJ_ICE_MAX_CAND];
unsigned int count = PJ_ARRAY_SIZE(address), pos = 0;
int basepos = -1;
diff --git a/tests/test_stream.c b/tests/test_stream.c
index 110e4e4b8..5134cfb50 100644
--- a/tests/test_stream.c
+++ b/tests/test_stream.c
@@ -37,10 +37,11 @@
#include "asterisk/format.h"
#include "asterisk/format_cap.h"
#include "asterisk/format_cache.h"
+#include "asterisk/channel.h"
AST_TEST_DEFINE(stream_create)
{
- RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_destroy);
+ RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_free);
switch (cmd) {
case TEST_INIT:
@@ -54,7 +55,7 @@ AST_TEST_DEFINE(stream_create)
break;
}
- stream = ast_stream_create("test", AST_MEDIA_TYPE_AUDIO);
+ stream = ast_stream_alloc("test", AST_MEDIA_TYPE_AUDIO);
if (!stream) {
ast_test_status_update(test, "Failed to create media stream given proper arguments\n");
return AST_TEST_FAIL;
@@ -80,7 +81,7 @@ AST_TEST_DEFINE(stream_create)
AST_TEST_DEFINE(stream_create_no_name)
{
- RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_destroy);
+ RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_free);
switch (cmd) {
case TEST_INIT:
@@ -94,7 +95,7 @@ AST_TEST_DEFINE(stream_create_no_name)
break;
}
- stream = ast_stream_create(NULL, AST_MEDIA_TYPE_AUDIO);
+ stream = ast_stream_alloc(NULL, AST_MEDIA_TYPE_AUDIO);
if (!stream) {
ast_test_status_update(test, "Failed to create media stream given proper arguments\n");
return AST_TEST_FAIL;
@@ -105,7 +106,7 @@ AST_TEST_DEFINE(stream_create_no_name)
AST_TEST_DEFINE(stream_set_type)
{
- RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_destroy);
+ RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_free);
switch (cmd) {
case TEST_INIT:
@@ -119,7 +120,7 @@ AST_TEST_DEFINE(stream_set_type)
break;
}
- stream = ast_stream_create("test", AST_MEDIA_TYPE_AUDIO);
+ stream = ast_stream_alloc("test", AST_MEDIA_TYPE_AUDIO);
if (!stream) {
ast_test_status_update(test, "Failed to create media stream given proper arguments\n");
return AST_TEST_FAIL;
@@ -142,7 +143,7 @@ AST_TEST_DEFINE(stream_set_type)
AST_TEST_DEFINE(stream_set_formats)
{
- RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_destroy);
+ RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_free);
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
switch (cmd) {
@@ -163,7 +164,7 @@ AST_TEST_DEFINE(stream_set_formats)
return AST_TEST_FAIL;
}
- stream = ast_stream_create("test", AST_MEDIA_TYPE_AUDIO);
+ stream = ast_stream_alloc("test", AST_MEDIA_TYPE_AUDIO);
if (!stream) {
ast_test_status_update(test, "Failed to create media stream given proper arguments\n");
return AST_TEST_FAIL;
@@ -188,7 +189,7 @@ AST_TEST_DEFINE(stream_set_formats)
AST_TEST_DEFINE(stream_set_state)
{
- RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_destroy);
+ RAII_VAR(struct ast_stream *, stream, NULL, ast_stream_free);
switch (cmd) {
case TEST_INIT:
@@ -202,7 +203,7 @@ AST_TEST_DEFINE(stream_set_state)
break;
}
- stream = ast_stream_create("test", AST_MEDIA_TYPE_AUDIO);
+ stream = ast_stream_alloc("test", AST_MEDIA_TYPE_AUDIO);
if (!stream) {
ast_test_status_update(test, "Failed to create media stream given proper arguments\n");
return AST_TEST_FAIL;
@@ -225,7 +226,7 @@ AST_TEST_DEFINE(stream_set_state)
AST_TEST_DEFINE(stream_topology_create)
{
- RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_destroy);
+ RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
switch (cmd) {
case TEST_INIT:
@@ -239,7 +240,7 @@ AST_TEST_DEFINE(stream_topology_create)
break;
}
- topology = ast_stream_topology_create();
+ topology = ast_stream_topology_alloc();
if (!topology) {
ast_test_status_update(test, "Failed to create media stream topology\n");
return AST_TEST_FAIL;
@@ -250,8 +251,8 @@ AST_TEST_DEFINE(stream_topology_create)
AST_TEST_DEFINE(stream_topology_clone)
{
- RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_destroy);
- RAII_VAR(struct ast_stream_topology *, cloned, NULL, ast_stream_topology_destroy);
+ RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
+ RAII_VAR(struct ast_stream_topology *, cloned, NULL, ast_stream_topology_free);
struct ast_stream *audio_stream, *video_stream;
switch (cmd) {
@@ -266,13 +267,13 @@ AST_TEST_DEFINE(stream_topology_clone)
break;
}
- topology = ast_stream_topology_create();
+ topology = ast_stream_topology_alloc();
if (!topology) {
ast_test_status_update(test, "Failed to create media stream topology\n");
return AST_TEST_FAIL;
}
- audio_stream = ast_stream_create("audio", AST_MEDIA_TYPE_AUDIO);
+ audio_stream = ast_stream_alloc("audio", AST_MEDIA_TYPE_AUDIO);
if (!audio_stream) {
ast_test_status_update(test, "Failed to create an audio stream for testing stream topology\n");
return AST_TEST_FAIL;
@@ -280,11 +281,11 @@ AST_TEST_DEFINE(stream_topology_clone)
if (ast_stream_topology_append_stream(topology, audio_stream) == -1) {
ast_test_status_update(test, "Failed to append valid audio stream to stream topology\n");
- ast_stream_destroy(audio_stream);
+ ast_stream_free(audio_stream);
return AST_TEST_FAIL;
}
- video_stream = ast_stream_create("video", AST_MEDIA_TYPE_VIDEO);
+ video_stream = ast_stream_alloc("video", AST_MEDIA_TYPE_VIDEO);
if (!video_stream) {
ast_test_status_update(test, "Failed to create a video stream for testing stream topology\n");
return AST_TEST_FAIL;
@@ -292,7 +293,7 @@ AST_TEST_DEFINE(stream_topology_clone)
if (ast_stream_topology_append_stream(topology, video_stream) == -1) {
ast_test_status_update(test, "Failed to append valid video stream to stream topology\n");
- ast_stream_destroy(video_stream);
+ ast_stream_free(video_stream);
return AST_TEST_FAIL;
}
@@ -322,7 +323,7 @@ AST_TEST_DEFINE(stream_topology_clone)
AST_TEST_DEFINE(stream_topology_append_stream)
{
- RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_destroy);
+ RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
struct ast_stream *audio_stream, *video_stream;
int position;
@@ -338,13 +339,13 @@ AST_TEST_DEFINE(stream_topology_append_stream)
break;
}
- topology = ast_stream_topology_create();
+ topology = ast_stream_topology_alloc();
if (!topology) {
ast_test_status_update(test, "Failed to create media stream topology\n");
return AST_TEST_FAIL;
}
- audio_stream = ast_stream_create("audio", AST_MEDIA_TYPE_AUDIO);
+ audio_stream = ast_stream_alloc("audio", AST_MEDIA_TYPE_AUDIO);
if (!audio_stream) {
ast_test_status_update(test, "Failed to create an audio stream for testing stream topology\n");
return AST_TEST_FAIL;
@@ -353,7 +354,7 @@ AST_TEST_DEFINE(stream_topology_append_stream)
position = ast_stream_topology_append_stream(topology, audio_stream);
if (position == -1) {
ast_test_status_update(test, "Failed to append valid audio stream to stream topology\n");
- ast_stream_destroy(audio_stream);
+ ast_stream_free(audio_stream);
return AST_TEST_FAIL;
} else if (position != 0) {
ast_test_status_update(test, "Appended audio stream to stream topology but position is '%d' instead of 0\n",
@@ -378,7 +379,7 @@ AST_TEST_DEFINE(stream_topology_append_stream)
return AST_TEST_FAIL;
}
- video_stream = ast_stream_create("video", AST_MEDIA_TYPE_VIDEO);
+ video_stream = ast_stream_alloc("video", AST_MEDIA_TYPE_VIDEO);
if (!video_stream) {
ast_test_status_update(test, "Failed to create a video stream for testing stream topology\n");
return AST_TEST_FAIL;
@@ -387,7 +388,7 @@ AST_TEST_DEFINE(stream_topology_append_stream)
position = ast_stream_topology_append_stream(topology, video_stream);
if (position == -1) {
ast_test_status_update(test, "Failed to append valid video stream to stream topology\n");
- ast_stream_destroy(video_stream);
+ ast_stream_free(video_stream);
return AST_TEST_FAIL;
} else if (position != 1) {
ast_test_status_update(test, "Appended video stream to stream topology but position is '%d' instead of 1\n",
@@ -417,7 +418,7 @@ AST_TEST_DEFINE(stream_topology_append_stream)
AST_TEST_DEFINE(stream_topology_set_stream)
{
- RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_destroy);
+ RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
struct ast_stream *audio_stream, *video_stream;
switch (cmd) {
@@ -432,13 +433,13 @@ AST_TEST_DEFINE(stream_topology_set_stream)
break;
}
- topology = ast_stream_topology_create();
+ topology = ast_stream_topology_alloc();
if (!topology) {
ast_test_status_update(test, "Failed to create media stream topology\n");
return AST_TEST_FAIL;
}
- audio_stream = ast_stream_create("audio", AST_MEDIA_TYPE_AUDIO);
+ audio_stream = ast_stream_alloc("audio", AST_MEDIA_TYPE_AUDIO);
if (!audio_stream) {
ast_test_status_update(test, "Failed to create an audio stream for testing stream topology\n");
return AST_TEST_FAIL;
@@ -446,7 +447,7 @@ AST_TEST_DEFINE(stream_topology_set_stream)
if (ast_stream_topology_set_stream(topology, 0, audio_stream)) {
ast_test_status_update(test, "Failed to set an audio stream to a position where it is permitted\n");
- ast_stream_destroy(audio_stream);
+ ast_stream_free(audio_stream);
return AST_TEST_FAIL;
}
@@ -467,7 +468,7 @@ AST_TEST_DEFINE(stream_topology_set_stream)
return AST_TEST_FAIL;
}
- video_stream = ast_stream_create("video", AST_MEDIA_TYPE_VIDEO);
+ video_stream = ast_stream_alloc("video", AST_MEDIA_TYPE_VIDEO);
if (!video_stream) {
ast_test_status_update(test, "Failed to create a video stream for testing stream topology\n");
return AST_TEST_FAIL;
@@ -475,7 +476,7 @@ AST_TEST_DEFINE(stream_topology_set_stream)
if (ast_stream_topology_set_stream(topology, 0, video_stream)) {
ast_test_status_update(test, "Failed to set a video stream to a position where it is permitted\n");
- ast_stream_destroy(video_stream);
+ ast_stream_free(video_stream);
return AST_TEST_FAIL;
}
@@ -496,7 +497,7 @@ AST_TEST_DEFINE(stream_topology_set_stream)
return AST_TEST_FAIL;
}
- audio_stream = ast_stream_create("audio", AST_MEDIA_TYPE_AUDIO);
+ audio_stream = ast_stream_alloc("audio", AST_MEDIA_TYPE_AUDIO);
if (!audio_stream) {
ast_test_status_update(test, "Failed to create an audio stream for testing stream topology\n");
return AST_TEST_FAIL;
@@ -504,7 +505,7 @@ AST_TEST_DEFINE(stream_topology_set_stream)
if (ast_stream_topology_set_stream(topology, 1, audio_stream)) {
ast_test_status_update(test, "Failed to set an audio stream to a position where it is permitted\n");
- ast_stream_destroy(audio_stream);
+ ast_stream_free(audio_stream);
return AST_TEST_FAIL;
}
@@ -530,7 +531,7 @@ AST_TEST_DEFINE(stream_topology_set_stream)
AST_TEST_DEFINE(stream_topology_create_from_format_cap)
{
- RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_destroy);
+ RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
switch (cmd) {
@@ -579,7 +580,7 @@ AST_TEST_DEFINE(stream_topology_create_from_format_cap)
return AST_TEST_FAIL;
}
- ast_stream_topology_destroy(topology);
+ ast_stream_topology_free(topology);
topology = NULL;
ast_format_cap_append(caps, ast_format_h264, 0);
@@ -611,6 +612,247 @@ AST_TEST_DEFINE(stream_topology_create_from_format_cap)
return AST_TEST_PASS;
}
+AST_TEST_DEFINE(stream_topology_get_first_stream_by_type)
+{
+ RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
+ struct ast_stream *first_stream, *second_stream, *third_stream, *fourth_stream;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "stream_topology_get_first_stream_by_type";
+ info->category = "/main/stream/";
+ info->summary = "stream topology getting first stream by type unit test";
+ info->description =
+ "Test that getting the first stream by type from a topology actually returns the first stream";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ topology = ast_stream_topology_alloc();
+ if (!topology) {
+ ast_test_status_update(test, "Failed to create media stream topology\n");
+ return AST_TEST_FAIL;
+ }
+
+ first_stream = ast_stream_alloc("audio", AST_MEDIA_TYPE_AUDIO);
+ if (!first_stream) {
+ ast_test_status_update(test, "Failed to create an audio stream for testing stream topology\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_stream_topology_append_stream(topology, first_stream) == -1) {
+ ast_test_status_update(test, "Failed to append a perfectly good stream to a topology\n");
+ ast_stream_free(first_stream);
+ return AST_TEST_FAIL;
+ }
+
+ second_stream = ast_stream_alloc("audio2", AST_MEDIA_TYPE_AUDIO);
+ if (!second_stream) {
+ ast_test_status_update(test, "Failed to create a second audio stream for testing stream topology\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_stream_topology_append_stream(topology, second_stream) == -1) {
+ ast_test_status_update(test, "Failed to append a perfectly good stream to a topology\n");
+ ast_stream_free(second_stream);
+ return AST_TEST_FAIL;
+ }
+
+ third_stream = ast_stream_alloc("video", AST_MEDIA_TYPE_VIDEO);
+ if (!third_stream) {
+ ast_test_status_update(test, "Failed to create a video stream for testing stream topology\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_stream_topology_append_stream(topology, third_stream) == -1) {
+ ast_test_status_update(test, "Failed to append a perfectly good stream to a topology\n");
+ ast_stream_free(third_stream);
+ return AST_TEST_FAIL;
+ }
+
+ fourth_stream = ast_stream_alloc("video2", AST_MEDIA_TYPE_VIDEO);
+ if (!fourth_stream) {
+ ast_test_status_update(test, "Failed to create a second video stream for testing stream topology\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_stream_topology_append_stream(topology, fourth_stream) == -1) {
+ ast_test_status_update(test, "Failed to append a perfectly good stream to a topology\n");
+ ast_stream_free(fourth_stream);
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_stream_topology_get_first_stream_by_type(topology, AST_MEDIA_TYPE_AUDIO) != first_stream) {
+ ast_test_status_update(test, "Retrieved first audio stream from topology but it is not the correct one\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_stream_topology_get_first_stream_by_type(topology, AST_MEDIA_TYPE_VIDEO) != third_stream) {
+ ast_test_status_update(test, "Retrieved first video stream from topology but it is not the correct one\n");
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
+static const struct ast_channel_tech mock_channel_tech = {
+};
+
+AST_TEST_DEFINE(stream_topology_create_from_channel_nativeformats)
+{
+ RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
+ RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
+ struct ast_channel *mock_channel;
+ enum ast_test_result_state res = AST_TEST_FAIL;
+ struct ast_str *codec_have_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
+ struct ast_str *codec_wanted_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "stream_topology_create_from_channel_nativeformats";
+ info->category = "/main/stream/";
+ info->summary = "stream topology creation from channel native formats unit test";
+ info->description =
+ "Test that creating a stream topology from the setting of channel nativeformats results in the expected streams";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!caps) {
+ ast_test_status_update(test, "Could not allocate an empty format capabilities structure\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_format_cap_append(caps, ast_format_ulaw, 0)) {
+ ast_test_status_update(test, "Failed to append a ulaw format to capabilities for channel nativeformats\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_format_cap_append(caps, ast_format_alaw, 0)) {
+ ast_test_status_update(test, "Failed to append an alaw format to capabilities for channel nativeformats\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_format_cap_append(caps, ast_format_h264, 0)) {
+ ast_test_status_update(test, "Failed to append an h264 format to capabilities for channel nativeformats\n");
+ return AST_TEST_FAIL;
+ }
+
+ mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "TestChannel");
+ if (!mock_channel) {
+ ast_test_status_update(test, "Failed to create a mock channel for testing\n");
+ return AST_TEST_FAIL;
+ }
+
+ ast_channel_tech_set(mock_channel, &mock_channel_tech);
+ ast_channel_nativeformats_set(mock_channel, caps);
+
+ if (!ast_channel_get_stream_topology(mock_channel)) {
+ ast_test_status_update(test, "Set nativeformats with ulaw, alaw, and h264 on channel but it did not create a topology\n");
+ goto end;
+ }
+
+ if (ast_stream_topology_get_count(ast_channel_get_stream_topology(mock_channel)) != 2) {
+ ast_test_status_update(test, "Set nativeformats on a channel to ulaw, alaw, and h264 and received '%d' streams instead of expected 2\n",
+ ast_stream_topology_get_count(ast_channel_get_stream_topology(mock_channel)));
+ goto end;
+ }
+
+ if (ast_stream_get_type(ast_stream_topology_get_stream(ast_channel_get_stream_topology(mock_channel), 0)) != AST_MEDIA_TYPE_AUDIO) {
+ ast_test_status_update(test, "First stream on channel is of %s when it should be audio\n",
+ ast_codec_media_type2str(ast_stream_get_type(ast_stream_topology_get_stream(ast_channel_get_stream_topology(mock_channel), 0))));
+ goto end;
+ }
+
+ ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_VIDEO);
+ if (!ast_format_cap_identical(ast_stream_get_formats(ast_stream_topology_get_stream(ast_channel_get_stream_topology(mock_channel), 0)), caps)) {
+ ast_test_status_update(test, "Formats on audio stream of channel are '%s' when they should be '%s'\n",
+ ast_format_cap_get_names(ast_stream_get_formats(ast_stream_topology_get_stream(ast_channel_get_stream_topology(mock_channel), 0)), &codec_have_buf),
+ ast_format_cap_get_names(caps, &codec_wanted_buf));
+ goto end;
+ }
+
+ if (ast_stream_get_type(ast_stream_topology_get_stream(ast_channel_get_stream_topology(mock_channel), 1)) != AST_MEDIA_TYPE_VIDEO) {
+ ast_test_status_update(test, "Second stream on channel is of type %s when it should be video\n",
+ ast_codec_media_type2str(ast_stream_get_type(ast_stream_topology_get_stream(ast_channel_get_stream_topology(mock_channel), 1))));
+ goto end;
+ }
+
+ ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_AUDIO);
+
+ if (ast_format_cap_append(caps, ast_format_h264, 0)) {
+ ast_test_status_update(test, "Failed to append h264 video codec to capabilities for capabilities comparison\n");
+ goto end;
+ }
+
+ if (!ast_format_cap_identical(ast_stream_get_formats(ast_stream_topology_get_stream(ast_channel_get_stream_topology(mock_channel), 1)), caps)) {
+ ast_test_status_update(test, "Formats on video stream of channel are '%s' when they should be '%s'\n",
+ ast_format_cap_get_names(ast_stream_get_formats(ast_stream_topology_get_stream(ast_channel_get_stream_topology(mock_channel), 1)), &codec_wanted_buf),
+ ast_format_cap_get_names(caps, &codec_wanted_buf));
+ goto end;
+ }
+
+ res = AST_TEST_PASS;
+
+end:
+ ast_channel_unlock(mock_channel);
+ ast_hangup(mock_channel);
+
+ return res;
+}
+
+static const struct ast_channel_tech mock_stream_channel_tech = {
+ .properties = AST_CHAN_TP_MULTISTREAM,
+};
+
+AST_TEST_DEFINE(stream_topology_channel_set)
+{
+ RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
+ struct ast_channel *mock_channel;
+ enum ast_test_result_state res = AST_TEST_PASS;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "stream_topology_channel_set";
+ info->category = "/main/stream/";
+ info->summary = "stream topology setting on a channel unit test";
+ info->description =
+ "Test that setting a stream topology on a channel works";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ topology = ast_stream_topology_alloc();
+ if (!topology) {
+ ast_test_status_update(test, "Failed to create media stream topology\n");
+ return AST_TEST_FAIL;
+ }
+
+ mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "TestChannel");
+ if (!mock_channel) {
+ ast_test_status_update(test, "Failed to create a mock channel for testing\n");
+ return AST_TEST_FAIL;
+ }
+
+ ast_channel_tech_set(mock_channel, &mock_stream_channel_tech);
+ ast_channel_set_stream_topology(mock_channel, topology);
+
+ if (ast_channel_get_stream_topology(mock_channel) != topology) {
+ ast_test_status_update(test, "Set an explicit stream topology on a channel but the returned one did not match it\n");
+ res = AST_TEST_FAIL;
+ }
+
+ topology = NULL;
+ ast_channel_unlock(mock_channel);
+ ast_hangup(mock_channel);
+
+ return res;
+}
+
static int unload_module(void)
{
AST_TEST_UNREGISTER(stream_create);
@@ -624,6 +866,9 @@ static int unload_module(void)
AST_TEST_UNREGISTER(stream_topology_append_stream);
AST_TEST_UNREGISTER(stream_topology_set_stream);
AST_TEST_UNREGISTER(stream_topology_create_from_format_cap);
+ AST_TEST_UNREGISTER(stream_topology_get_first_stream_by_type);
+ AST_TEST_UNREGISTER(stream_topology_create_from_channel_nativeformats);
+ AST_TEST_UNREGISTER(stream_topology_channel_set);
return 0;
}
@@ -639,6 +884,9 @@ static int load_module(void)
AST_TEST_REGISTER(stream_topology_append_stream);
AST_TEST_REGISTER(stream_topology_set_stream);
AST_TEST_REGISTER(stream_topology_create_from_format_cap);
+ AST_TEST_REGISTER(stream_topology_get_first_stream_by_type);
+ AST_TEST_REGISTER(stream_topology_create_from_channel_nativeformats);
+ AST_TEST_REGISTER(stream_topology_channel_set);
return AST_MODULE_LOAD_SUCCESS;
}
diff --git a/tests/test_voicemail_api.c b/tests/test_voicemail_api.c
index 802b6bf1b..e46757263 100644
--- a/tests/test_voicemail_api.c
+++ b/tests/test_voicemail_api.c
@@ -825,12 +825,12 @@ static struct ast_channel *test_vm_api_create_mock_channel(void)
}
ast_channel_set_writeformat(mock_channel, ast_format_gsm);
- native_formats = ast_channel_nativeformats(mock_channel);
- ast_format_cap_append(native_formats, ast_channel_writeformat(mock_channel), 0);
ast_channel_set_rawwriteformat(mock_channel, ast_format_gsm);
ast_channel_set_readformat(mock_channel, ast_format_gsm);
ast_channel_set_rawreadformat(mock_channel, ast_format_gsm);
ast_channel_tech_set(mock_channel, &mock_channel_tech);
+ native_formats = ast_channel_nativeformats(mock_channel);
+ ast_format_cap_append(native_formats, ast_channel_writeformat(mock_channel), 0);
ast_channel_unlock(mock_channel);
diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4
index 8294d8ef9..d5c85317d 100644
--- a/third-party/pjproject/configure.m4
+++ b/third-party/pjproject/configure.m4
@@ -62,6 +62,7 @@ AC_DEFUN([_PJPROJECT_CONFIGURE],
AC_DEFINE([HAVE_PJSIP_EVSUB_GRP_LOCK], 1, [Define if your system has PJSIP_EVSUB_GRP_LOCK])
AC_DEFINE([HAVE_PJSIP_INV_SESSION_REF], 1, [Define if your system has PJSIP_INV_SESSION_REF])
AC_DEFINE([HAVE_PJSIP_AUTH_CLT_DEINIT], 1, [Define if your system has pjsip_auth_clt_deinit declared.])
+ AC_DEFINE([HAVE_PJSIP_EVSUB_SET_UAS_TIMEOUT], 1, [Define if your system has pjsip_evsub_set_uas_timeout declared.])
AC_SUBST([PJPROJECT_BUNDLED])
AC_SUBST([PJPROJECT_DIR])
diff --git a/third-party/pjproject/patches/0010-evsub-Add-pjsip_evsub_set_uas_timeout.patch b/third-party/pjproject/patches/0010-evsub-Add-pjsip_evsub_set_uas_timeout.patch
new file mode 100644
index 000000000..a55aa0013
--- /dev/null
+++ b/third-party/pjproject/patches/0010-evsub-Add-pjsip_evsub_set_uas_timeout.patch
@@ -0,0 +1,84 @@
+From b7af9e6639f29feb4db6d0866c98e552b025ec96 Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@digium.com>
+Date: Mon, 6 Feb 2017 15:39:29 -0700
+Subject: [PATCH] evsub: Add pjsip_evsub_set_uas_timeout.
+
+A UAS which needs to recreate incoming subscriptions from a persistent
+store can call pjsip_dlg_create_uas_and_inc_lock and
+pjsip_evsub_create_uas as long as they've persisted the
+correct data but since the timer is triggered by an incoming subscribe,
+it's never set and the subscription never expires.
+
+* Add pjsip_evsub_set_uas_timeout which is just a wrapper around
+ evsub.c:set_timeout(sub, TIMER_TYPE_UAS_TIMEOUT, seconds)
+
+* Also, fixed copy-paste error in pjsip_sub_state_hdr_print when
+ printing retry-after parameter.
+---
+ pjsip/include/pjsip-simple/evsub.h | 14 ++++++++++++++
+ pjsip/src/pjsip-simple/evsub.c | 10 ++++++++++
+ pjsip/src/pjsip-simple/evsub_msg.c | 2 +-
+ 3 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/pjsip/include/pjsip-simple/evsub.h b/pjsip/include/pjsip-simple/evsub.h
+index 82e0a7c..45e6411 100644
+--- a/pjsip/include/pjsip-simple/evsub.h
++++ b/pjsip/include/pjsip-simple/evsub.h
+@@ -511,6 +511,20 @@ PJ_DEF(pj_status_t) pjsip_evsub_add_ref(pjsip_evsub *sub);
+ PJ_DEF(pj_status_t) pjsip_evsub_dec_ref(pjsip_evsub *sub);
+
+
++/**
++ * Sets, resets or cancels the UAS subscription timeout.
++ *
++ * If there is an existing timer, it is cancelled before any
++ * other action.
++ *
++ * A timeout of 0 is ignored except that any existing timer
++ * is cancelled.
++ *
++ * @param sub The server subscription instance.
++ * @param seconds The new timeout.
++ */
++PJ_DEF(void) pjsip_evsub_set_uas_timeout(pjsip_evsub *sub, pj_int32_t seconds);
++
+
+ PJ_END_DECL
+
+diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c
+index 3fe4b49..6918a8c 100644
+--- a/pjsip/src/pjsip-simple/evsub.c
++++ b/pjsip/src/pjsip-simple/evsub.c
+@@ -530,6 +530,16 @@ static void set_timer( pjsip_evsub *sub, int timer_id,
+
+
+ /*
++ * Set event subscription UAS timout.
++ */
++PJ_DEF(void) pjsip_evsub_set_uas_timeout(pjsip_evsub *sub, pj_int32_t seconds)
++{
++ PJ_ASSERT_RETURN(sub != NULL, PJ_EINVAL);
++ set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, seconds);
++}
++
++
++/*
+ * Destructor.
+ */
+ static void evsub_on_destroy(void *obj)
+diff --git a/pjsip/src/pjsip-simple/evsub_msg.c b/pjsip/src/pjsip-simple/evsub_msg.c
+index b44a715..b37db1c 100644
+--- a/pjsip/src/pjsip-simple/evsub_msg.c
++++ b/pjsip/src/pjsip-simple/evsub_msg.c
+@@ -179,7 +179,7 @@ static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
+ }
+ if (hdr->retry_after >= 0) {
+ pj_memcpy(p, ";retry-after=", 13);
+- p += 9;
++ p += 13;
+ printed = pj_utoa(hdr->retry_after, p);
+ p += printed;
+ }
+--
+2.9.3
+