Merge "bridge_softmix: Use removed stream spots when renegotiating."
authorJenkins2 <jenkins2@gerrit.asterisk.org>
Wed, 19 Jul 2017 15:42:51 +0000 (10:42 -0500)
committerGerrit Code Review <gerrit2@gerrit.digium.api>
Wed, 19 Jul 2017 15:42:51 +0000 (10:42 -0500)
27 files changed:
UPGRADE.txt
apps/app_confbridge.c
apps/app_queue.c
bridges/bridge_softmix.c
channels/chan_iax2.c
channels/chan_pjsip.c
configs/samples/config_test.conf.sample
configs/samples/pjsip.conf.sample
include/asterisk/channel.h
include/asterisk/config.h
include/asterisk/config_options.h
include/asterisk/res_pjsip.h
include/asterisk/res_pjsip_session.h
main/app.c
main/bridge_channel.c
main/channel.c
main/config.c
main/config_options.c
main/core_unreal.c
res/res_pjsip.c
res/res_pjsip.exports.in
res/res_pjsip/pjsip_configuration.c
res/res_pjsip_sdp_rtp.c
res/res_pjsip_session.c
res/res_rtp_asterisk.c
res/res_stasis_snoop.c
tests/test_config.c

index eb05b03..6bb7444 100644 (file)
@@ -32,6 +32,12 @@ Core:
    ARI. As a result, the 'DataGet' AMI action as well as the 'data get'
    CLI command have been removed.
 
+From 14.6.0 to 14.7.0:
+
+Core:
+ - ast_app_parse_timelen now returns an error if it encounters extra characters
+   at the end of the string to be parsed.
+
 From 14.4.0 to 14.5.0:
 
 Core:
index 0f846b6..c6372fa 100644 (file)
@@ -2145,6 +2145,7 @@ static int conf_rec_name(struct confbridge_user *user, const char *conf_name)
        }
 
        if (res == -1) {
+               ast_filedelete(user->name_rec_location, NULL);
                user->name_rec_location[0] = '\0';
                return -1;
        }
@@ -2236,6 +2237,7 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
 {
        int res = 0, volume_adjustments[2];
        int quiet = 0;
+       int async_delete_task_pushed = 0;
        char *parse;
        const char *b_profile_name = NULL;
        const char *u_profile_name = NULL;
@@ -2481,6 +2483,7 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
                async_play_sound_file(conference,
                        conf_get_sound(CONF_SOUND_HAS_LEFT, conference->b_profile.sounds), NULL);
                async_delete_name_rec(conference, user.name_rec_location);
+               async_delete_task_pushed = 1;
        }
 
        /* play the leave sound */
@@ -2509,6 +2512,9 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
        }
 
 confbridge_cleanup:
+       if (!async_delete_task_pushed && !ast_strlen_zero(user.name_rec_location)) {
+               ast_filedelete(user.name_rec_location, NULL);
+       }
        ast_bridge_features_cleanup(&user.features);
        conf_bridge_profile_destroy(&user.b_profile);
        return res;
index f7e0996..762119e 100644 (file)
                        <para>Reset the statistics for a queue.</para>
                </description>
        </manager>
+       <manager name="QueueChangePriorityCaller" language="en_US">
+               <synopsis>
+                       Change priority of a caller on queue.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="Queue" required="true">
+                               <para>The name of the queue to take action on.</para>
+                       </parameter>
+                       <parameter name="Caller" required="true">
+                               <para>The caller (channel) to change priority on queue.</para>
+                       </parameter>
+
+                       <parameter name="Priority" required="true">
+                               <para>Priority value for change for caller on queue.</para>
+                       </parameter>
+               </syntax>
+               <description>
+               </description>
+       </manager>
 
        <managerEvent language="en_US" name="QueueMemberStatus">
                <managerEventInstance class="EVENT_FLAG_AGENT">
