Merge "Binaural synthesis (confbridge): Adds utils/conf_bridge_binaural_hrir_importer"
authorJoshua Colp <jcolp@digium.com>
Mon, 20 Feb 2017 16:24:55 +0000 (10:24 -0600)
committerGerrit Code Review <gerrit2@gerrit.digium.api>
Mon, 20 Feb 2017 16:24:55 +0000 (10:24 -0600)
23 files changed:
channels/chan_unistim.c
configure
configure.ac
include/asterisk/autoconfig.h.in
include/asterisk/channel.h
include/asterisk/channel_internal.h
include/asterisk/stream.h
main/Makefile
main/channel.c
main/channel_internal_api.c
main/http.c
main/manager.c
main/stream.c
res/res_config_sqlite3.c
res/res_pjsip/pjsip_distributor.c
res/res_pjsip/pjsip_scheduler.c
res/res_pjsip_exten_state.c
res/res_pjsip_pubsub.c
res/res_rtp_asterisk.c
tests/test_stream.c
tests/test_voicemail_api.c
third-party/pjproject/configure.m4
third-party/pjproject/patches/0010-evsub-Add-pjsip_evsub_set_uas_timeout.patch [new file with mode: 0644]

index b8ccdbb..f825221 100644 (file)
@@ -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 */
index ba8d593..42a21d7 100755 (executable)
--- 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
 
index 982412e..fff8ecf 100644 (file)
@@ -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
 
index 3468492..eecd957 100644 (file)
 /* 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
index e5f792f..4170a8a 100644 (file)
@@ -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 */
index 2316e2f..3de2b14 100644 (file)
@@ -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);
index cffe6ea..edb00b9 100644 (file)
@@ -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 */
index 4d1b2c4..331da84 100644 (file)
@@ -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::
index 54db473..1e7bc56 100644 (file)
@@ -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.
index a0cbe86..1934eb9 100644 (file)
@@ -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);
+}
index 5f57b1e..0db6ee7 100644 (file)
@@ -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);
 
index a25497f..f11c8dc 100644 (file)
@@ -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;
        }
index 24844c4..aacd33f 100644 (file)
@@ -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;
+}
index b5c70ec..f2a6b00 100644 (file)
@@ -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;
index 7412445..eabfa4b 100644 (file)
@@ -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;
index 27202c6..e4459da 100644 (file)
@@ -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);
index 9bb53bf..95a4082 100644 (file)
@@ -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;
        }
 
index 42f0dc1..709dc66 100644 (file)
@@ -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;
        }
 
index 3d12400..5d7adcd 100644 (file)
@@ -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;
 
index 110e4e4..5134cfb 100644 (file)
 #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;
 }
 
index 802b6bf..e467572 100644 (file)
@@ -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);
 
index 8294d8e..d5c8531 100644 (file)
@@ -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 (file)
index 0000000..a55aa00
--- /dev/null
@@ -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
+