@@ -1397,6 +1417,7 @@ static const struct autopause {
 #define        RES_OUTOFMEMORY (-2)            /*!< Out of memory */
 #define        RES_NOSUCHQUEUE (-3)            /*!< No such queue */
 #define RES_NOT_DYNAMIC (-4)           /*!< Member is not dynamic */
+#define RES_NOT_CALLER  (-5)           /*!< Caller not found */
 
 static char *app = "Queue";
 
@@ -7275,6 +7296,39 @@ static int add_to_queue(const char *queuename, const char *interface, const char
        return res;
 }
 
+
+/*! \brief Change priority caller into a queue
+ * \retval RES_NOSUCHQUEUE queue does not exist
+ * \retval RES_OKAY change priority
+ * \retval RES_NOT_CALLER queue exists but no caller
+*/
+static int change_priority_caller_on_queue(const char *queuename, const char *caller, int priority)
+{
+       struct call_queue *q;
+       struct queue_ent *qe;
+       int res = RES_NOSUCHQUEUE;
+
+       /*! \note Ensure the appropriate realtime queue is loaded.  Note that this
+        * short-circuits if the queue is already in memory. */
+       if (!(q = find_load_queue_rt_friendly(queuename))) {
+               return res;
+       }
+
+       ao2_lock(q);
+       res = RES_NOT_CALLER;
+       for (qe = q->head; qe; qe = qe->next) {
+               if (strcmp(ast_channel_name(qe->chan), caller) == 0) {
+                       ast_debug(1, "%s Caller new prioriry %d in queue %s\n",
+                                    caller, priority, queuename);
+                       qe->prio = priority;
+                       res = RES_OKAY;
+               }
+       }
+       ao2_unlock(q);
+       return res;
+}
+
+
 static int publish_queue_member_pause(struct call_queue *q, struct member *member, const char *reason)
 {
        struct ast_json *json_blob = queue_member_blob_create(q, member);
@@ -10239,6 +10293,50 @@ static int manager_queue_member_penalty(struct mansession *s, const struct messa
        return 0;
 }
 
+static int manager_change_priority_caller_on_queue(struct mansession *s, const struct message *m)
+{
+       const char *queuename, *caller, *priority_s;
+       int priority = 0;
+
+       queuename = astman_get_header(m, "Queue");
+       caller = astman_get_header(m, "Caller");
+       priority_s = astman_get_header(m, "Priority");
+
+       if (ast_strlen_zero(queuename)) {
+               astman_send_error(s, m, "'Queue' not specified.");
+               return 0;
+       }
+
+       if (ast_strlen_zero(caller)) {
+               astman_send_error(s, m, "'Caller' not specified.");
+               return 0;
+       }
+
+       if (ast_strlen_zero(priority_s)) {
+               astman_send_error(s, m, "'Priority' not specified.");
+               return 0;
+       } else if (sscanf(priority_s, "%30d", &priority) != 1) {
+               astman_send_error(s, m, "'Priority' need integer.");
+               return 0;
+       }
+
+       switch (change_priority_caller_on_queue(queuename, caller, priority)) {
+       case RES_OKAY:
+               astman_send_ack(s, m, "Priority change for caller on queue");
+               break;
+       case RES_NOSUCHQUEUE:
+               astman_send_error(s, m, "Unable to change priority caller on queue: No such queue");
+               break;
+       case RES_NOT_CALLER:
+               astman_send_error(s, m, "Unable to change priority caller on queue: No such caller");
+               break;
+       }
+
+       return 0;
+}
+
+
+
 static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        const char *queuename, *interface, *membername = NULL, *state_interface = NULL;
@@ -10426,6 +10524,57 @@ static char *handle_queue_remove_member(struct ast_cli_entry *e, int cmd, struct
        return res;
 }
 
+
+
+static char *handle_queue_change_priority_caller(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       const char *queuename, *caller;
+       int priority;
+       char *res = CLI_FAILURE;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "queue priority caller";
+               e->usage =
+                       "Usage: queue priority caller <channel> on <queue> to <priority>\n"
+                       "       Change the priority of a channel on a queue.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != 8) {
+               return CLI_SHOWUSAGE;
+       } else if (strcmp(a->argv[4], "on")) {
+               return CLI_SHOWUSAGE;
+       } else if (strcmp(a->argv[6], "to")) {
+               return CLI_SHOWUSAGE;
+       } else if (sscanf(a->argv[7], "%30d", &priority) != 1) {
+               ast_log (LOG_ERROR, "<priority> parameter must be an integer.\n");
+               return CLI_SHOWUSAGE;
+       }
+
+       caller = a->argv[3];
+       queuename = a->argv[5];
+
+       switch (change_priority_caller_on_queue(queuename, caller, priority)) {
+       case RES_OKAY:
+               res = CLI_SUCCESS;
+               break;
+       case RES_NOSUCHQUEUE:
+               ast_cli(a->fd, "Unable change priority caller %s on queue '%s': No such queue\n", caller, queuename);
+               break;
+       case RES_NOT_CALLER:
+               ast_cli(a->fd, "Unable to change priority caller '%s' on queue '%s': Not there\n", caller, queuename);
+
+               break;
+       }
+
+       return res;
+}
+
+
+
 static char *complete_queue_pause_member(const char *line, const char *word, int pos, int state)
 {
        /* 0 - queue; 1 - pause; 2 - member; 3 - <interface>; 4 - queue; 5 - <queue>; 6 - reason; 7 - <reason> */
@@ -10871,6 +11020,7 @@ static struct ast_cli_entry cli_queue[] = {
        AST_CLI_DEFINE(handle_queue_set_member_ringinuse, "Set ringinuse for a channel of a specified queue"),
        AST_CLI_DEFINE(handle_queue_reload, "Reload queues, members, queue rules, or parameters"),
        AST_CLI_DEFINE(handle_queue_reset, "Reset statistics for a queue"),
+       AST_CLI_DEFINE(handle_queue_change_priority_caller, "Change priority caller on queue"),
 };
 
 static struct stasis_message_router *agent_router;
@@ -10913,6 +11063,7 @@ static int unload_module(void)
        ast_manager_unregister("QueueReload");
        ast_manager_unregister("QueueReset");
        ast_manager_unregister("QueueMemberRingInUse");
+       ast_manager_unregister("QueueChangePriorityCaller");
        ast_unregister_application(app_aqm);
        ast_unregister_application(app_rqm);
        ast_unregister_application(app_pqm);
@@ -11026,6 +11177,7 @@ static int load_module(void)
        err |= ast_manager_register_xml("QueueRule", 0, manager_queue_rule_show);
        err |= ast_manager_register_xml("QueueReload", 0, manager_queue_reload);
        err |= ast_manager_register_xml("QueueReset", 0, manager_queue_reset);
+       err |= ast_manager_register_xml("QueueChangePriorityCaller", 0,  manager_change_priority_caller_on_queue);
        err |= ast_custom_function_register(&queuevar_function);
        err |= ast_custom_function_register(&queueexists_function);
        err |= ast_custom_function_register(&queuemembercount_function);
index 3801ccb..f3b5b2a 100644 (file)
@@ -617,7 +617,7 @@ static void sfu_topologies_on_join(struct ast_bridge_channel *joiner, struct ast
                if (participant == joiner) {
                        continue;
                }
-               participant_topology = ast_stream_topology_clone(ast_channel_get_stream_topology(joiner->chan));
+               participant_topology = ast_stream_topology_clone(ast_channel_get_stream_topology(participant->chan));
                if (!participant_topology) {
                        goto cleanup;
                }
@@ -718,14 +718,15 @@ static int remove_destination_streams(struct ast_stream_topology *dest,
 
                stream = ast_stream_topology_get_stream(source, i);
 
-               if (is_video_dest(stream, channel_name, NULL)) {
-                       continue;
-               }
-
                stream_clone = ast_stream_clone(stream, NULL);
                if (!stream_clone) {
                        continue;
                }
+
+               if (is_video_dest(stream, channel_name, NULL)) {
+                       ast_stream_set_state(stream_clone, AST_STREAM_STATE_REMOVED);
+               }
+
                if (ast_stream_topology_append_stream(dest, stream_clone) < 0) {
                        ast_stream_free(stream_clone);
                }
@@ -1981,9 +1982,9 @@ AST_TEST_DEFINE(sfu_remove_destination_streams)
                int num_streams;
                int params_index[4];
        } removal_results[] = {
-               { "PJSIP/Bob-00000001", 3, { 0, 1, 3, -1 }, },
+               { "PJSIP/Bob-00000001", 4, { 0, 1, 2, 3 }, },
                { "PJSIP/Edward-00000004", 4, { 0, 1, 2, 3 }, },
-               { "", 2, { 0, 1, -1, -1 }, },
+               { "", 4, { 0, 1, 2, 3 }, },
        };
        struct ast_stream_topology *orig = NULL;
        struct ast_stream_topology *result = NULL;
@@ -2050,6 +2051,12 @@ AST_TEST_DEFINE(sfu_remove_destination_streams)
                                        ast_format_cap_get_names(ast_stream_get_formats(actual), &actual_str));
                                goto end;
                        }
+
+                       if (is_video_dest(actual, removal_results[i].channel_name, NULL) &&
+                               ast_stream_get_state(actual) != AST_STREAM_STATE_REMOVED) {
+                               ast_test_status_update(test, "Removed stream %s does not have a state of removed\n", ast_stream_get_name(actual));
+                               goto end;
+                       }
                }
        }
 
index f40873e..5abb6c3 100644 (file)
@@ -13055,7 +13055,7 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
                ast_free_acl_list(oldacl);
        }
 
-       if (!ast_strlen_zero(peer->mailbox)) {
+       if (!ast_strlen_zero(peer->mailbox) && !peer->mwi_event_sub) {
                struct stasis_topic *mailbox_specific_topic;
 
                mailbox_specific_topic = ast_mwi_topic(peer->mailbox);
index 931b608..f009943 100644 (file)
@@ -1595,7 +1595,9 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
                                /* FIXME: Only use this for VP8. Additional work would have to be done to
                                 * fully support other video codecs */
 
-                               if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) {
+                               if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL ||
+                                       (channel->session->endpoint->media.webrtc &&
+                                        ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_h264) != AST_FORMAT_CMP_NOT_EQUAL)) {
                                        /* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the
                                         * RTP engine would provide a way to externally write/schedule RTCP
                                         * packets */
index 2fff45e..b7cb212 100644 (file)
@@ -6,6 +6,10 @@
 [global]
 intopt=-1
 uintopt=1
+timelenopt1=1ms
+timelenopt2=1s
+timelenopt3=1m
+timelenopt4=1h
 doubleopt=0.1
 sockaddropt=1.2.3.4:1234
 boolopt=true
@@ -23,6 +27,10 @@ customopt=yes
 [item]
 intopt=-1
 uintopt=1
+timelenopt1=1
+timelenopt2=1
+timelenopt3=1
+timelenopt4=1
 doubleopt=0.1
 sockaddropt=1.2.3.4:1234
 boolopt=true
index c05938e..3c3e52a 100644 (file)
                     ; (default: 1)
 ;max_video_streams= ; The maximum number of allowed negotiated video streams
                     ; (default: 1)
+;webrtc= ; When set to "yes" this also enables the following values that are needed
+         ; for webrtc: rtcp_mux, use_avpf, ice_support, and use_received_transport.
+         ; The following configuration settings also get defaulted as follows:
+         ;     media_encryption=dtls
+         ;     dtls_verify=fingerprint
+         ;     dtls_setup=actpass
+         ; A dtls_cert_file and a dtls_ca_file still need to be specified.
+         ; Default for this option is "no"
 
 ;==========================AUTH SECTION OPTIONS=========================
 ;[auth]
index 197cc99..55126b4 100644 (file)
@@ -2030,6 +2030,26 @@ struct ast_frame *ast_read_stream(struct ast_channel *chan);
 struct ast_frame *ast_read_noaudio(struct ast_channel *chan);
 
 /*!
+ * \brief Reads a frame, but does not filter to just the default streams,
+ * returning AST_FRAME_NULL frame if audio.
+ *
+ * \param chan channel to read a frame from
+ *
+ * \return Returns a frame, or NULL on error.  If it returns NULL, you
+ * best just stop reading frames and assume the channel has been
+ * disconnected.
+ *
+ * \note This function will not perform any filtering and will return
+ *       media frames from all streams on the channel. To determine which
+ *       stream a frame originated from the stream_num on it can be
+ *       examined.
+ *
+ * \note Audio is replaced with AST_FRAME_NULL to avoid
+ * transcode when the resulting audio is not necessary.
+ */
+struct ast_frame *ast_read_stream_noaudio(struct ast_channel *chan);
+
+/*!
  * \brief Write a frame to a channel
  * This function writes the given frame to the indicated channel.
  * \param chan destination channel of the frame
index f57966b..1addfa3 100644 (file)
@@ -1086,6 +1086,11 @@ enum ast_parse_flags {
        PARSE_UINT16    =       0x0005,
 #endif
 
+       /* Returns an int processed by ast_app_parse_timelen.
+        * The first argument is an enum ast_timelen value (required).
+        */
+       PARSE_TIMELEN   =       0x0006,
+
        /* Returns a struct ast_sockaddr, with optional default value
         * (passed by reference) and port handling (accept, ignore,
         * require, forbid). The format is 'ipaddress[:port]'. IPv6 address
@@ -1152,6 +1157,12 @@ enum ast_parse_flags {
  * returns 1, b unchanged
  *    ast_parse_arg("12", PARSE_UINT32|PARSE_IN_RANGE|PARSE_RANGE_DEFAULTS, &a, 1, 10);
  * returns 1, a = 10
+ *     ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE, &a, TIMELEN_SECONDS, -1000, 1000);
+ * returns 0, a = 1000
+ *     ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE, &a, TIMELEN_SECONDS, -1000, 250000);
+ * returns 0, a = 223000
+ *     ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE|PARSE_DEFAULT, &a, TIMELEN_SECONDS, 9999, -1000, 250000);
+ * returns 0, a = 9999
  *    ast_parse_arg("www.foo.biz:44", PARSE_INADDR, &sa);
  * returns 0, sa contains address and port
  *    ast_parse_arg("www.foo.biz", PARSE_INADDR|PARSE_PORT_REQUIRE, &sa);
index f2a457e..f4c3db1 100644 (file)
@@ -468,6 +468,30 @@ enum aco_option_type {
         */
        OPT_YESNO_T,
 
+       /*! \brief Type for default option handler for time length signed integers
+        *
+        * \note aco_option_register flags:
+        *   See flags available for use with the PARSE_TIMELEN type for the ast_parse_arg function
+        * aco_option_register varargs:
+        *   FLDSET macro with the field of type int
+        *   The remaining varargs for should be arguments compatible with the varargs for the
+        *   ast_parse_arg function with the PARSE_TIMELEN type and the flags passed in the
+        *   aco_option_register flags parameter.
+        *
+        * \note In most situations, it is preferable to not pass the PARSE_DEFAULT flag. If a config
+        * contains an invalid value, it is better to let the config loading fail with warnings so that
+        * the problem is fixed by the administrator.
+        *
+        * Example:
+        * struct test_item {
+        *     int timelen;
+        * };
+        * {code}
+        * aco_option_register(&cfg_info, "timelen", ACO_EXACT, my_types, "3", OPT_TIMELEN_T, PARSE_IN_RANGE, FLDSET(struct test_item, intopt), TIMELEN_MILLISECONDS, -10, 10);
+        * {endcode}
+        */
+       OPT_TIMELEN_T,
+
 };
 
 /*! \brief A callback function for handling a particular option
index d499d55..cf366cb 100644 (file)
@@ -690,6 +690,8 @@ struct ast_sip_endpoint_media_configuration {
        unsigned int max_video_streams;
        /*! Use BUNDLE */
        unsigned int bundle;
+       /*! Enable webrtc settings and defaults */
+       unsigned int webrtc;
 };
 
 /*!
@@ -2061,6 +2063,24 @@ int ast_sip_append_body(pjsip_tx_data *tdata, const char *body_text);
 void ast_copy_pj_str(char *dest, const pj_str_t *src, size_t size);
 
 /*!
+ * \brief Create and copy a pj_str_t into a standard character buffer.
+ *
+ * pj_str_t is not NULL-terminated. Any place that expects a NULL-
+ * terminated string needs to have the pj_str_t copied into a separate
+ * buffer.
+ *
+ * Copies the pj_str_t contents into a newly allocated buffer pointed to
+ * by dest. NULL-terminates the buffer.
+ *
+ * \note Caller is responsible for freeing the allocated memory.
+ *
+ * \param dest [out] The destination buffer
+ * \param src The pj_str_t to copy
+ * \retval Number of characters copied or negative value on error
+ */
+int ast_copy_pj_str2(char **dest, const pj_str_t *src);
+
+/*!
  * \brief Get the looked-up endpoint on an out-of dialog request or response
  *
  * The function may ONLY be called on out-of-dialog requests or responses. For
index eae29de..eae11af 100644 (file)
@@ -105,6 +105,8 @@ struct ast_sip_session_media {
        int bundle_group;
        /*! \brief Whether this stream is currently bundled or not */
        unsigned int bundled;
+       /*! \brief RTP/Media streams association identifier */
+       char *msid;
 };
 
 /*!
index 1eb0741..69c96c0 100644 (file)
@@ -3060,19 +3060,32 @@ int ast_app_parse_timelen(const char *timestr, int *result, enum ast_timelen uni
                case 'h':
                case 'H':
                        unit = TIMELEN_HOURS;
+                       if (u[1] != '\0') {
+                               return -1;
+                       }
                        break;
                case 's':
                case 'S':
                        unit = TIMELEN_SECONDS;
+                       if (u[1] != '\0') {
+                               return -1;
+                       }
                        break;
                case 'm':
                case 'M':
                        if (toupper(u[1]) == 'S') {
                                unit = TIMELEN_MILLISECONDS;
+                               if (u[2] != '\0') {
+                                       return -1;
+                               }
                        } else if (u[1] == '\0') {
                                unit = TIMELEN_MINUTES;
+                       } else {
+                               return -1;
                        }
                        break;
+               default:
+                       return -1;
                }
        }
 
index e8ab8a8..2e94300 100644 (file)
@@ -998,21 +998,6 @@ int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, st
                return 0;
        }
 
-       if (ast_channel_is_multistream(bridge_channel->chan) &&
-           (fr->frametype == AST_FRAME_IMAGE || fr->frametype == AST_FRAME_TEXT ||
-            fr->frametype == AST_FRAME_VIDEO || fr->frametype == AST_FRAME_VOICE)) {
-               /* Media frames need to be mapped to an appropriate write stream */
-               dup->stream_num = AST_VECTOR_GET(
-                       &bridge_channel->stream_map.to_bridge, fr->stream_num);
-               if (dup->stream_num == -1) {
-                       ast_bridge_channel_unlock(bridge_channel);
-                       bridge_frame_free(dup);
-                       return 0;
-               }
-       } else {
-               dup->stream_num = -1;
-       }
-
        AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list);
        if (ast_alertpipe_write(bridge_channel->alert_pipe)) {
                ast_log(LOG_ERROR, "We couldn't write alert pipe for %p(%s)... something is VERY wrong\n",
@@ -2455,15 +2440,26 @@ static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel)
        }
 
        if (bridge_channel->features->mute) {
-               frame = ast_read_noaudio(bridge_channel->chan);
+               frame = ast_read_stream_noaudio(bridge_channel->chan);
        } else {
-               frame = ast_read(bridge_channel->chan);
+               frame = ast_read_stream(bridge_channel->chan);
        }
 
        if (!frame) {
                ast_bridge_channel_kick(bridge_channel, 0);
                return;
        }
+
+       if (ast_channel_is_multistream(bridge_channel->chan) &&
+           (frame->frametype == AST_FRAME_IMAGE || frame->frametype == AST_FRAME_TEXT ||
+            frame->frametype == AST_FRAME_VIDEO || frame->frametype == AST_FRAME_VOICE)) {
+               /* Media frames need to be mapped to an appropriate write stream */
+               frame->stream_num = AST_VECTOR_GET(
+                       &bridge_channel->stream_map.to_bridge, frame->stream_num);
+       } else {
+               frame->stream_num = -1;
+       }
+
        switch (frame->frametype) {
        case AST_FRAME_CONTROL:
                switch (frame->subclass.integer) {
index 811826f..23bb74f 100644 (file)
@@ -4180,6 +4180,11 @@ struct ast_frame *ast_read_noaudio(struct ast_channel *chan)
        return __ast_read(chan, 1, 1);
 }
 
+struct ast_frame *ast_read_stream_noaudio(struct ast_channel *chan)
+{
+       return __ast_read(chan, 1, 0);
+}
+
 int ast_indicate(struct ast_channel *chan, int condition)
 {
        return ast_indicate_data(chan, condition, NULL, 0);
index a3e09f6..3d8dcfb 100644 (file)
@@ -3741,6 +3741,55 @@ uint32_done:
                break;
        }
 
+       case PARSE_TIMELEN:
+       {
+               int x = 0;
+               int *result = p_result;
+               int def = result ? *result : 0;
+               int high = INT_MAX;
+               int low = INT_MIN;
+               enum ast_timelen defunit;
+
+               defunit = va_arg(ap, enum ast_timelen);
+               /* optional arguments: default value and/or (low, high) */
+               if (flags & PARSE_DEFAULT) {
+                       def = va_arg(ap, int);
+               }
+               if (flags & (PARSE_IN_RANGE | PARSE_OUT_RANGE)) {
+                       low = va_arg(ap, int);
+                       high = va_arg(ap, int);
+               }
+               if (ast_strlen_zero(arg)) {
+                       error = 1;
+                       goto timelen_done;
+               }
+               error = ast_app_parse_timelen(arg, &x, defunit);
+               if (error || x < INT_MIN || x > INT_MAX) {
+                       /* Parse error, or type out of int bounds */
+                       error = 1;
+                       goto timelen_done;
+               }
+               error = (x < low) || (x > high);
+               if (flags & PARSE_RANGE_DEFAULTS) {
+                       if (x < low) {
+                               def = low;
+                       } else if (x > high) {
+                               def = high;
+                       }
+               }
+               if (flags & PARSE_OUT_RANGE) {
+                       error = !error;
+               }
+timelen_done:
+               if (result) {
+                       *result  = error ? def : x;
+               }
+
+               ast_debug(3, "extract timelen from [%s] in [%d, %d] gives [%d](%d)\n",
+                               arg, low, high, result ? *result : x, error);
+               break;
+       }
+
        case PARSE_DOUBLE:
        {
                double *result = p_result;
index c807779..8eacbda 100644 (file)
@@ -34,6 +34,7 @@
 #include "asterisk/config_options.h"
 #include "asterisk/stringfields.h"
 #include "asterisk/acl.h"
+#include "asterisk/app.h"
 #include "asterisk/frame.h"
 #include "asterisk/xmldoc.h"
 #include "asterisk/cli.h"
@@ -118,6 +119,7 @@ static void config_option_destroy(void *obj)
 
 static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
 static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int timelen_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
 static int double_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
 static int sockaddr_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
 static int stringfield_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
@@ -151,6 +153,7 @@ static aco_option_handler ast_config_option_default_handler(enum aco_option_type
        case OPT_SOCKADDR_T: return sockaddr_handler_fn;
        case OPT_STRINGFIELD_T: return stringfield_handler_fn;
        case OPT_UINT_T: return uint_handler_fn;
+       case OPT_TIMELEN_T: return timelen_handler_fn;
 
        case OPT_CUSTOM_T: return NULL;
        }
@@ -1378,6 +1381,39 @@ static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *va
        return res;
 }
 
+/*! \brief Default option handler for timelen signed integers
+ * \note For a description of the opt->flags and opt->args values, see the documentation for
+ * enum aco_option_type in config_options.h
+ */
+static int timelen_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       int *field = (int *)(obj + opt->args[0]);
+       unsigned int flags = PARSE_TIMELEN | opt->flags;
+       int res = 0;
+       if (opt->flags & PARSE_IN_RANGE) {
+               if (opt->flags & PARSE_DEFAULT) {
+                       res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2], (int) opt->args[3], opt->args[4]);
+               } else {
+                       res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2], (int) opt->args[3]);
+               }
+               if (res) {
+                       if (opt->flags & PARSE_RANGE_DEFAULTS) {
+                               ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[2], (int) opt->args[3]);
+                               res = 0;
+                       } else if (opt->flags & PARSE_DEFAULT) {
+                               ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
+                               res = 0;
+                       }
+               }
+       } else if ((opt->flags & PARSE_DEFAULT) && ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2])) {
+               ast_log(LOG_WARNING, "Attempted to set %s=%s, but set it to %d instead due to default)\n", var->name, var->value, *field);
+       } else {
+               res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1]);
+       }
+
+       return res;
+}
+
 /*! \brief Default option handler for doubles
  * \note For a description of the opt->flags and opt->args values, see the documentation for
  * enum aco_option_type in config_options.h
index 5da7408..3db6a4d 100644 (file)
@@ -323,6 +323,19 @@ int ast_unreal_write(struct ast_channel *ast, struct ast_frame *f)
                return -1;
        }
 
+       /* If we are told to write a frame with a type that has no corresponding
+        * stream on the channel then drop it.
+        */
+       if (f->frametype == AST_FRAME_VOICE) {
+               if (!ast_channel_get_default_stream(ast, AST_MEDIA_TYPE_AUDIO)) {
+                       return 0;
+               }
+       } else if (f->frametype == AST_FRAME_VIDEO) {
+               if (!ast_channel_get_default_stream(ast, AST_MEDIA_TYPE_VIDEO)) {
+                       return 0;
+               }
+       }
+
        /* Just queue for delivery to the other side */
        ao2_ref(p, 1);
        ao2_lock(p);
index ee5c5fe..0211211 100644 (file)
                                                underlying transport. Note that enabling bundle will also enable the rtcp_mux option.
                                        </para></description>
                                </configOption>
+                               <configOption name="webrtc" default="no">
+                                       <synopsis>Defaults and enables some options that are relevant to WebRTC</synopsis>
+                                       <description><para>
+                                               When set to "yes" this also enables the following values that are needed in
+                                               order for basic WebRTC support to work: rtcp_mux, use_avpf, ice_support, and
+                                               use_received_transport. The following configuration settings also get defaulted
+                                               as follows:</para>
+                                               <para>media_encryption=dtls</para>
+                                               <para>dtls_verify=fingerprint</para>
+                                               <para>dtls_setup=actpass</para>
+                                       </description>
+                               </configOption>
                        </configObject>
                        <configObject name="auth">
                                <synopsis>Authentication type</synopsis>
@@ -4244,6 +4256,18 @@ void ast_copy_pj_str(char *dest, const pj_str_t *src, size_t size)
        dest[chars_to_copy] = '\0';
 }
 
+int ast_copy_pj_str2(char **dest, const pj_str_t *src)
+{
+       int res = ast_asprintf(dest, "%.*s", (int)pj_strlen(src), pj_strbuf(src));
+
+       if (res < 0) {
+               *dest = NULL;
+       }
+
+       return res;
+}
+
+
 int ast_sip_is_content_type(pjsip_media_type *content_type, char *type, char *subtype)
 {
        pjsip_media_type compare;
index 8b62abb..4adecd4 100644 (file)
@@ -2,6 +2,7 @@
        global:
                LINKER_SYMBOL_PREFIXast_sip_*;
                LINKER_SYMBOL_PREFIXast_copy_pj_str;
+               LINKER_SYMBOL_PREFIXast_copy_pj_str2;
                LINKER_SYMBOL_PREFIXast_pjsip_rdata_get_endpoint;
        local:
                *;
index c601737..9f9de36 100644 (file)
@@ -1363,8 +1363,30 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
                return -1;
        }
 
-       if (endpoint->media.bundle) {
-               endpoint->media.rtcp_mux = 1;
+       endpoint->media.rtcp_mux |= endpoint->media.bundle;
+
+       /*
+        * If webrtc has been enabled then enable those attributes, and default
+        * some, that are needed in order for webrtc to work.
+        */
+       endpoint->media.bundle |= endpoint->media.webrtc;
+       endpoint->media.rtcp_mux |= endpoint->media.webrtc;
+       endpoint->media.rtp.use_avpf |= endpoint->media.webrtc;
+       endpoint->media.rtp.ice_support |= endpoint->media.webrtc;
+       endpoint->media.rtp.use_received_transport |= endpoint->media.webrtc;
+
+       if (endpoint->media.webrtc) {
+               endpoint->media.rtp.encryption = AST_SIP_MEDIA_ENCRYPT_DTLS;
+               endpoint->media.rtp.dtls_cfg.enabled = 1;
+               endpoint->media.rtp.dtls_cfg.default_setup = AST_RTP_DTLS_SETUP_ACTPASS;
+               endpoint->media.rtp.dtls_cfg.verify = AST_RTP_DTLS_VERIFY_FINGERPRINT;
+
+               if (ast_strlen_zero(endpoint->media.rtp.dtls_cfg.certfile) ||
+                       (ast_strlen_zero(endpoint->media.rtp.dtls_cfg.cafile))) {
+                       ast_log(LOG_ERROR, "WebRTC can't be enabled on endpoint '%s' - a DTLS cert "
+                               "or ca file has not been specified", ast_sorcery_object_get_id(endpoint));
+                       return -1;
+               }
        }
 
        return 0;
@@ -1990,6 +2012,7 @@ int ast_res_pjsip_initialize_configuration(void)
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_audio_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_audio_streams));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_video_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_video_streams));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "bundle", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.bundle));
+       ast_sorcery_object_field_register(sip_sorcery, "endpoint", "webrtc", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.webrtc));
 
        if (ast_sip_initialize_sorcery_transport()) {
                ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
index 4ec8115..a2e7f8f 100644 (file)
@@ -1025,6 +1025,65 @@ static void process_ssrc_attributes(struct ast_sip_session *session, struct ast_
        }
 }
 
+static void process_msid_attribute(struct ast_sip_session *session,
+       struct ast_sip_session_media *session_media, pjmedia_sdp_media *media)
+{
+       pjmedia_sdp_attr *attr;
+
+       if (!session->endpoint->media.webrtc) {
+               return;
+       }
+
+       attr = pjmedia_sdp_media_find_attr2(media, "msid", NULL);
+       if (attr) {
+               ast_free(session_media->msid);
+               ast_copy_pj_str2(&session_media->msid, &attr->value);
+       }
+}
+
+static void add_msid_to_stream(struct ast_sip_session *session,
+       struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
+{
+       pj_str_t stmp;
+       pjmedia_sdp_attr *attr;
+
+       if (!session->endpoint->media.webrtc) {
+               return;
+       }
+
+       if (ast_strlen_zero(session_media->msid)) {
+               char uuid1[AST_UUID_STR_LEN], uuid2[AST_UUID_STR_LEN];
+
+               if (ast_asprintf(&session_media->msid, "{%s} {%s}",
+                       ast_uuid_generate_str(uuid1, sizeof(uuid1)),
+                       ast_uuid_generate_str(uuid2, sizeof(uuid2))) < 0) {
+                       session_media->msid = NULL;
+                       return;
+               }
+       }
+
+       attr = pjmedia_sdp_attr_create(pool, "msid", pj_cstr(&stmp, session_media->msid));
+       pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
+}
+
+static void add_rtcp_fb_to_stream(struct ast_sip_session *session,
+       struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
+{
+       pj_str_t stmp;
+       pjmedia_sdp_attr *attr;
+
+       if (!session->endpoint->media.webrtc || session_media->type != AST_MEDIA_TYPE_VIDEO) {
+               return;
+       }
+
+       /*
+        * For now just automatically add it the stream even though it hasn't
+        * necessarily been negotiated.
+        */
+       attr = pjmedia_sdp_attr_create(pool, "rtcp-fb", pj_cstr(&stmp, "* ccm fir"));
+       pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
+}
+
 /*! \brief Function which negotiates an incoming media stream */
 static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
        struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,
@@ -1068,7 +1127,7 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
        }
 
        process_ssrc_attributes(session, session_media, stream);
-
+       process_msid_attribute(session, session_media, stream);
        session_media_transport = ast_sip_session_media_get_transport(session, session_media);
 
        if (session_media_transport == session_media || !session_media->bundled) {
@@ -1527,6 +1586,8 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
        }
 
        add_ssrc_to_stream(session, session_media, pool, media);
+       add_msid_to_stream(session, session_media, pool, media);
+       add_rtcp_fb_to_stream(session, session_media, pool, media);
 
        /* Add the media stream to the SDP */
        sdp->media[sdp->media_count++] = media;
index 315db6d..fe3680f 100644 (file)
@@ -395,6 +395,7 @@ static void session_media_dtor(void *obj)
        }
 
        ast_free(session_media->mid);
+       ast_free(session_media->msid);
 }
 
 struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,
@@ -3573,15 +3574,17 @@ static int add_bundle_groups(struct ast_sip_session *session, pj_pool_t *pool, p
        int index, mid_id;
        struct sip_session_media_bundle_group *bundle_group;
 
+       if (session->endpoint->media.webrtc) {
+               attr = pjmedia_sdp_attr_create(pool, "msid-semantic", pj_cstr(&stmp, "WMS *"));
+               pjmedia_sdp_attr_add(&answer->attr_count, answer->attr, attr);
+       }
+
        if (!session->endpoint->media.bundle) {
                return 0;
        }
 
        memset(bundle_groups, 0, sizeof(bundle_groups));
 
-       attr = pjmedia_sdp_attr_create(pool, "msid-semantic", pj_cstr(&stmp, "WMS *"));
-       pjmedia_sdp_attr_add(&answer->attr_count, answer->attr, attr);
-
        /* Build the bundle group layout so we can then add it to the SDP */
        for (index = 0; index < AST_VECTOR_SIZE(&session->pending_media_state->sessions); ++index) {
                struct ast_sip_session_media *session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);
index 9d3969f..a2e63ec 100644 (file)
@@ -2650,9 +2650,15 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
 
 #ifdef HAVE_PJPROJECT
        if (transport_rtp->ice) {
+               enum ast_rtp_ice_component_type component = rtcp ? AST_RTP_ICE_COMPONENT_RTCP : AST_RTP_ICE_COMPONENT_RTP;
                pj_status_t status;
                struct ice_wrap *ice;
 
+               /* If RTCP is sharing the same socket then use the same component */
+               if (rtcp && rtp->rtcp->s == rtp->s) {
+                       component = AST_RTP_ICE_COMPONENT_RTP;
+               }
+
                pj_thread_register_check();
 
                /* Release the instance lock to avoid deadlock with PJPROJECT group lock */
@@ -2661,8 +2667,7 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
                if (instance == transport) {
                        ao2_unlock(instance);
                }
-               status = pj_ice_sess_send_data(ice->real_ice,
-                       rtcp ? AST_RTP_ICE_COMPONENT_RTCP : AST_RTP_ICE_COMPONENT_RTP, temp, len);
+               status = pj_ice_sess_send_data(ice->real_ice, component, temp, len);
                ao2_ref(ice, -1);
                if (instance == transport) {
                        ao2_lock(instance);
index cd51638..f797a9b 100644 (file)
@@ -72,6 +72,8 @@ struct stasis_app_snoop {
        unsigned int whisper_active:1;
        /*! \brief Uniqueid of the channel this snoop is snooping on */
        char uniqueid[AST_MAX_UNIQUEID];
+       /*! \brief A frame of silence to use when the audiohook returns null */
+       struct ast_frame silence;
 };
 
 /*! \brief Destructor for snoop structure */
@@ -91,6 +93,11 @@ static void snoop_destroy(void *obj)
                ast_audiohook_destroy(&snoop->whisper);
        }
 
+       if (snoop->silence.data.ptr) {
+               ast_free(snoop->silence.data.ptr);
+               snoop->silence.data.ptr = NULL;
+       }
+
        ast_free(snoop->app);
 
        ast_channel_cleanup(snoop->chan);
@@ -197,7 +204,7 @@ static struct ast_frame *snoop_read(struct ast_channel *chan)
        frame = ast_audiohook_read_frame(&snoop->spy, snoop->spy_samples, snoop->spy_direction, snoop->spy_format);
        ast_audiohook_unlock(&snoop->spy);
 
-       return frame ? frame : &ast_null_frame;
+       return frame ? frame : &snoop->silence;
 }
 
 /*! \brief Callback function for hanging up a Snoop channel */
@@ -383,6 +390,19 @@ struct ast_channel *stasis_app_control_snoop(struct ast_channel *chan,
 
                snoop->spy_samples = ast_format_get_sample_rate(snoop->spy_format) / (1000 / SNOOP_INTERVAL);
                snoop->spy_active = 1;
+
+               snoop->silence.frametype = AST_FRAME_VOICE,
+               snoop->silence.datalen = snoop->spy_samples * sizeof(uint16_t),
+               snoop->silence.samples = snoop->spy_samples,
+               snoop->silence.mallocd = 0,
+               snoop->silence.offset = 0,
+               snoop->silence.src = __PRETTY_FUNCTION__,
+               snoop->silence.subclass.format = snoop->spy_format,
+               snoop->silence.data.ptr = ast_calloc(snoop->spy_samples, sizeof(uint16_t));
+               if (!snoop->silence.data.ptr) {
+                       ast_hangup(snoop->chan);
+                       return NULL;
+               }
        }
 
        /* If whispering is enabled set up the audiohook */
index 6635c6f..d737108 100644 (file)
@@ -41,6 +41,7 @@
 #include "asterisk/config_options.h"
 #include "asterisk/netsock2.h"
 #include "asterisk/acl.h"
+#include "asterisk/app.h"
 #include "asterisk/pbx.h"
 #include "asterisk/frame.h"
 #include "asterisk/utils.h"
@@ -1080,6 +1081,13 @@ enum {
                                ast_test_status_update(test, "ast_parse_arg double failed with %f != %f\n", *r, e); \
                                ret = AST_TEST_FAIL; \
                        } \
+               } else if (((flags) & PARSE_TYPE) == PARSE_TIMELEN) { \
+                       int *r = (int *) (void *) result; \
+                       int e = (int) expected_result; \
+                       if (*r != e) { \
+                               ast_test_status_update(test, "ast_parse_arg timelen failed with %d != %d\n", *r, e); \
+                               ret = AST_TEST_FAIL; \
+                       } \
                } \
        } \
        *(result) = DEFAULTVAL; \
@@ -1090,6 +1098,7 @@ AST_TEST_DEFINE(ast_parse_arg_test)
        int ret = AST_TEST_PASS;
        int32_t int32_t_val = DEFAULTVAL;
        uint32_t uint32_t_val = DEFAULTVAL;
+       int timelen_val = DEFAULTVAL;
        double double_val = DEFAULTVAL;
 
        switch (cmd) {
@@ -1222,6 +1231,60 @@ AST_TEST_DEFINE(ast_parse_arg_test)
 
        TEST_PARSE("   -123", EXPECT_FAIL, DEFAULTVAL, PARSE_UINT32, &uint32_t_val);
 
+       /* timelen testing */
+       TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+       TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+       TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+       TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+       TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+
+       TEST_PARSE("123s", EXPECT_SUCCEED, 123000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+       TEST_PARSE("-123s", EXPECT_SUCCEED, -123000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+       TEST_PARSE("1m", EXPECT_SUCCEED, 60000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+       TEST_PARSE("1", EXPECT_SUCCEED, 60000, PARSE_TIMELEN, &timelen_val, TIMELEN_MINUTES);
+       TEST_PARSE("1h", EXPECT_SUCCEED, 3600000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+       TEST_PARSE("1", EXPECT_SUCCEED, 3600000, PARSE_TIMELEN, &timelen_val, TIMELEN_HOURS);
+
+       TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+       TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+       TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+       TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+       TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+
+       TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 200);
+       TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -200, 100);
+       TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -1, 0);
+       TEST_PARSE("123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 122);
+       TEST_PARSE("-123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -122, 100);
+       TEST_PARSE("0", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 1, 100);
+       TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX);
+       TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX);
+       TEST_PARSE("123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 200);
+       TEST_PARSE("-123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -200, 100);
+       TEST_PARSE("0", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -1, 0);
+       TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 122);
+       TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -122, 100);
+       TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 1, 100);
+       TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX);
+       TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX);
+
+       TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 200);
+       TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -200, 100);
+       TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -1, 0);
+       TEST_PARSE("123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 122);
+       TEST_PARSE("-123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -122, 100);
+       TEST_PARSE("0", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 1, 100);
+       TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX);
+       TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX);
+       TEST_PARSE("123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 200);
+       TEST_PARSE("-123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -200, 100);
+       TEST_PARSE("0", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -1, 0);
+       TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 122);
+       TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -122, 100);
+       TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 1, 100);
+       TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX);
+       TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX);
+
        /* double testing */
        TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_DOUBLE, &double_val);
        TEST_PARSE("123.123", EXPECT_SUCCEED, 123.123, PARSE_DOUBLE, &double_val);
@@ -1281,6 +1344,10 @@ struct test_item {
        );
        int32_t intopt;
        uint32_t uintopt;
+       int timelenopt1;
+       int timelenopt2;
+       int timelenopt3;
+       int timelenopt4;
        unsigned int flags;
        double doubleopt;
        struct ast_sockaddr sockaddropt;
@@ -1435,6 +1502,8 @@ AST_TEST_DEFINE(config_options_test)
 #define INT_CONFIG "-1"
 #define UINT_DEFAULT "2"
 #define UINT_CONFIG "1"
+#define TIMELEN_DEFAULT "2"
+#define TIMELEN_CONFIG "1"
 #define DOUBLE_DEFAULT "1.1"
 #define DOUBLE_CONFIG "0.1"
 #define SOCKADDR_DEFAULT "4.3.2.1:4321"
@@ -1469,6 +1538,10 @@ AST_TEST_DEFINE(config_options_test)
        /* Register all options */
        aco_option_register(&cfg_info, "intopt", ACO_EXACT, config_test_conf.types, INT_DEFAULT, OPT_INT_T, 0, FLDSET(struct test_item, intopt));
        aco_option_register(&cfg_info, "uintopt", ACO_EXACT, config_test_conf.types, UINT_DEFAULT, OPT_UINT_T, 0, FLDSET(struct test_item, uintopt));
+       aco_option_register(&cfg_info, "timelenopt1", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt1), TIMELEN_MILLISECONDS);
+       aco_option_register(&cfg_info, "timelenopt2", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt2), TIMELEN_SECONDS);
+       aco_option_register(&cfg_info, "timelenopt3", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt3), TIMELEN_MINUTES);
+       aco_option_register(&cfg_info, "timelenopt4", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt4), TIMELEN_HOURS);
        aco_option_register(&cfg_info, "doubleopt", ACO_EXACT, config_test_conf.types, DOUBLE_DEFAULT, OPT_DOUBLE_T, 0, FLDSET(struct test_item, doubleopt));
        aco_option_register(&cfg_info, "sockaddropt", ACO_EXACT, config_test_conf.types, SOCKADDR_DEFAULT, OPT_SOCKADDR_T, 0, FLDSET(struct test_item, sockaddropt));
        aco_option_register(&cfg_info, "boolopt", ACO_EXACT, config_test_conf.types, BOOL_DEFAULT, OPT_BOOL_T, 1, FLDSET(struct test_item, boolopt));
@@ -1490,6 +1563,14 @@ AST_TEST_DEFINE(config_options_test)
 
        ast_parse_arg(INT_DEFAULT, PARSE_INT32, &defaults.intopt);
        ast_parse_arg(INT_CONFIG, PARSE_INT32, &configs.intopt);
+       ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt1, TIMELEN_MILLISECONDS);
+       ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt1, TIMELEN_MILLISECONDS);
+       ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt2, TIMELEN_SECONDS);
+       ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt2, TIMELEN_SECONDS);
+       ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt3, TIMELEN_MINUTES);
+       ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt3, TIMELEN_MINUTES);
+       ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt4, TIMELEN_HOURS);
+       ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt4, TIMELEN_HOURS);
        ast_parse_arg(UINT_DEFAULT, PARSE_UINT32, &defaults.uintopt);
        ast_parse_arg(UINT_CONFIG, PARSE_UINT32, &configs.uintopt);
        ast_parse_arg(DOUBLE_DEFAULT, PARSE_DOUBLE, &defaults.doubleopt);
@@ -1551,6 +1632,10 @@ AST_TEST_DEFINE(config_options_test)
 
                NOT_EQUAL_FAIL(intopt, "%d");
                NOT_EQUAL_FAIL(uintopt, "%u");
+               NOT_EQUAL_FAIL(timelenopt1, "%d");
+               NOT_EQUAL_FAIL(timelenopt2, "%d");
+               NOT_EQUAL_FAIL(timelenopt3, "%d");
+               NOT_EQUAL_FAIL(timelenopt4, "%d");
                NOT_EQUAL_FAIL(boolopt, "%d");
                NOT_EQUAL_FAIL(flags, "%u");
                NOT_EQUAL_FAIL(customopt, "%d");